Skip to content

Commit bb326f7

Browse files
panvatargos
authored andcommitted
crypto: handle webcrypto generateKey() usages edge case
PR-URL: #43454 Reviewed-By: Tobias Nießen <[email protected]>
1 parent 0636f86 commit bb326f7

File tree

4 files changed

+65
-38
lines changed

4 files changed

+65
-38
lines changed

lib/internal/crypto/webcrypto.js

+30-10
Original file line numberDiff line numberDiff line change
@@ -89,48 +89,68 @@ async function generateKey(
8989
algorithm = normalizeAlgorithm(algorithm);
9090
validateBoolean(extractable, 'extractable');
9191
validateArray(keyUsages, 'keyUsages');
92-
if (keyUsages.length === 0) {
93-
throw lazyDOMException(
94-
'Usages cannot be empty when creating a key',
95-
'SyntaxError');
96-
}
92+
let result;
93+
let resultType;
9794
switch (algorithm.name) {
9895
case 'RSASSA-PKCS1-v1_5':
9996
// Fall through
10097
case 'RSA-PSS':
10198
// Fall through
10299
case 'RSA-OAEP':
103-
return lazyRequire('internal/crypto/rsa')
100+
resultType = 'CryptoKeyPair';
101+
result = await lazyRequire('internal/crypto/rsa')
104102
.rsaKeyGenerate(algorithm, extractable, keyUsages);
103+
break;
105104
case 'Ed25519':
106105
// Fall through
107106
case 'Ed448':
108107
// Fall through
109108
case 'X25519':
110109
// Fall through
111110
case 'X448':
112-
return lazyRequire('internal/crypto/cfrg')
111+
resultType = 'CryptoKeyPair';
112+
result = await lazyRequire('internal/crypto/cfrg')
113113
.cfrgGenerateKey(algorithm, extractable, keyUsages);
114+
break;
114115
case 'ECDSA':
115116
// Fall through
116117
case 'ECDH':
117-
return lazyRequire('internal/crypto/ec')
118+
resultType = 'CryptoKeyPair';
119+
result = await lazyRequire('internal/crypto/ec')
118120
.ecGenerateKey(algorithm, extractable, keyUsages);
121+
break;
119122
case 'HMAC':
120-
return lazyRequire('internal/crypto/mac')
123+
resultType = 'CryptoKey';
124+
result = await lazyRequire('internal/crypto/mac')
121125
.hmacGenerateKey(algorithm, extractable, keyUsages);
126+
break;
122127
case 'AES-CTR':
123128
// Fall through
124129
case 'AES-CBC':
125130
// Fall through
126131
case 'AES-GCM':
127132
// Fall through
128133
case 'AES-KW':
129-
return lazyRequire('internal/crypto/aes')
134+
resultType = 'CryptoKey';
135+
result = await lazyRequire('internal/crypto/aes')
130136
.aesGenerateKey(algorithm, extractable, keyUsages);
137+
break;
131138
default:
132139
throw lazyDOMException('Unrecognized name.');
133140
}
141+
142+
if (
143+
(resultType === 'CryptoKey' &&
144+
(result.type === 'secret' || result.type === 'private') &&
145+
result.usages.length === 0) ||
146+
(resultType === 'CryptoKeyPair' && result.privateKey.usages.length === 0)
147+
) {
148+
throw lazyDOMException(
149+
'Usages cannot be empty when creating a key.',
150+
'SyntaxError');
151+
}
152+
153+
return result;
134154
}
135155

136156
async function deriveBits(algorithm, baseKey, length) {

test/parallel/test-webcrypto-derivebits-ecdh.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ async function prepareKeys() {
201201
{
202202
name: 'ECDSA',
203203
namedCurve: 'P-521'
204-
}, false, ['verify']);
204+
}, false, ['sign', 'verify']);
205205

206206
await assert.rejects(subtle.deriveBits({
207207
name: 'ECDH',

test/parallel/test-webcrypto-derivekey-ecdh.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ async function prepareKeys() {
165165
namedCurve: 'P-521'
166166
},
167167
false,
168-
['verify']);
168+
['sign', 'verify']);
169169

170170
await assert.rejects(
171171
subtle.deriveKey(

test/parallel/test-webcrypto-keygen.js

+33-26
Original file line numberDiff line numberDiff line change
@@ -29,143 +29,131 @@ const allUsages = [
2929
const vectors = {
3030
'AES-CTR': {
3131
algorithm: { length: 256 },
32+
result: 'CryptoKey',
3233
usages: [
3334
'encrypt',
3435
'decrypt',
3536
'wrapKey',
3637
'unwrapKey',
3738
],
38-
mandatoryUsages: []
3939
},
4040
'AES-CBC': {
4141
algorithm: { length: 256 },
42+
result: 'CryptoKey',
4243
usages: [
4344
'encrypt',
4445
'decrypt',
4546
'wrapKey',
4647
'unwrapKey',
4748
],
48-
mandatoryUsages: []
4949
},
5050
'AES-GCM': {
5151
algorithm: { length: 256 },
52+
result: 'CryptoKey',
5253
usages: [
5354
'encrypt',
5455
'decrypt',
5556
'wrapKey',
5657
'unwrapKey',
5758
],
58-
mandatoryUsages: []
5959
},
6060
'AES-KW': {
6161
algorithm: { length: 256 },
62+
result: 'CryptoKey',
6263
usages: [
6364
'wrapKey',
6465
'unwrapKey',
6566
],
66-
mandatoryUsages: []
6767
},
6868
'HMAC': {
6969
algorithm: { length: 256, hash: 'SHA-256' },
70+
result: 'CryptoKey',
7071
usages: [
7172
'sign',
7273
'verify',
7374
],
74-
mandatoryUsages: []
7575
},
7676
'RSASSA-PKCS1-v1_5': {
7777
algorithm: {
7878
modulusLength: 1024,
7979
publicExponent: new Uint8Array([1, 0, 1]),
8080
hash: 'SHA-256'
8181
},
82+
result: 'CryptoKeyPair',
8283
usages: [
8384
'sign',
8485
'verify',
8586
],
86-
mandatoryUsages: ['sign'],
8787
},
8888
'RSA-PSS': {
8989
algorithm: {
9090
modulusLength: 1024,
9191
publicExponent: new Uint8Array([1, 0, 1]),
9292
hash: 'SHA-256'
9393
},
94+
result: 'CryptoKeyPair',
9495
usages: [
9596
'sign',
9697
'verify',
9798
],
98-
mandatoryUsages: ['sign']
9999
},
100100
'RSA-OAEP': {
101101
algorithm: {
102102
modulusLength: 1024,
103103
publicExponent: new Uint8Array([1, 0, 1]),
104104
hash: 'SHA-256'
105105
},
106+
result: 'CryptoKeyPair',
106107
usages: [
107108
'encrypt',
108109
'decrypt',
109110
'wrapKey',
110111
'unwrapKey',
111112
],
112-
mandatoryUsages: [
113-
'decrypt',
114-
'unwrapKey',
115-
]
116113
},
117114
'ECDSA': {
118115
algorithm: { namedCurve: 'P-521' },
116+
result: 'CryptoKeyPair',
119117
usages: [
120118
'sign',
121119
'verify',
122120
],
123-
mandatoryUsages: ['sign']
124121
},
125122
'ECDH': {
126123
algorithm: { namedCurve: 'P-521' },
124+
result: 'CryptoKeyPair',
127125
usages: [
128126
'deriveKey',
129127
'deriveBits',
130128
],
131-
mandatoryUsages: [
132-
'deriveKey',
133-
'deriveBits',
134-
]
135129
},
136130
'Ed25519': {
131+
result: 'CryptoKeyPair',
137132
usages: [
138133
'sign',
139134
'verify',
140135
],
141-
mandatoryUsages: ['sign']
142136
},
143137
'Ed448': {
138+
result: 'CryptoKeyPair',
144139
usages: [
145140
'sign',
146141
'verify',
147142
],
148-
mandatoryUsages: ['sign']
149143
},
150144
'X25519': {
145+
result: 'CryptoKeyPair',
151146
usages: [
152147
'deriveKey',
153148
'deriveBits',
154149
],
155-
mandatoryUsages: [
156-
'deriveKey',
157-
'deriveBits',
158-
]
159150
},
160151
'X448': {
152+
result: 'CryptoKeyPair',
161153
usages: [
162154
'deriveKey',
163155
'deriveBits',
164156
],
165-
mandatoryUsages: [
166-
'deriveKey',
167-
'deriveBits',
168-
]
169157
},
170158
};
171159

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

210+
// For CryptoKeyPair results the private key
211+
// usages must not be empty.
212+
// - ECDH(-like) algorithm key pairs only have private key usages
213+
// - Signing algorithm key pairs may pass a non-empty array but
214+
// with only a public key usage
215+
if (
216+
vectors[name].result === 'CryptoKeyPair' &&
217+
vectors[name].usages.includes('verify')
218+
) {
219+
await assert.rejects(
220+
subtle.generateKey(
221+
{
222+
name, ...vectors[name].algorithm
223+
},
224+
true,
225+
['verify']),
226+
{ message: /Usages cannot be empty/ });
227+
}
228+
222229
const invalidUsages = [];
223230
allUsages.forEach((usage) => {
224231
if (!vectors[name].usages.includes(usage))

0 commit comments

Comments
 (0)