react-native-quick-crypto
Version:
A fast implementation of Node's `crypto` module written in C/C++ JSI
255 lines (223 loc) • 6.91 kB
text/typescript
import {
AsymmetricKeyObject,
CryptoKey,
KeyObject,
SecretKeyObject,
PublicKeyObject,
PrivateKeyObject,
} from './classes';
import { generateKeyPair, generateKeyPairSync } from './generateKeyPair';
import { createSign, createVerify, Sign, Verify } from './signVerify';
import {
publicEncrypt,
publicDecrypt,
privateEncrypt,
privateDecrypt,
} from './publicCipher';
import {
isCryptoKey,
parseKeyEncoding,
parsePrivateKeyEncoding,
parsePublicKeyEncoding,
} from './utils';
import type { BinaryLike } from '../utils';
import {
binaryLikeToArrayBuffer as toAB,
isStringOrBuffer,
KFormatType,
KeyEncoding,
} from '../utils';
import { randomBytes } from '../random';
interface KeyInputObject {
key: BinaryLike | KeyObject | CryptoKey;
format?: 'pem' | 'der' | 'jwk';
type?: 'pkcs1' | 'pkcs8' | 'spki' | 'sec1';
passphrase?: BinaryLike;
encoding?: BufferEncoding;
}
type KeyInput = BinaryLike | KeyInputObject | KeyObject | CryptoKey;
function createSecretKey(key: BinaryLike): SecretKeyObject {
const keyBuffer = toAB(key);
return KeyObject.createKeyObject('secret', keyBuffer) as SecretKeyObject;
}
function prepareAsymmetricKey(
key: KeyInput,
isPublic: boolean,
): {
data: ArrayBuffer;
format?: 'pem' | 'der';
type?: 'pkcs1' | 'pkcs8' | 'spki' | 'sec1';
} {
if (key instanceof KeyObject) {
if (isPublic) {
// createPublicKey can accept either a public key or extract public from private
if (key.type === 'secret') {
throw new Error('Cannot create public key from secret key');
}
// Export as SPKI (public key format) - works for both public and private keys
const exported = key.handle.exportKey(KFormatType.DER, KeyEncoding.SPKI);
return { data: exported, format: 'der', type: 'spki' };
} else {
// createPrivateKey requires a private key
if (key.type !== 'private') {
throw new Error('Key must be a private key');
}
const exported = key.handle.exportKey(KFormatType.DER, KeyEncoding.PKCS8);
return { data: exported, format: 'der', type: 'pkcs8' };
}
}
if (isCryptoKey(key)) {
const cryptoKey = key as CryptoKey;
return prepareAsymmetricKey(cryptoKey.keyObject, isPublic);
}
if (isStringOrBuffer(key)) {
// Detect PEM format from string content
const isPem = typeof key === 'string' && key.includes('-----BEGIN');
return { data: toAB(key), format: isPem ? 'pem' : undefined };
}
if (typeof key === 'object' && 'key' in key) {
const keyObj = key as KeyInputObject;
const { key: data, format, type } = keyObj;
if (data instanceof KeyObject) {
return prepareAsymmetricKey(data, isPublic);
}
if (isCryptoKey(data)) {
return prepareAsymmetricKey((data as CryptoKey).keyObject, isPublic);
}
if (!isStringOrBuffer(data)) {
throw new Error('Invalid key data type');
}
// For PEM format with string data, convert to ArrayBuffer
if (
(format === 'pem' ||
(typeof data === 'string' && data.includes('-----BEGIN'))) &&
typeof data === 'string'
) {
return { data: toAB(data), format: 'pem', type };
}
// Filter out 'jwk' format - only 'pem' and 'der' are supported here
const filteredFormat = format === 'jwk' ? undefined : format;
return { data: toAB(data), format: filteredFormat, type };
}
throw new Error('Invalid key input');
}
function createPublicKey(key: KeyInput): PublicKeyObject {
const { data, format, type } = prepareAsymmetricKey(key, true);
// Map format string to KFormatType enum
let kFormat: KFormatType | undefined;
if (format === 'pem') kFormat = KFormatType.PEM;
else if (format === 'der') kFormat = KFormatType.DER;
// Map type string to KeyEncoding enum
let kType: KeyEncoding | undefined;
if (type === 'spki') kType = KeyEncoding.SPKI;
else if (type === 'pkcs1') kType = KeyEncoding.PKCS1;
return KeyObject.createKeyObject(
'public',
data,
kFormat,
kType,
) as PublicKeyObject;
}
function createPrivateKey(key: KeyInput): PrivateKeyObject {
const { data, format, type } = prepareAsymmetricKey(key, false);
// Map format string to KFormatType enum
let kFormat: KFormatType | undefined;
if (format === 'pem') kFormat = KFormatType.PEM;
else if (format === 'der') kFormat = KFormatType.DER;
// Map type string to KeyEncoding enum
let kType: KeyEncoding | undefined;
if (type === 'pkcs8') kType = KeyEncoding.PKCS8;
else if (type === 'pkcs1') kType = KeyEncoding.PKCS1;
else if (type === 'sec1') kType = KeyEncoding.SEC1;
return KeyObject.createKeyObject(
'private',
data,
kFormat,
kType,
) as PrivateKeyObject;
}
export interface GenerateKeyOptions {
length: number;
}
function generateKeySync(
type: 'aes' | 'hmac',
options: GenerateKeyOptions,
): SecretKeyObject {
if (typeof type !== 'string') {
throw new TypeError('The "type" argument must be a string');
}
if (typeof options !== 'object' || options === null) {
throw new TypeError('The "options" argument must be an object');
}
const { length } = options;
if (typeof length !== 'number' || !Number.isInteger(length)) {
throw new TypeError('The "options.length" property must be an integer');
}
switch (type) {
case 'hmac':
if (length < 8 || length > 2 ** 31 - 1) {
throw new RangeError(
'The "options.length" property must be >= 8 and <= 2147483647',
);
}
break;
case 'aes':
if (length !== 128 && length !== 192 && length !== 256) {
throw new RangeError(
'The "options.length" property must be 128, 192, or 256',
);
}
break;
default:
throw new TypeError(
`The "type" argument must be 'aes' or 'hmac'. Received '${type}'`,
);
}
const keyBytes = length / 8;
const keyMaterial = randomBytes(keyBytes);
return createSecretKey(keyMaterial);
}
function generateKey(
type: 'aes' | 'hmac',
options: GenerateKeyOptions,
callback: (err: Error | null, key?: SecretKeyObject) => void,
): void {
if (typeof callback !== 'function') {
throw new TypeError('The "callback" argument must be a function');
}
try {
const key = generateKeySync(type, options);
process.nextTick(callback, null, key);
} catch (err) {
process.nextTick(callback, err as Error);
}
}
export {
// Node Public API
createSecretKey,
createPublicKey,
createPrivateKey,
CryptoKey,
generateKey,
generateKeySync,
generateKeyPair,
generateKeyPairSync,
AsymmetricKeyObject,
KeyObject,
createSign,
createVerify,
Sign,
Verify,
publicEncrypt,
publicDecrypt,
privateEncrypt,
privateDecrypt,
// Node Internal API
parsePublicKeyEncoding,
parsePrivateKeyEncoding,
parseKeyEncoding,
SecretKeyObject,
PublicKeyObject,
PrivateKeyObject,
isCryptoKey,
};