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
text/typescript
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);
}