Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/jsc/bindings/node/crypto/CryptoSignJob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -388,22 +388,26 @@ std::optional<SignJobCtx> 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);
}
}
Expand Down
40 changes: 39 additions & 1 deletion test/regression/issue/11029-crypto-verify-null-algorithm.test.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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,
Expand Down
Loading