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