Skip to content

Commit 7c37a1c

Browse files
committed
crypto: add ML-DSA support to crypto.sign and crypto.verify
1 parent 1a01a92 commit 7c37a1c

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
@@ -2491,7 +2491,18 @@ bool EVPKeyPointer::isRsaVariant() const {
24912491
bool EVPKeyPointer::isOneShotVariant() const {
24922492
if (!pkey_) return false;
24932493
int type = id();
2494-
return type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448;
2494+
switch (type) {
2495+
case EVP_PKEY_ED25519:
2496+
case EVP_PKEY_ED448:
2497+
#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5
2498+
case EVP_PKEY_ML_DSA_44:
2499+
case EVP_PKEY_ML_DSA_65:
2500+
case EVP_PKEY_ML_DSA_87:
2501+
#endif
2502+
return true;
2503+
default:
2504+
return false;
2505+
}
24952506
}
24962507

24972508
bool EVPKeyPointer::isSigVariant() const {

doc/api/crypto.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5468,6 +5468,9 @@ Throws an error if FIPS mode is not available.
54685468
<!-- YAML
54695469
added: v12.0.0
54705470
changes:
5471+
- version: REPLACEME
5472+
pr-url: https://github.com/nodejs/node/pull/59259
5473+
description: Add support for ML-DSA signing.
54715474
- version: v18.0.0
54725475
pr-url: https://github.com/nodejs/node/pull/41678
54735476
description: Passing an invalid callback to the `callback` argument
@@ -5578,6 +5581,9 @@ not introduce timing vulnerabilities.
55785581
<!-- YAML
55795582
added: v12.0.0
55805583
changes:
5584+
- version: REPLACEME
5585+
pr-url: https://github.com/nodejs/node/pull/59259
5586+
description: Add support for ML-DSA signature verification.
55815587
- version: v18.0.0
55825588
pr-url: https://github.com/nodejs/node/pull/41678
55835589
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)