Skip to content

Commit

Permalink
crypto: handle webcrypto generateKey() usages edge case
Browse files Browse the repository at this point in the history
PR-URL: #43454
Reviewed-By: Tobias Nießen <[email protected]>
  • Loading branch information
panva authored and targos committed Jul 20, 2022
1 parent 04b8d22 commit 8b3ce36
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 38 deletions.
40 changes: 30 additions & 10 deletions lib/internal/crypto/webcrypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,48 +89,68 @@ async function generateKey(
algorithm = normalizeAlgorithm(algorithm);
validateBoolean(extractable, 'extractable');
validateArray(keyUsages, 'keyUsages');
if (keyUsages.length === 0) {
throw lazyDOMException(
'Usages cannot be empty when creating a key',
'SyntaxError');
}
let result;
let resultType;
switch (algorithm.name) {
case 'RSASSA-PKCS1-v1_5':
// Fall through
case 'RSA-PSS':
// Fall through
case 'RSA-OAEP':
return lazyRequire('internal/crypto/rsa')
resultType = 'CryptoKeyPair';
result = await lazyRequire('internal/crypto/rsa')
.rsaKeyGenerate(algorithm, extractable, keyUsages);
break;
case 'Ed25519':
// Fall through
case 'Ed448':
// Fall through
case 'X25519':
// Fall through
case 'X448':
return lazyRequire('internal/crypto/cfrg')
resultType = 'CryptoKeyPair';
result = await lazyRequire('internal/crypto/cfrg')
.cfrgGenerateKey(algorithm, extractable, keyUsages);
break;
case 'ECDSA':
// Fall through
case 'ECDH':
return lazyRequire('internal/crypto/ec')
resultType = 'CryptoKeyPair';
result = await lazyRequire('internal/crypto/ec')
.ecGenerateKey(algorithm, extractable, keyUsages);
break;
case 'HMAC':
return lazyRequire('internal/crypto/mac')
resultType = 'CryptoKey';
result = await lazyRequire('internal/crypto/mac')
.hmacGenerateKey(algorithm, extractable, keyUsages);
break;
case 'AES-CTR':
// Fall through
case 'AES-CBC':
// Fall through
case 'AES-GCM':
// Fall through
case 'AES-KW':
return lazyRequire('internal/crypto/aes')
resultType = 'CryptoKey';
result = await lazyRequire('internal/crypto/aes')
.aesGenerateKey(algorithm, extractable, keyUsages);
break;
default:
throw lazyDOMException('Unrecognized name.');
}

if (
(resultType === 'CryptoKey' &&
(result.type === 'secret' || result.type === 'private') &&
result.usages.length === 0) ||
(resultType === 'CryptoKeyPair' && result.privateKey.usages.length === 0)
) {
throw lazyDOMException(
'Usages cannot be empty when creating a key.',
'SyntaxError');
}

return result;
}

async function deriveBits(algorithm, baseKey, length) {
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-webcrypto-derivebits-ecdh.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ async function prepareKeys() {
{
name: 'ECDSA',
namedCurve: 'P-521'
}, false, ['verify']);
}, false, ['sign', 'verify']);

await assert.rejects(subtle.deriveBits({
name: 'ECDH',
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-webcrypto-derivekey-ecdh.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ async function prepareKeys() {
namedCurve: 'P-521'
},
false,
['verify']);
['sign', 'verify']);

await assert.rejects(
subtle.deriveKey(
Expand Down
59 changes: 33 additions & 26 deletions test/parallel/test-webcrypto-keygen.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,143 +29,131 @@ const allUsages = [
const vectors = {
'AES-CTR': {
algorithm: { length: 256 },
result: 'CryptoKey',
usages: [
'encrypt',
'decrypt',
'wrapKey',
'unwrapKey',
],
mandatoryUsages: []
},
'AES-CBC': {
algorithm: { length: 256 },
result: 'CryptoKey',
usages: [
'encrypt',
'decrypt',
'wrapKey',
'unwrapKey',
],
mandatoryUsages: []
},
'AES-GCM': {
algorithm: { length: 256 },
result: 'CryptoKey',
usages: [
'encrypt',
'decrypt',
'wrapKey',
'unwrapKey',
],
mandatoryUsages: []
},
'AES-KW': {
algorithm: { length: 256 },
result: 'CryptoKey',
usages: [
'wrapKey',
'unwrapKey',
],
mandatoryUsages: []
},
'HMAC': {
algorithm: { length: 256, hash: 'SHA-256' },
result: 'CryptoKey',
usages: [
'sign',
'verify',
],
mandatoryUsages: []
},
'RSASSA-PKCS1-v1_5': {
algorithm: {
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256'
},
result: 'CryptoKeyPair',
usages: [
'sign',
'verify',
],
mandatoryUsages: ['sign'],
},
'RSA-PSS': {
algorithm: {
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256'
},
result: 'CryptoKeyPair',
usages: [
'sign',
'verify',
],
mandatoryUsages: ['sign']
},
'RSA-OAEP': {
algorithm: {
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256'
},
result: 'CryptoKeyPair',
usages: [
'encrypt',
'decrypt',
'wrapKey',
'unwrapKey',
],
mandatoryUsages: [
'decrypt',
'unwrapKey',
]
},
'ECDSA': {
algorithm: { namedCurve: 'P-521' },
result: 'CryptoKeyPair',
usages: [
'sign',
'verify',
],
mandatoryUsages: ['sign']
},
'ECDH': {
algorithm: { namedCurve: 'P-521' },
result: 'CryptoKeyPair',
usages: [
'deriveKey',
'deriveBits',
],
mandatoryUsages: [
'deriveKey',
'deriveBits',
]
},
'Ed25519': {
result: 'CryptoKeyPair',
usages: [
'sign',
'verify',
],
mandatoryUsages: ['sign']
},
'Ed448': {
result: 'CryptoKeyPair',
usages: [
'sign',
'verify',
],
mandatoryUsages: ['sign']
},
'X25519': {
result: 'CryptoKeyPair',
usages: [
'deriveKey',
'deriveBits',
],
mandatoryUsages: [
'deriveKey',
'deriveBits',
]
},
'X448': {
result: 'CryptoKeyPair',
usages: [
'deriveKey',
'deriveBits',
],
mandatoryUsages: [
'deriveKey',
'deriveBits',
]
},
};

Expand Down Expand Up @@ -219,6 +207,25 @@ const vectors = {
[]),
{ message: /Usages cannot be empty/ });

// For CryptoKeyPair results the private key
// usages must not be empty.
// - ECDH(-like) algorithm key pairs only have private key usages
// - Signing algorithm key pairs may pass a non-empty array but
// with only a public key usage
if (
vectors[name].result === 'CryptoKeyPair' &&
vectors[name].usages.includes('verify')
) {
await assert.rejects(
subtle.generateKey(
{
name, ...vectors[name].algorithm
},
true,
['verify']),
{ message: /Usages cannot be empty/ });
}

const invalidUsages = [];
allUsages.forEach((usage) => {
if (!vectors[name].usages.includes(usage))
Expand Down

0 comments on commit 8b3ce36

Please sign in to comment.