Skip to content

Commit 0e43511

Browse files
committed
crypto: add JWK support for ML-KEM and SLH-DSA key types
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
1 parent dfe438d commit 0e43511

39 files changed

+842
-357
lines changed

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
@@ -88,23 +88,23 @@ The following table lists the asymmetric key types recognized by the
8888
| `'ml-dsa-44'`[^openssl35] | ML-DSA-44 | 2.16.840.1.101.3.4.3.17 ||||| ||
8989
| `'ml-dsa-65'`[^openssl35] | ML-DSA-65 | 2.16.840.1.101.3.4.3.18 ||||| ||
9090
| `'ml-dsa-87'`[^openssl35] | ML-DSA-87 | 2.16.840.1.101.3.4.3.19 ||||| ||
91-
| `'ml-kem-512'`[^openssl35] | ML-KEM-512 | 2.16.840.1.101.3.4.4.1 ||| || ||
92-
| `'ml-kem-768'`[^openssl35] | ML-KEM-768 | 2.16.840.1.101.3.4.4.2 ||| || ||
93-
| `'ml-kem-1024'`[^openssl35] | ML-KEM-1024 | 2.16.840.1.101.3.4.4.3 ||| || ||
91+
| `'ml-kem-512'`[^openssl35] | ML-KEM-512 | 2.16.840.1.101.3.4.4.1 ||| || ||
92+
| `'ml-kem-768'`[^openssl35] | ML-KEM-768 | 2.16.840.1.101.3.4.4.2 ||| || ||
93+
| `'ml-kem-1024'`[^openssl35] | ML-KEM-1024 | 2.16.840.1.101.3.4.4.3 ||| || ||
9494
| `'rsa-pss'` | RSA PSS | 1.2.840.113549.1.1.10 ||| | | | |
9595
| `'rsa'` | RSA | 1.2.840.113549.1.1.1 |||| | | |
96-
| `'slh-dsa-sha2-128f'`[^openssl35] | SLH-DSA-SHA2-128f | 2.16.840.1.101.3.4.3.21 ||| ||| |
97-
| `'slh-dsa-sha2-128s'`[^openssl35] | SLH-DSA-SHA2-128s | 2.16.840.1.101.3.4.3.20 ||| ||| |
98-
| `'slh-dsa-sha2-192f'`[^openssl35] | SLH-DSA-SHA2-192f | 2.16.840.1.101.3.4.3.23 ||| ||| |
99-
| `'slh-dsa-sha2-192s'`[^openssl35] | SLH-DSA-SHA2-192s | 2.16.840.1.101.3.4.3.22 ||| ||| |
100-
| `'slh-dsa-sha2-256f'`[^openssl35] | SLH-DSA-SHA2-256f | 2.16.840.1.101.3.4.3.25 ||| ||| |
101-
| `'slh-dsa-sha2-256s'`[^openssl35] | SLH-DSA-SHA2-256s | 2.16.840.1.101.3.4.3.24 ||| ||| |
102-
| `'slh-dsa-shake-128f'`[^openssl35] | SLH-DSA-SHAKE-128f | 2.16.840.1.101.3.4.3.27 ||| ||| |
103-
| `'slh-dsa-shake-128s'`[^openssl35] | SLH-DSA-SHAKE-128s | 2.16.840.1.101.3.4.3.26 ||| ||| |
104-
| `'slh-dsa-shake-192f'`[^openssl35] | SLH-DSA-SHAKE-192f | 2.16.840.1.101.3.4.3.29 ||| ||| |
105-
| `'slh-dsa-shake-192s'`[^openssl35] | SLH-DSA-SHAKE-192s | 2.16.840.1.101.3.4.3.28 ||| ||| |
106-
| `'slh-dsa-shake-256f'`[^openssl35] | SLH-DSA-SHAKE-256f | 2.16.840.1.101.3.4.3.31 ||| ||| |
107-
| `'slh-dsa-shake-256s'`[^openssl35] | SLH-DSA-SHAKE-256s | 2.16.840.1.101.3.4.3.30 ||| ||| |
96+
| `'slh-dsa-sha2-128f'`[^openssl35] | SLH-DSA-SHA2-128f | 2.16.840.1.101.3.4.3.21 ||| ||| |
97+
| `'slh-dsa-sha2-128s'`[^openssl35] | SLH-DSA-SHA2-128s | 2.16.840.1.101.3.4.3.20 ||| ||| |
98+
| `'slh-dsa-sha2-192f'`[^openssl35] | SLH-DSA-SHA2-192f | 2.16.840.1.101.3.4.3.23 ||| ||| |
99+
| `'slh-dsa-sha2-192s'`[^openssl35] | SLH-DSA-SHA2-192s | 2.16.840.1.101.3.4.3.22 ||| ||| |
100+
| `'slh-dsa-sha2-256f'`[^openssl35] | SLH-DSA-SHA2-256f | 2.16.840.1.101.3.4.3.25 ||| ||| |
101+
| `'slh-dsa-sha2-256s'`[^openssl35] | SLH-DSA-SHA2-256s | 2.16.840.1.101.3.4.3.24 ||| ||| |
102+
| `'slh-dsa-shake-128f'`[^openssl35] | SLH-DSA-SHAKE-128f | 2.16.840.1.101.3.4.3.27 ||| ||| |
103+
| `'slh-dsa-shake-128s'`[^openssl35] | SLH-DSA-SHAKE-128s | 2.16.840.1.101.3.4.3.26 ||| ||| |
104+
| `'slh-dsa-shake-192f'`[^openssl35] | SLH-DSA-SHAKE-192f | 2.16.840.1.101.3.4.3.29 ||| ||| |
105+
| `'slh-dsa-shake-192s'`[^openssl35] | SLH-DSA-SHAKE-192s | 2.16.840.1.101.3.4.3.28 ||| ||| |
106+
| `'slh-dsa-shake-256f'`[^openssl35] | SLH-DSA-SHAKE-256f | 2.16.840.1.101.3.4.3.31 ||| ||| |
107+
| `'slh-dsa-shake-256s'`[^openssl35] | SLH-DSA-SHAKE-256s | 2.16.840.1.101.3.4.3.30 ||| ||| |
108108
| `'x25519'` | X25519 | 1.3.101.110 |||||| |
109109
| `'x448'` | X448 | 1.3.101.111 |||||| |
110110

@@ -2397,6 +2397,10 @@ type, value, and parameters. This method is not
23972397
<!-- YAML
23982398
added: v11.6.0
23992399
changes:
2400+
- version: REPLACEME
2401+
pr-url: https://github.com/nodejs/node/pull/62706
2402+
description: Added JWK format support for ML-KEM and SLH-DSA
2403+
key types.
24002404
- version: REPLACEME
24012405
pr-url: https://github.com/nodejs/node/pull/62240
24022406
description: Added support for `'raw-public'`, `'raw-private'`,
@@ -3926,6 +3930,10 @@ input.on('readable', () => {
39263930
<!-- YAML
39273931
added: v11.6.0
39283932
changes:
3933+
- version: REPLACEME
3934+
pr-url: https://github.com/nodejs/node/pull/62706
3935+
description: Added JWK format support for ML-KEM and SLH-DSA
3936+
key types.
39293937
- version: REPLACEME
39303938
pr-url: https://github.com/nodejs/node/pull/62453
39313939
description: Passing a CryptoKey as `key` is deprecated.
@@ -3977,6 +3985,10 @@ of the passphrase is limited to 1024 bytes.
39773985
<!-- YAML
39783986
added: v11.6.0
39793987
changes:
3988+
- version: REPLACEME
3989+
pr-url: https://github.com/nodejs/node/pull/62706
3990+
description: Added JWK format support for ML-KEM and SLH-DSA
3991+
key types.
39803992
- version: REPLACEME
39813993
pr-url: https://github.com/nodejs/node/pull/62453
39823994
description: Passing a CryptoKey as `key` is deprecated.

doc/api/webcrypto.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,6 +1132,9 @@ The algorithms currently supported include:
11321132
<!-- YAML
11331133
added: v15.0.0
11341134
changes:
1135+
- version: REPLACEME
1136+
pr-url: https://github.com/nodejs/node/pull/62706
1137+
description: Added JWK format support for ML-KEM key types.
11351138
- version: v24.8.0
11361139
pr-url: https://github.com/nodejs/node/pull/59647
11371140
description: KMAC algorithms are now supported.
@@ -1190,9 +1193,9 @@ specification.
11901193
| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
11911194
| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
11921195
| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
1193-
| `'ML-KEM-512'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1194-
| `'ML-KEM-768'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1195-
| `'ML-KEM-1024'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1196+
| `'ML-KEM-512'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1197+
| `'ML-KEM-768'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1198+
| `'ML-KEM-1024'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
11961199
| `'RSA-OAEP'` | ✔ | ✔ | ✔ | | | | |
11971200
| `'RSA-PSS'` | ✔ | ✔ | ✔ | | | | |
11981201
| `'RSASSA-PKCS1-v1_5'` | ✔ | ✔ | ✔ | | | | |
@@ -1280,6 +1283,9 @@ The {CryptoKey} (secret key) generating algorithms supported include:
12801283
<!-- YAML
12811284
added: v15.0.0
12821285
changes:
1286+
- version: REPLACEME
1287+
pr-url: https://github.com/nodejs/node/pull/62706
1288+
description: Added JWK format support for ML-KEM key types.
12831289
- version: v25.9.0
12841290
pr-url: https://github.com/nodejs/node/pull/62218
12851291
description: Importing ML-DSA and ML-KEM PKCS#8 keys
@@ -1353,9 +1359,9 @@ The algorithms currently supported include:
13531359
| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
13541360
| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
13551361
| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
1356-
| `'ML-KEM-512'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1357-
| `'ML-KEM-768'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1358-
| `'ML-KEM-1024'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1362+
| `'ML-KEM-512'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1363+
| `'ML-KEM-768'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
1364+
| `'ML-KEM-1024'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
13591365
| `'PBKDF2'` | | | | ✔ | ✔ | | |
13601366
| `'RSA-OAEP'` | ✔ | ✔ | ✔ | | | | |
13611367
| `'RSA-PSS'` | ✔ | ✔ | ✔ | | | | |

lib/internal/crypto/ml_kem.js

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

4444
const {
4545
importDerKey,
46+
importJwkKey,
4647
importRawKey,
48+
validateJwk,
4749
} = require('internal/crypto/webcrypto_util');
4850

4951
const generateKeyPair = promisify(_generateKeyPair);
@@ -177,6 +179,18 @@ function mlKemImportKey(
177179
keyObject = importRawKey(isPublic, keyData, isPublic ? kKeyFormatRawPublic : kKeyFormatRawPrivate, name);
178180
break;
179181
}
182+
case 'jwk': {
183+
validateJwk(keyData, 'AKP', extractable, usagesSet, 'enc');
184+
185+
if (keyData.alg !== name)
186+
throw lazyDOMException(
187+
'JWK "alg" Parameter and algorithm name mismatch', 'DataError');
188+
189+
const isPublic = keyData.priv === undefined;
190+
verifyAcceptableMlKemKeyUse(name, isPublic, usagesSet);
191+
keyObject = importJwkKey(isPublic, keyData);
192+
break;
193+
}
180194
default:
181195
return undefined;
182196
}

lib/internal/crypto/webcrypto.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,12 @@ async function exportKeyJWK(key) {
603603
case 'ML-DSA-65':
604604
// Fall through
605605
case 'ML-DSA-87':
606+
// Fall through
607+
case 'ML-KEM-512':
608+
// Fall through
609+
case 'ML-KEM-768':
610+
// Fall through
611+
case 'ML-KEM-1024':
606612
break;
607613
case 'Ed25519':
608614
// Fall through

node.gyp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@
388388
'src/crypto/crypto_cipher.cc',
389389
'src/crypto/crypto_context.cc',
390390
'src/crypto/crypto_ec.cc',
391-
'src/crypto/crypto_ml_dsa.cc',
391+
'src/crypto/crypto_pqc.cc',
392392
'src/crypto/crypto_kem.cc',
393393
'src/crypto/crypto_hmac.cc',
394394
'src/crypto/crypto_kmac.cc',
@@ -426,7 +426,7 @@
426426
'src/crypto/crypto_clienthello.h',
427427
'src/crypto/crypto_context.h',
428428
'src/crypto/crypto_ec.h',
429-
'src/crypto/crypto_ml_dsa.h',
429+
'src/crypto/crypto_pqc.h',
430430
'src/crypto/crypto_hkdf.h',
431431
'src/crypto/crypto_pbkdf2.h',
432432
'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"
@@ -200,7 +200,37 @@ bool ExportJWKAsymmetricKey(Environment* env,
200200
case EVP_PKEY_ML_DSA_65:
201201
// Fall through
202202
case EVP_PKEY_ML_DSA_87:
203-
return ExportJwkMlDsaKey(env, key, target);
203+
// Fall through
204+
case EVP_PKEY_SLH_DSA_SHA2_128F:
205+
// Fall through
206+
case EVP_PKEY_SLH_DSA_SHA2_128S:
207+
// Fall through
208+
case EVP_PKEY_SLH_DSA_SHA2_192F:
209+
// Fall through
210+
case EVP_PKEY_SLH_DSA_SHA2_192S:
211+
// Fall through
212+
case EVP_PKEY_SLH_DSA_SHA2_256F:
213+
// Fall through
214+
case EVP_PKEY_SLH_DSA_SHA2_256S:
215+
// Fall through
216+
case EVP_PKEY_SLH_DSA_SHAKE_128F:
217+
// Fall through
218+
case EVP_PKEY_SLH_DSA_SHAKE_128S:
219+
// Fall through
220+
case EVP_PKEY_SLH_DSA_SHAKE_192F:
221+
// Fall through
222+
case EVP_PKEY_SLH_DSA_SHAKE_192S:
223+
// Fall through
224+
case EVP_PKEY_SLH_DSA_SHAKE_256F:
225+
// Fall through
226+
case EVP_PKEY_SLH_DSA_SHAKE_256S:
227+
// Fall through
228+
case EVP_PKEY_ML_KEM_512:
229+
// Fall through
230+
case EVP_PKEY_ML_KEM_768:
231+
// Fall through
232+
case EVP_PKEY_ML_KEM_1024:
233+
return ExportJwkPqcKey(env, key, target);
204234
#endif
205235
}
206236
THROW_ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE(env);
@@ -723,7 +753,7 @@ static KeyObjectData ImportJWKFromArgs(Environment* env, Local<Object> jwk) {
723753
return ImportJWKEdKey(env, jwk);
724754
} else if (*kty_string == std::string_view("AKP")) {
725755
#if OPENSSL_WITH_PQC
726-
return ImportJWKAkpKey(env, jwk);
756+
return ImportJWKPqcKey(env, jwk);
727757
#else
728758
THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported key type");
729759
return {};

0 commit comments

Comments
 (0)