src: refactor SubtleCrypto algorithm and length validations

PR-URL: https://github.com/nodejs/node/pull/57273
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Jason Zhang <xzha4350@gmail.com>
Reviewed-By: Mattias Buelens <mattias@buelens.com>
This commit is contained in:
Filip Skokan 2025-03-04 18:45:51 +01:00 committed by GitHub
parent 52ac44888d
commit 6fdd4e6dcf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 210 additions and 182 deletions

View File

@ -58,7 +58,6 @@ const {
generateKey: _generateKey, generateKey: _generateKey,
} = require('internal/crypto/keygen'); } = require('internal/crypto/keygen');
const kMaxCounterLength = 128;
const kTagLengths = [32, 64, 96, 104, 112, 120, 128]; const kTagLengths = [32, 64, 96, 104, 112, 120, 128];
const generateKey = promisify(_generateKey); const generateKey = promisify(_generateKey);
@ -109,15 +108,19 @@ function getVariant(name, length) {
} }
} }
function asyncAesCtrCipher(mode, key, data, { counter, length }) { function validateAesCtrAlgorithm(algorithm) {
validateByteLength(counter, 'algorithm.counter', 16); validateByteLength(algorithm.counter, 'algorithm.counter', 16);
// The length must specify an integer between 1 and 128. While // The length must specify an integer between 1 and 128. While
// there is no default, this should typically be 64. // there is no default, this should typically be 64.
if (length === 0 || length > kMaxCounterLength) { if (algorithm.length === 0 || algorithm.length > 128) {
throw lazyDOMException( throw lazyDOMException(
'AES-CTR algorithm.length must be between 1 and 128', 'AES-CTR algorithm.length must be between 1 and 128',
'OperationError'); 'OperationError');
} }
}
function asyncAesCtrCipher(mode, key, data, algorithm) {
validateAesCtrAlgorithm(algorithm);
return jobPromise(() => new AESCipherJob( return jobPromise(() => new AESCipherJob(
kCryptoJobAsync, kCryptoJobAsync,
@ -125,19 +128,23 @@ function asyncAesCtrCipher(mode, key, data, { counter, length }) {
key[kKeyObject][kHandle], key[kKeyObject][kHandle],
data, data,
getVariant('AES-CTR', key.algorithm.length), getVariant('AES-CTR', key.algorithm.length),
counter, algorithm.counter,
length)); algorithm.length));
} }
function asyncAesCbcCipher(mode, key, data, { iv }) { function validateAesCbcAlgorithm(algorithm) {
validateByteLength(iv, 'algorithm.iv', 16); validateByteLength(algorithm.iv, 'algorithm.iv', 16);
}
function asyncAesCbcCipher(mode, key, data, algorithm) {
validateAesCbcAlgorithm(algorithm);
return jobPromise(() => new AESCipherJob( return jobPromise(() => new AESCipherJob(
kCryptoJobAsync, kCryptoJobAsync,
mode, mode,
key[kKeyObject][kHandle], key[kKeyObject][kHandle],
data, data,
getVariant('AES-CBC', key.algorithm.length), getVariant('AES-CBC', key.algorithm.length),
iv)); algorithm.iv));
} }
function asyncAesKwCipher(mode, key, data) { function asyncAesKwCipher(mode, key, data) {
@ -149,24 +156,25 @@ function asyncAesKwCipher(mode, key, data) {
getVariant('AES-KW', key.algorithm.length))); getVariant('AES-KW', key.algorithm.length)));
} }
function asyncAesGcmCipher( function validateAesGcmAlgorithm(algorithm) {
mode, if (!ArrayPrototypeIncludes(kTagLengths, algorithm.tagLength)) {
key, throw lazyDOMException(
data, `${algorithm.tagLength} is not a valid AES-GCM tag length`,
{ iv, additionalData, tagLength = 128 }) { 'OperationError');
if (!ArrayPrototypeIncludes(kTagLengths, tagLength)) {
return PromiseReject(lazyDOMException(
`${tagLength} is not a valid AES-GCM tag length`,
'OperationError'));
} }
validateMaxBufferLength(iv, 'algorithm.iv'); validateMaxBufferLength(algorithm.iv, 'algorithm.iv');
if (additionalData !== undefined) { if (algorithm.additionalData !== undefined) {
validateMaxBufferLength(additionalData, 'algorithm.additionalData'); validateMaxBufferLength(algorithm.additionalData, 'algorithm.additionalData');
}
} }
const tagByteLength = MathFloor(tagLength / 8); function asyncAesGcmCipher(mode, key, data, algorithm) {
algorithm.tagLength ??= 128;
validateAesGcmAlgorithm(algorithm);
const tagByteLength = MathFloor(algorithm.tagLength / 8);
let tag; let tag;
switch (mode) { switch (mode) {
case kWebCryptoCipherDecrypt: { case kWebCryptoCipherDecrypt: {
@ -198,9 +206,9 @@ function asyncAesGcmCipher(
key[kKeyObject][kHandle], key[kKeyObject][kHandle],
data, data,
getVariant('AES-GCM', key.algorithm.length), getVariant('AES-GCM', key.algorithm.length),
iv, algorithm.iv,
tag, tag,
additionalData)); algorithm.additionalData));
} }
function aesCipher(mode, key, data, algorithm) { function aesCipher(mode, key, data, algorithm) {
@ -212,13 +220,17 @@ function aesCipher(mode, key, data, algorithm) {
} }
} }
async function aesGenerateKey(algorithm, extractable, keyUsages) { function validateAesGenerateKeyAlgorithm(algorithm) {
const { name, length } = algorithm; if (!ArrayPrototypeIncludes(kAesKeyLengths, algorithm.length)) {
if (!ArrayPrototypeIncludes(kAesKeyLengths, length)) {
throw lazyDOMException( throw lazyDOMException(
'AES key length must be 128, 192, or 256 bits', 'AES key length must be 128, 192, or 256 bits',
'OperationError'); 'OperationError');
} }
}
async function aesGenerateKey(algorithm, extractable, keyUsages) {
validateAesGenerateKeyAlgorithm(algorithm);
const { name, length } = algorithm;
const checkUsages = ['wrapKey', 'unwrapKey']; const checkUsages = ['wrapKey', 'unwrapKey'];
if (name !== 'AES-KW') if (name !== 'AES-KW')

View File

@ -329,18 +329,21 @@ function cfrgImportKey(
extractable); extractable);
} }
function eddsaSignVerify(key, data, { name, context }, signature) { function validateEdDSASignVerifyAlgorithm(algorithm) {
if (algorithm.name === 'Ed448' && algorithm.context?.byteLength) {
throw lazyDOMException(
'Non zero-length context is not yet supported.', 'NotSupportedError');
}
}
function eddsaSignVerify(key, data, algorithm, signature) {
validateEdDSASignVerifyAlgorithm(algorithm);
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
const type = mode === kSignJobModeSign ? 'private' : 'public'; const type = mode === kSignJobModeSign ? 'private' : 'public';
if (key.type !== type) if (key.type !== type)
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
if (name === 'Ed448' && context?.byteLength) {
throw lazyDOMException(
'Non zero-length context is not yet supported.', 'NotSupportedError');
}
return jobPromise(() => new SignJob( return jobPromise(() => new SignJob(
kCryptoJobAsync, kCryptoJobAsync,
mode, mode,

View File

@ -298,28 +298,28 @@ function diffieHellman(options) {
let masks; let masks;
// The ecdhDeriveBits function is part of the Web Crypto API and serves both function validateEcdhDeriveBitsAlgorithmAndLength(algorithm, length) {
// deriveKeys and deriveBits functions. if (algorithm.public.type !== 'public') {
async function ecdhDeriveBits(algorithm, baseKey, length) {
const { 'public': key } = algorithm;
if (key.type !== 'public') {
throw lazyDOMException( throw lazyDOMException(
'algorithm.public must be a public key', 'InvalidAccessError'); 'algorithm.public must be a public key', 'InvalidAccessError');
} }
if (algorithm.name !== algorithm.public.algorithm.name) {
throw lazyDOMException(`algorithm.public must be an ${algorithm.name} key`, 'InvalidAccessError');
}
}
// The ecdhDeriveBits function is part of the Web Crypto API and serves both
// deriveKeys and deriveBits functions.
async function ecdhDeriveBits(algorithm, baseKey, length) {
validateEcdhDeriveBitsAlgorithmAndLength(algorithm, length);
const { 'public': key } = algorithm;
if (baseKey.type !== 'private') { if (baseKey.type !== 'private') {
throw lazyDOMException( throw lazyDOMException(
'baseKey must be a private key', 'InvalidAccessError'); 'baseKey must be a private key', 'InvalidAccessError');
} }
if (
key.algorithm.name !== 'ECDH' &&
key.algorithm.name !== 'X25519' &&
key.algorithm.name !== 'X448'
) {
throw lazyDOMException('Keys must be ECDH, X25519, or X448 keys', 'InvalidAccessError');
}
if (key.algorithm.name !== baseKey.algorithm.name) { if (key.algorithm.name !== baseKey.algorithm.name) {
throw lazyDOMException( throw lazyDOMException(
'The public and private keys must be of the same type', 'The public and private keys must be of the same type',

View File

@ -1,8 +1,7 @@
'use strict'; 'use strict';
const { const {
ArrayPrototypeIncludes, ObjectPrototypeHasOwnProperty,
ObjectKeys,
SafeSet, SafeSet,
} = primordials; } = primordials;
@ -77,14 +76,17 @@ function createECPublicKeyRaw(namedCurve, keyData) {
return new PublicKeyObject(handle); return new PublicKeyObject(handle);
} }
async function ecGenerateKey(algorithm, extractable, keyUsages) { function validateEcKeyAlgorithm(algorithm) {
const { name, namedCurve } = algorithm; if (!ObjectPrototypeHasOwnProperty(kNamedCurveAliases, algorithm.namedCurve)) {
if (!ArrayPrototypeIncludes(ObjectKeys(kNamedCurveAliases), namedCurve)) {
throw lazyDOMException( throw lazyDOMException(
'Unrecognized namedCurve', 'Unrecognized namedCurve',
'NotSupportedError'); 'NotSupportedError');
} }
}
async function ecGenerateKey(algorithm, extractable, keyUsages) {
validateEcKeyAlgorithm(algorithm);
const { name, namedCurve } = algorithm;
const usageSet = new SafeSet(keyUsages); const usageSet = new SafeSet(keyUsages);
switch (name) { switch (name) {
@ -154,16 +156,11 @@ function ecImportKey(
keyData, keyData,
algorithm, algorithm,
extractable, extractable,
keyUsages) { keyUsages,
) {
validateEcKeyAlgorithm(algorithm);
const { name, namedCurve } = algorithm; const { name, namedCurve } = algorithm;
if (!ArrayPrototypeIncludes(ObjectKeys(kNamedCurveAliases), namedCurve)) {
throw lazyDOMException(
'Unrecognized namedCurve',
'NotSupportedError');
}
let keyObject; let keyObject;
const usagesSet = new SafeSet(keyUsages); const usagesSet = new SafeSet(keyUsages);
switch (format) { switch (format) {

View File

@ -138,11 +138,7 @@ function hkdfSync(hash, key, salt, info, length) {
} }
const hkdfPromise = promisify(hkdf); const hkdfPromise = promisify(hkdf);
async function hkdfDeriveBits(algorithm, baseKey, length) { function validateHkdfDeriveBitsAlgorithmAndLength(algorithm, length) {
const { hash, salt, info } = algorithm;
if (length === 0)
return new ArrayBuffer(0);
if (length === null) if (length === null)
throw lazyDOMException('length cannot be null', 'OperationError'); throw lazyDOMException('length cannot be null', 'OperationError');
if (length % 8) { if (length % 8) {
@ -150,6 +146,14 @@ async function hkdfDeriveBits(algorithm, baseKey, length) {
'length must be a multiple of 8', 'length must be a multiple of 8',
'OperationError'); 'OperationError');
} }
}
async function hkdfDeriveBits(algorithm, baseKey, length) {
validateHkdfDeriveBitsAlgorithmAndLength(algorithm, length);
const { hash, salt, info } = algorithm;
if (length === 0)
return new ArrayBuffer(0);
try { try {
return await hkdfPromise( return await hkdfPromise(

View File

@ -895,12 +895,14 @@ function isCryptoKey(obj) {
} }
function importGenericSecretKey( function importGenericSecretKey(
{ name, length }, algorithm,
format, format,
keyData, keyData,
extractable, extractable,
keyUsages) { keyUsages,
) {
const usagesSet = new SafeSet(keyUsages); const usagesSet = new SafeSet(keyUsages);
const { name } = algorithm;
if (extractable) if (extractable)
throw lazyDOMException(`${name} keys are not extractable`, 'SyntaxError'); throw lazyDOMException(`${name} keys are not extractable`, 'SyntaxError');
@ -910,46 +912,21 @@ function importGenericSecretKey(
'SyntaxError'); 'SyntaxError');
} }
let keyObject;
switch (format) { switch (format) {
case 'KeyObject': { case 'KeyObject': {
if (hasAnyNotIn(usagesSet, ['deriveKey', 'deriveBits'])) { keyObject = keyData;
throw lazyDOMException( break;
`Unsupported key usage for a ${name} key`,
'SyntaxError');
}
const checkLength = keyData.symmetricKeySize * 8;
// The Web Crypto spec allows for key lengths that are not multiples of
// 8. We don't. Our check here is stricter than that defined by the spec
// in that we require that algorithm.length match keyData.length * 8 if
// algorithm.length is specified.
if (length !== undefined && length !== checkLength) {
throw lazyDOMException('Invalid key length', 'DataError');
}
return new InternalCryptoKey(keyData, { name }, keyUsages, false);
} }
case 'raw': { case 'raw': {
if (hasAnyNotIn(usagesSet, ['deriveKey', 'deriveBits'])) { keyObject = createSecretKey(keyData);
throw lazyDOMException( break;
`Unsupported key usage for a ${name} key`, }
'SyntaxError');
} }
const checkLength = keyData.byteLength * 8; if (keyObject) {
// The Web Crypto spec allows for key lengths that are not multiples of
// 8. We don't. Our check here is stricter than that defined by the spec
// in that we require that algorithm.length match keyData.length * 8 if
// algorithm.length is specified.
if (length !== undefined && length !== checkLength) {
throw lazyDOMException('Invalid key length', 'DataError');
}
const keyObject = createSecretKey(keyData);
return new InternalCryptoKey(keyObject, { name }, keyUsages, false); return new InternalCryptoKey(keyObject, { name }, keyUsages, false);
} }
}
throw lazyDOMException( throw lazyDOMException(
`Unable to import ${name} key with format ${format}`, `Unable to import ${name} key with format ${format}`,

View File

@ -18,7 +18,6 @@ const {
hasAnyNotIn, hasAnyNotIn,
jobPromise, jobPromise,
normalizeHashName, normalizeHashName,
validateBitLength,
validateKeyOps, validateKeyOps,
kHandle, kHandle,
kKeyObject, kKeyObject,
@ -41,15 +40,30 @@ const {
const generateKey = promisify(_generateKey); const generateKey = promisify(_generateKey);
function validateHmacGenerateKeyAlgorithm(algorithm) {
if (algorithm.length !== undefined) {
if (algorithm.length === 0)
throw lazyDOMException(
'Zero-length key is not supported',
'OperationError');
// The Web Crypto spec allows for key lengths that are not multiples of 8. We don't.
if (algorithm.length % 8) {
throw lazyDOMException(
'Unsupported algorithm.length',
'NotSupportedError');
}
}
}
async function hmacGenerateKey(algorithm, extractable, keyUsages) { async function hmacGenerateKey(algorithm, extractable, keyUsages) {
validateHmacGenerateKeyAlgorithm(algorithm);
const { hash, name } = algorithm; const { hash, name } = algorithm;
let { length } = algorithm; let { length } = algorithm;
if (length === undefined) if (length === undefined)
length = getBlockSize(hash.name); length = getBlockSize(hash.name);
validateBitLength(length, 'algorithm.length', true);
const usageSet = new SafeSet(keyUsages); const usageSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {
throw lazyDOMException( throw lazyDOMException(
@ -82,12 +96,27 @@ function getAlgorithmName(hash) {
} }
} }
function validateHmacImportKeyAlgorithm(algorithm) {
if (algorithm.length !== undefined) {
if (algorithm.length === 0) {
throw lazyDOMException('Zero-length key is not supported', 'DataError');
}
// The Web Crypto spec allows for key lengths that are not multiples of 8. We don't.
if (algorithm.length % 8) {
throw lazyDOMException('Unsupported algorithm.length', 'NotSupportedError');
}
}
}
function hmacImportKey( function hmacImportKey(
format, format,
keyData, keyData,
algorithm, algorithm,
extractable, extractable,
keyUsages) { keyUsages,
) {
validateHmacImportKeyAlgorithm(algorithm);
const usagesSet = new SafeSet(keyUsages); const usagesSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usagesSet, ['sign', 'verify'])) { if (hasAnyNotIn(usagesSet, ['sign', 'verify'])) {
throw lazyDOMException( throw lazyDOMException(
@ -97,38 +126,10 @@ function hmacImportKey(
let keyObject; let keyObject;
switch (format) { switch (format) {
case 'KeyObject': { case 'KeyObject': {
const checkLength = keyData.symmetricKeySize * 8;
if (checkLength === 0 || algorithm.length === 0)
throw lazyDOMException('Zero-length key is not supported', 'DataError');
// The Web Crypto spec allows for key lengths that are not multiples of
// 8. We don't. Our check here is stricter than that defined by the spec
// in that we require that algorithm.length match keyData.length * 8 if
// algorithm.length is specified.
if (algorithm.length !== undefined &&
algorithm.length !== checkLength) {
throw lazyDOMException('Invalid key length', 'DataError');
}
keyObject = keyData; keyObject = keyData;
break; break;
} }
case 'raw': { case 'raw': {
const checkLength = keyData.byteLength * 8;
if (checkLength === 0 || algorithm.length === 0)
throw lazyDOMException('Zero-length key is not supported', 'DataError');
// The Web Crypto spec allows for key lengths that are not multiples of
// 8. We don't. Our check here is stricter than that defined by the spec
// in that we require that algorithm.length match keyData.length * 8 if
// algorithm.length is specified.
if (algorithm.length !== undefined &&
algorithm.length !== checkLength) {
throw lazyDOMException('Invalid key length', 'DataError');
}
keyObject = createSecretKey(keyData); keyObject = createSecretKey(keyData);
break; break;
} }
@ -178,6 +179,14 @@ function hmacImportKey(
const { length } = keyObject[kHandle].keyDetail({}); const { length } = keyObject[kHandle].keyDetail({});
if (length === 0)
throw lazyDOMException('Zero-length key is not supported', 'DataError');
if (algorithm.length !== undefined &&
algorithm.length !== length) {
throw lazyDOMException('Invalid key length', 'DataError');
}
return new InternalCryptoKey( return new InternalCryptoKey(
keyObject, { keyObject, {
name: 'HMAC', name: 'HMAC',

View File

@ -92,22 +92,28 @@ function check(password, salt, iterations, keylen, digest) {
} }
const pbkdf2Promise = promisify(pbkdf2); const pbkdf2Promise = promisify(pbkdf2);
async function pbkdf2DeriveBits(algorithm, baseKey, length) { function validatePbkdf2DeriveBitsAlgorithmAndLength(algorithm, length) {
const { iterations, hash, salt } = algorithm; if (algorithm.iterations === 0)
if (iterations === 0)
throw lazyDOMException( throw lazyDOMException(
'iterations cannot be zero', 'iterations cannot be zero',
'OperationError'); 'OperationError');
if (length === 0)
return new ArrayBuffer(0);
if (length === null) if (length === null)
throw lazyDOMException('length cannot be null', 'OperationError'); throw lazyDOMException('length cannot be null', 'OperationError');
if (length % 8) { if (length % 8) {
throw lazyDOMException( throw lazyDOMException(
'length must be a multiple of 8', 'length must be a multiple of 8',
'OperationError'); 'OperationError');
} }
}
async function pbkdf2DeriveBits(algorithm, baseKey, length) {
validatePbkdf2DeriveBitsAlgorithmAndLength(algorithm, length);
const { iterations, hash, salt } = algorithm;
if (length === 0)
return new ArrayBuffer(0);
let result; let result;
try { try {

View File

@ -85,16 +85,21 @@ function verifyAcceptableRsaKeyUse(name, isPublic, usages) {
} }
} }
function rsaOaepCipher(mode, key, data, { label }) { function validateRsaOaepAlgorithm(algorithm) {
if (algorithm.label !== undefined) {
validateMaxBufferLength(algorithm.label, 'algorithm.label');
}
}
function rsaOaepCipher(mode, key, data, algorithm) {
validateRsaOaepAlgorithm(algorithm);
const type = mode === kWebCryptoCipherEncrypt ? 'public' : 'private'; const type = mode === kWebCryptoCipherEncrypt ? 'public' : 'private';
if (key.type !== type) { if (key.type !== type) {
throw lazyDOMException( throw lazyDOMException(
'The requested operation is not valid for the provided key', 'The requested operation is not valid for the provided key',
'InvalidAccessError'); 'InvalidAccessError');
} }
if (label !== undefined) {
validateMaxBufferLength(label, 'algorithm.label');
}
return jobPromise(() => new RSACipherJob( return jobPromise(() => new RSACipherJob(
kCryptoJobAsync, kCryptoJobAsync,
@ -103,14 +108,26 @@ function rsaOaepCipher(mode, key, data, { label }) {
data, data,
kKeyVariantRSA_OAEP, kKeyVariantRSA_OAEP,
normalizeHashName(key.algorithm.hash.name), normalizeHashName(key.algorithm.hash.name),
label)); algorithm.label));
}
function validateRsaKeyGenerateAlgorithm(algorithm) {
const publicExponentConverted = bigIntArrayToUnsignedInt(algorithm.publicExponent);
if (publicExponentConverted === undefined) {
throw lazyDOMException(
'The publicExponent must be equivalent to an unsigned 32-bit value',
'OperationError');
}
return publicExponentConverted;
} }
async function rsaKeyGenerate( async function rsaKeyGenerate(
algorithm, algorithm,
extractable, extractable,
keyUsages) { keyUsages,
) {
const publicExponentConverted = validateRsaKeyGenerateAlgorithm(algorithm);
const { const {
name, name,
modulusLength, modulusLength,
@ -120,13 +137,6 @@ async function rsaKeyGenerate(
const usageSet = new SafeSet(keyUsages); const usageSet = new SafeSet(keyUsages);
const publicExponentConverted = bigIntArrayToUnsignedInt(publicExponent);
if (publicExponentConverted === undefined) {
throw lazyDOMException(
'The publicExponent must be equivalent to an unsigned 32-bit value',
'OperationError');
}
switch (name) { switch (name) {
case 'RSA-OAEP': case 'RSA-OAEP':
if (hasAnyNotIn(usageSet, if (hasAnyNotIn(usageSet,
@ -319,7 +329,7 @@ function rsaImportKey(
}, keyUsages, extractable); }, keyUsages, extractable);
} }
async function rsaSignVerify(key, data, { saltLength }, signature) { function rsaSignVerify(key, data, { saltLength }, signature) {
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
const type = mode === kSignJobModeSign ? 'private' : 'public'; const type = mode === kSignJobModeSign ? 'private' : 'public';

View File

@ -50,8 +50,6 @@ const {
ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED, ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED,
ERR_CRYPTO_ENGINE_UNKNOWN, ERR_CRYPTO_ENGINE_UNKNOWN,
ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_OUT_OF_RANGE,
}, },
hideStackFrames, hideStackFrames,
} = require('internal/errors'); } = require('internal/errors');
@ -418,20 +416,6 @@ function hasAnyNotIn(set, checks) {
return false; return false;
} }
function validateBitLength(length, name, required = false) {
if (length !== undefined || required) {
validateNumber(length, name);
if (length < 0)
throw new ERR_OUT_OF_RANGE(name, '> 0');
if (length % 8) {
throw new ERR_INVALID_ARG_VALUE(
name,
length,
'must be a multiple of 8');
}
}
}
function validateByteLength(buf, name, target) { function validateByteLength(buf, name, target) {
if (buf.byteLength !== target) { if (buf.byteLength !== target) {
throw lazyDOMException( throw lazyDOMException(
@ -617,7 +601,6 @@ module.exports = {
normalizeAlgorithm, normalizeAlgorithm,
normalizeHashName, normalizeHashName,
hasAnyNotIn, hasAnyNotIn,
validateBitLength,
validateByteLength, validateByteLength,
validateByteSource, validateByteSource,
validateKeyOps, validateKeyOps,

View File

@ -182,7 +182,7 @@ async function prepareKeys() {
}, },
keys.X448.privateKey, keys.X448.privateKey,
8 * keys.X448.size), 8 * keys.X448.size),
{ message: 'The public and private keys must be of the same type' }); { message: 'algorithm.public must be an X448 key' });
} }
{ {

View File

@ -202,7 +202,7 @@ async function prepareKeys() {
public: keys['P-384'].publicKey public: keys['P-384'].publicKey
}, },
keys['P-521'].privateKey, keys['P-521'].privateKey,
8 * keys['P-521'].size), 8 * keys['P-384'].size),
{ message: /Named curve mismatch/ }); { message: /Named curve mismatch/ });
} }
@ -218,7 +218,7 @@ async function prepareKeys() {
name: 'ECDH', name: 'ECDH',
public: publicKey public: publicKey
}, keys['P-521'].privateKey, null), { }, keys['P-521'].privateKey, null), {
message: /Keys must be ECDH, X25519, or X448 keys/ message: 'algorithm.public must be an ECDH key'
}); });
} }

View File

@ -136,7 +136,7 @@ async function prepareKeys() {
}, },
keys.X448.privateKey, keys.X448.privateKey,
...otherArgs), ...otherArgs),
{ message: 'The public and private keys must be of the same type' }); { message: 'algorithm.public must be an X448 key' });
} }
{ {

View File

@ -174,7 +174,7 @@ async function prepareKeys() {
}, },
keys['P-521'].privateKey, keys['P-521'].privateKey,
...otherArgs), ...otherArgs),
{ message: /Keys must be ECDH, X25519, or X448 keys/ }); { message: 'algorithm.public must be an ECDH key' });
} }
{ {

View File

@ -37,6 +37,20 @@ const { subtle } = globalThis.crypto;
assert.strictEqual( assert.strictEqual(
Buffer.from(plaintext).toString('hex'), Buffer.from(plaintext).toString('hex'),
Buffer.from(buf).toString('hex')); Buffer.from(buf).toString('hex'));
await assert.rejects(() => subtle.encrypt({
name: 'RSA-OAEP',
}, privateKey, buf), {
name: 'InvalidAccessError',
message: 'The requested operation is not valid for the provided key'
});
await assert.rejects(() => subtle.decrypt({
name: 'RSA-OAEP',
}, publicKey, ciphertext), {
name: 'InvalidAccessError',
message: 'The requested operation is not valid for the provided key'
});
} }
test().then(common.mustCall()); test().then(common.mustCall());

View File

@ -36,6 +36,15 @@ const { subtle } = globalThis.crypto;
}, false, ['sign', 'verify']), { }, false, ['sign', 'verify']), {
code: 'ERR_MISSING_OPTION' code: 'ERR_MISSING_OPTION'
}); });
await assert.rejects(
subtle.importKey('raw', keyData, {
name: 'HMAC',
hash: 'SHA-256',
length: 384,
}, false, ['sign', 'verify']), {
name: 'DataError',
message: 'Invalid key length'
});
await assert.rejects( await assert.rejects(
subtle.importKey('raw', keyData, { subtle.importKey('raw', keyData, {
name: 'HMAC', name: 'HMAC',
@ -59,8 +68,8 @@ const { subtle } = globalThis.crypto;
hash: 'SHA-256', hash: 'SHA-256',
length: 1 length: 1
}, false, ['sign', 'verify']), { }, false, ['sign', 'verify']), {
name: 'DataError', name: 'NotSupportedError',
message: 'Invalid key length' message: 'Unsupported algorithm.length'
}); });
await assert.rejects( await assert.rejects(
subtle.importKey('jwk', null, { subtle.importKey('jwk', null, {

View File

@ -345,6 +345,14 @@ const vectors = {
{ code: 'ERR_INVALID_ARG_TYPE' }); { code: 'ERR_INVALID_ARG_TYPE' });
})); }));
await assert.rejects(
subtle.generateKey(
{ name, modulusLength, publicExponent: new Uint8Array([1, 1, 1, 1, 1]), hash }, true, usages),
{
message: /The publicExponent must be equivalent to an unsigned 32-bit value/,
name: 'OperationError',
});
await Promise.all([true, 1].map((hash) => { await Promise.all([true, 1].map((hash) => {
return assert.rejects(subtle.generateKey({ return assert.rejects(subtle.generateKey({
name, name,
@ -494,8 +502,6 @@ const vectors = {
const tests = kTests.map((args) => test(...args)); const tests = kTests.map((args) => test(...args));
// Test bad parameters
Promise.all(tests).then(common.mustCall()); Promise.all(tests).then(common.mustCall());
} }
@ -675,7 +681,5 @@ assert.throws(() => new CryptoKey(), { code: 'ERR_ILLEGAL_CONSTRUCTOR' });
const tests = kTests.map((args) => test(...args)); const tests = kTests.map((args) => test(...args));
// Test bad parameters
Promise.all(tests).then(common.mustCall()); Promise.all(tests).then(common.mustCall());
} }