react-native-quick-crypto
Version:
A fast implementation of Node's `crypto` module written in C/C++ JSI
277 lines (231 loc) • 7.42 kB
text/typescript
import { NitroModules } from 'react-native-nitro-modules';
import { Buffer } from '@craftzdog/react-native-buffer';
import type { X509CertificateHandle } from './specs/x509certificate.nitro';
import { PublicKeyObject, KeyObject } from './keys';
import type { BinaryLike } from './utils';
import { binaryLikeToArrayBuffer } from './utils';
const X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT = 0x1;
const X509_CHECK_FLAG_NO_WILDCARDS = 0x2;
const X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS = 0x4;
const X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS = 0x8;
const X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS = 0x10;
const X509_CHECK_FLAG_NEVER_CHECK_SUBJECT = 0x20;
export interface X509LegacyObject {
subject: string;
issuer: string;
subjectaltname: string;
infoAccess: string;
ca: boolean;
modulus: undefined;
bits: undefined;
exponent: undefined;
valid_from: string;
valid_to: string;
fingerprint: string;
fingerprint256: string;
fingerprint512: string;
ext_key_usage: string[];
serialNumber: string;
raw: Buffer;
}
export interface CheckOptions {
subject?: 'default' | 'always' | 'never';
wildcards?: boolean;
partialWildcards?: boolean;
multiLabelWildcards?: boolean;
singleLabelSubdomains?: boolean;
}
function getFlags(options?: CheckOptions): number {
if (!options) return 0;
let flags = 0;
if (options.subject === 'always') {
flags |= X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT;
} else if (options.subject === 'never') {
flags |= X509_CHECK_FLAG_NEVER_CHECK_SUBJECT;
}
if (options.wildcards === false) {
flags |= X509_CHECK_FLAG_NO_WILDCARDS;
}
if (options.partialWildcards === false) {
flags |= X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS;
}
if (options.multiLabelWildcards === true) {
flags |= X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS;
}
if (options.singleLabelSubdomains === true) {
flags |= X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS;
}
return flags;
}
export class X509Certificate {
private readonly handle: X509CertificateHandle;
private readonly cache = new Map<string, unknown>();
constructor(buffer: BinaryLike) {
this.handle = NitroModules.createHybridObject<X509CertificateHandle>(
'X509CertificateHandle',
);
// For string input, route through binaryLikeToArrayBuffer so the result
// is a tight ArrayBuffer of just the encoded bytes. `Buffer.from(str).buffer`
// can return a pool-backed ArrayBuffer with byteOffset > 0, exposing
// unrelated bytes (and giving the wrong byteLength) to native.
const ab: ArrayBuffer = binaryLikeToArrayBuffer(buffer);
this.handle.init(ab);
}
private cached<T>(key: string, compute: () => T): T {
if (this.cache.has(key)) {
return this.cache.get(key) as T;
}
const value = compute();
this.cache.set(key, value);
return value;
}
get subject(): string {
return this.cached('subject', () => this.handle.subject());
}
get subjectAltName(): string {
return this.cached('subjectAltName', () => this.handle.subjectAltName());
}
get issuer(): string {
return this.cached('issuer', () => this.handle.issuer());
}
get infoAccess(): string {
return this.cached('infoAccess', () => this.handle.infoAccess());
}
get validFrom(): string {
return this.cached('validFrom', () => this.handle.validFrom());
}
get validTo(): string {
return this.cached('validTo', () => this.handle.validTo());
}
get validFromDate(): Date {
return this.cached(
'validFromDate',
() => new Date(this.handle.validFromDate()),
);
}
get validToDate(): Date {
return this.cached(
'validToDate',
() => new Date(this.handle.validToDate()),
);
}
get fingerprint(): string {
return this.cached('fingerprint', () => this.handle.fingerprint());
}
get fingerprint256(): string {
return this.cached('fingerprint256', () => this.handle.fingerprint256());
}
get fingerprint512(): string {
return this.cached('fingerprint512', () => this.handle.fingerprint512());
}
get extKeyUsage(): string[] {
return this.cached('extKeyUsage', () => this.handle.keyUsage());
}
get keyUsage(): string[] {
return this.extKeyUsage;
}
get serialNumber(): string {
return this.cached('serialNumber', () => this.handle.serialNumber());
}
get signatureAlgorithm(): string {
return this.cached('signatureAlgorithm', () =>
this.handle.signatureAlgorithm(),
);
}
get signatureAlgorithmOid(): string {
return this.cached('signatureAlgorithmOid', () =>
this.handle.signatureAlgorithmOid(),
);
}
get ca(): boolean {
return this.cached('ca', () => this.handle.ca());
}
get raw(): Buffer {
return this.cached('raw', () => Buffer.from(this.handle.raw()));
}
get publicKey(): PublicKeyObject {
return this.cached(
'publicKey',
() => new PublicKeyObject(this.handle.publicKey()),
);
}
get issuerCertificate(): undefined {
return undefined;
}
checkHost(name: string, options?: CheckOptions): string | undefined {
if (typeof name !== 'string') {
throw new TypeError('The "name" argument must be a string');
}
return this.handle.checkHost(name, getFlags(options));
}
checkEmail(email: string, options?: CheckOptions): string | undefined {
if (typeof email !== 'string') {
throw new TypeError('The "email" argument must be a string');
}
return this.handle.checkEmail(email, getFlags(options));
}
checkIP(ip: string): string | undefined {
if (typeof ip !== 'string') {
throw new TypeError('The "ip" argument must be a string');
}
return this.handle.checkIP(ip);
}
checkIssued(otherCert: X509Certificate): boolean {
if (!(otherCert instanceof X509Certificate)) {
throw new TypeError(
'The "otherCert" argument must be an instance of X509Certificate',
);
}
return this.handle.checkIssued(otherCert.handle);
}
checkPrivateKey(pkey: KeyObject): boolean {
if (!(pkey instanceof KeyObject)) {
throw new TypeError(
'The "pkey" argument must be an instance of KeyObject',
);
}
if (pkey.type !== 'private') {
throw new TypeError('The "pkey" argument must be a private key');
}
return this.handle.checkPrivateKey(pkey.handle);
}
verify(pkey: KeyObject): boolean {
if (!(pkey instanceof KeyObject)) {
throw new TypeError(
'The "pkey" argument must be an instance of KeyObject',
);
}
if (pkey.type !== 'public') {
throw new TypeError(
`The "pkey" argument must be a public key, got '${pkey.type}'`,
);
}
return this.handle.verify(pkey.handle);
}
toString(): string {
return this.cached('pem', () => this.handle.pem());
}
toJSON(): string {
return this.toString();
}
toLegacyObject(): X509LegacyObject {
return {
subject: this.subject,
issuer: this.issuer,
subjectaltname: this.subjectAltName,
infoAccess: this.infoAccess,
ca: this.ca,
modulus: undefined,
bits: undefined,
exponent: undefined,
valid_from: this.validFrom,
valid_to: this.validTo,
fingerprint: this.fingerprint,
fingerprint256: this.fingerprint256,
fingerprint512: this.fingerprint512,
ext_key_usage: this.keyUsage,
serialNumber: this.serialNumber,
raw: this.raw,
};
}
}