UNPKG

react-native-quick-crypto

Version:

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

178 lines (170 loc) 7.36 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.hkdf = hkdf; exports.hkdfDeriveBits = hkdfDeriveBits; exports.hkdfExpand = hkdfExpand; exports.hkdfExpandSync = hkdfExpandSync; exports.hkdfExtract = hkdfExtract; exports.hkdfExtractSync = hkdfExtractSync; exports.hkdfSync = hkdfSync; var _reactNativeBuffer = require("@craftzdog/react-native-buffer"); var _reactNativeNitroModules = require("react-native-nitro-modules"); var _utils = require("./utils"); // Lazy load native module let native; function getNative() { if (native == null) { native = _reactNativeNitroModules.NitroModules.createHybridObject('Hkdf'); } return native; } function validateCallback(callback) { if (callback === undefined || typeof callback !== 'function') { throw new Error('No callback provided to hkdf'); } } function sanitizeInput(input, name) { try { return (0, _utils.binaryLikeToArrayBuffer)(input); } catch { throw new Error(`${name} must be a string, a Buffer, a typed array, or a DataView`); } } // Output byte-length of each fixed-length digest. HKDF requires a fixed- // output hash (it builds on HMAC), so XOFs like SHAKE128/256 are not // included even though `normalizeHashName` will accept them — passing // SHAKE here is a caller bug we surface as `Unsupported HKDF digest` // instead of letting the native side return an opaque error. const HKDF_HASH_BYTES = { sha1: 20, sha224: 28, sha256: 32, sha384: 48, sha512: 64, 'sha3-256': 32, 'sha3-384': 48, 'sha3-512': 64, ripemd160: 20 }; function hkdfHashLen(digest) { const hashLen = HKDF_HASH_BYTES[digest.toLowerCase()]; if (hashLen === undefined) { throw new TypeError(`Unsupported HKDF digest: ${digest}`); } return hashLen; } function validateHkdfKeylen(digest, keylen) { if (typeof keylen !== 'number' || !Number.isFinite(keylen) || !Number.isInteger(keylen) || keylen < 0 || keylen > 0x7fff_ffff) { throw new TypeError('Bad key length'); } const hashLen = HKDF_HASH_BYTES[digest.toLowerCase()]; if (hashLen === undefined) { throw new TypeError(`Unsupported HKDF digest: ${digest}`); } // RFC 5869 §2.3: L ≤ 255 * HashLen. if (keylen > 255 * hashLen) { throw new RangeError(`HKDF keylen ${keylen} exceeds RFC 5869 ceiling ` + `255 * HashLen (${255 * hashLen}) for ${digest}`); } } function hkdf(digest, key, salt, info, keylen, callback) { validateCallback(callback); try { const normalizedDigest = (0, _utils.normalizeHashName)(digest); const sanitizedKey = sanitizeInput(key, 'Key'); const sanitizedSalt = sanitizeInput(salt, 'Salt'); const sanitizedInfo = sanitizeInput(info, 'Info'); validateHkdfKeylen(normalizedDigest, keylen); const nativeMod = getNative(); nativeMod.deriveKey(normalizedDigest, sanitizedKey, sanitizedSalt, sanitizedInfo, keylen, 'full').then(res => { callback(null, _reactNativeBuffer.Buffer.from(res)); }, err => { callback(err); }); } catch (err) { callback(err); } } function hkdfSync(digest, key, salt, info, keylen) { const normalizedDigest = (0, _utils.normalizeHashName)(digest); const sanitizedKey = sanitizeInput(key, 'Key'); const sanitizedSalt = sanitizeInput(salt, 'Salt'); const sanitizedInfo = sanitizeInput(info, 'Info'); validateHkdfKeylen(normalizedDigest, keylen); const nativeMod = getNative(); const result = nativeMod.deriveKeySync(normalizedDigest, sanitizedKey, sanitizedSalt, sanitizedInfo, keylen, 'full'); return _reactNativeBuffer.Buffer.from(result); } // RFC 5869 §2.2 HKDF-Extract (sync): PRK = HMAC(salt, IKM). Salt defaults to // a string of HashLen zeros when omitted. Returns a PRK of HashLen bytes. function hkdfExtractSync(digest, ikm, salt = new Uint8Array(0)) { const normalizedDigest = (0, _utils.normalizeHashName)(digest); const hashLen = hkdfHashLen(normalizedDigest); const sanitizedIkm = sanitizeInput(ikm, 'IKM'); const sanitizedSalt = sanitizeInput(salt, 'Salt'); const result = getNative().deriveKeySync(normalizedDigest, sanitizedIkm, sanitizedSalt, new ArrayBuffer(0), hashLen, 'extract'); return _reactNativeBuffer.Buffer.from(result); } // Async HKDF-Extract, mirroring `hkdf`. Unlike the sync form, `salt` is // required here because the callback occupies the trailing argument. function hkdfExtract(digest, ikm, salt, callback) { validateCallback(callback); try { const normalizedDigest = (0, _utils.normalizeHashName)(digest); const hashLen = hkdfHashLen(normalizedDigest); const sanitizedIkm = sanitizeInput(ikm, 'IKM'); const sanitizedSalt = sanitizeInput(salt, 'Salt'); getNative().deriveKey(normalizedDigest, sanitizedIkm, sanitizedSalt, new ArrayBuffer(0), hashLen, 'extract').then(res => callback(null, _reactNativeBuffer.Buffer.from(res)), err => callback(err)); } catch (err) { callback(err); } } // RFC 5869 §2.3 HKDF-Expand (sync): OKM = expand(PRK, info, L). `prk` must be // at least HashLen bytes (a pseudorandom key, e.g. from hkdfExtract). function hkdfExpandSync(digest, prk, info, keylen) { const normalizedDigest = (0, _utils.normalizeHashName)(digest); const hashLen = hkdfHashLen(normalizedDigest); const sanitizedPrk = sanitizeInput(prk, 'PRK'); if (sanitizedPrk.byteLength < hashLen) { throw new RangeError(`HKDF-Expand PRK must be at least HashLen (${hashLen}) bytes for ${digest}`); } const sanitizedInfo = sanitizeInput(info, 'Info'); validateHkdfKeylen(normalizedDigest, keylen); const result = getNative().deriveKeySync(normalizedDigest, sanitizedPrk, new ArrayBuffer(0), sanitizedInfo, keylen, 'expand'); return _reactNativeBuffer.Buffer.from(result); } // Async HKDF-Expand, mirroring `hkdf`. function hkdfExpand(digest, prk, info, keylen, callback) { validateCallback(callback); try { const normalizedDigest = (0, _utils.normalizeHashName)(digest); const hashLen = hkdfHashLen(normalizedDigest); const sanitizedPrk = sanitizeInput(prk, 'PRK'); if (sanitizedPrk.byteLength < hashLen) { throw new RangeError(`HKDF-Expand PRK must be at least HashLen (${hashLen}) bytes for ${digest}`); } const sanitizedInfo = sanitizeInput(info, 'Info'); validateHkdfKeylen(normalizedDigest, keylen); getNative().deriveKey(normalizedDigest, sanitizedPrk, new ArrayBuffer(0), sanitizedInfo, keylen, 'expand').then(res => callback(null, _reactNativeBuffer.Buffer.from(res)), err => callback(err)); } catch (err) { callback(err); } } function hkdfDeriveBits(algorithm, baseKey, length) { const hash = algorithm.hash; const salt = algorithm.salt; const info = algorithm.info; // Check if key is extractable or we can access its handle/buffer // For raw keys, we can export. const keyBuffer = baseKey.keyObject.export(); // length is in bits, native expects bytes const keylen = Math.ceil(length / 8); const hashName = typeof hash === 'string' ? hash : hash.name; const normalizedDigest = (0, _utils.normalizeHashName)(hashName); validateHkdfKeylen(normalizedDigest, keylen); const nativeMod = getNative(); const result = nativeMod.deriveKeySync(normalizedDigest, (0, _utils.binaryLikeToArrayBuffer)(keyBuffer), (0, _utils.binaryLikeToArrayBuffer)(salt), (0, _utils.binaryLikeToArrayBuffer)(info), keylen, 'full'); return result; } //# sourceMappingURL=hkdf.js.map