Skip to content

Commit 18888dc

Browse files
committed
src: refactor SubtleCrypto algorithm and length validations
1 parent 60dff42 commit 18888dc

13 files changed

+174
-143
lines changed

lib/internal/crypto/aes.js

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -109,35 +109,43 @@ function getVariant(name, length) {
109109
}
110110
}
111111

112-
function asyncAesCtrCipher(mode, key, data, { counter, length }) {
113-
validateByteLength(counter, 'algorithm.counter', 16);
112+
function validateAesCtrAlgorithm(algorithm) {
113+
validateByteLength(algorithm.counter, 'algorithm.counter', 16);
114114
// The length must specify an integer between 1 and 128. While
115115
// there is no default, this should typically be 64.
116-
if (length === 0 || length > kMaxCounterLength) {
116+
if (algorithm.length === 0 || algorithm.length > kMaxCounterLength) {
117117
throw lazyDOMException(
118118
'AES-CTR algorithm.length must be between 1 and 128',
119119
'OperationError');
120120
}
121+
}
122+
123+
function asyncAesCtrCipher(mode, key, data, algorithm) {
124+
validateAesCtrAlgorithm(algorithm);
121125

122126
return jobPromise(() => new AESCipherJob(
123127
kCryptoJobAsync,
124128
mode,
125129
key[kKeyObject][kHandle],
126130
data,
127131
getVariant('AES-CTR', key.algorithm.length),
128-
counter,
129-
length));
132+
algorithm.counter,
133+
algorithm.length));
134+
}
135+
136+
function validateAesCbcAlgorithm(algorithm) {
137+
validateByteLength(algorithm.iv, 'algorithm.iv', 16);
130138
}
131139

132-
function asyncAesCbcCipher(mode, key, data, { iv }) {
133-
validateByteLength(iv, 'algorithm.iv', 16);
140+
function asyncAesCbcCipher(mode, key, data, algorithm) {
141+
validateAesCbcAlgorithm(algorithm);
134142
return jobPromise(() => new AESCipherJob(
135143
kCryptoJobAsync,
136144
mode,
137145
key[kKeyObject][kHandle],
138146
data,
139147
getVariant('AES-CBC', key.algorithm.length),
140-
iv));
148+
algorithm.iv));
141149
}
142150

143151
function asyncAesKwCipher(mode, key, data) {
@@ -149,24 +157,25 @@ function asyncAesKwCipher(mode, key, data) {
149157
getVariant('AES-KW', key.algorithm.length)));
150158
}
151159

152-
function asyncAesGcmCipher(
153-
mode,
154-
key,
155-
data,
156-
{ iv, additionalData, tagLength = 128 }) {
157-
if (!ArrayPrototypeIncludes(kTagLengths, tagLength)) {
158-
return PromiseReject(lazyDOMException(
159-
`${tagLength} is not a valid AES-GCM tag length`,
160-
'OperationError'));
160+
function validateAesGcmAlgorithm(algorithm) {
161+
if (!ArrayPrototypeIncludes(kTagLengths, algorithm.tagLength)) {
162+
throw lazyDOMException(
163+
`${algorithm.tagLength} is not a valid AES-GCM tag length`,
164+
'OperationError');
161165
}
162166

163-
validateMaxBufferLength(iv, 'algorithm.iv');
167+
validateMaxBufferLength(algorithm.iv, 'algorithm.iv');
164168

165-
if (additionalData !== undefined) {
166-
validateMaxBufferLength(additionalData, 'algorithm.additionalData');
169+
if (algorithm.additionalData !== undefined) {
170+
validateMaxBufferLength(algorithm.additionalData, 'algorithm.additionalData');
167171
}
172+
}
168173

169-
const tagByteLength = MathFloor(tagLength / 8);
174+
function asyncAesGcmCipher(mode, key, data, algorithm) {
175+
algorithm.tagLength ??= 128;
176+
validateAesGcmAlgorithm(algorithm);
177+
178+
const tagByteLength = MathFloor(algorithm.tagLength / 8);
170179
let tag;
171180
switch (mode) {
172181
case kWebCryptoCipherDecrypt: {
@@ -198,9 +207,9 @@ function asyncAesGcmCipher(
198207
key[kKeyObject][kHandle],
199208
data,
200209
getVariant('AES-GCM', key.algorithm.length),
201-
iv,
210+
algorithm.iv,
202211
tag,
203-
additionalData));
212+
algorithm.additionalData));
204213
}
205214

206215
function aesCipher(mode, key, data, algorithm) {
@@ -212,13 +221,17 @@ function aesCipher(mode, key, data, algorithm) {
212221
}
213222
}
214223

215-
async function aesGenerateKey(algorithm, extractable, keyUsages) {
216-
const { name, length } = algorithm;
217-
if (!ArrayPrototypeIncludes(kAesKeyLengths, length)) {
224+
function validateAesGenerateKeyAlgorithm(algorithm) {
225+
if (!ArrayPrototypeIncludes(kAesKeyLengths, algorithm.length)) {
218226
throw lazyDOMException(
219227
'AES key length must be 128, 192, or 256 bits',
220228
'OperationError');
221229
}
230+
}
231+
232+
async function aesGenerateKey(algorithm, extractable, keyUsages) {
233+
validateAesGenerateKeyAlgorithm(algorithm);
234+
const { name, length } = algorithm;
222235

223236
const checkUsages = ['wrapKey', 'unwrapKey'];
224237
if (name !== 'AES-KW')

lib/internal/crypto/cfrg.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -329,18 +329,21 @@ function cfrgImportKey(
329329
extractable);
330330
}
331331

332-
function eddsaSignVerify(key, data, { name, context }, signature) {
332+
function validateEdDSASignVerifyAlgorithm(algorithm) {
333+
if (algorithm.name === 'Ed448' && algorithm.context?.byteLength) {
334+
throw lazyDOMException(
335+
'Non zero-length context is not yet supported.', 'NotSupportedError');
336+
}
337+
}
338+
339+
function eddsaSignVerify(key, data, algorithm, signature) {
340+
validateEdDSASignVerifyAlgorithm(algorithm);
333341
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
334342
const type = mode === kSignJobModeSign ? 'private' : 'public';
335343

336344
if (key.type !== type)
337345
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
338346

339-
if (name === 'Ed448' && context?.byteLength) {
340-
throw lazyDOMException(
341-
'Non zero-length context is not yet supported.', 'NotSupportedError');
342-
}
343-
344347
return jobPromise(() => new SignJob(
345348
kCryptoJobAsync,
346349
mode,

lib/internal/crypto/diffiehellman.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -298,28 +298,28 @@ function diffieHellman(options) {
298298

299299
let masks;
300300

301+
function validateEcdhDeriveBitsAlgorithmAndLength(algorithm, length) {
302+
if (algorithm.public.type !== 'public') {
303+
throw lazyDOMException(
304+
'algorithm.public must be a public key', 'InvalidAccessError');
305+
}
306+
307+
if (algorithm.name !== algorithm.public.algorithm.name) {
308+
throw lazyDOMException(`algorithm.public must be an ${algorithm.name} key`, 'InvalidAccessError');
309+
}
310+
}
311+
301312
// The ecdhDeriveBits function is part of the Web Crypto API and serves both
302313
// deriveKeys and deriveBits functions.
303314
async function ecdhDeriveBits(algorithm, baseKey, length) {
315+
validateEcdhDeriveBitsAlgorithmAndLength(algorithm, length);
304316
const { 'public': key } = algorithm;
305317

306-
if (key.type !== 'public') {
307-
throw lazyDOMException(
308-
'algorithm.public must be a public key', 'InvalidAccessError');
309-
}
310318
if (baseKey.type !== 'private') {
311319
throw lazyDOMException(
312320
'baseKey must be a private key', 'InvalidAccessError');
313321
}
314322

315-
if (
316-
key.algorithm.name !== 'ECDH' &&
317-
key.algorithm.name !== 'X25519' &&
318-
key.algorithm.name !== 'X448'
319-
) {
320-
throw lazyDOMException('Keys must be ECDH, X25519, or X448 keys', 'InvalidAccessError');
321-
}
322-
323323
if (key.algorithm.name !== baseKey.algorithm.name) {
324324
throw lazyDOMException(
325325
'The public and private keys must be of the same type',

lib/internal/crypto/ec.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,17 @@ function createECPublicKeyRaw(namedCurve, keyData) {
7777
return new PublicKeyObject(handle);
7878
}
7979

80-
async function ecGenerateKey(algorithm, extractable, keyUsages) {
81-
const { name, namedCurve } = algorithm;
82-
83-
if (!ArrayPrototypeIncludes(ObjectKeys(kNamedCurveAliases), namedCurve)) {
80+
function validateEcGenerateKeyAlgorithm(algorithm) {
81+
if (!ArrayPrototypeIncludes(ObjectKeys(kNamedCurveAliases), algorithm.namedCurve)) {
8482
throw lazyDOMException(
8583
'Unrecognized namedCurve',
8684
'NotSupportedError');
8785
}
86+
}
87+
88+
async function ecGenerateKey(algorithm, extractable, keyUsages) {
89+
validateEcGenerateKeyAlgorithm(algorithm);
90+
const { name, namedCurve } = algorithm;
8891

8992
const usageSet = new SafeSet(keyUsages);
9093
switch (name) {

lib/internal/crypto/hkdf.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,18 +138,22 @@ function hkdfSync(hash, key, salt, info, length) {
138138
}
139139

140140
const hkdfPromise = promisify(hkdf);
141-
async function hkdfDeriveBits(algorithm, baseKey, length) {
142-
const { hash, salt, info } = algorithm;
143-
144-
if (length === 0)
145-
return new ArrayBuffer(0);
141+
function validateHkdfDeriveBitsAlgorithmAndLength(algorithm, length) {
146142
if (length === null)
147143
throw lazyDOMException('length cannot be null', 'OperationError');
148144
if (length % 8) {
149145
throw lazyDOMException(
150146
'length must be a multiple of 8',
151147
'OperationError');
152148
}
149+
}
150+
151+
async function hkdfDeriveBits(algorithm, baseKey, length) {
152+
validateHkdfDeriveBitsAlgorithmAndLength(algorithm, length);
153+
const { hash, salt, info } = algorithm;
154+
155+
if (length === 0)
156+
return new ArrayBuffer(0);
153157

154158
try {
155159
return await hkdfPromise(

lib/internal/crypto/keys.js

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -894,13 +894,24 @@ function isCryptoKey(obj) {
894894
return obj != null && obj[kKeyObject] !== undefined;
895895
}
896896

897+
function checkImportGenericSecretKeyAlgorithm(algorithm) {
898+
// The Web Crypto spec allows for key lengths that are not multiples of 8. We don't.
899+
if (algorithm.length % 8) {
900+
// TODO(@panva): change this from DataError to NotSupportedError in a semver-major
901+
throw lazyDOMException('Invalid key length', 'DataError');
902+
}
903+
}
904+
897905
function importGenericSecretKey(
898-
{ name, length },
906+
algorithm,
899907
format,
900908
keyData,
901909
extractable,
902-
keyUsages) {
910+
keyUsages,
911+
) {
912+
checkImportGenericSecretKeyAlgorithm(algorithm);
903913
const usagesSet = new SafeSet(keyUsages);
914+
const { name, length } = algorithm;
904915
if (extractable)
905916
throw lazyDOMException(`${name} keys are not extractable`, 'SyntaxError');
906917

@@ -910,45 +921,25 @@ function importGenericSecretKey(
910921
'SyntaxError');
911922
}
912923

924+
let keyObject;
913925
switch (format) {
914926
case 'KeyObject': {
915-
if (hasAnyNotIn(usagesSet, ['deriveKey', 'deriveBits'])) {
916-
throw lazyDOMException(
917-
`Unsupported key usage for a ${name} key`,
918-
'SyntaxError');
919-
}
920-
921-
const checkLength = keyData.symmetricKeySize * 8;
922-
923-
// The Web Crypto spec allows for key lengths that are not multiples of
924-
// 8. We don't. Our check here is stricter than that defined by the spec
925-
// in that we require that algorithm.length match keyData.length * 8 if
926-
// algorithm.length is specified.
927-
if (length !== undefined && length !== checkLength) {
928-
throw lazyDOMException('Invalid key length', 'DataError');
929-
}
930-
return new InternalCryptoKey(keyData, { name }, keyUsages, false);
927+
keyObject = keyData;
928+
break;
931929
}
932930
case 'raw': {
933-
if (hasAnyNotIn(usagesSet, ['deriveKey', 'deriveBits'])) {
934-
throw lazyDOMException(
935-
`Unsupported key usage for a ${name} key`,
936-
'SyntaxError');
937-
}
938-
939-
const checkLength = keyData.byteLength * 8;
931+
keyObject = createSecretKey(keyData);
932+
break;
933+
}
934+
}
940935

941-
// The Web Crypto spec allows for key lengths that are not multiples of
942-
// 8. We don't. Our check here is stricter than that defined by the spec
943-
// in that we require that algorithm.length match keyData.length * 8 if
944-
// algorithm.length is specified.
945-
if (length !== undefined && length !== checkLength) {
946-
throw lazyDOMException('Invalid key length', 'DataError');
947-
}
936+
if (keyObject) {
937+
const checkLength = keyObject.symmetricKeySize * 8;
948938

949-
const keyObject = createSecretKey(keyData);
950-
return new InternalCryptoKey(keyObject, { name }, keyUsages, false);
939+
if (length !== undefined && length !== checkLength) {
940+
throw lazyDOMException('Invalid key length', 'DataError');
951941
}
942+
return new InternalCryptoKey(keyObject, { name }, keyUsages, false);
952943
}
953944

954945
throw lazyDOMException(

0 commit comments

Comments
 (0)