react-native-quick-crypto
Version:
A fast implementation of Node's `crypto` module written in C/C++ JSI
282 lines (276 loc) • 10.5 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.kAesKeyLengths = exports.getAlgorithmName = exports.aesImportKey = exports.aesGenerateKey = exports.aesCipher = exports.AESKeyVariant = void 0;
var _NativeQuickCrypto = require("./NativeQuickCrypto/NativeQuickCrypto");
var _Utils = require("./Utils");
var _keys = require("./keys");
var _keygen = require("./keygen");
// needs to match the values in cpp/webcrypto/crypto_aes.{h,cpp}
let AESKeyVariant = exports.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];
const kAesKeyLengths = exports.kAesKeyLengths = [128, 192, 256];
const getAlgorithmName = (name, length) => {
if (length === undefined) throw (0, _Utils.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 (0, _Utils.lazyDOMException)(`invalid algorithm name: ${name}`, 'SyntaxError');
}
};
exports.getAlgorithmName = getAlgorithmName;
function validateKeyLength(length) {
if (length !== 128 && length !== 192 && length !== 256) throw (0, _Utils.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 (0, _Utils.lazyDOMException)(`Error getting variant ${name} at length: ${length}`, 'DataError');
}
function asyncAesCtrCipher(mode, key, data, {
counter,
length
}) {
(0, _Utils.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 (0, _Utils.lazyDOMException)('AES-CTR algorithm.length must be between 1 and 128', 'OperationError');
}
return _NativeQuickCrypto.NativeQuickCrypto.webcrypto.aesCipher(mode, key.keyObject.handle, data, getVariant('AES-CTR', key.algorithm.length), (0, _Utils.bufferLikeToArrayBuffer)(counter), length);
}
function asyncAesCbcCipher(mode, key, data, {
iv
}) {
(0, _Utils.validateByteLength)(iv, 'algorithm.iv', 16);
return _NativeQuickCrypto.NativeQuickCrypto.webcrypto.aesCipher(mode, key.keyObject.handle, data, getVariant('AES-CBC', key.algorithm.length), (0, _Utils.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 (0, _Utils.lazyDOMException)(`${tagLength} is not a valid AES-GCM tag length`, 'OperationError');
}
(0, _Utils.validateMaxBufferLength)(iv, 'algorithm.iv');
if (additionalData !== undefined) {
(0, _Utils.validateMaxBufferLength)(additionalData, 'algorithm.additionalData');
}
const tagByteLength = Math.floor(tagLength / 8);
let length;
let tag = new ArrayBuffer(0);
switch (mode) {
case _keys.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 (0, _Utils.lazyDOMException)('The provided data is too small.', 'OperationError');
}
data = data.slice(0, -tagByteLength);
break;
}
case _keys.CipherOrWrapMode.kWebCryptoCipherEncrypt:
length = tagByteLength;
break;
}
return _NativeQuickCrypto.NativeQuickCrypto.webcrypto.aesCipher(mode, key.keyObject.handle, data, getVariant('AES-GCM', key.algorithm.length), (0, _Utils.bufferLikeToArrayBuffer)(iv), length, (0, _Utils.bufferLikeToArrayBuffer)(tag), (0, _Utils.bufferLikeToArrayBuffer)(additionalData || new ArrayBuffer(0)));
}
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}`);
};
exports.aesCipher = aesCipher;
const aesGenerateKey = async (algorithm, extractable, keyUsages) => {
const {
name,
length
} = algorithm;
if (!name) {
throw (0, _Utils.lazyDOMException)('Algorithm name is undefined', 'SyntaxError');
}
if (!kAesKeyLengths.includes(length)) {
throw (0, _Utils.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 ((0, _Utils.hasAnyNotIn)(keyUsages, checkUsages)) {
throw (0, _Utils.lazyDOMException)(`Unsupported key usage for an AES key: ${keyUsages}`, 'SyntaxError');
}
const [err, key] = await (0, _keygen.generateKeyPromise)('aes', {
length
});
if (err) {
throw (0, _Utils.lazyDOMException)(`aesGenerateKey (generateKeyPromise) failed: [${err.message}]`, {
name: 'OperationError',
cause: err
});
}
return new _keys.CryptoKey(key, {
name,
length
}, Array.from(keyUsages), extractable);
};
exports.aesGenerateKey = aesGenerateKey;
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 ((0, _Utils.hasAnyNotIn)(keyUsages, checkUsages)) {
throw (0, _Utils.lazyDOMException)('Unsupported key usage for an AES key', 'SyntaxError');
}
let keyObject;
let length;
switch (format) {
case 'raw':
{
const data = (0, _Utils.bufferLikeToArrayBuffer)(keyData);
validateKeyLength(data.byteLength * 8);
keyObject = (0, _keys.createSecretKey)(data);
break;
}
case 'jwk':
{
const data = keyData;
if (!data.kty) throw (0, _Utils.lazyDOMException)('Invalid keyData', 'DataError');
if (data.kty !== 'oct') throw (0, _Utils.lazyDOMException)('Invalid JWK "kty" Parameter', 'DataError');
if (keyUsages.length > 0 && data.use !== undefined && data.use !== 'enc') {
throw (0, _Utils.lazyDOMException)('Invalid JWK "use" Parameter', 'DataError');
}
(0, _Utils.validateKeyOps)(data.key_ops, keyUsages);
if (data.ext !== undefined && data.ext === false && extractable === true) {
throw (0, _Utils.lazyDOMException)('JWK "ext" Parameter and extractable mismatch', 'DataError');
}
const handle = _NativeQuickCrypto.NativeQuickCrypto.webcrypto.createKeyObjectHandle();
handle.initJwk(data);
({
length
} = handle.keyDetail());
validateKeyLength(length);
if (data.alg !== undefined) {
if (data.alg !== getAlgorithmName(algorithm.name, length)) throw (0, _Utils.lazyDOMException)('JWK "alg" does not match the requested algorithm', 'DataError');
}
keyObject = new _keys.SecretKeyObject(handle);
break;
}
default:
throw (0, _Utils.lazyDOMException)(`Unable to import AES key with format ${format}`, 'NotSupportedError');
}
if (length === undefined) {
({
length
} = keyObject.handle.keyDetail());
validateKeyLength(length);
}
return new _keys.CryptoKey(keyObject, {
name,
length
}, keyUsages, extractable);
};
exports.aesImportKey = aesImportKey;
//# sourceMappingURL=aes.js.map
;