UNPKG

react-native-quick-crypto

Version:

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

1,480 lines (1,430 loc) 118 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Subtle = exports.PQC_SEEDLESS_PKCS8_LENGTHS = void 0; exports.isCryptoKeyPair = isCryptoKeyPair; exports.subtle = void 0; var _safeBuffer = require("safe-buffer"); var _utils = require("./utils"); var _reactNativeBuffer = require("@craftzdog/react-native-buffer"); var _keys = require("./keys"); var _conversion = require("./utils/conversion"); var _argon = require("./argon2"); var _errors = require("./utils/errors"); var _hashnames = require("./utils/hashnames"); var _validation = require("./utils/validation"); var _hash = require("./hash"); var _reactNativeNitroModules = require("react-native-nitro-modules"); var _pbkdf = require("./pbkdf2"); var _ec = require("./ec"); var _rsa = require("./rsa"); var _random = require("./random"); var _hmac = require("./hmac"); var _timingSafeEqual = require("./utils/timingSafeEqual"); var _signVerify = require("./keys/signVerify"); var _ed = require("./ed"); var _mldsa = require("./mldsa"); var _slhdsa = require("./slhdsa"); var _mlkem = require("./mlkem"); var _hkdf = require("./hkdf"); // Temporary enums that need to be defined var KWebCryptoKeyFormat = /*#__PURE__*/function (KWebCryptoKeyFormat) { KWebCryptoKeyFormat[KWebCryptoKeyFormat["kWebCryptoKeyFormatRaw"] = 0] = "kWebCryptoKeyFormatRaw"; KWebCryptoKeyFormat[KWebCryptoKeyFormat["kWebCryptoKeyFormatSPKI"] = 1] = "kWebCryptoKeyFormatSPKI"; KWebCryptoKeyFormat[KWebCryptoKeyFormat["kWebCryptoKeyFormatPKCS8"] = 2] = "kWebCryptoKeyFormatPKCS8"; return KWebCryptoKeyFormat; }(KWebCryptoKeyFormat || {}); var CipherOrWrapMode = /*#__PURE__*/function (CipherOrWrapMode) { CipherOrWrapMode[CipherOrWrapMode["kWebCryptoCipherEncrypt"] = 0] = "kWebCryptoCipherEncrypt"; CipherOrWrapMode[CipherOrWrapMode["kWebCryptoCipherDecrypt"] = 1] = "kWebCryptoCipherDecrypt"; return CipherOrWrapMode; }(CipherOrWrapMode || {}); // Placeholder functions that need to be implemented function hasAnyNotIn(usages, allowed) { return usages.some(usage => !allowed.includes(usage)); } // Mirrors webidl.requiredArguments. Node throws TypeError when a SubtleCrypto // method is called with fewer than the spec-required number of arguments // (webcrypto.js:866 etc.); we relied on TypeScript types alone, which apps // catching `instanceof TypeError` could not see at runtime. function requireArgs(actual, required, method) { if (actual < required) { throw new TypeError(`Failed to execute '${method}' on 'SubtleCrypto': ${required} arguments required, but only ${actual} present.`); } } // WebCrypto §18.4.4: algorithm name lookup is case-insensitive, but the // canonical mixed-case form is preserved in the resulting `name` field // (e.g. "aes-gcm" → "AES-GCM"). This map is built lazily on first call so // the registry of canonical names below can stay declared after the // function. Without this, callers who pass lowercase strings bypass the // downstream `SUPPORTED_ALGORITHMS` set comparisons silently. // // The map's value type is `AnyAlgorithm` so callers can use the lookup // result directly without re-asserting. The `as AnyAlgorithm` at insertion // is the single contract boundary: every name in `SUPPORTED_ALGORITHMS` is // already a member of `AnyAlgorithm` by construction. let _canonicalAlgorithmNames = null; function getCanonicalAlgorithmNames() { if (_canonicalAlgorithmNames === null) { const map = new Map(); for (const set of Object.values(SUPPORTED_ALGORITHMS)) { if (!set) continue; for (const name of set) { map.set(name.toLowerCase(), name); } } _canonicalAlgorithmNames = map; } return _canonicalAlgorithmNames; } // Per-algorithm WebIDL converter table. Mirrors Node's kAlgorithmDefinitions // (lib/internal/crypto/util.js): each (algorithm, operation) pair maps to a // dictionary converter name, or null when only the `name` member is required. // Operation keys are missing when an algorithm cannot perform that operation, // causing `normalizeAlgorithm` to reject the call. const kAlgorithmDefinitions = { 'AES-CBC': { generateKey: 'AesKeyGenParams', exportKey: null, importKey: null, encrypt: 'AesCbcParams', decrypt: 'AesCbcParams', 'get key length': 'AesDerivedKeyParams' }, 'AES-CTR': { generateKey: 'AesKeyGenParams', exportKey: null, importKey: null, encrypt: 'AesCtrParams', decrypt: 'AesCtrParams', 'get key length': 'AesDerivedKeyParams' }, 'AES-GCM': { generateKey: 'AesKeyGenParams', exportKey: null, importKey: null, encrypt: 'AeadParams', decrypt: 'AeadParams', 'get key length': 'AesDerivedKeyParams' }, 'AES-KW': { generateKey: 'AesKeyGenParams', exportKey: null, importKey: null, 'get key length': 'AesDerivedKeyParams', wrapKey: null, unwrapKey: null }, 'AES-OCB': { generateKey: 'AesKeyGenParams', exportKey: null, importKey: null, encrypt: 'AeadParams', decrypt: 'AeadParams', 'get key length': 'AesDerivedKeyParams' }, Argon2d: { deriveBits: 'Argon2Params', 'get key length': null, importKey: null }, Argon2i: { deriveBits: 'Argon2Params', 'get key length': null, importKey: null }, Argon2id: { deriveBits: 'Argon2Params', 'get key length': null, importKey: null }, 'ChaCha20-Poly1305': { generateKey: null, exportKey: null, importKey: null, encrypt: 'AeadParams', decrypt: 'AeadParams', 'get key length': null }, ECDH: { generateKey: 'EcKeyGenParams', exportKey: null, importKey: 'EcKeyImportParams', deriveBits: 'EcdhKeyDeriveParams' }, ECDSA: { generateKey: 'EcKeyGenParams', exportKey: null, importKey: 'EcKeyImportParams', sign: 'EcdsaParams', verify: 'EcdsaParams' }, Ed25519: { generateKey: null, exportKey: null, importKey: null, sign: null, verify: null }, Ed448: { generateKey: null, exportKey: null, importKey: null, sign: 'ContextParams', verify: 'ContextParams' }, HKDF: { importKey: null, deriveBits: 'HkdfParams', 'get key length': null }, HMAC: { generateKey: 'HmacKeyGenParams', exportKey: null, importKey: 'HmacImportParams', sign: null, verify: null, 'get key length': 'HmacImportParams' }, KMAC128: { generateKey: 'KmacKeyGenParams', exportKey: null, importKey: 'KmacImportParams', sign: 'KmacParams', verify: 'KmacParams', 'get key length': 'KmacImportParams' }, KMAC256: { generateKey: 'KmacKeyGenParams', exportKey: null, importKey: 'KmacImportParams', sign: 'KmacParams', verify: 'KmacParams', 'get key length': 'KmacImportParams' }, 'ML-DSA-44': { generateKey: null, exportKey: null, importKey: null, sign: 'ContextParams', verify: 'ContextParams' }, 'ML-DSA-65': { generateKey: null, exportKey: null, importKey: null, sign: 'ContextParams', verify: 'ContextParams' }, 'ML-DSA-87': { generateKey: null, exportKey: null, importKey: null, sign: 'ContextParams', verify: 'ContextParams' }, 'ML-KEM-512': { generateKey: null, exportKey: null, importKey: null, encapsulateBits: null, decapsulateBits: null, encapsulateKey: null, decapsulateKey: null }, 'ML-KEM-768': { generateKey: null, exportKey: null, importKey: null, encapsulateBits: null, decapsulateBits: null, encapsulateKey: null, decapsulateKey: null }, 'ML-KEM-1024': { generateKey: null, exportKey: null, importKey: null, encapsulateBits: null, decapsulateBits: null, encapsulateKey: null, decapsulateKey: null }, PBKDF2: { importKey: null, deriveBits: 'Pbkdf2Params', 'get key length': null }, 'RSA-OAEP': { generateKey: 'RsaHashedKeyGenParams', exportKey: null, importKey: 'RsaHashedImportParams', encrypt: 'RsaOaepParams', decrypt: 'RsaOaepParams' }, 'RSA-PSS': { generateKey: 'RsaHashedKeyGenParams', exportKey: null, importKey: 'RsaHashedImportParams', sign: 'RsaPssParams', verify: 'RsaPssParams' }, 'RSASSA-PKCS1-v1_5': { generateKey: 'RsaHashedKeyGenParams', exportKey: null, importKey: 'RsaHashedImportParams', sign: null, verify: null }, 'SHA-1': { digest: null }, 'SHA-256': { digest: null }, 'SHA-384': { digest: null }, 'SHA-512': { digest: null }, 'SHA3-256': { digest: null }, 'SHA3-384': { digest: null }, 'SHA3-512': { digest: null }, cSHAKE128: { digest: 'CShakeParams' }, cSHAKE256: { digest: 'CShakeParams' }, KT128: { digest: 'KangarooTwelveParams' }, KT256: { digest: 'KangarooTwelveParams' }, TurboSHAKE128: { digest: 'TurboShakeParams' }, TurboSHAKE256: { digest: 'TurboShakeParams' }, X25519: { generateKey: null, exportKey: null, importKey: null, deriveBits: 'EcdhKeyDeriveParams' }, X448: { generateKey: null, exportKey: null, importKey: null, deriveBits: 'EcdhKeyDeriveParams' } }; for (const v of _slhdsa.SLH_DSA_VARIANTS) { kAlgorithmDefinitions[v] = { generateKey: null, exportKey: null, importKey: null, sign: null, verify: null }; } // WebIDL dictionary member specs. Mirrors Node's per-converter // `createDictionaryConverter` definitions in lib/internal/crypto/webidl.js. // `required: true` causes `normalizeAlgorithm` to throw a TypeError when the // member is missing — matching the spec'd WebCrypto behavior that // `SubtleCrypto.supports` relies on via try/catch. const kRequiredFields = { AesKeyGenParams: [{ key: 'length', required: true }], AesDerivedKeyParams: [{ key: 'length', required: true }], AesCbcParams: [{ key: 'iv', required: true }], AesCtrParams: [{ key: 'counter', required: true }, { key: 'length', required: true }], AeadParams: [{ key: 'iv', required: true }, { key: 'tagLength' }, { key: 'additionalData' }], EcKeyGenParams: [{ key: 'namedCurve', required: true }], EcKeyImportParams: [{ key: 'namedCurve', required: true }], EcdsaParams: [{ key: 'hash', required: true }], EcdhKeyDeriveParams: [{ key: 'public', required: true }], HmacKeyGenParams: [{ key: 'hash', required: true }, { key: 'length' }], HmacImportParams: [{ key: 'hash', required: true }, { key: 'length' }], HkdfParams: [{ key: 'hash', required: true }, { key: 'salt', required: true }, { key: 'info', required: true }], Pbkdf2Params: [{ key: 'hash', required: true }, { key: 'iterations', required: true }, { key: 'salt', required: true }], RsaHashedKeyGenParams: [{ key: 'modulusLength', required: true }, { key: 'publicExponent', required: true }, { key: 'hash', required: true }], RsaHashedImportParams: [{ key: 'hash', required: true }], RsaOaepParams: [{ key: 'label' }], RsaPssParams: [{ key: 'saltLength', required: true }], ContextParams: [{ key: 'context' }], Argon2Params: [{ key: 'nonce', required: true }, { key: 'parallelism', required: true }, { key: 'memory', required: true }, { key: 'passes', required: true }, { key: 'version' }, { key: 'secretValue' }, { key: 'associatedData' }], KmacKeyGenParams: [{ key: 'length' }], KmacImportParams: [{ key: 'length' }], KmacParams: [{ key: 'outputLength', required: true }, { key: 'customization' }], CShakeParams: [{ key: 'outputLength', required: true }, { key: 'functionName' }, { key: 'customization' }], KangarooTwelveParams: [{ key: 'outputLength', required: true }, { key: 'customization' }], TurboShakeParams: [{ key: 'outputLength', required: true }, { key: 'domainSeparation' }] }; function isBufferSource(value) { return value instanceof ArrayBuffer || ArrayBuffer.isView(value); } function validateBufferSource(algorithm, key) { const value = algorithm[key]; if (value === undefined) return undefined; if (!isBufferSource(value)) { throw new TypeError(`Failed to normalize algorithm: '${key}' must be a BufferSource`); } return (0, _conversion.bufferLikeToArrayBuffer)(value); } function validateBinaryLike(algorithm, key) { const value = algorithm[key]; if (value === undefined) return undefined; try { return (0, _conversion.binaryLikeToArrayBuffer)(value); } catch { throw new TypeError(`Failed to normalize algorithm: '${key}' must be a BufferSource`); } } function validateByteLength(buffer, key, length, message) { if (buffer !== undefined && buffer.byteLength !== length) { throw (0, _errors.lazyDOMException)(message ?? `${key} must be ${length} bytes`, 'OperationError'); } } function validateUnsignedInteger(algorithm, key) { const value = algorithm[key]; if (value === undefined) return undefined; const numberValue = Number(value); if (!Number.isFinite(numberValue) || !Number.isInteger(numberValue) || numberValue < 0) { throw new TypeError(`Failed to normalize algorithm: '${key}' must be an unsigned integer`); } algorithm[key] = numberValue; return numberValue; } function validateHashAlgorithm(algorithm, converterName) { const hash = algorithm.hash; if (hash === undefined) return; const normalizedHash = (0, _hashnames.normalizeHashName)(hash, _hashnames.HashContext.WebCrypto); if (!['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512', 'SHA3-256', 'SHA3-384', 'SHA3-512'].includes(normalizedHash)) { throw (0, _errors.lazyDOMException)(`Unsupported ${converterName}.hash`, 'NotSupportedError'); } algorithm.hash = { name: normalizedHash }; } function validateAesLength(length) { if (length !== undefined && length !== 128 && length !== 192 && length !== 256) { throw (0, _errors.lazyDOMException)('Invalid key length', 'OperationError'); } } function validateMacLength(algorithm, key, zeroError) { const length = validateUnsignedInteger(algorithm, key); if (length === undefined) return; if (length === 0) { throw (0, _errors.lazyDOMException)(`${key} cannot be 0`, zeroError); } if (length % 8) { throw (0, _errors.lazyDOMException)(`Unsupported ${key}`, 'NotSupportedError'); } } function validateAeadParams(algorithm) { const iv = validateBufferSource(algorithm, 'iv'); const tagLength = validateUnsignedInteger(algorithm, 'tagLength'); validateBufferSource(algorithm, 'additionalData'); switch (algorithm.name) { case 'AES-GCM': if (tagLength !== undefined && ![32, 64, 96, 104, 112, 120, 128].includes(tagLength)) { throw (0, _errors.lazyDOMException)(`${tagLength} is not a valid AES-GCM tag length`, 'OperationError'); } break; case 'AES-OCB': if (iv !== undefined && (iv.byteLength < 1 || iv.byteLength > 15)) { throw (0, _errors.lazyDOMException)('AES-OCB algorithm.iv must be between 1 and 15 bytes', 'OperationError'); } if (tagLength !== undefined && ![64, 96, 128].includes(tagLength)) { throw (0, _errors.lazyDOMException)(`${tagLength} is not a valid AES-OCB tag length`, 'OperationError'); } break; case 'ChaCha20-Poly1305': validateByteLength(iv, 'algorithm.iv', 12, 'ChaCha20-Poly1305 IV must be exactly 12 bytes'); if (tagLength !== undefined && tagLength !== 128) { throw (0, _errors.lazyDOMException)(`${tagLength} is not a valid ChaCha20-Poly1305 tag length`, 'OperationError'); } break; } } function validateNormalizedAlgorithm(converterName, algorithm) { switch (converterName) { case 'AesKeyGenParams': case 'AesDerivedKeyParams': validateAesLength(validateUnsignedInteger(algorithm, 'length')); break; case 'AesCbcParams': validateByteLength(validateBufferSource(algorithm, 'iv'), 'algorithm.iv', 16, 'algorithm.iv must contain exactly 16 bytes'); break; case 'AesCtrParams': { validateByteLength(validateBufferSource(algorithm, 'counter'), 'algorithm.counter', 16); const length = validateUnsignedInteger(algorithm, 'length'); if (length !== undefined && (length === 0 || length > 128)) { throw (0, _errors.lazyDOMException)('AES-CTR algorithm.length must be between 1 and 128', 'OperationError'); } break; } case 'AeadParams': validateAeadParams(algorithm); break; case 'EcdsaParams': case 'HmacKeyGenParams': case 'HmacImportParams': case 'HkdfParams': case 'Pbkdf2Params': case 'RsaHashedKeyGenParams': case 'RsaHashedImportParams': validateHashAlgorithm(algorithm, converterName); if (converterName === 'HmacKeyGenParams') { validateMacLength(algorithm, 'length', 'OperationError'); } if (converterName === 'HkdfParams') { validateBinaryLike(algorithm, 'salt'); validateBinaryLike(algorithm, 'info'); } else if (converterName === 'Pbkdf2Params') { const iterations = validateUnsignedInteger(algorithm, 'iterations'); if (iterations === 0) { throw (0, _errors.lazyDOMException)('iterations cannot be zero', 'OperationError'); } validateBinaryLike(algorithm, 'salt'); } else if (converterName === 'RsaHashedKeyGenParams') { validateUnsignedInteger(algorithm, 'modulusLength'); validateBufferSource(algorithm, 'publicExponent'); } break; case 'RsaPssParams': validateUnsignedInteger(algorithm, 'saltLength'); break; case 'RsaOaepParams': validateBufferSource(algorithm, 'label'); break; case 'ContextParams': validateBufferSource(algorithm, 'context'); break; case 'EcdhKeyDeriveParams': if (!(algorithm.public instanceof _keys.CryptoKey)) { throw (0, _errors.lazyDOMException)('algorithm.public must be a public key', 'InvalidAccessError'); } break; case 'Argon2Params': { validateBufferSource(algorithm, 'nonce'); const parallelism = validateUnsignedInteger(algorithm, 'parallelism'); const memory = validateUnsignedInteger(algorithm, 'memory'); validateUnsignedInteger(algorithm, 'passes'); const version = validateUnsignedInteger(algorithm, 'version'); validateBufferSource(algorithm, 'secretValue'); validateBufferSource(algorithm, 'associatedData'); if (parallelism !== undefined && (parallelism === 0 || parallelism > 2 ** 24 - 1)) { throw (0, _errors.lazyDOMException)('parallelism must be > 0 and < 16777215', 'OperationError'); } if (memory !== undefined && parallelism !== undefined && memory < 8 * parallelism) { throw (0, _errors.lazyDOMException)('memory must be at least 8 times the degree of parallelism', 'OperationError'); } if (version !== undefined && version !== 0x13) { throw (0, _errors.lazyDOMException)(`${version} is not a valid Argon2 version`, 'OperationError'); } break; } case 'KmacKeyGenParams': validateMacLength(algorithm, 'length', 'OperationError'); break; case 'KmacImportParams': validateMacLength(algorithm, 'length', 'DataError'); break; case 'KmacParams': validateMacLength(algorithm, 'outputLength', 'OperationError'); validateBufferSource(algorithm, 'customization'); break; case 'CShakeParams': case 'KangarooTwelveParams': { const outputLength = validateUnsignedInteger(algorithm, 'outputLength'); if (outputLength !== undefined && (outputLength === 0 || outputLength % 8)) { throw (0, _errors.lazyDOMException)(`Invalid ${converterName} outputLength`, 'OperationError'); } validateBufferSource(algorithm, 'functionName'); validateBufferSource(algorithm, 'customization'); break; } case 'TurboShakeParams': { const outputLength = validateUnsignedInteger(algorithm, 'outputLength'); const domainSeparation = validateUnsignedInteger(algorithm, 'domainSeparation'); if (outputLength !== undefined && (outputLength === 0 || outputLength % 8)) { throw (0, _errors.lazyDOMException)('Invalid TurboShakeParams outputLength', 'OperationError'); } if (domainSeparation !== undefined && (domainSeparation < 0x01 || domainSeparation > 0x7f)) { throw (0, _errors.lazyDOMException)('TurboShakeParams.domainSeparation must be in range 0x01-0x7f', 'OperationError'); } break; } } } // WebCrypto §18.4.4 algorithm normalization. Mirrors Node's normalizeAlgorithm // in lib/internal/crypto/util.js: canonicalizes `name`, looks up the // (name, op) → converter mapping, and rejects inputs that omit required // dictionary members. Callers in Subtle.supports rely on this throwing for // invalid params — without it, supports() over-reports capability (#1025). function normalizeAlgorithm(algorithm, operation) { if (typeof algorithm === 'string') { return normalizeAlgorithm({ name: algorithm }, operation); } const name = algorithm.name; if (typeof name !== 'string') { throw new TypeError("Algorithm: 'name' is required"); } const map = getCanonicalAlgorithmNames(); const canonical = map.get(name.toLowerCase()); if (canonical === undefined) { throw (0, _errors.lazyDOMException)('Unrecognized algorithm name', 'NotSupportedError'); } const opMap = kAlgorithmDefinitions[canonical]; if (!opMap || !(operation in opMap)) { throw (0, _errors.lazyDOMException)('Unrecognized algorithm name', 'NotSupportedError'); } const converterName = opMap[operation]; if (converterName == null) { return { name: canonical }; } const fields = kRequiredFields[converterName]; if (!fields) { return { ...algorithm, name: canonical }; } const out = { name: canonical }; const src = algorithm; for (const field of fields) { const value = src[field.key]; if (value === undefined) { if (field.required) { throw new TypeError(`Failed to normalize algorithm: '${field.key}' is required in '${converterName}'`); } continue; } out[field.key] = value; } validateNormalizedAlgorithm(converterName, out); return out; } function getAlgorithmName(name, length) { switch (name) { case 'AES-CBC': return `A${length}CBC`; case 'AES-CTR': return `A${length}CTR`; case 'AES-GCM': return `A${length}GCM`; case 'AES-KW': return `A${length}KW`; case 'AES-OCB': return `A${length}OCB`; case 'ChaCha20-Poly1305': return 'C20P'; default: return `${name}${length}`; } } // Mirrors Node's aliasKeyFormat (lib/internal/crypto/webcrypto.js): for // algorithms whose import/export accepts both 'raw' and the disambiguated // 'raw-secret' / 'raw-public', collapse the latter to 'raw'. Used per-algorithm // — algorithms that demand the disambiguated form (KMAC, AES-OCB, // ChaCha20-Poly1305, Argon2*, ML-DSA, ML-KEM) MUST NOT alias. function aliasKeyFormat(format) { if (format === 'raw-secret' || format === 'raw-public') return 'raw'; return format; } const kUncompressedSpkiLength = { 'P-256': 91, 'P-384': 120, 'P-521': 158 }; function ecExportKey(key, format) { const keyObject = key.keyObject; if (format === KWebCryptoKeyFormat.kWebCryptoKeyFormatRaw) { return (0, _conversion.bufferLikeToArrayBuffer)(keyObject.handle.exportKey()); } else if (format === KWebCryptoKeyFormat.kWebCryptoKeyFormatSPKI) { const exported = (0, _conversion.bufferLikeToArrayBuffer)(keyObject.export({ format: 'der', type: 'spki' })); // WebCrypto requires uncompressed point format for SPKI exports. // If the key was imported in compressed form, re-export as uncompressed // by reconstructing the point from the JWK x,y coordinates and // round-tripping through initECRaw. const namedCurve = key.algorithm.namedCurve; const expected = namedCurve === undefined ? undefined : kUncompressedSpkiLength[namedCurve]; if (expected !== undefined && exported.byteLength !== expected) { const jwk = keyObject.handle.exportJwk({}, false); if (!jwk.x || !jwk.y) { throw (0, _errors.lazyDOMException)('Failed to re-export EC public key as uncompressed SPKI', 'OperationError'); } const x = _reactNativeBuffer.Buffer.from(jwk.x, 'base64url'); const y = _reactNativeBuffer.Buffer.from(jwk.y, 'base64url'); const raw = new Uint8Array(1 + x.length + y.length); raw[0] = 0x04; raw.set(x, 1); raw.set(y, 1 + x.length); const tmp = _reactNativeNitroModules.NitroModules.createHybridObject('KeyObjectHandle'); const curveAlias = _utils.kNamedCurveAliases[namedCurve]; if (!tmp.initECRaw(curveAlias, raw.buffer)) { throw (0, _errors.lazyDOMException)('Failed to re-export EC public key as uncompressed SPKI', 'OperationError'); } return (0, _conversion.bufferLikeToArrayBuffer)(tmp.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.SPKI)); } return exported; } else if (format === KWebCryptoKeyFormat.kWebCryptoKeyFormatPKCS8) { const exported = keyObject.export({ format: 'der', type: 'pkcs8' }); return (0, _conversion.bufferLikeToArrayBuffer)(exported); } else { throw new Error(`Unsupported EC export format: ${format}`); } } function rsaExportKey(key, format) { const keyObject = key.keyObject; if (format === KWebCryptoKeyFormat.kWebCryptoKeyFormatSPKI) { // Export public key in SPKI format const exported = keyObject.export({ format: 'der', type: 'spki' }); return (0, _conversion.bufferLikeToArrayBuffer)(exported); } else if (format === KWebCryptoKeyFormat.kWebCryptoKeyFormatPKCS8) { // Export private key in PKCS8 format const exported = keyObject.export({ format: 'der', type: 'pkcs8' }); return (0, _conversion.bufferLikeToArrayBuffer)(exported); } else { throw new Error(`Unsupported RSA export format: ${format}`); } } async function rsaCipher(mode, key, data, algorithm) { const rsaParams = algorithm; // Validate key type matches operation const expectedType = mode === CipherOrWrapMode.kWebCryptoCipherEncrypt ? 'public' : 'private'; if (key.type !== expectedType) { throw (0, _errors.lazyDOMException)('The requested operation is not valid for the provided key', 'InvalidAccessError'); } // Get hash algorithm from key const hashAlgorithm = (0, _hashnames.normalizeHashName)(key.algorithm.hash); // Prepare label (optional) const label = rsaParams.label ? (0, _conversion.bufferLikeToArrayBuffer)(rsaParams.label) : undefined; // Create RSA cipher instance const rsaCipherModule = _reactNativeNitroModules.NitroModules.createHybridObject('RsaCipher'); // RSA-OAEP padding constant = 4 const RSA_PKCS1_OAEP_PADDING = 4; if (mode === CipherOrWrapMode.kWebCryptoCipherEncrypt) { // Encrypt with public key return rsaCipherModule.encrypt(key.keyObject.handle, data, RSA_PKCS1_OAEP_PADDING, hashAlgorithm, label); } else { // Decrypt with private key return rsaCipherModule.decrypt(key.keyObject.handle, data, RSA_PKCS1_OAEP_PADDING, hashAlgorithm, label); } } async function aesCipher(mode, key, data, algorithm) { const { name } = algorithm; switch (name) { case 'AES-CTR': return aesCtrCipher(mode, key, data, algorithm); case 'AES-CBC': return aesCbcCipher(mode, key, data, algorithm); case 'AES-GCM': return aesGcmCipher(mode, key, data, algorithm); case 'AES-OCB': return aesOcbCipher(mode, key, data, algorithm); default: throw (0, _errors.lazyDOMException)(`Unsupported AES algorithm: ${name}`, 'NotSupportedError'); } } async function aesCtrCipher(mode, key, data, algorithm) { // Validate counter and length if (!algorithm.counter || algorithm.counter.byteLength !== 16) { throw (0, _errors.lazyDOMException)('AES-CTR algorithm.counter must be 16 bytes', 'OperationError'); } if (algorithm.length < 1 || algorithm.length > 128) { throw (0, _errors.lazyDOMException)('AES-CTR algorithm.length must be between 1 and 128', 'OperationError'); } // Get cipher type based on key length const keyLength = key.algorithm.length; const cipherType = `aes-${keyLength}-ctr`; // Create cipher const factory = _reactNativeNitroModules.NitroModules.createHybridObject('CipherFactory'); const cipher = factory.createCipher({ isCipher: mode === CipherOrWrapMode.kWebCryptoCipherEncrypt, cipherType, cipherKey: (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.export()), iv: (0, _conversion.bufferLikeToArrayBuffer)(algorithm.counter) }); // Process data const updated = cipher.update(data); const final = cipher.final(); // Concatenate results const result = new Uint8Array(updated.byteLength + final.byteLength); result.set(new Uint8Array(updated), 0); result.set(new Uint8Array(final), updated.byteLength); return result.buffer; } async function aesCbcCipher(mode, key, data, algorithm) { // Validate IV const iv = (0, _conversion.bufferLikeToArrayBuffer)(algorithm.iv); if (iv.byteLength !== 16) { throw (0, _errors.lazyDOMException)('algorithm.iv must contain exactly 16 bytes', 'OperationError'); } // Get cipher type based on key length const keyLength = key.algorithm.length; const cipherType = `aes-${keyLength}-cbc`; // Create cipher const factory = _reactNativeNitroModules.NitroModules.createHybridObject('CipherFactory'); const cipher = factory.createCipher({ isCipher: mode === CipherOrWrapMode.kWebCryptoCipherEncrypt, cipherType, cipherKey: (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.export()), iv }); // Process data const updated = cipher.update(data); const final = cipher.final(); // Concatenate results const result = new Uint8Array(updated.byteLength + final.byteLength); result.set(new Uint8Array(updated), 0); result.set(new Uint8Array(final), updated.byteLength); return result.buffer; } async function aesAeadCipher(mode, key, data, config, additionalData, tagLength = 128) { if (!config.validTagLengths.includes(tagLength)) { throw (0, _errors.lazyDOMException)(`${tagLength} is not a valid ${config.algorithmName} tag length`, 'OperationError'); } const tagByteLength = tagLength / 8; const keyLength = key.algorithm.length; const cipherType = `aes-${keyLength}-${config.cipherSuffix}`; const factory = _reactNativeNitroModules.NitroModules.createHybridObject('CipherFactory'); const cipher = factory.createCipher({ isCipher: mode === CipherOrWrapMode.kWebCryptoCipherEncrypt, cipherType, cipherKey: (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.export()), iv: config.iv, authTagLen: tagByteLength }); let processData; if (mode === CipherOrWrapMode.kWebCryptoCipherDecrypt) { const dataView = new Uint8Array(data); if (dataView.byteLength < tagByteLength) { throw (0, _errors.lazyDOMException)('The provided data is too small.', 'OperationError'); } const ciphertextLength = dataView.byteLength - tagByteLength; processData = dataView.slice(0, ciphertextLength).buffer; const authTag = dataView.slice(ciphertextLength).buffer; cipher.setAuthTag(authTag); } else { processData = data; } if (additionalData) { cipher.setAAD((0, _conversion.bufferLikeToArrayBuffer)(additionalData)); } const updated = cipher.update(processData); const final = cipher.final(); if (mode === CipherOrWrapMode.kWebCryptoCipherEncrypt) { const tag = cipher.getAuthTag(); const result = new Uint8Array(updated.byteLength + final.byteLength + tag.byteLength); result.set(new Uint8Array(updated), 0); result.set(new Uint8Array(final), updated.byteLength); result.set(new Uint8Array(tag), updated.byteLength + final.byteLength); return result.buffer; } else { const result = new Uint8Array(updated.byteLength + final.byteLength); result.set(new Uint8Array(updated), 0); result.set(new Uint8Array(final), updated.byteLength); return result.buffer; } } async function aesGcmCipher(mode, key, data, algorithm) { return aesAeadCipher(mode, key, data, { algorithmName: 'AES-GCM', validTagLengths: [32, 64, 96, 104, 112, 120, 128], cipherSuffix: 'gcm', iv: (0, _conversion.bufferLikeToArrayBuffer)(algorithm.iv) }, algorithm.additionalData, algorithm.tagLength); } async function aesOcbCipher(mode, key, data, algorithm) { const ivBuffer = (0, _conversion.bufferLikeToArrayBuffer)(algorithm.iv); if (ivBuffer.byteLength < 1 || ivBuffer.byteLength > 15) { throw (0, _errors.lazyDOMException)('AES-OCB algorithm.iv must be between 1 and 15 bytes', 'OperationError'); } return aesAeadCipher(mode, key, data, { algorithmName: 'AES-OCB', validTagLengths: [64, 96, 128], cipherSuffix: 'ocb', iv: ivBuffer }, algorithm.additionalData, algorithm.tagLength); } async function aesKwCipher(mode, key, data) { const isWrap = mode === CipherOrWrapMode.kWebCryptoCipherEncrypt; // AES-KW requires input to be a multiple of 8 bytes (64 bits) if (data.byteLength % 8 !== 0) { throw (0, _errors.lazyDOMException)(`AES-KW input length must be a multiple of 8 bytes, got ${data.byteLength}`, 'OperationError'); } // AES-KW requires at least 16 bytes of input (128 bits) if (isWrap && data.byteLength < 16) { throw (0, _errors.lazyDOMException)(`AES-KW input must be at least 16 bytes, got ${data.byteLength}`, 'OperationError'); } // Get cipher type based on key length const keyLength = key.algorithm.length; // Use aes*-wrap for both operations (matching Node.js) const cipherType = `aes${keyLength}-wrap`; // Export key material const exportedKey = key.keyObject.export(); const cipherKey = (0, _conversion.bufferLikeToArrayBuffer)(exportedKey); // AES-KW uses a default IV as specified in RFC 3394 const defaultWrapIV = new Uint8Array([0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6]); const factory = _reactNativeNitroModules.NitroModules.createHybridObject('CipherFactory'); const cipher = factory.createCipher({ isCipher: isWrap, cipherType, cipherKey, iv: defaultWrapIV.buffer // RFC 3394 default IV for AES-KW }); // Process data const updated = cipher.update(data); const final = cipher.final(); // Concatenate results const result = new Uint8Array(updated.byteLength + final.byteLength); result.set(new Uint8Array(updated), 0); result.set(new Uint8Array(final), updated.byteLength); return result.buffer; } async function chaCha20Poly1305Cipher(mode, key, data, algorithm) { const { iv, additionalData, tagLength = 128 } = algorithm; // Validate IV (must be 12 bytes for ChaCha20-Poly1305) const ivBuffer = (0, _conversion.bufferLikeToArrayBuffer)(iv); if (!ivBuffer || ivBuffer.byteLength !== 12) { throw (0, _errors.lazyDOMException)('ChaCha20-Poly1305 IV must be exactly 12 bytes', 'OperationError'); } // Validate tag length (only 128-bit supported) if (tagLength !== 128) { throw (0, _errors.lazyDOMException)('ChaCha20-Poly1305 only supports 128-bit auth tags', 'NotSupportedError'); } const tagByteLength = 16; // 128 bits = 16 bytes // Create cipher using existing ChaCha20-Poly1305 implementation const factory = _reactNativeNitroModules.NitroModules.createHybridObject('CipherFactory'); const cipher = factory.createCipher({ isCipher: mode === CipherOrWrapMode.kWebCryptoCipherEncrypt, cipherType: 'chacha20-poly1305', cipherKey: (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.export()), iv: ivBuffer, authTagLen: tagByteLength }); let processData; let authTag; if (mode === CipherOrWrapMode.kWebCryptoCipherDecrypt) { // For decryption, extract auth tag from end of data const dataView = new Uint8Array(data); if (dataView.byteLength < tagByteLength) { throw (0, _errors.lazyDOMException)('The provided data is too small.', 'OperationError'); } // Split data and tag const ciphertextLength = dataView.byteLength - tagByteLength; processData = dataView.slice(0, ciphertextLength).buffer; authTag = dataView.slice(ciphertextLength).buffer; // Set auth tag for verification cipher.setAuthTag(authTag); } else { processData = data; } // Set additional authenticated data if provided if (additionalData) { cipher.setAAD((0, _conversion.bufferLikeToArrayBuffer)(additionalData)); } // Process data const updated = cipher.update(processData); const final = cipher.final(); if (mode === CipherOrWrapMode.kWebCryptoCipherEncrypt) { // For encryption, append auth tag to result const tag = cipher.getAuthTag(); const result = new Uint8Array(updated.byteLength + final.byteLength + tag.byteLength); result.set(new Uint8Array(updated), 0); result.set(new Uint8Array(final), updated.byteLength); result.set(new Uint8Array(tag), updated.byteLength + final.byteLength); return result.buffer; } else { // For decryption, just concatenate plaintext const result = new Uint8Array(updated.byteLength + final.byteLength); result.set(new Uint8Array(updated), 0); result.set(new Uint8Array(final), updated.byteLength); return result.buffer; } } async function aesGenerateKey(algorithm, extractable, keyUsages) { const { length } = algorithm; const name = algorithm.name; if (!name) { throw (0, _errors.lazyDOMException)('Algorithm name is required', 'OperationError'); } // Validate key length if (![128, 192, 256].includes(length)) { throw (0, _errors.lazyDOMException)(`Invalid AES key length: ${length}. Must be 128, 192, or 256.`, 'OperationError'); } // Validate usages const validUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']; if (hasAnyNotIn(keyUsages, validUsages)) { throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name}`, 'SyntaxError'); } // Generate random key bytes const keyBytes = new Uint8Array(length / 8); (0, _random.getRandomValues)(keyBytes); // Create secret key const keyObject = (0, _keys.createSecretKey)(keyBytes); // Construct algorithm object with guaranteed name const keyAlgorithm = { name, length }; return new _keys.CryptoKey(keyObject, keyAlgorithm, keyUsages, extractable); } async function hmacGenerateKey(algorithm, extractable, keyUsages) { // Validate usages if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) { throw (0, _errors.lazyDOMException)('Unsupported key usage for HMAC key', 'SyntaxError'); } // Get hash algorithm const hash = algorithm.hash; if (!hash) { throw (0, _errors.lazyDOMException)('HMAC algorithm requires a hash parameter', 'TypeError'); } const hashName = (0, _hashnames.normalizeHashName)(hash); // Determine key length let length = algorithm.length; if (length === undefined) { // Use hash output length as default key length switch (hashName) { case 'SHA-1': length = 160; break; case 'SHA-256': length = 256; break; case 'SHA-384': length = 384; break; case 'SHA-512': length = 512; break; default: length = 256; // Default to 256 bits } } if (length === 0) { throw (0, _errors.lazyDOMException)('Zero-length key is not supported', 'OperationError'); } // Generate random key bytes const keyBytes = new Uint8Array(Math.ceil(length / 8)); (0, _random.getRandomValues)(keyBytes); // Create secret key const keyObject = (0, _keys.createSecretKey)(keyBytes); // Construct algorithm object with hash normalized to { name: string } format per WebCrypto spec const webCryptoHashName = (0, _hashnames.normalizeHashName)(hash, _hashnames.HashContext.WebCrypto); const keyAlgorithm = { name: 'HMAC', hash: { name: webCryptoHashName }, length }; return new _keys.CryptoKey(keyObject, keyAlgorithm, keyUsages, extractable); } async function kmacGenerateKey(algorithm, extractable, keyUsages) { const { name } = algorithm; if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) { throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError'); } const defaultLength = name === 'KMAC128' ? 128 : 256; const length = algorithm.length ?? defaultLength; if (length === 0) { throw (0, _errors.lazyDOMException)('Zero-length key is not supported', 'OperationError'); } const keyBytes = new Uint8Array(Math.ceil(length / 8)); (0, _random.getRandomValues)(keyBytes); const keyObject = (0, _keys.createSecretKey)(keyBytes); const keyAlgorithm = { name: name, length }; return new _keys.CryptoKey(keyObject, keyAlgorithm, keyUsages, extractable); } function kmacSignVerify(key, data, algorithm, signature) { const { name } = algorithm; // KmacParams.outputLength is required per // https://wicg.github.io/webcrypto-modern-algos/#KmacParams-dictionary // and the rename from `length` (commit ab8dc2b84c2). Mirror Node's // mac.js:213-223 by reading `outputLength` (in bits). if (typeof algorithm.outputLength !== 'number') { throw (0, _errors.lazyDOMException)(`${name}Params.outputLength is required`, 'OperationError'); } const outputLengthBits = algorithm.outputLength; if (outputLengthBits % 8 !== 0) { throw (0, _errors.lazyDOMException)(`Unsupported ${name}Params outputLength`, 'NotSupportedError'); } const outputLengthBytes = outputLengthBits / 8; const keyData = key.keyObject.export(); const kmac = _reactNativeNitroModules.NitroModules.createHybridObject('Kmac'); let customizationBuffer; if (algorithm.customization !== undefined) { customizationBuffer = (0, _conversion.bufferLikeToArrayBuffer)(algorithm.customization); } kmac.createKmac(name, (0, _conversion.bufferLikeToArrayBuffer)(keyData), outputLengthBytes, customizationBuffer); kmac.update((0, _conversion.bufferLikeToArrayBuffer)(data)); const computed = kmac.digest(); if (signature === undefined) { return computed; } const sigBuffer = (0, _conversion.bufferLikeToArrayBuffer)(signature); if (computed.byteLength !== sigBuffer.byteLength) { return false; } return (0, _timingSafeEqual.timingSafeEqual)(new Uint8Array(computed), new Uint8Array(sigBuffer)); } async function kmacImportKey(algorithm, format, data, extractable, keyUsages) { const { name } = algorithm; let keyObject; if (format === 'jwk') { const jwk = data; if (!jwk || typeof jwk !== 'object') { throw (0, _errors.lazyDOMException)('Invalid keyData', 'DataError'); } if (jwk.kty !== 'oct') { throw (0, _errors.lazyDOMException)('Invalid JWK "kty" Parameter', 'DataError'); } (0, _validation.validateJwkStructure)(jwk, extractable, keyUsages, 'sig'); const expectedAlg = name === 'KMAC128' ? 'K128' : 'K256'; if (jwk.alg !== undefined && jwk.alg !== expectedAlg) { throw (0, _errors.lazyDOMException)('JWK "alg" Parameter and algorithm name mismatch', 'DataError'); } if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) { throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError'); } const handle = _reactNativeNitroModules.NitroModules.createHybridObject('KeyObjectHandle'); let keyType; try { keyType = handle.initJwk(jwk, undefined); } catch (err) { throw (0, _errors.lazyDOMException)('Invalid keyData', { name: 'DataError', cause: err }); } if (keyType === undefined || keyType !== 0) { throw (0, _errors.lazyDOMException)('Invalid keyData', 'DataError'); } keyObject = new _keys.SecretKeyObject(handle); } else if (format === 'raw-secret') { // KMAC accepts only the disambiguated 'raw-secret' form (Node mac.js:141-145 // returns undefined for plain 'raw' when not HMAC). if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) { throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError'); } keyObject = (0, _keys.createSecretKey)(data); } else { throw (0, _errors.lazyDOMException)(`Unable to import ${name} key with format ${format}`, 'NotSupportedError'); } const exported = keyObject.export(); const keyLength = exported.byteLength * 8; if (keyLength === 0) { throw (0, _errors.lazyDOMException)('Zero-length key is not supported', 'DataError'); } if (algorithm.length !== undefined && algorithm.length !== keyLength) { throw (0, _errors.lazyDOMException)('Invalid key length', 'DataError'); } const keyAlgorithm = { name: name, length: keyLength }; return new _keys.CryptoKey(keyObject, keyAlgorithm, keyUsages, extractable); } function rsaImportKey(format, data, algorithm, extractable, keyUsages) { const { name } = algorithm; let checkSet; switch (name) { case 'RSASSA-PKCS1-v1_5': case 'RSA-PSS': checkSet = ['sign', 'verify']; break; case 'RSA-OAEP': checkSet = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']; break; default: throw new Error(`Unsupported RSA algorithm: ${name}`); } const checkUsages = () => { if (hasAnyNotIn(keyUsages, checkSet)) { throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError'); } }; let keyObject; if (format === 'jwk') { const jwk = data; if (!jwk || typeof jwk !== 'object') { throw (0, _errors.lazyDOMException)('Invalid keyData', 'DataError'); } if (jwk.kty !== 'RSA') { throw (0, _errors.lazyDOMException)('Invalid JWK "kty" Parameter', 'DataError'); } const expectedUse = name === 'RSA-OAEP' ? 'enc' : 'sig'; (0, _validation.validateJwkStructure)(jwk, extractable, keyUsages, expectedUse); checkUsages(); if (jwk.alg !== undefined) { let jwkContext; switch (name) { case 'RSASSA-PKCS1-v1_5': jwkContext = _hashnames.HashContext.JwkRsa; break; case 'RSA-PSS': jwkContext = _hashnames.HashContext.JwkRsaPss; break; default: jwkContext = _hashnames.HashContext.JwkRsaOaep; } const expectedAlg = (0, _hashnames.normalizeHashName)(algorithm.hash, jwkContext); if (jwk.alg !== expectedAlg) { throw (0, _errors.lazyDOMException)('JWK "alg" does not match the requested algorithm', 'DataError'); } } const handle = _reactNativeNitroModules.NitroModules.createHybridObject('KeyObjectHandle'); let keyType; try { keyType = handle.initJwk(jwk, undefined); } catch (err) { throw (0, _errors.lazyDOMException)('Invalid keyData', { name: 'DataError', cause: err }); } if (keyType === undefined) { throw (0, _errors.lazyDOMException)('Invalid keyData', 'DataError'); } if (keyType === _utils.KeyType.PUBLIC) { keyObject = new _keys.PublicKeyObject(handle); } else if (keyType === _utils.KeyType.PRIVATE) { keyObject = new _keys.PrivateKeyObject(handle); } else { throw (0, _errors.lazyDOMException)('Invalid keyData', 'DataError'); } } else if (format === 'spki') { checkUsages(); const keyData = (0, _conversion.bufferLikeToArrayBuffer)(data); keyObject = _keys.KeyObject.createKeyObject('public', keyData, _utils.KFormatType.DER, _utils.KeyEncoding.SPKI); } else if (format === 'pkcs8') { checkUsages(); const keyData = (0, _conversion.bufferLikeToArrayBuffer)(data); keyObject = _keys.KeyObject.createKeyObject('private', keyData, _utils.KFormatType.DER, _utils.KeyEncoding.PKCS8); } else { throw (0, _errors.lazyDOMException)(`Unsupported format for ${name} import: ${format}`, 'NotSupportedError'); } // Get the modulus length from the key and add it to the algorithm const keyDetails = keyObject.asymmetricKeyDetails; // Convert publicExponent number to big-endian byte array let publicExponentBytes; if (keyDetails?.publicExponent) { const exp = keyDetails.publicExponent; // Convert number to big-endian bytes const bytes = []; let value = exp; while (value > 0) { bytes.unshift(value & 0xff); value = Math.floor(value / 256); } publicExponentBytes = new Uint8Array(bytes.length > 0 ? bytes : [0]); } // Normalize hash to { name: string } format per WebCrypto spec const hashName = (0, _hashnames.normalizeHashName)(algorithm.hash, _hashnames.HashContext.WebCrypto); const normalizedHash = { name: hashName }; const algorithmWithDetails = { ...algorithm, modulusLength: keyDetails?.modulusLength, publicExponent: publicExponentBytes, hash: normalizedHash }; return new _keys.CryptoKey(keyObject, algorithmWithDetails, keyUsages, extractable); } async function hmacImportKey(algorithm, format, data, extractable, keyUsages) { const checkUsages = () => { if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) { throw new Error('Unsupported key usage for an HMAC key'); } }; let keyObject; if (format === 'jwk') { const jwk = data; if (!jwk || typeof jwk !== 'object') { throw new Error('Invalid keyData'); } if (jwk.kty !== 'oct') { throw new Error('Invalid JWK format for HMAC key'); } (0, _validation.validateJwkStructure)(jwk, extractable, keyUsages, 'sig'); checkUsag