react-native-quick-crypto 1.0.0 → 1.0.2
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.
- package/QuickCrypto.podspec +19 -52
- package/android/CMakeLists.txt +4 -2
- package/android/build.gradle +1 -1
- package/cpp/cipher/HybridCipher.cpp +20 -3
- package/cpp/cipher/HybridRsaCipher.cpp +20 -1
- package/cpp/ed25519/HybridEdKeyPair.cpp +8 -2
- package/cpp/keys/HybridKeyObjectHandle.cpp +8 -0
- package/cpp/keys/KeyObjectData.hpp +1 -1
- package/cpp/mldsa/HybridMlDsaKeyPair.cpp +264 -0
- package/cpp/mldsa/HybridMlDsaKeyPair.hpp +47 -0
- package/cpp/sign/HybridSignHandle.cpp +97 -22
- package/cpp/sign/HybridVerifyHandle.cpp +90 -21
- package/deps/ncrypto/.bazelignore +4 -0
- package/deps/ncrypto/.bazelrc +2 -0
- package/deps/ncrypto/.bazelversion +1 -0
- package/deps/ncrypto/.clang-format +111 -0
- package/deps/ncrypto/.github/workflows/bazel.yml +58 -0
- package/deps/ncrypto/.github/workflows/linter.yml +38 -0
- package/deps/ncrypto/.github/workflows/macos.yml +43 -0
- package/deps/ncrypto/.github/workflows/ubuntu.yml +46 -0
- package/deps/ncrypto/.github/workflows/visual-studio.yml +49 -0
- package/deps/ncrypto/.python-version +1 -0
- package/deps/ncrypto/BUILD.bazel +36 -0
- package/deps/ncrypto/CMakeLists.txt +55 -0
- package/deps/ncrypto/LICENSE +21 -0
- package/deps/ncrypto/MODULE.bazel +1 -0
- package/deps/ncrypto/MODULE.bazel.lock +280 -0
- package/deps/ncrypto/README.md +18 -0
- package/deps/ncrypto/WORKSPACE +15 -0
- package/deps/ncrypto/cmake/CPM.cmake +1225 -0
- package/deps/ncrypto/cmake/ncrypto-flags.cmake +16 -0
- package/deps/ncrypto/include/dh-primes.h +67 -0
- package/deps/ncrypto/{ncrypto.h → include/ncrypto.h} +361 -89
- package/deps/ncrypto/patches/0001-Expose-libdecrepit-so-NodeJS-can-use-it-for-ncrypto.patch +28 -0
- package/deps/ncrypto/pyproject.toml +38 -0
- package/deps/ncrypto/src/CMakeLists.txt +15 -0
- package/deps/ncrypto/src/engine.cpp +93 -0
- package/deps/ncrypto/{ncrypto.cc → src/ncrypto.cpp} +1168 -234
- package/deps/ncrypto/tests/BUILD.bazel +9 -0
- package/deps/ncrypto/tests/CMakeLists.txt +7 -0
- package/deps/ncrypto/tests/basic.cpp +86 -0
- package/deps/ncrypto/tools/run-clang-format.sh +42 -0
- package/lib/commonjs/ed.js +68 -0
- package/lib/commonjs/ed.js.map +1 -1
- package/lib/commonjs/keys/classes.js +6 -0
- package/lib/commonjs/keys/classes.js.map +1 -1
- package/lib/commonjs/mldsa.js +69 -0
- package/lib/commonjs/mldsa.js.map +1 -0
- package/lib/commonjs/specs/mlDsaKeyPair.nitro.js +6 -0
- package/lib/commonjs/specs/mlDsaKeyPair.nitro.js.map +1 -0
- package/lib/commonjs/subtle.js +483 -13
- package/lib/commonjs/subtle.js.map +1 -1
- package/lib/commonjs/utils/types.js.map +1 -1
- package/lib/module/ed.js +66 -0
- package/lib/module/ed.js.map +1 -1
- package/lib/module/keys/classes.js +6 -0
- package/lib/module/keys/classes.js.map +1 -1
- package/lib/module/mldsa.js +63 -0
- package/lib/module/mldsa.js.map +1 -0
- package/lib/module/specs/mlDsaKeyPair.nitro.js +4 -0
- package/lib/module/specs/mlDsaKeyPair.nitro.js.map +1 -0
- package/lib/module/subtle.js +484 -14
- package/lib/module/subtle.js.map +1 -1
- package/lib/module/utils/types.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/typescript/ed.d.ts +4 -1
- package/lib/typescript/ed.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/keys/classes.d.ts +2 -0
- package/lib/typescript/keys/classes.d.ts.map +1 -1
- package/lib/typescript/mldsa.d.ts +18 -0
- package/lib/typescript/mldsa.d.ts.map +1 -0
- package/lib/typescript/specs/mlDsaKeyPair.nitro.d.ts +16 -0
- package/lib/typescript/specs/mlDsaKeyPair.nitro.d.ts.map +1 -0
- package/lib/typescript/subtle.d.ts +4 -1
- package/lib/typescript/subtle.d.ts.map +1 -1
- package/lib/typescript/utils/types.d.ts +14 -6
- package/lib/typescript/utils/types.d.ts.map +1 -1
- package/nitrogen/generated/android/QuickCrypto+autolinking.cmake +1 -0
- package/nitrogen/generated/android/QuickCryptoOnLoad.cpp +10 -0
- package/nitrogen/generated/ios/QuickCryptoAutolinking.mm +10 -0
- package/nitrogen/generated/shared/c++/AsymmetricKeyType.hpp +12 -0
- package/nitrogen/generated/shared/c++/HybridMlDsaKeyPairSpec.cpp +29 -0
- package/nitrogen/generated/shared/c++/HybridMlDsaKeyPairSpec.hpp +73 -0
- package/package.json +7 -3
- package/src/ed.ts +102 -0
- package/src/keys/classes.ts +9 -0
- package/src/mldsa.ts +125 -0
- package/src/specs/mlDsaKeyPair.nitro.ts +29 -0
- package/src/subtle.ts +667 -17
- package/src/utils/types.ts +27 -6
package/lib/commonjs/subtle.js
CHANGED
|
@@ -22,6 +22,7 @@ var _random = require("./random");
|
|
|
22
22
|
var _hmac = require("./hmac");
|
|
23
23
|
var _signVerify = require("./keys/signVerify");
|
|
24
24
|
var _ed = require("./ed");
|
|
25
|
+
var _mldsa = require("./mldsa");
|
|
25
26
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
26
27
|
// import { pbkdf2DeriveBits } from './pbkdf2';
|
|
27
28
|
// import { aesCipher, aesGenerateKey, aesImportKey, getAlgorithmName } from './aes';
|
|
@@ -270,6 +271,120 @@ async function aesGcmCipher(mode, key, data, algorithm) {
|
|
|
270
271
|
return result.buffer;
|
|
271
272
|
}
|
|
272
273
|
}
|
|
274
|
+
async function aesKwCipher(mode, key, data) {
|
|
275
|
+
const isWrap = mode === CipherOrWrapMode.kWebCryptoCipherEncrypt;
|
|
276
|
+
|
|
277
|
+
// AES-KW requires input to be a multiple of 8 bytes (64 bits)
|
|
278
|
+
if (data.byteLength % 8 !== 0) {
|
|
279
|
+
throw (0, _errors.lazyDOMException)(`AES-KW input length must be a multiple of 8 bytes, got ${data.byteLength}`, 'OperationError');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// AES-KW requires at least 16 bytes of input (128 bits)
|
|
283
|
+
if (isWrap && data.byteLength < 16) {
|
|
284
|
+
throw (0, _errors.lazyDOMException)(`AES-KW input must be at least 16 bytes, got ${data.byteLength}`, 'OperationError');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Get cipher type based on key length
|
|
288
|
+
const keyLength = key.algorithm.length;
|
|
289
|
+
// Use aes*-wrap for both operations (matching Node.js)
|
|
290
|
+
const cipherType = `aes${keyLength}-wrap`;
|
|
291
|
+
|
|
292
|
+
// Export key material
|
|
293
|
+
const exportedKey = key.keyObject.export();
|
|
294
|
+
const cipherKey = (0, _conversion.bufferLikeToArrayBuffer)(exportedKey);
|
|
295
|
+
|
|
296
|
+
// AES-KW uses a default IV as specified in RFC 3394
|
|
297
|
+
const defaultWrapIV = new Uint8Array([0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6]);
|
|
298
|
+
const factory = _reactNativeNitroModules.NitroModules.createHybridObject('CipherFactory');
|
|
299
|
+
const cipher = factory.createCipher({
|
|
300
|
+
isCipher: isWrap,
|
|
301
|
+
cipherType,
|
|
302
|
+
cipherKey,
|
|
303
|
+
iv: defaultWrapIV.buffer // RFC 3394 default IV for AES-KW
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Process data
|
|
307
|
+
const updated = cipher.update(data);
|
|
308
|
+
const final = cipher.final();
|
|
309
|
+
|
|
310
|
+
// Concatenate results
|
|
311
|
+
const result = new Uint8Array(updated.byteLength + final.byteLength);
|
|
312
|
+
result.set(new Uint8Array(updated), 0);
|
|
313
|
+
result.set(new Uint8Array(final), updated.byteLength);
|
|
314
|
+
return result.buffer;
|
|
315
|
+
}
|
|
316
|
+
async function chaCha20Poly1305Cipher(mode, key, data, algorithm) {
|
|
317
|
+
const {
|
|
318
|
+
iv,
|
|
319
|
+
additionalData,
|
|
320
|
+
tagLength = 128
|
|
321
|
+
} = algorithm;
|
|
322
|
+
|
|
323
|
+
// Validate IV (must be 12 bytes for ChaCha20-Poly1305)
|
|
324
|
+
const ivBuffer = (0, _conversion.bufferLikeToArrayBuffer)(iv);
|
|
325
|
+
if (!ivBuffer || ivBuffer.byteLength !== 12) {
|
|
326
|
+
throw (0, _errors.lazyDOMException)('ChaCha20-Poly1305 IV must be exactly 12 bytes', 'OperationError');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Validate tag length (only 128-bit supported)
|
|
330
|
+
if (tagLength !== 128) {
|
|
331
|
+
throw (0, _errors.lazyDOMException)('ChaCha20-Poly1305 only supports 128-bit auth tags', 'NotSupportedError');
|
|
332
|
+
}
|
|
333
|
+
const tagByteLength = 16; // 128 bits = 16 bytes
|
|
334
|
+
|
|
335
|
+
// Create cipher using existing ChaCha20-Poly1305 implementation
|
|
336
|
+
const factory = _reactNativeNitroModules.NitroModules.createHybridObject('CipherFactory');
|
|
337
|
+
const cipher = factory.createCipher({
|
|
338
|
+
isCipher: mode === CipherOrWrapMode.kWebCryptoCipherEncrypt,
|
|
339
|
+
cipherType: 'chacha20-poly1305',
|
|
340
|
+
cipherKey: (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.export()),
|
|
341
|
+
iv: ivBuffer,
|
|
342
|
+
authTagLen: tagByteLength
|
|
343
|
+
});
|
|
344
|
+
let processData;
|
|
345
|
+
let authTag;
|
|
346
|
+
if (mode === CipherOrWrapMode.kWebCryptoCipherDecrypt) {
|
|
347
|
+
// For decryption, extract auth tag from end of data
|
|
348
|
+
const dataView = new Uint8Array(data);
|
|
349
|
+
if (dataView.byteLength < tagByteLength) {
|
|
350
|
+
throw (0, _errors.lazyDOMException)('The provided data is too small.', 'OperationError');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Split data and tag
|
|
354
|
+
const ciphertextLength = dataView.byteLength - tagByteLength;
|
|
355
|
+
processData = dataView.slice(0, ciphertextLength).buffer;
|
|
356
|
+
authTag = dataView.slice(ciphertextLength).buffer;
|
|
357
|
+
|
|
358
|
+
// Set auth tag for verification
|
|
359
|
+
cipher.setAuthTag(authTag);
|
|
360
|
+
} else {
|
|
361
|
+
processData = data;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Set additional authenticated data if provided
|
|
365
|
+
if (additionalData) {
|
|
366
|
+
cipher.setAAD((0, _conversion.bufferLikeToArrayBuffer)(additionalData));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Process data
|
|
370
|
+
const updated = cipher.update(processData);
|
|
371
|
+
const final = cipher.final();
|
|
372
|
+
if (mode === CipherOrWrapMode.kWebCryptoCipherEncrypt) {
|
|
373
|
+
// For encryption, append auth tag to result
|
|
374
|
+
const tag = cipher.getAuthTag();
|
|
375
|
+
const result = new Uint8Array(updated.byteLength + final.byteLength + tag.byteLength);
|
|
376
|
+
result.set(new Uint8Array(updated), 0);
|
|
377
|
+
result.set(new Uint8Array(final), updated.byteLength);
|
|
378
|
+
result.set(new Uint8Array(tag), updated.byteLength + final.byteLength);
|
|
379
|
+
return result.buffer;
|
|
380
|
+
} else {
|
|
381
|
+
// For decryption, just concatenate plaintext
|
|
382
|
+
const result = new Uint8Array(updated.byteLength + final.byteLength);
|
|
383
|
+
result.set(new Uint8Array(updated), 0);
|
|
384
|
+
result.set(new Uint8Array(final), updated.byteLength);
|
|
385
|
+
return result.buffer;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
273
388
|
async function aesGenerateKey(algorithm, extractable, keyUsages) {
|
|
274
389
|
const {
|
|
275
390
|
length
|
|
@@ -350,10 +465,13 @@ async function hmacGenerateKey(algorithm, extractable, keyUsages) {
|
|
|
350
465
|
// Create secret key
|
|
351
466
|
const keyObject = (0, _keys.createSecretKey)(keyBytes);
|
|
352
467
|
|
|
353
|
-
// Construct algorithm object
|
|
468
|
+
// Construct algorithm object with hash normalized to { name: string } format per WebCrypto spec
|
|
469
|
+
const webCryptoHashName = (0, _hashnames.normalizeHashName)(hash, _hashnames.HashContext.WebCrypto);
|
|
354
470
|
const keyAlgorithm = {
|
|
355
471
|
name: 'HMAC',
|
|
356
|
-
hash:
|
|
472
|
+
hash: {
|
|
473
|
+
name: webCryptoHashName
|
|
474
|
+
},
|
|
357
475
|
length
|
|
358
476
|
};
|
|
359
477
|
return new _keys.CryptoKey(keyObject, keyAlgorithm, keyUsages, extractable);
|
|
@@ -427,10 +545,17 @@ function rsaImportKey(format, data, algorithm, extractable, keyUsages) {
|
|
|
427
545
|
}
|
|
428
546
|
publicExponentBytes = new Uint8Array(bytes.length > 0 ? bytes : [0]);
|
|
429
547
|
}
|
|
548
|
+
|
|
549
|
+
// Normalize hash to { name: string } format per WebCrypto spec
|
|
550
|
+
const hashName = (0, _hashnames.normalizeHashName)(algorithm.hash, _hashnames.HashContext.WebCrypto);
|
|
551
|
+
const normalizedHash = {
|
|
552
|
+
name: hashName
|
|
553
|
+
};
|
|
430
554
|
const algorithmWithDetails = {
|
|
431
555
|
...algorithm,
|
|
432
556
|
modulusLength: keyDetails?.modulusLength,
|
|
433
|
-
publicExponent: publicExponentBytes
|
|
557
|
+
publicExponent: publicExponentBytes,
|
|
558
|
+
hash: normalizedHash
|
|
434
559
|
};
|
|
435
560
|
return new _keys.CryptoKey(keyObject, algorithmWithDetails, keyUsages, extractable);
|
|
436
561
|
}
|
|
@@ -477,10 +602,17 @@ async function hmacImportKey(algorithm, format, data, extractable, keyUsages) {
|
|
|
477
602
|
} else {
|
|
478
603
|
throw new Error(`Unable to import HMAC key with format ${format}`);
|
|
479
604
|
}
|
|
480
|
-
|
|
605
|
+
|
|
606
|
+
// Normalize hash to { name: string } format per WebCrypto spec
|
|
607
|
+
const hashName = (0, _hashnames.normalizeHashName)(algorithm.hash, _hashnames.HashContext.WebCrypto);
|
|
608
|
+
const normalizedAlgorithm = {
|
|
481
609
|
...algorithm,
|
|
482
|
-
name: 'HMAC'
|
|
483
|
-
|
|
610
|
+
name: 'HMAC',
|
|
611
|
+
hash: {
|
|
612
|
+
name: hashName
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
return new _keys.CryptoKey(keyObject, normalizedAlgorithm, keyUsages, extractable);
|
|
484
616
|
}
|
|
485
617
|
async function aesImportKey(algorithm, format, data, extractable, keyUsages) {
|
|
486
618
|
const {
|
|
@@ -540,7 +672,9 @@ function edImportKey(format, data, algorithm, extractable, keyUsages) {
|
|
|
540
672
|
} = algorithm;
|
|
541
673
|
|
|
542
674
|
// Validate usages
|
|
543
|
-
|
|
675
|
+
const isX = name === 'X25519' || name === 'X448';
|
|
676
|
+
const allowedUsages = isX ? ['deriveKey', 'deriveBits'] : ['sign', 'verify'];
|
|
677
|
+
if (hasAnyNotIn(keyUsages, allowedUsages)) {
|
|
544
678
|
throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError');
|
|
545
679
|
}
|
|
546
680
|
let keyObject;
|
|
@@ -567,6 +701,31 @@ function edImportKey(format, data, algorithm, extractable, keyUsages) {
|
|
|
567
701
|
name
|
|
568
702
|
}, keyUsages, extractable);
|
|
569
703
|
}
|
|
704
|
+
function mldsaImportKey(format, data, algorithm, extractable, keyUsages) {
|
|
705
|
+
const {
|
|
706
|
+
name
|
|
707
|
+
} = algorithm;
|
|
708
|
+
|
|
709
|
+
// Validate usages
|
|
710
|
+
if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
|
|
711
|
+
throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError');
|
|
712
|
+
}
|
|
713
|
+
let keyObject;
|
|
714
|
+
if (format === 'spki') {
|
|
715
|
+
// Import public key
|
|
716
|
+
const keyData = (0, _conversion.bufferLikeToArrayBuffer)(data);
|
|
717
|
+
keyObject = _keys.KeyObject.createKeyObject('public', keyData, _utils.KFormatType.DER, _utils.KeyEncoding.SPKI);
|
|
718
|
+
} else if (format === 'pkcs8') {
|
|
719
|
+
// Import private key
|
|
720
|
+
const keyData = (0, _conversion.bufferLikeToArrayBuffer)(data);
|
|
721
|
+
keyObject = _keys.KeyObject.createKeyObject('private', keyData, _utils.KFormatType.DER, _utils.KeyEncoding.PKCS8);
|
|
722
|
+
} else {
|
|
723
|
+
throw (0, _errors.lazyDOMException)(`Unsupported format for ${name} import: ${format}`, 'NotSupportedError');
|
|
724
|
+
}
|
|
725
|
+
return new _keys.CryptoKey(keyObject, {
|
|
726
|
+
name
|
|
727
|
+
}, keyUsages, extractable);
|
|
728
|
+
}
|
|
570
729
|
const exportKeySpki = async key => {
|
|
571
730
|
switch (key.algorithm.name) {
|
|
572
731
|
case 'RSASSA-PKCS1-v1_5':
|
|
@@ -588,8 +747,22 @@ const exportKeySpki = async key => {
|
|
|
588
747
|
case 'Ed25519':
|
|
589
748
|
// Fall through
|
|
590
749
|
case 'Ed448':
|
|
750
|
+
// Fall through
|
|
751
|
+
case 'X25519':
|
|
752
|
+
// Fall through
|
|
753
|
+
case 'X448':
|
|
754
|
+
if (key.type === 'public') {
|
|
755
|
+
// Export Ed/X key in SPKI DER format
|
|
756
|
+
return (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.SPKI));
|
|
757
|
+
}
|
|
758
|
+
break;
|
|
759
|
+
case 'ML-DSA-44':
|
|
760
|
+
// Fall through
|
|
761
|
+
case 'ML-DSA-65':
|
|
762
|
+
// Fall through
|
|
763
|
+
case 'ML-DSA-87':
|
|
591
764
|
if (key.type === 'public') {
|
|
592
|
-
// Export
|
|
765
|
+
// Export ML-DSA key in SPKI DER format
|
|
593
766
|
return (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.SPKI));
|
|
594
767
|
}
|
|
595
768
|
break;
|
|
@@ -617,8 +790,22 @@ const exportKeyPkcs8 = async key => {
|
|
|
617
790
|
case 'Ed25519':
|
|
618
791
|
// Fall through
|
|
619
792
|
case 'Ed448':
|
|
793
|
+
// Fall through
|
|
794
|
+
case 'X25519':
|
|
795
|
+
// Fall through
|
|
796
|
+
case 'X448':
|
|
620
797
|
if (key.type === 'private') {
|
|
621
|
-
// Export Ed key in PKCS8 DER format
|
|
798
|
+
// Export Ed/X key in PKCS8 DER format
|
|
799
|
+
return (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.PKCS8));
|
|
800
|
+
}
|
|
801
|
+
break;
|
|
802
|
+
case 'ML-DSA-44':
|
|
803
|
+
// Fall through
|
|
804
|
+
case 'ML-DSA-65':
|
|
805
|
+
// Fall through
|
|
806
|
+
case 'ML-DSA-87':
|
|
807
|
+
if (key.type === 'private') {
|
|
808
|
+
// Export ML-DSA key in PKCS8 DER format
|
|
622
809
|
return (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.PKCS8));
|
|
623
810
|
}
|
|
624
811
|
break;
|
|
@@ -634,6 +821,19 @@ const exportKeyRaw = key => {
|
|
|
634
821
|
return ecExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatRaw);
|
|
635
822
|
}
|
|
636
823
|
break;
|
|
824
|
+
case 'Ed25519':
|
|
825
|
+
// Fall through
|
|
826
|
+
case 'Ed448':
|
|
827
|
+
// Fall through
|
|
828
|
+
case 'X25519':
|
|
829
|
+
// Fall through
|
|
830
|
+
case 'X448':
|
|
831
|
+
if (key.type === 'public') {
|
|
832
|
+
// Export raw public key
|
|
833
|
+
const exported = key.keyObject.handle.exportKey();
|
|
834
|
+
return (0, _conversion.bufferLikeToArrayBuffer)(exported);
|
|
835
|
+
}
|
|
836
|
+
break;
|
|
637
837
|
case 'AES-CTR':
|
|
638
838
|
// Fall through
|
|
639
839
|
case 'AES-CBC':
|
|
@@ -642,6 +842,8 @@ const exportKeyRaw = key => {
|
|
|
642
842
|
// Fall through
|
|
643
843
|
case 'AES-KW':
|
|
644
844
|
// Fall through
|
|
845
|
+
case 'ChaCha20-Poly1305':
|
|
846
|
+
// Fall through
|
|
645
847
|
case 'HMAC':
|
|
646
848
|
{
|
|
647
849
|
const exported = key.keyObject.export();
|
|
@@ -681,6 +883,8 @@ const exportKeyJWK = key => {
|
|
|
681
883
|
case 'AES-GCM':
|
|
682
884
|
// Fall through
|
|
683
885
|
case 'AES-KW':
|
|
886
|
+
// Fall through
|
|
887
|
+
case 'ChaCha20-Poly1305':
|
|
684
888
|
if (key.algorithm.length === undefined) {
|
|
685
889
|
throw (0, _errors.lazyDOMException)(`Algorithm ${key.algorithm.name} missing required length property`, 'InvalidAccessError');
|
|
686
890
|
}
|
|
@@ -816,6 +1020,29 @@ function edSignVerify(key, data, signature) {
|
|
|
816
1020
|
return ed.verifySync(signatureBuffer, dataBuffer, rawKey);
|
|
817
1021
|
}
|
|
818
1022
|
}
|
|
1023
|
+
function mldsaSignVerify(key, data, signature) {
|
|
1024
|
+
const isSign = signature === undefined;
|
|
1025
|
+
const expectedKeyType = isSign ? 'private' : 'public';
|
|
1026
|
+
if (key.type !== expectedKeyType) {
|
|
1027
|
+
throw (0, _errors.lazyDOMException)(`Key must be a ${expectedKeyType} key`, 'InvalidAccessError');
|
|
1028
|
+
}
|
|
1029
|
+
const dataBuffer = (0, _conversion.bufferLikeToArrayBuffer)(data);
|
|
1030
|
+
if (isSign) {
|
|
1031
|
+
const signer = (0, _signVerify.createSign)('');
|
|
1032
|
+
signer.update(dataBuffer);
|
|
1033
|
+
const sig = signer.sign({
|
|
1034
|
+
key: key
|
|
1035
|
+
});
|
|
1036
|
+
return sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength);
|
|
1037
|
+
} else {
|
|
1038
|
+
const signatureBuffer = (0, _conversion.bufferLikeToArrayBuffer)(signature);
|
|
1039
|
+
const verifier = (0, _signVerify.createVerify)('');
|
|
1040
|
+
verifier.update(dataBuffer);
|
|
1041
|
+
return verifier.verify({
|
|
1042
|
+
key: key
|
|
1043
|
+
}, signatureBuffer);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
819
1046
|
const signVerify = (algorithm, key, data, signature) => {
|
|
820
1047
|
const usage = signature === undefined ? 'sign' : 'verify';
|
|
821
1048
|
algorithm = normalizeAlgorithm(algorithm, usage);
|
|
@@ -834,6 +1061,10 @@ const signVerify = (algorithm, key, data, signature) => {
|
|
|
834
1061
|
case 'Ed25519':
|
|
835
1062
|
case 'Ed448':
|
|
836
1063
|
return edSignVerify(key, data, signature);
|
|
1064
|
+
case 'ML-DSA-44':
|
|
1065
|
+
case 'ML-DSA-65':
|
|
1066
|
+
case 'ML-DSA-87':
|
|
1067
|
+
return mldsaSignVerify(key, data, signature);
|
|
837
1068
|
}
|
|
838
1069
|
throw (0, _errors.lazyDOMException)(`Unrecognized algorithm name '${algorithm.name}' for '${usage}'`, 'NotSupportedError');
|
|
839
1070
|
};
|
|
@@ -851,6 +1082,10 @@ const cipherOrWrap = async (mode, algorithm, key, data, op) => {
|
|
|
851
1082
|
// Fall through
|
|
852
1083
|
case 'AES-GCM':
|
|
853
1084
|
return aesCipher(mode, key, data, algorithm);
|
|
1085
|
+
case 'AES-KW':
|
|
1086
|
+
return aesKwCipher(mode, key, data);
|
|
1087
|
+
case 'ChaCha20-Poly1305':
|
|
1088
|
+
return chaCha20Poly1305Cipher(mode, key, data, algorithm);
|
|
854
1089
|
}
|
|
855
1090
|
};
|
|
856
1091
|
class Subtle {
|
|
@@ -863,16 +1098,49 @@ class Subtle {
|
|
|
863
1098
|
return (0, _hash.asyncDigest)(normalizedAlgorithm, data);
|
|
864
1099
|
}
|
|
865
1100
|
async deriveBits(algorithm, baseKey, length) {
|
|
866
|
-
|
|
867
|
-
|
|
1101
|
+
// Allow either deriveBits OR deriveKey usage (WebCrypto spec allows both)
|
|
1102
|
+
if (!baseKey.keyUsages.includes('deriveBits') && !baseKey.keyUsages.includes('deriveKey')) {
|
|
1103
|
+
throw new Error('baseKey does not have deriveBits or deriveKey usage');
|
|
868
1104
|
}
|
|
869
1105
|
if (baseKey.algorithm.name !== algorithm.name) throw new Error('Key algorithm mismatch');
|
|
870
1106
|
switch (algorithm.name) {
|
|
871
1107
|
case 'PBKDF2':
|
|
872
1108
|
return (0, _pbkdf.pbkdf2DeriveBits)(algorithm, baseKey, length);
|
|
1109
|
+
case 'X25519':
|
|
1110
|
+
// Fall through
|
|
1111
|
+
case 'X448':
|
|
1112
|
+
return (0, _ed.xDeriveBits)(algorithm, baseKey, length);
|
|
873
1113
|
}
|
|
874
1114
|
throw new Error(`'subtle.deriveBits()' for ${algorithm.name} is not implemented.`);
|
|
875
1115
|
}
|
|
1116
|
+
async deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages) {
|
|
1117
|
+
// Validate baseKey usage
|
|
1118
|
+
if (!baseKey.usages.includes('deriveKey') && !baseKey.usages.includes('deriveBits')) {
|
|
1119
|
+
throw (0, _errors.lazyDOMException)('baseKey does not have deriveKey or deriveBits usage', 'InvalidAccessError');
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// Calculate required key length
|
|
1123
|
+
const length = getKeyLength(derivedKeyAlgorithm);
|
|
1124
|
+
|
|
1125
|
+
// Step 1: Derive bits
|
|
1126
|
+
let derivedBits;
|
|
1127
|
+
if (baseKey.algorithm.name !== algorithm.name) throw new Error('Key algorithm mismatch');
|
|
1128
|
+
switch (algorithm.name) {
|
|
1129
|
+
case 'PBKDF2':
|
|
1130
|
+
derivedBits = await (0, _pbkdf.pbkdf2DeriveBits)(algorithm, baseKey, length);
|
|
1131
|
+
break;
|
|
1132
|
+
case 'X25519':
|
|
1133
|
+
// Fall through
|
|
1134
|
+
case 'X448':
|
|
1135
|
+
derivedBits = await (0, _ed.xDeriveBits)(algorithm, baseKey, length);
|
|
1136
|
+
break;
|
|
1137
|
+
default:
|
|
1138
|
+
throw new Error(`'subtle.deriveKey()' for ${algorithm.name} is not implemented.`);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// Step 2: Import as key
|
|
1142
|
+
return this.importKey('raw', derivedBits, derivedKeyAlgorithm, extractable, keyUsages);
|
|
1143
|
+
}
|
|
876
1144
|
async encrypt(algorithm, key, data) {
|
|
877
1145
|
const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'encrypt');
|
|
878
1146
|
return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherEncrypt, normalizedAlgorithm, key, (0, _conversion.bufferLikeToArrayBuffer)(data), 'encrypt');
|
|
@@ -890,6 +1158,76 @@ class Subtle {
|
|
|
890
1158
|
return exportKeyRaw(key);
|
|
891
1159
|
}
|
|
892
1160
|
}
|
|
1161
|
+
async wrapKey(format, key, wrappingKey, wrapAlgorithm) {
|
|
1162
|
+
// Validate wrappingKey usage
|
|
1163
|
+
if (!wrappingKey.usages.includes('wrapKey')) {
|
|
1164
|
+
throw (0, _errors.lazyDOMException)('wrappingKey does not have wrapKey usage', 'InvalidAccessError');
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// Step 1: Export the key
|
|
1168
|
+
const exported = await this.exportKey(format, key);
|
|
1169
|
+
|
|
1170
|
+
// Step 2: Convert to ArrayBuffer if JWK
|
|
1171
|
+
let keyData;
|
|
1172
|
+
if (format === 'jwk') {
|
|
1173
|
+
const jwkString = JSON.stringify(exported);
|
|
1174
|
+
const buffer = _safeBuffer.Buffer.from(jwkString, 'utf8');
|
|
1175
|
+
|
|
1176
|
+
// For AES-KW, pad to multiple of 8 bytes (accounting for null terminator)
|
|
1177
|
+
if (wrapAlgorithm.name === 'AES-KW') {
|
|
1178
|
+
const length = buffer.length;
|
|
1179
|
+
// Add 1 for null terminator, then pad to multiple of 8
|
|
1180
|
+
const paddedLength = Math.ceil((length + 1) / 8) * 8;
|
|
1181
|
+
const paddedBuffer = _safeBuffer.Buffer.alloc(paddedLength);
|
|
1182
|
+
buffer.copy(paddedBuffer);
|
|
1183
|
+
// Null terminator for JSON string (remaining bytes are already zeros from alloc)
|
|
1184
|
+
paddedBuffer.writeUInt8(0, length);
|
|
1185
|
+
keyData = (0, _conversion.bufferLikeToArrayBuffer)(paddedBuffer);
|
|
1186
|
+
} else {
|
|
1187
|
+
keyData = (0, _conversion.bufferLikeToArrayBuffer)(buffer);
|
|
1188
|
+
}
|
|
1189
|
+
} else {
|
|
1190
|
+
keyData = exported;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// Step 3: Encrypt the exported key
|
|
1194
|
+
return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherEncrypt, wrapAlgorithm, wrappingKey, keyData, 'wrapKey');
|
|
1195
|
+
}
|
|
1196
|
+
async unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages) {
|
|
1197
|
+
// Validate unwrappingKey usage
|
|
1198
|
+
if (!unwrappingKey.usages.includes('unwrapKey')) {
|
|
1199
|
+
throw (0, _errors.lazyDOMException)('unwrappingKey does not have unwrapKey usage', 'InvalidAccessError');
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// Step 1: Decrypt the wrapped key
|
|
1203
|
+
const decrypted = await cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherDecrypt, unwrapAlgorithm, unwrappingKey, (0, _conversion.bufferLikeToArrayBuffer)(wrappedKey), 'unwrapKey');
|
|
1204
|
+
|
|
1205
|
+
// Step 2: Convert to appropriate format
|
|
1206
|
+
let keyData;
|
|
1207
|
+
if (format === 'jwk') {
|
|
1208
|
+
const buffer = _safeBuffer.Buffer.from(decrypted);
|
|
1209
|
+
// For AES-KW, the data may be padded - find the null terminator
|
|
1210
|
+
let jwkString;
|
|
1211
|
+
if (unwrapAlgorithm.name === 'AES-KW') {
|
|
1212
|
+
// Find the null terminator (if present) to get the original string
|
|
1213
|
+
const nullIndex = buffer.indexOf(0);
|
|
1214
|
+
if (nullIndex !== -1) {
|
|
1215
|
+
jwkString = buffer.toString('utf8', 0, nullIndex);
|
|
1216
|
+
} else {
|
|
1217
|
+
// No null terminator, try to parse the whole buffer
|
|
1218
|
+
jwkString = buffer.toString('utf8').trim();
|
|
1219
|
+
}
|
|
1220
|
+
} else {
|
|
1221
|
+
jwkString = buffer.toString('utf8');
|
|
1222
|
+
}
|
|
1223
|
+
keyData = JSON.parse(jwkString);
|
|
1224
|
+
} else {
|
|
1225
|
+
keyData = decrypted;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// Step 3: Import the key
|
|
1229
|
+
return this.importKey(format, keyData, unwrappedKeyAlgorithm, extractable, keyUsages);
|
|
1230
|
+
}
|
|
893
1231
|
async generateKey(algorithm, extractable, keyUsages) {
|
|
894
1232
|
algorithm = normalizeAlgorithm(algorithm, 'generateKey');
|
|
895
1233
|
let result;
|
|
@@ -916,6 +1254,18 @@ class Subtle {
|
|
|
916
1254
|
case 'AES-KW':
|
|
917
1255
|
result = await aesGenerateKey(algorithm, extractable, keyUsages);
|
|
918
1256
|
break;
|
|
1257
|
+
case 'ChaCha20-Poly1305':
|
|
1258
|
+
{
|
|
1259
|
+
const length = algorithm.length ?? 256;
|
|
1260
|
+
if (length !== 256) {
|
|
1261
|
+
throw (0, _errors.lazyDOMException)('ChaCha20-Poly1305 only supports 256-bit keys', 'NotSupportedError');
|
|
1262
|
+
}
|
|
1263
|
+
result = await aesGenerateKey({
|
|
1264
|
+
name: 'ChaCha20-Poly1305',
|
|
1265
|
+
length: 256
|
|
1266
|
+
}, extractable, keyUsages);
|
|
1267
|
+
break;
|
|
1268
|
+
}
|
|
919
1269
|
case 'HMAC':
|
|
920
1270
|
result = await hmacGenerateKey(algorithm, extractable, keyUsages);
|
|
921
1271
|
break;
|
|
@@ -925,6 +1275,20 @@ class Subtle {
|
|
|
925
1275
|
result = await (0, _ed.ed_generateKeyPairWebCrypto)(algorithm.name.toLowerCase(), extractable, keyUsages);
|
|
926
1276
|
checkCryptoKeyPairUsages(result);
|
|
927
1277
|
break;
|
|
1278
|
+
case 'ML-DSA-44':
|
|
1279
|
+
// Fall through
|
|
1280
|
+
case 'ML-DSA-65':
|
|
1281
|
+
// Fall through
|
|
1282
|
+
case 'ML-DSA-87':
|
|
1283
|
+
result = await (0, _mldsa.mldsa_generateKeyPairWebCrypto)(algorithm.name, extractable, keyUsages);
|
|
1284
|
+
checkCryptoKeyPairUsages(result);
|
|
1285
|
+
break;
|
|
1286
|
+
case 'X25519':
|
|
1287
|
+
// Fall through
|
|
1288
|
+
case 'X448':
|
|
1289
|
+
result = await (0, _ed.x_generateKeyPairWebCrypto)(algorithm.name.toLowerCase(), extractable, keyUsages);
|
|
1290
|
+
checkCryptoKeyPairUsages(result);
|
|
1291
|
+
break;
|
|
928
1292
|
default:
|
|
929
1293
|
throw new Error(`'subtle.generateKey()' is not implemented for ${algorithm.name}.
|
|
930
1294
|
Unrecognized algorithm name`);
|
|
@@ -957,16 +1321,29 @@ class Subtle {
|
|
|
957
1321
|
case 'AES-GCM':
|
|
958
1322
|
// Fall through
|
|
959
1323
|
case 'AES-KW':
|
|
1324
|
+
// Fall through
|
|
1325
|
+
case 'ChaCha20-Poly1305':
|
|
960
1326
|
result = await aesImportKey(normalizedAlgorithm, format, data, extractable, keyUsages);
|
|
961
1327
|
break;
|
|
962
1328
|
case 'PBKDF2':
|
|
963
1329
|
result = await importGenericSecretKey(normalizedAlgorithm, format, data, extractable, keyUsages);
|
|
964
1330
|
break;
|
|
1331
|
+
case 'X25519':
|
|
1332
|
+
// Fall through
|
|
1333
|
+
case 'X448':
|
|
1334
|
+
// Fall through
|
|
965
1335
|
case 'Ed25519':
|
|
966
1336
|
// Fall through
|
|
967
1337
|
case 'Ed448':
|
|
968
1338
|
result = edImportKey(format, data, normalizedAlgorithm, extractable, keyUsages);
|
|
969
1339
|
break;
|
|
1340
|
+
case 'ML-DSA-44':
|
|
1341
|
+
// Fall through
|
|
1342
|
+
case 'ML-DSA-65':
|
|
1343
|
+
// Fall through
|
|
1344
|
+
case 'ML-DSA-87':
|
|
1345
|
+
result = mldsaImportKey(format, data, normalizedAlgorithm, extractable, keyUsages);
|
|
1346
|
+
break;
|
|
970
1347
|
default:
|
|
971
1348
|
throw new Error(`"subtle.importKey()" is not implemented for ${normalizedAlgorithm.name}`);
|
|
972
1349
|
}
|
|
@@ -976,12 +1353,105 @@ class Subtle {
|
|
|
976
1353
|
return result;
|
|
977
1354
|
}
|
|
978
1355
|
async sign(algorithm, key, data) {
|
|
979
|
-
|
|
1356
|
+
const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'sign');
|
|
1357
|
+
if (normalizedAlgorithm.name === 'HMAC') {
|
|
1358
|
+
// Validate key usage
|
|
1359
|
+
if (!key.usages.includes('sign')) {
|
|
1360
|
+
throw (0, _errors.lazyDOMException)('Key does not have sign usage', 'InvalidAccessError');
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// Get hash algorithm from key or algorithm params
|
|
1364
|
+
// Hash can be either a string or an object with name property
|
|
1365
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1366
|
+
const alg = normalizedAlgorithm;
|
|
1367
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1368
|
+
const keyAlg = key.algorithm;
|
|
1369
|
+
let hashAlgorithm = 'SHA-256';
|
|
1370
|
+
if (typeof alg.hash === 'string') {
|
|
1371
|
+
hashAlgorithm = alg.hash;
|
|
1372
|
+
} else if (alg.hash?.name) {
|
|
1373
|
+
hashAlgorithm = alg.hash.name;
|
|
1374
|
+
} else if (typeof keyAlg.hash === 'string') {
|
|
1375
|
+
hashAlgorithm = keyAlg.hash;
|
|
1376
|
+
} else if (keyAlg.hash?.name) {
|
|
1377
|
+
hashAlgorithm = keyAlg.hash.name;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
// Create HMAC and sign
|
|
1381
|
+
const keyData = key.keyObject.export();
|
|
1382
|
+
const hmac = (0, _hmac.createHmac)(hashAlgorithm, keyData);
|
|
1383
|
+
hmac.update((0, _conversion.bufferLikeToArrayBuffer)(data));
|
|
1384
|
+
return (0, _conversion.bufferLikeToArrayBuffer)(hmac.digest());
|
|
1385
|
+
}
|
|
1386
|
+
return signVerify(normalizedAlgorithm, key, data);
|
|
980
1387
|
}
|
|
981
1388
|
async verify(algorithm, key, signature, data) {
|
|
982
|
-
|
|
1389
|
+
const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'verify');
|
|
1390
|
+
if (normalizedAlgorithm.name === 'HMAC') {
|
|
1391
|
+
// Validate key usage
|
|
1392
|
+
if (!key.usages.includes('verify')) {
|
|
1393
|
+
throw (0, _errors.lazyDOMException)('Key does not have verify usage', 'InvalidAccessError');
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// Get hash algorithm
|
|
1397
|
+
// Hash can be either a string or an object with name property
|
|
1398
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1399
|
+
const alg = normalizedAlgorithm;
|
|
1400
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1401
|
+
const keyAlg = key.algorithm;
|
|
1402
|
+
let hashAlgorithm = 'SHA-256';
|
|
1403
|
+
if (typeof alg.hash === 'string') {
|
|
1404
|
+
hashAlgorithm = alg.hash;
|
|
1405
|
+
} else if (alg.hash?.name) {
|
|
1406
|
+
hashAlgorithm = alg.hash.name;
|
|
1407
|
+
} else if (typeof keyAlg.hash === 'string') {
|
|
1408
|
+
hashAlgorithm = keyAlg.hash;
|
|
1409
|
+
} else if (keyAlg.hash?.name) {
|
|
1410
|
+
hashAlgorithm = keyAlg.hash.name;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// Create HMAC and compute expected signature
|
|
1414
|
+
const keyData = key.keyObject.export();
|
|
1415
|
+
const hmac = (0, _hmac.createHmac)(hashAlgorithm, keyData);
|
|
1416
|
+
const dataBuffer = (0, _conversion.bufferLikeToArrayBuffer)(data);
|
|
1417
|
+
hmac.update(dataBuffer);
|
|
1418
|
+
const expectedDigest = hmac.digest();
|
|
1419
|
+
const expected = new Uint8Array((0, _conversion.bufferLikeToArrayBuffer)(expectedDigest));
|
|
1420
|
+
|
|
1421
|
+
// Constant-time comparison
|
|
1422
|
+
const signatureArray = new Uint8Array((0, _conversion.bufferLikeToArrayBuffer)(signature));
|
|
1423
|
+
if (expected.length !== signatureArray.length) {
|
|
1424
|
+
return false;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
// Manual constant-time comparison
|
|
1428
|
+
let result = 0;
|
|
1429
|
+
for (let i = 0; i < expected.length; i++) {
|
|
1430
|
+
result |= expected[i] ^ signatureArray[i];
|
|
1431
|
+
}
|
|
1432
|
+
return result === 0;
|
|
1433
|
+
}
|
|
1434
|
+
return signVerify(normalizedAlgorithm, key, data, signature);
|
|
983
1435
|
}
|
|
984
1436
|
}
|
|
985
1437
|
exports.Subtle = Subtle;
|
|
986
1438
|
const subtle = exports.subtle = new Subtle();
|
|
1439
|
+
function getKeyLength(algorithm) {
|
|
1440
|
+
const name = algorithm.name;
|
|
1441
|
+
switch (name) {
|
|
1442
|
+
case 'AES-CTR':
|
|
1443
|
+
case 'AES-CBC':
|
|
1444
|
+
case 'AES-GCM':
|
|
1445
|
+
case 'AES-KW':
|
|
1446
|
+
case 'ChaCha20-Poly1305':
|
|
1447
|
+
return algorithm.length || 256;
|
|
1448
|
+
case 'HMAC':
|
|
1449
|
+
{
|
|
1450
|
+
const hmacAlg = algorithm;
|
|
1451
|
+
return hmacAlg.length || 256;
|
|
1452
|
+
}
|
|
1453
|
+
default:
|
|
1454
|
+
throw (0, _errors.lazyDOMException)(`Cannot determine key length for ${name}`, 'NotSupportedError');
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
987
1457
|
//# sourceMappingURL=subtle.js.map
|