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.
- package/QuickCrypto.podspec +6 -47
- package/README.md +1 -1
- package/android/CMakeLists.txt +4 -0
- package/cpp/cipher/HybridCipher.cpp +17 -1
- package/cpp/ed25519/HybridEdKeyPair.cpp +8 -2
- package/cpp/hkdf/HybridHkdf.cpp +96 -0
- package/cpp/hkdf/HybridHkdf.hpp +28 -0
- package/cpp/scrypt/HybridScrypt.cpp +62 -0
- package/cpp/scrypt/HybridScrypt.hpp +28 -0
- package/lib/commonjs/ed.js +68 -0
- package/lib/commonjs/ed.js.map +1 -1
- package/lib/commonjs/hkdf.js +81 -0
- package/lib/commonjs/hkdf.js.map +1 -0
- package/lib/commonjs/index.js +33 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/scrypt.js +98 -0
- package/lib/commonjs/scrypt.js.map +1 -0
- package/lib/commonjs/specs/hkdf.nitro.js +6 -0
- package/lib/commonjs/specs/hkdf.nitro.js.map +1 -0
- package/lib/commonjs/specs/scrypt.nitro.js +6 -0
- package/lib/commonjs/specs/scrypt.nitro.js.map +1 -0
- package/lib/commonjs/subtle.js +400 -7
- 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/hkdf.js +75 -0
- package/lib/module/hkdf.js.map +1 -0
- package/lib/module/index.js +13 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/scrypt.js +93 -0
- package/lib/module/scrypt.js.map +1 -0
- package/lib/module/specs/hkdf.nitro.js +4 -0
- package/lib/module/specs/hkdf.nitro.js.map +1 -0
- package/lib/module/specs/scrypt.nitro.js +4 -0
- package/lib/module/specs/scrypt.nitro.js.map +1 -0
- package/lib/module/subtle.js +401 -8
- 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/hkdf.d.ts +26 -0
- package/lib/typescript/hkdf.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +11 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/scrypt.d.ts +18 -0
- package/lib/typescript/scrypt.d.ts.map +1 -0
- package/lib/typescript/specs/hkdf.nitro.d.ts +9 -0
- package/lib/typescript/specs/hkdf.nitro.d.ts.map +1 -0
- package/lib/typescript/specs/scrypt.nitro.d.ts +9 -0
- package/lib/typescript/specs/scrypt.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 +9 -3
- package/lib/typescript/utils/types.d.ts.map +1 -1
- package/nitrogen/generated/android/QuickCrypto+autolinking.cmake +2 -0
- package/nitrogen/generated/android/QuickCryptoOnLoad.cpp +20 -0
- package/nitrogen/generated/ios/QuickCryptoAutolinking.mm +20 -0
- package/nitrogen/generated/shared/c++/HybridHkdfSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridHkdfSpec.hpp +66 -0
- package/nitrogen/generated/shared/c++/HybridScryptSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridScryptSpec.hpp +65 -0
- package/package.json +1 -1
- package/src/ed.ts +102 -0
- package/src/hkdf.ts +152 -0
- package/src/index.ts +13 -1
- package/src/scrypt.ts +134 -0
- package/src/specs/hkdf.nitro.ts +19 -0
- package/src/specs/scrypt.nitro.ts +23 -0
- package/src/subtle.ts +564 -9
- package/src/utils/types.ts +16 -3
package/lib/commonjs/subtle.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
957
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|