mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 02:48:50 +00:00
## Summary Fixes #11029 - `crypto.verify()` now correctly handles null/undefined algorithm parameter for RSA keys, matching Node.js behavior. ## Problem When calling `crypto.verify()` with a null or undefined algorithm parameter, Bun was throwing an error: ``` error: error:06000077:public key routines:OPENSSL_internal:NO_DEFAULT_DIGEST ``` ## Root Cause The issue stems from the difference between OpenSSL (used by Node.js) and BoringSSL (used by Bun): - **OpenSSL v3**: Automatically provides SHA256 as the default digest for RSA keys when NULL is passed - **BoringSSL**: Returns an error when NULL digest is passed for RSA keys ## Solution This fix explicitly sets SHA256 as the default digest for RSA keys when no algorithm is specified, achieving OpenSSL-compatible behavior. ## OpenSSL v3 Source Code Analysis I traced through the OpenSSL v3 source code to understand exactly how it handles null digests: ### 1. Entry Point (`crypto/evp/m_sigver.c`) When `EVP_DigestSignInit` or `EVP_DigestVerifyInit` is called with NULL digest: ```c // Lines 215-220 in do_sigver_init function if (mdname == NULL && !reinit) { if (evp_keymgmt_util_get_deflt_digest_name(tmp_keymgmt, provkey, locmdname, sizeof(locmdname)) > 0) { mdname = canon_mdname(locmdname); } } ``` ### 2. Default Digest Query (`crypto/evp/keymgmt_lib.c`) ```c // Lines 533-571 in evp_keymgmt_util_get_deflt_digest_name params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_DEFAULT_DIGEST, mddefault, sizeof(mddefault)); if (!evp_keymgmt_get_params(keymgmt, keydata, params)) return 0; ``` ### 3. RSA Provider Implementation (`providers/implementations/keymgmt/rsa_kmgmt.c`) ```c // Line 54: Define the default #define RSA_DEFAULT_MD "SHA256" // Lines 351-355: Return it for RSA keys if ((p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_DEFAULT_DIGEST)) != NULL && (rsa_type != RSA_FLAG_TYPE_RSASSAPSS || ossl_rsa_pss_params_30_is_unrestricted(pss_params))) { if (!OSSL_PARAM_set_utf8_string(p, RSA_DEFAULT_MD)) return 0; } ``` ## Implementation Details The fix includes extensive documentation in the source code explaining: - The OpenSSL v3 mechanism with specific file paths and line numbers - Why BoringSSL behaves differently - Why Ed25519/Ed448 keys are handled differently (they don't need a digest) ## Test Plan ✅ Added comprehensive regression test in `test/regression/issue/11029-crypto-verify-null-algorithm.test.ts` ✅ Tests cover: - RSA keys with null/undefined algorithm - Ed25519 keys with null algorithm - Cross-verification between null and explicit SHA256 - `createVerify()` compatibility ✅ All tests pass and behavior matches Node.js ## Verification ```bash # Test with Bun bun test test/regression/issue/11029-crypto-verify-null-algorithm.test.ts # Compare with Node.js behavior node -e "const crypto = require('crypto'); const {publicKey, privateKey} = crypto.generateKeyPairSync('rsa', {modulusLength: 2048}); const data = Buffer.from('test'); const sig = crypto.sign(null, data, privateKey); console.log('Node.js verify with null:', crypto.verify(null, data, publicKey, sig));" ``` 🤖 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
136 lines
3.9 KiB
TypeScript
136 lines
3.9 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import crypto from "crypto";
|
|
|
|
// Regression test for issue #11029
|
|
// crypto.verify() should support null/undefined algorithm parameter
|
|
test("crypto.verify with null algorithm should work for RSA keys", () => {
|
|
// Generate RSA key pair
|
|
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
|
|
modulusLength: 2048,
|
|
publicKeyEncoding: {
|
|
type: "spki",
|
|
format: "pem",
|
|
},
|
|
privateKeyEncoding: {
|
|
type: "pkcs8",
|
|
format: "pem",
|
|
},
|
|
});
|
|
|
|
const data = Buffer.from("test data");
|
|
|
|
// Sign with null algorithm (should use default SHA256 for RSA)
|
|
const signature = crypto.sign(null, data, privateKey);
|
|
expect(signature).toBeInstanceOf(Buffer);
|
|
|
|
// Verify with null algorithm should succeed
|
|
const isVerified = crypto.verify(null, data, publicKey, signature);
|
|
expect(isVerified).toBe(true);
|
|
|
|
// Verify with wrong data should fail
|
|
const wrongData = Buffer.from("wrong data");
|
|
const isVerifiedWrong = crypto.verify(null, wrongData, publicKey, signature);
|
|
expect(isVerifiedWrong).toBe(false);
|
|
});
|
|
|
|
test("crypto.verify with undefined algorithm should work for RSA keys", () => {
|
|
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
|
|
modulusLength: 2048,
|
|
publicKeyEncoding: {
|
|
type: "spki",
|
|
format: "pem",
|
|
},
|
|
privateKeyEncoding: {
|
|
type: "pkcs8",
|
|
format: "pem",
|
|
},
|
|
});
|
|
|
|
const data = Buffer.from("test data");
|
|
const signature = crypto.sign(undefined, data, privateKey);
|
|
|
|
// Verify with undefined algorithm
|
|
const isVerified = crypto.verify(undefined, data, publicKey, signature);
|
|
expect(isVerified).toBe(true);
|
|
});
|
|
|
|
test("crypto.verify with null algorithm should work for Ed25519 keys", () => {
|
|
// Generate Ed25519 key pair (one-shot variant that doesn't need digest)
|
|
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519", {
|
|
publicKeyEncoding: {
|
|
type: "spki",
|
|
format: "pem",
|
|
},
|
|
privateKeyEncoding: {
|
|
type: "pkcs8",
|
|
format: "pem",
|
|
},
|
|
});
|
|
|
|
const data = Buffer.from("test data");
|
|
|
|
// Ed25519 should work with null algorithm (no digest needed)
|
|
const signature = crypto.sign(null, data, privateKey);
|
|
expect(signature).toBeInstanceOf(Buffer);
|
|
|
|
const isVerified = crypto.verify(null, data, publicKey, signature);
|
|
expect(isVerified).toBe(true);
|
|
});
|
|
|
|
test("crypto.verify cross-verification between null and explicit SHA256", () => {
|
|
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
|
|
modulusLength: 2048,
|
|
publicKeyEncoding: {
|
|
type: "spki",
|
|
format: "pem",
|
|
},
|
|
privateKeyEncoding: {
|
|
type: "pkcs8",
|
|
format: "pem",
|
|
},
|
|
});
|
|
|
|
const data = Buffer.from("test data");
|
|
|
|
// Sign with SHA256
|
|
const signatureSHA256 = crypto.sign("SHA256", data, privateKey);
|
|
|
|
// Should be able to verify with null (defaults to SHA256 for RSA)
|
|
const isVerifiedWithNull = crypto.verify(null, data, publicKey, signatureSHA256);
|
|
expect(isVerifiedWithNull).toBe(true);
|
|
|
|
// Sign with null
|
|
const signatureNull = crypto.sign(null, data, privateKey);
|
|
|
|
// Should be able to verify with explicit SHA256
|
|
const isVerifiedWithSHA256 = crypto.verify("SHA256", data, publicKey, signatureNull);
|
|
expect(isVerifiedWithSHA256).toBe(true);
|
|
});
|
|
|
|
test("crypto.createVerify should also work with RSA keys", () => {
|
|
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
|
|
modulusLength: 2048,
|
|
publicKeyEncoding: {
|
|
type: "spki",
|
|
format: "pem",
|
|
},
|
|
privateKeyEncoding: {
|
|
type: "pkcs8",
|
|
format: "pem",
|
|
},
|
|
});
|
|
|
|
const data = Buffer.from("test data");
|
|
|
|
// Create signature using createSign
|
|
const signer = crypto.createSign("SHA256");
|
|
signer.update(data);
|
|
const signature = signer.sign(privateKey);
|
|
|
|
// Verify using createVerify
|
|
const verifier = crypto.createVerify("SHA256");
|
|
verifier.update(data);
|
|
const isVerified = verifier.verify(publicKey, signature);
|
|
expect(isVerified).toBe(true);
|
|
});
|