Skip to content

Commit

Permalink
crypto: fix webcrypto private/secret import with empty usages
Browse files Browse the repository at this point in the history
Refs: nodejs#47864
PR-URL: nodejs#47877
Refs: nodejs#47864
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
panva authored and MoLow committed Jul 6, 2023
1 parent 81d8d95 commit de1338d
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 47 deletions.
29 changes: 22 additions & 7 deletions lib/internal/crypto/webcrypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -600,52 +600,67 @@ async function importKey(
});

algorithm = normalizeAlgorithm(algorithm, 'importKey');
let result;
switch (algorithm.name) {
case 'RSASSA-PKCS1-v1_5':
// Fall through
case 'RSA-PSS':
// Fall through
case 'RSA-OAEP':
return require('internal/crypto/rsa')
result = await require('internal/crypto/rsa')
.rsaImportKey(format, keyData, algorithm, extractable, keyUsages);
break;
case 'ECDSA':
// Fall through
case 'ECDH':
return require('internal/crypto/ec')
result = await require('internal/crypto/ec')
.ecImportKey(format, keyData, algorithm, extractable, keyUsages);
break;
case 'Ed25519':
// Fall through
case 'Ed448':
// Fall through
case 'X25519':
// Fall through
case 'X448':
return require('internal/crypto/cfrg')
result = await require('internal/crypto/cfrg')
.cfrgImportKey(format, keyData, algorithm, extractable, keyUsages);
break;
case 'HMAC':
return require('internal/crypto/mac')
result = await require('internal/crypto/mac')
.hmacImportKey(format, keyData, algorithm, extractable, keyUsages);
break;
case 'AES-CTR':
// Fall through
case 'AES-CBC':
// Fall through
case 'AES-GCM':
// Fall through
case 'AES-KW':
return require('internal/crypto/aes')
result = await require('internal/crypto/aes')
.aesImportKey(algorithm, format, keyData, extractable, keyUsages);
break;
case 'HKDF':
// Fall through
case 'PBKDF2':
return importGenericSecretKey(
result = await importGenericSecretKey(
algorithm,
format,
keyData,
extractable,
keyUsages);
break;
default:
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}

throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
if ((result.type === 'secret' || result.type === 'private') && result.usages.length === 0) {
throw lazyDOMException(
`Usages cannot be empty when importing a ${result.type} key.`,
'SyntaxError');
}

return result;
}

// subtle.wrapKey() is essentially a subtle.exportKey() followed
Expand Down
18 changes: 18 additions & 0 deletions test/parallel/test-webcrypto-export-import-cfrg.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,15 @@ async function testImportPkcs8({ name, privateUsages }, extractable) {
message: /key is not extractable/
});
}

await assert.rejects(
subtle.importKey(
'pkcs8',
keyData[name].pkcs8,
{ name },
extractable,
[/* empty usages */]),
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
}

async function testImportJwk({ name, publicUsages, privateUsages }, extractable) {
Expand Down Expand Up @@ -311,6 +320,15 @@ async function testImportJwk({ name, publicUsages, privateUsages }, extractable)
publicUsages),
{ message: 'JWK "crv" Parameter and algorithm name mismatch' });
}

await assert.rejects(
subtle.importKey(
'jwk',
{ ...jwk },
{ name },
extractable,
[/* empty usages */]),
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
}

async function testImportRaw({ name, publicUsages }) {
Expand Down
18 changes: 18 additions & 0 deletions test/parallel/test-webcrypto-export-import-ec.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,15 @@ async function testImportPkcs8(
message: /key is not extractable/
});
}

await assert.rejects(
subtle.importKey(
'pkcs8',
keyData[namedCurve].pkcs8,
{ name, namedCurve },
extractable,
[/* empty usages */]),
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
}

async function testImportJwk(
Expand Down Expand Up @@ -312,6 +321,15 @@ async function testImportJwk(
privateUsages),
{ message: 'JWK "crv" does not match the requested algorithm' });
}

await assert.rejects(
subtle.importKey(
'jwk',
{ ...jwk },
{ name, namedCurve },
extractable,
[/* empty usages */]),
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
}

async function testImportRaw({ name, publicUsages }, namedCurve) {
Expand Down
18 changes: 18 additions & 0 deletions test/parallel/test-webcrypto-export-import-rsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,15 @@ async function testImportPkcs8(
message: /key is not extractable/
});
}

await assert.rejects(
subtle.importKey(
'pkcs8',
keyData[size].pkcs8,
{ name, hash },
extractable,
[/* empty usages */]),
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
}

async function testImportJwk(
Expand Down Expand Up @@ -495,6 +504,15 @@ async function testImportJwk(
privateUsages),
{ message: 'JWK "alg" does not match the requested algorithm' });
}

await assert.rejects(
subtle.importKey(
'jwk',
{ ...jwk },
{ name, hash },
extractable,
[/* empty usages */]),
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
}

// combinations to test
Expand Down
24 changes: 24 additions & 0 deletions test/parallel/test-webcrypto-export-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,18 @@ const { subtle } = webcrypto;
assert.deepStrictEqual(
Buffer.from(jwk.k, 'base64').toString('hex'),
Buffer.from(raw).toString('hex'));

await assert.rejects(
subtle.importKey(
'raw',
keyData,
{
name: 'HMAC',
hash: 'SHA-256'
},
true,
[/* empty usages */]),
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a secret key.' });
}

test().then(common.mustCall());
Expand Down Expand Up @@ -126,6 +138,18 @@ const { subtle } = webcrypto;
assert.deepStrictEqual(
Buffer.from(jwk.k, 'base64').toString('hex'),
Buffer.from(raw).toString('hex'));

await assert.rejects(
subtle.importKey(
'raw',
keyData,
{
name: 'AES-CTR',
length: 256,
},
true,
[/* empty usages */]),
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a secret key.' });
}

test().then(common.mustCall());
Expand Down
13 changes: 0 additions & 13 deletions test/parallel/test-webcrypto-sign-verify-ecdsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ async function testSign({ name,
plaintext }) {
const [
publicKey,
noSignPrivateKey,
privateKey,
hmacKey,
rsaKeys,
Expand All @@ -161,12 +160,6 @@ async function testSign({ name,
{ name, namedCurve },
false,
['verify']),
subtle.importKey(
'pkcs8',
privateKeyBuffer,
{ name, namedCurve },
false,
[ /* No usages */ ]),
subtle.importKey(
'pkcs8',
privateKeyBuffer,
Expand Down Expand Up @@ -214,12 +207,6 @@ async function testSign({ name,
message: /Unable to use this key to sign/
});

// Test failure when no sign usage
await assert.rejects(
subtle.sign({ name, hash }, noSignPrivateKey, plaintext), {
message: /Unable to use this key to sign/
});

// Test failure when using the wrong algorithms
await assert.rejects(
subtle.sign({ name, hash }, hmacKey, plaintext), {
Expand Down
13 changes: 0 additions & 13 deletions test/parallel/test-webcrypto-sign-verify-eddsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ async function testSign({ name,
data }) {
const [
publicKey,
noSignPrivateKey,
privateKey,
hmacKey,
rsaKeys,
Expand All @@ -143,12 +142,6 @@ async function testSign({ name,
{ name },
false,
['verify']),
subtle.importKey(
'pkcs8',
privateKeyBuffer,
{ name },
false,
[ /* No usages */ ]),
subtle.importKey(
'pkcs8',
privateKeyBuffer,
Expand Down Expand Up @@ -197,12 +190,6 @@ async function testSign({ name,
message: /Unable to use this key to sign/
});

// Test failure when no sign usage
await assert.rejects(
subtle.sign({ name }, noSignPrivateKey, data), {
message: /Unable to use this key to sign/
});

// Test failure when using the wrong algorithms
await assert.rejects(
subtle.sign({ name }, hmacKey, data), {
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-webcrypto-sign-verify-hmac.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ async function testVerify({ hash,
keyBuffer,
{ name, hash },
false,
[ /* No usages */ ]),
['sign']),
subtle.generateKey(
{
name: 'RSA-PSS',
Expand Down
13 changes: 0 additions & 13 deletions test/parallel/test-webcrypto-sign-verify-rsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ async function testSign({
}) {
const [
publicKey,
noSignPrivateKey,
privateKey,
hmacKey,
ecdsaKeys,
Expand All @@ -143,12 +142,6 @@ async function testSign({
{ name: algorithm.name, hash },
false,
['verify']),
subtle.importKey(
'pkcs8',
privateKeyBuffer,
{ name: algorithm.name, hash },
false,
[ /* No usages */ ]),
subtle.importKey(
'pkcs8',
privateKeyBuffer,
Expand Down Expand Up @@ -189,12 +182,6 @@ async function testSign({
message: /Unable to use this key to sign/
});

// Test failure when no sign usage
await assert.rejects(
subtle.sign(algorithm, noSignPrivateKey, plaintext), {
message: /Unable to use this key to sign/
});

// Test failure when using the wrong algorithms
await assert.rejects(
subtle.sign(algorithm, hmacKey, plaintext), {
Expand Down

0 comments on commit de1338d

Please sign in to comment.