UNPKG

react-native-quick-crypto

Version:

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

211 lines (210 loc) 7.08 kB
"use strict"; import { NitroModules } from 'react-native-nitro-modules'; import { CryptoKey, KeyObject, isCryptoKey } from './keys'; import { hasAnyNotIn, lazyDOMException, getUsagesUnion, KFormatType, KeyEncoding, isStringOrBuffer, binaryLikeToArrayBuffer as toAB } from './utils'; const ML_KEM_VARIANTS = { 'ml-kem-512': 'ML-KEM-512', 'ml-kem-768': 'ML-KEM-768', 'ml-kem-1024': 'ML-KEM-1024' }; function isMlKemKeyType(type) { return type in ML_KEM_VARIANTS; } function unpackEncapsulateResult(packed) { const view = new DataView(packed); const ciphertextLen = view.getUint32(0, true); const sharedKeyLen = view.getUint32(4, true); const headerSize = 8; const ciphertext = packed.slice(headerSize, headerSize + ciphertextLen); const sharedKey = packed.slice(headerSize + ciphertextLen, headerSize + ciphertextLen + sharedKeyLen); return { ciphertext, sharedKey }; } export class MlKem { constructor(variant) { this.variant = variant; this.native = NitroModules.createHybridObject('MlKemKeyPair'); this.native.setVariant(variant); } async generateKeyPair() { await this.native.generateKeyPair(KFormatType.DER, KeyEncoding.SPKI, KFormatType.DER, KeyEncoding.PKCS8); } generateKeyPairSync() { this.native.generateKeyPairSync(KFormatType.DER, KeyEncoding.SPKI, KFormatType.DER, KeyEncoding.PKCS8); } getPublicKey() { return this.native.getPublicKey(); } getPrivateKey() { return this.native.getPrivateKey(); } setPublicKey(keyData, format, type) { this.native.setPublicKey(keyData, format, type); } setPrivateKey(keyData, format, type) { this.native.setPrivateKey(keyData, format, type); } async encapsulate() { const packed = await this.native.encapsulate(); return unpackEncapsulateResult(packed); } encapsulateSync() { const packed = this.native.encapsulateSync(); return unpackEncapsulateResult(packed); } async decapsulate(ciphertext) { return this.native.decapsulate(ciphertext); } decapsulateSync(ciphertext) { return this.native.decapsulateSync(ciphertext); } } function prepareKey(key, isPublic) { if (key instanceof KeyObject) { if (isPublic) { if (key.type === 'secret') { throw new Error('Cannot use secret key for encapsulation'); } } else { if (key.type !== 'private') { throw new Error('Key must be a private key for decapsulation'); } } return { keyObject: key }; } if (isCryptoKey(key)) { const cryptoKey = key; return prepareKey(cryptoKey.keyObject, isPublic); } if (isStringOrBuffer(key)) { const isPem = typeof key === 'string' && key.includes('-----BEGIN'); const format = isPem ? KFormatType.PEM : undefined; const keyType = isPublic ? 'public' : 'private'; const keyData = toAB(key); const keyObject = KeyObject.createKeyObject(keyType, keyData, format); return { keyObject }; } if (typeof key === 'object' && 'key' in key) { const keyObj = key; const { key: data, format, type } = keyObj; if (data instanceof KeyObject) { return { keyObject: data }; } if (isCryptoKey(data)) { return { keyObject: data.keyObject }; } if (!isStringOrBuffer(data)) { throw new Error('Invalid key data type'); } const isPem = format === 'pem' || typeof data === 'string' && data.includes('-----BEGIN'); const kFormat = isPem ? KFormatType.PEM : format === 'der' ? KFormatType.DER : undefined; let kType; if (type === 'pkcs8') kType = KeyEncoding.PKCS8;else if (type === 'pkcs1') kType = KeyEncoding.PKCS1;else if (type === 'sec1') kType = KeyEncoding.SEC1;else if (type === 'spki') kType = KeyEncoding.SPKI; const keyType = isPublic ? 'public' : 'private'; const keyData = toAB(data); const keyObject = KeyObject.createKeyObject(keyType, keyData, kFormat, kType); return { keyObject }; } throw new Error('Invalid key input'); } function getVariantFromKey(keyObject) { const keyType = keyObject.handle.getAsymmetricKeyType(); if (!isMlKemKeyType(keyType)) { throw new Error(`Key is not an ML-KEM key. Got asymmetricKeyType: ${keyType}`); } return ML_KEM_VARIANTS[keyType]; } export function encapsulate(key, callback) { const doEncapsulate = () => { if (key === null || key === undefined) { throw new Error('Public key is required for encapsulation'); } const { keyObject } = prepareKey(key, true); const variant = getVariantFromKey(keyObject); const mlkem = new MlKem(variant); const keyData = keyObject.handle.exportKey(KFormatType.DER, KeyEncoding.SPKI); mlkem.setPublicKey(keyData, KFormatType.DER, KeyEncoding.SPKI); return mlkem.encapsulateSync(); }; if (callback) { try { const result = doEncapsulate(); process.nextTick(callback, null, result); } catch (err) { process.nextTick(callback, err); } return; } return doEncapsulate(); } export function decapsulate(key, ciphertext, callback) { const doDecapsulate = () => { if (key === null || key === undefined) { throw new Error('Private key is required for decapsulation'); } const { keyObject } = prepareKey(key, false); const variant = getVariantFromKey(keyObject); const mlkem = new MlKem(variant); const keyData = keyObject.handle.exportKey(KFormatType.DER, KeyEncoding.PKCS8); mlkem.setPrivateKey(keyData, KFormatType.DER, KeyEncoding.PKCS8); const ciphertextBuffer = toAB(ciphertext); return mlkem.decapsulateSync(ciphertextBuffer); }; if (callback) { try { const result = doDecapsulate(); process.nextTick(callback, null, result); } catch (err) { process.nextTick(callback, err); } return; } return doDecapsulate(); } export async function mlkem_generateKeyPairWebCrypto(variant, extractable, keyUsages) { if (hasAnyNotIn(keyUsages, ['encapsulateBits', 'encapsulateKey', 'decapsulateBits', 'decapsulateKey'])) { throw lazyDOMException(`Unsupported key usage for ${variant}`, 'SyntaxError'); } const publicUsages = getUsagesUnion(keyUsages, 'encapsulateBits', 'encapsulateKey'); const privateUsages = getUsagesUnion(keyUsages, 'decapsulateBits', 'decapsulateKey'); if (privateUsages.length === 0) { throw lazyDOMException('Usages cannot be empty', 'SyntaxError'); } const mlkem = new MlKem(variant); await mlkem.generateKeyPair(); const publicKeyData = mlkem.getPublicKey(); const privateKeyData = mlkem.getPrivateKey(); const pub = KeyObject.createKeyObject('public', publicKeyData, KFormatType.DER, KeyEncoding.SPKI); const publicKey = new CryptoKey(pub, { name: variant }, publicUsages, true); const priv = KeyObject.createKeyObject('private', privateKeyData, KFormatType.DER, KeyEncoding.PKCS8); const privateKey = new CryptoKey(priv, { name: variant }, privateUsages, extractable); return { publicKey, privateKey }; } //# sourceMappingURL=mlkem.js.map