mirror of
https://github.com/nodejs/node.git
synced 2025-04-30 07:19:19 +00:00
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:
parent
52ac44888d
commit
6fdd4e6dcf
@ -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')
|
||||||
|
@ -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,
|
||||||
|
@ -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',
|
||||||
|
@ -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) {
|
||||||
|
@ -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(
|
||||||
|
@ -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}`,
|
||||||
|
@ -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',
|
||||||
|
@ -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 {
|
||||||
|
@ -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';
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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' });
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -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'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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' });
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -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' });
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -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());
|
||||||
|
@ -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, {
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user