Skip to content

Commit 3ef38c4

Browse files
authored
crypto: use WebIDL converters in WebCryptoAPI
WebCryptoAPI functions' arguments are now coersed and validated as per their WebIDL definitions like in other Web Crypto API implementations. This further improves interoperability with other implementations of Web Crypto API. PR-URL: #46067 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent 66b1356 commit 3ef38c4

34 files changed

+2041
-423
lines changed

doc/api/webcrypto.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
<!-- YAML
44
changes:
5+
- version: REPLACEME
6+
pr-url: https://github.com/nodejs/node/pull/46067
7+
description: Arguments are now coersed and validated as per their WebIDL
8+
definitions like in other Web Crypto API implementations.
59
- version: v19.0.0
610
pr-url: https://github.com/nodejs/node/pull/44897
711
description: No longer experimental except for the `Ed25519`, `Ed448`,

lib/internal/crypto/aes.js

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ const {
3232
} = internalBinding('crypto');
3333

3434
const {
35-
getArrayBufferOrView,
3635
hasAnyNotIn,
3736
jobPromise,
3837
validateByteLength,
@@ -112,13 +111,10 @@ function getVariant(name, length) {
112111
}
113112

114113
function asyncAesCtrCipher(mode, key, data, { counter, length }) {
115-
counter = getArrayBufferOrView(counter, 'algorithm.counter');
116114
validateByteLength(counter, 'algorithm.counter', 16);
117115
// The length must specify an integer between 1 and 128. While
118116
// there is no default, this should typically be 64.
119-
if (typeof length !== 'number' ||
120-
length <= 0 ||
121-
length > kMaxCounterLength) {
117+
if (length === 0 || length > kMaxCounterLength) {
122118
throw lazyDOMException(
123119
'AES-CTR algorithm.length must be between 1 and 128',
124120
'OperationError');
@@ -135,7 +131,6 @@ function asyncAesCtrCipher(mode, key, data, { counter, length }) {
135131
}
136132

137133
function asyncAesCbcCipher(mode, key, data, { iv }) {
138-
iv = getArrayBufferOrView(iv, 'algorithm.iv');
139134
validateByteLength(iv, 'algorithm.iv', 16);
140135
return jobPromise(() => new AESCipherJob(
141136
kCryptoJobAsync,
@@ -166,12 +161,9 @@ function asyncAesGcmCipher(
166161
'OperationError'));
167162
}
168163

169-
iv = getArrayBufferOrView(iv, 'algorithm.iv');
170164
validateMaxBufferLength(iv, 'algorithm.iv');
171165

172166
if (additionalData !== undefined) {
173-
additionalData =
174-
getArrayBufferOrView(additionalData, 'algorithm.additionalData');
175167
validateMaxBufferLength(additionalData, 'algorithm.additionalData');
176168
}
177169

@@ -281,24 +273,26 @@ async function aesImportKey(
281273
break;
282274
}
283275
case 'jwk': {
284-
if (keyData == null || typeof keyData !== 'object')
285-
throw lazyDOMException('Invalid JWK keyData', 'DataError');
276+
if (!keyData.kty)
277+
throw lazyDOMException('Invalid keyData', 'DataError');
286278

287279
if (keyData.kty !== 'oct')
288-
throw lazyDOMException('Invalid key type', 'DataError');
280+
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
289281

290282
if (usagesSet.size > 0 &&
291283
keyData.use !== undefined &&
292284
keyData.use !== 'enc') {
293-
throw lazyDOMException('Invalid use type', 'DataError');
285+
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
294286
}
295287

296288
validateKeyOps(keyData.key_ops, usagesSet);
297289

298290
if (keyData.ext !== undefined &&
299291
keyData.ext === false &&
300292
extractable === true) {
301-
throw lazyDOMException('JWK is not extractable', 'DataError');
293+
throw lazyDOMException(
294+
'JWK "ext" Parameter and extractable mismatch',
295+
'DataError');
302296
}
303297

304298
const handle = new KeyObjectHandle();
@@ -308,10 +302,10 @@ async function aesImportKey(
308302
validateKeyLength(length);
309303

310304
if (keyData.alg !== undefined) {
311-
if (typeof keyData.alg !== 'string')
312-
throw lazyDOMException('Invalid alg', 'DataError');
313305
if (keyData.alg !== getAlgorithmName(algorithm.name, length))
314-
throw lazyDOMException('Algorithm mismatch', 'DataError');
306+
throw lazyDOMException(
307+
'JWK "alg" does not match the requested algorithm',
308+
'DataError');
315309
}
316310

317311
keyObject = new SecretKeyObject(handle);

lib/internal/crypto/cfrg.js

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ const {
1818
} = internalBinding('crypto');
1919

2020
const {
21-
getArrayBufferOrView,
2221
getUsagesUnion,
2322
hasAnyNotIn,
2423
jobPromise,
@@ -73,7 +72,6 @@ function verifyAcceptableCfrgKeyUse(name, isPublic, usages) {
7372

7473
function createCFRGRawKey(name, keyData, isPublic) {
7574
const handle = new KeyObjectHandle();
76-
keyData = getArrayBufferOrView(keyData, 'keyData');
7775

7876
switch (name) {
7977
case 'Ed25519':
@@ -237,12 +235,13 @@ async function cfrgImportKey(
237235
break;
238236
}
239237
case 'jwk': {
240-
if (keyData == null || typeof keyData !== 'object')
241-
throw lazyDOMException('Invalid JWK keyData', 'DataError');
238+
if (!keyData.kty)
239+
throw lazyDOMException('Invalid keyData', 'DataError');
242240
if (keyData.kty !== 'OKP')
243-
throw lazyDOMException('Invalid key type', 'DataError');
241+
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
244242
if (keyData.crv !== name)
245-
throw lazyDOMException('Subtype mismatch', 'DataError');
243+
throw lazyDOMException(
244+
'JWK "crv" Parameter and algorithm name mismatch', 'DataError');
246245
const isPublic = keyData.d === undefined;
247246

248247
if (usagesSet.size > 0 && keyData.use !== undefined) {
@@ -260,30 +259,32 @@ async function cfrgImportKey(
260259
break;
261260
}
262261
if (keyData.use !== checkUse)
263-
throw lazyDOMException('Invalid use type', 'DataError');
262+
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
264263
}
265264

266265
validateKeyOps(keyData.key_ops, usagesSet);
267266

268267
if (keyData.ext !== undefined &&
269268
keyData.ext === false &&
270269
extractable === true) {
271-
throw lazyDOMException('JWK is not extractable', 'DataError');
270+
throw lazyDOMException(
271+
'JWK "ext" Parameter and extractable mismatch',
272+
'DataError');
272273
}
273274

274275
if (keyData.alg !== undefined) {
275-
if (typeof keyData.alg !== 'string')
276-
throw lazyDOMException('Invalid alg', 'DataError');
277276
if (
278277
(name === 'Ed25519' || name === 'Ed448') &&
279278
keyData.alg !== 'EdDSA'
280279
) {
281-
throw lazyDOMException('Invalid alg', 'DataError');
280+
throw lazyDOMException(
281+
'JWK "alg" does not match the requested algorithm',
282+
'DataError');
282283
}
283284
}
284285

285286
if (!isPublic && typeof keyData.x !== 'string') {
286-
throw lazyDOMException('Invalid JWK keyData', 'DataError');
287+
throw lazyDOMException('Invalid JWK', 'DataError');
287288
}
288289

289290
verifyAcceptableCfrgKeyUse(
@@ -305,7 +306,7 @@ async function cfrgImportKey(
305306
false);
306307

307308
if (!createPublicKey(keyObject).equals(publicKeyObject)) {
308-
throw lazyDOMException('Invalid JWK keyData', 'DataError');
309+
throw lazyDOMException('Invalid JWK', 'DataError');
309310
}
310311
}
311312
break;
@@ -336,13 +337,9 @@ function eddsaSignVerify(key, data, { name, context }, signature) {
336337
if (key.type !== type)
337338
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
338339

339-
if (name === 'Ed448' && context !== undefined) {
340-
context =
341-
getArrayBufferOrView(context, 'algorithm.context');
342-
if (context.byteLength !== 0) {
343-
throw lazyDOMException(
344-
'Non zero-length context is not yet supported.', 'NotSupportedError');
345-
}
340+
if (name === 'Ed448' && context?.byteLength) {
341+
throw lazyDOMException(
342+
'Non zero-length context is not yet supported.', 'NotSupportedError');
346343
}
347344

348345
return jobPromise(() => new SignJob(

lib/internal/crypto/diffiehellman.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ const {
3434
validateInt32,
3535
validateObject,
3636
validateString,
37-
validateUint32,
3837
} = require('internal/validators');
3938

4039
const {
@@ -48,7 +47,6 @@ const {
4847

4948
const {
5049
KeyObject,
51-
isCryptoKey,
5250
} = require('internal/crypto/keys');
5351

5452
const {
@@ -320,13 +318,6 @@ function diffieHellman(options) {
320318
async function ecdhDeriveBits(algorithm, baseKey, length) {
321319
const { 'public': key } = algorithm;
322320

323-
// Null means that we're not asking for a specific number of bits, just
324-
// give us everything that is generated.
325-
if (length !== null)
326-
validateUint32(length, 'length');
327-
if (!isCryptoKey(key))
328-
throw new ERR_INVALID_ARG_TYPE('algorithm.public', 'CryptoKey', key);
329-
330321
if (key.type !== 'public') {
331322
throw lazyDOMException(
332323
'algorithm.public must be a public key', 'InvalidAccessError');

lib/internal/crypto/ec.js

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,6 @@ const {
1818
} = internalBinding('crypto');
1919

2020
const {
21-
codes: {
22-
ERR_MISSING_OPTION,
23-
}
24-
} = require('internal/errors');
25-
26-
const {
27-
getArrayBufferOrView,
2821
getUsagesUnion,
2922
hasAnyNotIn,
3023
jobPromise,
@@ -76,7 +69,6 @@ function verifyAcceptableEcKeyUse(name, isPublic, usages) {
7669

7770
function createECPublicKeyRaw(namedCurve, keyData) {
7871
const handle = new KeyObjectHandle();
79-
keyData = getArrayBufferOrView(keyData, 'keyData');
8072

8173
if (!handle.initECRaw(kNamedCurveAliases[namedCurve], keyData)) {
8274
throw lazyDOMException('Invalid keyData', 'DataError');
@@ -204,50 +196,53 @@ async function ecImportKey(
204196
break;
205197
}
206198
case 'jwk': {
207-
if (keyData == null || typeof keyData !== 'object')
208-
throw lazyDOMException('Invalid JWK keyData', 'DataError');
199+
if (!keyData.kty)
200+
throw lazyDOMException('Invalid keyData', 'DataError');
209201
if (keyData.kty !== 'EC')
210-
throw lazyDOMException('Invalid key type', 'DataError');
202+
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
211203
if (keyData.crv !== namedCurve)
212-
throw lazyDOMException('Named curve mismatch', 'DataError');
204+
throw lazyDOMException(
205+
'JWK "crv" does not match the requested algorithm',
206+
'DataError');
213207

214208
verifyAcceptableEcKeyUse(
215209
name,
216210
keyData.d === undefined,
217211
usagesSet);
218212

219213
if (usagesSet.size > 0 && keyData.use !== undefined) {
220-
if (algorithm.name === 'ECDSA' && keyData.use !== 'sig')
221-
throw lazyDOMException('Invalid use type', 'DataError');
222-
if (algorithm.name === 'ECDH' && keyData.use !== 'enc')
223-
throw lazyDOMException('Invalid use type', 'DataError');
214+
const checkUse = name === 'ECDH' ? 'enc' : 'sig';
215+
if (keyData.use !== checkUse)
216+
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
224217
}
225218

226219
validateKeyOps(keyData.key_ops, usagesSet);
227220

228221
if (keyData.ext !== undefined &&
229222
keyData.ext === false &&
230223
extractable === true) {
231-
throw lazyDOMException('JWK is not extractable', 'DataError');
224+
throw lazyDOMException(
225+
'JWK "ext" Parameter and extractable mismatch',
226+
'DataError');
232227
}
233228

234229
if (algorithm.name === 'ECDSA' && keyData.alg !== undefined) {
235-
if (typeof keyData.alg !== 'string')
236-
throw lazyDOMException('Invalid alg', 'DataError');
237230
let algNamedCurve;
238231
switch (keyData.alg) {
239232
case 'ES256': algNamedCurve = 'P-256'; break;
240233
case 'ES384': algNamedCurve = 'P-384'; break;
241234
case 'ES512': algNamedCurve = 'P-521'; break;
242235
}
243236
if (algNamedCurve !== namedCurve)
244-
throw lazyDOMException('Named curve mismatch', 'DataError');
237+
throw lazyDOMException(
238+
'JWK "alg" does not match the requested algorithm',
239+
'DataError');
245240
}
246241

247242
const handle = new KeyObjectHandle();
248243
const type = handle.initJwk(keyData, namedCurve);
249244
if (type === undefined)
250-
throw lazyDOMException('Invalid JWK keyData', 'DataError');
245+
throw lazyDOMException('Invalid JWK', 'DataError');
251246
keyObject = type === kKeyTypePrivate ?
252247
new PrivateKeyObject(handle) :
253248
new PublicKeyObject(handle);
@@ -289,8 +284,6 @@ function ecdsaSignVerify(key, data, { name, hash }, signature) {
289284
if (key.type !== type)
290285
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
291286

292-
if (hash === undefined)
293-
throw new ERR_MISSING_OPTION('algorithm.hash');
294287
const hashname = normalizeHashName(hash.name);
295288

296289
return jobPromise(() => new SignJob(

lib/internal/crypto/hash.js

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,9 @@ const {
1414
} = internalBinding('crypto');
1515

1616
const {
17-
getArrayBufferOrView,
1817
getDefaultEncoding,
1918
getStringOption,
2019
jobPromise,
21-
normalizeAlgorithm,
2220
normalizeHashName,
2321
validateMaxBufferLength,
2422
kHandle,
@@ -168,13 +166,8 @@ Hmac.prototype._transform = Hash.prototype._transform;
168166
// Implementation for WebCrypto subtle.digest()
169167

170168
async function asyncDigest(algorithm, data) {
171-
algorithm = normalizeAlgorithm(algorithm);
172-
data = getArrayBufferOrView(data, 'data');
173169
validateMaxBufferLength(data, 'data');
174170

175-
if (algorithm.length !== undefined)
176-
validateUint32(algorithm.length, 'algorithm.length');
177-
178171
switch (algorithm.name) {
179172
case 'SHA-1':
180173
// Fall through
@@ -186,11 +179,10 @@ async function asyncDigest(algorithm, data) {
186179
return jobPromise(() => new HashJob(
187180
kCryptoJobAsync,
188181
normalizeHashName(algorithm.name),
189-
data,
190-
algorithm.length));
182+
data));
191183
}
192184

193-
throw lazyDOMException('Unrecognized name.', 'NotSupportedError');
185+
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
194186
}
195187

196188
module.exports = {

lib/internal/crypto/hkdf.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ const {
1919
const { kMaxLength } = require('buffer');
2020

2121
const {
22-
getArrayBufferOrView,
2322
normalizeHashName,
2423
toBuf,
2524
validateByteSource,
@@ -45,7 +44,6 @@ const {
4544
codes: {
4645
ERR_INVALID_ARG_TYPE,
4746
ERR_OUT_OF_RANGE,
48-
ERR_MISSING_OPTION,
4947
},
5048
hideStackFrames,
5149
} = require('internal/errors');
@@ -140,11 +138,7 @@ function hkdfSync(hash, key, salt, info, length) {
140138

141139
const hkdfPromise = promisify(hkdf);
142140
async function hkdfDeriveBits(algorithm, baseKey, length) {
143-
const { hash } = algorithm;
144-
const salt = getArrayBufferOrView(algorithm.salt, 'algorithm.salt');
145-
const info = getArrayBufferOrView(algorithm.info, 'algorithm.info');
146-
if (hash === undefined)
147-
throw new ERR_MISSING_OPTION('algorithm.hash');
141+
const { hash, salt, info } = algorithm;
148142

149143
if (length === 0)
150144
throw lazyDOMException('length cannot be zero', 'OperationError');

0 commit comments

Comments
 (0)