UNPKG

react-native-quick-crypto

Version:

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

164 lines (134 loc) 4.35 kB
import { NativeQuickCrypto } from './NativeQuickCrypto/NativeQuickCrypto'; import type { InternalSign, InternalVerify } from './NativeQuickCrypto/sig'; import Stream, { type WritableOptions } from 'readable-stream'; // TODO(osp) same as publicCipher on node this are defined on C++ and exposed to node // Do the same here enum DSASigEnc { kSigEncDER, kSigEncP1363, } import { type BinaryLike, binaryLikeToArrayBuffer, getDefaultEncoding, } from './Utils'; import { preparePrivateKey, preparePublicOrPrivateKey, type EncodingOptions, } from './keys'; const createInternalSign = NativeQuickCrypto.createSign; const createInternalVerify = NativeQuickCrypto.createVerify; function getPadding(options: EncodingOptions) { return getIntOption('padding', options); } function getSaltLength(options: EncodingOptions) { return getIntOption('saltLength', options); } function getDSASignatureEncoding(options: EncodingOptions) { if (typeof options === 'object') { const { dsaEncoding = 'der' } = options; if (dsaEncoding === 'der') return DSASigEnc.kSigEncDER; else if (dsaEncoding === 'ieee-p1363') return DSASigEnc.kSigEncP1363; throw new Error(`options.dsaEncoding: ${dsaEncoding} not a valid encoding`); } return DSASigEnc.kSigEncDER; } function getIntOption(name: keyof EncodingOptions, options: EncodingOptions) { const value = options[name]; if (value !== undefined) { if (value === value >> 0) { return value; } throw new Error(`options.${name}: ${value} not a valid int value`); } return undefined; } class Verify extends Stream.Writable { private internal: InternalVerify; constructor(algorithm: string, options?: WritableOptions) { super(options); this.internal = createInternalVerify(); this.internal.init(algorithm); } _write(chunk: BinaryLike, encoding: string, callback: () => void) { this.update(chunk, encoding); callback(); } update(data: BinaryLike, encoding?: string) { encoding = encoding ?? getDefaultEncoding(); data = binaryLikeToArrayBuffer(data, encoding); this.internal.update(data); return this; } verify(options: EncodingOptions, signature: BinaryLike): boolean { if (!options) { throw new Error('Crypto sign key required'); } const { data, format, type, passphrase } = preparePublicOrPrivateKey(options); const rsaPadding = getPadding(options); const pssSaltLength = getSaltLength(options); // Options specific to (EC)DSA const dsaSigEnc = getDSASignatureEncoding(options); const ret = this.internal.verify( data, format, type, passphrase, binaryLikeToArrayBuffer(signature), rsaPadding, pssSaltLength, dsaSigEnc, ); return ret; } } class Sign extends Stream.Writable { private internal: InternalSign; constructor(algorithm: string, options?: WritableOptions) { super(options); this.internal = createInternalSign(); this.internal.init(algorithm); } _write(chunk: BinaryLike, encoding: string, callback: () => void) { this.update(chunk, encoding); callback(); } update(data: BinaryLike, encoding?: string) { encoding = encoding ?? getDefaultEncoding(); data = binaryLikeToArrayBuffer(data, encoding); this.internal.update(data); return this; } sign(options: EncodingOptions, encoding?: string) { if (!options) { throw new Error('Crypto sign key required'); } const { data, format, type, passphrase } = preparePrivateKey(options); const rsaPadding = getPadding(options); const pssSaltLength = getSaltLength(options); // Options specific to (EC)DSA const dsaSigEnc = getDSASignatureEncoding(options); const ret = this.internal.sign( data, format, type, passphrase, rsaPadding, pssSaltLength, dsaSigEnc, ); encoding = encoding || getDefaultEncoding(); if (encoding && encoding !== 'buffer') { return Buffer.from(ret).toString(encoding as BufferEncoding); } return Buffer.from(ret); } } export function createSign(algorithm: string, options?: WritableOptions) { return new Sign(algorithm, options); } export function createVerify(algorithm: string, options?: WritableOptions) { return new Verify(algorithm, options); }