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
JavaScript
"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