Repository navigation
Conversation
`crypto.publicEncrypt`, `privateDecrypt`, `privateEncrypt` and `publicDecrypt` threw ERR_OSSL_EVP_ILLEGAL_OR_UNSUPPORTED_PADDING_MODE when the key object had `padding: null`, `0`, `false`, `NaN` or `""`, and a TypeError for `0n`. `rsaFunction` skipped the option only when it was undefined, so each of these values went to `toInt32` and became padding mode 0. Node reads the option as `key.padding || defaultPadding`. Read it the same way: a falsy value means the default padding, and a truthy value is converted to an integer as before.
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 3 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Status Reproduced on Bun 1.4.3 (release build) and on a debug build of main. Node v26.3.0 is the reference. import crypto from "node:crypto";
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", { modulusLength: 2048 });
crypto.privateEncrypt({ key: privateKey, padding: null }, Buffer.from("the same message"));
// Bun 1.4.3: ERR_OSSL_EVP_ILLEGAL_OR_UNSUPPORTED_PADDING_MODE
// Node v26.3.0: returns the same bytes as the call without `padding`The same holds for Fix: #45359 Update: main |
|
Updated 10:41 AM PT - Oct 15th, 2026
✅ @robobun, your commit 282e4d823aebdd55d62d5540dc8988559c213379 passed in 🧪 To try this PR locally: bunx bun-pr 45359That installs a local version of the PR into your bun-45359 --bun |
There was a problem hiding this comment.
I reviewed the updated PR and found no bugs; the second commit addresses the earlier nit by asserting the exact error code. Because this changes option handling on a node:crypto entry point, a human look is still worthwhile.
What was reviewed:
- The
toBooleangate at src/jsc/bindings/node/crypto/JSCipher.cpp:88:JSValue::toBooleandoes not throw, and thetoInt32that can run user JS for{}/[]keeps its RETURN_IF_EXCEPTION, so the exception-check contract is intact. - Sibling readers of
padding(CryptoUtil.cppgetPaddingused by sign/verify viagetIntOption) implement Node's int32-validation contract that rejects null, so excluding them from this change matches Node. - Test matrix covers all seven falsy values and three truthy non-numeric values across all four entry points, with byte-equality against a deterministic PKCS1
privateEncrypt.
Extended reasoning...
The change is a one-line condition flip in rsaFunction (src/jsc/bindings/node/crypto/JSCipher.cpp) so that a falsy padding option on publicEncrypt/privateDecrypt/privateEncrypt/publicDecrypt selects the default padding instead of being coerced to 0, matching Node's key.padding || defaultPadding, plus a new test.each block in test/js/node/crypto/crypto-rsa.test.js. It touches a crypto surface (RSA padding mode selection), which is why a human look is suggested even though the diff is small, self-contained, cites the Node source line, and the padding integer is still validated by BoringSSL downstream. The second commit resolved the one nit from the prior review (loose substring match on the error code), and the exception-check and sibling-site claims in the PR description were verified against the code.
|
Closing: #47836 landed this fix on main. The test file of this PR passes on the canary build of main ( |
On main (
675ee5b97b): #47836 has this fix and tests the same seven falsy values. Only the three test rows for"0",{}and[]are not on main.Problem
crypto.publicEncrypt,privateDecrypt,privateEncryptandpublicDecryptthrowERR_OSSL_EVP_ILLEGAL_OR_UNSUPPORTED_PADDING_MODEwhen the key object haspadding: null,0,-0,false,NaNor"".padding: 0nthrows aTypeError. Node v26.3.0 takes the default padding for all seven.rsaFunction(src/jsc/bindings/node/crypto/JSCipher.cpp:87). It skips the option only when it isundefined. Each other value goes totoInt32, which gives 0, and 0 is not a padding mode.Fix
rsaFunctionskips the option when it is falsy (toBoolean). A truthy value goes totoInt32as before.key.padding || defaultPadding(cipher.js:72).test/js/node/crypto/crypto-rsa.test.js(10 new tests, 7 fail without the fix). Alsonode-crypto.test.js,crypto.key-objects.test.tsand six vendored Node tests (see Notes).Background
rsaFunctionis the one native function behind the four calls. The default isRSA_PKCS1_OAEP_PADDINGforpublicEncryptandprivateDecrypt,RSA_PKCS1_PADDINGfor the other two.sign()andverify()readpaddingin other code, where Node refusesnull. This PR does not touch it.rsaFunctionis the only reader of this option.Downsides
paddingnow runs with the default padding, as in Node.rsaFunctiongrows by 200 bytes (4458 to 4658,-O3without LTO).Notes
Repro. Each call below throws on Bun 1.4.3 and returns on Node v26.3.0. With this PR each returns the same result as the call without
padding.Input space. A probe ran 27
paddingvalues through the four calls on Node v26.3.0 and on this branch. Falsy values (undefined,null,0,-0,false,NaN,"",0n) give the default on both. Truthy values go through the integer conversion on both:true,1and"1"areRSA_PKCS1_PADDING,4,4.7,[4]and2 ** 32 + 4areRSA_PKCS1_OAEP_PADDING, and"0"," ","abc",{},[],new Number(0),-1andInfinityare rejected as a padding mode.1nandSymbol()throw aTypeErroron both.Two differences from Node remain. Both exist on main and come from BoringSSL, not from this read:
ERR_OSSL_EVP_ILLEGAL_OR_UNSUPPORTED_PADDING_MODEon Bun andERR_OSSL_ILLEGAL_OR_UNSUPPORTED_PADDING_MODEon Node with OpenSSL 3.privateDecryptwithRSA_PKCS1_PADDINGthrowsERR_INVALID_ARG_VALUEon Bun. BoringSSL has no implicit rejection, and Node'sPublicKeyCipher::Cipherhas the same branch for that case.Tests. The seven falsy cases fail on Bun 1.4.3 and pass on the debug build of this branch. The three truthy cases (
"0",{},[]) pass on both. They hold the other side of the rule: only a falsy value means the default, so a fix written as "integer 0 means the default" would fail them.Suites run on the debug ASAN build of this branch, all pass:
test/js/node/crypto/crypto-rsa.test.js, also withBUN_JSC_validateExceptionChecks=1test/js/node/crypto/node-crypto.test.jstest/js/node/crypto/crypto.key-objects.test.tstest/js/node/test/parallel/test-crypto-rsa-dsa.jstest/js/node/test/parallel/test-crypto-sign-verify.js(it asserts thatcreateVerify().verify({ key, padding: null })throwsERR_INVALID_ARG_VALUE)test/js/node/test/parallel/test-crypto-private-decrypt-gh32240.jstest/js/node/test/parallel/test-crypto-publicDecrypt-fails-first-time.jstest/js/node/test/parallel/test-crypto-oaep-zero-length.jstest/js/node/test/parallel/test-crypto-key-objects.jsSiblings. Node's
lib/internal/cryptohas two other reads where a falsy value means the default: thegeneratorofcreateDiffieHellman(diffiehellman.js:115) and the pointformatofECDH(diffiehellman.js:272). Forundefined,null,0,false,NaNand"", both give the same result on Bun 1.4.3 and on Node v26.3.0.History. The check for
undefinedonly dates from the native port of these calls in #19040.How the numbers were measured.
JSCipher.cppwas compiled two times, from main and from this branch, with the flags of the release build minus the LTO flags.nm -Sgives the size ofBun::rsaFunction:0x116aand0x1232. The instruction counts come from the disassembly of the two objects, on the path from the load of thepaddingvalue to the next statement. With nopaddingon the key object the read costs 13 instructions, up from 3. With an integerpaddingit stays at 12.toBooleanis inline and calls nothing for a number,undefined,nullor a boolean. The change adds no allocation and no call.Not in this PR.
rsaFunctionreadspadding,oaepHashandoaepLabelbefore it prepares the key. Node prepares the key first. The result is a different error on input that is invalid in two ways, for examplepublicEncrypt({ key: "not a pem", oaepHash: "nope" }, buf). This is a separate change. #45335 rewrites the key preparation in the same function.