Skip to content
Closed
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
41 changes: 41 additions & 0 deletions patches/boringssl/sha512t-sign.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--- a/crypto/fipsmodule/rsa/rsa.cc.inc
+++ b/crypto/fipsmodule/rsa/rsa.cc.inc
@@ -458,6 +458,22 @@
{0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03,
0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40},
},
+ // id-sha512-224 / id-sha512-256 (NIST CSOR 2.16.840.1.101.3.4.2.{5,6}) with
+ // the NULL parameters RFC 8017 specifies for PKCS#1 v1.5.
+ {
+ NID_sha512_224,
+ SHA512_224_DIGEST_LENGTH,
+ 19,
+ {0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03,
+ 0x04, 0x02, 0x05, 0x05, 0x00, 0x04, 0x1c},
+ },
+ {
+ NID_sha512_256,
+ SHA512_256_DIGEST_LENGTH,
+ 19,
+ {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03,
+ 0x04, 0x02, 0x06, 0x05, 0x00, 0x04, 0x20},
+ },
{
NID_undef,
0,
--- a/crypto/evp/p_ec.cc
+++ b/crypto/evp/p_ec.cc
@@ -428,9 +428,12 @@
case EVP_PKEY_CTRL_MD: {
const EVP_MD *md = reinterpret_cast<const EVP_MD *>(p2);
int md_type = EVP_MD_type(md);
+ // pkey_ec_sign/pkey_ec_verify hand the digest straight to ECDSA_sign and
+ // never read dctx->md, so the truncated SHA-512 variants only need this gate.
if (md_type != NID_sha1 && md_type != NID_sha224 &&
md_type != NID_sha256 && md_type != NID_sha384 &&
- md_type != NID_sha512) {
+ md_type != NID_sha512 && md_type != NID_sha512_224 &&
+ md_type != NID_sha512_256) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_DIGEST_TYPE);
return 0;
}
6 changes: 5 additions & 1 deletion scripts/build/deps/boringssl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ export const boringssl: Dependency = {
// Upstream mem.cc gates OPENSSL_memory_* weak-symbol overrides on __ELF__;
// on Mach-O/COFF the hooks compile to static nullptr and OPENSSL_malloc goes
// straight to libc. Declare them as plain externs so lib.rs binds everywhere.
patches: ["patches/boringssl/require-memory-hooks.patch"],
//
// sha512t-sign: BoringSSL ships EVP_sha512_224/256 but omits NID_sha512_224/256 from
// kPKCS1SigPrefixes and pkey_ec_ctrl, so RSA PKCS#1 v1.5 and EC sign/verify reject
// them. Drop once the fork carries the change and BORINGSSL_COMMIT moves.
patches: ["patches/boringssl/require-memory-hooks.patch", "patches/boringssl/sha512t-sign.patch"],

build: cfg => {
// win-x64 uses NASM-syntax .asm; everything else (including win-aarch64)
Expand Down
6 changes: 6 additions & 0 deletions src/jsc/bindings/ncrypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1969,6 +1969,12 @@ const EVP_MD* getDigestByName(const WTF::StringView name)
if (WTF::equalIgnoringASCIICase(bits, "512"_s)) {
return EVP_sha512();
}
if (WTF::equalIgnoringASCIICase(bits, "512/224"_s)) {
return EVP_sha512_224();
}
if (WTF::equalIgnoringASCIICase(bits, "512/256"_s)) {
return EVP_sha512_256();
}
Comment thread
robobun marked this conversation as resolved.
}

if (WTF::startsWithIgnoringASCIICase(name, "sha"_s)) {
Expand Down
106 changes: 106 additions & 0 deletions test/js/node/crypto/crypto-oneshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,109 @@ describe("crypto.verify", () => {
expect(verified).toBe(true);
});
});

describe("SHA-512/224 and SHA-512/256 sign/verify", () => {
const digests = ["sha512-224", "sha512-256"];
const message = Buffer.from("sha512/t truncated digest conformance");
const fixture = (name: string) => readFileSync(`${__dirname}/fixtures/${name}`, "utf8");

const rsaPrivate = fixture("rsa_private_2048.pem");
const rsaPublic = fixture("rsa_public_2048.pem");
const ecPrivate = fixture("ec_p256_private.pem");
const ecPublic = fixture("ec_p256_public.pem");

// Signatures produced by Node.js (OpenSSL) over `message` with the fixtures
// above. RSA PKCS#1 v1.5 is deterministic, so these pin the exact DigestInfo
// encoding rather than just proving we can verify what we sign.
const rsaPkcs1: Record<string, string> = {
"sha512-224":
"psEUABh24pmkwPp4prxY2T0QzxpunNaOnfadI/WBRkQzP15tQcKh1bV1ZGeGo4YmmyfMdib/QhFbadd2FSdsqS5LJKcnZlj9gb3kTHgu6YyiQiLyeq9z0SWz4pdkKc1iq7KAa5TMIgak7b6SqVMI5iA+x80Bi2Xh9qAGE0IZuIuRWEnVNQYEZK9ORau8lNxn9Ib7DwdKcCsLS626H+cxm8YHu3QcVT4YV5mPZ+E6WhtNV0Ff1r7myMoXFefrDsDyPVSED0bGrKBbbawR1Y8s+I0c+YAaGVLlO2ZSFdbqAC+VkeNEy1lxNFrNKavf9UM6s6Ml85yRcnaa1Kwue3BTjw==",
"sha512-256":
"CnZSfkxxTlGqykoCLNolJpRi0pHmEQ0JV6YgtEB/weA/zB4RWK6D3s6ZJ9+Xo10ld0DmMpTBG61Nck6qm8MevsWdePR0Ldj/jHSEnvEyS6K4RhOjAfXxqbAWdoh04JEaz6v72LSLFqEAs4siJcaKFZ/d9HPTVXgkSD2wOjlDAvKgu+5UMh/5FxXJAXPhr5K1YdU/Uie2lbQ5VwzRJq8dG9afT0UgpTyUdr9XGuPr09HRrflsedSElarsGorUa1O1V1l7L8Btj29shDX0NBTl5niA7SwnzeAawW3dfakRPeDGqn/we94kyK8vw71oCpVySg/PLQNt6ArUBN23Zcbs1A==",
};

// PSS and ECDSA are randomized, so these only exercise the verify path.
const rsaPss: Record<string, string> = {
"sha512-224":
"HYlsKCnKIZIoaQ2mqS5+eTdypx2ypQlOV+qfJYWT/8350FpqFO5fugLTT6ap68AeAVwSTJayTwwjb6uelIMnJsHLAmeaFwFnA34lrhv5CMhzgThxoN/hLlo7PmTItXOHzATEqSuWOlCT8JmcCYTRkXWNmPCvRPO6b4cFiB1w1XD99l8sb90FBOEuNjnVL872O0pdFcA87Z6wdXpVMIn2OmD+KwvR3c8f+Yck6vo9QwXpiKNaEuwdRhQV++ZvxfI5XHqHT+lkBToXgRGuHJda9wLvzvdJ+HoGpqIigglNLPXaOyLqDbK4KIBjchzNf77az9DqtADGKtetQikru4DILQ==",
"sha512-256":
"InLtCNTKfBkPlVKGdzOVeSeloObt0E1+03AfxZ5EvIH8XBmT8nJhuDBJoA/02ndpJCQmEPuGOYhDm5f8ZOjwwfkTGGhAG2D77k9xvVzrPeJYkZsff13/2LwwgI36P7/TXjBgCNsUly0Y9WkuM1JVg/rSDUsrw+YDCfocP3My8AkgxCfE7fLQQW+Jb0rjFgyaRUz6wItzB/+aXjKpJl1oWZj8GDjZToWTThumHLIn4sm/MFOXyoCuLv59qiPApYD14uvTckA98TkeeBOT9upehNt5GGLjeRJNK8n2jjWtscKBya5tZ01JwFppXjwqE/X832yuHoc6YcLtHBEARp82Dw==",
};

const ecdsa: Record<string, string> = {
"sha512-224": "MEUCIQCicz3JtMf0wDnpIH21Ux9roA+zcG8cqdKCvL3Qe/PprQIgK7waBH2z/G5+TrEsZ+zuov+C52V6WpH5WlbjaM5SU5A=",
"sha512-256": "MEQCICjmdz59MesS8BnyG0pzPeRZ70BApipq99pTGmguTwA1AiBGhRw1PBYI6fO09gN1aAkdwfFjo+6Jma/b8LizYA8F8A==",
};

describe.each(digests)("%s", digest => {
test("crypto.sign() and createSign() reproduce OpenSSL's RSA PKCS#1 v1.5 bytes", () => {
expect({
oneShot: crypto.sign(digest, message, rsaPrivate).toString("base64"),
streaming: crypto.createSign(digest).update(message).sign(rsaPrivate, "base64"),
}).toEqual({ oneShot: rsaPkcs1[digest], streaming: rsaPkcs1[digest] });
});

test("crypto.verify() accepts OpenSSL's signatures", () => {
expect({
pkcs1: crypto.verify(digest, message, rsaPublic, Buffer.from(rsaPkcs1[digest], "base64")),
pss: crypto.verify(
digest,
message,
{ key: rsaPublic, padding: crypto.constants.RSA_PKCS1_PSS_PADDING },
Buffer.from(rsaPss[digest], "base64"),
),
ecdsa: crypto.verify(digest, message, ecPublic, Buffer.from(ecdsa[digest], "base64")),
}).toEqual({ pkcs1: true, pss: true, ecdsa: true });
});

test("createVerify() accepts OpenSSL's signatures", () => {
expect({
pkcs1: crypto.createVerify(digest).update(message).verify(rsaPublic, rsaPkcs1[digest], "base64"),
ecdsa: crypto.createVerify(digest).update(message).verify(ecPublic, ecdsa[digest], "base64"),
}).toEqual({ pkcs1: true, ecdsa: true });
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});

test("ECDSA signatures round-trip", () => {
const oneShot = crypto.sign(digest, message, ecPrivate);
const streaming = crypto.createSign(digest).update(message).sign(ecPrivate);
expect({
oneShot: crypto.verify(digest, message, ecPublic, oneShot),
streaming: crypto.createVerify(digest).update(message).verify(ecPublic, streaming),
}).toEqual({ oneShot: true, streaming: true });
});

test("RSA-PSS signatures round-trip", () => {
const padding = crypto.constants.RSA_PKCS1_PSS_PADDING;
const oneShot = crypto.sign(digest, message, { key: rsaPrivate, padding });
const streaming = crypto.createSign(digest).update(message).sign({ key: rsaPrivate, padding });
expect({
oneShotVerify: crypto.verify(digest, message, { key: rsaPublic, padding }, oneShot),
streamingVerify: crypto.createVerify(digest).update(message).verify({ key: rsaPublic, padding }, streaming),
// PSS output is randomized, so the two signatures must differ.
randomized: !oneShot.equals(streaming),
}).toEqual({ oneShotVerify: true, streamingVerify: true, randomized: true });
});

test("the RSA-SHA512/<t> alias resolves", () => {
const alias = digest === "sha512-224" ? "RSA-SHA512/224" : "RSA-SHA512/256";
expect({
sign: crypto.sign(alias, message, rsaPrivate).toString("base64"),
verify: crypto.verify(alias, message, rsaPublic, Buffer.from(rsaPkcs1[digest], "base64")),
}).toEqual({ sign: rsaPkcs1[digest], verify: true });
});

test("tampered and mismatched signatures are rejected", () => {
const tamperedRsa = Buffer.from(rsaPkcs1[digest], "base64");
tamperedRsa[tamperedRsa.length - 1] ^= 1;
const tamperedEcdsa = Buffer.from(ecdsa[digest], "base64");
tamperedEcdsa[tamperedEcdsa.length - 1] ^= 1;

expect({
tamperedRsa: crypto.verify(digest, message, rsaPublic, tamperedRsa),
tamperedEcdsa: crypto.verify(digest, message, ecPublic, tamperedEcdsa),
wrongMessage: crypto.verify(digest, Buffer.from("other"), rsaPublic, Buffer.from(rsaPkcs1[digest], "base64")),
wrongDigest: crypto.verify("sha256", message, rsaPublic, Buffer.from(rsaPkcs1[digest], "base64")),
}).toEqual({ tamperedRsa: false, tamperedEcdsa: false, wrongMessage: false, wrongDigest: false });
});
});
});
2 changes: 0 additions & 2 deletions test/js/node/crypto/node-crypto.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,6 @@ describe("createHash", () => {
"rsa-sha3-256",
"rsa-sha3-384",
"rsa-sha3-512",
"rsa-sha512/224",
"rsa-sha512/256",
"rsa-sm3",
"sha1withrsaencryption",
"sha224withrsaencryption",
Expand Down
Loading