react-native-quick-crypto
Version:
A fast implementation of Node's `crypto` module written in C/C++ JSI
191 lines (169 loc) • 5.18 kB
text/typescript
import {
binaryLikeToArrayBuffer,
isStringOrBuffer,
KeyEncoding,
KFormatType,
} from '../utils';
import type { CryptoKeyPair, EncodingOptions } from '../utils';
import type { CryptoKey } from './classes';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const isCryptoKey = (obj: any): boolean => {
return obj !== null && obj?.keyObject !== undefined;
};
export function getCryptoKeyPair(
key: CryptoKey | CryptoKeyPair,
): CryptoKeyPair {
if ('publicKey' in key && 'privateKey' in key) return key;
throw new Error('Invalid CryptoKeyPair');
}
/**
* Parses the public key encoding based on an object. keyType must be undefined
* when this is used to parse an input encoding and must be a valid key type if
* used to parse an output encoding.
*/
export function parsePublicKeyEncoding(
enc: EncodingOptions,
keyType: string | undefined,
objName?: string,
) {
return parseKeyEncoding(enc, keyType, keyType ? true : undefined, objName);
}
/**
* Parses the private key encoding based on an object. keyType must be undefined
* when this is used to parse an input encoding and must be a valid key type if
* used to parse an output encoding.
*/
export function parsePrivateKeyEncoding(
enc: EncodingOptions,
keyType: string | undefined,
objName?: string,
) {
return parseKeyEncoding(enc, keyType, false, objName);
}
export function parseKeyEncoding(
enc: EncodingOptions,
keyType?: string,
isPublic?: boolean,
objName?: string,
) {
// validateObject(enc, 'options');
const isInput = keyType === undefined;
const { format, type } = parseKeyFormatAndType(
enc,
keyType,
isPublic,
objName,
);
let cipher, passphrase, encoding;
if (isPublic !== true) {
({ cipher, passphrase, encoding } = enc);
if (!isInput) {
if (cipher != null) {
if (typeof cipher !== 'string')
throw new Error(
`Invalid argument ${option('cipher', objName)}: ${cipher}`,
);
if (
format === KFormatType.DER &&
(type === KeyEncoding.PKCS1 || type === KeyEncoding.SEC1)
) {
throw new Error(
`Incompatible key options ${encodingNames[type]} does not support encryption`,
);
}
} else if (passphrase !== undefined) {
throw new Error(
`invalid argument ${option('cipher', objName)}: ${cipher}`,
);
}
}
if (
(isInput && passphrase !== undefined && !isStringOrBuffer(passphrase)) ||
(!isInput && cipher != null && !isStringOrBuffer(passphrase))
) {
throw new Error(
`Invalid argument value ${option('passphrase', objName)}: ${passphrase}`,
);
}
}
if (passphrase !== undefined)
passphrase = binaryLikeToArrayBuffer(passphrase, encoding);
return { format, type, cipher, passphrase };
}
const encodingNames = {
[KeyEncoding.PKCS1]: 'pkcs1',
[KeyEncoding.PKCS8]: 'pkcs8',
[KeyEncoding.SPKI]: 'spki',
[KeyEncoding.SEC1]: 'sec1',
};
function option(name: string, objName?: string) {
return objName === undefined
? `options.${name}`
: `options.${objName}.${name}`;
}
function parseKeyFormat(
formatStr?: string,
defaultFormat?: KFormatType,
optionName?: string,
) {
if (formatStr === undefined && defaultFormat !== undefined)
return defaultFormat;
else if (formatStr === 'pem') return KFormatType.PEM;
else if (formatStr === 'der') return KFormatType.DER;
else if (formatStr === 'jwk') return KFormatType.JWK;
throw new Error(`Invalid key format str: ${optionName}`);
}
function parseKeyType(
typeStr: string | undefined,
required: boolean,
keyType: string | undefined,
isPublic: boolean | undefined,
optionName: string,
): KeyEncoding | undefined {
if (typeStr === undefined && !required) {
return undefined;
} else if (typeStr === 'pkcs1') {
if (keyType !== undefined && keyType !== 'rsa') {
throw new Error(
`Crypto incompatible key options: ${typeStr} can only be used for RSA keys`,
);
}
return KeyEncoding.PKCS1;
} else if (typeStr === 'spki' && isPublic !== false) {
return KeyEncoding.SPKI;
} else if (typeStr === 'pkcs8' && isPublic !== true) {
return KeyEncoding.PKCS8;
} else if (typeStr === 'sec1' && isPublic !== true) {
if (keyType !== undefined && keyType !== 'ec') {
throw new Error(
`Incompatible key options ${typeStr} can only be used for EC keys`,
);
}
return KeyEncoding.SEC1;
}
throw new Error(`Invalid option ${optionName} - ${typeStr}`);
}
function parseKeyFormatAndType(
enc: EncodingOptions,
keyType?: string,
isPublic?: boolean,
objName?: string,
) {
const { format: formatStr, type: typeStr } = enc;
const isInput = keyType === undefined;
const format = parseKeyFormat(
formatStr,
isInput ? KFormatType.PEM : undefined,
option('format', objName),
);
const isRequired =
(!isInput || format === KFormatType.DER) && format !== KFormatType.JWK;
const type = parseKeyType(
typeStr,
isRequired,
keyType,
isPublic,
option('type', objName),
);
return { format, type };
}