Repository navigation
fix: keep RSA-OAEP-256 digest correct in runtimes that ignore oaepHash - #106
mmorales-post wants to merge 2 commits into
Conversation
f19a834 to
ccb1501
Compare
talha-API
left a comment
There was a problem hiding this comment.
Thanks for raising this PR and issue @mmorales-post and apologies for the delay in the response.
The changes are sound, just a few points to address. Please also update the PR with latest changes from main (if not done already) and add any relevant tests for the comments provided.
| if ( | ||
| typeof nodeCrypto.publicEncrypt !== "function" || | ||
| !nodeCrypto.constants || | ||
| !keyPem |
There was a problem hiding this comment.
honoursOaepHash will cache false when the key is missing. The key is not related to the runtime environment.
Also, an encrypt-only instance that calls decryptData sets the module-level nativeOaepHash to false. Every JweCrypto in the process then falls back to node-forge, even on a real Node runtime, which should not be the case.
There was a problem hiding this comment.
Fixed. The probe now always uses the encryption certificate, so a missing private key can't affect it.
| }); | ||
| } | ||
| }); | ||
|
|
There was a problem hiding this comment.
Please add a test covering the forge path for decryptData.
There was a problem hiding this comment.
Added tests for forge decrypt: tokens wrapped by node and by forge, plus a SHA-1 wrapped key that gets rejected.
| Buffer.alloc(1) | ||
| ); | ||
| nativeOaepHash = false; | ||
| } catch { |
There was a problem hiding this comment.
This could catch unrelated exceptions, we should probably check for a digest specific one.
There was a problem hiding this comment.
Fixed. It now only treats node's invalid-digest error as support. Anything else isn't cached and falls back to forge.
PR checklist
masterbranchLink to issue/feature request: #105
Description
The JWE key wrap asked node's
crypto.publicEncryptforoaepHash: "sha256". nodehonours that; the
crypto-browserifypolyfill silently ignores it and wraps with SHA-1,producing a token whose header claims
RSA-OAEP-256. It fails only at the recipient.node callers are unaffected. The native path is retained and selected by a one-time
probe. Measured 0.055 ms against 0.050 ms on
mainforencryptData, within noise.The probe. node validates the digest name and throws on an unrecognised one; a
polyfill that ignores
oaepHashaccepts anything. Attempting a wrap under a deliberatelybogus digest therefore distinguishes them, costs one call, and is memoised. It also treats
an absent or stubbed
cryptomodule as unsupported, which is whatresolve.fallback.crypto = falsein this repo'swebpack.config.jsproduces.The fallback goes through node-forge with SHA-256 set for both the OAEP label digest
and MGF1. MGF1 has to be set explicitly or forge defaults it to SHA-1, which would
reintroduce the same class of bug one layer down. The helper mirrors
createOAEPOptionsin
field-level-crypto.js. Forge keys are parsed lazily, so node callers never parse them.decryptDatagets the same treatment, so a browser build can read responses as well assend requests.
Scope. This fixes the digest, which is the failure that is silent. It does not make
the library work under
resolve.fallback.crypto = false, becauserandomBytes,createCipherivandcreateDecipherivstill require the module. Under a bundler thatpolyfills
cryptothe full JWE path now works. Happy to raise the remaining pieceseparately if that is useful.
Validation
Three tests added to
test/jwe-crypto.test.js:still wraps with SHA-256 when the runtime ignores oaepHashstubspublicEncrypttobehave like the polyfill, then asserts the emitted key unwraps with SHA-256 and not
SHA-1. Fails on
main.detects whether the runtime honours oaepHashcovers the probe against real node, apolyfill, and an empty module. Fails on
main.wraps the content encryption key with RSA-OAEP-256, not SHA-1locks the digest for thenative path. This one passes on
mainas well, by design: under node the old code wasalready correct, which is why the bug survived. It is there to stop a regression.
Cross-checked that forge and native interoperate in both directions on the same key, and
that a SHA-1 unwrap of the forge output is rejected.
Full suite: 192 passing. eslint: 0 errors, 5 warnings, unchanged from
main.Validated on Node 24.