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
@@ -17,8 +17,9 @@ import { rsa_generateKeyPair } from './rsa';
17
17
  import { getRandomValues } from './random';
18
18
  import { createHmac } from './hmac';
19
19
  import { createSign, createVerify } from './keys/signVerify';
20
- import { ed_generateKeyPairWebCrypto, Ed } from './ed';
20
+ import { ed_generateKeyPairWebCrypto, x_generateKeyPairWebCrypto, xDeriveBits, Ed } from './ed';
21
21
  import { mldsa_generateKeyPairWebCrypto } from './mldsa';
22
+ import { hkdfDeriveBits } from './hkdf';
22
23
  // import { pbkdf2DeriveBits } from './pbkdf2';
23
24
  // import { aesCipher, aesGenerateKey, aesImportKey, getAlgorithmName } from './aes';
24
25
  // import { rsaCipher, rsaExportKey, rsaImportKey, rsaKeyGenerate } from './rsa';
@@ -267,6 +268,120 @@ async function aesGcmCipher(mode, key, data, algorithm) {
267
268
  return result.buffer;
268
269
  }
269
270
  }
271
+ async function aesKwCipher(mode, key, data) {
272
+ const isWrap = mode === CipherOrWrapMode.kWebCryptoCipherEncrypt;
273
+
274
+ // AES-KW requires input to be a multiple of 8 bytes (64 bits)
275
+ if (data.byteLength % 8 !== 0) {
276
+ throw lazyDOMException(`AES-KW input length must be a multiple of 8 bytes, got ${data.byteLength}`, 'OperationError');
277
+ }
278
+
279
+ // AES-KW requires at least 16 bytes of input (128 bits)
280
+ if (isWrap && data.byteLength < 16) {
281
+ throw lazyDOMException(`AES-KW input must be at least 16 bytes, got ${data.byteLength}`, 'OperationError');
282
+ }
283
+
284
+ // Get cipher type based on key length
285
+ const keyLength = key.algorithm.length;
286
+ // Use aes*-wrap for both operations (matching Node.js)
287
+ const cipherType = `aes${keyLength}-wrap`;
288
+
289
+ // Export key material
290
+ const exportedKey = key.keyObject.export();
291
+ const cipherKey = bufferLikeToArrayBuffer(exportedKey);
292
+
293
+ // AES-KW uses a default IV as specified in RFC 3394
294
+ const defaultWrapIV = new Uint8Array([0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6]);
295
+ const factory = NitroModules.createHybridObject('CipherFactory');
296
+ const cipher = factory.createCipher({
297
+ isCipher: isWrap,
298
+ cipherType,
299
+ cipherKey,
300
+ iv: defaultWrapIV.buffer // RFC 3394 default IV for AES-KW
301
+ });
302
+
303
+ // Process data
304
+ const updated = cipher.update(data);
305
+ const final = cipher.final();
306
+
307
+ // Concatenate results
308
+ const result = new Uint8Array(updated.byteLength + final.byteLength);
309
+ result.set(new Uint8Array(updated), 0);
310
+ result.set(new Uint8Array(final), updated.byteLength);
311
+ return result.buffer;
312
+ }
313
+ async function chaCha20Poly1305Cipher(mode, key, data, algorithm) {
314
+ const {
315
+ iv,
316
+ additionalData,
317
+ tagLength = 128
318
+ } = algorithm;
319
+
320
+ // Validate IV (must be 12 bytes for ChaCha20-Poly1305)
321
+ const ivBuffer = bufferLikeToArrayBuffer(iv);
322
+ if (!ivBuffer || ivBuffer.byteLength !== 12) {
323
+ throw lazyDOMException('ChaCha20-Poly1305 IV must be exactly 12 bytes', 'OperationError');
324
+ }
325
+
326
+ // Validate tag length (only 128-bit supported)
327
+ if (tagLength !== 128) {
328
+ throw lazyDOMException('ChaCha20-Poly1305 only supports 128-bit auth tags', 'NotSupportedError');
329
+ }
330
+ const tagByteLength = 16; // 128 bits = 16 bytes
331
+
332
+ // Create cipher using existing ChaCha20-Poly1305 implementation
333
+ const factory = NitroModules.createHybridObject('CipherFactory');
334
+ const cipher = factory.createCipher({
335
+ isCipher: mode === CipherOrWrapMode.kWebCryptoCipherEncrypt,
336
+ cipherType: 'chacha20-poly1305',
337
+ cipherKey: bufferLikeToArrayBuffer(key.keyObject.export()),
338
+ iv: ivBuffer,
339
+ authTagLen: tagByteLength
340
+ });
341
+ let processData;
342
+ let authTag;
343
+ if (mode === CipherOrWrapMode.kWebCryptoCipherDecrypt) {
344
+ // For decryption, extract auth tag from end of data
345
+ const dataView = new Uint8Array(data);
346
+ if (dataView.byteLength < tagByteLength) {
347
+ throw lazyDOMException('The provided data is too small.', 'OperationError');
348
+ }
349
+
350
+ // Split data and tag
351
+ const ciphertextLength = dataView.byteLength - tagByteLength;
352
+ processData = dataView.slice(0, ciphertextLength).buffer;
353
+ authTag = dataView.slice(ciphertextLength).buffer;
354
+
355
+ // Set auth tag for verification
356
+ cipher.setAuthTag(authTag);
357
+ } else {
358
+ processData = data;
359
+ }
360
+
361
+ // Set additional authenticated data if provided
362
+ if (additionalData) {
363
+ cipher.setAAD(bufferLikeToArrayBuffer(additionalData));
364
+ }
365
+
366
+ // Process data
367
+ const updated = cipher.update(processData);
368
+ const final = cipher.final();
369
+ if (mode === CipherOrWrapMode.kWebCryptoCipherEncrypt) {
370
+ // For encryption, append auth tag to result
371
+ const tag = cipher.getAuthTag();
372
+ const result = new Uint8Array(updated.byteLength + final.byteLength + tag.byteLength);
373
+ result.set(new Uint8Array(updated), 0);
374
+ result.set(new Uint8Array(final), updated.byteLength);
375
+ result.set(new Uint8Array(tag), updated.byteLength + final.byteLength);
376
+ return result.buffer;
377
+ } else {
378
+ // For decryption, just concatenate plaintext
379
+ const result = new Uint8Array(updated.byteLength + final.byteLength);
380
+ result.set(new Uint8Array(updated), 0);
381
+ result.set(new Uint8Array(final), updated.byteLength);
382
+ return result.buffer;
383
+ }
384
+ }
270
385
  async function aesGenerateKey(algorithm, extractable, keyUsages) {
271
386
  const {
272
387
  length
@@ -554,7 +669,9 @@ function edImportKey(format, data, algorithm, extractable, keyUsages) {
554
669
  } = algorithm;
555
670
 
556
671
  // Validate usages
557
- if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
672
+ const isX = name === 'X25519' || name === 'X448';
673
+ const allowedUsages = isX ? ['deriveKey', 'deriveBits'] : ['sign', 'verify'];
674
+ if (hasAnyNotIn(keyUsages, allowedUsages)) {
558
675
  throw lazyDOMException(`Unsupported key usage for ${name} key`, 'SyntaxError');
559
676
  }
560
677
  let keyObject;
@@ -627,8 +744,12 @@ const exportKeySpki = async key => {
627
744
  case 'Ed25519':
628
745
  // Fall through
629
746
  case 'Ed448':
747
+ // Fall through
748
+ case 'X25519':
749
+ // Fall through
750
+ case 'X448':
630
751
  if (key.type === 'public') {
631
- // Export Ed key in SPKI DER format
752
+ // Export Ed/X key in SPKI DER format
632
753
  return bufferLikeToArrayBuffer(key.keyObject.handle.exportKey(KFormatType.DER, KeyEncoding.SPKI));
633
754
  }
634
755
  break;
@@ -666,8 +787,12 @@ const exportKeyPkcs8 = async key => {
666
787
  case 'Ed25519':
667
788
  // Fall through
668
789
  case 'Ed448':
790
+ // Fall through
791
+ case 'X25519':
792
+ // Fall through
793
+ case 'X448':
669
794
  if (key.type === 'private') {
670
- // Export Ed key in PKCS8 DER format
795
+ // Export Ed/X key in PKCS8 DER format
671
796
  return bufferLikeToArrayBuffer(key.keyObject.handle.exportKey(KFormatType.DER, KeyEncoding.PKCS8));
672
797
  }
673
798
  break;
@@ -693,6 +818,19 @@ const exportKeyRaw = key => {
693
818
  return ecExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatRaw);
694
819
  }
695
820
  break;
821
+ case 'Ed25519':
822
+ // Fall through
823
+ case 'Ed448':
824
+ // Fall through
825
+ case 'X25519':
826
+ // Fall through
827
+ case 'X448':
828
+ if (key.type === 'public') {
829
+ // Export raw public key
830
+ const exported = key.keyObject.handle.exportKey();
831
+ return bufferLikeToArrayBuffer(exported);
832
+ }
833
+ break;
696
834
  case 'AES-CTR':
697
835
  // Fall through
698
836
  case 'AES-CBC':
@@ -701,6 +839,8 @@ const exportKeyRaw = key => {
701
839
  // Fall through
702
840
  case 'AES-KW':
703
841
  // Fall through
842
+ case 'ChaCha20-Poly1305':
843
+ // Fall through
704
844
  case 'HMAC':
705
845
  {
706
846
  const exported = key.keyObject.export();
@@ -740,6 +880,8 @@ const exportKeyJWK = key => {
740
880
  case 'AES-GCM':
741
881
  // Fall through
742
882
  case 'AES-KW':
883
+ // Fall through
884
+ case 'ChaCha20-Poly1305':
743
885
  if (key.algorithm.length === undefined) {
744
886
  throw lazyDOMException(`Algorithm ${key.algorithm.name} missing required length property`, 'InvalidAccessError');
745
887
  }
@@ -778,6 +920,25 @@ const importGenericSecretKey = async ({
778
920
  }
779
921
  throw new Error(`Unable to import ${name} key with format ${format}`);
780
922
  };
923
+ const hkdfImportKey = async (format, keyData, algorithm, extractable, keyUsages) => {
924
+ const {
925
+ name
926
+ } = algorithm;
927
+ if (hasAnyNotIn(keyUsages, ['deriveKey', 'deriveBits'])) {
928
+ throw new Error(`Unsupported key usage for a ${name} key`);
929
+ }
930
+ switch (format) {
931
+ case 'raw':
932
+ {
933
+ const keyObject = createSecretKey(keyData);
934
+ return new CryptoKey(keyObject, {
935
+ name
936
+ }, keyUsages, extractable);
937
+ }
938
+ default:
939
+ throw new Error(`Unable to import ${name} key with format ${format}`);
940
+ }
941
+ };
781
942
  const checkCryptoKeyPairUsages = pair => {
782
943
  if (pair.privateKey && pair.privateKey instanceof CryptoKey && pair.privateKey.keyUsages && pair.privateKey.keyUsages.length > 0) {
783
944
  return;
@@ -937,6 +1098,10 @@ const cipherOrWrap = async (mode, algorithm, key, data, op) => {
937
1098
  // Fall through
938
1099
  case 'AES-GCM':
939
1100
  return aesCipher(mode, key, data, algorithm);
1101
+ case 'AES-KW':
1102
+ return aesKwCipher(mode, key, data);
1103
+ case 'ChaCha20-Poly1305':
1104
+ return chaCha20Poly1305Cipher(mode, key, data, algorithm);
940
1105
  }
941
1106
  };
942
1107
  export class Subtle {
@@ -949,16 +1114,54 @@ export class Subtle {
949
1114
  return asyncDigest(normalizedAlgorithm, data);
950
1115
  }
951
1116
  async deriveBits(algorithm, baseKey, length) {
952
- if (!baseKey.keyUsages.includes('deriveBits')) {
953
- throw new Error('baseKey does not have deriveBits usage');
1117
+ // Allow either deriveBits OR deriveKey usage (WebCrypto spec allows both)
1118
+ if (!baseKey.keyUsages.includes('deriveBits') && !baseKey.keyUsages.includes('deriveKey')) {
1119
+ throw new Error('baseKey does not have deriveBits or deriveKey usage');
954
1120
  }
955
1121
  if (baseKey.algorithm.name !== algorithm.name) throw new Error('Key algorithm mismatch');
956
1122
  switch (algorithm.name) {
957
1123
  case 'PBKDF2':
958
1124
  return pbkdf2DeriveBits(algorithm, baseKey, length);
1125
+ case 'X25519':
1126
+ // Fall through
1127
+ case 'X448':
1128
+ return xDeriveBits(algorithm, baseKey, length);
1129
+ case 'HKDF':
1130
+ return hkdfDeriveBits(algorithm, baseKey, length);
959
1131
  }
960
1132
  throw new Error(`'subtle.deriveBits()' for ${algorithm.name} is not implemented.`);
961
1133
  }
1134
+ async deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages) {
1135
+ // Validate baseKey usage
1136
+ if (!baseKey.usages.includes('deriveKey') && !baseKey.usages.includes('deriveBits')) {
1137
+ throw lazyDOMException('baseKey does not have deriveKey or deriveBits usage', 'InvalidAccessError');
1138
+ }
1139
+
1140
+ // Calculate required key length
1141
+ const length = getKeyLength(derivedKeyAlgorithm);
1142
+
1143
+ // Step 1: Derive bits
1144
+ let derivedBits;
1145
+ if (baseKey.algorithm.name !== algorithm.name) throw new Error('Key algorithm mismatch');
1146
+ switch (algorithm.name) {
1147
+ case 'PBKDF2':
1148
+ derivedBits = await pbkdf2DeriveBits(algorithm, baseKey, length);
1149
+ break;
1150
+ case 'X25519':
1151
+ // Fall through
1152
+ case 'X448':
1153
+ derivedBits = await xDeriveBits(algorithm, baseKey, length);
1154
+ break;
1155
+ case 'HKDF':
1156
+ derivedBits = hkdfDeriveBits(algorithm, baseKey, length);
1157
+ break;
1158
+ default:
1159
+ throw new Error(`'subtle.deriveKey()' for ${algorithm.name} is not implemented.`);
1160
+ }
1161
+
1162
+ // Step 2: Import as key
1163
+ return this.importKey('raw', derivedBits, derivedKeyAlgorithm, extractable, keyUsages);
1164
+ }
962
1165
  async encrypt(algorithm, key, data) {
963
1166
  const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'encrypt');
964
1167
  return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherEncrypt, normalizedAlgorithm, key, bufferLikeToArrayBuffer(data), 'encrypt');
@@ -976,6 +1179,76 @@ export class Subtle {
976
1179
  return exportKeyRaw(key);
977
1180
  }
978
1181
  }
1182
+ async wrapKey(format, key, wrappingKey, wrapAlgorithm) {
1183
+ // Validate wrappingKey usage
1184
+ if (!wrappingKey.usages.includes('wrapKey')) {
1185
+ throw lazyDOMException('wrappingKey does not have wrapKey usage', 'InvalidAccessError');
1186
+ }
1187
+
1188
+ // Step 1: Export the key
1189
+ const exported = await this.exportKey(format, key);
1190
+
1191
+ // Step 2: Convert to ArrayBuffer if JWK
1192
+ let keyData;
1193
+ if (format === 'jwk') {
1194
+ const jwkString = JSON.stringify(exported);
1195
+ const buffer = SBuffer.from(jwkString, 'utf8');
1196
+
1197
+ // For AES-KW, pad to multiple of 8 bytes (accounting for null terminator)
1198
+ if (wrapAlgorithm.name === 'AES-KW') {
1199
+ const length = buffer.length;
1200
+ // Add 1 for null terminator, then pad to multiple of 8
1201
+ const paddedLength = Math.ceil((length + 1) / 8) * 8;
1202
+ const paddedBuffer = SBuffer.alloc(paddedLength);
1203
+ buffer.copy(paddedBuffer);
1204
+ // Null terminator for JSON string (remaining bytes are already zeros from alloc)
1205
+ paddedBuffer.writeUInt8(0, length);
1206
+ keyData = bufferLikeToArrayBuffer(paddedBuffer);
1207
+ } else {
1208
+ keyData = bufferLikeToArrayBuffer(buffer);
1209
+ }
1210
+ } else {
1211
+ keyData = exported;
1212
+ }
1213
+
1214
+ // Step 3: Encrypt the exported key
1215
+ return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherEncrypt, wrapAlgorithm, wrappingKey, keyData, 'wrapKey');
1216
+ }
1217
+ async unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages) {
1218
+ // Validate unwrappingKey usage
1219
+ if (!unwrappingKey.usages.includes('unwrapKey')) {
1220
+ throw lazyDOMException('unwrappingKey does not have unwrapKey usage', 'InvalidAccessError');
1221
+ }
1222
+
1223
+ // Step 1: Decrypt the wrapped key
1224
+ const decrypted = await cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherDecrypt, unwrapAlgorithm, unwrappingKey, bufferLikeToArrayBuffer(wrappedKey), 'unwrapKey');
1225
+
1226
+ // Step 2: Convert to appropriate format
1227
+ let keyData;
1228
+ if (format === 'jwk') {
1229
+ const buffer = SBuffer.from(decrypted);
1230
+ // For AES-KW, the data may be padded - find the null terminator
1231
+ let jwkString;
1232
+ if (unwrapAlgorithm.name === 'AES-KW') {
1233
+ // Find the null terminator (if present) to get the original string
1234
+ const nullIndex = buffer.indexOf(0);
1235
+ if (nullIndex !== -1) {
1236
+ jwkString = buffer.toString('utf8', 0, nullIndex);
1237
+ } else {
1238
+ // No null terminator, try to parse the whole buffer
1239
+ jwkString = buffer.toString('utf8').trim();
1240
+ }
1241
+ } else {
1242
+ jwkString = buffer.toString('utf8');
1243
+ }
1244
+ keyData = JSON.parse(jwkString);
1245
+ } else {
1246
+ keyData = decrypted;
1247
+ }
1248
+
1249
+ // Step 3: Import the key
1250
+ return this.importKey(format, keyData, unwrappedKeyAlgorithm, extractable, keyUsages);
1251
+ }
979
1252
  async generateKey(algorithm, extractable, keyUsages) {
980
1253
  algorithm = normalizeAlgorithm(algorithm, 'generateKey');
981
1254
  let result;
@@ -1002,6 +1275,18 @@ export class Subtle {
1002
1275
  case 'AES-KW':
1003
1276
  result = await aesGenerateKey(algorithm, extractable, keyUsages);
1004
1277
  break;
1278
+ case 'ChaCha20-Poly1305':
1279
+ {
1280
+ const length = algorithm.length ?? 256;
1281
+ if (length !== 256) {
1282
+ throw lazyDOMException('ChaCha20-Poly1305 only supports 256-bit keys', 'NotSupportedError');
1283
+ }
1284
+ result = await aesGenerateKey({
1285
+ name: 'ChaCha20-Poly1305',
1286
+ length: 256
1287
+ }, extractable, keyUsages);
1288
+ break;
1289
+ }
1005
1290
  case 'HMAC':
1006
1291
  result = await hmacGenerateKey(algorithm, extractable, keyUsages);
1007
1292
  break;
@@ -1019,6 +1304,12 @@ export class Subtle {
1019
1304
  result = await mldsa_generateKeyPairWebCrypto(algorithm.name, extractable, keyUsages);
1020
1305
  checkCryptoKeyPairUsages(result);
1021
1306
  break;
1307
+ case 'X25519':
1308
+ // Fall through
1309
+ case 'X448':
1310
+ result = await x_generateKeyPairWebCrypto(algorithm.name.toLowerCase(), extractable, keyUsages);
1311
+ checkCryptoKeyPairUsages(result);
1312
+ break;
1022
1313
  default:
1023
1314
  throw new Error(`'subtle.generateKey()' is not implemented for ${algorithm.name}.
1024
1315
  Unrecognized algorithm name`);
@@ -1051,11 +1342,20 @@ export class Subtle {
1051
1342
  case 'AES-GCM':
1052
1343
  // Fall through
1053
1344
  case 'AES-KW':
1345
+ // Fall through
1346
+ case 'ChaCha20-Poly1305':
1054
1347
  result = await aesImportKey(normalizedAlgorithm, format, data, extractable, keyUsages);
1055
1348
  break;
1056
1349
  case 'PBKDF2':
1057
1350
  result = await importGenericSecretKey(normalizedAlgorithm, format, data, extractable, keyUsages);
1058
1351
  break;
1352
+ case 'HKDF':
1353
+ result = await hkdfImportKey(format, data, normalizedAlgorithm, extractable, keyUsages);
1354
+ break;
1355
+ case 'X25519':
1356
+ // Fall through
1357
+ case 'X448':
1358
+ // Fall through
1059
1359
  case 'Ed25519':
1060
1360
  // Fall through
1061
1361
  case 'Ed448':
@@ -1077,11 +1377,104 @@ export class Subtle {
1077
1377
  return result;
1078
1378
  }
1079
1379
  async sign(algorithm, key, data) {
1080
- return signVerify(algorithm, key, data);
1380
+ const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'sign');
1381
+ if (normalizedAlgorithm.name === 'HMAC') {
1382
+ // Validate key usage
1383
+ if (!key.usages.includes('sign')) {
1384
+ throw lazyDOMException('Key does not have sign usage', 'InvalidAccessError');
1385
+ }
1386
+
1387
+ // Get hash algorithm from key or algorithm params
1388
+ // Hash can be either a string or an object with name property
1389
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1390
+ const alg = normalizedAlgorithm;
1391
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1392
+ const keyAlg = key.algorithm;
1393
+ let hashAlgorithm = 'SHA-256';
1394
+ if (typeof alg.hash === 'string') {
1395
+ hashAlgorithm = alg.hash;
1396
+ } else if (alg.hash?.name) {
1397
+ hashAlgorithm = alg.hash.name;
1398
+ } else if (typeof keyAlg.hash === 'string') {
1399
+ hashAlgorithm = keyAlg.hash;
1400
+ } else if (keyAlg.hash?.name) {
1401
+ hashAlgorithm = keyAlg.hash.name;
1402
+ }
1403
+
1404
+ // Create HMAC and sign
1405
+ const keyData = key.keyObject.export();
1406
+ const hmac = createHmac(hashAlgorithm, keyData);
1407
+ hmac.update(bufferLikeToArrayBuffer(data));
1408
+ return bufferLikeToArrayBuffer(hmac.digest());
1409
+ }
1410
+ return signVerify(normalizedAlgorithm, key, data);
1081
1411
  }
1082
1412
  async verify(algorithm, key, signature, data) {
1083
- return signVerify(algorithm, key, data, signature);
1413
+ const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'verify');
1414
+ if (normalizedAlgorithm.name === 'HMAC') {
1415
+ // Validate key usage
1416
+ if (!key.usages.includes('verify')) {
1417
+ throw lazyDOMException('Key does not have verify usage', 'InvalidAccessError');
1418
+ }
1419
+
1420
+ // Get hash algorithm
1421
+ // Hash can be either a string or an object with name property
1422
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1423
+ const alg = normalizedAlgorithm;
1424
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1425
+ const keyAlg = key.algorithm;
1426
+ let hashAlgorithm = 'SHA-256';
1427
+ if (typeof alg.hash === 'string') {
1428
+ hashAlgorithm = alg.hash;
1429
+ } else if (alg.hash?.name) {
1430
+ hashAlgorithm = alg.hash.name;
1431
+ } else if (typeof keyAlg.hash === 'string') {
1432
+ hashAlgorithm = keyAlg.hash;
1433
+ } else if (keyAlg.hash?.name) {
1434
+ hashAlgorithm = keyAlg.hash.name;
1435
+ }
1436
+
1437
+ // Create HMAC and compute expected signature
1438
+ const keyData = key.keyObject.export();
1439
+ const hmac = createHmac(hashAlgorithm, keyData);
1440
+ const dataBuffer = bufferLikeToArrayBuffer(data);
1441
+ hmac.update(dataBuffer);
1442
+ const expectedDigest = hmac.digest();
1443
+ const expected = new Uint8Array(bufferLikeToArrayBuffer(expectedDigest));
1444
+
1445
+ // Constant-time comparison
1446
+ const signatureArray = new Uint8Array(bufferLikeToArrayBuffer(signature));
1447
+ if (expected.length !== signatureArray.length) {
1448
+ return false;
1449
+ }
1450
+
1451
+ // Manual constant-time comparison
1452
+ let result = 0;
1453
+ for (let i = 0; i < expected.length; i++) {
1454
+ result |= expected[i] ^ signatureArray[i];
1455
+ }
1456
+ return result === 0;
1457
+ }
1458
+ return signVerify(normalizedAlgorithm, key, data, signature);
1084
1459
  }
1085
1460
  }
1086
1461
  export const subtle = new Subtle();
1462
+ function getKeyLength(algorithm) {
1463
+ const name = algorithm.name;
1464
+ switch (name) {
1465
+ case 'AES-CTR':
1466
+ case 'AES-CBC':
1467
+ case 'AES-GCM':
1468
+ case 'AES-KW':
1469
+ case 'ChaCha20-Poly1305':
1470
+ return algorithm.length || 256;
1471
+ case 'HMAC':
1472
+ {
1473
+ const hmacAlg = algorithm;
1474
+ return hmacAlg.length || 256;
1475
+ }
1476
+ default:
1477
+ throw lazyDOMException(`Cannot determine key length for ${name}`, 'NotSupportedError');
1478
+ }
1479
+ }
1087
1480
  //# sourceMappingURL=subtle.js.map