Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit dc034a4

Browse filesBrowse files
panvaaduh95
authored andcommitted
crypto: reject ML-KEM/ML-DSA PKCS#8 import without seed in SubtleCrypto
Reject importing ML-KEM and ML-DSA PKCS#8 private keys that do not include a seed, throwing NotSupportedError. Also add tests for importing PKCS#8 keys with a mismatched expanded key. Refs: https://redirect.github.com/WICG/webcrypto-modern-algos/pull/34 PR-URL: #62218 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Mattias Buelens <mattias@buelens.com>
1 parent 384a410 commit dc034a4
Copy full SHA for dc034a4

5 files changed

+100-56Lines changed: 100 additions & 56 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎doc/api/webcrypto.md‎

Copy file name to clipboardExpand all lines: doc/api/webcrypto.md
+4Lines changed: 4 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,10 @@ The {CryptoKey} (secret key) generating algorithms supported include:
12591259
<!-- YAML
12601260
added: v15.0.0
12611261
changes:
1262+
- version: REPLACEME
1263+
pr-url: https://github.com/nodejs/node/pull/62218
1264+
description: Importing ML-DSA and ML-KEM PKCS#8 keys
1265+
without a seed is no longer supported.
12621266
- version: v24.8.0
12631267
pr-url: https://github.com/nodejs/node/pull/59647
12641268
description: KMAC algorithms are now supported.
Collapse file

‎lib/internal/crypto/ml_dsa.js‎

Copy file name to clipboardExpand all lines: lib/internal/crypto/ml_dsa.js
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,19 @@ function mlDsaImportKey(
192192
}
193193
case 'pkcs8': {
194194
verifyAcceptableMlDsaKeyUse(name, false, usagesSet);
195+
196+
const privOnlyLengths = {
197+
'__proto__': null,
198+
'ML-DSA-44': 2588,
199+
'ML-DSA-65': 4060,
200+
'ML-DSA-87': 4924,
201+
};
202+
if (keyData.byteLength === privOnlyLengths[name]) {
203+
throw lazyDOMException(
204+
'Importing an ML-DSA PKCS#8 key without a seed is not supported',
205+
'NotSupportedError');
206+
}
207+
195208
try {
196209
keyObject = createPrivateKey({
197210
key: keyData,
Collapse file

‎lib/internal/crypto/ml_kem.js‎

Copy file name to clipboardExpand all lines: lib/internal/crypto/ml_kem.js
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,19 @@ function mlKemImportKey(
182182
}
183183
case 'pkcs8': {
184184
verifyAcceptableMlKemKeyUse(name, false, usagesSet);
185+
186+
const privOnlyLengths = {
187+
'__proto__': null,
188+
'ML-KEM-512': 1660,
189+
'ML-KEM-768': 2428,
190+
'ML-KEM-1024': 3196,
191+
};
192+
if (keyData.byteLength === privOnlyLengths[name]) {
193+
throw lazyDOMException(
194+
'Importing an ML-KEM PKCS#8 key without a seed is not supported',
195+
'NotSupportedError');
196+
}
197+
185198
try {
186199
keyObject = createPrivateKey({
187200
key: keyData,
Collapse file

‎test/parallel/test-webcrypto-export-import-ml-dsa.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-webcrypto-export-import-ml-dsa.js
+35-28Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ if (!hasOpenSSL(3, 5))
1212

1313
const assert = require('assert');
1414
const { subtle } = globalThis.crypto;
15+
const { createPrivateKey } = require('crypto');
1516

1617
const fixtures = require('../common/fixtures');
1718

@@ -196,41 +197,32 @@ async function testImportPkcs8SeedOnly({ name, privateUsages }, extractable) {
196197
}
197198

198199
async function testImportPkcs8PrivOnly({ name, privateUsages }, extractable) {
199-
const key = await subtle.importKey(
200-
'pkcs8',
201-
keyData[name].pkcs8_priv_only,
202-
{ name },
203-
extractable,
204-
privateUsages);
205-
assert.strictEqual(key.type, 'private');
206-
assert.strictEqual(key.extractable, extractable);
207-
assert.deepStrictEqual(key.usages, privateUsages);
208-
assert.deepStrictEqual(key.algorithm.name, name);
209-
assert.strictEqual(key.algorithm, key.algorithm);
210-
assert.strictEqual(key.usages, key.usages);
211-
212-
if (extractable) {
213-
await assert.rejects(subtle.exportKey('pkcs8', key), (err) => {
214-
assert.strictEqual(err.name, 'OperationError');
215-
assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED');
216-
assert.strictEqual(err.cause.message, 'Failed to get raw seed');
217-
return true;
200+
await assert.rejects(
201+
subtle.importKey(
202+
'pkcs8',
203+
keyData[name].pkcs8_priv_only,
204+
{ name },
205+
extractable,
206+
privateUsages),
207+
{
208+
name: 'NotSupportedError',
209+
message: 'Importing an ML-DSA PKCS#8 key without a seed is not supported',
218210
});
219-
} else {
220-
await assert.rejects(
221-
subtle.exportKey('pkcs8', key), {
222-
message: /key is not extractable/
223-
});
224-
}
211+
}
225212

213+
async function testImportPkcs8MismatchedSeed({ name, privateUsages }, extractable) {
214+
const modified = Buffer.from(keyData[name].pkcs8);
215+
modified[30] ^= 0xff;
226216
await assert.rejects(
227217
subtle.importKey(
228218
'pkcs8',
229-
keyData[name].pkcs8_seed_only,
219+
modified,
230220
{ name },
231221
extractable,
232-
[/* empty usages */]),
233-
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
222+
privateUsages),
223+
{
224+
name: 'DataError',
225+
});
234226
}
235227

236228
async function testImportJwk({ name, publicUsages, privateUsages }, extractable) {
@@ -493,6 +485,7 @@ async function testImportRawSeed({ name, privateUsages }, extractable) {
493485
tests.push(testImportPkcs8(vector, extractable));
494486
tests.push(testImportPkcs8SeedOnly(vector, extractable));
495487
tests.push(testImportPkcs8PrivOnly(vector, extractable));
488+
tests.push(testImportPkcs8MismatchedSeed(vector, extractable));
496489
tests.push(testImportJwk(vector, extractable));
497490
tests.push(testImportRawSeed(vector, extractable));
498491
tests.push(testImportRawPublic(vector, extractable));
@@ -509,3 +502,17 @@ async function testImportRawSeed({ name, privateUsages }, extractable) {
509502
message: 'Unable to import ML-DSA-44 using raw format',
510503
});
511504
})().then(common.mustCall());
505+
506+
(async function() {
507+
for (const { name, privateUsages } of testVectors) {
508+
const pem = fixtures.readKey(getKeyFileName(name.toLowerCase(), 'private_priv_only'), 'ascii');
509+
const keyObject = createPrivateKey(pem);
510+
const key = keyObject.toCryptoKey({ name }, true, privateUsages);
511+
await assert.rejects(subtle.exportKey('pkcs8', key), (err) => {
512+
assert.strictEqual(err.name, 'OperationError');
513+
assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED');
514+
assert.strictEqual(err.cause.message, 'Failed to get raw seed');
515+
return true;
516+
});
517+
}
518+
})().then(common.mustCall());
Collapse file

‎test/parallel/test-webcrypto-export-import-ml-kem.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-webcrypto-export-import-ml-kem.js
+35-28Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ if (!hasOpenSSL(3, 5))
1212

1313
const assert = require('assert');
1414
const { subtle } = globalThis.crypto;
15+
const { createPrivateKey } = require('crypto');
1516

1617
const fixtures = require('../common/fixtures');
1718

@@ -179,41 +180,32 @@ async function testImportPkcs8SeedOnly({ name, privateUsages }, extractable) {
179180
}
180181

181182
async function testImportPkcs8PrivOnly({ name, privateUsages }, extractable) {
182-
const key = await subtle.importKey(
183-
'pkcs8',
184-
keyData[name].pkcs8_priv_only,
185-
{ name },
186-
extractable,
187-
privateUsages);
188-
assert.strictEqual(key.type, 'private');
189-
assert.strictEqual(key.extractable, extractable);
190-
assert.deepStrictEqual(key.usages, privateUsages);
191-
assert.deepStrictEqual(key.algorithm.name, name);
192-
assert.strictEqual(key.algorithm, key.algorithm);
193-
assert.strictEqual(key.usages, key.usages);
194-
195-
if (extractable) {
196-
await assert.rejects(subtle.exportKey('pkcs8', key), (err) => {
197-
assert.strictEqual(err.name, 'OperationError');
198-
assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED');
199-
assert.strictEqual(err.cause.message, 'Failed to get raw seed');
200-
return true;
183+
await assert.rejects(
184+
subtle.importKey(
185+
'pkcs8',
186+
keyData[name].pkcs8_priv_only,
187+
{ name },
188+
extractable,
189+
privateUsages),
190+
{
191+
name: 'NotSupportedError',
192+
message: 'Importing an ML-KEM PKCS#8 key without a seed is not supported',
201193
});
202-
} else {
203-
await assert.rejects(
204-
subtle.exportKey('pkcs8', key), {
205-
message: /key is not extractable/
206-
});
207-
}
194+
}
208195

196+
async function testImportPkcs8MismatchedSeed({ name, privateUsages }, extractable) {
197+
const modified = Buffer.from(keyData[name].pkcs8);
198+
modified[30] ^= 0xff;
209199
await assert.rejects(
210200
subtle.importKey(
211201
'pkcs8',
212-
keyData[name].pkcs8_seed_only,
202+
modified,
213203
{ name },
214204
extractable,
215-
[/* empty usages */]),
216-
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
205+
privateUsages),
206+
{
207+
name: 'DataError',
208+
});
217209
}
218210

219211
async function testImportRawPublic({ name, publicUsages }, extractable) {
@@ -298,6 +290,7 @@ async function testImportRawSeed({ name, privateUsages }, extractable) {
298290
tests.push(testImportPkcs8(vector, extractable));
299291
tests.push(testImportPkcs8SeedOnly(vector, extractable));
300292
tests.push(testImportPkcs8PrivOnly(vector, extractable));
293+
tests.push(testImportPkcs8MismatchedSeed(vector, extractable));
301294
tests.push(testImportRawSeed(vector, extractable));
302295
tests.push(testImportRawPublic(vector, extractable));
303296
}
@@ -313,3 +306,17 @@ async function testImportRawSeed({ name, privateUsages }, extractable) {
313306
message: 'Unable to import ML-KEM-512 using raw format',
314307
});
315308
})().then(common.mustCall());
309+
310+
(async function() {
311+
for (const { name, privateUsages } of testVectors) {
312+
const pem = fixtures.readKey(getKeyFileName(name.toLowerCase(), 'private_priv_only'), 'ascii');
313+
const keyObject = createPrivateKey(pem);
314+
const key = keyObject.toCryptoKey({ name }, true, privateUsages);
315+
await assert.rejects(subtle.exportKey('pkcs8', key), (err) => {
316+
assert.strictEqual(err.name, 'OperationError');
317+
assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED');
318+
assert.strictEqual(err.cause.message, 'Failed to get raw seed');
319+
return true;
320+
});
321+
}
322+
})().then(common.mustCall());

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.