UNPKG

react-native-quick-crypto

Version:

A fast implementation of Node's `crypto` module written in C/C++ JSI

275 lines (268 loc) 9.8 kB
"use strict"; import { NativeQuickCrypto } from './NativeQuickCrypto/NativeQuickCrypto'; import { lazyDOMException, hasAnyNotIn, validateKeyOps, validateByteLength, validateMaxBufferLength, bufferLikeToArrayBuffer } from './Utils'; import { CryptoKey, createSecretKey, SecretKeyObject, CipherOrWrapMode } from './keys'; import { generateKeyPromise } from './keygen'; // needs to match the values in cpp/webcrypto/crypto_aes.{h,cpp} export let AESKeyVariant = /*#__PURE__*/function (AESKeyVariant) { AESKeyVariant[AESKeyVariant["AES_CTR_128"] = 0] = "AES_CTR_128"; AESKeyVariant[AESKeyVariant["AES_CTR_192"] = 1] = "AES_CTR_192"; AESKeyVariant[AESKeyVariant["AES_CTR_256"] = 2] = "AES_CTR_256"; AESKeyVariant[AESKeyVariant["AES_CBC_128"] = 3] = "AES_CBC_128"; AESKeyVariant[AESKeyVariant["AES_CBC_192"] = 4] = "AES_CBC_192"; AESKeyVariant[AESKeyVariant["AES_CBC_256"] = 5] = "AES_CBC_256"; AESKeyVariant[AESKeyVariant["AES_GCM_128"] = 6] = "AES_GCM_128"; AESKeyVariant[AESKeyVariant["AES_GCM_192"] = 7] = "AES_GCM_192"; AESKeyVariant[AESKeyVariant["AES_GCM_256"] = 8] = "AES_GCM_256"; AESKeyVariant[AESKeyVariant["AES_KW_128"] = 9] = "AES_KW_128"; AESKeyVariant[AESKeyVariant["AES_KW_192"] = 10] = "AES_KW_192"; AESKeyVariant[AESKeyVariant["AES_KW_256"] = 11] = "AES_KW_256"; return AESKeyVariant; }({}); const kMaxCounterLength = 128; const kTagLengths = [32, 64, 96, 104, 112, 120, 128]; export const kAesKeyLengths = [128, 192, 256]; export const getAlgorithmName = (name, length) => { if (length === undefined) throw lazyDOMException(`Invalid algorithm length: ${length}`, 'SyntaxError'); 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`; default: throw lazyDOMException(`invalid algorithm name: ${name}`, 'SyntaxError'); } }; function validateKeyLength(length) { if (length !== 128 && length !== 192 && length !== 256) throw lazyDOMException(`Invalid key length: ${length}`, 'DataError'); } function getVariant(name, length) { switch (name) { case 'AES-CBC': switch (length) { case 128: return AESKeyVariant.AES_CBC_128; case 192: return AESKeyVariant.AES_CBC_192; case 256: return AESKeyVariant.AES_CBC_256; } // @ts-expect-error unreachable code break; case 'AES-CTR': switch (length) { case 128: return AESKeyVariant.AES_CTR_128; case 192: return AESKeyVariant.AES_CTR_192; case 256: return AESKeyVariant.AES_CTR_256; } // @ts-expect-error unreachable code break; case 'AES-GCM': switch (length) { case 128: return AESKeyVariant.AES_GCM_128; case 192: return AESKeyVariant.AES_GCM_192; case 256: return AESKeyVariant.AES_GCM_256; } // @ts-expect-error unreachable code break; case 'AES-KW': switch (length) { case 128: return AESKeyVariant.AES_KW_128; case 192: return AESKeyVariant.AES_KW_192; case 256: return AESKeyVariant.AES_KW_256; } // @ts-expect-error unreachable code break; } // @ts-expect-error unreachable code throw lazyDOMException(`Error getting variant ${name} at length: ${length}`, 'DataError'); } 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 NativeQuickCrypto.webcrypto.aesCipher(mode, key.keyObject.handle, data, getVariant('AES-CTR', key.algorithm.length), bufferLikeToArrayBuffer(counter), length); } function asyncAesCbcCipher(mode, key, data, { iv }) { validateByteLength(iv, 'algorithm.iv', 16); return NativeQuickCrypto.webcrypto.aesCipher(mode, key.keyObject.handle, data, getVariant('AES-CBC', key.algorithm.length), bufferLikeToArrayBuffer(iv)); } // function asyncAesKwCipher( // mode: CipherOrWrapMode, // key: CryptoKey, // data: BufferLike // ): Promise<ArrayBuffer> { // return NativeQuickCrypto.webcrypto.aesCipher( // mode, // key.keyObject.handle, // data, // getVariant('AES-KW', key.algorithm.length) // ); // } function asyncAesGcmCipher(mode, key, data, { iv, additionalData, tagLength = 128 }) { if (!kTagLengths.includes(tagLength)) { throw 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 length; let tag = new ArrayBuffer(0); switch (mode) { case CipherOrWrapMode.kWebCryptoCipherDecrypt: { // const slice = ArrayBuffer.isView(data) // ? DataView.prototype.buffer.slice // : ArrayBuffer.prototype.slice; tag = data.slice(-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) { throw lazyDOMException('The provided data is too small.', 'OperationError'); } data = data.slice(0, -tagByteLength); break; } case CipherOrWrapMode.kWebCryptoCipherEncrypt: length = tagByteLength; break; } return NativeQuickCrypto.webcrypto.aesCipher(mode, key.keyObject.handle, data, getVariant('AES-GCM', key.algorithm.length), bufferLikeToArrayBuffer(iv), length, bufferLikeToArrayBuffer(tag), bufferLikeToArrayBuffer(additionalData || new ArrayBuffer(0))); } export const aesCipher = (mode, key, data, algorithm // | WrapUnwrapParams ) => { 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); } throw new Error(`aesCipher: Unknown algorithm ${algorithm.name}`); }; export const aesGenerateKey = async (algorithm, extractable, keyUsages) => { const { name, length } = algorithm; if (!name) { throw lazyDOMException('Algorithm name is undefined', 'SyntaxError'); } if (!kAesKeyLengths.includes(length)) { throw lazyDOMException('AES key length must be 128, 192, or 256 bits', 'OperationError'); } const checkUsages = ['wrapKey', 'unwrapKey']; if (name !== 'AES-KW') { checkUsages.push('encrypt', 'decrypt'); } // const usagesSet = new SafeSet(keyUsages); if (hasAnyNotIn(keyUsages, checkUsages)) { throw lazyDOMException(`Unsupported key usage for an AES key: ${keyUsages}`, 'SyntaxError'); } const [err, key] = await generateKeyPromise('aes', { length }); if (err) { throw lazyDOMException(`aesGenerateKey (generateKeyPromise) failed: [${err.message}]`, { name: 'OperationError', cause: err }); } return new CryptoKey(key, { name, length }, Array.from(keyUsages), extractable); }; export const aesImportKey = async (algorithm, format, keyData, extractable, keyUsages) => { const { name } = algorithm; const checkUsages = ['wrapKey', 'unwrapKey']; if (name !== 'AES-KW') { checkUsages.push('encrypt', 'decrypt'); } // const usagesSet = new SafeSet(keyUsages); if (hasAnyNotIn(keyUsages, checkUsages)) { throw lazyDOMException('Unsupported key usage for an AES key', 'SyntaxError'); } let keyObject; let length; switch (format) { case 'raw': { const data = bufferLikeToArrayBuffer(keyData); validateKeyLength(data.byteLength * 8); keyObject = createSecretKey(data); break; } case 'jwk': { const data = keyData; if (!data.kty) throw lazyDOMException('Invalid keyData', 'DataError'); if (data.kty !== 'oct') throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); if (keyUsages.length > 0 && data.use !== undefined && data.use !== 'enc') { throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); } validateKeyOps(data.key_ops, keyUsages); if (data.ext !== undefined && data.ext === false && extractable === true) { throw lazyDOMException('JWK "ext" Parameter and extractable mismatch', 'DataError'); } const handle = NativeQuickCrypto.webcrypto.createKeyObjectHandle(); handle.initJwk(data); ({ length } = handle.keyDetail()); validateKeyLength(length); if (data.alg !== undefined) { if (data.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.handle.keyDetail()); validateKeyLength(length); } return new CryptoKey(keyObject, { name, length }, keyUsages, extractable); }; //# sourceMappingURL=aes.js.map