react-native-quick-crypto
Version:
A fast implementation of Node's `crypto` module written in C/C++ JSI
322 lines (301 loc) • 11.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Ed = void 0;
exports.diffieHellman = diffieHellman;
exports.ed_generateKeyPair = ed_generateKeyPair;
exports.ed_generateKeyPairWebCrypto = ed_generateKeyPairWebCrypto;
exports.xDeriveBits = xDeriveBits;
exports.x_generateKeyPairWebCrypto = x_generateKeyPairWebCrypto;
var _reactNativeNitroModules = require("react-native-nitro-modules");
var _reactNativeBuffer = require("@craftzdog/react-native-buffer");
var _keys = require("./keys");
var _utils = require("./utils");
class Ed {
constructor(type, config) {
this.type = type;
this.config = config;
this.native = _reactNativeNitroModules.NitroModules.createHybridObject('EdKeyPair');
this.native.setCurve(type);
}
/**
* Computes the Diffie-Hellman secret based on a privateKey and a publicKey.
* Both keys must have the same asymmetricKeyType, which must be one of 'dh'
* (for Diffie-Hellman), 'ec', 'x448', or 'x25519' (for ECDH).
*
* @api nodejs/node
*
* @param options `{ privateKey, publicKey }`, both of which are `KeyObject`s
* @param callback optional `(err, secret) => void`
* @returns `Buffer` if no callback, or `void` if callback is provided
*/
diffieHellman(options, callback) {
checkDiffieHellmanOptions(options);
// key types must be of certain type
const keyType = options.privateKey.asymmetricKeyType;
switch (keyType) {
case 'x25519':
case 'x448':
break;
default:
throw new Error(`Unsupported or unimplemented curve type: ${keyType}`);
}
// extract the private and public keys as ArrayBuffers
const privateKey = (0, _utils.binaryLikeToArrayBuffer)(options.privateKey);
const publicKey = (0, _utils.binaryLikeToArrayBuffer)(options.publicKey);
try {
const ret = this.native.diffieHellman(privateKey, publicKey);
if (!ret) {
throw new Error('No secret');
}
if (callback) {
callback(null, _reactNativeBuffer.Buffer.from(ret));
} else {
return _reactNativeBuffer.Buffer.from(ret);
}
} catch (e) {
const err = e;
if (callback) {
callback(err, undefined);
} else {
throw err;
}
}
}
async generateKeyPair() {
await this.native.generateKeyPair(this.config.publicFormat ?? -1, this.config.publicType ?? -1, this.config.privateFormat ?? -1, this.config.privateType ?? -1, this.config.cipher, this.config.passphrase);
}
generateKeyPairSync() {
this.native.generateKeyPairSync(this.config.publicFormat ?? -1, this.config.publicType ?? -1, this.config.privateFormat ?? -1, this.config.privateType ?? -1, this.config.cipher, this.config.passphrase);
}
getPublicKey() {
return this.native.getPublicKey();
}
getPrivateKey() {
return this.native.getPrivateKey();
}
/**
* Computes the Diffie-Hellman shared secret based on a privateKey and a
* publicKey for key exchange
*
* @api \@paulmillr/noble-curves/ed25519
*
* @param privateKey
* @param publicKey
* @returns shared secret key
*/
getSharedSecret(privateKey, publicKey) {
return this.native.diffieHellman((0, _utils.binaryLikeToArrayBuffer)(privateKey), (0, _utils.binaryLikeToArrayBuffer)(publicKey));
}
async sign(message, key) {
return key ? this.native.sign((0, _utils.binaryLikeToArrayBuffer)(message), (0, _utils.binaryLikeToArrayBuffer)(key)) : this.native.sign((0, _utils.binaryLikeToArrayBuffer)(message));
}
signSync(message, key) {
return key ? this.native.signSync((0, _utils.binaryLikeToArrayBuffer)(message), (0, _utils.binaryLikeToArrayBuffer)(key)) : this.native.signSync((0, _utils.binaryLikeToArrayBuffer)(message));
}
async verify(signature, message, key) {
return key ? this.native.verify((0, _utils.binaryLikeToArrayBuffer)(signature), (0, _utils.binaryLikeToArrayBuffer)(message), (0, _utils.binaryLikeToArrayBuffer)(key)) : this.native.verify((0, _utils.binaryLikeToArrayBuffer)(signature), (0, _utils.binaryLikeToArrayBuffer)(message));
}
verifySync(signature, message, key) {
return key ? this.native.verifySync((0, _utils.binaryLikeToArrayBuffer)(signature), (0, _utils.binaryLikeToArrayBuffer)(message), (0, _utils.binaryLikeToArrayBuffer)(key)) : this.native.verifySync((0, _utils.binaryLikeToArrayBuffer)(signature), (0, _utils.binaryLikeToArrayBuffer)(message));
}
}
// Node API
exports.Ed = Ed;
function diffieHellman(options, callback) {
const privateKey = options.privateKey;
const type = privateKey.asymmetricKeyType;
const ed = new Ed(type, {});
return ed.diffieHellman(options, callback);
}
// Node API
function ed_generateKeyPair(isAsync, type, encoding, callback) {
const ed = new Ed(type, encoding);
// Helper to convert keys to proper output format
const formatKeys = () => {
const publicKeyRaw = ed.getPublicKey();
const privateKeyRaw = ed.getPrivateKey();
// Check if PEM format was requested (KFormatType.PEM = 1)
const isPemPublic = encoding.publicFormat === _utils.KFormatType.PEM;
const isPemPrivate = encoding.privateFormat === _utils.KFormatType.PEM;
// Convert ArrayBuffer to string for PEM format
const arrayBufferToString = ab => {
return _reactNativeBuffer.Buffer.from(new Uint8Array(ab)).toString('utf-8');
};
const publicKey = isPemPublic ? arrayBufferToString(publicKeyRaw) : publicKeyRaw;
const privateKey = isPemPrivate ? arrayBufferToString(privateKeyRaw) : privateKeyRaw;
return {
publicKey,
privateKey
};
};
// Async path
if (isAsync) {
if (!callback) {
// This should not happen if called from public API
throw new Error('A callback is required for async key generation.');
}
ed.generateKeyPair().then(() => {
const {
publicKey,
privateKey
} = formatKeys();
callback(undefined, publicKey, privateKey);
}).catch(err => {
callback(err, undefined, undefined);
});
return;
}
// Sync path
let err;
try {
ed.generateKeyPairSync();
} catch (e) {
err = e instanceof Error ? e : new Error(String(e));
}
const {
publicKey,
privateKey
} = err ? {
publicKey: undefined,
privateKey: undefined
} : formatKeys();
if (callback) {
callback(err, publicKey, privateKey);
return;
}
return [err, publicKey, privateKey];
}
function checkDiffieHellmanOptions(options) {
const {
privateKey,
publicKey
} = options;
// Check if keys are KeyObject instances
if (!privateKey || typeof privateKey !== 'object' || !('type' in privateKey)) {
throw new Error('privateKey must be a KeyObject');
}
if (!publicKey || typeof publicKey !== 'object' || !('type' in publicKey)) {
throw new Error('publicKey must be a KeyObject');
}
// type checks
if (privateKey.type !== 'private') {
throw new Error('privateKey must be a private KeyObject');
}
if (publicKey.type !== 'public') {
throw new Error('publicKey must be a public KeyObject');
}
// For asymmetric keys, check if they have the asymmetricKeyType property
const privateKeyAsym = privateKey;
const publicKeyAsym = publicKey;
// key types must match
if (privateKeyAsym.asymmetricKeyType && publicKeyAsym.asymmetricKeyType && privateKeyAsym.asymmetricKeyType !== publicKeyAsym.asymmetricKeyType) {
throw new Error('Keys must be asymmetric and their types must match');
}
switch (privateKeyAsym.asymmetricKeyType) {
// case 'dh': // TODO: uncomment when implemented
case 'x25519':
case 'x448':
break;
default:
throw new Error(`Unknown curve type: ${privateKeyAsym.asymmetricKeyType}`);
}
}
async function ed_generateKeyPairWebCrypto(type, extractable, keyUsages) {
if ((0, _utils.hasAnyNotIn)(keyUsages, ['sign', 'verify'])) {
throw (0, _utils.lazyDOMException)(`Unsupported key usage for ${type}`, 'SyntaxError');
}
const publicUsages = (0, _utils.getUsagesUnion)(keyUsages, 'verify');
const privateUsages = (0, _utils.getUsagesUnion)(keyUsages, 'sign');
if (privateUsages.length === 0) {
throw (0, _utils.lazyDOMException)('Usages cannot be empty', 'SyntaxError');
}
// Request DER-encoded SPKI for public key, PKCS8 for private key
const config = {
publicFormat: _utils.KFormatType.DER,
publicType: _utils.KeyEncoding.SPKI,
privateFormat: _utils.KFormatType.DER,
privateType: _utils.KeyEncoding.PKCS8
};
const ed = new Ed(type, config);
await ed.generateKeyPair();
const algorithmName = type === 'ed25519' ? 'Ed25519' : 'Ed448';
const publicKeyData = ed.getPublicKey();
const privateKeyData = ed.getPrivateKey();
const pub = _keys.KeyObject.createKeyObject('public', publicKeyData, _utils.KFormatType.DER, _utils.KeyEncoding.SPKI);
const publicKey = new _keys.CryptoKey(pub, {
name: algorithmName
}, publicUsages, true);
const priv = _keys.KeyObject.createKeyObject('private', privateKeyData, _utils.KFormatType.DER, _utils.KeyEncoding.PKCS8);
const privateKey = new _keys.CryptoKey(priv, {
name: algorithmName
}, privateUsages, extractable);
return {
publicKey,
privateKey
};
}
async function x_generateKeyPairWebCrypto(type, extractable, keyUsages) {
if ((0, _utils.hasAnyNotIn)(keyUsages, ['deriveKey', 'deriveBits'])) {
throw (0, _utils.lazyDOMException)(`Unsupported key usage for ${type}`, 'SyntaxError');
}
const publicUsages = (0, _utils.getUsagesUnion)(keyUsages);
const privateUsages = (0, _utils.getUsagesUnion)(keyUsages, 'deriveKey', 'deriveBits');
if (privateUsages.length === 0) {
throw (0, _utils.lazyDOMException)('Usages cannot be empty', 'SyntaxError');
}
// Request DER-encoded SPKI for public key, PKCS8 for private key
const config = {
publicFormat: _utils.KFormatType.DER,
publicType: _utils.KeyEncoding.SPKI,
privateFormat: _utils.KFormatType.DER,
privateType: _utils.KeyEncoding.PKCS8
};
const ed = new Ed(type, config);
await ed.generateKeyPair();
const algorithmName = type === 'x25519' ? 'X25519' : 'X448';
const publicKeyData = ed.getPublicKey();
const privateKeyData = ed.getPrivateKey();
const pub = _keys.KeyObject.createKeyObject('public', publicKeyData, _utils.KFormatType.DER, _utils.KeyEncoding.SPKI);
const publicKey = new _keys.CryptoKey(pub, {
name: algorithmName
}, publicUsages, true);
const priv = _keys.KeyObject.createKeyObject('private', privateKeyData, _utils.KFormatType.DER, _utils.KeyEncoding.PKCS8);
const privateKey = new _keys.CryptoKey(priv, {
name: algorithmName
}, privateUsages, extractable);
return {
publicKey,
privateKey
};
}
function xDeriveBits(algorithm, baseKey, length) {
const publicParams = algorithm;
const publicKey = publicParams.public;
if (!publicKey) {
throw new Error('Public key is required for X25519/X448 derivation');
}
if (baseKey.algorithm.name !== publicKey.algorithm.name) {
throw new Error('Keys must be of the same algorithm');
}
const type = baseKey.algorithm.name.toLowerCase();
const ed = new Ed(type, {});
// Export raw keys
const privateKeyBytes = baseKey.keyObject.handle.exportKey();
const publicKeyBytes = publicKey.keyObject.handle.exportKey();
const privateKeyTyped = new Uint8Array(privateKeyBytes);
const publicKeyTyped = new Uint8Array(publicKeyBytes);
const secret = ed.getSharedSecret(privateKeyTyped, publicKeyTyped);
// If length is null, return the full secret
if (length === null) {
return secret;
}
// If length is specified, truncate
const byteLength = Math.ceil(length / 8);
if (secret.byteLength >= byteLength) {
return secret.slice(0, byteLength);
}
throw new Error('Derived key is shorter than requested length');
}
//# sourceMappingURL=ed.js.map