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
JavaScript
;
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