@zip.js/zip.js 2.13.1 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/BENCHMARKS.md +46 -1
  2. package/deno.json +1 -1
  3. package/dist/zip-core-external.js +559 -924
  4. package/dist/zip-core-external.min.js +1 -1
  5. package/dist/zip-core.js +426 -897
  6. package/dist/zip-core.min.js +1 -1
  7. package/dist/zip-fs-core-external.js +566 -927
  8. package/dist/zip-fs-core-external.min.js +1 -1
  9. package/dist/zip-fs-core.js +433 -900
  10. package/dist/zip-fs-core.min.js +1 -1
  11. package/dist/zip-fs-external.js +566 -927
  12. package/dist/zip-fs-external.min.js +1 -1
  13. package/dist/zip-fs-native.js +435 -902
  14. package/dist/zip-fs-native.min.js +1 -1
  15. package/dist/zip-fs.js +569 -930
  16. package/dist/zip-fs.min.js +1 -1
  17. package/dist/zip-legacy.js +428 -899
  18. package/dist/zip-legacy.min.js +1 -1
  19. package/dist/zip-module.wasm +0 -0
  20. package/dist/zip-native.js +428 -899
  21. package/dist/zip-native.min.js +1 -1
  22. package/dist/zip-web-worker-native.js +1 -1
  23. package/dist/zip-web-worker.js +1 -1
  24. package/dist/zip.js +562 -927
  25. package/dist/zip.min.js +1 -1
  26. package/index-native.cjs +435 -902
  27. package/index-native.min.js +1 -1
  28. package/index.cjs +569 -930
  29. package/index.d.cts +52 -12
  30. package/index.d.ts +52 -12
  31. package/index.min.js +1 -1
  32. package/lib/core/codec-worker.js +33 -23
  33. package/lib/core/io.js +29 -21
  34. package/lib/core/streams/aes-crypto-stream.js +58 -99
  35. package/lib/core/streams/codecs/aes-hmac-sha1.js +348 -0
  36. package/lib/core/streams/zlib-wasm/aes-hmac-sha1-wasm.js +106 -0
  37. package/lib/core/streams/zlib-wasm/zlib-streams-loader.js +4 -0
  38. package/lib/core/streams/zlib-wasm/zlib-streams.wasm +0 -0
  39. package/lib/core/version.js +1 -1
  40. package/lib/core/web-worker-base.js +15 -9
  41. package/lib/core/web-worker-inline-native.js +1 -1
  42. package/lib/core/web-worker-inline-wasm.js +1 -1
  43. package/lib/core/web-worker-wasm.js +2 -0
  44. package/lib/core/zip-fs.js +7 -3
  45. package/lib/core/zip-writer.js +1 -2
  46. package/lib/core/zlib-streams-inline.js +1 -1
  47. package/lib/zip-module-wasm-base.js +3 -1
  48. package/package.json +1 -1
  49. package/lib/core/streams/codecs/sjcl.js +0 -795
@@ -78,11 +78,18 @@ async function supportsDeflate(config) {
78
78
  return true;
79
79
  }
80
80
  if (FallbackStream) {
81
+ return await loadModule(config);
82
+ }
83
+ return false;
84
+ }
85
+
86
+ async function loadModule(config) {
87
+ if (initModule) {
81
88
  try {
82
89
  await initModule(config);
83
90
  return true;
84
91
  } catch {
85
- return false;
92
+ // ignored
86
93
  }
87
94
  }
88
95
  return false;
@@ -125,26 +132,27 @@ class CodecWorker {
125
132
  terminate() {
126
133
  return new Promise(resolve => {
127
134
  const { worker, busy } = workerData;
128
- if (worker) {
129
- if (busy) {
130
- workerData.resolveTerminated = resolve;
131
- } else {
135
+ if (busy) {
136
+ workerData.resolveTerminated = resolve;
137
+ } else {
138
+ if (worker) {
132
139
  worker.terminate();
133
- resolve();
140
+ workerData.worker = null;
134
141
  }
135
- workerData.interface = null;
136
- } else {
137
142
  resolve();
138
143
  }
144
+ workerData.interface = null;
139
145
  });
140
146
  },
141
147
  onTaskFinished() {
142
148
  if (workerData.busy) {
143
- const { resolveTerminated } = workerData;
149
+ const { resolveTerminated, worker } = workerData;
144
150
  if (resolveTerminated) {
145
151
  workerData.resolveTerminated = null;
146
- workerData.terminated = true;
147
- workerData.worker.terminate();
152
+ if (worker) {
153
+ workerData.terminated = true;
154
+ worker.terminate();
155
+ }
148
156
  resolveTerminated();
149
157
  }
150
158
  workerData.busy = false;
@@ -201,28 +209,23 @@ function createWorkerInterface(workerData, config) {
201
209
  }
202
210
 
203
211
  async function runWorker({ options, readable, writable, onTaskFinished, workerOptions }, config) {
204
- let codecStream, chunkStream;
212
+ let codecStream, chunkStream, modulePromise;
205
213
  try {
206
214
  if (options.compressed && !options.format) {
207
215
  const deflate = options.codecType.startsWith(CODEC_DEFLATE);
208
216
  const FallbackStream = deflate ? config.CompressionStreamFallback : config.DecompressionStreamFallback;
209
217
  const NativeStream = deflate ? config.CompressionStream : config.DecompressionStream;
210
218
  if (!options.useCompressionStream) {
211
- try {
212
- await initModule(config);
213
- } catch {
214
- if (!FallbackStream || FallbackStream.requiresModule) {
215
- options.useCompressionStream = true;
216
- }
219
+ if (!await moduleLoaded() && (!FallbackStream || FallbackStream.requiresModule)) {
220
+ options.useCompressionStream = true;
217
221
  }
218
222
  } else if (FallbackStream && FallbackStream.requiresModule && !supportsDeflateRaw(NativeStream)) {
219
- try {
220
- await initModule(config);
221
- } catch {
222
- // ignored
223
- }
223
+ await moduleLoaded();
224
224
  }
225
225
  }
226
+ if (options.encrypted && !options.zipCrypto) {
227
+ await moduleLoaded();
228
+ }
226
229
  codecStream = new CodecStream(options, config);
227
230
  chunkStream = new ChunkStream(getChunkSize(config));
228
231
  await readable
@@ -255,4 +258,11 @@ async function runWorker({ options, readable, writable, onTaskFinished, workerOp
255
258
  } finally {
256
259
  onTaskFinished();
257
260
  }
261
+
262
+ function moduleLoaded() {
263
+ if (!modulePromise) {
264
+ modulePromise = loadModule(config);
265
+ }
266
+ return modulePromise;
267
+ }
258
268
  }
package/lib/core/io.js CHANGED
@@ -34,6 +34,7 @@ import {
34
34
  FUNCTION_TYPE,
35
35
  UNDEFINED_TYPE,
36
36
  END_OF_CENTRAL_DIR_LENGTH,
37
+ MAX_16_BITS,
37
38
  EMPTY_UINT8_ARRAY
38
39
  } from "./constants.js";
39
40
  import { getConfiguration, getChunkSize, normalizeChunkSize } from "./configuration.js";
@@ -63,6 +64,7 @@ const HTTP_METHOD_GET = "GET";
63
64
  const HTTP_RANGE_UNIT = "bytes";
64
65
  const DEFAULT_BUFFER_SIZE = 256 * 1024;
65
66
  const DEFAULT_MAXIMUM_RANGE_SIZE = 16 * 1024 * 1024;
67
+ const END_OF_CENTRAL_DIR_SEARCH_LENGTH = END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS;
66
68
 
67
69
  const PROPERTY_NAME_WRITABLE = "writable";
68
70
  const DISK_BOUNDARY = Symbol();
@@ -425,16 +427,14 @@ async function initHttpReader(httpReader, sendRequest, getRequestData) {
425
427
  combineSizeEocd
426
428
  } = httpReader;
427
429
  if (isHttpFamily(url) && (useRangeHeader || forceRangeRequests) && (typeof preventHeadRequest == UNDEFINED_TYPE || preventHeadRequest)) {
428
- const response = await sendRequest(HTTP_METHOD_GET, httpReader, getRangeHeaders(httpReader, combineSizeEocd ? -END_OF_CENTRAL_DIR_LENGTH : undefined));
430
+ const response = await sendRequest(HTTP_METHOD_GET, httpReader, getRangeHeaders(httpReader, combineSizeEocd ? -END_OF_CENTRAL_DIR_SEARCH_LENGTH : undefined));
429
431
  const acceptRanges = response.headers.get(HTTP_HEADER_ACCEPT_RANGES);
430
432
  if (!forceRangeRequests && (!acceptRanges || acceptRanges.toLowerCase() != HTTP_RANGE_UNIT)) {
431
433
  throw new Error(ERR_HTTP_RANGE);
432
434
  } else {
433
- if (combineSizeEocd) {
434
- const eocdCache = new Uint8Array(await response.arrayBuffer());
435
- if (response.status == 206 && eocdCache.length == END_OF_CENTRAL_DIR_LENGTH) {
436
- httpReader.eocdCache = eocdCache;
437
- }
435
+ let eocdCache;
436
+ if (combineSizeEocd && response.status == 206) {
437
+ eocdCache = new Uint8Array(await response.arrayBuffer());
438
438
  }
439
439
  setResourceValidators(httpReader, response);
440
440
  const contentSize = getContentRangeSize(response);
@@ -443,6 +443,9 @@ async function initHttpReader(httpReader, sendRequest, getRequestData) {
443
443
  } else {
444
444
  httpReader.size = contentSize;
445
445
  }
446
+ if (eocdCache && eocdCache.length && getContentRangeOffset(response) === httpReader.size - eocdCache.length) {
447
+ httpReader.eocdCache = eocdCache;
448
+ }
446
449
  }
447
450
  } else {
448
451
  await getContentLength(httpReader, sendRequest, getRequestData);
@@ -458,25 +461,23 @@ async function readUint8ArrayHttpReader(httpReader, index, length, sendRequest,
458
461
  options
459
462
  } = httpReader;
460
463
  if (useRangeHeader || forceRangeRequests) {
461
- if (eocdCache && index == size - END_OF_CENTRAL_DIR_LENGTH && length == END_OF_CENTRAL_DIR_LENGTH) {
462
- return eocdCache;
463
- }
464
464
  if (index >= size || length === 0) {
465
465
  return EMPTY_UINT8_ARRAY;
466
466
  } else {
467
467
  if (index + length > size) {
468
468
  length = size - index;
469
469
  }
470
+ if (eocdCache && index >= size - eocdCache.length) {
471
+ const cacheIndex = index - (size - eocdCache.length);
472
+ return eocdCache.slice(cacheIndex, cacheIndex + length);
473
+ }
470
474
  const response = await sendRequest(HTTP_METHOD_GET, httpReader, getRangeHeaders(httpReader, index, length));
471
475
  if (response.status != 206) {
472
476
  throw new Error(ERR_HTTP_RANGE);
473
477
  }
474
- const contentRangeHeader = response.headers.get(HTTP_HEADER_CONTENT_RANGE);
475
- if (contentRangeHeader) {
476
- const rangeStart = Number(contentRangeHeader.trim().split(/[\s-]+/)[1]);
477
- if (!Number.isNaN(rangeStart) && rangeStart != index) {
478
- throw new Error(ERR_HTTP_RANGE);
479
- }
478
+ const rangeStart = getContentRangeOffset(response);
479
+ if (rangeStart !== UNDEFINED_VALUE && rangeStart != index) {
480
+ throw new Error(ERR_HTTP_RANGE);
480
481
  }
481
482
  checkResourceValidators(httpReader, response);
482
483
  setResourceValidators(httpReader, response);
@@ -536,12 +537,9 @@ function createRangeReadable(httpReader, offset, size) {
536
537
  if (response.status != 206) {
537
538
  throw new Error(ERR_HTTP_RANGE);
538
539
  }
539
- const contentRangeHeader = response.headers.get(HTTP_HEADER_CONTENT_RANGE);
540
- if (contentRangeHeader) {
541
- const rangeStart = Number(contentRangeHeader.trim().split(/[\s-]+/)[1]);
542
- if (!Number.isNaN(rangeStart) && rangeStart != windowOffset) {
543
- throw new Error(ERR_HTTP_RANGE);
544
- }
540
+ const rangeStart = getContentRangeOffset(response);
541
+ if (rangeStart !== UNDEFINED_VALUE && rangeStart != windowOffset) {
542
+ throw new Error(ERR_HTTP_RANGE);
545
543
  }
546
544
  checkResourceValidators(httpReader, response);
547
545
  setResourceValidators(httpReader, response);
@@ -557,6 +555,16 @@ function createRangeReadable(httpReader, offset, size) {
557
555
  }
558
556
  }
559
557
 
558
+ function getContentRangeOffset(response) {
559
+ const contentRangeHeader = response.headers.get(HTTP_HEADER_CONTENT_RANGE);
560
+ if (contentRangeHeader) {
561
+ const rangeStart = Number(contentRangeHeader.trim().split(/[\s-]+/)[1]);
562
+ if (!Number.isNaN(rangeStart)) {
563
+ return rangeStart;
564
+ }
565
+ }
566
+ }
567
+
560
568
  function getContentRangeSize(response) {
561
569
  const contentRangeHeader = response.headers.get(HTTP_HEADER_CONTENT_RANGE);
562
570
  if (contentRangeHeader) {
@@ -7,8 +7,8 @@
7
7
  1. Redistributions of source code must retain the above copyright notice,
8
8
  this list of conditions and the following disclaimer.
9
9
 
10
- 2. Redistributions in binary form must reproduce the above copyright
11
- notice, this list of conditions and the following disclaimer in
10
+ 2. Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in
12
12
  the documentation and/or other materials provided with the distribution.
13
13
 
14
14
  3. The names of the authors may not be used to endorse or promote products
@@ -32,12 +32,7 @@
32
32
  import { UNDEFINED_VALUE, UNDEFINED_TYPE, FUNCTION_TYPE, EMPTY_UINT8_ARRAY } from "../constants.js";
33
33
  import { encodeText } from "./../util/encode-text.js";
34
34
  import { concat } from "./../util/array.js";
35
- import {
36
- cipher,
37
- codec,
38
- misc,
39
- mode
40
- } from "./codecs/sjcl.js";
35
+ import { createEngine as createDefaultEngine, pbkdf2 } from "./codecs/aes-hmac-sha1.js";
41
36
  import {
42
37
  ERR_INVALID_PASSWORD,
43
38
  ERR_INVALID_AUTHENTICATION_CODE,
@@ -50,24 +45,21 @@ const RAW_FORMAT = "raw";
50
45
  const PBKDF2_ALGORITHM = { name: "PBKDF2" };
51
46
  const HASH_ALGORITHM = { name: "HMAC" };
52
47
  const HASH_FUNCTION = "SHA-1";
48
+ const PBKDF2_ITERATIONS = 1000;
53
49
  const BASE_KEY_ALGORITHM = Object.assign({ hash: HASH_ALGORITHM }, PBKDF2_ALGORITHM);
54
- const DERIVED_BITS_ALGORITHM = Object.assign({ iterations: 1000, hash: { name: HASH_FUNCTION } }, PBKDF2_ALGORITHM);
50
+ const DERIVED_BITS_ALGORITHM = Object.assign({ iterations: PBKDF2_ITERATIONS, hash: { name: HASH_FUNCTION } }, PBKDF2_ALGORITHM);
55
51
  const DERIVED_BITS_USAGE = ["deriveBits"];
56
52
  const SALT_LENGTH = [8, 12, 16];
57
53
  const KEY_LENGTH = [16, 24, 32];
58
54
  const AUTHENTICATION_CODE_LENGTH = 10;
59
- const COUNTER_DEFAULT_VALUE = [0, 0, 0, 0];
55
+ const PASSWORD_VERIFICATION_LENGTH = 2;
60
56
  // deno-lint-ignore valid-typeof
61
57
  const CRYPTO_API_SUPPORTED = typeof crypto != UNDEFINED_TYPE;
62
58
  const subtle = CRYPTO_API_SUPPORTED && crypto.subtle;
63
59
  const SUBTLE_API_SUPPORTED = CRYPTO_API_SUPPORTED && typeof subtle != UNDEFINED_TYPE;
64
- const codecBytes = codec.bytes;
65
- const Aes = cipher.aes;
66
- const CtrGladman = mode.ctrGladman;
67
- const HmacSha1 = misc.hmacSha1;
68
60
 
69
- let IMPORT_KEY_SUPPORTED = CRYPTO_API_SUPPORTED && SUBTLE_API_SUPPORTED && typeof subtle.importKey == FUNCTION_TYPE;
70
- let DERIVE_BITS_SUPPORTED = CRYPTO_API_SUPPORTED && SUBTLE_API_SUPPORTED && typeof subtle.deriveBits == FUNCTION_TYPE;
61
+ let DERIVE_BITS_SUPPORTED = SUBTLE_API_SUPPORTED && typeof subtle.importKey == FUNCTION_TYPE && typeof subtle.deriveBits == FUNCTION_TYPE;
62
+ let createEngine = createDefaultEngine;
71
63
 
72
64
  class AESDecryptionStream extends TransformStream {
73
65
 
@@ -85,9 +77,10 @@ class AESDecryptionStream extends TransformStream {
85
77
  ready
86
78
  } = aesCrypto;
87
79
  if (password) {
88
- await createDecryptionKeys(aesCrypto, strength, password, subarray(chunk, 0, SALT_LENGTH[strength] + 2));
89
- chunk = subarray(chunk, SALT_LENGTH[strength] + 2);
80
+ await createDecryptionKeys(aesCrypto, strength, password, subarray(chunk, 0, SALT_LENGTH[strength] + PASSWORD_VERIFICATION_LENGTH));
81
+ chunk = subarray(chunk, SALT_LENGTH[strength] + PASSWORD_VERIFICATION_LENGTH);
90
82
  if (checkPasswordOnly) {
83
+ disposeEngine(aesCrypto);
91
84
  controller.error(new Error(ERR_ABORT_CHECK_PASSWORD));
92
85
  } else {
93
86
  resolveReady();
@@ -100,23 +93,16 @@ class AESDecryptionStream extends TransformStream {
100
93
  },
101
94
  async flush(controller) {
102
95
  const {
103
- ctr,
104
- hmac,
96
+ engine,
105
97
  pendingInput,
106
98
  ready
107
99
  } = this;
108
- if (hmac && ctr) {
100
+ if (engine) {
109
101
  await ready;
110
- const chunkToDecrypt = subarray(pendingInput, 0, pendingInput.length - AUTHENTICATION_CODE_LENGTH);
111
102
  const originalAuthenticationCode = subarray(pendingInput, pendingInput.length - AUTHENTICATION_CODE_LENGTH);
112
- let decryptedChunkArray = EMPTY_UINT8_ARRAY;
113
- if (chunkToDecrypt.length) {
114
- const encryptedChunk = toBits(codecBytes, chunkToDecrypt);
115
- hmac.update(encryptedChunk);
116
- const decryptedChunk = ctr.update(encryptedChunk);
117
- decryptedChunkArray = fromBits(codecBytes, decryptedChunk);
118
- }
119
- const authenticationCode = subarray(fromBits(codecBytes, hmac.digest()), 0, AUTHENTICATION_CODE_LENGTH);
103
+ const decryptedChunkArray = new Uint8Array(subarray(pendingInput, 0, pendingInput.length - AUTHENTICATION_CODE_LENGTH));
104
+ engine.process(decryptedChunkArray, true);
105
+ const authenticationCode = engine.digest();
120
106
  let invalidAuthenticationCode = pendingInput.length < AUTHENTICATION_CODE_LENGTH ? 1 : 0;
121
107
  for (let indexByte = 0; indexByte < AUTHENTICATION_CODE_LENGTH; indexByte++) {
122
108
  invalidAuthenticationCode |= authenticationCode[indexByte] ^ originalAuthenticationCode[indexByte];
@@ -126,6 +112,9 @@ class AESDecryptionStream extends TransformStream {
126
112
  }
127
113
  controller.enqueue(decryptedChunkArray);
128
114
  }
115
+ },
116
+ cancel() {
117
+ disposeEngine(this);
129
118
  }
130
119
  });
131
120
  }
@@ -155,26 +144,24 @@ class AESEncryptionStream extends TransformStream {
155
144
  }
156
145
  const output = new Uint8Array(preamble.length + chunk.length - (chunk.length % BLOCK_LENGTH));
157
146
  output.set(preamble, 0);
158
- controller.enqueue(append(aesCrypto, chunk, output, preamble.length, 0));
147
+ controller.enqueue(append(aesCrypto, chunk, output, preamble.length, 0, false));
159
148
  },
160
149
  async flush(controller) {
161
150
  const {
162
- ctr,
163
- hmac,
151
+ engine,
164
152
  pendingInput,
165
153
  ready
166
154
  } = this;
167
- if (hmac && ctr) {
155
+ if (engine) {
168
156
  await ready;
169
- let encryptedChunkArray = EMPTY_UINT8_ARRAY;
170
- if (pendingInput.length) {
171
- const encryptedChunk = ctr.update(toBits(codecBytes, pendingInput));
172
- hmac.update(encryptedChunk);
173
- encryptedChunkArray = fromBits(codecBytes, encryptedChunk);
174
- }
175
- const authenticationCode = fromBits(codecBytes, hmac.digest()).slice(0, AUTHENTICATION_CODE_LENGTH);
157
+ const encryptedChunkArray = new Uint8Array(pendingInput);
158
+ engine.process(encryptedChunkArray, false);
159
+ const authenticationCode = subarray(engine.digest(), 0, AUTHENTICATION_CODE_LENGTH);
176
160
  controller.enqueue(concat(encryptedChunkArray, authenticationCode));
177
161
  }
162
+ },
163
+ cancel() {
164
+ disposeEngine(this);
178
165
  }
179
166
  });
180
167
  }
@@ -182,9 +169,14 @@ class AESEncryptionStream extends TransformStream {
182
169
 
183
170
  export {
184
171
  AESDecryptionStream,
185
- AESEncryptionStream
172
+ AESEncryptionStream,
173
+ setAESEngine
186
174
  };
187
175
 
176
+ function setAESEngine(createEngineFunction) {
177
+ createEngine = createEngineFunction || createDefaultEngine;
178
+ }
179
+
188
180
  function initAesCrypto(aesCrypto, password, rawPassword, encryptionStrength) {
189
181
  Object.assign(aesCrypto, {
190
182
  ready: new Promise(resolve => aesCrypto.resolveReady = resolve),
@@ -194,30 +186,23 @@ function initAesCrypto(aesCrypto, password, rawPassword, encryptionStrength) {
194
186
  });
195
187
  }
196
188
 
197
- function append(aesCrypto, input, output, paddingStart, paddingEnd, verifyAuthenticationCode) {
189
+ function append(aesCrypto, input, output, paddingStart, paddingEnd, decrypt) {
198
190
  const {
199
- ctr,
200
- hmac,
191
+ engine,
201
192
  pendingInput
202
193
  } = aesCrypto;
203
194
  if (pendingInput.length) {
204
195
  input = concat(pendingInput, input);
205
196
  }
206
197
  const inputLength = input.length - paddingEnd;
207
- output = expand(output, paddingStart + (inputLength - (inputLength % BLOCK_LENGTH)));
208
- let offset;
209
- for (offset = 0; offset <= inputLength - BLOCK_LENGTH; offset += BLOCK_LENGTH) {
210
- const inputChunk = toBits(codecBytes, subarray(input, offset, offset + BLOCK_LENGTH));
211
- if (verifyAuthenticationCode) {
212
- hmac.update(inputChunk);
213
- }
214
- const outputChunk = ctr.update(inputChunk);
215
- if (!verifyAuthenticationCode) {
216
- hmac.update(outputChunk);
217
- }
218
- output.set(fromBits(codecBytes, outputChunk), offset + paddingStart);
198
+ const alignedLength = inputLength - (inputLength % BLOCK_LENGTH);
199
+ output = expand(output, paddingStart + alignedLength);
200
+ if (alignedLength) {
201
+ const chunk = subarray(output, paddingStart, paddingStart + alignedLength);
202
+ chunk.set(subarray(input, 0, alignedLength));
203
+ engine.process(chunk, decrypt);
219
204
  }
220
- aesCrypto.pendingInput = subarray(input, offset);
205
+ aesCrypto.pendingInput = subarray(input, alignedLength);
221
206
  return output;
222
207
  }
223
208
 
@@ -225,10 +210,17 @@ async function createDecryptionKeys(decrypt, strength, password, preamble) {
225
210
  const passwordVerificationKey = await createKeys(decrypt, strength, password, subarray(preamble, 0, SALT_LENGTH[strength]));
226
211
  const passwordVerification = subarray(preamble, SALT_LENGTH[strength]);
227
212
  if (passwordVerificationKey[0] != passwordVerification[0] || passwordVerificationKey[1] != passwordVerification[1]) {
213
+ disposeEngine(decrypt);
228
214
  throw new Error(ERR_INVALID_PASSWORD);
229
215
  }
230
216
  }
231
217
 
218
+ function disposeEngine({ engine }) {
219
+ if (engine && engine.dispose) {
220
+ engine.dispose();
221
+ }
222
+ }
223
+
232
224
  async function createEncryptionKeys(encrypt, strength, password) {
233
225
  const salt = getRandomValues(new Uint8Array(SALT_LENGTH[strength]));
234
226
  const passwordVerification = await createKeys(encrypt, strength, password, salt);
@@ -237,48 +229,22 @@ async function createEncryptionKeys(encrypt, strength, password) {
237
229
 
238
230
  async function createKeys(aesCrypto, strength, password, salt) {
239
231
  aesCrypto.password = null;
240
- const baseKey = await importKey(RAW_FORMAT, password, BASE_KEY_ALGORITHM, false, DERIVED_BITS_USAGE);
241
- const derivedBits = await deriveBits(Object.assign({ salt }, DERIVED_BITS_ALGORITHM), baseKey, 8 * ((KEY_LENGTH[strength] * 2) + 2));
242
- const compositeKey = new Uint8Array(derivedBits);
243
- const key = toBits(codecBytes, subarray(compositeKey, 0, KEY_LENGTH[strength]));
244
- const authentication = toBits(codecBytes, subarray(compositeKey, KEY_LENGTH[strength], KEY_LENGTH[strength] * 2));
245
- const passwordVerification = subarray(compositeKey, KEY_LENGTH[strength] * 2);
246
- Object.assign(aesCrypto, {
247
- keys: {
248
- key,
249
- authentication,
250
- passwordVerification
251
- },
252
- ctr: new CtrGladman(new Aes(key), Array.from(COUNTER_DEFAULT_VALUE)),
253
- hmac: new HmacSha1(authentication)
254
- });
255
- return passwordVerification;
256
- }
257
-
258
- async function importKey(format, password, algorithm, extractable, keyUsages) {
259
- if (IMPORT_KEY_SUPPORTED) {
260
- try {
261
- return await subtle.importKey(format, password, algorithm, extractable, keyUsages);
262
- } catch {
263
- IMPORT_KEY_SUPPORTED = false;
264
- return misc.importKey(password);
265
- }
266
- } else {
267
- return misc.importKey(password);
268
- }
232
+ const keyLength = KEY_LENGTH[strength];
233
+ const compositeKey = await deriveKey(password, salt, keyLength * 2 + PASSWORD_VERIFICATION_LENGTH);
234
+ aesCrypto.engine = createEngine(subarray(compositeKey, 0, keyLength), subarray(compositeKey, keyLength, keyLength * 2));
235
+ return subarray(compositeKey, keyLength * 2);
269
236
  }
270
237
 
271
- async function deriveBits(algorithm, baseKey, length) {
238
+ async function deriveKey(password, salt, length) {
272
239
  if (DERIVE_BITS_SUPPORTED) {
273
240
  try {
274
- return await subtle.deriveBits(algorithm, baseKey, length);
241
+ const baseKey = await subtle.importKey(RAW_FORMAT, password, BASE_KEY_ALGORITHM, false, DERIVED_BITS_USAGE);
242
+ return new Uint8Array(await subtle.deriveBits(Object.assign({ salt }, DERIVED_BITS_ALGORITHM), baseKey, length * 8));
275
243
  } catch {
276
244
  DERIVE_BITS_SUPPORTED = false;
277
- return misc.pbkdf2(baseKey, algorithm.salt, DERIVED_BITS_ALGORITHM.iterations, length);
278
245
  }
279
- } else {
280
- return misc.pbkdf2(baseKey, algorithm.salt, DERIVED_BITS_ALGORITHM.iterations, length);
281
246
  }
247
+ return pbkdf2(password, salt, PBKDF2_ITERATIONS, length);
282
248
  }
283
249
 
284
250
  function encodePassword(password, rawPassword) {
@@ -301,10 +267,3 @@ function expand(inputArray, length) {
301
267
  function subarray(array, begin, end) {
302
268
  return array.subarray(begin, end);
303
269
  }
304
-
305
- function fromBits(codecBytes, chunk) {
306
- return codecBytes.fromBits(chunk);
307
- }
308
- function toBits(codecBytes, chunk) {
309
- return codecBytes.toBits(chunk);
310
- }