diff --git a/src/jsc/bindings/node/crypto/CryptoSignJob.cpp b/src/jsc/bindings/node/crypto/CryptoSignJob.cpp index 972328ee33e..c17e0ac74e8 100644 --- a/src/jsc/bindings/node/crypto/CryptoSignJob.cpp +++ b/src/jsc/bindings/node/crypto/CryptoSignJob.cpp @@ -388,22 +388,26 @@ std::optional SignJobCtx::fromJS(JSGlobalObject* globalObject, Throw // return 0; // } // + // EC and DSA providers do the same thing: + // - providers/implementations/keymgmt/ec_kmgmt.c returns "SHA256" + // - providers/implementations/keymgmt/dsa_kmgmt.c returns DSA_DEFAULT_MD ("SHA256") + // // BoringSSL Difference: // ===================== // BoringSSL (used by Bun) does not have this automatic default mechanism. - // When NULL is passed as the digest to EVP_DigestVerifyInit for RSA keys, + // When NULL is passed as the digest to EVP_DigestVerifyInit for RSA/EC/DSA keys, // BoringSSL returns error 0x06000077 (NO_DEFAULT_DIGEST). // // This Fix: // ========= // To achieve Node.js/OpenSSL compatibility, we explicitly set SHA256 as the - // default digest for RSA keys when no algorithm is specified, matching the - // OpenSSL behavior documented above. + // default digest for RSA, EC, and DSA keys when no algorithm is specified, + // matching the OpenSSL behavior documented above. // // For Ed25519/Ed448 keys (one-shot variants), we intentionally leave digest // as null since these algorithms perform their own hashing internally and // don't require a separate digest algorithm. - if (keyObject.asymmetricKey().isRsaVariant()) { + if (keyObject.asymmetricKey().isRsaVariant() || keyObject.asymmetricKey().isSigVariant()) { digest = Digest::FromName("SHA256"_s); } } diff --git a/test/regression/issue/11029-crypto-verify-null-algorithm.test.ts b/test/regression/issue/11029-crypto-verify-null-algorithm.test.ts index e8ce993cfc7..d7a284ccd12 100644 --- a/test/regression/issue/11029-crypto-verify-null-algorithm.test.ts +++ b/test/regression/issue/11029-crypto-verify-null-algorithm.test.ts @@ -1,5 +1,6 @@ -import { expect, test } from "bun:test"; +import { describe, expect, test } from "bun:test"; import crypto from "crypto"; +import { promisify } from "util"; // Regression test for issue #11029 // crypto.verify() should support null/undefined algorithm parameter @@ -107,6 +108,43 @@ test("crypto.verify cross-verification between null and explicit SHA256", () => expect(isVerifiedWithSHA256).toBe(true); }); +describe.each(["prime256v1", "secp384r1", "secp521r1"])("crypto.sign/verify with null algorithm for EC (%s)", curve => { + const { publicKey, privateKey } = crypto.generateKeyPairSync("ec", { namedCurve: curve }); + const data = Buffer.from("test data"); + + test.each([null, undefined])("sync sign(%p)", algorithm => { + const signature = crypto.sign(algorithm, data, privateKey); + expect(signature).toBeInstanceOf(Buffer); + expect(crypto.verify(algorithm, data, publicKey, signature)).toBe(true); + expect(crypto.verify(algorithm, Buffer.from("wrong data"), publicKey, signature)).toBe(false); + }); + + test("async sign(null)", async () => { + const signature = await promisify(crypto.sign)(null, data, privateKey); + expect(signature).toBeInstanceOf(Buffer); + expect(await promisify(crypto.verify)(null, data, publicKey, signature)).toBe(true); + }); + + test("null defaults to SHA256 (cross-verification)", () => { + // Node.js/OpenSSL default the digest to SHA256 for EC keys when algorithm is null. + const signedWithSha256 = crypto.sign("sha256", data, privateKey); + expect(crypto.verify(null, data, publicKey, signedWithSha256)).toBe(true); + + const signedWithNull = crypto.sign(null, data, privateKey); + expect(crypto.verify("sha256", data, publicKey, signedWithNull)).toBe(true); + }); + + test("sign(null) with ieee-p1363 encoding", () => { + const signature = crypto.sign(null, data, { key: privateKey, dsaEncoding: "ieee-p1363" }); + expect(signature).toBeInstanceOf(Buffer); + expect(crypto.verify(null, data, { key: publicKey, dsaEncoding: "ieee-p1363" }, signature)).toBe(true); + }); + + test("verify(null) returns false for a bad signature instead of throwing", () => { + expect(crypto.verify(null, data, publicKey, Buffer.alloc(70))).toBe(false); + }); +}); + test("crypto.createVerify should also work with RSA keys", () => { const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", { modulusLength: 2048,