Skip to content

Commit 3c220b4

Browse files
committed
crypto: add ML-DSA support to crypto.sign and crypto.verify
1 parent b16a667 commit 3c220b4

File tree

3 files changed

+86
-1
lines changed

3 files changed

+86
-1
lines changed

deps/ncrypto/ncrypto.cc

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2488,7 +2488,18 @@ bool EVPKeyPointer::isRsaVariant() const {
24882488
bool EVPKeyPointer::isOneShotVariant() const {
24892489
if (!pkey_) return false;
24902490
int type = id();
2491-
return type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448;
2491+
switch (type) {
2492+
case EVP_PKEY_ED25519:
2493+
case EVP_PKEY_ED448:
2494+
#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5
2495+
case EVP_PKEY_ML_DSA_44:
2496+
case EVP_PKEY_ML_DSA_65:
2497+
case EVP_PKEY_ML_DSA_87:
2498+
#endif
2499+
return true;
2500+
default:
2501+
return false;
2502+
}
24922503
}
24932504

24942505
bool EVPKeyPointer::isSigVariant() const {

doc/api/crypto.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5442,6 +5442,9 @@ Throws an error if FIPS mode is not available.
54425442
<!-- YAML
54435443
added: v12.0.0
54445444
changes:
5445+
- version: REPLACEME
5446+
pr-url: https://github.com/nodejs/node/pull/59259
5447+
description: Add support for ML-DSA signing.
54455448
- version: v18.0.0
54465449
pr-url: https://github.com/nodejs/node/pull/41678
54475450
description: Passing an invalid callback to the `callback` argument
@@ -5552,6 +5555,9 @@ not introduce timing vulnerabilities.
55525555
<!-- YAML
55535556
added: v12.0.0
55545557
changes:
5558+
- version: REPLACEME
5559+
pr-url: https://github.com/nodejs/node/pull/59259
5560+
description: Add support for ML-DSA signature verification.
55555561
- version: v18.0.0
55565562
pr-url: https://github.com/nodejs/node/pull/41678
55575563
description: Passing an invalid callback to the `callback` argument
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if (!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
const { hasOpenSSL } = require('../common/crypto');
8+
9+
if (!hasOpenSSL(3, 5))
10+
common.skip('requires OpenSSL >= 3.5');
11+
12+
const assert = require('assert');
13+
const {
14+
randomBytes,
15+
sign,
16+
verify,
17+
} = require('crypto');
18+
19+
const fixtures = require('../common/fixtures');
20+
21+
function getKeyFileName(type, suffix) {
22+
return `${type.replaceAll('-', '_')}_${suffix}.pem`;
23+
}
24+
25+
for (const [asymmetricKeyType, sigLen] of [
26+
['ml-dsa-44', 2420], ['ml-dsa-65', 3309], ['ml-dsa-87', 4627],
27+
]) {
28+
const keys = {
29+
public: fixtures.readKey(getKeyFileName(asymmetricKeyType, 'public'), 'ascii'),
30+
private: fixtures.readKey(getKeyFileName(asymmetricKeyType, 'private'), 'ascii'),
31+
private_seed_only: fixtures.readKey(getKeyFileName(asymmetricKeyType, 'private_seed_only'), 'ascii'),
32+
private_priv_only: fixtures.readKey(getKeyFileName(asymmetricKeyType, 'private_priv_only'), 'ascii'),
33+
};
34+
35+
for (const privateKey of [keys.private, keys.private_seed_only, keys.private_priv_only]) {
36+
for (const data of [randomBytes(0), randomBytes(1), randomBytes(32), randomBytes(128), randomBytes(1024)]) {
37+
// sync
38+
{
39+
const signature = sign(undefined, data, privateKey);
40+
assert.strictEqual(signature.byteLength, sigLen);
41+
assert.strictEqual(verify(undefined, randomBytes(32), keys.public, signature), false);
42+
assert.strictEqual(verify(undefined, data, keys.public, Buffer.alloc(sigLen)), false);
43+
assert.strictEqual(verify(undefined, data, keys.public, signature), true);
44+
assert.strictEqual(verify(undefined, data, privateKey, signature), true);
45+
assert.throws(() => sign('sha256', data, privateKey), { code: 'ERR_OSSL_INVALID_DIGEST' });
46+
assert.throws(
47+
() => verify('sha256', data, keys.public, Buffer.alloc(sigLen)),
48+
{ code: 'ERR_OSSL_INVALID_DIGEST' });
49+
}
50+
51+
// async
52+
{
53+
sign(undefined, data, privateKey, common.mustSucceed((signature) => {
54+
assert.strictEqual(signature.byteLength, sigLen);
55+
verify(undefined, data, privateKey, signature, common.mustSucceed((valid) => {
56+
assert.strictEqual(valid, true);
57+
}));
58+
verify(undefined, data, privateKey, Buffer.alloc(sigLen), common.mustSucceed((valid) => {
59+
assert.strictEqual(valid, false);
60+
}));
61+
}));
62+
63+
sign('sha256', data, privateKey, common.expectsError(/invalid digest/));
64+
verify('sha256', data, keys.public, Buffer.alloc(sigLen), common.expectsError(/invalid digest/));
65+
}
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)