react-native-quick-crypto
Version:
A fast implementation of Node's `crypto` module written in C/C++ JSI
194 lines (185 loc) • 6.56 kB
JavaScript
;
import { NitroModules } from 'react-native-nitro-modules';
import { CryptoKey, KeyObject } from './keys';
import { getUsagesUnion, hasAnyNotIn, lazyDOMException, normalizeHashName, KFormatType, KeyEncoding } from './utils';
export class Rsa {
constructor(modulusLength, publicExponent, hashAlgorithm) {
this.native = NitroModules.createHybridObject('RsaKeyPair');
this.native.setModulusLength(modulusLength);
this.native.setPublicExponent(publicExponent.buffer.slice(publicExponent.byteOffset, publicExponent.byteOffset + publicExponent.byteLength));
this.native.setHashAlgorithm(hashAlgorithm);
}
async generateKeyPair() {
await this.native.generateKeyPair();
return {
publicKey: this.native.getPublicKey(),
privateKey: this.native.getPrivateKey()
};
}
generateKeyPairSync() {
this.native.generateKeyPairSync();
return {
publicKey: this.native.getPublicKey(),
privateKey: this.native.getPrivateKey()
};
}
}
// Node API
export async function rsa_generateKeyPair(algorithm, extractable, keyUsages) {
const {
name,
modulusLength,
publicExponent,
hash
} = algorithm;
// Validate parameters first
if (!modulusLength || modulusLength < 256) {
throw lazyDOMException('Invalid key length', 'OperationError');
}
if (!publicExponent || publicExponent.length === 0) {
throw lazyDOMException('Invalid public exponent', 'OperationError');
}
// Validate hash algorithm using existing validation function
let hashName;
try {
const normalizedHash = normalizeHashName(hash);
hashName = typeof hash === 'string' ? hash : hash?.name || normalizedHash;
} catch {
throw lazyDOMException('Invalid Hash Algorithm', 'NotSupportedError');
}
// Validate usages are not empty
if (keyUsages.length === 0) {
throw lazyDOMException('Usages cannot be empty', 'SyntaxError');
}
// Usage validation based on algorithm type
switch (name) {
case 'RSASSA-PKCS1-v1_5':
if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
throw lazyDOMException(`Unsupported key usage for a ${name} key`, 'SyntaxError');
}
break;
case 'RSA-PSS':
if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
throw lazyDOMException(`Unsupported key usage for a ${name} key`, 'SyntaxError');
}
break;
case 'RSA-OAEP':
if (hasAnyNotIn(keyUsages, ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'])) {
throw lazyDOMException(`Unsupported key usage for a ${name} key`, 'SyntaxError');
}
break;
default:
throw lazyDOMException('The algorithm is not supported', 'NotSupportedError');
}
// Split usages between public and private keys
let publicUsages = [];
let privateUsages = [];
switch (name) {
case 'RSASSA-PKCS1-v1_5':
case 'RSA-PSS':
publicUsages = getUsagesUnion(keyUsages, 'verify');
privateUsages = getUsagesUnion(keyUsages, 'sign');
break;
case 'RSA-OAEP':
publicUsages = getUsagesUnion(keyUsages, 'encrypt', 'wrapKey');
privateUsages = getUsagesUnion(keyUsages, 'decrypt', 'unwrapKey');
break;
}
// Validate that private key has usages for CryptoKeyPair
if (privateUsages.length === 0) {
throw lazyDOMException('Usages cannot be empty', 'SyntaxError');
}
const rsa = new Rsa(modulusLength, publicExponent, hashName);
await rsa.generateKeyPair();
const keyAlgorithm = {
name,
modulusLength,
publicExponent,
hash: {
name: hashName
}
};
// Create KeyObject instances using the standard createKeyObject method
const publicKeyData = rsa.native.getPublicKey();
const pub = KeyObject.createKeyObject('public', publicKeyData);
const publicKey = new CryptoKey(pub, keyAlgorithm, publicUsages, true);
const privateKeyData = rsa.native.getPrivateKey();
const priv = KeyObject.createKeyObject('private', privateKeyData);
const privateKey = new CryptoKey(priv, keyAlgorithm, privateUsages, extractable);
return {
publicKey,
privateKey
};
}
function rsa_prepareKeyGenParams(_type, options) {
if (!options) {
throw new Error('Options are required for RSA key generation');
}
const {
modulusLength,
publicExponent,
hash = 'sha256'
} = options;
if (!modulusLength || modulusLength < 256) {
throw new Error('Invalid modulus length');
}
const pubExp = publicExponent || 65537;
const pubExpBytes = new Uint8Array([pubExp >> 16 & 0xff, pubExp >> 8 & 0xff, pubExp & 0xff]);
const hashName = typeof hash === 'string' ? hash : hash;
return new Rsa(modulusLength, pubExpBytes, hashName);
}
function rsa_formatKeyPairOutput(rsa, encoding) {
const {
publicFormat,
publicType,
privateFormat,
privateType,
cipher,
passphrase
} = encoding;
const publicKeyData = rsa.native.getPublicKey();
const privateKeyData = rsa.native.getPrivateKey();
const pub = KeyObject.createKeyObject('public', publicKeyData);
const priv = KeyObject.createKeyObject('private', privateKeyData);
let publicKey;
let privateKey;
if (publicFormat === -1) {
publicKey = pub;
} else {
const format = publicFormat === KFormatType.PEM ? KFormatType.PEM : KFormatType.DER;
const keyEncoding = publicType === KeyEncoding.SPKI ? KeyEncoding.SPKI : KeyEncoding.PKCS1;
const exported = pub.handle.exportKey(format, keyEncoding);
if (format === KFormatType.PEM) {
publicKey = Buffer.from(new Uint8Array(exported)).toString('utf-8');
} else {
publicKey = exported;
}
}
if (privateFormat === -1) {
privateKey = priv;
} else {
const format = privateFormat === KFormatType.PEM ? KFormatType.PEM : KFormatType.DER;
const keyEncoding = privateType === KeyEncoding.PKCS8 ? KeyEncoding.PKCS8 : KeyEncoding.PKCS1;
const exported = priv.handle.exportKey(format, keyEncoding, cipher, passphrase);
if (format === KFormatType.PEM) {
privateKey = Buffer.from(new Uint8Array(exported)).toString('utf-8');
} else {
privateKey = exported;
}
}
return {
publicKey,
privateKey
};
}
export async function rsa_generateKeyPairNode(type, options, encoding) {
const rsa = rsa_prepareKeyGenParams(type, options);
await rsa.generateKeyPair();
return rsa_formatKeyPairOutput(rsa, encoding);
}
export function rsa_generateKeyPairNodeSync(type, options, encoding) {
const rsa = rsa_prepareKeyGenParams(type, options);
rsa.generateKeyPairSync();
return rsa_formatKeyPairOutput(rsa, encoding);
}
//# sourceMappingURL=rsa.js.map