Skip to content

Commit 4fe383e

Browse files
panvatargos
authored andcommitted
crypto: support ML-DSA spki/pkcs8 key formats in Web Cryptography
PR-URL: #59365 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
1 parent 93fc80a commit 4fe383e

File tree

4 files changed

+280
-22
lines changed

4 files changed

+280
-22
lines changed

doc/api/webcrypto.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -939,9 +939,9 @@ specification.
939939
| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
940940
| `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ | | ✔ | |
941941
| `'HMAC'` | | | ✔ | ✔ | ✔ | | |
942-
| `'ML-DSA-44'`[^modern-algos] | | | ✔ | | | ✔ | ✔ |
943-
| `'ML-DSA-65'`[^modern-algos] | | | ✔ | | | ✔ | ✔ |
944-
| `'ML-DSA-87'`[^modern-algos] | | | ✔ | | | ✔ | ✔ |
942+
| `'ML-DSA-44'`[^modern-algos] | | ✔ | ✔ | | | ✔ | ✔ |
943+
| `'ML-DSA-65'`[^modern-algos] | | ✔ | ✔ | | | ✔ | ✔ |
944+
| `'ML-DSA-87'`[^modern-algos] | | ✔ | ✔ | | | ✔ | ✔ |
945945
| `'RSA-OAEP'` | ✔ | ✔ | ✔ | | | | |
946946
| `'RSA-PSS'` | ✔ | ✔ | ✔ | | | | |
947947
| `'RSASSA-PKCS1-v1_5'` | ✔ | ✔ | ✔ | | | | |
@@ -1070,9 +1070,9 @@ The algorithms currently supported include:
10701070
| `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ | | ✔ | |
10711071
| `'HDKF'` | | | | ✔ | ✔ | | |
10721072
| `'HMAC'` | | | ✔ | ✔ | ✔ | | |
1073-
| `'ML-DSA-44'`[^modern-algos] | | | ✔ | | | ✔ | ✔ |
1074-
| `'ML-DSA-65'`[^modern-algos] | | | ✔ | | | ✔ | ✔ |
1075-
| `'ML-DSA-87'`[^modern-algos] | | | ✔ | | | ✔ | ✔ |
1073+
| `'ML-DSA-44'`[^modern-algos] | | ✔ | ✔ | | | ✔ | ✔ |
1074+
| `'ML-DSA-65'`[^modern-algos] | | ✔ | ✔ | | | ✔ | ✔ |
1075+
| `'ML-DSA-87'`[^modern-algos] | | ✔ | ✔ | | | ✔ | ✔ |
10761076
| `'PBKDF2'` | | | | ✔ | ✔ | | |
10771077
| `'RSA-OAEP'` | ✔ | ✔ | ✔ | | | | |
10781078
| `'RSA-PSS'` | ✔ | ✔ | ✔ | | | | |

lib/internal/crypto/ml_dsa.js

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const {
44
SafeSet,
5+
Uint8Array,
56
} = primordials;
67

78
const { Buffer } = require('buffer');
@@ -14,6 +15,10 @@ const {
1415
kKeyTypePublic,
1516
kSignJobModeSign,
1617
kSignJobModeVerify,
18+
kKeyFormatDER,
19+
kWebCryptoKeyFormatRaw,
20+
kWebCryptoKeyFormatPKCS8,
21+
kWebCryptoKeyFormatSPKI,
1722
} = internalBinding('crypto');
1823

1924
const {
@@ -44,6 +49,7 @@ const {
4449
InternalCryptoKey,
4550
PrivateKeyObject,
4651
PublicKeyObject,
52+
createPrivateKey,
4753
createPublicKey,
4854
} = require('internal/crypto/keys');
4955

@@ -106,15 +112,47 @@ async function mlDsaGenerateKey(algorithm, extractable, keyUsages) {
106112
return { __proto__: null, privateKey, publicKey };
107113
}
108114

109-
function mlDsaExportKey(key) {
115+
function mlDsaExportKey(key, format) {
110116
try {
111-
if (key.type === 'private') {
112-
const { priv } = key[kKeyObject][kHandle].exportJwk({}, false);
113-
return Buffer.alloc(32, priv, 'base64url').buffer;
114-
}
117+
switch (format) {
118+
case kWebCryptoKeyFormatRaw: {
119+
if (key.type === 'private') {
120+
const { priv } = key[kKeyObject][kHandle].exportJwk({}, false);
121+
return Buffer.alloc(32, priv, 'base64url').buffer;
122+
}
115123

116-
const { pub } = key[kKeyObject][kHandle].exportJwk({}, false);
117-
return Buffer.alloc(Buffer.byteLength(pub, 'base64url'), pub, 'base64url').buffer;
124+
const { pub } = key[kKeyObject][kHandle].exportJwk({}, false);
125+
return Buffer.alloc(Buffer.byteLength(pub, 'base64url'), pub, 'base64url').buffer;
126+
}
127+
case kWebCryptoKeyFormatSPKI: {
128+
return key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatSPKI).buffer;
129+
}
130+
case kWebCryptoKeyFormatPKCS8: {
131+
const { priv } = key[kKeyObject][kHandle].exportJwk({}, false);
132+
const seed = Buffer.alloc(32, priv, 'base64url');
133+
const buffer = new Uint8Array(54);
134+
buffer.set([
135+
0x30, 0x34, 0x02, 0x01, 0x00, 0x30, 0x0B, 0x06,
136+
0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
137+
0x03, 0x00, 0x04, 0x22, 0x80, 0x20,
138+
], 0);
139+
switch (key.algorithm.name) {
140+
case 'ML-DSA-44':
141+
buffer.set([0x11], 17);
142+
break;
143+
case 'ML-DSA-65':
144+
buffer.set([0x12], 17);
145+
break;
146+
case 'ML-DSA-87':
147+
buffer.set([0x13], 17);
148+
break;
149+
}
150+
buffer.set(seed, 22);
151+
return buffer.buffer;
152+
}
153+
default:
154+
return undefined;
155+
}
118156
} catch (err) {
119157
throw lazyDOMException(
120158
'The operation failed for an operation-specific reason',
@@ -138,6 +176,34 @@ function mlDsaImportKey(
138176
keyObject = keyData;
139177
break;
140178
}
179+
case 'spki': {
180+
verifyAcceptableMlDsaKeyUse(name, true, usagesSet);
181+
try {
182+
keyObject = createPublicKey({
183+
key: keyData,
184+
format: 'der',
185+
type: 'spki',
186+
});
187+
} catch (err) {
188+
throw lazyDOMException(
189+
'Invalid keyData', { name: 'DataError', cause: err });
190+
}
191+
break;
192+
}
193+
case 'pkcs8': {
194+
verifyAcceptableMlDsaKeyUse(name, false, usagesSet);
195+
try {
196+
keyObject = createPrivateKey({
197+
key: keyData,
198+
format: 'der',
199+
type: 'pkcs8',
200+
});
201+
} catch (err) {
202+
throw lazyDOMException(
203+
'Invalid keyData', { name: 'DataError', cause: err });
204+
}
205+
break;
206+
}
141207
case 'jwk': {
142208
if (!keyData.kty)
143209
throw lazyDOMException('Invalid keyData', 'DataError');

lib/internal/crypto/webcrypto.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,14 @@ async function exportKeySpki(key) {
357357
case 'X448':
358358
return require('internal/crypto/cfrg')
359359
.cfrgExportKey(key, kWebCryptoKeyFormatSPKI);
360+
case 'ML-DSA-44':
361+
// Fall through
362+
case 'ML-DSA-65':
363+
// Fall through
364+
case 'ML-DSA-87': {
365+
return require('internal/crypto/ml_dsa')
366+
.mlDsaExportKey(key, kWebCryptoKeyFormatSPKI);
367+
}
360368
default:
361369
return undefined;
362370
}
@@ -385,6 +393,14 @@ async function exportKeyPkcs8(key) {
385393
case 'X448':
386394
return require('internal/crypto/cfrg')
387395
.cfrgExportKey(key, kWebCryptoKeyFormatPKCS8);
396+
case 'ML-DSA-44':
397+
// Fall through
398+
case 'ML-DSA-65':
399+
// Fall through
400+
case 'ML-DSA-87': {
401+
return require('internal/crypto/ml_dsa')
402+
.mlDsaExportKey(key, kWebCryptoKeyFormatPKCS8);
403+
}
388404
default:
389405
return undefined;
390406
}

0 commit comments

Comments
 (0)