fix: #927 — jwt.verify dispatches per algorithm (ES256/RS256), accepts private PEM - #1025
Merged
Merged
Conversation
…s private PEM The previous #927 fix (#936) made `jwt.verify` return the parsed claims object instead of the JSON text, but ES256 / RS256 tokens were still failing verification silently — the generic NativeModSig table routed every algorithm through `js_jwt_verify`, which was hardcoded to HS256. The token was signed correctly with ES256, then `Validation::new(Algorithm::HS256)` rejected it with `InvalidAlgorithm`, the runtime returned a null pointer, and `js_json_parse_or_null` surfaced it as `null` — breaking the shop-admin auth middleware on the very first authenticated request after a successful signup. Fix mirrors the `sign` side's algorithm-aware dispatch: - Add `js_jwt_verify_es256` / `js_jwt_verify_rs256` runtime functions in `perry-stdlib`. Both auto-derive the public key from a private PEM (PKCS#8 *or* SEC1 for EC, PKCS#8 *or* PKCS#1 for RSA) so callers can reuse the same PEM they passed to `sign` — matches Node `jsonwebtoken`'s ergonomics. Direct public-key PEMs (SPKI) also accepted unchanged. - Replace the generic NativeModSig `verify` row with `lower_jsonwebtoken_verify` in `lower_call/native.rs`. Routes on the canonical `algorithms: ['…']` array option as well as the singular `algorithm: '…'`. Falls back to HS256 when the option is absent or unparseable. Return path still pipes through `js_json_parse_or_null` so user code sees an object on success and `null` on failure (same contract as before). - Bonus: `js_jwt_sign_es256` now accepts SEC1 (`BEGIN EC PRIVATE KEY`) PEMs in addition to PKCS#8, which is what `openssl ecparam -genkey` emits by default. Symmetric with the new verify ergonomics. New `perry-stdlib` deps gated on `bundled-jsonwebtoken`: `p256` (EC public-key derivation), `rsa` (RSA public-key derivation), `spki` (PEM serialization). Verified with a 9-line standalone repro (sign + verify a token with `algorithms: ['ES256']`) — previously returned `null`, now returns the parsed claims object with `decoded.sub` accessible.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The previous #927 fix (#936) made
jwt.verifyreturn the parsed claims object instead of the JSON text, but ES256 / RS256 tokens were still failing verification silently — the genericNativeModSigtable routed every algorithm throughjs_jwt_verify, which was hardcoded to HS256. The token was signed correctly with ES256, thenValidation::new(Algorithm::HS256)rejected it withInvalidAlgorithm, the runtime returned a null pointer, andjs_json_parse_or_nullsurfaced it asnull— breaking shop-admin's auth middleware on the very first authenticated request after a successful signup.Fix mirrors the
signside's algorithm-aware dispatch:js_jwt_verify_es256/js_jwt_verify_rs256runtime functions inperry-stdlib/src/jsonwebtoken.rs. Both auto-derive the public key from a private PEM (PKCS#8 or SEC1 for EC, PKCS#8 or PKCS#1 for RSA) so callers can reuse the same PEM they passed tosign— matches Nodejsonwebtoken's ergonomics. Direct public-key PEMs (SPKI) also accepted unchanged.NativeModSigverifyrow withlower_jsonwebtoken_verifyincrates/perry-codegen/src/lower_call/native.rs. Routes on the canonicalalgorithms: ['…']array option as well as the singularalgorithm: '…'. Falls back to HS256 when the option is absent or unparseable. Return path still pipes throughjs_json_parse_or_nullso user code sees an object on success andnullon failure (same contract as before).js_jwt_sign_es256now also accepts SEC1 (-----BEGIN EC PRIVATE KEY-----) PEMs, which is whatopenssl ecparam -genkey -name prime256v1emits by default. Symmetric with the new verify ergonomics.New
perry-stdlibdeps gated onbundled-jsonwebtoken:p256(EC public-key derivation),rsa(RSA public-key derivation),spki(PEM serialization helpers).Test plan
algorithms: ['ES256']) now returns the parsed claims object —typeof decodedis"object",decoded.subaccessible. Previously returnednull.InvalidKeyFormat.0+ HTTP 200 after sequence of @perryts/mysql writes; first INSERT commits, rest silently no-op, explicit return dropped #748 → Closure-captured numeric params read as 0 inside object-literal: Datemethod (@perryts/mysql MyDateTime.toDate shape) #858 → Native shop-admin signup SIGBUS in toUser → dtRequiredToIso (downstream of @perryts/mysql MyDateTime.toDate corruption) #859 → Native server SIGSEGV atjwt.sign(...)(jsonwebtoken) after resumed async-step body — follow-up to #859 #915 → jwt.verify returns JSON-stringified payload (string) instead of parsed object — auth middleware breaks #927 →throw new Error(...)in async Fastify route handler crashes process (exit 1) instead of being caught by setErrorHandler #928 cascade is finally complete end-to-end.Repro
Before this PR:
typeof: object sub: undefined(decoded wasnull).After:
typeof: object sub: u-001.