UNPKG

react-native-quick-crypto

Version:

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

311 lines (277 loc) 8.27 kB
import { NitroModules } from 'react-native-nitro-modules'; import { CryptoKey, KeyObject, PrivateKeyObject, PublicKeyObject, } from './keys'; import { getUsagesUnion, hasAnyNotIn, lazyDOMException, normalizeHashName, KFormatType, KeyEncoding, } from './utils'; import type { CryptoKeyPair, KeyUsage, RsaHashedKeyGenParams, SubtleAlgorithm, GenerateKeyPairOptions, KeyPairGenConfig, } from './utils'; import type { RsaKeyPair } from './specs/rsaKeyPair.nitro'; export class Rsa { native: RsaKeyPair; constructor( modulusLength: number, publicExponent: Uint8Array, hashAlgorithm: string, ) { this.native = NitroModules.createHybridObject<RsaKeyPair>('RsaKeyPair'); this.native.setModulusLength(modulusLength); this.native.setPublicExponent( publicExponent.buffer.slice( publicExponent.byteOffset, publicExponent.byteOffset + publicExponent.byteLength, ) as ArrayBuffer, ); this.native.setHashAlgorithm(hashAlgorithm); } async generateKeyPair(): Promise<CryptoKeyPair> { await this.native.generateKeyPair(); return { publicKey: this.native.getPublicKey(), privateKey: this.native.getPrivateKey(), }; } generateKeyPairSync(): CryptoKeyPair { this.native.generateKeyPairSync(); return { publicKey: this.native.getPublicKey(), privateKey: this.native.getPrivateKey(), }; } } // Node API export async function rsa_generateKeyPair( algorithm: SubtleAlgorithm, extractable: boolean, keyUsages: KeyUsage[], ): Promise<CryptoKeyPair> { const { name, modulusLength, publicExponent, hash } = algorithm as RsaHashedKeyGenParams; // 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: string; 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: KeyUsage[] = []; let privateUsages: KeyUsage[] = []; 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, ) as PublicKeyObject; const publicKey = new CryptoKey(pub, keyAlgorithm, publicUsages, true); const privateKeyData = rsa.native.getPrivateKey(); const priv = KeyObject.createKeyObject( 'private', privateKeyData, ) as PrivateKeyObject; const privateKey = new CryptoKey( priv, keyAlgorithm, privateUsages, extractable, ); return { publicKey, privateKey }; } function rsa_prepareKeyGenParams( _type: 'rsa' | 'rsa-pss', options: GenerateKeyPairOptions | undefined, ): Rsa { if (!options) { throw new Error('Options are required for RSA key generation'); } const { modulusLength, publicExponent, hash = 'sha256', } = options as { modulusLength?: number; publicExponent?: number; hash?: string; }; 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: Rsa, encoding: KeyPairGenConfig, ): { publicKey: PublicKeyObject | Buffer | string | ArrayBuffer; privateKey: PrivateKeyObject | Buffer | string | ArrayBuffer; } { const { publicFormat, publicType, privateFormat, privateType, cipher, passphrase, } = encoding; const publicKeyData = rsa.native.getPublicKey(); const privateKeyData = rsa.native.getPrivateKey(); const pub = KeyObject.createKeyObject( 'public', publicKeyData, ) as PublicKeyObject; const priv = KeyObject.createKeyObject( 'private', privateKeyData, ) as PrivateKeyObject; let publicKey: PublicKeyObject | Buffer | string | ArrayBuffer; let privateKey: PrivateKeyObject | Buffer | string | ArrayBuffer; 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: 'rsa' | 'rsa-pss', options: GenerateKeyPairOptions | undefined, encoding: KeyPairGenConfig, ): Promise<{ publicKey: PublicKeyObject | Buffer | string | ArrayBuffer; privateKey: PrivateKeyObject | Buffer | string | ArrayBuffer; }> { const rsa = rsa_prepareKeyGenParams(type, options); await rsa.generateKeyPair(); return rsa_formatKeyPairOutput(rsa, encoding); } export function rsa_generateKeyPairNodeSync( type: 'rsa' | 'rsa-pss', options: GenerateKeyPairOptions | undefined, encoding: KeyPairGenConfig, ): { publicKey: PublicKeyObject | Buffer | string | ArrayBuffer; privateKey: PrivateKeyObject | Buffer | string | ArrayBuffer; } { const rsa = rsa_prepareKeyGenParams(type, options); rsa.generateKeyPairSync(); return rsa_formatKeyPairOutput(rsa, encoding); }