UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

369 lines (332 loc) 9.04 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/crypto/aes.js import { AESCipherJob, KeyObjectHandle, kCryptoJobAsync, kKeyVariantAES_CTR_128, kKeyVariantAES_CBC_128, kKeyVariantAES_GCM_128, kKeyVariantAES_KW_128, kKeyVariantAES_CTR_192, kKeyVariantAES_CBC_192, kKeyVariantAES_GCM_192, kKeyVariantAES_KW_192, kKeyVariantAES_CTR_256, kKeyVariantAES_CBC_256, kKeyVariantAES_GCM_256, kKeyVariantAES_KW_256, kWebCryptoCipherDecrypt, kWebCryptoCipherEncrypt, } from "nstdlib/stub/binding/crypto"; import { hasAnyNotIn, jobPromise, validateByteLength, validateKeyOps, validateMaxBufferLength, kAesKeyLengths, kHandle, kKeyObject, } from "nstdlib/lib/internal/crypto/util"; import { lazyDOMException, promisify } from "nstdlib/lib/internal/util"; import { InternalCryptoKey, SecretKeyObject, createSecretKey, } from "nstdlib/lib/internal/crypto/keys"; import { generateKey as _generateKey } from "nstdlib/lib/internal/crypto/keygen"; const kMaxCounterLength = 128; const kTagLengths = [32, 64, 96, 104, 112, 120, 128]; const generateKey = promisify(_generateKey); 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`; } } 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; } } function asyncAesCtrCipher(mode, key, data, { counter, length }) { validateByteLength(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 (length === 0 || length > kMaxCounterLength) { throw lazyDOMException( "AES-CTR algorithm.length must be between 1 and 128", "OperationError", ); } return jobPromise( () => new AESCipherJob( kCryptoJobAsync, mode, key[kKeyObject][kHandle], data, getVariant("AES-CTR", key.algorithm.length), counter, length, ), ); } function asyncAesCbcCipher(mode, key, data, { iv }) { validateByteLength(iv, "algorithm.iv", 16); return jobPromise( () => new AESCipherJob( kCryptoJobAsync, mode, key[kKeyObject][kHandle], data, getVariant("AES-CBC", key.algorithm.length), iv, ), ); } function asyncAesKwCipher(mode, key, data) { return jobPromise( () => new AESCipherJob( kCryptoJobAsync, mode, key[kKeyObject][kHandle], data, getVariant("AES-KW", key.algorithm.length), ), ); } function asyncAesGcmCipher( mode, key, data, { iv, additionalData, tagLength = 128 }, ) { if (!Array.prototype.includes.call(kTagLengths, tagLength)) { return Promise.reject( lazyDOMException( `${tagLength} is not a valid AES-GCM tag length`, "OperationError", ), ); } validateMaxBufferLength(iv, "algorithm.iv"); if (additionalData !== undefined) { validateMaxBufferLength(additionalData, "algorithm.additionalData"); } const tagByteLength = Math.floor(tagLength / 8); let tag; switch (mode) { case kWebCryptoCipherDecrypt: { const slice = ArrayBuffer.isView(data) ? TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice; tag = slice(data, -tagByteLength); // Refs: https://www.w3.org/TR/WebCryptoAPI/#aes-gcm-operations // // > If *plaintext* has a length less than *tagLength* bits, then `throw` // > an `OperationError`. if (tagByteLength > tag.byteLength) { return Promise.reject( lazyDOMException("The provided data is too small.", "OperationError"), ); } data = slice(data, 0, -tagByteLength); break; } case kWebCryptoCipherEncrypt: tag = tagByteLength; break; } return jobPromise( () => new AESCipherJob( kCryptoJobAsync, mode, key[kKeyObject][kHandle], data, getVariant("AES-GCM", key.algorithm.length), iv, tag, 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-KW": return asyncAesKwCipher(mode, key, data); } } async function aesGenerateKey(algorithm, extractable, keyUsages) { const { name, length } = algorithm; if (!Array.prototype.includes.call(kAesKeyLengths, length)) { throw lazyDOMException( "AES key length must be 128, 192, or 256 bits", "OperationError", ); } const checkUsages = ["wrapKey", "unwrapKey"]; if (name !== "AES-KW") Array.prototype.push.call(checkUsages, "encrypt", "decrypt"); const usagesSet = new Set(keyUsages); if (hasAnyNotIn(usagesSet, checkUsages)) { throw lazyDOMException( "Unsupported key usage for an AES key", "SyntaxError", ); } const key = await generateKey("aes", { length }).catch((err) => { throw lazyDOMException( "The operation failed for an operation-specific reason" + `[${err.message}]`, { name: "OperationError", cause: err }, ); }); return new InternalCryptoKey( key, { name, length }, Array.from(usagesSet), extractable, ); } async function aesImportKey( algorithm, format, keyData, extractable, keyUsages, ) { const { name } = algorithm; const checkUsages = ["wrapKey", "unwrapKey"]; if (name !== "AES-KW") Array.prototype.push.call(checkUsages, "encrypt", "decrypt"); const usagesSet = new Set(keyUsages); if (hasAnyNotIn(usagesSet, checkUsages)) { throw lazyDOMException( "Unsupported key usage for an AES key", "SyntaxError", ); } let keyObject; let length; switch (format) { case "raw": { validateKeyLength(keyData.byteLength * 8); keyObject = createSecretKey(keyData); break; } case "jwk": { if (!keyData.kty) throw lazyDOMException("Invalid keyData", "DataError"); if (keyData.kty !== "oct") throw lazyDOMException('Invalid JWK "kty" Parameter', "DataError"); if ( usagesSet.size > 0 && keyData.use !== undefined && keyData.use !== "enc" ) { throw lazyDOMException('Invalid JWK "use" Parameter', "DataError"); } validateKeyOps(keyData.key_ops, usagesSet); if ( keyData.ext !== undefined && keyData.ext === false && extractable === true ) { throw lazyDOMException( 'JWK "ext" Parameter and extractable mismatch', "DataError", ); } const handle = new KeyObjectHandle(); handle.initJwk(keyData); ({ length } = handle.keyDetail({})); validateKeyLength(length); if (keyData.alg !== undefined) { if (keyData.alg !== getAlgorithmName(algorithm.name, length)) throw lazyDOMException( 'JWK "alg" does not match the requested algorithm', "DataError", ); } keyObject = new SecretKeyObject(handle); break; } default: throw lazyDOMException( `Unable to import AES key with format ${format}`, "NotSupportedError", ); } if (length === undefined) { ({ length } = keyObject[kHandle].keyDetail({})); validateKeyLength(length); } return new InternalCryptoKey( keyObject, { name, length }, keyUsages, extractable, ); } export { aesCipher }; export { aesGenerateKey }; export { aesImportKey }; export { getAlgorithmName };