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
JavaScript
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
;