react-native-quick-crypto 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/QuickCrypto.podspec +6 -47
  2. package/README.md +1 -1
  3. package/android/CMakeLists.txt +4 -0
  4. package/cpp/cipher/HybridCipher.cpp +17 -1
  5. package/cpp/ed25519/HybridEdKeyPair.cpp +8 -2
  6. package/cpp/hkdf/HybridHkdf.cpp +96 -0
  7. package/cpp/hkdf/HybridHkdf.hpp +28 -0
  8. package/cpp/scrypt/HybridScrypt.cpp +62 -0
  9. package/cpp/scrypt/HybridScrypt.hpp +28 -0
  10. package/lib/commonjs/ed.js +68 -0
  11. package/lib/commonjs/ed.js.map +1 -1
  12. package/lib/commonjs/hkdf.js +81 -0
  13. package/lib/commonjs/hkdf.js.map +1 -0
  14. package/lib/commonjs/index.js +33 -1
  15. package/lib/commonjs/index.js.map +1 -1
  16. package/lib/commonjs/scrypt.js +98 -0
  17. package/lib/commonjs/scrypt.js.map +1 -0
  18. package/lib/commonjs/specs/hkdf.nitro.js +6 -0
  19. package/lib/commonjs/specs/hkdf.nitro.js.map +1 -0
  20. package/lib/commonjs/specs/scrypt.nitro.js +6 -0
  21. package/lib/commonjs/specs/scrypt.nitro.js.map +1 -0
  22. package/lib/commonjs/subtle.js +400 -7
  23. package/lib/commonjs/subtle.js.map +1 -1
  24. package/lib/commonjs/utils/types.js.map +1 -1
  25. package/lib/module/ed.js +66 -0
  26. package/lib/module/ed.js.map +1 -1
  27. package/lib/module/hkdf.js +75 -0
  28. package/lib/module/hkdf.js.map +1 -0
  29. package/lib/module/index.js +13 -1
  30. package/lib/module/index.js.map +1 -1
  31. package/lib/module/scrypt.js +93 -0
  32. package/lib/module/scrypt.js.map +1 -0
  33. package/lib/module/specs/hkdf.nitro.js +4 -0
  34. package/lib/module/specs/hkdf.nitro.js.map +1 -0
  35. package/lib/module/specs/scrypt.nitro.js +4 -0
  36. package/lib/module/specs/scrypt.nitro.js.map +1 -0
  37. package/lib/module/subtle.js +401 -8
  38. package/lib/module/subtle.js.map +1 -1
  39. package/lib/module/utils/types.js.map +1 -1
  40. package/lib/tsconfig.tsbuildinfo +1 -1
  41. package/lib/typescript/ed.d.ts +4 -1
  42. package/lib/typescript/ed.d.ts.map +1 -1
  43. package/lib/typescript/hkdf.d.ts +26 -0
  44. package/lib/typescript/hkdf.d.ts.map +1 -0
  45. package/lib/typescript/index.d.ts +11 -0
  46. package/lib/typescript/index.d.ts.map +1 -1
  47. package/lib/typescript/scrypt.d.ts +18 -0
  48. package/lib/typescript/scrypt.d.ts.map +1 -0
  49. package/lib/typescript/specs/hkdf.nitro.d.ts +9 -0
  50. package/lib/typescript/specs/hkdf.nitro.d.ts.map +1 -0
  51. package/lib/typescript/specs/scrypt.nitro.d.ts +9 -0
  52. package/lib/typescript/specs/scrypt.nitro.d.ts.map +1 -0
  53. package/lib/typescript/subtle.d.ts +4 -1
  54. package/lib/typescript/subtle.d.ts.map +1 -1
  55. package/lib/typescript/utils/types.d.ts +9 -3
  56. package/lib/typescript/utils/types.d.ts.map +1 -1
  57. package/nitrogen/generated/android/QuickCrypto+autolinking.cmake +2 -0
  58. package/nitrogen/generated/android/QuickCryptoOnLoad.cpp +20 -0
  59. package/nitrogen/generated/ios/QuickCryptoAutolinking.mm +20 -0
  60. package/nitrogen/generated/shared/c++/HybridHkdfSpec.cpp +22 -0
  61. package/nitrogen/generated/shared/c++/HybridHkdfSpec.hpp +66 -0
  62. package/nitrogen/generated/shared/c++/HybridScryptSpec.cpp +22 -0
  63. package/nitrogen/generated/shared/c++/HybridScryptSpec.hpp +65 -0
  64. package/package.json +1 -1
  65. package/src/ed.ts +102 -0
  66. package/src/hkdf.ts +152 -0
  67. package/src/index.ts +13 -1
  68. package/src/scrypt.ts +134 -0
  69. package/src/specs/hkdf.nitro.ts +19 -0
  70. package/src/specs/scrypt.nitro.ts +23 -0
  71. package/src/subtle.ts +564 -9
  72. package/src/utils/types.ts +16 -3
@@ -23,6 +23,7 @@ var _hmac = require("./hmac");
23
23
  var _signVerify = require("./keys/signVerify");
24
24
  var _ed = require("./ed");
25
25
  var _mldsa = require("./mldsa");
26
+ var _hkdf = require("./hkdf");
26
27
  /* eslint-disable @typescript-eslint/no-unused-vars */
27
28
  // import { pbkdf2DeriveBits } from './pbkdf2';
28
29
  // import { aesCipher, aesGenerateKey, aesImportKey, getAlgorithmName } from './aes';
@@ -271,6 +272,120 @@ async function aesGcmCipher(mode, key, data, algorithm) {
271
272
  return result.buffer;
272
273
  }
273
274
  }
275
+ async function aesKwCipher(mode, key, data) {
276
+ const isWrap = mode === CipherOrWrapMode.kWebCryptoCipherEncrypt;
277
+
278
+ // AES-KW requires input to be a multiple of 8 bytes (64 bits)
279
+ if (data.byteLength % 8 !== 0) {
280
+ throw (0, _errors.lazyDOMException)(`AES-KW input length must be a multiple of 8 bytes, got ${data.byteLength}`, 'OperationError');
281
+ }
282
+
283
+ // AES-KW requires at least 16 bytes of input (128 bits)
284
+ if (isWrap && data.byteLength < 16) {
285
+ throw (0, _errors.lazyDOMException)(`AES-KW input must be at least 16 bytes, got ${data.byteLength}`, 'OperationError');
286
+ }
287
+
288
+ // Get cipher type based on key length
289
+ const keyLength = key.algorithm.length;
290
+ // Use aes*-wrap for both operations (matching Node.js)
291
+ const cipherType = `aes${keyLength}-wrap`;
292
+
293
+ // Export key material
294
+ const exportedKey = key.keyObject.export();
295
+ const cipherKey = (0, _conversion.bufferLikeToArrayBuffer)(exportedKey);
296
+
297
+ // AES-KW uses a default IV as specified in RFC 3394
298
+ const defaultWrapIV = new Uint8Array([0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6]);
299
+ const factory = _reactNativeNitroModules.NitroModules.createHybridObject('CipherFactory');
300
+ const cipher = factory.createCipher({
301
+ isCipher: isWrap,
302
+ cipherType,
303
+ cipherKey,
304
+ iv: defaultWrapIV.buffer // RFC 3394 default IV for AES-KW
305
+ });
306
+
307
+ // Process data
308
+ const updated = cipher.update(data);
309
+ const final = cipher.final();
310
+
311
+ // Concatenate results
312
+ const result = new Uint8Array(updated.byteLength + final.byteLength);
313
+ result.set(new Uint8Array(updated), 0);
314
+ result.set(new Uint8Array(final), updated.byteLength);
315
+ return result.buffer;
316
+ }
317
+ async function chaCha20Poly1305Cipher(mode, key, data, algorithm) {
318
+ const {
319
+ iv,
320
+ additionalData,
321
+ tagLength = 128
322
+ } = algorithm;
323
+
324
+ // Validate IV (must be 12 bytes for ChaCha20-Poly1305)
325
+ const ivBuffer = (0, _conversion.bufferLikeToArrayBuffer)(iv);
326
+ if (!ivBuffer || ivBuffer.byteLength !== 12) {
327
+ throw (0, _errors.lazyDOMException)('ChaCha20-Poly1305 IV must be exactly 12 bytes', 'OperationError');
328
+ }
329
+
330
+ // Validate tag length (only 128-bit supported)
331
+ if (tagLength !== 128) {
332
+ throw (0, _errors.lazyDOMException)('ChaCha20-Poly1305 only supports 128-bit auth tags', 'NotSupportedError');
333
+ }
334
+ const tagByteLength = 16; // 128 bits = 16 bytes
335
+
336
+ // Create cipher using existing ChaCha20-Poly1305 implementation
337
+ const factory = _reactNativeNitroModules.NitroModules.createHybridObject('CipherFactory');
338
+ const cipher = factory.createCipher({
339
+ isCipher: mode === CipherOrWrapMode.kWebCryptoCipherEncrypt,
340
+ cipherType: 'chacha20-poly1305',
341
+ cipherKey: (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.export()),
342
+ iv: ivBuffer,
343
+ authTagLen: tagByteLength
344
+ });
345
+ let processData;
346
+ let authTag;
347
+ if (mode === CipherOrWrapMode.kWebCryptoCipherDecrypt) {
348
+ // For decryption, extract auth tag from end of data
349
+ const dataView = new Uint8Array(data);
350
+ if (dataView.byteLength < tagByteLength) {
351
+ throw (0, _errors.lazyDOMException)('The provided data is too small.', 'OperationError');
352
+ }
353
+
354
+ // Split data and tag
355
+ const ciphertextLength = dataView.byteLength - tagByteLength;
356
+ processData = dataView.slice(0, ciphertextLength).buffer;
357
+ authTag = dataView.slice(ciphertextLength).buffer;
358
+
359
+ // Set auth tag for verification
360
+ cipher.setAuthTag(authTag);
361
+ } else {
362
+ processData = data;
363
+ }
364
+
365
+ // Set additional authenticated data if provided
366
+ if (additionalData) {
367
+ cipher.setAAD((0, _conversion.bufferLikeToArrayBuffer)(additionalData));
368
+ }
369
+
370
+ // Process data
371
+ const updated = cipher.update(processData);
372
+ const final = cipher.final();
373
+ if (mode === CipherOrWrapMode.kWebCryptoCipherEncrypt) {
374
+ // For encryption, append auth tag to result
375
+ const tag = cipher.getAuthTag();
376
+ const result = new Uint8Array(updated.byteLength + final.byteLength + tag.byteLength);
377
+ result.set(new Uint8Array(updated), 0);
378
+ result.set(new Uint8Array(final), updated.byteLength);
379
+ result.set(new Uint8Array(tag), updated.byteLength + final.byteLength);
380
+ return result.buffer;
381
+ } else {
382
+ // For decryption, just concatenate plaintext
383
+ const result = new Uint8Array(updated.byteLength + final.byteLength);
384
+ result.set(new Uint8Array(updated), 0);
385
+ result.set(new Uint8Array(final), updated.byteLength);
386
+ return result.buffer;
387
+ }
388
+ }
274
389
  async function aesGenerateKey(algorithm, extractable, keyUsages) {
275
390
  const {
276
391
  length
@@ -558,7 +673,9 @@ function edImportKey(format, data, algorithm, extractable, keyUsages) {
558
673
  } = algorithm;
559
674
 
560
675
  // Validate usages
561
- if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
676
+ const isX = name === 'X25519' || name === 'X448';
677
+ const allowedUsages = isX ? ['deriveKey', 'deriveBits'] : ['sign', 'verify'];
678
+ if (hasAnyNotIn(keyUsages, allowedUsages)) {
562
679
  throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError');
563
680
  }
564
681
  let keyObject;
@@ -631,8 +748,12 @@ const exportKeySpki = async key => {
631
748
  case 'Ed25519':
632
749
  // Fall through
633
750
  case 'Ed448':
751
+ // Fall through
752
+ case 'X25519':
753
+ // Fall through
754
+ case 'X448':
634
755
  if (key.type === 'public') {
635
- // Export Ed key in SPKI DER format
756
+ // Export Ed/X key in SPKI DER format
636
757
  return (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.SPKI));
637
758
  }
638
759
  break;
@@ -670,8 +791,12 @@ const exportKeyPkcs8 = async key => {
670
791
  case 'Ed25519':
671
792
  // Fall through
672
793
  case 'Ed448':
794
+ // Fall through
795
+ case 'X25519':
796
+ // Fall through
797
+ case 'X448':
673
798
  if (key.type === 'private') {
674
- // Export Ed key in PKCS8 DER format
799
+ // Export Ed/X key in PKCS8 DER format
675
800
  return (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.PKCS8));
676
801
  }
677
802
  break;
@@ -697,6 +822,19 @@ const exportKeyRaw = key => {
697
822
  return ecExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatRaw);
698
823
  }
699
824
  break;
825
+ case 'Ed25519':
826
+ // Fall through
827
+ case 'Ed448':
828
+ // Fall through
829
+ case 'X25519':
830
+ // Fall through
831
+ case 'X448':
832
+ if (key.type === 'public') {
833
+ // Export raw public key
834
+ const exported = key.keyObject.handle.exportKey();
835
+ return (0, _conversion.bufferLikeToArrayBuffer)(exported);
836
+ }
837
+ break;
700
838
  case 'AES-CTR':
701
839
  // Fall through
702
840
  case 'AES-CBC':
@@ -705,6 +843,8 @@ const exportKeyRaw = key => {
705
843
  // Fall through
706
844
  case 'AES-KW':
707
845
  // Fall through
846
+ case 'ChaCha20-Poly1305':
847
+ // Fall through
708
848
  case 'HMAC':
709
849
  {
710
850
  const exported = key.keyObject.export();
@@ -744,6 +884,8 @@ const exportKeyJWK = key => {
744
884
  case 'AES-GCM':
745
885
  // Fall through
746
886
  case 'AES-KW':
887
+ // Fall through
888
+ case 'ChaCha20-Poly1305':
747
889
  if (key.algorithm.length === undefined) {
748
890
  throw (0, _errors.lazyDOMException)(`Algorithm ${key.algorithm.name} missing required length property`, 'InvalidAccessError');
749
891
  }
@@ -782,6 +924,25 @@ const importGenericSecretKey = async ({
782
924
  }
783
925
  throw new Error(`Unable to import ${name} key with format ${format}`);
784
926
  };
927
+ const hkdfImportKey = async (format, keyData, algorithm, extractable, keyUsages) => {
928
+ const {
929
+ name
930
+ } = algorithm;
931
+ if (hasAnyNotIn(keyUsages, ['deriveKey', 'deriveBits'])) {
932
+ throw new Error(`Unsupported key usage for a ${name} key`);
933
+ }
934
+ switch (format) {
935
+ case 'raw':
936
+ {
937
+ const keyObject = (0, _keys.createSecretKey)(keyData);
938
+ return new _keys.CryptoKey(keyObject, {
939
+ name
940
+ }, keyUsages, extractable);
941
+ }
942
+ default:
943
+ throw new Error(`Unable to import ${name} key with format ${format}`);
944
+ }
945
+ };
785
946
  const checkCryptoKeyPairUsages = pair => {
786
947
  if (pair.privateKey && pair.privateKey instanceof _keys.CryptoKey && pair.privateKey.keyUsages && pair.privateKey.keyUsages.length > 0) {
787
948
  return;
@@ -941,6 +1102,10 @@ const cipherOrWrap = async (mode, algorithm, key, data, op) => {
941
1102
  // Fall through
942
1103
  case 'AES-GCM':
943
1104
  return aesCipher(mode, key, data, algorithm);
1105
+ case 'AES-KW':
1106
+ return aesKwCipher(mode, key, data);
1107
+ case 'ChaCha20-Poly1305':
1108
+ return chaCha20Poly1305Cipher(mode, key, data, algorithm);
944
1109
  }
945
1110
  };
946
1111
  class Subtle {
@@ -953,16 +1118,54 @@ class Subtle {
953
1118
  return (0, _hash.asyncDigest)(normalizedAlgorithm, data);
954
1119
  }
955
1120
  async deriveBits(algorithm, baseKey, length) {
956
- if (!baseKey.keyUsages.includes('deriveBits')) {
957
- throw new Error('baseKey does not have deriveBits usage');
1121
+ // Allow either deriveBits OR deriveKey usage (WebCrypto spec allows both)
1122
+ if (!baseKey.keyUsages.includes('deriveBits') && !baseKey.keyUsages.includes('deriveKey')) {
1123
+ throw new Error('baseKey does not have deriveBits or deriveKey usage');
958
1124
  }
959
1125
  if (baseKey.algorithm.name !== algorithm.name) throw new Error('Key algorithm mismatch');
960
1126
  switch (algorithm.name) {
961
1127
  case 'PBKDF2':
962
1128
  return (0, _pbkdf.pbkdf2DeriveBits)(algorithm, baseKey, length);
1129
+ case 'X25519':
1130
+ // Fall through
1131
+ case 'X448':
1132
+ return (0, _ed.xDeriveBits)(algorithm, baseKey, length);
1133
+ case 'HKDF':
1134
+ return (0, _hkdf.hkdfDeriveBits)(algorithm, baseKey, length);
963
1135
  }
964
1136
  throw new Error(`'subtle.deriveBits()' for ${algorithm.name} is not implemented.`);
965
1137
  }
1138
+ async deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages) {
1139
+ // Validate baseKey usage
1140
+ if (!baseKey.usages.includes('deriveKey') && !baseKey.usages.includes('deriveBits')) {
1141
+ throw (0, _errors.lazyDOMException)('baseKey does not have deriveKey or deriveBits usage', 'InvalidAccessError');
1142
+ }
1143
+
1144
+ // Calculate required key length
1145
+ const length = getKeyLength(derivedKeyAlgorithm);
1146
+
1147
+ // Step 1: Derive bits
1148
+ let derivedBits;
1149
+ if (baseKey.algorithm.name !== algorithm.name) throw new Error('Key algorithm mismatch');
1150
+ switch (algorithm.name) {
1151
+ case 'PBKDF2':
1152
+ derivedBits = await (0, _pbkdf.pbkdf2DeriveBits)(algorithm, baseKey, length);
1153
+ break;
1154
+ case 'X25519':
1155
+ // Fall through
1156
+ case 'X448':
1157
+ derivedBits = await (0, _ed.xDeriveBits)(algorithm, baseKey, length);
1158
+ break;
1159
+ case 'HKDF':
1160
+ derivedBits = (0, _hkdf.hkdfDeriveBits)(algorithm, baseKey, length);
1161
+ break;
1162
+ default:
1163
+ throw new Error(`'subtle.deriveKey()' for ${algorithm.name} is not implemented.`);
1164
+ }
1165
+
1166
+ // Step 2: Import as key
1167
+ return this.importKey('raw', derivedBits, derivedKeyAlgorithm, extractable, keyUsages);
1168
+ }
966
1169
  async encrypt(algorithm, key, data) {
967
1170
  const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'encrypt');
968
1171
  return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherEncrypt, normalizedAlgorithm, key, (0, _conversion.bufferLikeToArrayBuffer)(data), 'encrypt');
@@ -980,6 +1183,76 @@ class Subtle {
980
1183
  return exportKeyRaw(key);
981
1184
  }
982
1185
  }
1186
+ async wrapKey(format, key, wrappingKey, wrapAlgorithm) {
1187
+ // Validate wrappingKey usage
1188
+ if (!wrappingKey.usages.includes('wrapKey')) {
1189
+ throw (0, _errors.lazyDOMException)('wrappingKey does not have wrapKey usage', 'InvalidAccessError');
1190
+ }
1191
+
1192
+ // Step 1: Export the key
1193
+ const exported = await this.exportKey(format, key);
1194
+
1195
+ // Step 2: Convert to ArrayBuffer if JWK
1196
+ let keyData;
1197
+ if (format === 'jwk') {
1198
+ const jwkString = JSON.stringify(exported);
1199
+ const buffer = _safeBuffer.Buffer.from(jwkString, 'utf8');
1200
+
1201
+ // For AES-KW, pad to multiple of 8 bytes (accounting for null terminator)
1202
+ if (wrapAlgorithm.name === 'AES-KW') {
1203
+ const length = buffer.length;
1204
+ // Add 1 for null terminator, then pad to multiple of 8
1205
+ const paddedLength = Math.ceil((length + 1) / 8) * 8;
1206
+ const paddedBuffer = _safeBuffer.Buffer.alloc(paddedLength);
1207
+ buffer.copy(paddedBuffer);
1208
+ // Null terminator for JSON string (remaining bytes are already zeros from alloc)
1209
+ paddedBuffer.writeUInt8(0, length);
1210
+ keyData = (0, _conversion.bufferLikeToArrayBuffer)(paddedBuffer);
1211
+ } else {
1212
+ keyData = (0, _conversion.bufferLikeToArrayBuffer)(buffer);
1213
+ }
1214
+ } else {
1215
+ keyData = exported;
1216
+ }
1217
+
1218
+ // Step 3: Encrypt the exported key
1219
+ return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherEncrypt, wrapAlgorithm, wrappingKey, keyData, 'wrapKey');
1220
+ }
1221
+ async unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages) {
1222
+ // Validate unwrappingKey usage
1223
+ if (!unwrappingKey.usages.includes('unwrapKey')) {
1224
+ throw (0, _errors.lazyDOMException)('unwrappingKey does not have unwrapKey usage', 'InvalidAccessError');
1225
+ }
1226
+
1227
+ // Step 1: Decrypt the wrapped key
1228
+ const decrypted = await cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherDecrypt, unwrapAlgorithm, unwrappingKey, (0, _conversion.bufferLikeToArrayBuffer)(wrappedKey), 'unwrapKey');
1229
+
1230
+ // Step 2: Convert to appropriate format
1231
+ let keyData;
1232
+ if (format === 'jwk') {
1233
+ const buffer = _safeBuffer.Buffer.from(decrypted);
1234
+ // For AES-KW, the data may be padded - find the null terminator
1235
+ let jwkString;
1236
+ if (unwrapAlgorithm.name === 'AES-KW') {
1237
+ // Find the null terminator (if present) to get the original string
1238
+ const nullIndex = buffer.indexOf(0);
1239
+ if (nullIndex !== -1) {
1240
+ jwkString = buffer.toString('utf8', 0, nullIndex);
1241
+ } else {
1242
+ // No null terminator, try to parse the whole buffer
1243
+ jwkString = buffer.toString('utf8').trim();
1244
+ }
1245
+ } else {
1246
+ jwkString = buffer.toString('utf8');
1247
+ }
1248
+ keyData = JSON.parse(jwkString);
1249
+ } else {
1250
+ keyData = decrypted;
1251
+ }
1252
+
1253
+ // Step 3: Import the key
1254
+ return this.importKey(format, keyData, unwrappedKeyAlgorithm, extractable, keyUsages);
1255
+ }
983
1256
  async generateKey(algorithm, extractable, keyUsages) {
984
1257
  algorithm = normalizeAlgorithm(algorithm, 'generateKey');
985
1258
  let result;
@@ -1006,6 +1279,18 @@ class Subtle {
1006
1279
  case 'AES-KW':
1007
1280
  result = await aesGenerateKey(algorithm, extractable, keyUsages);
1008
1281
  break;
1282
+ case 'ChaCha20-Poly1305':
1283
+ {
1284
+ const length = algorithm.length ?? 256;
1285
+ if (length !== 256) {
1286
+ throw (0, _errors.lazyDOMException)('ChaCha20-Poly1305 only supports 256-bit keys', 'NotSupportedError');
1287
+ }
1288
+ result = await aesGenerateKey({
1289
+ name: 'ChaCha20-Poly1305',
1290
+ length: 256
1291
+ }, extractable, keyUsages);
1292
+ break;
1293
+ }
1009
1294
  case 'HMAC':
1010
1295
  result = await hmacGenerateKey(algorithm, extractable, keyUsages);
1011
1296
  break;
@@ -1023,6 +1308,12 @@ class Subtle {
1023
1308
  result = await (0, _mldsa.mldsa_generateKeyPairWebCrypto)(algorithm.name, extractable, keyUsages);
1024
1309
  checkCryptoKeyPairUsages(result);
1025
1310
  break;
1311
+ case 'X25519':
1312
+ // Fall through
1313
+ case 'X448':
1314
+ result = await (0, _ed.x_generateKeyPairWebCrypto)(algorithm.name.toLowerCase(), extractable, keyUsages);
1315
+ checkCryptoKeyPairUsages(result);
1316
+ break;
1026
1317
  default:
1027
1318
  throw new Error(`'subtle.generateKey()' is not implemented for ${algorithm.name}.
1028
1319
  Unrecognized algorithm name`);
@@ -1055,11 +1346,20 @@ class Subtle {
1055
1346
  case 'AES-GCM':
1056
1347
  // Fall through
1057
1348
  case 'AES-KW':
1349
+ // Fall through
1350
+ case 'ChaCha20-Poly1305':
1058
1351
  result = await aesImportKey(normalizedAlgorithm, format, data, extractable, keyUsages);
1059
1352
  break;
1060
1353
  case 'PBKDF2':
1061
1354
  result = await importGenericSecretKey(normalizedAlgorithm, format, data, extractable, keyUsages);
1062
1355
  break;
1356
+ case 'HKDF':
1357
+ result = await hkdfImportKey(format, data, normalizedAlgorithm, extractable, keyUsages);
1358
+ break;
1359
+ case 'X25519':
1360
+ // Fall through
1361
+ case 'X448':
1362
+ // Fall through
1063
1363
  case 'Ed25519':
1064
1364
  // Fall through
1065
1365
  case 'Ed448':
@@ -1081,12 +1381,105 @@ class Subtle {
1081
1381
  return result;
1082
1382
  }
1083
1383
  async sign(algorithm, key, data) {
1084
- return signVerify(algorithm, key, data);
1384
+ const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'sign');
1385
+ if (normalizedAlgorithm.name === 'HMAC') {
1386
+ // Validate key usage
1387
+ if (!key.usages.includes('sign')) {
1388
+ throw (0, _errors.lazyDOMException)('Key does not have sign usage', 'InvalidAccessError');
1389
+ }
1390
+
1391
+ // Get hash algorithm from key or algorithm params
1392
+ // Hash can be either a string or an object with name property
1393
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1394
+ const alg = normalizedAlgorithm;
1395
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1396
+ const keyAlg = key.algorithm;
1397
+ let hashAlgorithm = 'SHA-256';
1398
+ if (typeof alg.hash === 'string') {
1399
+ hashAlgorithm = alg.hash;
1400
+ } else if (alg.hash?.name) {
1401
+ hashAlgorithm = alg.hash.name;
1402
+ } else if (typeof keyAlg.hash === 'string') {
1403
+ hashAlgorithm = keyAlg.hash;
1404
+ } else if (keyAlg.hash?.name) {
1405
+ hashAlgorithm = keyAlg.hash.name;
1406
+ }
1407
+
1408
+ // Create HMAC and sign
1409
+ const keyData = key.keyObject.export();
1410
+ const hmac = (0, _hmac.createHmac)(hashAlgorithm, keyData);
1411
+ hmac.update((0, _conversion.bufferLikeToArrayBuffer)(data));
1412
+ return (0, _conversion.bufferLikeToArrayBuffer)(hmac.digest());
1413
+ }
1414
+ return signVerify(normalizedAlgorithm, key, data);
1085
1415
  }
1086
1416
  async verify(algorithm, key, signature, data) {
1087
- return signVerify(algorithm, key, data, signature);
1417
+ const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'verify');
1418
+ if (normalizedAlgorithm.name === 'HMAC') {
1419
+ // Validate key usage
1420
+ if (!key.usages.includes('verify')) {
1421
+ throw (0, _errors.lazyDOMException)('Key does not have verify usage', 'InvalidAccessError');
1422
+ }
1423
+
1424
+ // Get hash algorithm
1425
+ // Hash can be either a string or an object with name property
1426
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1427
+ const alg = normalizedAlgorithm;
1428
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1429
+ const keyAlg = key.algorithm;
1430
+ let hashAlgorithm = 'SHA-256';
1431
+ if (typeof alg.hash === 'string') {
1432
+ hashAlgorithm = alg.hash;
1433
+ } else if (alg.hash?.name) {
1434
+ hashAlgorithm = alg.hash.name;
1435
+ } else if (typeof keyAlg.hash === 'string') {
1436
+ hashAlgorithm = keyAlg.hash;
1437
+ } else if (keyAlg.hash?.name) {
1438
+ hashAlgorithm = keyAlg.hash.name;
1439
+ }
1440
+
1441
+ // Create HMAC and compute expected signature
1442
+ const keyData = key.keyObject.export();
1443
+ const hmac = (0, _hmac.createHmac)(hashAlgorithm, keyData);
1444
+ const dataBuffer = (0, _conversion.bufferLikeToArrayBuffer)(data);
1445
+ hmac.update(dataBuffer);
1446
+ const expectedDigest = hmac.digest();
1447
+ const expected = new Uint8Array((0, _conversion.bufferLikeToArrayBuffer)(expectedDigest));
1448
+
1449
+ // Constant-time comparison
1450
+ const signatureArray = new Uint8Array((0, _conversion.bufferLikeToArrayBuffer)(signature));
1451
+ if (expected.length !== signatureArray.length) {
1452
+ return false;
1453
+ }
1454
+
1455
+ // Manual constant-time comparison
1456
+ let result = 0;
1457
+ for (let i = 0; i < expected.length; i++) {
1458
+ result |= expected[i] ^ signatureArray[i];
1459
+ }
1460
+ return result === 0;
1461
+ }
1462
+ return signVerify(normalizedAlgorithm, key, data, signature);
1088
1463
  }
1089
1464
  }
1090
1465
  exports.Subtle = Subtle;
1091
1466
  const subtle = exports.subtle = new Subtle();
1467
+ function getKeyLength(algorithm) {
1468
+ const name = algorithm.name;
1469
+ switch (name) {
1470
+ case 'AES-CTR':
1471
+ case 'AES-CBC':
1472
+ case 'AES-GCM':
1473
+ case 'AES-KW':
1474
+ case 'ChaCha20-Poly1305':
1475
+ return algorithm.length || 256;
1476
+ case 'HMAC':
1477
+ {
1478
+ const hmacAlg = algorithm;
1479
+ return hmacAlg.length || 256;
1480
+ }
1481
+ default:
1482
+ throw (0, _errors.lazyDOMException)(`Cannot determine key length for ${name}`, 'NotSupportedError');
1483
+ }
1484
+ }
1092
1485
  //# sourceMappingURL=subtle.js.map