Introduce prepareSubtleMethod() and convertSubtleArgument() for the common SubtleCrypto call prelude. Methods now reuse the same receiver check, required-argument check, error prefix construction, argument context selection, and WebIDL conversion path. Introduce WebCrypto key usage helpers for common usage validation and key pair usage splitting. Algorithm modules now define their allowed public, private, and key generation usages once, then call the shared helpers from generateKey() and importKey() paths. This removes repeated function-invocation setup in webcrypto.js and repeated key usage checks across the WebCrypto algorithm modules while preserving the existing validation behavior. Mark normalized-algorithm fallback branches as unreachable assertions. Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: https://github.com/nodejs/node/pull/63975 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
255 lines
6.3 KiB
JavaScript
255 lines
6.3 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
AESCipherJob,
|
|
kCryptoJobWebCrypto,
|
|
kKeyVariantAES_CTR_128,
|
|
kKeyVariantAES_CBC_128,
|
|
kKeyVariantAES_GCM_128,
|
|
kKeyVariantAES_KW_128,
|
|
kKeyVariantAES_OCB_128,
|
|
kKeyVariantAES_CTR_192,
|
|
kKeyVariantAES_CBC_192,
|
|
kKeyVariantAES_GCM_192,
|
|
kKeyVariantAES_KW_192,
|
|
kKeyVariantAES_OCB_192,
|
|
kKeyVariantAES_CTR_256,
|
|
kKeyVariantAES_CBC_256,
|
|
kKeyVariantAES_GCM_256,
|
|
kKeyVariantAES_KW_256,
|
|
kKeyVariantAES_OCB_256,
|
|
SecretKeyGenJob,
|
|
} = internalBinding('crypto');
|
|
|
|
const {
|
|
getUsagesMask,
|
|
jobPromise,
|
|
} = require('internal/crypto/util');
|
|
|
|
const {
|
|
lazyDOMException,
|
|
} = require('internal/util');
|
|
|
|
const {
|
|
InternalCryptoKey,
|
|
getCryptoKeyAlgorithm,
|
|
getCryptoKeyHandle,
|
|
} = require('internal/crypto/keys');
|
|
|
|
const {
|
|
importJwkSecretKey,
|
|
importSecretKey,
|
|
validateJwk,
|
|
validateKeyUsages,
|
|
validateUsagesNotEmpty,
|
|
} = require('internal/crypto/webcrypto_util');
|
|
|
|
const kCipherUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'];
|
|
const kWrapUsages = ['wrapKey', 'unwrapKey'];
|
|
|
|
const kUsages = {
|
|
'__proto__': null,
|
|
'AES-CBC': kCipherUsages,
|
|
'AES-CTR': kCipherUsages,
|
|
'AES-GCM': kCipherUsages,
|
|
'AES-KW': kWrapUsages,
|
|
'AES-OCB': kCipherUsages,
|
|
};
|
|
|
|
function getAlgorithmName(name, length) {
|
|
switch (name) {
|
|
case 'AES-CBC': return `A${length}CBC`;
|
|
case 'AES-CTR': return `A${length}CTR`;
|
|
case 'AES-GCM': return `A${length}GCM`;
|
|
case 'AES-KW': return `A${length}KW`;
|
|
case 'AES-OCB': return `A${length}OCB`;
|
|
}
|
|
}
|
|
|
|
function validateKeyLength(length) {
|
|
if (length !== 128 && length !== 192 && length !== 256)
|
|
throw lazyDOMException('Invalid key length', 'DataError');
|
|
}
|
|
|
|
function getVariant(name, length) {
|
|
switch (name) {
|
|
case 'AES-CBC':
|
|
switch (length) {
|
|
case 128: return kKeyVariantAES_CBC_128;
|
|
case 192: return kKeyVariantAES_CBC_192;
|
|
case 256: return kKeyVariantAES_CBC_256;
|
|
}
|
|
break;
|
|
case 'AES-CTR':
|
|
switch (length) {
|
|
case 128: return kKeyVariantAES_CTR_128;
|
|
case 192: return kKeyVariantAES_CTR_192;
|
|
case 256: return kKeyVariantAES_CTR_256;
|
|
}
|
|
break;
|
|
case 'AES-GCM':
|
|
switch (length) {
|
|
case 128: return kKeyVariantAES_GCM_128;
|
|
case 192: return kKeyVariantAES_GCM_192;
|
|
case 256: return kKeyVariantAES_GCM_256;
|
|
}
|
|
break;
|
|
case 'AES-KW':
|
|
switch (length) {
|
|
case 128: return kKeyVariantAES_KW_128;
|
|
case 192: return kKeyVariantAES_KW_192;
|
|
case 256: return kKeyVariantAES_KW_256;
|
|
}
|
|
break;
|
|
case 'AES-OCB':
|
|
switch (length) {
|
|
case 128: return kKeyVariantAES_OCB_128;
|
|
case 192: return kKeyVariantAES_OCB_192;
|
|
case 256: return kKeyVariantAES_OCB_256;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
function asyncAesCtrCipher(mode, key, data, algorithm) {
|
|
return jobPromise(() => new AESCipherJob(
|
|
kCryptoJobWebCrypto,
|
|
mode,
|
|
getCryptoKeyHandle(key),
|
|
data,
|
|
getVariant('AES-CTR', getCryptoKeyAlgorithm(key).length),
|
|
algorithm.counter,
|
|
algorithm.length));
|
|
}
|
|
|
|
function asyncAesCbcCipher(mode, key, data, algorithm) {
|
|
return jobPromise(() => new AESCipherJob(
|
|
kCryptoJobWebCrypto,
|
|
mode,
|
|
getCryptoKeyHandle(key),
|
|
data,
|
|
getVariant('AES-CBC', getCryptoKeyAlgorithm(key).length),
|
|
algorithm.iv));
|
|
}
|
|
|
|
function asyncAesKwCipher(mode, key, data) {
|
|
return jobPromise(() => new AESCipherJob(
|
|
kCryptoJobWebCrypto,
|
|
mode,
|
|
getCryptoKeyHandle(key),
|
|
data,
|
|
getVariant('AES-KW', getCryptoKeyAlgorithm(key).length)));
|
|
}
|
|
|
|
function asyncAesGcmCipher(mode, key, data, algorithm) {
|
|
const { tagLength = 128 } = algorithm;
|
|
const tagByteLength = tagLength / 8;
|
|
|
|
return jobPromise(() => new AESCipherJob(
|
|
kCryptoJobWebCrypto,
|
|
mode,
|
|
getCryptoKeyHandle(key),
|
|
data,
|
|
getVariant('AES-GCM', getCryptoKeyAlgorithm(key).length),
|
|
algorithm.iv,
|
|
tagByteLength,
|
|
algorithm.additionalData));
|
|
}
|
|
|
|
function asyncAesOcbCipher(mode, key, data, algorithm) {
|
|
const { tagLength = 128 } = algorithm;
|
|
const tagByteLength = tagLength / 8;
|
|
|
|
return jobPromise(() => new AESCipherJob(
|
|
kCryptoJobWebCrypto,
|
|
mode,
|
|
getCryptoKeyHandle(key),
|
|
data,
|
|
getVariant('AES-OCB', getCryptoKeyAlgorithm(key).length),
|
|
algorithm.iv,
|
|
tagByteLength,
|
|
algorithm.additionalData));
|
|
}
|
|
|
|
function aesCipher(mode, key, data, algorithm) {
|
|
switch (algorithm.name) {
|
|
case 'AES-CTR': return asyncAesCtrCipher(mode, key, data, algorithm);
|
|
case 'AES-CBC': return asyncAesCbcCipher(mode, key, data, algorithm);
|
|
case 'AES-GCM': return asyncAesGcmCipher(mode, key, data, algorithm);
|
|
case 'AES-OCB': return asyncAesOcbCipher(mode, key, data, algorithm);
|
|
case 'AES-KW': return asyncAesKwCipher(mode, key, data);
|
|
}
|
|
}
|
|
|
|
function aesGenerateKey(algorithm, extractable, usages) {
|
|
const { name, length } = algorithm;
|
|
|
|
const usagesSet = validateUsagesNotEmpty(
|
|
validateKeyUsages(usages, kUsages[name], name));
|
|
|
|
return jobPromise(() => new SecretKeyGenJob(
|
|
kCryptoJobWebCrypto,
|
|
length,
|
|
{ name, length },
|
|
getUsagesMask(usagesSet),
|
|
extractable));
|
|
}
|
|
|
|
function aesImportKey(
|
|
algorithm,
|
|
format,
|
|
keyData,
|
|
extractable,
|
|
usages) {
|
|
const { name } = algorithm;
|
|
const usagesSet = validateKeyUsages(usages, kUsages[name], name);
|
|
|
|
let handle;
|
|
let length;
|
|
switch (format) {
|
|
case 'KeyObjectHandle': {
|
|
length = keyData.getSymmetricKeySize() * 8;
|
|
validateKeyLength(length);
|
|
handle = keyData;
|
|
break;
|
|
}
|
|
case 'raw-secret':
|
|
case 'raw': {
|
|
if (format === 'raw' && name === 'AES-OCB') {
|
|
return undefined;
|
|
}
|
|
length = keyData.byteLength * 8;
|
|
validateKeyLength(length);
|
|
handle = importSecretKey(keyData);
|
|
break;
|
|
}
|
|
case 'jwk': {
|
|
validateJwk(keyData, 'oct', extractable, usagesSet, 'enc');
|
|
handle = importJwkSecretKey(keyData);
|
|
length = handle.getSymmetricKeySize() * 8;
|
|
validateKeyLength(length);
|
|
if (keyData.alg !== undefined) {
|
|
if (keyData.alg !== getAlgorithmName(algorithm.name, length))
|
|
throw lazyDOMException(
|
|
'JWK "alg" does not match the requested algorithm',
|
|
'DataError');
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return undefined;
|
|
}
|
|
|
|
return new InternalCryptoKey(
|
|
handle,
|
|
{ name, length },
|
|
getUsagesMask(usagesSet),
|
|
extractable);
|
|
}
|
|
|
|
module.exports = {
|
|
aesCipher,
|
|
aesGenerateKey,
|
|
aesImportKey,
|
|
getAlgorithmName,
|
|
};
|