@omnituum/pqc-shared 0.4.0 → 0.6.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 (40) hide show
  1. package/dist/crypto/index.cjs +91 -54
  2. package/dist/crypto/index.d.cts +43 -11
  3. package/dist/crypto/index.d.ts +43 -11
  4. package/dist/crypto/index.js +87 -55
  5. package/dist/{decrypt-eSHlbh1j.d.cts → decrypt-O1iqFIDL.d.cts} +80 -6
  6. package/dist/{decrypt-eSHlbh1j.d.ts → decrypt-O1iqFIDL.d.ts} +80 -6
  7. package/dist/fs/index.cjs +286 -147
  8. package/dist/fs/index.d.cts +28 -7
  9. package/dist/fs/index.d.ts +28 -7
  10. package/dist/fs/index.js +277 -147
  11. package/dist/index.cjs +368 -229
  12. package/dist/index.d.cts +4 -4
  13. package/dist/index.d.ts +4 -4
  14. package/dist/index.js +362 -230
  15. package/dist/{integrity-ByMp24VA.d.cts → integrity-BkBDrHA-.d.cts} +12 -4
  16. package/dist/{integrity-BPvNsC50.d.ts → integrity-DsFJv5c-.d.ts} +12 -4
  17. package/dist/{types-Dx_AFqow.d.cts → types-DmnVueAY.d.cts} +15 -5
  18. package/dist/{types-Dx_AFqow.d.ts → types-DmnVueAY.d.ts} +15 -5
  19. package/dist/utils/index.cjs +7 -10
  20. package/dist/utils/index.d.cts +2 -2
  21. package/dist/utils/index.d.ts +2 -2
  22. package/dist/utils/index.js +7 -10
  23. package/dist/vault/index.cjs +8 -11
  24. package/dist/vault/index.d.cts +2 -2
  25. package/dist/vault/index.d.ts +2 -2
  26. package/dist/vault/index.js +8 -11
  27. package/package.json +19 -25
  28. package/src/crypto/dilithium.ts +4 -4
  29. package/src/crypto/hybrid.ts +182 -80
  30. package/src/crypto/index.ts +8 -1
  31. package/src/crypto/kyber.ts +20 -0
  32. package/src/fs/decrypt.ts +145 -68
  33. package/src/fs/encrypt.ts +161 -112
  34. package/src/fs/format.ts +110 -15
  35. package/src/fs/index.ts +4 -0
  36. package/src/fs/types.ts +77 -6
  37. package/src/index.ts +9 -0
  38. package/src/utils/integrity.ts +16 -15
  39. package/src/vault/manager.ts +1 -1
  40. package/src/version.ts +29 -7
package/dist/index.cjs CHANGED
@@ -155,6 +155,11 @@ function deriveKeyFromShared(shared, salt, info) {
155
155
  return hkdfSha256(shared, { salt: u8(salt), info: u8(info), length: 32 });
156
156
  }
157
157
  var KYBER_SUITE = "ML-KEM-1024-FIPS203";
158
+ var KYBER_PUBLIC_KEY_SIZE = 1568;
159
+ var KYBER_SECRET_KEY_SIZE = 3168;
160
+ var KYBER_CIPHERTEXT_SIZE = 1568;
161
+ var KYBER_SHARED_SECRET_SIZE = 32;
162
+ var KYBER_SEED_SIZE = 64;
158
163
  async function isKyberAvailable() {
159
164
  return true;
160
165
  }
@@ -202,17 +207,20 @@ function kyberUnwrapKey(sharedSecret, nonceB64, wrappedB64) {
202
207
  return nacl4__default.default.secretbox.open(wrapped, nonce, kek) || null;
203
208
  }
204
209
  var ENVELOPE_VERSION = envelopeRegistry.OMNI_VERSIONS.HYBRID_V1;
210
+ var ENVELOPE_VERSION_V2 = envelopeRegistry.OMNI_VERSIONS.HYBRID_V2;
205
211
  var ENVELOPE_VERSION_LEGACY = envelopeRegistry.DEPRECATED_VERSIONS.PQC_DEMO_HYBRID_V1;
206
212
  var VAULT_VERSION = "omnituum.vault.v1";
207
213
  var VAULT_ENCRYPTED_VERSION = "omnituum.vault.enc.v1";
208
214
  var VAULT_ENCRYPTED_VERSION_V2 = "omnituum.vault.enc.v2";
209
215
  var ENVELOPE_SUITE = "x25519+kyber768";
216
+ var ENVELOPE_SUITE_V2 = "x25519+mlkem1024";
210
217
  var ENVELOPE_AEAD = "xsalsa20poly1305";
211
218
  var VAULT_KDF = "PBKDF2-SHA256";
212
219
  var VAULT_KDF_V2 = "Argon2id";
213
220
  var VAULT_ALGORITHM = "AES-256-GCM";
214
221
  var SUPPORTED_ENVELOPE_VERSIONS = [
215
222
  ENVELOPE_VERSION,
223
+ ENVELOPE_VERSION_V2,
216
224
  ENVELOPE_VERSION_LEGACY
217
225
  ];
218
226
  var SUPPORTED_VAULT_VERSIONS = [
@@ -257,6 +265,8 @@ function isVaultVersionSupported(version) {
257
265
  function isVaultEncryptedVersionSupported(version) {
258
266
  return SUPPORTED_VAULT_ENCRYPTED_VERSIONS.includes(version);
259
267
  }
268
+ var ENVELOPE_V1_FIELDS = ["x25519Epk", "x25519Wrap", "kyberKemCt", "kyberWrap", "contentNonce", "ciphertext", "meta"];
269
+ var ENVELOPE_V2_FIELDS = ["x25519Epk", "kyberKemCt", "ckWrap", "contentNonce", "ciphertext", "meta"];
260
270
  function validateEnvelope(envelope) {
261
271
  const errors = [];
262
272
  if (!envelope || typeof envelope !== "object") {
@@ -268,14 +278,16 @@ function validateEnvelope(envelope) {
268
278
  } else if (!isEnvelopeVersionSupported(env.v)) {
269
279
  errors.push(`Unsupported envelope version: ${env.v}`);
270
280
  }
271
- if (env.suite !== ENVELOPE_SUITE) {
272
- errors.push(`Invalid suite: expected "${ENVELOPE_SUITE}", got "${env.suite}"`);
281
+ const isV2 = env.v === ENVELOPE_VERSION_V2;
282
+ const expectedSuite = isV2 ? ENVELOPE_SUITE_V2 : ENVELOPE_SUITE;
283
+ const requiredFields = isV2 ? ENVELOPE_V2_FIELDS : ENVELOPE_V1_FIELDS;
284
+ if (env.suite !== expectedSuite) {
285
+ errors.push(`Invalid suite: expected "${expectedSuite}", got "${env.suite}"`);
273
286
  }
274
287
  if (env.aead !== ENVELOPE_AEAD) {
275
288
  errors.push(`Invalid aead: expected "${ENVELOPE_AEAD}", got "${env.aead}"`);
276
289
  }
277
- const required = ["x25519Epk", "x25519Wrap", "kyberKemCt", "kyberWrap", "contentNonce", "ciphertext", "meta"];
278
- for (const field of required) {
290
+ for (const field of requiredFields) {
279
291
  if (!(field in env)) {
280
292
  errors.push(`Missing required field: ${field}`);
281
293
  }
@@ -370,7 +382,6 @@ async function generateHybridIdentity(name) {
370
382
  const x25519 = generateX25519Keypair();
371
383
  const kyber = await generateKyberKeypair();
372
384
  if (!kyber) {
373
- console.error("Kyber key generation failed - library not available");
374
385
  return null;
375
386
  }
376
387
  return {
@@ -396,49 +407,74 @@ function getSecretKeys(identity) {
396
407
  kyberSecB64: identity.kyberSecB64
397
408
  };
398
409
  }
410
+ function combinedKekV2(kyberShared, x25519Shared, x25519EpkHex, kyberKemCtB64) {
411
+ const ikm = new Uint8Array(kyberShared.length + x25519Shared.length);
412
+ ikm.set(kyberShared, 0);
413
+ ikm.set(x25519Shared, kyberShared.length);
414
+ try {
415
+ return hkdfFlex(ikm, "omnituum/hybrid-v2", `wrap-ck|${x25519EpkHex}|${kyberKemCtB64}`);
416
+ } finally {
417
+ ikm.fill(0);
418
+ }
419
+ }
399
420
  async function hybridEncrypt(plaintext, recipientPublicKeys, sender) {
400
421
  const pt = typeof plaintext === "string" ? textEncoder.encode(plaintext) : plaintext;
401
422
  const CK = rand32();
402
- const contentNonce = rand24();
403
- const ciphertext = nacl4__default.default.secretbox(pt, contentNonce, CK);
404
- const x25519EphKp = nacl4__default.default.box.keyPair();
405
- const recipientX25519Pk = fromHex(recipientPublicKeys.x25519PubHex);
406
- const x25519Shared = nacl4__default.default.scalarMult(x25519EphKp.secretKey, recipientX25519Pk);
407
- const x25519Kek = hkdfFlex(x25519Shared, "omnituum/x25519", "wrap-ck");
408
- const x25519WrapNonce = rand24();
409
- const x25519Wrapped = nacl4__default.default.secretbox(CK, x25519WrapNonce, x25519Kek);
410
- const kyberResult = await kyberEncapsulate(recipientPublicKeys.kyberPubB64);
411
- const kyberKek = hkdfFlex(kyberResult.sharedSecret, "omnituum/kyber", "wrap-ck");
412
- const kyberWrapNonce = rand24();
413
- const kyberWrapped = nacl4__default.default.secretbox(CK, kyberWrapNonce, kyberKek);
414
- return {
415
- v: ENVELOPE_VERSION,
416
- suite: ENVELOPE_SUITE,
417
- aead: ENVELOPE_AEAD,
418
- x25519Epk: toHex(x25519EphKp.publicKey),
419
- x25519Wrap: {
420
- nonce: b64(x25519WrapNonce),
421
- wrapped: b64(x25519Wrapped)
422
- },
423
- kyberKemCt: b64(kyberResult.ciphertext),
424
- kyberWrap: {
425
- nonce: b64(kyberWrapNonce),
426
- wrapped: b64(kyberWrapped)
427
- },
428
- contentNonce: b64(contentNonce),
429
- ciphertext: b64(ciphertext),
430
- meta: {
431
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
432
- senderName: sender?.name,
433
- senderId: sender?.id
434
- }
435
- };
423
+ try {
424
+ const contentNonce = rand24();
425
+ const ciphertext = nacl4__default.default.secretbox(pt, contentNonce, CK);
426
+ const x25519EphKp = nacl4__default.default.box.keyPair();
427
+ const recipientX25519Pk = fromHex(recipientPublicKeys.x25519PubHex);
428
+ const x25519Shared = nacl4__default.default.scalarMult(x25519EphKp.secretKey, recipientX25519Pk);
429
+ const x25519EpkHex = toHex(x25519EphKp.publicKey);
430
+ const kyberResult = await kyberEncapsulate(recipientPublicKeys.kyberPubB64);
431
+ const kyberKemCtB64 = b64(kyberResult.ciphertext);
432
+ const kek = combinedKekV2(kyberResult.sharedSecret, x25519Shared, x25519EpkHex, kyberKemCtB64);
433
+ const ckWrapNonce = rand24();
434
+ const ckWrapped = nacl4__default.default.secretbox(CK, ckWrapNonce, kek);
435
+ kek.fill(0);
436
+ x25519Shared.fill(0);
437
+ kyberResult.sharedSecret.fill(0);
438
+ x25519EphKp.secretKey.fill(0);
439
+ return {
440
+ v: ENVELOPE_VERSION_V2,
441
+ suite: ENVELOPE_SUITE_V2,
442
+ aead: ENVELOPE_AEAD,
443
+ x25519Epk: x25519EpkHex,
444
+ kyberKemCt: kyberKemCtB64,
445
+ ckWrap: {
446
+ nonce: b64(ckWrapNonce),
447
+ wrapped: b64(ckWrapped)
448
+ },
449
+ contentNonce: b64(contentNonce),
450
+ ciphertext: b64(ciphertext),
451
+ meta: {
452
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
453
+ senderName: sender?.name,
454
+ senderId: sender?.id
455
+ }
456
+ };
457
+ } finally {
458
+ CK.fill(0);
459
+ }
460
+ }
461
+ async function unwrapCkV2(envelope, secretKeys) {
462
+ const kyberShared = await kyberDecapsulate(envelope.kyberKemCt, secretKeys.kyberSecB64);
463
+ const ephPk = fromHex(envelope.x25519Epk);
464
+ const sk = fromHex(secretKeys.x25519SecHex);
465
+ const x25519Shared = nacl4__default.default.scalarMult(sk, ephPk);
466
+ const kek = combinedKekV2(kyberShared, x25519Shared, envelope.x25519Epk, envelope.kyberKemCt);
467
+ kyberShared.fill(0);
468
+ x25519Shared.fill(0);
469
+ const CK = nacl4__default.default.secretbox.open(ub64(envelope.ckWrap.wrapped), ub64(envelope.ckWrap.nonce), kek);
470
+ kek.fill(0);
471
+ if (!CK) {
472
+ throw new Error("Could not unwrap content key \u2014 combined-KEK authentication failed");
473
+ }
474
+ return CK;
436
475
  }
437
- async function hybridDecrypt(envelope, secretKeys) {
438
- const version = envelope.v;
439
- assertEnvelopeVersion(version);
476
+ async function unwrapCkV1(envelope, secretKeys, saltPrefix) {
440
477
  let CK = null;
441
- const saltPrefix = version === "pqc-demo.hybrid.v1" ? "pqc-demo" : "omnituum";
442
478
  try {
443
479
  const kyberShared = await kyberDecapsulate(envelope.kyberKemCt, secretKeys.kyberSecB64);
444
480
  const kyberKek = hkdfFlex(kyberShared, `${saltPrefix}/kyber`, "wrap-ck");
@@ -447,11 +483,7 @@ async function hybridDecrypt(envelope, secretKeys) {
447
483
  ub64(envelope.kyberWrap.nonce),
448
484
  kyberKek
449
485
  );
450
- if (CK) {
451
- console.log("[Hybrid] Decrypted using Kyber (post-quantum)");
452
- }
453
- } catch (e) {
454
- console.warn("[Hybrid] Kyber decapsulation failed:", e);
486
+ } catch {
455
487
  }
456
488
  if (!CK) {
457
489
  try {
@@ -464,21 +496,28 @@ async function hybridDecrypt(envelope, secretKeys) {
464
496
  ub64(envelope.x25519Wrap.nonce),
465
497
  x25519Kek
466
498
  );
467
- if (CK) {
468
- console.log("[Hybrid] Decrypted using X25519 (classical)");
469
- }
470
- } catch (e) {
471
- console.warn("[Hybrid] X25519 decryption failed:", e);
499
+ } catch {
472
500
  }
473
501
  }
474
502
  if (!CK) {
475
503
  throw new Error("Could not unwrap content key with either algorithm");
476
504
  }
505
+ return CK;
506
+ }
507
+ async function hybridDecrypt(envelope, secretKeys) {
508
+ const version = envelope.v;
509
+ assertEnvelopeVersion(version);
510
+ const CK = version === ENVELOPE_VERSION_V2 ? await unwrapCkV2(envelope, secretKeys) : await unwrapCkV1(
511
+ envelope,
512
+ secretKeys,
513
+ version === "pqc-demo.hybrid.v1" ? "pqc-demo" : "omnituum"
514
+ );
477
515
  const pt = nacl4__default.default.secretbox.open(
478
516
  ub64(envelope.ciphertext),
479
517
  ub64(envelope.contentNonce),
480
518
  CK
481
519
  );
520
+ CK.fill(0);
482
521
  if (!pt) {
483
522
  throw new Error("Content authentication failed");
484
523
  }
@@ -497,8 +536,7 @@ async function loadDilithium() {
497
536
  const { ml_dsa65 } = await import('@noble/post-quantum/ml-dsa.js');
498
537
  dilithiumModule = ml_dsa65;
499
538
  return dilithiumModule;
500
- } catch (e) {
501
- console.warn("[Dilithium] Failed to load @noble/post-quantum:", e);
539
+ } catch {
502
540
  return null;
503
541
  }
504
542
  }
@@ -516,8 +554,7 @@ async function generateDilithiumKeypair() {
516
554
  publicB64: toB64(kp.publicKey),
517
555
  secretB64: toB64(kp.secretKey)
518
556
  };
519
- } catch (e) {
520
- console.warn("[Dilithium] Key generation failed:", e);
557
+ } catch {
521
558
  return null;
522
559
  }
523
560
  }
@@ -856,17 +893,11 @@ function computeIntegrityHash(identities) {
856
893
  createdAt: i.createdAt,
857
894
  rotationCount: i.rotationCount
858
895
  }));
859
- const serialized = JSON.stringify(canonical, Object.keys(canonical[0] || {}).sort());
896
+ const serialized = JSON.stringify(canonical);
860
897
  return computeStringHash(serialized);
861
898
  }
862
899
  function computeStringHash(str) {
863
- let hash = 0;
864
- for (let i = 0; i < str.length; i++) {
865
- const char = str.charCodeAt(i);
866
- hash = (hash << 5) - hash + char;
867
- hash = hash & hash;
868
- }
869
- return Math.abs(hash).toString(16).padStart(16, "0");
900
+ return toHex(sha256(textEncoder.encode(str)));
870
901
  }
871
902
  async function computeHashAsync(data) {
872
903
  const encoder = new TextEncoder();
@@ -1117,7 +1148,6 @@ async function createIdentity(name) {
1117
1148
  const x25519 = generateX25519Keypair();
1118
1149
  const kyber = await generateKyberKeypair();
1119
1150
  if (!kyber) {
1120
- console.error("Kyber key generation failed");
1121
1151
  return null;
1122
1152
  }
1123
1153
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -1340,12 +1370,22 @@ async function migrateEncryptedVault(options) {
1340
1370
 
1341
1371
  // src/fs/types.ts
1342
1372
  var OQE_MAGIC = new Uint8Array([79, 81, 69, 70]);
1343
- var OQE_FORMAT_VERSION = 1;
1373
+ var OQE_FORMAT_VERSION_V1 = 1;
1374
+ var OQE_FORMAT_VERSION_V2 = 2;
1375
+ var SUPPORTED_OQE_VERSIONS = [OQE_FORMAT_VERSION_V1, OQE_FORMAT_VERSION_V2];
1344
1376
  var ALGORITHM_SUITES = {
1345
- /** Hybrid: X25519 ECDH + Kyber768 KEM + AES-256-GCM */
1377
+ /**
1378
+ * LEGACY (read-only): Hybrid X25519 + Kyber with independent per-primitive
1379
+ * wraps (either secret unwraps). Only appears in v1 files. Never written.
1380
+ */
1346
1381
  HYBRID_X25519_KYBER768_AES256GCM: 1,
1347
1382
  /** Password: Argon2id + AES-256-GCM */
1348
- PASSWORD_ARGON2ID_AES256GCM: 2
1383
+ PASSWORD_ARGON2ID_AES256GCM: 2,
1384
+ /**
1385
+ * Hybrid X25519 + ML-KEM-1024 with a single AND-combined KEK wrap
1386
+ * (HKDF(ss_mlkem || ss_x25519), transcript-bound). Written by v2.
1387
+ */
1388
+ HYBRID_X25519_MLKEM1024_AES256GCM: 3
1349
1389
  };
1350
1390
  var DEFAULT_ARGON2ID_PARAMS = {
1351
1391
  memoryCost: 65536,
@@ -1355,7 +1395,8 @@ var DEFAULT_ARGON2ID_PARAMS = {
1355
1395
  hashLength: 32,
1356
1396
  saltLength: 32
1357
1397
  };
1358
- var OQE_HEADER_SIZE = 30;
1398
+ var OQE_HEADER_SIZE_V1 = 30;
1399
+ var OQE_HEADER_SIZE_V2 = 42;
1359
1400
  var OQEError = class extends Error {
1360
1401
  constructor(code, message) {
1361
1402
  super(message);
@@ -1425,6 +1466,7 @@ function toArrayBuffer(arr) {
1425
1466
  }
1426
1467
  var AES_KEY_SIZE = 32;
1427
1468
  var AES_GCM_IV_SIZE = 12;
1469
+ var AES_GCM_TAG_SIZE = 16;
1428
1470
  async function importAesKey(keyBytes) {
1429
1471
  if (keyBytes.length !== AES_KEY_SIZE) {
1430
1472
  throw new Error(`AES key must be ${AES_KEY_SIZE} bytes, got ${keyBytes.length}`);
@@ -1448,7 +1490,7 @@ async function aesEncrypt(plaintext, key, iv, additionalData) {
1448
1490
  {
1449
1491
  name: "AES-GCM",
1450
1492
  iv: toArrayBuffer(ivBytes),
1451
- additionalData: void 0,
1493
+ additionalData: additionalData ? toArrayBuffer(additionalData) : void 0,
1452
1494
  tagLength: 128
1453
1495
  // 16 bytes
1454
1496
  },
@@ -1484,6 +1526,9 @@ async function aesDecrypt(ciphertext, key, iv, additionalData) {
1484
1526
  }
1485
1527
 
1486
1528
  // src/fs/format.ts
1529
+ function oqeHeaderSize(version) {
1530
+ return version === OQE_FORMAT_VERSION_V2 ? OQE_HEADER_SIZE_V2 : OQE_HEADER_SIZE_V1;
1531
+ }
1487
1532
  function writeUint32BE(value) {
1488
1533
  const buffer = new ArrayBuffer(4);
1489
1534
  new DataView(buffer).setUint32(0, value, false);
@@ -1501,7 +1546,8 @@ function readUint16BE(data, offset) {
1501
1546
  return new DataView(data.buffer, data.byteOffset + offset).getUint16(0, false);
1502
1547
  }
1503
1548
  function writeOQEHeader(header) {
1504
- const buffer = new Uint8Array(OQE_HEADER_SIZE);
1549
+ const isV2 = header.version === OQE_FORMAT_VERSION_V2;
1550
+ const buffer = new Uint8Array(oqeHeaderSize(header.version));
1505
1551
  let offset = 0;
1506
1552
  buffer.set(OQE_MAGIC, offset);
1507
1553
  offset += 4;
@@ -1514,11 +1560,18 @@ function writeOQEHeader(header) {
1514
1560
  buffer.set(writeUint32BE(header.keyMaterialLength), offset);
1515
1561
  offset += 4;
1516
1562
  buffer.set(header.iv, offset);
1563
+ offset += 12;
1564
+ if (isV2) {
1565
+ if (!header.contentIv || header.contentIv.length !== 12) {
1566
+ throw new OQEError("INVALID_HEADER", "v2 header requires a 12-byte contentIv");
1567
+ }
1568
+ buffer.set(header.contentIv, offset);
1569
+ }
1517
1570
  return buffer;
1518
1571
  }
1519
1572
  function parseOQEHeader(data) {
1520
- if (data.length < OQE_HEADER_SIZE) {
1521
- throw new OQEError("INVALID_HEADER", `File too small: need ${OQE_HEADER_SIZE} bytes, got ${data.length}`);
1573
+ if (data.length < OQE_HEADER_SIZE_V1) {
1574
+ throw new OQEError("INVALID_HEADER", `File too small: need at least ${OQE_HEADER_SIZE_V1} bytes, got ${data.length}`);
1522
1575
  }
1523
1576
  let offset = 0;
1524
1577
  const magic = data.slice(0, 4);
@@ -1527,14 +1580,19 @@ function parseOQEHeader(data) {
1527
1580
  }
1528
1581
  offset += 4;
1529
1582
  const version = data[offset++];
1530
- if (version !== OQE_FORMAT_VERSION) {
1583
+ if (!SUPPORTED_OQE_VERSIONS.includes(version)) {
1531
1584
  throw new OQEError("UNSUPPORTED_VERSION", `Unsupported OQE version: ${version}`);
1532
1585
  }
1533
1586
  const algorithmSuiteRaw = data[offset++];
1534
- if (algorithmSuiteRaw !== ALGORITHM_SUITES.HYBRID_X25519_KYBER768_AES256GCM && algorithmSuiteRaw !== ALGORITHM_SUITES.PASSWORD_ARGON2ID_AES256GCM) {
1587
+ if (algorithmSuiteRaw !== ALGORITHM_SUITES.HYBRID_X25519_KYBER768_AES256GCM && algorithmSuiteRaw !== ALGORITHM_SUITES.HYBRID_X25519_MLKEM1024_AES256GCM && algorithmSuiteRaw !== ALGORITHM_SUITES.PASSWORD_ARGON2ID_AES256GCM) {
1535
1588
  throw new OQEError("UNSUPPORTED_ALGORITHM", `Unsupported algorithm suite: 0x${algorithmSuiteRaw.toString(16)}`);
1536
1589
  }
1537
1590
  const algorithmSuite = algorithmSuiteRaw;
1591
+ const isV2 = version === OQE_FORMAT_VERSION_V2;
1592
+ const headerSize = oqeHeaderSize(version);
1593
+ if (data.length < headerSize) {
1594
+ throw new OQEError("INVALID_HEADER", `Truncated v${version} header: need ${headerSize} bytes, got ${data.length}`);
1595
+ }
1538
1596
  const flags = readUint32BE(data, offset);
1539
1597
  offset += 4;
1540
1598
  const metadataLength = readUint32BE(data, offset);
@@ -1542,35 +1600,18 @@ function parseOQEHeader(data) {
1542
1600
  const keyMaterialLength = readUint32BE(data, offset);
1543
1601
  offset += 4;
1544
1602
  const iv = data.slice(offset, offset + 12);
1603
+ offset += 12;
1604
+ const contentIv = isV2 ? data.slice(offset, offset + 12) : void 0;
1545
1605
  return {
1546
1606
  version,
1547
1607
  algorithmSuite,
1548
1608
  flags,
1549
1609
  metadataLength,
1550
1610
  keyMaterialLength,
1551
- iv
1611
+ iv,
1612
+ contentIv
1552
1613
  };
1553
1614
  }
1554
- function serializeHybridKeyMaterial(km) {
1555
- const parts = [];
1556
- parts.push(km.x25519EphemeralPk);
1557
- parts.push(km.x25519Nonce);
1558
- parts.push(writeUint16BE(km.x25519WrappedKey.length));
1559
- parts.push(km.x25519WrappedKey);
1560
- parts.push(writeUint16BE(km.kyberCiphertext.length));
1561
- parts.push(km.kyberCiphertext);
1562
- parts.push(km.kyberNonce);
1563
- parts.push(writeUint16BE(km.kyberWrappedKey.length));
1564
- parts.push(km.kyberWrappedKey);
1565
- const totalLength = parts.reduce((sum, p) => sum + p.length, 0);
1566
- const result = new Uint8Array(totalLength);
1567
- let offset = 0;
1568
- for (const part of parts) {
1569
- result.set(part, offset);
1570
- offset += part.length;
1571
- }
1572
- return result;
1573
- }
1574
1615
  function parseHybridKeyMaterial(data) {
1575
1616
  let offset = 0;
1576
1617
  const x25519EphemeralPk = data.slice(offset, offset + 32);
@@ -1599,6 +1640,38 @@ function parseHybridKeyMaterial(data) {
1599
1640
  kyberWrappedKey
1600
1641
  };
1601
1642
  }
1643
+ function serializeHybridKeyMaterialV2(km) {
1644
+ const parts = [];
1645
+ parts.push(km.x25519EphemeralPk);
1646
+ parts.push(writeUint16BE(km.kyberCiphertext.length));
1647
+ parts.push(km.kyberCiphertext);
1648
+ parts.push(km.ckWrapNonce);
1649
+ parts.push(writeUint16BE(km.ckWrapped.length));
1650
+ parts.push(km.ckWrapped);
1651
+ const totalLength = parts.reduce((sum, p) => sum + p.length, 0);
1652
+ const result = new Uint8Array(totalLength);
1653
+ let offset = 0;
1654
+ for (const part of parts) {
1655
+ result.set(part, offset);
1656
+ offset += part.length;
1657
+ }
1658
+ return result;
1659
+ }
1660
+ function parseHybridKeyMaterialV2(data) {
1661
+ let offset = 0;
1662
+ const x25519EphemeralPk = data.slice(offset, offset + 32);
1663
+ offset += 32;
1664
+ const kyberCtLen = readUint16BE(data, offset);
1665
+ offset += 2;
1666
+ const kyberCiphertext = data.slice(offset, offset + kyberCtLen);
1667
+ offset += kyberCtLen;
1668
+ const ckWrapNonce = data.slice(offset, offset + 24);
1669
+ offset += 24;
1670
+ const ckWrappedLen = readUint16BE(data, offset);
1671
+ offset += 2;
1672
+ const ckWrapped = data.slice(offset, offset + ckWrappedLen);
1673
+ return { x25519EphemeralPk, kyberCiphertext, ckWrapNonce, ckWrapped };
1674
+ }
1602
1675
  function serializePasswordKeyMaterial(km) {
1603
1676
  const result = new Uint8Array(32 + 4 + 4 + 4);
1604
1677
  result.set(km.salt, 0);
@@ -1640,7 +1713,7 @@ function assembleOQEFile(components) {
1640
1713
  }
1641
1714
  function parseOQEFile(data) {
1642
1715
  const header = parseOQEHeader(data);
1643
- let offset = OQE_HEADER_SIZE;
1716
+ let offset = oqeHeaderSize(header.version);
1644
1717
  const keyMaterial = data.slice(offset, offset + header.keyMaterialLength);
1645
1718
  offset += header.keyMaterialLength;
1646
1719
  const encryptedMetadata = data.slice(offset, offset + header.metadataLength);
@@ -1660,61 +1733,81 @@ function addOQEExtension(filename) {
1660
1733
  }
1661
1734
  return `${filename}${OQE_EXTENSION}`;
1662
1735
  }
1663
- function hkdfFlex2(ikm, salt, info) {
1664
- return hkdfSha256(ikm, { salt: u8(salt), info: u8(info), length: 32 });
1665
- }
1736
+ var AES_GCM_TAG_LEN = AES_GCM_TAG_SIZE;
1666
1737
  function computeIdentityHash(publicKeyHex) {
1667
1738
  const hash = sha256(fromHex(publicKeyHex));
1668
1739
  return toHex(hash).slice(0, 16);
1669
1740
  }
1741
+ function combinedFileKekV2(kyberShared, x25519Shared, x25519EpkHex, kyberKemCtB64) {
1742
+ const ikm = new Uint8Array(kyberShared.length + x25519Shared.length);
1743
+ ikm.set(kyberShared, 0);
1744
+ ikm.set(x25519Shared, kyberShared.length);
1745
+ try {
1746
+ return hkdfSha256(ikm, {
1747
+ salt: u8("omnituum/fs/hybrid-v2"),
1748
+ info: u8(`wrap-content-key|${x25519EpkHex}|${kyberKemCtB64}`),
1749
+ length: 32
1750
+ });
1751
+ } finally {
1752
+ ikm.fill(0);
1753
+ }
1754
+ }
1670
1755
  async function encryptHybrid(plaintext, metadata, options) {
1671
1756
  if (!await isKyberAvailable()) {
1672
1757
  throw new OQEError("KYBER_UNAVAILABLE", "Kyber library not available in this environment");
1673
1758
  }
1674
1759
  const contentKey = rand32();
1675
- const iv = rand12();
1676
- const x25519EphKp = nacl4__default.default.box.keyPair();
1677
- const recipientX25519Pk = fromHex(options.recipientPublicKeys.x25519PubHex);
1678
- const x25519Shared = nacl4__default.default.scalarMult(x25519EphKp.secretKey, recipientX25519Pk);
1679
- const x25519Kek = hkdfFlex2(x25519Shared, "omnituum/fs/x25519", "wrap-content-key");
1680
- const x25519Nonce = rand24();
1681
- const x25519WrappedKey = nacl4__default.default.secretbox(contentKey, x25519Nonce, x25519Kek);
1682
- const kyberResult = await kyberEncapsulate(options.recipientPublicKeys.kyberPubB64);
1683
- const kyberKek = hkdfFlex2(kyberResult.sharedSecret, "omnituum/fs/kyber", "wrap-content-key");
1684
- const kyberNonce = rand24();
1685
- const kyberWrappedKey = nacl4__default.default.secretbox(contentKey, kyberNonce, kyberKek);
1686
- const keyMaterial = {
1687
- x25519EphemeralPk: x25519EphKp.publicKey,
1688
- x25519Nonce,
1689
- x25519WrappedKey,
1690
- kyberCiphertext: kyberResult.ciphertext,
1691
- kyberNonce,
1692
- kyberWrappedKey
1693
- };
1694
- const keyMaterialBytes = serializeHybridKeyMaterial(keyMaterial);
1695
- const metadataBytes = serializeMetadata(metadata);
1696
- const { ciphertext: encryptedMetadata } = await aesEncrypt(metadataBytes, contentKey, iv);
1697
- const { ciphertext: encryptedContent } = await aesEncrypt(plaintext, contentKey, iv);
1698
- const header = {
1699
- version: OQE_FORMAT_VERSION,
1700
- algorithmSuite: ALGORITHM_SUITES.HYBRID_X25519_KYBER768_AES256GCM,
1701
- flags: 0,
1702
- metadataLength: encryptedMetadata.length,
1703
- keyMaterialLength: keyMaterialBytes.length,
1704
- iv
1705
- };
1706
- const fileData = assembleOQEFile({
1707
- header,
1708
- keyMaterial: keyMaterialBytes,
1709
- encryptedMetadata,
1710
- encryptedContent
1711
- });
1712
- return {
1713
- data: fileData,
1714
- filename: addOQEExtension(metadata.filename),
1715
- metadata,
1716
- mode: "hybrid"
1717
- };
1760
+ try {
1761
+ const metadataIv = rand12();
1762
+ const contentIv = rand12();
1763
+ const x25519EphKp = nacl4__default.default.box.keyPair();
1764
+ const recipientX25519Pk = fromHex(options.recipientPublicKeys.x25519PubHex);
1765
+ const x25519Shared = nacl4__default.default.scalarMult(x25519EphKp.secretKey, recipientX25519Pk);
1766
+ const x25519EpkHex = toHex(x25519EphKp.publicKey);
1767
+ const kyberResult = await kyberEncapsulate(options.recipientPublicKeys.kyberPubB64);
1768
+ const kyberKemCtB64 = b64(kyberResult.ciphertext);
1769
+ const kek = combinedFileKekV2(kyberResult.sharedSecret, x25519Shared, x25519EpkHex, kyberKemCtB64);
1770
+ const ckWrapNonce = rand24();
1771
+ const ckWrapped = nacl4__default.default.secretbox(contentKey, ckWrapNonce, kek);
1772
+ kek.fill(0);
1773
+ x25519Shared.fill(0);
1774
+ kyberResult.sharedSecret.fill(0);
1775
+ x25519EphKp.secretKey.fill(0);
1776
+ const keyMaterial = {
1777
+ x25519EphemeralPk: x25519EphKp.publicKey,
1778
+ kyberCiphertext: kyberResult.ciphertext,
1779
+ ckWrapNonce,
1780
+ ckWrapped
1781
+ };
1782
+ const keyMaterialBytes = serializeHybridKeyMaterialV2(keyMaterial);
1783
+ const metadataBytes = serializeMetadata(metadata);
1784
+ const header = {
1785
+ version: OQE_FORMAT_VERSION_V2,
1786
+ algorithmSuite: ALGORITHM_SUITES.HYBRID_X25519_MLKEM1024_AES256GCM,
1787
+ flags: 0,
1788
+ metadataLength: metadataBytes.length + AES_GCM_TAG_LEN,
1789
+ keyMaterialLength: keyMaterialBytes.length,
1790
+ iv: metadataIv,
1791
+ contentIv
1792
+ };
1793
+ const aad = writeOQEHeader(header);
1794
+ const { ciphertext: encryptedMetadata } = await aesEncrypt(metadataBytes, contentKey, metadataIv, aad);
1795
+ const { ciphertext: encryptedContent } = await aesEncrypt(plaintext, contentKey, contentIv, aad);
1796
+ const fileData = assembleOQEFile({
1797
+ header,
1798
+ keyMaterial: keyMaterialBytes,
1799
+ encryptedMetadata,
1800
+ encryptedContent
1801
+ });
1802
+ return {
1803
+ data: fileData,
1804
+ filename: addOQEExtension(metadata.filename),
1805
+ metadata,
1806
+ mode: "hybrid"
1807
+ };
1808
+ } finally {
1809
+ contentKey.fill(0);
1810
+ }
1718
1811
  }
1719
1812
  async function encryptPassword(plaintext, metadata, options) {
1720
1813
  if (!await isArgon2Available()) {
@@ -1726,37 +1819,44 @@ async function encryptPassword(plaintext, metadata, options) {
1726
1819
  };
1727
1820
  const salt = generateArgon2Salt(params.saltLength);
1728
1821
  const contentKey = await deriveKeyFromPassword(options.password, salt, params);
1729
- const iv = rand12();
1730
- const keyMaterial = {
1731
- salt,
1732
- memoryCost: params.memoryCost,
1733
- timeCost: params.timeCost,
1734
- parallelism: params.parallelism
1735
- };
1736
- const keyMaterialBytes = serializePasswordKeyMaterial(keyMaterial);
1737
- const metadataBytes = serializeMetadata(metadata);
1738
- const { ciphertext: encryptedMetadata } = await aesEncrypt(metadataBytes, contentKey, iv);
1739
- const { ciphertext: encryptedContent } = await aesEncrypt(plaintext, contentKey, iv);
1740
- const header = {
1741
- version: OQE_FORMAT_VERSION,
1742
- algorithmSuite: ALGORITHM_SUITES.PASSWORD_ARGON2ID_AES256GCM,
1743
- flags: 0,
1744
- metadataLength: encryptedMetadata.length,
1745
- keyMaterialLength: keyMaterialBytes.length,
1746
- iv
1747
- };
1748
- const fileData = assembleOQEFile({
1749
- header,
1750
- keyMaterial: keyMaterialBytes,
1751
- encryptedMetadata,
1752
- encryptedContent
1753
- });
1754
- return {
1755
- data: fileData,
1756
- filename: addOQEExtension(metadata.filename),
1757
- metadata,
1758
- mode: "password"
1759
- };
1822
+ try {
1823
+ const metadataIv = rand12();
1824
+ const contentIv = rand12();
1825
+ const keyMaterial = {
1826
+ salt,
1827
+ memoryCost: params.memoryCost,
1828
+ timeCost: params.timeCost,
1829
+ parallelism: params.parallelism
1830
+ };
1831
+ const keyMaterialBytes = serializePasswordKeyMaterial(keyMaterial);
1832
+ const metadataBytes = serializeMetadata(metadata);
1833
+ const header = {
1834
+ version: OQE_FORMAT_VERSION_V2,
1835
+ algorithmSuite: ALGORITHM_SUITES.PASSWORD_ARGON2ID_AES256GCM,
1836
+ flags: 0,
1837
+ metadataLength: metadataBytes.length + AES_GCM_TAG_LEN,
1838
+ keyMaterialLength: keyMaterialBytes.length,
1839
+ iv: metadataIv,
1840
+ contentIv
1841
+ };
1842
+ const aad = writeOQEHeader(header);
1843
+ const { ciphertext: encryptedMetadata } = await aesEncrypt(metadataBytes, contentKey, metadataIv, aad);
1844
+ const { ciphertext: encryptedContent } = await aesEncrypt(plaintext, contentKey, contentIv, aad);
1845
+ const fileData = assembleOQEFile({
1846
+ header,
1847
+ keyMaterial: keyMaterialBytes,
1848
+ encryptedMetadata,
1849
+ encryptedContent
1850
+ });
1851
+ return {
1852
+ data: fileData,
1853
+ filename: addOQEExtension(metadata.filename),
1854
+ metadata,
1855
+ mode: "password"
1856
+ };
1857
+ } finally {
1858
+ contentKey.fill(0);
1859
+ }
1760
1860
  }
1761
1861
  async function encryptFile(input, options) {
1762
1862
  const plaintext = await toUint8Array(input.data);
@@ -1784,17 +1884,56 @@ async function encryptFileWithPassword(input, password) {
1784
1884
  password
1785
1885
  });
1786
1886
  }
1787
- function hkdfFlex3(ikm, salt, info) {
1887
+ function hkdfFlex2(ikm, salt, info) {
1788
1888
  return hkdfSha256(ikm, { salt: u8(salt), info: u8(info), length: 32 });
1789
1889
  }
1890
+ function headerAad(header) {
1891
+ return writeOQEHeader(header);
1892
+ }
1790
1893
  async function decryptHybrid(encryptedData, options) {
1791
- const { header, keyMaterial, encryptedMetadata, encryptedContent } = parseOQEFile(encryptedData);
1792
- if (header.algorithmSuite !== ALGORITHM_SUITES.HYBRID_X25519_KYBER768_AES256GCM) {
1793
- throw new OQEError(
1794
- "UNSUPPORTED_ALGORITHM",
1795
- "This file was not encrypted with hybrid mode. Use password decryption."
1796
- );
1894
+ const parsed = parseOQEFile(encryptedData);
1895
+ const { header } = parsed;
1896
+ if (header.algorithmSuite === ALGORITHM_SUITES.HYBRID_X25519_MLKEM1024_AES256GCM) {
1897
+ return decryptHybridV2(parsed, options);
1898
+ }
1899
+ if (header.algorithmSuite === ALGORITHM_SUITES.HYBRID_X25519_KYBER768_AES256GCM) {
1900
+ return decryptHybridV1Legacy(parsed, options);
1797
1901
  }
1902
+ throw new OQEError(
1903
+ "UNSUPPORTED_ALGORITHM",
1904
+ "This file was not encrypted with hybrid mode. Use password decryption."
1905
+ );
1906
+ }
1907
+ async function decryptHybridV2(parsed, options) {
1908
+ const { header, keyMaterial, encryptedMetadata, encryptedContent } = parsed;
1909
+ if (header.version !== OQE_FORMAT_VERSION_V2 || !header.contentIv) {
1910
+ throw new OQEError("INVALID_HEADER", "v2 hybrid file missing content IV");
1911
+ }
1912
+ const km = parseHybridKeyMaterialV2(keyMaterial);
1913
+ const x25519EpkHex = toHex(km.x25519EphemeralPk);
1914
+ const kyberKemCtB64 = b64(km.kyberCiphertext);
1915
+ const kyberShared = await kyberDecapsulate(kyberKemCtB64, options.recipientSecretKeys.kyberSecB64);
1916
+ const sk = fromHex(options.recipientSecretKeys.x25519SecHex);
1917
+ const x25519Shared = nacl4__default.default.scalarMult(sk, km.x25519EphemeralPk);
1918
+ const kek = combinedFileKekV2(kyberShared, x25519Shared, x25519EpkHex, kyberKemCtB64);
1919
+ kyberShared.fill(0);
1920
+ x25519Shared.fill(0);
1921
+ const contentKey = nacl4__default.default.secretbox.open(km.ckWrapped, km.ckWrapNonce, kek);
1922
+ kek.fill(0);
1923
+ if (!contentKey) {
1924
+ throw new OQEError("KEY_UNWRAP_FAILED", "Could not unwrap content key \u2014 combined-KEK authentication failed");
1925
+ }
1926
+ const aad = headerAad(header);
1927
+ try {
1928
+ const metadata = await decryptSection(encryptedMetadata, contentKey, header.iv, aad, "metadata");
1929
+ const plaintext = await aesDecrypt(encryptedContent, contentKey, header.contentIv, aad);
1930
+ return buildResult(plaintext, metadata, "hybrid");
1931
+ } finally {
1932
+ contentKey.fill(0);
1933
+ }
1934
+ }
1935
+ async function decryptHybridV1Legacy(parsed, options) {
1936
+ const { header, keyMaterial, encryptedMetadata, encryptedContent } = parsed;
1798
1937
  const km = parseHybridKeyMaterial(keyMaterial);
1799
1938
  let contentKey = null;
1800
1939
  if (await isKyberAvailable()) {
@@ -1803,54 +1942,47 @@ async function decryptHybrid(encryptedData, options) {
1803
1942
  toB64(km.kyberCiphertext),
1804
1943
  options.recipientSecretKeys.kyberSecB64
1805
1944
  );
1806
- const kyberKek = hkdfFlex3(kyberShared, "omnituum/fs/kyber", "wrap-content-key");
1807
- const unwrapped = nacl4__default.default.secretbox.open(km.kyberWrappedKey, km.kyberNonce, kyberKek);
1808
- if (unwrapped) {
1809
- contentKey = unwrapped;
1810
- console.log("[OQE] Decrypted content key via Kyber (post-quantum secure)");
1811
- }
1812
- } catch (e) {
1813
- console.warn("[OQE] Kyber decapsulation failed, trying X25519:", e);
1945
+ const kyberKek = hkdfFlex2(kyberShared, "omnituum/fs/kyber", "wrap-content-key");
1946
+ contentKey = nacl4__default.default.secretbox.open(km.kyberWrappedKey, km.kyberNonce, kyberKek);
1947
+ } catch {
1814
1948
  }
1815
1949
  }
1816
1950
  if (!contentKey) {
1817
1951
  try {
1818
- const ephPk = km.x25519EphemeralPk;
1819
1952
  const sk = fromHex(options.recipientSecretKeys.x25519SecHex);
1820
- const x25519Shared = nacl4__default.default.scalarMult(sk, ephPk);
1821
- const x25519Kek = hkdfFlex3(x25519Shared, "omnituum/fs/x25519", "wrap-content-key");
1822
- const unwrapped = nacl4__default.default.secretbox.open(km.x25519WrappedKey, km.x25519Nonce, x25519Kek);
1823
- if (unwrapped) {
1824
- contentKey = unwrapped;
1825
- console.log("[OQE] Decrypted content key via X25519 (classical)");
1826
- }
1827
- } catch (e) {
1828
- console.warn("[OQE] X25519 decryption failed:", e);
1953
+ const x25519Shared = nacl4__default.default.scalarMult(sk, km.x25519EphemeralPk);
1954
+ const x25519Kek = hkdfFlex2(x25519Shared, "omnituum/fs/x25519", "wrap-content-key");
1955
+ contentKey = nacl4__default.default.secretbox.open(km.x25519WrappedKey, km.x25519Nonce, x25519Kek);
1956
+ } catch {
1829
1957
  }
1830
1958
  }
1831
1959
  if (!contentKey) {
1832
1960
  throw new OQEError("KEY_UNWRAP_FAILED", "Could not unwrap content key with provided keys");
1833
1961
  }
1834
- let metadata;
1835
1962
  try {
1836
- const metadataBytes = await aesDecrypt(encryptedMetadata, contentKey, header.iv);
1837
- metadata = parseMetadata(metadataBytes);
1838
- } catch (e) {
1839
- throw new OQEError("DECRYPTION_FAILED", "Failed to decrypt file metadata");
1963
+ const metadata = await decryptSection(encryptedMetadata, contentKey, header.iv, void 0, "metadata");
1964
+ const plaintext = await aesDecrypt(encryptedContent, contentKey, header.iv);
1965
+ return buildResult(plaintext, metadata, "hybrid");
1966
+ } finally {
1967
+ contentKey.fill(0);
1840
1968
  }
1841
- let plaintext;
1969
+ }
1970
+ async function decryptSection(ciphertext, key, iv, aad, what) {
1842
1971
  try {
1843
- plaintext = await aesDecrypt(encryptedContent, contentKey, header.iv);
1844
- } catch (e) {
1845
- throw new OQEError("DECRYPTION_FAILED", "Failed to decrypt file content");
1972
+ const bytes = await aesDecrypt(ciphertext, key, iv, aad);
1973
+ return parseMetadata(bytes);
1974
+ } catch {
1975
+ throw new OQEError("DECRYPTION_FAILED", `Failed to decrypt file ${what}`);
1846
1976
  }
1977
+ }
1978
+ function buildResult(plaintext, metadata, mode) {
1847
1979
  return {
1848
1980
  data: plaintext,
1849
1981
  filename: metadata.filename,
1850
1982
  mimeType: metadata.mimeType,
1851
1983
  originalSize: metadata.originalSize,
1852
1984
  metadata,
1853
- mode: "hybrid"
1985
+ mode
1854
1986
  };
1855
1987
  }
1856
1988
  async function decryptPassword(encryptedData, options) {
@@ -1872,27 +2004,27 @@ async function decryptPassword(encryptedData, options) {
1872
2004
  hashLength: 32,
1873
2005
  saltLength: km.salt.length
1874
2006
  });
1875
- let metadata;
2007
+ const isV2 = header.version === OQE_FORMAT_VERSION_V2;
2008
+ const aad = isV2 ? headerAad(header) : void 0;
2009
+ const contentIv = isV2 && header.contentIv ? header.contentIv : header.iv;
1876
2010
  try {
1877
- const metadataBytes = await aesDecrypt(encryptedMetadata, contentKey, header.iv);
1878
- metadata = parseMetadata(metadataBytes);
1879
- } catch (e) {
1880
- throw new OQEError("PASSWORD_WRONG", "Incorrect password or corrupted file");
1881
- }
1882
- let plaintext;
1883
- try {
1884
- plaintext = await aesDecrypt(encryptedContent, contentKey, header.iv);
1885
- } catch (e) {
1886
- throw new OQEError("DECRYPTION_FAILED", "Failed to decrypt file content");
2011
+ let metadata;
2012
+ try {
2013
+ const metadataBytes = await aesDecrypt(encryptedMetadata, contentKey, header.iv, aad);
2014
+ metadata = parseMetadata(metadataBytes);
2015
+ } catch {
2016
+ throw new OQEError("PASSWORD_WRONG", "Incorrect password or corrupted file");
2017
+ }
2018
+ let plaintext;
2019
+ try {
2020
+ plaintext = await aesDecrypt(encryptedContent, contentKey, contentIv, aad);
2021
+ } catch {
2022
+ throw new OQEError("DECRYPTION_FAILED", "Failed to decrypt file content");
2023
+ }
2024
+ return buildResult(plaintext, metadata, "password");
2025
+ } finally {
2026
+ contentKey.fill(0);
1887
2027
  }
1888
- return {
1889
- data: plaintext,
1890
- filename: metadata.filename,
1891
- mimeType: metadata.mimeType,
1892
- originalSize: metadata.originalSize,
1893
- metadata,
1894
- mode: "password"
1895
- };
1896
2028
  }
1897
2029
  async function decryptFile(encryptedData, options) {
1898
2030
  const data = await toUint8Array(encryptedData);
@@ -1999,9 +2131,16 @@ exports.DILITHIUM_SECRET_KEY_SIZE = DILITHIUM_SECRET_KEY_SIZE;
1999
2131
  exports.DILITHIUM_SIGNATURE_SIZE = DILITHIUM_SIGNATURE_SIZE;
2000
2132
  exports.ENVELOPE_AEAD = ENVELOPE_AEAD;
2001
2133
  exports.ENVELOPE_SUITE = ENVELOPE_SUITE;
2134
+ exports.ENVELOPE_SUITE_V2 = ENVELOPE_SUITE_V2;
2002
2135
  exports.ENVELOPE_VERSION = ENVELOPE_VERSION;
2136
+ exports.ENVELOPE_VERSION_V2 = ENVELOPE_VERSION_V2;
2003
2137
  exports.KDF_CONFIG_ARGON2ID = KDF_CONFIG_ARGON2ID;
2004
2138
  exports.KDF_CONFIG_PBKDF2 = KDF_CONFIG_PBKDF2;
2139
+ exports.KYBER_CIPHERTEXT_SIZE = KYBER_CIPHERTEXT_SIZE;
2140
+ exports.KYBER_PUBLIC_KEY_SIZE = KYBER_PUBLIC_KEY_SIZE;
2141
+ exports.KYBER_SECRET_KEY_SIZE = KYBER_SECRET_KEY_SIZE;
2142
+ exports.KYBER_SEED_SIZE = KYBER_SEED_SIZE;
2143
+ exports.KYBER_SHARED_SECRET_SIZE = KYBER_SHARED_SECRET_SIZE;
2005
2144
  exports.KYBER_SUITE = KYBER_SUITE;
2006
2145
  exports.POLY1305_TAG_SIZE = POLY1305_TAG_SIZE;
2007
2146
  exports.SECRETBOX_KEY_SIZE = SECRETBOX_KEY_SIZE;