Skip to content
/ node Public
forked from nodejs/node

Commit 5d70b02

Browse files
committed
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: nodejs#46067 Reviewed-By: Antoine du Hamel <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]> Backport-PR-URL: nodejs#47383
1 parent 8570ffa commit 5d70b02

33 files changed

+2061
-440
lines changed

doc/api/webcrypto.md

+4
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: v16.17.0
610
pr-url: https://github.com/nodejs/node/pull/43310
711
description: Removed proprietary `'node.keyObject'` import/export format.

lib/internal/crypto/aes.js

+11-17
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

@@ -282,24 +274,26 @@ async function aesImportKey(
282274
break;
283275
}
284276
case 'jwk': {
285-
if (keyData == null || typeof keyData !== 'object')
286-
throw lazyDOMException('Invalid JWK keyData', 'DataError');
277+
if (!keyData.kty)
278+
throw lazyDOMException('Invalid keyData', 'DataError');
287279

288280
if (keyData.kty !== 'oct')
289-
throw lazyDOMException('Invalid key type', 'DataError');
281+
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
290282

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

297289
validateKeyOps(keyData.key_ops, usagesSet);
298290

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

305299
const handle = new KeyObjectHandle();
@@ -309,10 +303,10 @@ async function aesImportKey(
309303
validateKeyLength(length);
310304

311305
if (keyData.alg !== undefined) {
312-
if (typeof keyData.alg !== 'string')
313-
throw lazyDOMException('Invalid alg', 'DataError');
314306
if (keyData.alg !== getAlgorithmName(algorithm.name, length))
315-
throw lazyDOMException('Algorithm mismatch', 'DataError');
307+
throw lazyDOMException(
308+
'JWK "alg" does not match the requested algorithm',
309+
'DataError');
316310
}
317311

318312
keyObject = new SecretKeyObject(handle);

lib/internal/crypto/cfrg.js

+17-20
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,
@@ -83,7 +82,6 @@ function verifyAcceptableCfrgKeyUse(name, type, usages) {
8382

8483
function createCFRGRawKey(name, keyData, isPublic) {
8584
const handle = new KeyObjectHandle();
86-
keyData = getArrayBufferOrView(keyData, 'keyData');
8785

8886
switch (name) {
8987
case 'Ed25519':
@@ -248,12 +246,13 @@ async function cfrgImportKey(
248246
break;
249247
}
250248
case 'jwk': {
251-
if (keyData == null || typeof keyData !== 'object')
252-
throw lazyDOMException('Invalid JWK keyData', 'DataError');
249+
if (!keyData.kty)
250+
throw lazyDOMException('Invalid keyData', 'DataError');
253251
if (keyData.kty !== 'OKP')
254-
throw lazyDOMException('Invalid key type', 'DataError');
252+
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
255253
if (keyData.crv !== name)
256-
throw lazyDOMException('Subtype mismatch', 'DataError');
254+
throw lazyDOMException(
255+
'JWK "crv" Parameter and algorithm name mismatch', 'DataError');
257256
const isPublic = keyData.d === undefined;
258257

259258
if (usagesSet.size > 0 && keyData.use !== undefined) {
@@ -271,30 +270,32 @@ async function cfrgImportKey(
271270
break;
272271
}
273272
if (keyData.use !== checkUse)
274-
throw lazyDOMException('Invalid use type', 'DataError');
273+
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
275274
}
276275

277276
validateKeyOps(keyData.key_ops, usagesSet);
278277

279278
if (keyData.ext !== undefined &&
280279
keyData.ext === false &&
281280
extractable === true) {
282-
throw lazyDOMException('JWK is not extractable', 'DataError');
281+
throw lazyDOMException(
282+
'JWK "ext" Parameter and extractable mismatch',
283+
'DataError');
283284
}
284285

285286
if (keyData.alg !== undefined) {
286-
if (typeof keyData.alg !== 'string')
287-
throw lazyDOMException('Invalid alg', 'DataError');
288287
if (
289288
(name === 'Ed25519' || name === 'Ed448') &&
290289
keyData.alg !== 'EdDSA'
291290
) {
292-
throw lazyDOMException('Invalid alg', 'DataError');
291+
throw lazyDOMException(
292+
'JWK "alg" does not match the requested algorithm',
293+
'DataError');
293294
}
294295
}
295296

296297
if (!isPublic && typeof keyData.x !== 'string') {
297-
throw lazyDOMException('Invalid JWK keyData', 'DataError');
298+
throw lazyDOMException('Invalid JWK', 'DataError');
298299
}
299300

300301
verifyAcceptableCfrgKeyUse(
@@ -316,7 +317,7 @@ async function cfrgImportKey(
316317
false);
317318

318319
if (!createPublicKey(keyObject).equals(publicKeyObject)) {
319-
throw lazyDOMException('Invalid JWK keyData', 'DataError');
320+
throw lazyDOMException('Invalid JWK', 'DataError');
320321
}
321322
}
322323
break;
@@ -347,13 +348,9 @@ function eddsaSignVerify(key, data, { name, context }, signature) {
347348
if (key.type !== type)
348349
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
349350

350-
if (name === 'Ed448' && context !== undefined) {
351-
context =
352-
getArrayBufferOrView(context, 'algorithm.context');
353-
if (context.byteLength !== 0) {
354-
throw lazyDOMException(
355-
'Non zero-length context is not yet supported.', 'NotSupportedError');
356-
}
351+
if (name === 'Ed448' && context?.byteLength) {
352+
throw lazyDOMException(
353+
'Non zero-length context is not yet supported.', 'NotSupportedError');
357354
}
358355

359356
return jobPromise(() => new SignJob(

lib/internal/crypto/diffiehellman.js

-9
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ const {
3737
validateInt32,
3838
validateObject,
3939
validateString,
40-
validateUint32,
4140
} = require('internal/validators');
4241

4342
const {
@@ -51,7 +50,6 @@ const {
5150

5251
const {
5352
KeyObject,
54-
isCryptoKey,
5553
} = require('internal/crypto/keys');
5654

5755
const {
@@ -335,13 +333,6 @@ function deriveBitsECDH(name, publicKey, privateKey, callback) {
335333
async function asyncDeriveBitsECDH(algorithm, baseKey, length) {
336334
const { 'public': key } = algorithm;
337335

338-
// Null means that we're not asking for a specific number of bits, just
339-
// give us everything that is generated.
340-
if (length !== null)
341-
validateUint32(length, 'length');
342-
if (!isCryptoKey(key))
343-
throw new ERR_INVALID_ARG_TYPE('algorithm.public', 'CryptoKey', key);
344-
345336
if (key.type !== 'public') {
346337
throw lazyDOMException(
347338
'algorithm.public must be a public key', 'InvalidAccessError');

lib/internal/crypto/ec.js

+16-23
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,
@@ -86,7 +79,6 @@ function verifyAcceptableEcKeyUse(name, type, usages) {
8679

8780
function createECPublicKeyRaw(namedCurve, keyData) {
8881
const handle = new KeyObjectHandle();
89-
keyData = getArrayBufferOrView(keyData, 'keyData');
9082

9183
if (!handle.initECRaw(kNamedCurveAliases[namedCurve], keyData)) {
9284
throw lazyDOMException('Invalid keyData', 'DataError');
@@ -215,12 +207,14 @@ async function ecImportKey(
215207
break;
216208
}
217209
case 'jwk': {
218-
if (keyData == null || typeof keyData !== 'object')
219-
throw lazyDOMException('Invalid JWK keyData', 'DataError');
210+
if (!keyData.kty)
211+
throw lazyDOMException('Invalid keyData', 'DataError');
220212
if (keyData.kty !== 'EC')
221-
throw lazyDOMException('Invalid key type', 'DataError');
213+
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
222214
if (keyData.crv !== namedCurve)
223-
throw lazyDOMException('Named curve mismatch', 'DataError');
215+
throw lazyDOMException(
216+
'JWK "crv" does not match the requested algorithm',
217+
'DataError');
224218

225219
if (keyData.d !== undefined) {
226220
verifyAcceptableEcKeyUse(name, 'private', usagesSet);
@@ -229,37 +223,38 @@ async function ecImportKey(
229223
}
230224

231225
if (usagesSet.size > 0 && keyData.use !== undefined) {
232-
if (algorithm.name === 'ECDSA' && keyData.use !== 'sig')
233-
throw lazyDOMException('Invalid use type', 'DataError');
234-
if (algorithm.name === 'ECDH' && keyData.use !== 'enc')
235-
throw lazyDOMException('Invalid use type', 'DataError');
226+
const checkUse = name === 'ECDH' ? 'enc' : 'sig';
227+
if (keyData.use !== checkUse)
228+
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
236229
}
237230

238231
validateKeyOps(keyData.key_ops, usagesSet);
239232

240233
if (keyData.ext !== undefined &&
241234
keyData.ext === false &&
242235
extractable === true) {
243-
throw lazyDOMException('JWK is not extractable', 'DataError');
236+
throw lazyDOMException(
237+
'JWK "ext" Parameter and extractable mismatch',
238+
'DataError');
244239
}
245240

246241
if (algorithm.name === 'ECDSA' && keyData.alg !== undefined) {
247-
if (typeof keyData.alg !== 'string')
248-
throw lazyDOMException('Invalid alg', 'DataError');
249242
let algNamedCurve;
250243
switch (keyData.alg) {
251244
case 'ES256': algNamedCurve = 'P-256'; break;
252245
case 'ES384': algNamedCurve = 'P-384'; break;
253246
case 'ES512': algNamedCurve = 'P-521'; break;
254247
}
255248
if (algNamedCurve !== namedCurve)
256-
throw lazyDOMException('Named curve mismatch', 'DataError');
249+
throw lazyDOMException(
250+
'JWK "alg" does not match the requested algorithm',
251+
'DataError');
257252
}
258253

259254
const handle = new KeyObjectHandle();
260255
const type = handle.initJwk(keyData, namedCurve);
261256
if (type === undefined)
262-
throw lazyDOMException('Invalid JWK keyData', 'DataError');
257+
throw lazyDOMException('Invalid JWK', 'DataError');
263258
keyObject = type === kKeyTypePrivate ?
264259
new PrivateKeyObject(handle) :
265260
new PublicKeyObject(handle);
@@ -301,8 +296,6 @@ function ecdsaSignVerify(key, data, { name, hash }, signature) {
301296
if (key.type !== type)
302297
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
303298

304-
if (hash === undefined)
305-
throw new ERR_MISSING_OPTION('algorithm.hash');
306299
const hashname = normalizeHashName(hash.name);
307300

308301
return jobPromise(() => new SignJob(

lib/internal/crypto/hash.js

+2-10
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

+1-7
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ const {
2121
const { kMaxLength } = require('buffer');
2222

2323
const {
24-
getArrayBufferOrView,
2524
normalizeHashName,
2625
toBuf,
2726
validateByteSource,
@@ -46,7 +45,6 @@ const {
4645
codes: {
4746
ERR_INVALID_ARG_TYPE,
4847
ERR_OUT_OF_RANGE,
49-
ERR_MISSING_OPTION,
5048
},
5149
hideStackFrames,
5250
} = require('internal/errors');
@@ -140,11 +138,7 @@ function hkdfSync(hash, key, salt, info, length) {
140138
}
141139

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
let byteLength = 512 / 8;
150144
if (length !== undefined) {

0 commit comments

Comments
 (0)