Skip to content

Commit aeea8f4

Browse files
panvasxa
authored andcommitted
crypto: add JWK support for ML-KEM and SLH-DSA key types
Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: #62706 Backport-PR-URL: #63563 Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 407cf91 commit aeea8f4

40 files changed

Lines changed: 805 additions & 359 deletions

benchmark/crypto/kem.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ const bench = common.createBenchmark(main, {
5454
// assess whether mutexes over the key material impact the operation
5555
if (p.keyFormat === 'keyObject.unique')
5656
return p.mode === 'async-parallel';
57-
// JWK is not supported for ml-kem for now
58-
if (p.keyFormat === 'jwk')
59-
return !p.keyType.startsWith('ml-');
6057
// raw-public is only supported for encapsulate, not rsa
6158
if (p.keyFormat === 'raw-public')
6259
return p.keyType !== 'rsa' && p.op === 'encapsulate';

doc/api/crypto.md

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -86,23 +86,23 @@ The following table lists the asymmetric key types recognized by the
8686
| `'ml-dsa-44'`[^openssl35] | ML-DSA-44 | 2.16.840.1.101.3.4.3.17 ||||| ||
8787
| `'ml-dsa-65'`[^openssl35] | ML-DSA-65 | 2.16.840.1.101.3.4.3.18 ||||| ||
8888
| `'ml-dsa-87'`[^openssl35] | ML-DSA-87 | 2.16.840.1.101.3.4.3.19 ||||| ||
89-
| `'ml-kem-512'`[^openssl35] | ML-KEM-512 | 2.16.840.1.101.3.4.4.1 ||| || ||
90-
| `'ml-kem-768'`[^openssl35] | ML-KEM-768 | 2.16.840.1.101.3.4.4.2 ||| || ||
91-
| `'ml-kem-1024'`[^openssl35] | ML-KEM-1024 | 2.16.840.1.101.3.4.4.3 ||| || ||
89+
| `'ml-kem-512'`[^openssl35] | ML-KEM-512 | 2.16.840.1.101.3.4.4.1 ||| || ||
90+
| `'ml-kem-768'`[^openssl35] | ML-KEM-768 | 2.16.840.1.101.3.4.4.2 ||| || ||
91+
| `'ml-kem-1024'`[^openssl35] | ML-KEM-1024 | 2.16.840.1.101.3.4.4.3 ||| || ||
9292
| `'rsa-pss'` | RSA PSS | 1.2.840.113549.1.1.10 ||| | | | |
9393
| `'rsa'` | RSA | 1.2.840.113549.1.1.1 |||| | | |
94-
| `'slh-dsa-sha2-128f'`[^openssl35] | SLH-DSA-SHA2-128f | 2.16.840.1.101.3.4.3.21 ||| ||| |
95-
| `'slh-dsa-sha2-128s'`[^openssl35] | SLH-DSA-SHA2-128s | 2.16.840.1.101.3.4.3.20 ||| ||| |
96-
| `'slh-dsa-sha2-192f'`[^openssl35] | SLH-DSA-SHA2-192f | 2.16.840.1.101.3.4.3.23 ||| ||| |
97-
| `'slh-dsa-sha2-192s'`[^openssl35] | SLH-DSA-SHA2-192s | 2.16.840.1.101.3.4.3.22 ||| ||| |
98-
| `'slh-dsa-sha2-256f'`[^openssl35] | SLH-DSA-SHA2-256f | 2.16.840.1.101.3.4.3.25 ||| ||| |
99-
| `'slh-dsa-sha2-256s'`[^openssl35] | SLH-DSA-SHA2-256s | 2.16.840.1.101.3.4.3.24 ||| ||| |
100-
| `'slh-dsa-shake-128f'`[^openssl35] | SLH-DSA-SHAKE-128f | 2.16.840.1.101.3.4.3.27 ||| ||| |
101-
| `'slh-dsa-shake-128s'`[^openssl35] | SLH-DSA-SHAKE-128s | 2.16.840.1.101.3.4.3.26 ||| ||| |
102-
| `'slh-dsa-shake-192f'`[^openssl35] | SLH-DSA-SHAKE-192f | 2.16.840.1.101.3.4.3.29 ||| ||| |
103-
| `'slh-dsa-shake-192s'`[^openssl35] | SLH-DSA-SHAKE-192s | 2.16.840.1.101.3.4.3.28 ||| ||| |
104-
| `'slh-dsa-shake-256f'`[^openssl35] | SLH-DSA-SHAKE-256f | 2.16.840.1.101.3.4.3.31 ||| ||| |
105-
| `'slh-dsa-shake-256s'`[^openssl35] | SLH-DSA-SHAKE-256s | 2.16.840.1.101.3.4.3.30 ||| ||| |
94+
| `'slh-dsa-sha2-128f'`[^openssl35] | SLH-DSA-SHA2-128f | 2.16.840.1.101.3.4.3.21 ||| ||| |
95+
| `'slh-dsa-sha2-128s'`[^openssl35] | SLH-DSA-SHA2-128s | 2.16.840.1.101.3.4.3.20 ||| ||| |
96+
| `'slh-dsa-sha2-192f'`[^openssl35] | SLH-DSA-SHA2-192f | 2.16.840.1.101.3.4.3.23 ||| ||| |
97+
| `'slh-dsa-sha2-192s'`[^openssl35] | SLH-DSA-SHA2-192s | 2.16.840.1.101.3.4.3.22 ||| ||| |
98+
| `'slh-dsa-sha2-256f'`[^openssl35] | SLH-DSA-SHA2-256f | 2.16.840.1.101.3.4.3.25 ||| ||| |
99+
| `'slh-dsa-sha2-256s'`[^openssl35] | SLH-DSA-SHA2-256s | 2.16.840.1.101.3.4.3.24 ||| ||| |
100+
| `'slh-dsa-shake-128f'`[^openssl35] | SLH-DSA-SHAKE-128f | 2.16.840.1.101.3.4.3.27 ||| ||| |
101+
| `'slh-dsa-shake-128s'`[^openssl35] | SLH-DSA-SHAKE-128s | 2.16.840.1.101.3.4.3.26 ||| ||| |
102+
| `'slh-dsa-shake-192f'`[^openssl35] | SLH-DSA-SHAKE-192f | 2.16.840.1.101.3.4.3.29 ||| ||| |
103+
| `'slh-dsa-shake-192s'`[^openssl35] | SLH-DSA-SHAKE-192s | 2.16.840.1.101.3.4.3.28 ||| ||| |
104+
| `'slh-dsa-shake-256f'`[^openssl35] | SLH-DSA-SHAKE-256f | 2.16.840.1.101.3.4.3.31 ||| ||| |
105+
| `'slh-dsa-shake-256s'`[^openssl35] | SLH-DSA-SHAKE-256s | 2.16.840.1.101.3.4.3.30 ||| ||| |
106106
| `'x25519'` | X25519 | 1.3.101.110 |||||| |
107107
| `'x448'` | X448 | 1.3.101.111 |||||| |
108108

@@ -2393,6 +2393,10 @@ type, value, and parameters. This method is not
23932393
<!-- YAML
23942394
added: v11.6.0
23952395
changes:
2396+
- version: REPLACEME
2397+
pr-url: https://github.com/nodejs/node/pull/62706
2398+
description: Added JWK format support for ML-KEM and SLH-DSA
2399+
key types.
23962400
- version: v24.15.0
23972401
pr-url: https://github.com/nodejs/node/pull/62240
23982402
description: Added support for `'raw-public'`, `'raw-private'`,
@@ -3913,6 +3917,10 @@ input.on('readable', () => {
39133917
<!-- YAML
39143918
added: v11.6.0
39153919
changes:
3920+
- version: REPLACEME
3921+
pr-url: https://github.com/nodejs/node/pull/62706
3922+
description: Added JWK format support for ML-KEM and SLH-DSA
3923+
key types.
39163924
- version: v24.15.0
39173925
pr-url: https://github.com/nodejs/node/pull/62240
39183926
description: Added support for `'raw-private'` and `'raw-seed'`
@@ -3961,6 +3969,10 @@ of the passphrase is limited to 1024 bytes.
39613969
<!-- YAML
39623970
added: v11.6.0
39633971
changes:
3972+
- version: REPLACEME
3973+
pr-url: https://github.com/nodejs/node/pull/62706
3974+
description: Added JWK format support for ML-KEM and SLH-DSA
3975+
key types.
39643976
- version: v24.15.0
39653977
pr-url: https://github.com/nodejs/node/pull/62240
39663978
description: Added support for `'raw-public'` format.

doc/api/webcrypto.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,6 +1131,9 @@ The algorithms currently supported include:
11311131
<!-- YAML
11321132
added: v15.0.0
11331133
changes:
1134+
- version: REPLACEME
1135+
pr-url: https://github.com/nodejs/node/pull/62706
1136+
description: Added JWK format support for ML-KEM key types.
11341137
- version: v24.8.0
11351138
pr-url: https://github.com/nodejs/node/pull/59647
11361139
description: KMAC algorithms are now supported.
@@ -1189,9 +1192,9 @@ specification.
11891192
| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
11901193
| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
11911194
| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
1192-
| `'ML-KEM-512'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1193-
| `'ML-KEM-768'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1194-
| `'ML-KEM-1024'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1195+
| `'ML-KEM-512'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1196+
| `'ML-KEM-768'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1197+
| `'ML-KEM-1024'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
11951198
| `'RSA-OAEP'` | ✔ | ✔ | ✔ | | | | |
11961199
| `'RSA-PSS'` | ✔ | ✔ | ✔ | | | | |
11971200
| `'RSASSA-PKCS1-v1_5'` | ✔ | ✔ | ✔ | | | | |
@@ -1279,6 +1282,9 @@ The {CryptoKey} (secret key) generating algorithms supported include:
12791282
<!-- YAML
12801283
added: v15.0.0
12811284
changes:
1285+
- version: REPLACEME
1286+
pr-url: https://github.com/nodejs/node/pull/62706
1287+
description: Added JWK format support for ML-KEM key types.
12821288
- version: v24.15.0
12831289
pr-url: https://github.com/nodejs/node/pull/62218
12841290
description: Importing ML-DSA and ML-KEM PKCS#8 keys
@@ -1352,9 +1358,9 @@ The algorithms currently supported include:
13521358
| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
13531359
| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
13541360
| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
1355-
| `'ML-KEM-512'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1356-
| `'ML-KEM-768'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1357-
| `'ML-KEM-1024'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1361+
| `'ML-KEM-512'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1362+
| `'ML-KEM-768'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1363+
| `'ML-KEM-1024'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
13581364
| `'PBKDF2'` | | | | ✔ | ✔ | | |
13591365
| `'RSA-OAEP'` | ✔ | ✔ | ✔ | | | | |
13601366
| `'RSA-PSS'` | ✔ | ✔ | ✔ | | | | |

lib/internal/crypto/keys.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,6 @@ function getKeyTypes(allowKeyObject, bufferOnly = false) {
628628
return types;
629629
}
630630

631-
632631
function prepareAsymmetricKey(key, ctx, name = 'key') {
633632
if (isKeyObject(key)) {
634633
// Best case: A key object, as simple as that.
@@ -661,17 +660,17 @@ function prepareAsymmetricKey(key, ctx, name = 'key') {
661660
format === 'raw-seed') {
662661
if (!isArrayBufferView(data) && !isAnyArrayBuffer(data)) {
663662
throw new ERR_INVALID_ARG_TYPE(
664-
'key.key',
663+
`${name}.key`,
665664
['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],
666665
data);
667666
}
668-
validateString(key.asymmetricKeyType, 'key.asymmetricKeyType');
667+
validateString(key.asymmetricKeyType, `${name}.asymmetricKeyType`);
669668
if (key.asymmetricKeyType === 'ec') {
670-
validateString(key.namedCurve, 'key.namedCurve');
669+
validateString(key.namedCurve, `${name}.namedCurve`);
671670
}
672-
const rawFormat = parseKeyFormat(format, undefined, 'options.format');
671+
const rawFormat = parseKeyFormat(format, undefined, `${name}.format`);
673672
return {
674-
data: getArrayBufferOrView(data, 'key.key'),
673+
data: getArrayBufferOrView(data, `${name}.key`),
675674
format: rawFormat,
676675
type: key.asymmetricKeyType,
677676
namedCurve: key.namedCurve ?? null,

lib/internal/crypto/ml_kem.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ const {
4545

4646
const {
4747
importDerKey,
48+
importJwkKey,
4849
importRawKey,
50+
validateJwk,
4951
} = require('internal/crypto/webcrypto_util');
5052

5153
const generateKeyPair = promisify(_generateKeyPair);
@@ -184,6 +186,18 @@ function mlKemImportKey(
184186
keyObject = importRawKey(isPublic, keyData, isPublic ? kKeyFormatRawPublic : kKeyFormatRawPrivate, name);
185187
break;
186188
}
189+
case 'jwk': {
190+
validateJwk(keyData, 'AKP', extractable, usagesSet, 'enc');
191+
192+
if (keyData.alg !== name)
193+
throw lazyDOMException(
194+
'JWK "alg" Parameter and algorithm name mismatch', 'DataError');
195+
196+
const isPublic = keyData.priv === undefined;
197+
verifyAcceptableMlKemKeyUse(name, isPublic, usagesSet);
198+
keyObject = importJwkKey(isPublic, keyData);
199+
break;
200+
}
187201
default:
188202
return undefined;
189203
}

lib/internal/crypto/webcrypto.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,12 @@ async function exportKeyJWK(key) {
611611
case 'ML-DSA-65':
612612
// Fall through
613613
case 'ML-DSA-87':
614+
// Fall through
615+
case 'ML-KEM-512':
616+
// Fall through
617+
case 'ML-KEM-768':
618+
// Fall through
619+
case 'ML-KEM-1024':
614620
break;
615621
case 'Ed25519':
616622
// Fall through

node.gyp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@
384384
'src/crypto/crypto_cipher.cc',
385385
'src/crypto/crypto_context.cc',
386386
'src/crypto/crypto_ec.cc',
387-
'src/crypto/crypto_ml_dsa.cc',
387+
'src/crypto/crypto_pqc.cc',
388388
'src/crypto/crypto_kem.cc',
389389
'src/crypto/crypto_hmac.cc',
390390
'src/crypto/crypto_kmac.cc',
@@ -422,7 +422,7 @@
422422
'src/crypto/crypto_clienthello.h',
423423
'src/crypto/crypto_context.h',
424424
'src/crypto/crypto_ec.h',
425-
'src/crypto/crypto_ml_dsa.h',
425+
'src/crypto/crypto_pqc.h',
426426
'src/crypto/crypto_hkdf.h',
427427
'src/crypto/crypto_pbkdf2.h',
428428
'src/crypto/crypto_sig.h',

src/crypto/crypto_keys.cc

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#include "crypto/crypto_dh.h"
66
#include "crypto/crypto_dsa.h"
77
#include "crypto/crypto_ec.h"
8-
#include "crypto/crypto_ml_dsa.h"
8+
#include "crypto/crypto_pqc.h"
99
#include "crypto/crypto_rsa.h"
1010
#include "crypto/crypto_util.h"
1111
#include "env-inl.h"
@@ -201,7 +201,37 @@ bool ExportJWKAsymmetricKey(Environment* env,
201201
case EVP_PKEY_ML_DSA_65:
202202
// Fall through
203203
case EVP_PKEY_ML_DSA_87:
204-
return ExportJwkMlDsaKey(env, key, target);
204+
// Fall through
205+
case EVP_PKEY_SLH_DSA_SHA2_128F:
206+
// Fall through
207+
case EVP_PKEY_SLH_DSA_SHA2_128S:
208+
// Fall through
209+
case EVP_PKEY_SLH_DSA_SHA2_192F:
210+
// Fall through
211+
case EVP_PKEY_SLH_DSA_SHA2_192S:
212+
// Fall through
213+
case EVP_PKEY_SLH_DSA_SHA2_256F:
214+
// Fall through
215+
case EVP_PKEY_SLH_DSA_SHA2_256S:
216+
// Fall through
217+
case EVP_PKEY_SLH_DSA_SHAKE_128F:
218+
// Fall through
219+
case EVP_PKEY_SLH_DSA_SHAKE_128S:
220+
// Fall through
221+
case EVP_PKEY_SLH_DSA_SHAKE_192F:
222+
// Fall through
223+
case EVP_PKEY_SLH_DSA_SHAKE_192S:
224+
// Fall through
225+
case EVP_PKEY_SLH_DSA_SHAKE_256F:
226+
// Fall through
227+
case EVP_PKEY_SLH_DSA_SHAKE_256S:
228+
// Fall through
229+
case EVP_PKEY_ML_KEM_512:
230+
// Fall through
231+
case EVP_PKEY_ML_KEM_768:
232+
// Fall through
233+
case EVP_PKEY_ML_KEM_1024:
234+
return ExportJwkPqcKey(env, key, target);
205235
#endif
206236
}
207237
THROW_ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE(env);
@@ -724,7 +754,7 @@ static KeyObjectData ImportJWKFromArgs(Environment* env, Local<Object> jwk) {
724754
return ImportJWKEdKey(env, jwk);
725755
} else if (*kty_string == std::string_view("AKP")) {
726756
#if OPENSSL_WITH_PQC
727-
return ImportJWKAkpKey(env, jwk);
757+
return ImportJWKPqcKey(env, jwk);
728758
#else
729759
THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported key type");
730760
return {};

0 commit comments

Comments
 (0)