react-native-quick-crypto
Version:
A fast implementation of Node's `crypto` module written in C/C++ JSI
443 lines (438 loc) • 14.9 kB
JavaScript
import { Buffer as SBuffer } from 'safe-buffer';
import { CryptoKey, KWebCryptoKeyFormat, createSecretKey, CipherOrWrapMode } from './keys';
import { hasAnyNotIn, lazyDOMException, normalizeHashName, HashContext, validateMaxBufferLength, bufferLikeToArrayBuffer } from './Utils';
import { ecImportKey, ecExportKey, ecGenerateKey, ecdsaSignVerify } from './ec';
import { pbkdf2DeriveBits } from './pbkdf2';
import { asyncDigest } from './Hash';
import { aesCipher, aesGenerateKey, aesImportKey, getAlgorithmName } from './aes';
import { rsaCipher, rsaExportKey, rsaImportKey, rsaKeyGenerate } from './rsa';
import { normalizeAlgorithm } from './Algorithms';
import { hmacImportKey } from './mac';
const exportKeySpki = async key => {
switch (key.algorithm.name) {
case 'RSASSA-PKCS1-v1_5':
// Fall through
case 'RSA-PSS':
// Fall through
case 'RSA-OAEP':
if (key.type === 'public') {
return rsaExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatSPKI);
}
break;
case 'ECDSA':
// Fall through
case 'ECDH':
if (key.type === 'public') {
return ecExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatSPKI);
}
break;
// case 'Ed25519':
// // Fall through
// case 'Ed448':
// // Fall through
// case 'X25519':
// // Fall through
// case 'X448':
// if (key.type === 'public') {
// return cfrgExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatSPKI);
// }
// break;
}
throw new Error(`Unable to export a spki ${key.algorithm.name} ${key.type} key`);
};
const exportKeyPkcs8 = async key => {
switch (key.algorithm.name) {
case 'RSASSA-PKCS1-v1_5':
// Fall through
case 'RSA-PSS':
// Fall through
case 'RSA-OAEP':
if (key.type === 'private') {
return rsaExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatPKCS8);
}
break;
case 'ECDSA':
// Fall through
case 'ECDH':
if (key.type === 'private') {
return ecExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatPKCS8);
}
break;
// case 'Ed25519':
// // Fall through
// case 'Ed448':
// // Fall through
// case 'X25519':
// // Fall through
// case 'X448':
// if (key.type === 'private') {
// return cfrgExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatPKCS8);
// }
// break;
}
throw new Error(`Unable to export a pkcs8 ${key.algorithm.name} ${key.type} key`);
};
const exportKeyRaw = key => {
switch (key.algorithm.name) {
case 'ECDSA':
// Fall through
case 'ECDH':
if (key.type === 'public') {
return ecExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatRaw);
}
break;
// case 'Ed25519':
// // Fall through
// case 'Ed448':
// // Fall through
// case 'X25519':
// // Fall through
// case 'X448':
// if (key.type === 'public') {
// return require('internal/crypto/cfrg')
// .cfrgExportKey(key, kWebCryptoKeyFormatRaw);
// }
// break;
case 'AES-CTR':
// Fall through
case 'AES-CBC':
// Fall through
case 'AES-GCM':
// Fall through
case 'AES-KW':
// Fall through
case 'HMAC':
return key.keyObject.export();
}
throw lazyDOMException(`Unable to export a raw ${key.algorithm.name} ${key.type} key`, 'InvalidAccessError');
};
const exportKeyJWK = key => {
const jwk = key.keyObject.handle.exportJwk({
key_ops: key.usages,
ext: key.extractable
}, true);
switch (key.algorithm.name) {
case 'RSASSA-PKCS1-v1_5':
jwk.alg = normalizeHashName(key.algorithm.hash, HashContext.JwkRsa);
return jwk;
case 'RSA-PSS':
jwk.alg = normalizeHashName(key.algorithm.hash, HashContext.JwkRsaPss);
return jwk;
case 'RSA-OAEP':
jwk.alg = normalizeHashName(key.algorithm.hash, HashContext.JwkRsaOaep);
return jwk;
case 'HMAC':
jwk.alg = normalizeHashName(key.algorithm.hash, HashContext.JwkHmac);
return jwk;
case 'ECDSA':
// Fall through
case 'ECDH':
jwk.crv ||= key.algorithm.namedCurve;
return jwk;
// case 'X25519':
// // Fall through
// case 'X448':
// jwk.crv ||= key.algorithm.name;
// return jwk;
// case 'Ed25519':
// // Fall through
// case 'Ed448':
// jwk.crv ||= key.algorithm.name;
// return jwk;
case 'AES-CTR':
// Fall through
case 'AES-CBC':
// Fall through
case 'AES-GCM':
// Fall through
case 'AES-KW':
jwk.alg = getAlgorithmName(key.algorithm.name, key.algorithm.length);
return jwk;
// case 'HMAC':
// jwk.alg = normalizeHashName(
// key.algorithm.hash.name,
// normalizeHashName.kContextJwkHmac);
// return jwk;
default:
// Fall through
}
throw lazyDOMException(`JWK export not yet supported: ${key.algorithm.name}`, 'NotSupportedError');
};
const importGenericSecretKey = async ({
name,
length
}, format, keyData, extractable, keyUsages) => {
if (extractable) {
throw new Error(`${name} keys are not extractable`);
}
if (hasAnyNotIn(keyUsages, ['deriveKey', 'deriveBits'])) {
throw new Error(`Unsupported key usage for a ${name} key`);
}
switch (format) {
case 'raw':
{
if (hasAnyNotIn(keyUsages, ['deriveKey', 'deriveBits'])) {
throw new Error(`Unsupported key usage for a ${name} key`);
}
const checkLength = typeof keyData === 'string' || SBuffer.isBuffer(keyData) ? keyData.length * 8 : keyData.byteLength * 8;
// The Web Crypto spec allows for key lengths that are not multiples of
// 8. We don't. Our check here is stricter than that defined by the spec
// in that we require that algorithm.length match keyData.length * 8 if
// algorithm.length is specified.
if (length !== undefined && length !== checkLength) {
throw new Error('Invalid key length');
}
const keyObject = createSecretKey(keyData);
return new CryptoKey(keyObject, {
name
}, keyUsages, false);
}
}
throw new Error(`Unable to import ${name} key with format ${format}`);
};
// const checkCryptoKeyUsages = (key: CryptoKey) => {
// if (
// (key.type === 'secret' || key.type === 'private') &&
// key.usages.length === 0
// ) {
// throw lazyDOMException(
// 'Usages cannot be empty when creating a key.',
// 'SyntaxError'
// );
// }
// };
const checkCryptoKeyPairUsages = pair => {
if (!(pair.privateKey instanceof Buffer) && pair.privateKey && Object.prototype.hasOwnProperty.call(pair.privateKey, 'keyUsages')) {
const priv = pair.privateKey;
if (priv.usages.length > 0) {
return;
}
}
console.log(pair.privateKey);
throw lazyDOMException('Usages cannot be empty when creating a key.', 'SyntaxError');
};
const signVerify = (algorithm, key, data, signature) => {
const usage = signature === undefined ? 'sign' : 'verify';
algorithm = normalizeAlgorithm(algorithm, usage);
if (!key.usages.includes(usage) || algorithm.name !== key.algorithm.name) {
throw lazyDOMException(`Unable to use this key to ${usage}`, 'InvalidAccessError');
}
switch (algorithm.name) {
// case 'RSA-PSS':
// // Fall through
// case 'RSASSA-PKCS1-v1_5':
// return require('internal/crypto/rsa').rsaSignVerify(
// key,
// data,
// algorithm,
// signature
// );
case 'ECDSA':
return ecdsaSignVerify(key, data, algorithm, signature);
// case 'Ed25519':
// // Fall through
// case 'Ed448':
// return require('internal/crypto/cfrg').eddsaSignVerify(
// key,
// data,
// algorithm,
// signature
// );
// case 'HMAC':
// return require('internal/crypto/mac').hmacSignVerify(
// key,
// data,
// algorithm,
// signature
// );
}
throw lazyDOMException(`Unrecognized algorithm name '${algorithm}' for '${usage}'`, 'NotSupportedError');
};
const cipherOrWrap = async (mode, algorithm, key, data, op) => {
// We use a Node.js style error here instead of a DOMException because
// the WebCrypto spec is not specific what kind of error is to be thrown
// in this case. Both Firefox and Chrome throw simple TypeErrors here.
// The key algorithm and cipher algorithm must match, and the
// key must have the proper usage.
if (key.algorithm.name !== algorithm.name || !key.usages.includes(op)) {
throw lazyDOMException('The requested operation is not valid for the provided key', 'InvalidAccessError');
}
// While WebCrypto allows for larger input buffer sizes, we limit
// those to sizes that can fit within uint32_t because of limitations
// in the OpenSSL API.
validateMaxBufferLength(data, 'data');
switch (algorithm.name) {
case 'RSA-OAEP':
return rsaCipher(mode, key, data, algorithm);
case 'AES-CTR':
// Fall through
case 'AES-CBC':
// Fall through
case 'AES-GCM':
return aesCipher(mode, key, data, algorithm);
// case 'AES-KW':
// if (op === 'wrapKey' || op === 'unwrapKey') {
// return aesCipher(mode, key, data, algorithm);
// }
}
// @ts-expect-error unreachable code
throw lazyDOMException(`Unrecognized algorithm name '${algorithm}' for '${op}'`, 'NotSupportedError');
};
export class Subtle {
async decrypt(algorithm, key, data) {
const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'decrypt');
return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherDecrypt, normalizedAlgorithm, key, bufferLikeToArrayBuffer(data), 'decrypt');
}
async digest(algorithm, data) {
const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'digest');
return asyncDigest(normalizedAlgorithm, data);
}
async deriveBits(algorithm, baseKey, length) {
if (!baseKey.keyUsages.includes('deriveBits')) {
throw new Error('baseKey does not have deriveBits usage');
}
if (baseKey.algorithm.name !== algorithm.name) throw new Error('Key algorithm mismatch');
switch (algorithm.name) {
// case 'X25519':
// // Fall through
// case 'X448':
// // Fall through
// case 'ECDH':
// return require('internal/crypto/diffiehellman')
// .ecdhDeriveBits(algorithm, baseKey, length);
// case 'HKDF':
// return require('internal/crypto/hkdf')
// .hkdfDeriveBits(algorithm, baseKey, length);
case 'PBKDF2':
return pbkdf2DeriveBits(algorithm, baseKey, length);
}
throw new Error(`'subtle.deriveBits()' for ${algorithm.name} is not implemented.`);
}
async encrypt(algorithm, key, data) {
const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'encrypt');
return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherEncrypt, normalizedAlgorithm, key, bufferLikeToArrayBuffer(data), 'encrypt');
}
async exportKey(format, key) {
if (!key.extractable) throw new Error('key is not extractable');
switch (format) {
case 'spki':
return await exportKeySpki(key);
case 'pkcs8':
return await exportKeyPkcs8(key);
case 'jwk':
return exportKeyJWK(key);
case 'raw':
return exportKeyRaw(key);
}
}
async generateKey(algorithm, extractable, keyUsages) {
algorithm = normalizeAlgorithm(algorithm, 'generateKey');
let result;
switch (algorithm.name) {
case 'RSASSA-PKCS1-v1_5':
// Fall through
case 'RSA-PSS':
// Fall through
case 'RSA-OAEP':
result = await rsaKeyGenerate(algorithm, extractable, keyUsages);
break;
// case 'Ed25519':
// // Fall through
// case 'Ed448':
// // Fall through
// case 'X25519':
// // Fall through
// case 'X448':
// resultType = 'CryptoKeyPair';
// result = await cfrgGenerateKey(algorithm, extractable, keyUsages);
// break;
case 'ECDSA':
// Fall through
case 'ECDH':
result = await ecGenerateKey(algorithm, extractable, keyUsages);
checkCryptoKeyPairUsages(result);
break;
// case 'HMAC':
// result = await hmacGenerateKey(algorithm, extractable, keyUsages);
// break;
case 'AES-CTR':
// Fall through
case 'AES-CBC':
// Fall through
case 'AES-GCM':
// Fall through
case 'AES-KW':
result = await aesGenerateKey(algorithm, extractable, keyUsages);
break;
default:
throw new Error(`'subtle.generateKey()' is not implemented for ${algorithm.name}.
Unrecognized algorithm name`);
}
return result;
}
async importKey(format, data, algorithm, extractable, keyUsages) {
const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'importKey');
let result;
switch (normalizedAlgorithm.name) {
case 'RSASSA-PKCS1-v1_5':
// Fall through
case 'RSA-PSS':
// Fall through
case 'RSA-OAEP':
result = rsaImportKey(format, data, normalizedAlgorithm, extractable, keyUsages);
break;
case 'ECDSA':
// Fall through
case 'ECDH':
result = ecImportKey(format, data, normalizedAlgorithm, extractable, keyUsages);
break;
// case 'Ed25519':
// // Fall through
// case 'Ed448':
// // Fall through
// case 'X25519':
// // Fall through
// case 'X448':
// result = await require('internal/crypto/cfrg').cfrgImportKey(
// format,
// keyData,
// algorithm,
// extractable,
// keyUsages
// );
// break;
case 'HMAC':
result = await hmacImportKey(normalizedAlgorithm, format, data, extractable, keyUsages);
break;
case 'AES-CTR':
// Fall through
case 'AES-CBC':
// Fall through
case 'AES-GCM':
// Fall through
case 'AES-KW':
result = await aesImportKey(normalizedAlgorithm, format, data, extractable, keyUsages);
break;
// case 'HKDF':
// // Fall through
case 'PBKDF2':
result = await importGenericSecretKey(normalizedAlgorithm, format, data, extractable, keyUsages);
break;
default:
throw new Error(`"subtle.importKey()" is not implemented for ${normalizedAlgorithm.name}`);
}
if ((result.type === 'secret' || result.type === 'private') && result.usages.length === 0) {
throw new Error(`Usages cannot be empty when importing a ${result.type} key.`);
}
return result;
}
async sign(algorithm, key, data) {
return signVerify(algorithm, key, data);
}
async verify(algorithm, key, signature, data) {
return signVerify(algorithm, key, data, signature);
}
}
export const subtle = new Subtle();
//# sourceMappingURL=subtle.js.map
;