Skip to content

src: refactor SubtleCrypto algorithm and length validations #57319

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 2 additions & 50 deletions lib/internal/crypto/aes.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ const {
ArrayBufferIsView,
ArrayBufferPrototypeSlice,
ArrayFrom,
ArrayPrototypeIncludes,
ArrayPrototypePush,
MathFloor,
PromiseReject,
SafeSet,
TypedArrayPrototypeSlice,
Expand Down Expand Up @@ -35,10 +33,7 @@ const {
const {
hasAnyNotIn,
jobPromise,
validateByteLength,
validateKeyOps,
validateMaxBufferLength,
kAesKeyLengths,
kHandle,
kKeyObject,
} = require('internal/crypto/util');
Expand All @@ -58,7 +53,6 @@ const {
generateKey: _generateKey,
} = require('internal/crypto/keygen');

const kTagLengths = [32, 64, 96, 104, 112, 120, 128];
const generateKey = promisify(_generateKey);

function getAlgorithmName(name, length) {
Expand Down Expand Up @@ -108,20 +102,7 @@ function getVariant(name, length) {
}
}

function validateAesCtrAlgorithm(algorithm) {
validateByteLength(algorithm.counter, 'algorithm.counter', 16);
// The length must specify an integer between 1 and 128. While
// there is no default, this should typically be 64.
if (algorithm.length === 0 || algorithm.length > 128) {
throw lazyDOMException(
'AES-CTR algorithm.length must be between 1 and 128',
'OperationError');
}
}

function asyncAesCtrCipher(mode, key, data, algorithm) {
validateAesCtrAlgorithm(algorithm);

return jobPromise(() => new AESCipherJob(
kCryptoJobAsync,
mode,
Expand All @@ -132,12 +113,7 @@ function asyncAesCtrCipher(mode, key, data, algorithm) {
algorithm.length));
}

function validateAesCbcAlgorithm(algorithm) {
validateByteLength(algorithm.iv, 'algorithm.iv', 16);
}

function asyncAesCbcCipher(mode, key, data, algorithm) {
validateAesCbcAlgorithm(algorithm);
return jobPromise(() => new AESCipherJob(
kCryptoJobAsync,
mode,
Expand All @@ -156,25 +132,10 @@ function asyncAesKwCipher(mode, key, data) {
getVariant('AES-KW', key.algorithm.length)));
}

function validateAesGcmAlgorithm(algorithm) {
if (!ArrayPrototypeIncludes(kTagLengths, algorithm.tagLength)) {
throw lazyDOMException(
`${algorithm.tagLength} is not a valid AES-GCM tag length`,
'OperationError');
}

validateMaxBufferLength(algorithm.iv, 'algorithm.iv');

if (algorithm.additionalData !== undefined) {
validateMaxBufferLength(algorithm.additionalData, 'algorithm.additionalData');
}
}

function asyncAesGcmCipher(mode, key, data, algorithm) {
algorithm.tagLength ??= 128;
validateAesGcmAlgorithm(algorithm);
const { tagLength = 128 } = algorithm;

const tagByteLength = MathFloor(algorithm.tagLength / 8);
const tagByteLength = tagLength / 8;
let tag;
switch (mode) {
case kWebCryptoCipherDecrypt: {
Expand Down Expand Up @@ -220,16 +181,7 @@ function aesCipher(mode, key, data, algorithm) {
}
}

function validateAesGenerateKeyAlgorithm(algorithm) {
if (!ArrayPrototypeIncludes(kAesKeyLengths, algorithm.length)) {
throw lazyDOMException(
'AES key length must be 128, 192, or 256 bits',
'OperationError');
}
}

async function aesGenerateKey(algorithm, extractable, keyUsages) {
validateAesGenerateKeyAlgorithm(algorithm);
const { name, length } = algorithm;

const checkUsages = ['wrapKey', 'unwrapKey'];
Expand Down
8 changes: 0 additions & 8 deletions lib/internal/crypto/cfrg.js
Original file line number Diff line number Diff line change
Expand Up @@ -329,15 +329,7 @@ function cfrgImportKey(
extractable);
}

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 type = mode === kSignJobModeSign ? 'private' : 'public';

Expand Down
13 changes: 0 additions & 13 deletions lib/internal/crypto/diffiehellman.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,22 +297,9 @@ function diffieHellman(options) {
}

let masks;

function validateEcdhDeriveBitsAlgorithmAndLength(algorithm, length) {
if (algorithm.public.type !== 'public') {
throw lazyDOMException(
'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') {
Expand Down
11 changes: 0 additions & 11 deletions lib/internal/crypto/ec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';

const {
ObjectPrototypeHasOwnProperty,
SafeSet,
} = primordials;

Expand Down Expand Up @@ -76,16 +75,7 @@ function createECPublicKeyRaw(namedCurve, keyData) {
return new PublicKeyObject(handle);
}

function validateEcKeyAlgorithm(algorithm) {
if (!ObjectPrototypeHasOwnProperty(kNamedCurveAliases, algorithm.namedCurve)) {
throw lazyDOMException(
'Unrecognized namedCurve',
'NotSupportedError');
}
}

async function ecGenerateKey(algorithm, extractable, keyUsages) {
validateEcKeyAlgorithm(algorithm);
const { name, namedCurve } = algorithm;

const usageSet = new SafeSet(keyUsages);
Expand Down Expand Up @@ -158,7 +148,6 @@ function ecImportKey(
extractable,
keyUsages,
) {
validateEcKeyAlgorithm(algorithm);
const { name, namedCurve } = algorithm;

let keyObject;
Expand Down
4 changes: 2 additions & 2 deletions lib/internal/crypto/hkdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ function hkdfSync(hash, key, salt, info, length) {
}

const hkdfPromise = promisify(hkdf);
function validateHkdfDeriveBitsAlgorithmAndLength(algorithm, length) {
function validateHkdfDeriveBitsLength(length) {
if (length === null)
throw lazyDOMException('length cannot be null', 'OperationError');
if (length % 8) {
Expand All @@ -149,7 +149,7 @@ function validateHkdfDeriveBitsAlgorithmAndLength(algorithm, length) {
}

async function hkdfDeriveBits(algorithm, baseKey, length) {
validateHkdfDeriveBitsAlgorithmAndLength(algorithm, length);
validateHkdfDeriveBitsLength(length);
const { hash, salt, info } = algorithm;

if (length === 0)
Expand Down
6 changes: 1 addition & 5 deletions lib/internal/crypto/keygen.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ const {
parsePrivateKeyEncoding,
} = require('internal/crypto/keys');

const {
kAesKeyLengths,
} = require('internal/crypto/util');

const {
customPromisifyArgs,
kEmptyObject,
Expand Down Expand Up @@ -355,7 +351,7 @@ function generateKeyJob(mode, keyType, options) {
validateInteger(length, 'options.length', 8, 2 ** 31 - 1);
break;
case 'aes':
validateOneOf(length, 'options.length', kAesKeyLengths);
validateOneOf(length, 'options.length', [128, 192, 256]);
break;
default:
throw new ERR_INVALID_ARG_VALUE(
Expand Down
31 changes: 0 additions & 31 deletions lib/internal/crypto/mac.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,7 @@ const {

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) {
validateHmacGenerateKeyAlgorithm(algorithm);
const { hash, name } = algorithm;
let { length } = algorithm;

Expand Down Expand Up @@ -96,27 +79,13 @@ 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(
format,
keyData,
algorithm,
extractable,
keyUsages,
) {
validateHmacImportKeyAlgorithm(algorithm);
const usagesSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usagesSet, ['sign', 'verify'])) {
throw lazyDOMException(
Expand Down
9 changes: 2 additions & 7 deletions lib/internal/crypto/pbkdf2.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,7 @@ function check(password, salt, iterations, keylen, digest) {
}

const pbkdf2Promise = promisify(pbkdf2);
function validatePbkdf2DeriveBitsAlgorithmAndLength(algorithm, length) {
if (algorithm.iterations === 0)
throw lazyDOMException(
'iterations cannot be zero',
'OperationError');

function validatePbkdf2DeriveBitsLength(length) {
if (length === null)
throw lazyDOMException('length cannot be null', 'OperationError');

Expand All @@ -109,7 +104,7 @@ function validatePbkdf2DeriveBitsAlgorithmAndLength(algorithm, length) {
}

async function pbkdf2DeriveBits(algorithm, baseKey, length) {
validatePbkdf2DeriveBitsAlgorithmAndLength(algorithm, length);
validatePbkdf2DeriveBitsLength(length);
const { iterations, hash, salt } = algorithm;

if (length === 0)
Expand Down
16 changes: 5 additions & 11 deletions lib/internal/crypto/rsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,23 +111,17 @@ function rsaOaepCipher(mode, key, data, algorithm) {
algorithm.label));
}

function validateRsaKeyGenerateAlgorithm(algorithm) {
async function rsaKeyGenerate(
algorithm,
extractable,
keyUsages,
) {
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(
algorithm,
extractable,
keyUsages,
) {
const publicExponentConverted = validateRsaKeyGenerateAlgorithm(algorithm);
const {
name,
modulusLength,
Expand Down
12 changes: 0 additions & 12 deletions lib/internal/crypto/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,6 @@ const kNamedCurveAliases = {
'P-521': 'secp521r1',
};

const kAesKeyLengths = [128, 192, 256];

const kSupportedAlgorithms = {
'digest': {
'SHA-1': null,
Expand Down Expand Up @@ -416,14 +414,6 @@ function hasAnyNotIn(set, checks) {
return false;
}

function validateByteLength(buf, name, target) {
if (buf.byteLength !== target) {
throw lazyDOMException(
`${name} must contain exactly ${target} bytes`,
'OperationError');
}
}

const validateByteSource = hideStackFrames((val, name) => {
val = toBuf(val);

Expand Down Expand Up @@ -597,11 +587,9 @@ module.exports = {
toBuf,

kNamedCurveAliases,
kAesKeyLengths,
normalizeAlgorithm,
normalizeHashName,
hasAnyNotIn,
validateByteLength,
validateByteSource,
validateKeyOps,
jobPromise,
Expand Down
Loading
Loading