node/lib/internal/crypto/aes.js
Filip Skokan 89bf6548b7
crypto: share WebCrypto method and usage helpers
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>
2026-06-20 13:29:46 +00:00

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,
};