react-native-quick-crypto 1.0.1 → 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 +5 -47
- package/cpp/cipher/HybridCipher.cpp +17 -1
- package/cpp/ed25519/HybridEdKeyPair.cpp +8 -2
- package/lib/commonjs/ed.js +68 -0
- package/lib/commonjs/ed.js.map +1 -1
- package/lib/commonjs/subtle.js +372 -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/subtle.js +373 -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/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- 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/package.json +1 -1
- package/src/ed.ts +102 -0
- package/src/subtle.ts +519 -9
- package/src/utils/types.ts +16 -3
package/lib/commonjs/subtle.js
CHANGED
|
@@ -271,6 +271,120 @@ async function aesGcmCipher(mode, key, data, algorithm) {
|
|
|
271
271
|
return result.buffer;
|
|
272
272
|
}
|
|
273
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
|
+
}
|
|
274
388
|
async function aesGenerateKey(algorithm, extractable, keyUsages) {
|
|
275
389
|
const {
|
|
276
390
|
length
|
|
@@ -558,7 +672,9 @@ function edImportKey(format, data, algorithm, extractable, keyUsages) {
|
|
|
558
672
|
} = algorithm;
|
|
559
673
|
|
|
560
674
|
// Validate usages
|
|
561
|
-
|
|
675
|
+
const isX = name === 'X25519' || name === 'X448';
|
|
676
|
+
const allowedUsages = isX ? ['deriveKey', 'deriveBits'] : ['sign', 'verify'];
|
|
677
|
+
if (hasAnyNotIn(keyUsages, allowedUsages)) {
|
|
562
678
|
throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError');
|
|
563
679
|
}
|
|
564
680
|
let keyObject;
|
|
@@ -631,8 +747,12 @@ const exportKeySpki = async key => {
|
|
|
631
747
|
case 'Ed25519':
|
|
632
748
|
// Fall through
|
|
633
749
|
case 'Ed448':
|
|
750
|
+
// Fall through
|
|
751
|
+
case 'X25519':
|
|
752
|
+
// Fall through
|
|
753
|
+
case 'X448':
|
|
634
754
|
if (key.type === 'public') {
|
|
635
|
-
// Export Ed key in SPKI DER format
|
|
755
|
+
// Export Ed/X key in SPKI DER format
|
|
636
756
|
return (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.SPKI));
|
|
637
757
|
}
|
|
638
758
|
break;
|
|
@@ -670,8 +790,12 @@ const exportKeyPkcs8 = async key => {
|
|
|
670
790
|
case 'Ed25519':
|
|
671
791
|
// Fall through
|
|
672
792
|
case 'Ed448':
|
|
793
|
+
// Fall through
|
|
794
|
+
case 'X25519':
|
|
795
|
+
// Fall through
|
|
796
|
+
case 'X448':
|
|
673
797
|
if (key.type === 'private') {
|
|
674
|
-
// Export Ed key in PKCS8 DER format
|
|
798
|
+
// Export Ed/X key in PKCS8 DER format
|
|
675
799
|
return (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.PKCS8));
|
|
676
800
|
}
|
|
677
801
|
break;
|
|
@@ -697,6 +821,19 @@ const exportKeyRaw = key => {
|
|
|
697
821
|
return ecExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatRaw);
|
|
698
822
|
}
|
|
699
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;
|
|
700
837
|
case 'AES-CTR':
|
|
701
838
|
// Fall through
|
|
702
839
|
case 'AES-CBC':
|
|
@@ -705,6 +842,8 @@ const exportKeyRaw = key => {
|
|
|
705
842
|
// Fall through
|
|
706
843
|
case 'AES-KW':
|
|
707
844
|
// Fall through
|
|
845
|
+
case 'ChaCha20-Poly1305':
|
|
846
|
+
// Fall through
|
|
708
847
|
case 'HMAC':
|
|
709
848
|
{
|
|
710
849
|
const exported = key.keyObject.export();
|
|
@@ -744,6 +883,8 @@ const exportKeyJWK = key => {
|
|
|
744
883
|
case 'AES-GCM':
|
|
745
884
|
// Fall through
|
|
746
885
|
case 'AES-KW':
|
|
886
|
+
// Fall through
|
|
887
|
+
case 'ChaCha20-Poly1305':
|
|
747
888
|
if (key.algorithm.length === undefined) {
|
|
748
889
|
throw (0, _errors.lazyDOMException)(`Algorithm ${key.algorithm.name} missing required length property`, 'InvalidAccessError');
|
|
749
890
|
}
|
|
@@ -941,6 +1082,10 @@ const cipherOrWrap = async (mode, algorithm, key, data, op) => {
|
|
|
941
1082
|
// Fall through
|
|
942
1083
|
case 'AES-GCM':
|
|
943
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);
|
|
944
1089
|
}
|
|
945
1090
|
};
|
|
946
1091
|
class Subtle {
|
|
@@ -953,16 +1098,49 @@ class Subtle {
|
|
|
953
1098
|
return (0, _hash.asyncDigest)(normalizedAlgorithm, data);
|
|
954
1099
|
}
|
|
955
1100
|
async deriveBits(algorithm, baseKey, length) {
|
|
956
|
-
|
|
957
|
-
|
|
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');
|
|
958
1104
|
}
|
|
959
1105
|
if (baseKey.algorithm.name !== algorithm.name) throw new Error('Key algorithm mismatch');
|
|
960
1106
|
switch (algorithm.name) {
|
|
961
1107
|
case 'PBKDF2':
|
|
962
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);
|
|
963
1113
|
}
|
|
964
1114
|
throw new Error(`'subtle.deriveBits()' for ${algorithm.name} is not implemented.`);
|
|
965
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
|
+
}
|
|
966
1144
|
async encrypt(algorithm, key, data) {
|
|
967
1145
|
const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'encrypt');
|
|
968
1146
|
return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherEncrypt, normalizedAlgorithm, key, (0, _conversion.bufferLikeToArrayBuffer)(data), 'encrypt');
|
|
@@ -980,6 +1158,76 @@ class Subtle {
|
|
|
980
1158
|
return exportKeyRaw(key);
|
|
981
1159
|
}
|
|
982
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
|
+
}
|
|
983
1231
|
async generateKey(algorithm, extractable, keyUsages) {
|
|
984
1232
|
algorithm = normalizeAlgorithm(algorithm, 'generateKey');
|
|
985
1233
|
let result;
|
|
@@ -1006,6 +1254,18 @@ class Subtle {
|
|
|
1006
1254
|
case 'AES-KW':
|
|
1007
1255
|
result = await aesGenerateKey(algorithm, extractable, keyUsages);
|
|
1008
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
|
+
}
|
|
1009
1269
|
case 'HMAC':
|
|
1010
1270
|
result = await hmacGenerateKey(algorithm, extractable, keyUsages);
|
|
1011
1271
|
break;
|
|
@@ -1023,6 +1283,12 @@ class Subtle {
|
|
|
1023
1283
|
result = await (0, _mldsa.mldsa_generateKeyPairWebCrypto)(algorithm.name, extractable, keyUsages);
|
|
1024
1284
|
checkCryptoKeyPairUsages(result);
|
|
1025
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;
|
|
1026
1292
|
default:
|
|
1027
1293
|
throw new Error(`'subtle.generateKey()' is not implemented for ${algorithm.name}.
|
|
1028
1294
|
Unrecognized algorithm name`);
|
|
@@ -1055,11 +1321,17 @@ class Subtle {
|
|
|
1055
1321
|
case 'AES-GCM':
|
|
1056
1322
|
// Fall through
|
|
1057
1323
|
case 'AES-KW':
|
|
1324
|
+
// Fall through
|
|
1325
|
+
case 'ChaCha20-Poly1305':
|
|
1058
1326
|
result = await aesImportKey(normalizedAlgorithm, format, data, extractable, keyUsages);
|
|
1059
1327
|
break;
|
|
1060
1328
|
case 'PBKDF2':
|
|
1061
1329
|
result = await importGenericSecretKey(normalizedAlgorithm, format, data, extractable, keyUsages);
|
|
1062
1330
|
break;
|
|
1331
|
+
case 'X25519':
|
|
1332
|
+
// Fall through
|
|
1333
|
+
case 'X448':
|
|
1334
|
+
// Fall through
|
|
1063
1335
|
case 'Ed25519':
|
|
1064
1336
|
// Fall through
|
|
1065
1337
|
case 'Ed448':
|
|
@@ -1081,12 +1353,105 @@ class Subtle {
|
|
|
1081
1353
|
return result;
|
|
1082
1354
|
}
|
|
1083
1355
|
async sign(algorithm, key, data) {
|
|
1084
|
-
|
|
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);
|
|
1085
1387
|
}
|
|
1086
1388
|
async verify(algorithm, key, signature, data) {
|
|
1087
|
-
|
|
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);
|
|
1088
1435
|
}
|
|
1089
1436
|
}
|
|
1090
1437
|
exports.Subtle = Subtle;
|
|
1091
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
|
+
}
|
|
1092
1457
|
//# sourceMappingURL=subtle.js.map
|