@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.js CHANGED
@@ -149,6 +149,11 @@ function deriveKeyFromShared(shared, salt, info) {
149
149
  return hkdfSha256(shared, { salt: u8(salt), info: u8(info), length: 32 });
150
150
  }
151
151
  var KYBER_SUITE = "ML-KEM-1024-FIPS203";
152
+ var KYBER_PUBLIC_KEY_SIZE = 1568;
153
+ var KYBER_SECRET_KEY_SIZE = 3168;
154
+ var KYBER_CIPHERTEXT_SIZE = 1568;
155
+ var KYBER_SHARED_SECRET_SIZE = 32;
156
+ var KYBER_SEED_SIZE = 64;
152
157
  async function isKyberAvailable() {
153
158
  return true;
154
159
  }
@@ -196,17 +201,20 @@ function kyberUnwrapKey(sharedSecret, nonceB64, wrappedB64) {
196
201
  return nacl4.secretbox.open(wrapped, nonce, kek) || null;
197
202
  }
198
203
  var ENVELOPE_VERSION = OMNI_VERSIONS.HYBRID_V1;
204
+ var ENVELOPE_VERSION_V2 = OMNI_VERSIONS.HYBRID_V2;
199
205
  var ENVELOPE_VERSION_LEGACY = DEPRECATED_VERSIONS.PQC_DEMO_HYBRID_V1;
200
206
  var VAULT_VERSION = "omnituum.vault.v1";
201
207
  var VAULT_ENCRYPTED_VERSION = "omnituum.vault.enc.v1";
202
208
  var VAULT_ENCRYPTED_VERSION_V2 = "omnituum.vault.enc.v2";
203
209
  var ENVELOPE_SUITE = "x25519+kyber768";
210
+ var ENVELOPE_SUITE_V2 = "x25519+mlkem1024";
204
211
  var ENVELOPE_AEAD = "xsalsa20poly1305";
205
212
  var VAULT_KDF = "PBKDF2-SHA256";
206
213
  var VAULT_KDF_V2 = "Argon2id";
207
214
  var VAULT_ALGORITHM = "AES-256-GCM";
208
215
  var SUPPORTED_ENVELOPE_VERSIONS = [
209
216
  ENVELOPE_VERSION,
217
+ ENVELOPE_VERSION_V2,
210
218
  ENVELOPE_VERSION_LEGACY
211
219
  ];
212
220
  var SUPPORTED_VAULT_VERSIONS = [
@@ -251,6 +259,8 @@ function isVaultVersionSupported(version) {
251
259
  function isVaultEncryptedVersionSupported(version) {
252
260
  return SUPPORTED_VAULT_ENCRYPTED_VERSIONS.includes(version);
253
261
  }
262
+ var ENVELOPE_V1_FIELDS = ["x25519Epk", "x25519Wrap", "kyberKemCt", "kyberWrap", "contentNonce", "ciphertext", "meta"];
263
+ var ENVELOPE_V2_FIELDS = ["x25519Epk", "kyberKemCt", "ckWrap", "contentNonce", "ciphertext", "meta"];
254
264
  function validateEnvelope(envelope) {
255
265
  const errors = [];
256
266
  if (!envelope || typeof envelope !== "object") {
@@ -262,14 +272,16 @@ function validateEnvelope(envelope) {
262
272
  } else if (!isEnvelopeVersionSupported(env.v)) {
263
273
  errors.push(`Unsupported envelope version: ${env.v}`);
264
274
  }
265
- if (env.suite !== ENVELOPE_SUITE) {
266
- errors.push(`Invalid suite: expected "${ENVELOPE_SUITE}", got "${env.suite}"`);
275
+ const isV2 = env.v === ENVELOPE_VERSION_V2;
276
+ const expectedSuite = isV2 ? ENVELOPE_SUITE_V2 : ENVELOPE_SUITE;
277
+ const requiredFields = isV2 ? ENVELOPE_V2_FIELDS : ENVELOPE_V1_FIELDS;
278
+ if (env.suite !== expectedSuite) {
279
+ errors.push(`Invalid suite: expected "${expectedSuite}", got "${env.suite}"`);
267
280
  }
268
281
  if (env.aead !== ENVELOPE_AEAD) {
269
282
  errors.push(`Invalid aead: expected "${ENVELOPE_AEAD}", got "${env.aead}"`);
270
283
  }
271
- const required = ["x25519Epk", "x25519Wrap", "kyberKemCt", "kyberWrap", "contentNonce", "ciphertext", "meta"];
272
- for (const field of required) {
284
+ for (const field of requiredFields) {
273
285
  if (!(field in env)) {
274
286
  errors.push(`Missing required field: ${field}`);
275
287
  }
@@ -364,7 +376,6 @@ async function generateHybridIdentity(name) {
364
376
  const x25519 = generateX25519Keypair();
365
377
  const kyber = await generateKyberKeypair();
366
378
  if (!kyber) {
367
- console.error("Kyber key generation failed - library not available");
368
379
  return null;
369
380
  }
370
381
  return {
@@ -390,49 +401,74 @@ function getSecretKeys(identity) {
390
401
  kyberSecB64: identity.kyberSecB64
391
402
  };
392
403
  }
404
+ function combinedKekV2(kyberShared, x25519Shared, x25519EpkHex, kyberKemCtB64) {
405
+ const ikm = new Uint8Array(kyberShared.length + x25519Shared.length);
406
+ ikm.set(kyberShared, 0);
407
+ ikm.set(x25519Shared, kyberShared.length);
408
+ try {
409
+ return hkdfFlex(ikm, "omnituum/hybrid-v2", `wrap-ck|${x25519EpkHex}|${kyberKemCtB64}`);
410
+ } finally {
411
+ ikm.fill(0);
412
+ }
413
+ }
393
414
  async function hybridEncrypt(plaintext, recipientPublicKeys, sender) {
394
415
  const pt = typeof plaintext === "string" ? textEncoder.encode(plaintext) : plaintext;
395
416
  const CK = rand32();
396
- const contentNonce = rand24();
397
- const ciphertext = nacl4.secretbox(pt, contentNonce, CK);
398
- const x25519EphKp = nacl4.box.keyPair();
399
- const recipientX25519Pk = fromHex(recipientPublicKeys.x25519PubHex);
400
- const x25519Shared = nacl4.scalarMult(x25519EphKp.secretKey, recipientX25519Pk);
401
- const x25519Kek = hkdfFlex(x25519Shared, "omnituum/x25519", "wrap-ck");
402
- const x25519WrapNonce = rand24();
403
- const x25519Wrapped = nacl4.secretbox(CK, x25519WrapNonce, x25519Kek);
404
- const kyberResult = await kyberEncapsulate(recipientPublicKeys.kyberPubB64);
405
- const kyberKek = hkdfFlex(kyberResult.sharedSecret, "omnituum/kyber", "wrap-ck");
406
- const kyberWrapNonce = rand24();
407
- const kyberWrapped = nacl4.secretbox(CK, kyberWrapNonce, kyberKek);
408
- return {
409
- v: ENVELOPE_VERSION,
410
- suite: ENVELOPE_SUITE,
411
- aead: ENVELOPE_AEAD,
412
- x25519Epk: toHex(x25519EphKp.publicKey),
413
- x25519Wrap: {
414
- nonce: b64(x25519WrapNonce),
415
- wrapped: b64(x25519Wrapped)
416
- },
417
- kyberKemCt: b64(kyberResult.ciphertext),
418
- kyberWrap: {
419
- nonce: b64(kyberWrapNonce),
420
- wrapped: b64(kyberWrapped)
421
- },
422
- contentNonce: b64(contentNonce),
423
- ciphertext: b64(ciphertext),
424
- meta: {
425
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
426
- senderName: sender?.name,
427
- senderId: sender?.id
428
- }
429
- };
417
+ try {
418
+ const contentNonce = rand24();
419
+ const ciphertext = nacl4.secretbox(pt, contentNonce, CK);
420
+ const x25519EphKp = nacl4.box.keyPair();
421
+ const recipientX25519Pk = fromHex(recipientPublicKeys.x25519PubHex);
422
+ const x25519Shared = nacl4.scalarMult(x25519EphKp.secretKey, recipientX25519Pk);
423
+ const x25519EpkHex = toHex(x25519EphKp.publicKey);
424
+ const kyberResult = await kyberEncapsulate(recipientPublicKeys.kyberPubB64);
425
+ const kyberKemCtB64 = b64(kyberResult.ciphertext);
426
+ const kek = combinedKekV2(kyberResult.sharedSecret, x25519Shared, x25519EpkHex, kyberKemCtB64);
427
+ const ckWrapNonce = rand24();
428
+ const ckWrapped = nacl4.secretbox(CK, ckWrapNonce, kek);
429
+ kek.fill(0);
430
+ x25519Shared.fill(0);
431
+ kyberResult.sharedSecret.fill(0);
432
+ x25519EphKp.secretKey.fill(0);
433
+ return {
434
+ v: ENVELOPE_VERSION_V2,
435
+ suite: ENVELOPE_SUITE_V2,
436
+ aead: ENVELOPE_AEAD,
437
+ x25519Epk: x25519EpkHex,
438
+ kyberKemCt: kyberKemCtB64,
439
+ ckWrap: {
440
+ nonce: b64(ckWrapNonce),
441
+ wrapped: b64(ckWrapped)
442
+ },
443
+ contentNonce: b64(contentNonce),
444
+ ciphertext: b64(ciphertext),
445
+ meta: {
446
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
447
+ senderName: sender?.name,
448
+ senderId: sender?.id
449
+ }
450
+ };
451
+ } finally {
452
+ CK.fill(0);
453
+ }
454
+ }
455
+ async function unwrapCkV2(envelope, secretKeys) {
456
+ const kyberShared = await kyberDecapsulate(envelope.kyberKemCt, secretKeys.kyberSecB64);
457
+ const ephPk = fromHex(envelope.x25519Epk);
458
+ const sk = fromHex(secretKeys.x25519SecHex);
459
+ const x25519Shared = nacl4.scalarMult(sk, ephPk);
460
+ const kek = combinedKekV2(kyberShared, x25519Shared, envelope.x25519Epk, envelope.kyberKemCt);
461
+ kyberShared.fill(0);
462
+ x25519Shared.fill(0);
463
+ const CK = nacl4.secretbox.open(ub64(envelope.ckWrap.wrapped), ub64(envelope.ckWrap.nonce), kek);
464
+ kek.fill(0);
465
+ if (!CK) {
466
+ throw new Error("Could not unwrap content key \u2014 combined-KEK authentication failed");
467
+ }
468
+ return CK;
430
469
  }
431
- async function hybridDecrypt(envelope, secretKeys) {
432
- const version = envelope.v;
433
- assertEnvelopeVersion(version);
470
+ async function unwrapCkV1(envelope, secretKeys, saltPrefix) {
434
471
  let CK = null;
435
- const saltPrefix = version === "pqc-demo.hybrid.v1" ? "pqc-demo" : "omnituum";
436
472
  try {
437
473
  const kyberShared = await kyberDecapsulate(envelope.kyberKemCt, secretKeys.kyberSecB64);
438
474
  const kyberKek = hkdfFlex(kyberShared, `${saltPrefix}/kyber`, "wrap-ck");
@@ -441,11 +477,7 @@ async function hybridDecrypt(envelope, secretKeys) {
441
477
  ub64(envelope.kyberWrap.nonce),
442
478
  kyberKek
443
479
  );
444
- if (CK) {
445
- console.log("[Hybrid] Decrypted using Kyber (post-quantum)");
446
- }
447
- } catch (e) {
448
- console.warn("[Hybrid] Kyber decapsulation failed:", e);
480
+ } catch {
449
481
  }
450
482
  if (!CK) {
451
483
  try {
@@ -458,21 +490,28 @@ async function hybridDecrypt(envelope, secretKeys) {
458
490
  ub64(envelope.x25519Wrap.nonce),
459
491
  x25519Kek
460
492
  );
461
- if (CK) {
462
- console.log("[Hybrid] Decrypted using X25519 (classical)");
463
- }
464
- } catch (e) {
465
- console.warn("[Hybrid] X25519 decryption failed:", e);
493
+ } catch {
466
494
  }
467
495
  }
468
496
  if (!CK) {
469
497
  throw new Error("Could not unwrap content key with either algorithm");
470
498
  }
499
+ return CK;
500
+ }
501
+ async function hybridDecrypt(envelope, secretKeys) {
502
+ const version = envelope.v;
503
+ assertEnvelopeVersion(version);
504
+ const CK = version === ENVELOPE_VERSION_V2 ? await unwrapCkV2(envelope, secretKeys) : await unwrapCkV1(
505
+ envelope,
506
+ secretKeys,
507
+ version === "pqc-demo.hybrid.v1" ? "pqc-demo" : "omnituum"
508
+ );
471
509
  const pt = nacl4.secretbox.open(
472
510
  ub64(envelope.ciphertext),
473
511
  ub64(envelope.contentNonce),
474
512
  CK
475
513
  );
514
+ CK.fill(0);
476
515
  if (!pt) {
477
516
  throw new Error("Content authentication failed");
478
517
  }
@@ -491,8 +530,7 @@ async function loadDilithium() {
491
530
  const { ml_dsa65 } = await import('@noble/post-quantum/ml-dsa.js');
492
531
  dilithiumModule = ml_dsa65;
493
532
  return dilithiumModule;
494
- } catch (e) {
495
- console.warn("[Dilithium] Failed to load @noble/post-quantum:", e);
533
+ } catch {
496
534
  return null;
497
535
  }
498
536
  }
@@ -510,8 +548,7 @@ async function generateDilithiumKeypair() {
510
548
  publicB64: toB64(kp.publicKey),
511
549
  secretB64: toB64(kp.secretKey)
512
550
  };
513
- } catch (e) {
514
- console.warn("[Dilithium] Key generation failed:", e);
551
+ } catch {
515
552
  return null;
516
553
  }
517
554
  }
@@ -850,17 +887,11 @@ function computeIntegrityHash(identities) {
850
887
  createdAt: i.createdAt,
851
888
  rotationCount: i.rotationCount
852
889
  }));
853
- const serialized = JSON.stringify(canonical, Object.keys(canonical[0] || {}).sort());
890
+ const serialized = JSON.stringify(canonical);
854
891
  return computeStringHash(serialized);
855
892
  }
856
893
  function computeStringHash(str) {
857
- let hash = 0;
858
- for (let i = 0; i < str.length; i++) {
859
- const char = str.charCodeAt(i);
860
- hash = (hash << 5) - hash + char;
861
- hash = hash & hash;
862
- }
863
- return Math.abs(hash).toString(16).padStart(16, "0");
894
+ return toHex(sha256(textEncoder.encode(str)));
864
895
  }
865
896
  async function computeHashAsync(data) {
866
897
  const encoder = new TextEncoder();
@@ -1111,7 +1142,6 @@ async function createIdentity(name) {
1111
1142
  const x25519 = generateX25519Keypair();
1112
1143
  const kyber = await generateKyberKeypair();
1113
1144
  if (!kyber) {
1114
- console.error("Kyber key generation failed");
1115
1145
  return null;
1116
1146
  }
1117
1147
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -1334,12 +1364,22 @@ async function migrateEncryptedVault(options) {
1334
1364
 
1335
1365
  // src/fs/types.ts
1336
1366
  var OQE_MAGIC = new Uint8Array([79, 81, 69, 70]);
1337
- var OQE_FORMAT_VERSION = 1;
1367
+ var OQE_FORMAT_VERSION_V1 = 1;
1368
+ var OQE_FORMAT_VERSION_V2 = 2;
1369
+ var SUPPORTED_OQE_VERSIONS = [OQE_FORMAT_VERSION_V1, OQE_FORMAT_VERSION_V2];
1338
1370
  var ALGORITHM_SUITES = {
1339
- /** Hybrid: X25519 ECDH + Kyber768 KEM + AES-256-GCM */
1371
+ /**
1372
+ * LEGACY (read-only): Hybrid X25519 + Kyber with independent per-primitive
1373
+ * wraps (either secret unwraps). Only appears in v1 files. Never written.
1374
+ */
1340
1375
  HYBRID_X25519_KYBER768_AES256GCM: 1,
1341
1376
  /** Password: Argon2id + AES-256-GCM */
1342
- PASSWORD_ARGON2ID_AES256GCM: 2
1377
+ PASSWORD_ARGON2ID_AES256GCM: 2,
1378
+ /**
1379
+ * Hybrid X25519 + ML-KEM-1024 with a single AND-combined KEK wrap
1380
+ * (HKDF(ss_mlkem || ss_x25519), transcript-bound). Written by v2.
1381
+ */
1382
+ HYBRID_X25519_MLKEM1024_AES256GCM: 3
1343
1383
  };
1344
1384
  var DEFAULT_ARGON2ID_PARAMS = {
1345
1385
  memoryCost: 65536,
@@ -1349,7 +1389,8 @@ var DEFAULT_ARGON2ID_PARAMS = {
1349
1389
  hashLength: 32,
1350
1390
  saltLength: 32
1351
1391
  };
1352
- var OQE_HEADER_SIZE = 30;
1392
+ var OQE_HEADER_SIZE_V1 = 30;
1393
+ var OQE_HEADER_SIZE_V2 = 42;
1353
1394
  var OQEError = class extends Error {
1354
1395
  constructor(code, message) {
1355
1396
  super(message);
@@ -1419,6 +1460,7 @@ function toArrayBuffer(arr) {
1419
1460
  }
1420
1461
  var AES_KEY_SIZE = 32;
1421
1462
  var AES_GCM_IV_SIZE = 12;
1463
+ var AES_GCM_TAG_SIZE = 16;
1422
1464
  async function importAesKey(keyBytes) {
1423
1465
  if (keyBytes.length !== AES_KEY_SIZE) {
1424
1466
  throw new Error(`AES key must be ${AES_KEY_SIZE} bytes, got ${keyBytes.length}`);
@@ -1442,7 +1484,7 @@ async function aesEncrypt(plaintext, key, iv, additionalData) {
1442
1484
  {
1443
1485
  name: "AES-GCM",
1444
1486
  iv: toArrayBuffer(ivBytes),
1445
- additionalData: void 0,
1487
+ additionalData: additionalData ? toArrayBuffer(additionalData) : void 0,
1446
1488
  tagLength: 128
1447
1489
  // 16 bytes
1448
1490
  },
@@ -1478,6 +1520,9 @@ async function aesDecrypt(ciphertext, key, iv, additionalData) {
1478
1520
  }
1479
1521
 
1480
1522
  // src/fs/format.ts
1523
+ function oqeHeaderSize(version) {
1524
+ return version === OQE_FORMAT_VERSION_V2 ? OQE_HEADER_SIZE_V2 : OQE_HEADER_SIZE_V1;
1525
+ }
1481
1526
  function writeUint32BE(value) {
1482
1527
  const buffer = new ArrayBuffer(4);
1483
1528
  new DataView(buffer).setUint32(0, value, false);
@@ -1495,7 +1540,8 @@ function readUint16BE(data, offset) {
1495
1540
  return new DataView(data.buffer, data.byteOffset + offset).getUint16(0, false);
1496
1541
  }
1497
1542
  function writeOQEHeader(header) {
1498
- const buffer = new Uint8Array(OQE_HEADER_SIZE);
1543
+ const isV2 = header.version === OQE_FORMAT_VERSION_V2;
1544
+ const buffer = new Uint8Array(oqeHeaderSize(header.version));
1499
1545
  let offset = 0;
1500
1546
  buffer.set(OQE_MAGIC, offset);
1501
1547
  offset += 4;
@@ -1508,11 +1554,18 @@ function writeOQEHeader(header) {
1508
1554
  buffer.set(writeUint32BE(header.keyMaterialLength), offset);
1509
1555
  offset += 4;
1510
1556
  buffer.set(header.iv, offset);
1557
+ offset += 12;
1558
+ if (isV2) {
1559
+ if (!header.contentIv || header.contentIv.length !== 12) {
1560
+ throw new OQEError("INVALID_HEADER", "v2 header requires a 12-byte contentIv");
1561
+ }
1562
+ buffer.set(header.contentIv, offset);
1563
+ }
1511
1564
  return buffer;
1512
1565
  }
1513
1566
  function parseOQEHeader(data) {
1514
- if (data.length < OQE_HEADER_SIZE) {
1515
- throw new OQEError("INVALID_HEADER", `File too small: need ${OQE_HEADER_SIZE} bytes, got ${data.length}`);
1567
+ if (data.length < OQE_HEADER_SIZE_V1) {
1568
+ throw new OQEError("INVALID_HEADER", `File too small: need at least ${OQE_HEADER_SIZE_V1} bytes, got ${data.length}`);
1516
1569
  }
1517
1570
  let offset = 0;
1518
1571
  const magic = data.slice(0, 4);
@@ -1521,14 +1574,19 @@ function parseOQEHeader(data) {
1521
1574
  }
1522
1575
  offset += 4;
1523
1576
  const version = data[offset++];
1524
- if (version !== OQE_FORMAT_VERSION) {
1577
+ if (!SUPPORTED_OQE_VERSIONS.includes(version)) {
1525
1578
  throw new OQEError("UNSUPPORTED_VERSION", `Unsupported OQE version: ${version}`);
1526
1579
  }
1527
1580
  const algorithmSuiteRaw = data[offset++];
1528
- if (algorithmSuiteRaw !== ALGORITHM_SUITES.HYBRID_X25519_KYBER768_AES256GCM && algorithmSuiteRaw !== ALGORITHM_SUITES.PASSWORD_ARGON2ID_AES256GCM) {
1581
+ if (algorithmSuiteRaw !== ALGORITHM_SUITES.HYBRID_X25519_KYBER768_AES256GCM && algorithmSuiteRaw !== ALGORITHM_SUITES.HYBRID_X25519_MLKEM1024_AES256GCM && algorithmSuiteRaw !== ALGORITHM_SUITES.PASSWORD_ARGON2ID_AES256GCM) {
1529
1582
  throw new OQEError("UNSUPPORTED_ALGORITHM", `Unsupported algorithm suite: 0x${algorithmSuiteRaw.toString(16)}`);
1530
1583
  }
1531
1584
  const algorithmSuite = algorithmSuiteRaw;
1585
+ const isV2 = version === OQE_FORMAT_VERSION_V2;
1586
+ const headerSize = oqeHeaderSize(version);
1587
+ if (data.length < headerSize) {
1588
+ throw new OQEError("INVALID_HEADER", `Truncated v${version} header: need ${headerSize} bytes, got ${data.length}`);
1589
+ }
1532
1590
  const flags = readUint32BE(data, offset);
1533
1591
  offset += 4;
1534
1592
  const metadataLength = readUint32BE(data, offset);
@@ -1536,35 +1594,18 @@ function parseOQEHeader(data) {
1536
1594
  const keyMaterialLength = readUint32BE(data, offset);
1537
1595
  offset += 4;
1538
1596
  const iv = data.slice(offset, offset + 12);
1597
+ offset += 12;
1598
+ const contentIv = isV2 ? data.slice(offset, offset + 12) : void 0;
1539
1599
  return {
1540
1600
  version,
1541
1601
  algorithmSuite,
1542
1602
  flags,
1543
1603
  metadataLength,
1544
1604
  keyMaterialLength,
1545
- iv
1605
+ iv,
1606
+ contentIv
1546
1607
  };
1547
1608
  }
1548
- function serializeHybridKeyMaterial(km) {
1549
- const parts = [];
1550
- parts.push(km.x25519EphemeralPk);
1551
- parts.push(km.x25519Nonce);
1552
- parts.push(writeUint16BE(km.x25519WrappedKey.length));
1553
- parts.push(km.x25519WrappedKey);
1554
- parts.push(writeUint16BE(km.kyberCiphertext.length));
1555
- parts.push(km.kyberCiphertext);
1556
- parts.push(km.kyberNonce);
1557
- parts.push(writeUint16BE(km.kyberWrappedKey.length));
1558
- parts.push(km.kyberWrappedKey);
1559
- const totalLength = parts.reduce((sum, p) => sum + p.length, 0);
1560
- const result = new Uint8Array(totalLength);
1561
- let offset = 0;
1562
- for (const part of parts) {
1563
- result.set(part, offset);
1564
- offset += part.length;
1565
- }
1566
- return result;
1567
- }
1568
1609
  function parseHybridKeyMaterial(data) {
1569
1610
  let offset = 0;
1570
1611
  const x25519EphemeralPk = data.slice(offset, offset + 32);
@@ -1593,6 +1634,38 @@ function parseHybridKeyMaterial(data) {
1593
1634
  kyberWrappedKey
1594
1635
  };
1595
1636
  }
1637
+ function serializeHybridKeyMaterialV2(km) {
1638
+ const parts = [];
1639
+ parts.push(km.x25519EphemeralPk);
1640
+ parts.push(writeUint16BE(km.kyberCiphertext.length));
1641
+ parts.push(km.kyberCiphertext);
1642
+ parts.push(km.ckWrapNonce);
1643
+ parts.push(writeUint16BE(km.ckWrapped.length));
1644
+ parts.push(km.ckWrapped);
1645
+ const totalLength = parts.reduce((sum, p) => sum + p.length, 0);
1646
+ const result = new Uint8Array(totalLength);
1647
+ let offset = 0;
1648
+ for (const part of parts) {
1649
+ result.set(part, offset);
1650
+ offset += part.length;
1651
+ }
1652
+ return result;
1653
+ }
1654
+ function parseHybridKeyMaterialV2(data) {
1655
+ let offset = 0;
1656
+ const x25519EphemeralPk = data.slice(offset, offset + 32);
1657
+ offset += 32;
1658
+ const kyberCtLen = readUint16BE(data, offset);
1659
+ offset += 2;
1660
+ const kyberCiphertext = data.slice(offset, offset + kyberCtLen);
1661
+ offset += kyberCtLen;
1662
+ const ckWrapNonce = data.slice(offset, offset + 24);
1663
+ offset += 24;
1664
+ const ckWrappedLen = readUint16BE(data, offset);
1665
+ offset += 2;
1666
+ const ckWrapped = data.slice(offset, offset + ckWrappedLen);
1667
+ return { x25519EphemeralPk, kyberCiphertext, ckWrapNonce, ckWrapped };
1668
+ }
1596
1669
  function serializePasswordKeyMaterial(km) {
1597
1670
  const result = new Uint8Array(32 + 4 + 4 + 4);
1598
1671
  result.set(km.salt, 0);
@@ -1634,7 +1707,7 @@ function assembleOQEFile(components) {
1634
1707
  }
1635
1708
  function parseOQEFile(data) {
1636
1709
  const header = parseOQEHeader(data);
1637
- let offset = OQE_HEADER_SIZE;
1710
+ let offset = oqeHeaderSize(header.version);
1638
1711
  const keyMaterial = data.slice(offset, offset + header.keyMaterialLength);
1639
1712
  offset += header.keyMaterialLength;
1640
1713
  const encryptedMetadata = data.slice(offset, offset + header.metadataLength);
@@ -1654,61 +1727,81 @@ function addOQEExtension(filename) {
1654
1727
  }
1655
1728
  return `${filename}${OQE_EXTENSION}`;
1656
1729
  }
1657
- function hkdfFlex2(ikm, salt, info) {
1658
- return hkdfSha256(ikm, { salt: u8(salt), info: u8(info), length: 32 });
1659
- }
1730
+ var AES_GCM_TAG_LEN = AES_GCM_TAG_SIZE;
1660
1731
  function computeIdentityHash(publicKeyHex) {
1661
1732
  const hash = sha256(fromHex(publicKeyHex));
1662
1733
  return toHex(hash).slice(0, 16);
1663
1734
  }
1735
+ function combinedFileKekV2(kyberShared, x25519Shared, x25519EpkHex, kyberKemCtB64) {
1736
+ const ikm = new Uint8Array(kyberShared.length + x25519Shared.length);
1737
+ ikm.set(kyberShared, 0);
1738
+ ikm.set(x25519Shared, kyberShared.length);
1739
+ try {
1740
+ return hkdfSha256(ikm, {
1741
+ salt: u8("omnituum/fs/hybrid-v2"),
1742
+ info: u8(`wrap-content-key|${x25519EpkHex}|${kyberKemCtB64}`),
1743
+ length: 32
1744
+ });
1745
+ } finally {
1746
+ ikm.fill(0);
1747
+ }
1748
+ }
1664
1749
  async function encryptHybrid(plaintext, metadata, options) {
1665
1750
  if (!await isKyberAvailable()) {
1666
1751
  throw new OQEError("KYBER_UNAVAILABLE", "Kyber library not available in this environment");
1667
1752
  }
1668
1753
  const contentKey = rand32();
1669
- const iv = rand12();
1670
- const x25519EphKp = nacl4.box.keyPair();
1671
- const recipientX25519Pk = fromHex(options.recipientPublicKeys.x25519PubHex);
1672
- const x25519Shared = nacl4.scalarMult(x25519EphKp.secretKey, recipientX25519Pk);
1673
- const x25519Kek = hkdfFlex2(x25519Shared, "omnituum/fs/x25519", "wrap-content-key");
1674
- const x25519Nonce = rand24();
1675
- const x25519WrappedKey = nacl4.secretbox(contentKey, x25519Nonce, x25519Kek);
1676
- const kyberResult = await kyberEncapsulate(options.recipientPublicKeys.kyberPubB64);
1677
- const kyberKek = hkdfFlex2(kyberResult.sharedSecret, "omnituum/fs/kyber", "wrap-content-key");
1678
- const kyberNonce = rand24();
1679
- const kyberWrappedKey = nacl4.secretbox(contentKey, kyberNonce, kyberKek);
1680
- const keyMaterial = {
1681
- x25519EphemeralPk: x25519EphKp.publicKey,
1682
- x25519Nonce,
1683
- x25519WrappedKey,
1684
- kyberCiphertext: kyberResult.ciphertext,
1685
- kyberNonce,
1686
- kyberWrappedKey
1687
- };
1688
- const keyMaterialBytes = serializeHybridKeyMaterial(keyMaterial);
1689
- const metadataBytes = serializeMetadata(metadata);
1690
- const { ciphertext: encryptedMetadata } = await aesEncrypt(metadataBytes, contentKey, iv);
1691
- const { ciphertext: encryptedContent } = await aesEncrypt(plaintext, contentKey, iv);
1692
- const header = {
1693
- version: OQE_FORMAT_VERSION,
1694
- algorithmSuite: ALGORITHM_SUITES.HYBRID_X25519_KYBER768_AES256GCM,
1695
- flags: 0,
1696
- metadataLength: encryptedMetadata.length,
1697
- keyMaterialLength: keyMaterialBytes.length,
1698
- iv
1699
- };
1700
- const fileData = assembleOQEFile({
1701
- header,
1702
- keyMaterial: keyMaterialBytes,
1703
- encryptedMetadata,
1704
- encryptedContent
1705
- });
1706
- return {
1707
- data: fileData,
1708
- filename: addOQEExtension(metadata.filename),
1709
- metadata,
1710
- mode: "hybrid"
1711
- };
1754
+ try {
1755
+ const metadataIv = rand12();
1756
+ const contentIv = rand12();
1757
+ const x25519EphKp = nacl4.box.keyPair();
1758
+ const recipientX25519Pk = fromHex(options.recipientPublicKeys.x25519PubHex);
1759
+ const x25519Shared = nacl4.scalarMult(x25519EphKp.secretKey, recipientX25519Pk);
1760
+ const x25519EpkHex = toHex(x25519EphKp.publicKey);
1761
+ const kyberResult = await kyberEncapsulate(options.recipientPublicKeys.kyberPubB64);
1762
+ const kyberKemCtB64 = b64(kyberResult.ciphertext);
1763
+ const kek = combinedFileKekV2(kyberResult.sharedSecret, x25519Shared, x25519EpkHex, kyberKemCtB64);
1764
+ const ckWrapNonce = rand24();
1765
+ const ckWrapped = nacl4.secretbox(contentKey, ckWrapNonce, kek);
1766
+ kek.fill(0);
1767
+ x25519Shared.fill(0);
1768
+ kyberResult.sharedSecret.fill(0);
1769
+ x25519EphKp.secretKey.fill(0);
1770
+ const keyMaterial = {
1771
+ x25519EphemeralPk: x25519EphKp.publicKey,
1772
+ kyberCiphertext: kyberResult.ciphertext,
1773
+ ckWrapNonce,
1774
+ ckWrapped
1775
+ };
1776
+ const keyMaterialBytes = serializeHybridKeyMaterialV2(keyMaterial);
1777
+ const metadataBytes = serializeMetadata(metadata);
1778
+ const header = {
1779
+ version: OQE_FORMAT_VERSION_V2,
1780
+ algorithmSuite: ALGORITHM_SUITES.HYBRID_X25519_MLKEM1024_AES256GCM,
1781
+ flags: 0,
1782
+ metadataLength: metadataBytes.length + AES_GCM_TAG_LEN,
1783
+ keyMaterialLength: keyMaterialBytes.length,
1784
+ iv: metadataIv,
1785
+ contentIv
1786
+ };
1787
+ const aad = writeOQEHeader(header);
1788
+ const { ciphertext: encryptedMetadata } = await aesEncrypt(metadataBytes, contentKey, metadataIv, aad);
1789
+ const { ciphertext: encryptedContent } = await aesEncrypt(plaintext, contentKey, contentIv, aad);
1790
+ const fileData = assembleOQEFile({
1791
+ header,
1792
+ keyMaterial: keyMaterialBytes,
1793
+ encryptedMetadata,
1794
+ encryptedContent
1795
+ });
1796
+ return {
1797
+ data: fileData,
1798
+ filename: addOQEExtension(metadata.filename),
1799
+ metadata,
1800
+ mode: "hybrid"
1801
+ };
1802
+ } finally {
1803
+ contentKey.fill(0);
1804
+ }
1712
1805
  }
1713
1806
  async function encryptPassword(plaintext, metadata, options) {
1714
1807
  if (!await isArgon2Available()) {
@@ -1720,37 +1813,44 @@ async function encryptPassword(plaintext, metadata, options) {
1720
1813
  };
1721
1814
  const salt = generateArgon2Salt(params.saltLength);
1722
1815
  const contentKey = await deriveKeyFromPassword(options.password, salt, params);
1723
- const iv = rand12();
1724
- const keyMaterial = {
1725
- salt,
1726
- memoryCost: params.memoryCost,
1727
- timeCost: params.timeCost,
1728
- parallelism: params.parallelism
1729
- };
1730
- const keyMaterialBytes = serializePasswordKeyMaterial(keyMaterial);
1731
- const metadataBytes = serializeMetadata(metadata);
1732
- const { ciphertext: encryptedMetadata } = await aesEncrypt(metadataBytes, contentKey, iv);
1733
- const { ciphertext: encryptedContent } = await aesEncrypt(plaintext, contentKey, iv);
1734
- const header = {
1735
- version: OQE_FORMAT_VERSION,
1736
- algorithmSuite: ALGORITHM_SUITES.PASSWORD_ARGON2ID_AES256GCM,
1737
- flags: 0,
1738
- metadataLength: encryptedMetadata.length,
1739
- keyMaterialLength: keyMaterialBytes.length,
1740
- iv
1741
- };
1742
- const fileData = assembleOQEFile({
1743
- header,
1744
- keyMaterial: keyMaterialBytes,
1745
- encryptedMetadata,
1746
- encryptedContent
1747
- });
1748
- return {
1749
- data: fileData,
1750
- filename: addOQEExtension(metadata.filename),
1751
- metadata,
1752
- mode: "password"
1753
- };
1816
+ try {
1817
+ const metadataIv = rand12();
1818
+ const contentIv = rand12();
1819
+ const keyMaterial = {
1820
+ salt,
1821
+ memoryCost: params.memoryCost,
1822
+ timeCost: params.timeCost,
1823
+ parallelism: params.parallelism
1824
+ };
1825
+ const keyMaterialBytes = serializePasswordKeyMaterial(keyMaterial);
1826
+ const metadataBytes = serializeMetadata(metadata);
1827
+ const header = {
1828
+ version: OQE_FORMAT_VERSION_V2,
1829
+ algorithmSuite: ALGORITHM_SUITES.PASSWORD_ARGON2ID_AES256GCM,
1830
+ flags: 0,
1831
+ metadataLength: metadataBytes.length + AES_GCM_TAG_LEN,
1832
+ keyMaterialLength: keyMaterialBytes.length,
1833
+ iv: metadataIv,
1834
+ contentIv
1835
+ };
1836
+ const aad = writeOQEHeader(header);
1837
+ const { ciphertext: encryptedMetadata } = await aesEncrypt(metadataBytes, contentKey, metadataIv, aad);
1838
+ const { ciphertext: encryptedContent } = await aesEncrypt(plaintext, contentKey, contentIv, aad);
1839
+ const fileData = assembleOQEFile({
1840
+ header,
1841
+ keyMaterial: keyMaterialBytes,
1842
+ encryptedMetadata,
1843
+ encryptedContent
1844
+ });
1845
+ return {
1846
+ data: fileData,
1847
+ filename: addOQEExtension(metadata.filename),
1848
+ metadata,
1849
+ mode: "password"
1850
+ };
1851
+ } finally {
1852
+ contentKey.fill(0);
1853
+ }
1754
1854
  }
1755
1855
  async function encryptFile(input, options) {
1756
1856
  const plaintext = await toUint8Array(input.data);
@@ -1778,17 +1878,56 @@ async function encryptFileWithPassword(input, password) {
1778
1878
  password
1779
1879
  });
1780
1880
  }
1781
- function hkdfFlex3(ikm, salt, info) {
1881
+ function hkdfFlex2(ikm, salt, info) {
1782
1882
  return hkdfSha256(ikm, { salt: u8(salt), info: u8(info), length: 32 });
1783
1883
  }
1884
+ function headerAad(header) {
1885
+ return writeOQEHeader(header);
1886
+ }
1784
1887
  async function decryptHybrid(encryptedData, options) {
1785
- const { header, keyMaterial, encryptedMetadata, encryptedContent } = parseOQEFile(encryptedData);
1786
- if (header.algorithmSuite !== ALGORITHM_SUITES.HYBRID_X25519_KYBER768_AES256GCM) {
1787
- throw new OQEError(
1788
- "UNSUPPORTED_ALGORITHM",
1789
- "This file was not encrypted with hybrid mode. Use password decryption."
1790
- );
1888
+ const parsed = parseOQEFile(encryptedData);
1889
+ const { header } = parsed;
1890
+ if (header.algorithmSuite === ALGORITHM_SUITES.HYBRID_X25519_MLKEM1024_AES256GCM) {
1891
+ return decryptHybridV2(parsed, options);
1892
+ }
1893
+ if (header.algorithmSuite === ALGORITHM_SUITES.HYBRID_X25519_KYBER768_AES256GCM) {
1894
+ return decryptHybridV1Legacy(parsed, options);
1791
1895
  }
1896
+ throw new OQEError(
1897
+ "UNSUPPORTED_ALGORITHM",
1898
+ "This file was not encrypted with hybrid mode. Use password decryption."
1899
+ );
1900
+ }
1901
+ async function decryptHybridV2(parsed, options) {
1902
+ const { header, keyMaterial, encryptedMetadata, encryptedContent } = parsed;
1903
+ if (header.version !== OQE_FORMAT_VERSION_V2 || !header.contentIv) {
1904
+ throw new OQEError("INVALID_HEADER", "v2 hybrid file missing content IV");
1905
+ }
1906
+ const km = parseHybridKeyMaterialV2(keyMaterial);
1907
+ const x25519EpkHex = toHex(km.x25519EphemeralPk);
1908
+ const kyberKemCtB64 = b64(km.kyberCiphertext);
1909
+ const kyberShared = await kyberDecapsulate(kyberKemCtB64, options.recipientSecretKeys.kyberSecB64);
1910
+ const sk = fromHex(options.recipientSecretKeys.x25519SecHex);
1911
+ const x25519Shared = nacl4.scalarMult(sk, km.x25519EphemeralPk);
1912
+ const kek = combinedFileKekV2(kyberShared, x25519Shared, x25519EpkHex, kyberKemCtB64);
1913
+ kyberShared.fill(0);
1914
+ x25519Shared.fill(0);
1915
+ const contentKey = nacl4.secretbox.open(km.ckWrapped, km.ckWrapNonce, kek);
1916
+ kek.fill(0);
1917
+ if (!contentKey) {
1918
+ throw new OQEError("KEY_UNWRAP_FAILED", "Could not unwrap content key \u2014 combined-KEK authentication failed");
1919
+ }
1920
+ const aad = headerAad(header);
1921
+ try {
1922
+ const metadata = await decryptSection(encryptedMetadata, contentKey, header.iv, aad, "metadata");
1923
+ const plaintext = await aesDecrypt(encryptedContent, contentKey, header.contentIv, aad);
1924
+ return buildResult(plaintext, metadata, "hybrid");
1925
+ } finally {
1926
+ contentKey.fill(0);
1927
+ }
1928
+ }
1929
+ async function decryptHybridV1Legacy(parsed, options) {
1930
+ const { header, keyMaterial, encryptedMetadata, encryptedContent } = parsed;
1792
1931
  const km = parseHybridKeyMaterial(keyMaterial);
1793
1932
  let contentKey = null;
1794
1933
  if (await isKyberAvailable()) {
@@ -1797,54 +1936,47 @@ async function decryptHybrid(encryptedData, options) {
1797
1936
  toB64(km.kyberCiphertext),
1798
1937
  options.recipientSecretKeys.kyberSecB64
1799
1938
  );
1800
- const kyberKek = hkdfFlex3(kyberShared, "omnituum/fs/kyber", "wrap-content-key");
1801
- const unwrapped = nacl4.secretbox.open(km.kyberWrappedKey, km.kyberNonce, kyberKek);
1802
- if (unwrapped) {
1803
- contentKey = unwrapped;
1804
- console.log("[OQE] Decrypted content key via Kyber (post-quantum secure)");
1805
- }
1806
- } catch (e) {
1807
- console.warn("[OQE] Kyber decapsulation failed, trying X25519:", e);
1939
+ const kyberKek = hkdfFlex2(kyberShared, "omnituum/fs/kyber", "wrap-content-key");
1940
+ contentKey = nacl4.secretbox.open(km.kyberWrappedKey, km.kyberNonce, kyberKek);
1941
+ } catch {
1808
1942
  }
1809
1943
  }
1810
1944
  if (!contentKey) {
1811
1945
  try {
1812
- const ephPk = km.x25519EphemeralPk;
1813
1946
  const sk = fromHex(options.recipientSecretKeys.x25519SecHex);
1814
- const x25519Shared = nacl4.scalarMult(sk, ephPk);
1815
- const x25519Kek = hkdfFlex3(x25519Shared, "omnituum/fs/x25519", "wrap-content-key");
1816
- const unwrapped = nacl4.secretbox.open(km.x25519WrappedKey, km.x25519Nonce, x25519Kek);
1817
- if (unwrapped) {
1818
- contentKey = unwrapped;
1819
- console.log("[OQE] Decrypted content key via X25519 (classical)");
1820
- }
1821
- } catch (e) {
1822
- console.warn("[OQE] X25519 decryption failed:", e);
1947
+ const x25519Shared = nacl4.scalarMult(sk, km.x25519EphemeralPk);
1948
+ const x25519Kek = hkdfFlex2(x25519Shared, "omnituum/fs/x25519", "wrap-content-key");
1949
+ contentKey = nacl4.secretbox.open(km.x25519WrappedKey, km.x25519Nonce, x25519Kek);
1950
+ } catch {
1823
1951
  }
1824
1952
  }
1825
1953
  if (!contentKey) {
1826
1954
  throw new OQEError("KEY_UNWRAP_FAILED", "Could not unwrap content key with provided keys");
1827
1955
  }
1828
- let metadata;
1829
1956
  try {
1830
- const metadataBytes = await aesDecrypt(encryptedMetadata, contentKey, header.iv);
1831
- metadata = parseMetadata(metadataBytes);
1832
- } catch (e) {
1833
- throw new OQEError("DECRYPTION_FAILED", "Failed to decrypt file metadata");
1957
+ const metadata = await decryptSection(encryptedMetadata, contentKey, header.iv, void 0, "metadata");
1958
+ const plaintext = await aesDecrypt(encryptedContent, contentKey, header.iv);
1959
+ return buildResult(plaintext, metadata, "hybrid");
1960
+ } finally {
1961
+ contentKey.fill(0);
1834
1962
  }
1835
- let plaintext;
1963
+ }
1964
+ async function decryptSection(ciphertext, key, iv, aad, what) {
1836
1965
  try {
1837
- plaintext = await aesDecrypt(encryptedContent, contentKey, header.iv);
1838
- } catch (e) {
1839
- throw new OQEError("DECRYPTION_FAILED", "Failed to decrypt file content");
1966
+ const bytes = await aesDecrypt(ciphertext, key, iv, aad);
1967
+ return parseMetadata(bytes);
1968
+ } catch {
1969
+ throw new OQEError("DECRYPTION_FAILED", `Failed to decrypt file ${what}`);
1840
1970
  }
1971
+ }
1972
+ function buildResult(plaintext, metadata, mode) {
1841
1973
  return {
1842
1974
  data: plaintext,
1843
1975
  filename: metadata.filename,
1844
1976
  mimeType: metadata.mimeType,
1845
1977
  originalSize: metadata.originalSize,
1846
1978
  metadata,
1847
- mode: "hybrid"
1979
+ mode
1848
1980
  };
1849
1981
  }
1850
1982
  async function decryptPassword(encryptedData, options) {
@@ -1866,27 +1998,27 @@ async function decryptPassword(encryptedData, options) {
1866
1998
  hashLength: 32,
1867
1999
  saltLength: km.salt.length
1868
2000
  });
1869
- let metadata;
2001
+ const isV2 = header.version === OQE_FORMAT_VERSION_V2;
2002
+ const aad = isV2 ? headerAad(header) : void 0;
2003
+ const contentIv = isV2 && header.contentIv ? header.contentIv : header.iv;
1870
2004
  try {
1871
- const metadataBytes = await aesDecrypt(encryptedMetadata, contentKey, header.iv);
1872
- metadata = parseMetadata(metadataBytes);
1873
- } catch (e) {
1874
- throw new OQEError("PASSWORD_WRONG", "Incorrect password or corrupted file");
1875
- }
1876
- let plaintext;
1877
- try {
1878
- plaintext = await aesDecrypt(encryptedContent, contentKey, header.iv);
1879
- } catch (e) {
1880
- throw new OQEError("DECRYPTION_FAILED", "Failed to decrypt file content");
2005
+ let metadata;
2006
+ try {
2007
+ const metadataBytes = await aesDecrypt(encryptedMetadata, contentKey, header.iv, aad);
2008
+ metadata = parseMetadata(metadataBytes);
2009
+ } catch {
2010
+ throw new OQEError("PASSWORD_WRONG", "Incorrect password or corrupted file");
2011
+ }
2012
+ let plaintext;
2013
+ try {
2014
+ plaintext = await aesDecrypt(encryptedContent, contentKey, contentIv, aad);
2015
+ } catch {
2016
+ throw new OQEError("DECRYPTION_FAILED", "Failed to decrypt file content");
2017
+ }
2018
+ return buildResult(plaintext, metadata, "password");
2019
+ } finally {
2020
+ contentKey.fill(0);
1881
2021
  }
1882
- return {
1883
- data: plaintext,
1884
- filename: metadata.filename,
1885
- mimeType: metadata.mimeType,
1886
- originalSize: metadata.originalSize,
1887
- metadata,
1888
- mode: "password"
1889
- };
1890
2022
  }
1891
2023
  async function decryptFile(encryptedData, options) {
1892
2024
  const data = await toUint8Array(encryptedData);
@@ -1983,4 +2115,4 @@ function createTunnelSession(keys) {
1983
2115
  };
1984
2116
  }
1985
2117
 
1986
- export { BLAKE3_OUTPUT_LENGTH, BOX_KEY_SIZE, BOX_NONCE_SIZE, CHACHA20_KEY_SIZE, DILITHIUM_ALGORITHM, DILITHIUM_PUBLIC_KEY_SIZE, DILITHIUM_SECRET_KEY_SIZE, DILITHIUM_SIGNATURE_SIZE, ENVELOPE_AEAD, ENVELOPE_SUITE, ENVELOPE_VERSION, KDF_CONFIG_ARGON2ID, KDF_CONFIG_PBKDF2, KYBER_SUITE, POLY1305_TAG_SIZE, SECRETBOX_KEY_SIZE, SECRETBOX_NONCE_SIZE, SECRETBOX_OVERHEAD, SecureBuffer, TUNNEL_KEY_SIZE, TUNNEL_NONCE_SIZE, TUNNEL_VERSION, VAULT_ALGORITHM, VAULT_ENCRYPTED_VERSION, VAULT_ENCRYPTED_VERSION_V2, VAULT_KDF, VAULT_KDF_V2, VAULT_VERSION, XCHACHA20_NONCE_SIZE, addIdentity, assertLen, b64, benchmarkKDF, blake3, blake3DeriveKey, blake3Hex, blake3Mac, boxDecrypt, boxEncrypt, boxUnwrapWithX25519, boxWrapWithX25519, chaCha20Poly1305Decrypt, chaCha20Poly1305Encrypt, computeIntegrityHash, computeKeyFingerprint, constantTimeEqual, createChaCha20Poly1305, createEmptyVault, createIdentity, createSession, createTunnelSession, createXChaCha20Poly1305, decryptFile, decryptFileWithPassword, decryptVault, deriveKeyFromShared, dilithiumSign, dilithiumSignRaw, dilithiumVerify, dilithiumVerifyRaw, encryptFile, encryptFileWithPassword, encryptVault, fromB64, fromHex, generateDilithiumKeypair, generateDilithiumKeypairFromSeed, generateHybridIdentity, generateKyberKeypair, generateKyberKeypairFromSeed, generateSalt, generateX25519Keypair, generateX25519KeypairFromSeed, getPublicKeys, getRecommendedConfig, getSecretKeys, getVaultKdfInfo, hkdfDerive, hkdfExpand, hkdfExtract, hkdfSha256, hkdfSplitForNoise, hkdfTripleSplitForNoise, hybridDecrypt, hybridDecryptToString, hybridEncrypt, isDilithiumAvailable, isKyberAvailable, isSessionTimedOut, isV2Vault, kdfDeriveKey, kyberDecapsulate, kyberEncapsulate, kyberUnwrapKey, kyberWrapKey, lockSecureSession, migrateEncryptedVault, needsMigration, rand12, rand24, rand32, randN, secretboxDecrypt, secretboxDecryptString, secretboxEncrypt, secretboxEncryptString, secretboxOpenRaw, secretboxRaw, sha256, sha256String, textDecoder, textEncoder, toB64, toHex, u8, ub64, unlockSecureSession, validateEncryptedVault, validateEnvelope, validateVault, withSecureData, x25519SharedSecret, xChaCha20Poly1305Decrypt, xChaCha20Poly1305Encrypt, zeroAll, zeroMemory };
2118
+ export { BLAKE3_OUTPUT_LENGTH, BOX_KEY_SIZE, BOX_NONCE_SIZE, CHACHA20_KEY_SIZE, DILITHIUM_ALGORITHM, DILITHIUM_PUBLIC_KEY_SIZE, DILITHIUM_SECRET_KEY_SIZE, DILITHIUM_SIGNATURE_SIZE, ENVELOPE_AEAD, ENVELOPE_SUITE, ENVELOPE_SUITE_V2, ENVELOPE_VERSION, ENVELOPE_VERSION_V2, KDF_CONFIG_ARGON2ID, KDF_CONFIG_PBKDF2, KYBER_CIPHERTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE, KYBER_SECRET_KEY_SIZE, KYBER_SEED_SIZE, KYBER_SHARED_SECRET_SIZE, KYBER_SUITE, POLY1305_TAG_SIZE, SECRETBOX_KEY_SIZE, SECRETBOX_NONCE_SIZE, SECRETBOX_OVERHEAD, SecureBuffer, TUNNEL_KEY_SIZE, TUNNEL_NONCE_SIZE, TUNNEL_VERSION, VAULT_ALGORITHM, VAULT_ENCRYPTED_VERSION, VAULT_ENCRYPTED_VERSION_V2, VAULT_KDF, VAULT_KDF_V2, VAULT_VERSION, XCHACHA20_NONCE_SIZE, addIdentity, assertLen, b64, benchmarkKDF, blake3, blake3DeriveKey, blake3Hex, blake3Mac, boxDecrypt, boxEncrypt, boxUnwrapWithX25519, boxWrapWithX25519, chaCha20Poly1305Decrypt, chaCha20Poly1305Encrypt, computeIntegrityHash, computeKeyFingerprint, constantTimeEqual, createChaCha20Poly1305, createEmptyVault, createIdentity, createSession, createTunnelSession, createXChaCha20Poly1305, decryptFile, decryptFileWithPassword, decryptVault, deriveKeyFromShared, dilithiumSign, dilithiumSignRaw, dilithiumVerify, dilithiumVerifyRaw, encryptFile, encryptFileWithPassword, encryptVault, fromB64, fromHex, generateDilithiumKeypair, generateDilithiumKeypairFromSeed, generateHybridIdentity, generateKyberKeypair, generateKyberKeypairFromSeed, generateSalt, generateX25519Keypair, generateX25519KeypairFromSeed, getPublicKeys, getRecommendedConfig, getSecretKeys, getVaultKdfInfo, hkdfDerive, hkdfExpand, hkdfExtract, hkdfSha256, hkdfSplitForNoise, hkdfTripleSplitForNoise, hybridDecrypt, hybridDecryptToString, hybridEncrypt, isDilithiumAvailable, isKyberAvailable, isSessionTimedOut, isV2Vault, kdfDeriveKey, kyberDecapsulate, kyberEncapsulate, kyberUnwrapKey, kyberWrapKey, lockSecureSession, migrateEncryptedVault, needsMigration, rand12, rand24, rand32, randN, secretboxDecrypt, secretboxDecryptString, secretboxEncrypt, secretboxEncryptString, secretboxOpenRaw, secretboxRaw, sha256, sha256String, textDecoder, textEncoder, toB64, toHex, u8, ub64, unlockSecureSession, validateEncryptedVault, validateEnvelope, validateVault, withSecureData, x25519SharedSecret, xChaCha20Poly1305Decrypt, xChaCha20Poly1305Encrypt, zeroAll, zeroMemory };