@omnituum/pqc-shared 0.2.6

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 (67) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +543 -0
  3. package/dist/crypto/index.cjs +807 -0
  4. package/dist/crypto/index.d.cts +641 -0
  5. package/dist/crypto/index.d.ts +641 -0
  6. package/dist/crypto/index.js +716 -0
  7. package/dist/decrypt-eSHlbh1j.d.cts +321 -0
  8. package/dist/decrypt-eSHlbh1j.d.ts +321 -0
  9. package/dist/fs/index.cjs +1168 -0
  10. package/dist/fs/index.d.cts +400 -0
  11. package/dist/fs/index.d.ts +400 -0
  12. package/dist/fs/index.js +1091 -0
  13. package/dist/index.cjs +2160 -0
  14. package/dist/index.d.cts +282 -0
  15. package/dist/index.d.ts +282 -0
  16. package/dist/index.js +2031 -0
  17. package/dist/integrity-CCYjrap3.d.ts +31 -0
  18. package/dist/integrity-Dx9jukMH.d.cts +31 -0
  19. package/dist/types-61c7Q9ri.d.ts +134 -0
  20. package/dist/types-Ch0y-n7K.d.cts +134 -0
  21. package/dist/utils/index.cjs +129 -0
  22. package/dist/utils/index.d.cts +49 -0
  23. package/dist/utils/index.d.ts +49 -0
  24. package/dist/utils/index.js +114 -0
  25. package/dist/vault/index.cjs +713 -0
  26. package/dist/vault/index.d.cts +237 -0
  27. package/dist/vault/index.d.ts +237 -0
  28. package/dist/vault/index.js +677 -0
  29. package/dist/version-BygzPVGs.d.cts +55 -0
  30. package/dist/version-BygzPVGs.d.ts +55 -0
  31. package/package.json +86 -0
  32. package/src/crypto/dilithium.ts +233 -0
  33. package/src/crypto/hybrid.ts +358 -0
  34. package/src/crypto/index.ts +181 -0
  35. package/src/crypto/kyber.ts +199 -0
  36. package/src/crypto/nacl.ts +204 -0
  37. package/src/crypto/primitives/blake3.ts +141 -0
  38. package/src/crypto/primitives/chacha.ts +211 -0
  39. package/src/crypto/primitives/hkdf.ts +192 -0
  40. package/src/crypto/primitives/index.ts +54 -0
  41. package/src/crypto/primitives.ts +144 -0
  42. package/src/crypto/x25519.ts +134 -0
  43. package/src/fs/aes.ts +343 -0
  44. package/src/fs/argon2.ts +184 -0
  45. package/src/fs/browser.ts +408 -0
  46. package/src/fs/decrypt.ts +320 -0
  47. package/src/fs/encrypt.ts +324 -0
  48. package/src/fs/format.ts +425 -0
  49. package/src/fs/index.ts +144 -0
  50. package/src/fs/types.ts +304 -0
  51. package/src/index.ts +414 -0
  52. package/src/kdf/index.ts +311 -0
  53. package/src/runtime/crypto.ts +16 -0
  54. package/src/security/index.ts +345 -0
  55. package/src/tunnel/index.ts +39 -0
  56. package/src/tunnel/session.ts +229 -0
  57. package/src/tunnel/types.ts +115 -0
  58. package/src/utils/entropy.ts +128 -0
  59. package/src/utils/index.ts +25 -0
  60. package/src/utils/integrity.ts +95 -0
  61. package/src/vault/decrypt.ts +167 -0
  62. package/src/vault/encrypt.ts +207 -0
  63. package/src/vault/index.ts +71 -0
  64. package/src/vault/manager.ts +327 -0
  65. package/src/vault/migrate.ts +190 -0
  66. package/src/vault/types.ts +177 -0
  67. package/src/version.ts +304 -0
@@ -0,0 +1,677 @@
1
+ import '@noble/hashes/sha256';
2
+ import '@noble/hashes/hmac';
3
+ import { argon2id } from 'hash-wasm';
4
+ import nacl from 'tweetnacl';
5
+ import '@noble/hashes/blake3';
6
+ import '@noble/ciphers/chacha';
7
+ import '@noble/hashes/hkdf';
8
+ import '@noble/hashes/sha512';
9
+
10
+ // src/vault/types.ts
11
+ var DEFAULT_VAULT_SETTINGS = {
12
+ autoUnlock: false,
13
+ lockTimeout: 15,
14
+ showFingerprints: true
15
+ };
16
+ var PBKDF2_ITERATIONS = 6e5;
17
+ new TextEncoder();
18
+ new TextDecoder();
19
+ function toB64(bytes) {
20
+ let binary = "";
21
+ for (let i = 0; i < bytes.length; i++) {
22
+ binary += String.fromCharCode(bytes[i]);
23
+ }
24
+ return btoa(binary);
25
+ }
26
+ function fromB64(str) {
27
+ const binary = atob(str);
28
+ const bytes = new Uint8Array(binary.length);
29
+ for (let i = 0; i < binary.length; i++) {
30
+ bytes[i] = binary.charCodeAt(i);
31
+ }
32
+ return bytes;
33
+ }
34
+ function toHex(bytes) {
35
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
36
+ }
37
+ var b64 = toB64;
38
+
39
+ // src/version.ts
40
+ var VAULT_VERSION = "omnituum.vault.v1";
41
+ var VAULT_ENCRYPTED_VERSION = "omnituum.vault.enc.v1";
42
+ var VAULT_ENCRYPTED_VERSION_V2 = "omnituum.vault.enc.v2";
43
+ var VAULT_KDF = "PBKDF2-SHA256";
44
+ var VAULT_KDF_V2 = "Argon2id";
45
+ var VAULT_ALGORITHM = "AES-256-GCM";
46
+ var SUPPORTED_VAULT_VERSIONS = [
47
+ VAULT_VERSION
48
+ ];
49
+ var SUPPORTED_VAULT_ENCRYPTED_VERSIONS = [
50
+ VAULT_ENCRYPTED_VERSION,
51
+ VAULT_ENCRYPTED_VERSION_V2
52
+ ];
53
+ var VersionMismatchError = class extends Error {
54
+ constructor(type, expected, received) {
55
+ super(
56
+ `Version mismatch for ${type}: expected one of [${expected.join(", ")}], got "${received}". This may indicate data corruption or a newer format. See pqc-docs/specs/ for format specifications.`
57
+ );
58
+ this.type = type;
59
+ this.expected = expected;
60
+ this.received = received;
61
+ this.name = "VersionMismatchError";
62
+ }
63
+ };
64
+ function assertVaultVersion(version) {
65
+ if (!SUPPORTED_VAULT_VERSIONS.includes(version)) {
66
+ throw new VersionMismatchError("vault", SUPPORTED_VAULT_VERSIONS, version);
67
+ }
68
+ }
69
+ function assertVaultEncryptedVersion(version) {
70
+ if (!SUPPORTED_VAULT_ENCRYPTED_VERSIONS.includes(version)) {
71
+ throw new VersionMismatchError("vault_encrypted", SUPPORTED_VAULT_ENCRYPTED_VERSIONS, version);
72
+ }
73
+ }
74
+ var KDF_CONFIG_PBKDF2 = {
75
+ algorithm: "PBKDF2-SHA256",
76
+ pbkdf2Iterations: 6e5,
77
+ // OWASP 2023
78
+ saltLength: 32,
79
+ hashLength: 32
80
+ };
81
+ var KDF_CONFIG_ARGON2ID = {
82
+ algorithm: "Argon2id",
83
+ argon2MemoryCost: 65536,
84
+ // 64 MB
85
+ argon2TimeCost: 3,
86
+ argon2Parallelism: 4,
87
+ saltLength: 32,
88
+ hashLength: 32
89
+ };
90
+ var KDF_CONFIG_DEFAULT = KDF_CONFIG_PBKDF2;
91
+ var textEncoder2 = new TextEncoder();
92
+ async function derivePBKDF2(password, salt, iterations, hashLength) {
93
+ const passwordKey = await globalThis.crypto.subtle.importKey(
94
+ "raw",
95
+ textEncoder2.encode(password),
96
+ "PBKDF2",
97
+ false,
98
+ ["deriveBits"]
99
+ );
100
+ const saltBuffer = new ArrayBuffer(salt.length);
101
+ new Uint8Array(saltBuffer).set(salt);
102
+ const bits = await globalThis.crypto.subtle.deriveBits(
103
+ {
104
+ name: "PBKDF2",
105
+ salt: saltBuffer,
106
+ iterations,
107
+ hash: "SHA-256"
108
+ },
109
+ passwordKey,
110
+ hashLength * 8
111
+ );
112
+ return new Uint8Array(bits);
113
+ }
114
+ async function deriveArgon2id(password, salt, memoryCost, timeCost, parallelism, hashLength) {
115
+ const hash = await argon2id({
116
+ password,
117
+ salt,
118
+ parallelism,
119
+ iterations: timeCost,
120
+ memorySize: memoryCost,
121
+ hashLength,
122
+ outputType: "binary"
123
+ });
124
+ return new Uint8Array(hash);
125
+ }
126
+ async function kdfDeriveKey(password, salt, config = KDF_CONFIG_DEFAULT) {
127
+ if (salt.length !== config.saltLength) {
128
+ throw new Error(`Salt must be ${config.saltLength} bytes, got ${salt.length}`);
129
+ }
130
+ if (config.algorithm === "PBKDF2-SHA256") {
131
+ return derivePBKDF2(
132
+ password,
133
+ salt,
134
+ config.pbkdf2Iterations ?? 6e5,
135
+ config.hashLength
136
+ );
137
+ } else if (config.algorithm === "Argon2id") {
138
+ return deriveArgon2id(
139
+ password,
140
+ salt,
141
+ config.argon2MemoryCost ?? 65536,
142
+ config.argon2TimeCost ?? 3,
143
+ config.argon2Parallelism ?? 4,
144
+ config.hashLength
145
+ );
146
+ } else {
147
+ throw new Error(`Unsupported KDF algorithm: ${config.algorithm}`);
148
+ }
149
+ }
150
+ function configFromParams(algorithm, params) {
151
+ {
152
+ return {
153
+ algorithm,
154
+ argon2MemoryCost: params.memoryCost,
155
+ argon2TimeCost: params.timeCost,
156
+ argon2Parallelism: params.parallelism,
157
+ saltLength: 32,
158
+ hashLength: 32
159
+ };
160
+ }
161
+ }
162
+
163
+ // src/vault/encrypt.ts
164
+ var textEncoder3 = new TextEncoder();
165
+ async function deriveKey(password, salt, iterations = PBKDF2_ITERATIONS) {
166
+ const passwordKey = await globalThis.crypto.subtle.importKey(
167
+ "raw",
168
+ textEncoder3.encode(password),
169
+ "PBKDF2",
170
+ false,
171
+ ["deriveBits", "deriveKey"]
172
+ );
173
+ const saltArrayBuffer = new ArrayBuffer(salt.length);
174
+ new Uint8Array(saltArrayBuffer).set(salt);
175
+ return globalThis.crypto.subtle.deriveKey(
176
+ {
177
+ name: "PBKDF2",
178
+ salt: saltArrayBuffer,
179
+ iterations,
180
+ hash: "SHA-256"
181
+ },
182
+ passwordKey,
183
+ { name: "AES-GCM", length: 256 },
184
+ false,
185
+ // not extractable
186
+ ["encrypt", "decrypt"]
187
+ );
188
+ }
189
+ async function encryptVault(vault, password) {
190
+ const salt = globalThis.crypto.getRandomValues(new Uint8Array(32));
191
+ const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
192
+ const key = await deriveKey(password, salt);
193
+ const plaintext = textEncoder3.encode(JSON.stringify(vault));
194
+ const ciphertext = await globalThis.crypto.subtle.encrypt(
195
+ { name: "AES-GCM", iv },
196
+ key,
197
+ plaintext
198
+ );
199
+ return {
200
+ version: VAULT_ENCRYPTED_VERSION,
201
+ kdf: VAULT_KDF,
202
+ iterations: PBKDF2_ITERATIONS,
203
+ salt: toB64(salt),
204
+ iv: toB64(iv),
205
+ ciphertext: toB64(new Uint8Array(ciphertext)),
206
+ algorithm: VAULT_ALGORITHM
207
+ };
208
+ }
209
+ async function encryptVaultToBlob(vault, password) {
210
+ const encrypted = await encryptVault(vault, password);
211
+ const json = JSON.stringify(encrypted, null, 2);
212
+ return new Blob([json], { type: "application/json" });
213
+ }
214
+ async function encryptVaultToDataURL(vault, password) {
215
+ const encrypted = await encryptVault(vault, password);
216
+ const json = JSON.stringify(encrypted, null, 2);
217
+ return "data:application/json;charset=utf-8," + encodeURIComponent(json);
218
+ }
219
+ async function encryptVaultV2(vault, password) {
220
+ const salt = globalThis.crypto.getRandomValues(new Uint8Array(32));
221
+ const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
222
+ const keyBytes = await kdfDeriveKey(password, salt, KDF_CONFIG_ARGON2ID);
223
+ const keyBuffer = new ArrayBuffer(keyBytes.length);
224
+ new Uint8Array(keyBuffer).set(keyBytes);
225
+ const key = await globalThis.crypto.subtle.importKey(
226
+ "raw",
227
+ keyBuffer,
228
+ { name: "AES-GCM", length: 256 },
229
+ false,
230
+ ["encrypt"]
231
+ );
232
+ const plaintext = textEncoder3.encode(JSON.stringify(vault));
233
+ const ciphertext = await globalThis.crypto.subtle.encrypt(
234
+ { name: "AES-GCM", iv },
235
+ key,
236
+ plaintext
237
+ );
238
+ return {
239
+ version: VAULT_ENCRYPTED_VERSION_V2,
240
+ kdf: VAULT_KDF_V2,
241
+ memoryCost: KDF_CONFIG_ARGON2ID.argon2MemoryCost,
242
+ timeCost: KDF_CONFIG_ARGON2ID.argon2TimeCost,
243
+ parallelism: KDF_CONFIG_ARGON2ID.argon2Parallelism,
244
+ salt: toB64(salt),
245
+ iv: toB64(iv),
246
+ ciphertext: toB64(new Uint8Array(ciphertext)),
247
+ algorithm: VAULT_ALGORITHM
248
+ };
249
+ }
250
+
251
+ // src/vault/decrypt.ts
252
+ var textDecoder2 = new TextDecoder();
253
+ async function decryptVault(encryptedFile, password) {
254
+ assertVaultEncryptedVersion(encryptedFile.version);
255
+ const salt = fromB64(encryptedFile.salt);
256
+ const iv = fromB64(encryptedFile.iv);
257
+ const ciphertext = fromB64(encryptedFile.ciphertext);
258
+ let key;
259
+ if (encryptedFile.version === VAULT_ENCRYPTED_VERSION_V2) {
260
+ const v2File = encryptedFile;
261
+ const kdfConfig = configFromParams("Argon2id", {
262
+ memoryCost: v2File.memoryCost,
263
+ timeCost: v2File.timeCost,
264
+ parallelism: v2File.parallelism
265
+ });
266
+ const keyBytes = await kdfDeriveKey(password, salt, kdfConfig);
267
+ const keyBuffer = new ArrayBuffer(keyBytes.length);
268
+ new Uint8Array(keyBuffer).set(keyBytes);
269
+ key = await globalThis.crypto.subtle.importKey(
270
+ "raw",
271
+ keyBuffer,
272
+ { name: "AES-GCM", length: 256 },
273
+ false,
274
+ ["decrypt"]
275
+ );
276
+ } else {
277
+ key = await deriveKey(password, salt, encryptedFile.iterations);
278
+ }
279
+ try {
280
+ const ivArrayBuffer = new ArrayBuffer(iv.length);
281
+ new Uint8Array(ivArrayBuffer).set(iv);
282
+ const ciphertextArrayBuffer = new ArrayBuffer(ciphertext.length);
283
+ new Uint8Array(ciphertextArrayBuffer).set(ciphertext);
284
+ const plaintext = await globalThis.crypto.subtle.decrypt(
285
+ { name: "AES-GCM", iv: ivArrayBuffer },
286
+ key,
287
+ ciphertextArrayBuffer
288
+ );
289
+ const json = textDecoder2.decode(plaintext);
290
+ const vault = JSON.parse(json);
291
+ assertVaultVersion(vault.version);
292
+ if (!Array.isArray(vault.identities)) {
293
+ throw new Error("Invalid vault structure: missing identities array");
294
+ }
295
+ return vault;
296
+ } catch (error) {
297
+ if (error instanceof DOMException && error.name === "OperationError") {
298
+ throw new Error("Incorrect password or corrupted vault");
299
+ }
300
+ throw error;
301
+ }
302
+ }
303
+ async function decryptVaultFromJson(json, password) {
304
+ const encryptedFile = JSON.parse(json);
305
+ return decryptVault(encryptedFile, password);
306
+ }
307
+ async function decryptVaultFromFile(file, password) {
308
+ const text = await file.text();
309
+ return decryptVaultFromJson(text, password);
310
+ }
311
+ function isValidEncryptedVaultFile(json) {
312
+ try {
313
+ const parsed = JSON.parse(json);
314
+ return parsed.version === VAULT_ENCRYPTED_VERSION && parsed.kdf === VAULT_KDF && typeof parsed.iterations === "number" && typeof parsed.salt === "string" && typeof parsed.iv === "string" && typeof parsed.ciphertext === "string" && parsed.algorithm === VAULT_ALGORITHM;
315
+ } catch {
316
+ return false;
317
+ }
318
+ }
319
+
320
+ // src/utils/integrity.ts
321
+ function computeIntegrityHash(identities) {
322
+ const canonical = identities.map((i) => ({
323
+ id: i.id,
324
+ name: i.name,
325
+ x25519PubHex: i.x25519PubHex,
326
+ kyberPubB64: i.kyberPubB64,
327
+ createdAt: i.createdAt,
328
+ rotationCount: i.rotationCount
329
+ }));
330
+ const serialized = JSON.stringify(canonical, Object.keys(canonical[0] || {}).sort());
331
+ return computeStringHash(serialized);
332
+ }
333
+ function computeStringHash(str) {
334
+ let hash = 0;
335
+ for (let i = 0; i < str.length; i++) {
336
+ const char = str.charCodeAt(i);
337
+ hash = (hash << 5) - hash + char;
338
+ hash = hash & hash;
339
+ }
340
+ return Math.abs(hash).toString(16).padStart(16, "0");
341
+ }
342
+
343
+ // src/utils/entropy.ts
344
+ function generateId() {
345
+ const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16));
346
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
347
+ }
348
+ nacl.secretbox.keyLength;
349
+ nacl.secretbox.nonceLength;
350
+ nacl.secretbox.overheadLength;
351
+ nacl.box.publicKeyLength;
352
+ nacl.box.nonceLength;
353
+ function generateX25519Keypair() {
354
+ const kp = nacl.box.keyPair();
355
+ return {
356
+ publicHex: "0x" + toHex(kp.publicKey),
357
+ secretHex: "0x" + toHex(kp.secretKey),
358
+ publicBytes: kp.publicKey,
359
+ secretBytes: kp.secretKey
360
+ };
361
+ }
362
+ var kyberModule = null;
363
+ async function loadKyber() {
364
+ if (kyberModule) return kyberModule;
365
+ try {
366
+ const m = await import('kyber-crystals');
367
+ const k = m.default ?? m;
368
+ kyberModule = k.kyber ?? k;
369
+ return kyberModule;
370
+ } catch (e) {
371
+ console.warn("[Kyber] Failed to load kyber-crystals:", e);
372
+ return null;
373
+ }
374
+ }
375
+ async function generateKyberKeypair() {
376
+ try {
377
+ const mod = await loadKyber();
378
+ if (!mod) return null;
379
+ if (mod?.ready?.then) {
380
+ await mod.ready;
381
+ }
382
+ const fn = mod?.keypair ?? mod?.keyPair ?? mod?.generateKeyPair ?? null;
383
+ if (typeof fn !== "function") {
384
+ console.warn("[Kyber] No keypair function found");
385
+ return null;
386
+ }
387
+ const kp = await fn.call(mod);
388
+ const pub = kp?.publicKey ?? kp?.public ?? kp?.pk;
389
+ const priv = kp?.privateKey ?? kp?.secretKey ?? kp?.secret ?? kp?.sk;
390
+ if (!pub || !priv) {
391
+ console.warn("[Kyber] Invalid keypair result");
392
+ return null;
393
+ }
394
+ return {
395
+ publicB64: b64(new Uint8Array(pub)),
396
+ secretB64: b64(new Uint8Array(priv))
397
+ };
398
+ } catch (e) {
399
+ console.warn("[Kyber] Key generation failed:", e);
400
+ return null;
401
+ }
402
+ }
403
+
404
+ // src/vault/manager.ts
405
+ function createEmptyVault() {
406
+ const now = (/* @__PURE__ */ new Date()).toISOString();
407
+ return {
408
+ version: VAULT_VERSION,
409
+ identities: [],
410
+ settings: {
411
+ autoUnlock: false,
412
+ lockTimeout: 15,
413
+ showFingerprints: true
414
+ },
415
+ integrityHash: computeIntegrityHash([]),
416
+ createdAt: now,
417
+ modifiedAt: now
418
+ };
419
+ }
420
+ async function createIdentity(name) {
421
+ const x25519 = generateX25519Keypair();
422
+ const kyber = await generateKyberKeypair();
423
+ if (!kyber) {
424
+ console.error("Kyber key generation failed");
425
+ return null;
426
+ }
427
+ const now = (/* @__PURE__ */ new Date()).toISOString();
428
+ const deviceFingerprint = await getDeviceFingerprint();
429
+ return {
430
+ id: generateId(),
431
+ name,
432
+ x25519PubHex: x25519.publicHex,
433
+ x25519SecHex: x25519.secretHex,
434
+ kyberPubB64: kyber.publicB64,
435
+ kyberSecB64: kyber.secretB64,
436
+ createdAt: now,
437
+ rotationCount: 0,
438
+ deviceFingerprint
439
+ };
440
+ }
441
+ function addIdentity(vault, identity) {
442
+ const identities = [...vault.identities, identity];
443
+ return {
444
+ ...vault,
445
+ identities,
446
+ integrityHash: computeIntegrityHash(identities),
447
+ modifiedAt: (/* @__PURE__ */ new Date()).toISOString()
448
+ };
449
+ }
450
+ function removeIdentity(vault, identityId) {
451
+ const identities = vault.identities.filter((i) => i.id !== identityId);
452
+ return {
453
+ ...vault,
454
+ identities,
455
+ integrityHash: computeIntegrityHash(identities),
456
+ modifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
457
+ settings: {
458
+ ...vault.settings,
459
+ lastUsedIdentity: vault.settings.lastUsedIdentity === identityId ? void 0 : vault.settings.lastUsedIdentity
460
+ }
461
+ };
462
+ }
463
+ async function rotateIdentityKeys(vault, identityId) {
464
+ const index = vault.identities.findIndex((i) => i.id === identityId);
465
+ if (index === -1) return null;
466
+ const existing = vault.identities[index];
467
+ const x25519 = generateX25519Keypair();
468
+ const kyber = await generateKyberKeypair();
469
+ if (!kyber) return null;
470
+ const now = (/* @__PURE__ */ new Date()).toISOString();
471
+ const updated = {
472
+ ...existing,
473
+ x25519PubHex: x25519.publicHex,
474
+ x25519SecHex: x25519.secretHex,
475
+ kyberPubB64: kyber.publicB64,
476
+ kyberSecB64: kyber.secretB64,
477
+ lastRotatedAt: now,
478
+ rotationCount: existing.rotationCount + 1
479
+ };
480
+ const identities = [...vault.identities];
481
+ identities[index] = updated;
482
+ return {
483
+ ...vault,
484
+ identities,
485
+ integrityHash: computeIntegrityHash(identities),
486
+ modifiedAt: now
487
+ };
488
+ }
489
+ function updateIdentityMetadata(vault, identityId, updates) {
490
+ const identities = vault.identities.map(
491
+ (i) => i.id === identityId ? { ...i, ...updates } : i
492
+ );
493
+ return {
494
+ ...vault,
495
+ identities,
496
+ integrityHash: computeIntegrityHash(identities),
497
+ modifiedAt: (/* @__PURE__ */ new Date()).toISOString()
498
+ };
499
+ }
500
+ function updateSettings(vault, settings) {
501
+ return {
502
+ ...vault,
503
+ settings: { ...vault.settings, ...settings },
504
+ modifiedAt: (/* @__PURE__ */ new Date()).toISOString()
505
+ };
506
+ }
507
+ function setActiveIdentity(vault, identityId) {
508
+ return updateSettings(vault, { lastUsedIdentity: identityId });
509
+ }
510
+ async function exportVault(vault, password) {
511
+ return encryptVaultToBlob(vault, password);
512
+ }
513
+ async function importVault(file, password) {
514
+ return decryptVaultFromFile(file, password);
515
+ }
516
+ async function downloadVault(vault, password) {
517
+ const blob = await exportVault(vault, password);
518
+ const url = URL.createObjectURL(blob);
519
+ const a = document.createElement("a");
520
+ a.href = url;
521
+ a.download = `omnituum_vault_${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.enc`;
522
+ document.body.appendChild(a);
523
+ a.click();
524
+ document.body.removeChild(a);
525
+ URL.revokeObjectURL(url);
526
+ }
527
+ var currentSession = {
528
+ unlocked: false,
529
+ sessionKey: null,
530
+ unlockedAt: null,
531
+ activeIdentityId: null
532
+ };
533
+ function getSession() {
534
+ return { ...currentSession };
535
+ }
536
+ async function unlockSession(password, vault) {
537
+ try {
538
+ const salt = globalThis.crypto.getRandomValues(new Uint8Array(32));
539
+ const passwordKey = await globalThis.crypto.subtle.importKey(
540
+ "raw",
541
+ new TextEncoder().encode(password),
542
+ "PBKDF2",
543
+ false,
544
+ ["deriveBits", "deriveKey"]
545
+ );
546
+ const sessionKey = await globalThis.crypto.subtle.deriveKey(
547
+ {
548
+ name: "PBKDF2",
549
+ salt,
550
+ iterations: 1e5,
551
+ // Faster for session key
552
+ hash: "SHA-256"
553
+ },
554
+ passwordKey,
555
+ { name: "AES-GCM", length: 256 },
556
+ false,
557
+ ["encrypt", "decrypt"]
558
+ );
559
+ currentSession = {
560
+ unlocked: true,
561
+ sessionKey,
562
+ unlockedAt: Date.now(),
563
+ activeIdentityId: vault.settings.lastUsedIdentity || vault.identities[0]?.id || null
564
+ };
565
+ return true;
566
+ } catch {
567
+ return false;
568
+ }
569
+ }
570
+ function lockSession() {
571
+ currentSession = {
572
+ unlocked: false,
573
+ sessionKey: null,
574
+ unlockedAt: null,
575
+ activeIdentityId: null
576
+ };
577
+ }
578
+ function setSessionActiveIdentity(identityId) {
579
+ if (currentSession.unlocked) {
580
+ currentSession.activeIdentityId = identityId;
581
+ }
582
+ }
583
+ async function getDeviceFingerprint() {
584
+ const data = [
585
+ navigator.userAgent,
586
+ navigator.language,
587
+ screen.width,
588
+ screen.height,
589
+ (/* @__PURE__ */ new Date()).getTimezoneOffset()
590
+ ].join("|");
591
+ const hash = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(data));
592
+ return toHex(new Uint8Array(hash)).slice(0, 16);
593
+ }
594
+
595
+ // src/security/index.ts
596
+ function zeroMemory(arr) {
597
+ if (!arr || arr.length === 0) return;
598
+ arr.fill(0);
599
+ if (arr[0] !== 0) {
600
+ throw new Error("Memory zeroing failed");
601
+ }
602
+ }
603
+
604
+ // src/vault/migrate.ts
605
+ function needsMigration(encryptedVault) {
606
+ return encryptedVault.version === VAULT_ENCRYPTED_VERSION;
607
+ }
608
+ function isV2Vault(encryptedVault) {
609
+ return encryptedVault.version === VAULT_ENCRYPTED_VERSION_V2;
610
+ }
611
+ function getVaultKdfInfo(encryptedVault) {
612
+ if (encryptedVault.version === VAULT_ENCRYPTED_VERSION_V2) {
613
+ const v2 = encryptedVault;
614
+ return {
615
+ kdf: `Argon2id (${v2.memoryCost / 1024}MB, ${v2.timeCost} iterations)`,
616
+ version: "v2",
617
+ isSecure: true
618
+ };
619
+ } else {
620
+ const v1 = encryptedVault;
621
+ return {
622
+ kdf: `PBKDF2-SHA256 (${v1.iterations.toLocaleString()} iterations)`,
623
+ version: "v1",
624
+ isSecure: true,
625
+ // Still secure, just not as modern
626
+ recommendation: "Consider upgrading to Argon2id for stronger protection"
627
+ };
628
+ }
629
+ }
630
+ async function migrateEncryptedVault(options) {
631
+ const { encryptedVault, password, keepBackup = false } = options;
632
+ if (isV2Vault(encryptedVault)) {
633
+ throw new Error("Vault is already v2 format, no migration needed");
634
+ }
635
+ let decryptedVaultJson = null;
636
+ try {
637
+ const vault = await decryptVault(encryptedVault, password);
638
+ const vaultJson = JSON.stringify(vault);
639
+ decryptedVaultJson = new TextEncoder().encode(vaultJson);
640
+ const newEncryptedVault = await encryptVaultV2(vault, password);
641
+ return {
642
+ encryptedVault: newEncryptedVault,
643
+ backup: keepBackup ? encryptedVault : void 0,
644
+ sourceVersion: encryptedVault.version,
645
+ targetVersion: VAULT_ENCRYPTED_VERSION_V2,
646
+ migratedAt: (/* @__PURE__ */ new Date()).toISOString()
647
+ };
648
+ } finally {
649
+ if (decryptedVaultJson) {
650
+ zeroMemory(decryptedVaultJson);
651
+ }
652
+ }
653
+ }
654
+ async function validateMigration(original, migrated, password) {
655
+ let originalVaultJson = null;
656
+ let migratedVaultJson = null;
657
+ try {
658
+ const originalVault = await decryptVault(original, password);
659
+ const migratedVault = await decryptVault(migrated, password);
660
+ originalVaultJson = new TextEncoder().encode(JSON.stringify(originalVault));
661
+ migratedVaultJson = new TextEncoder().encode(JSON.stringify(migratedVault));
662
+ if (originalVaultJson.length !== migratedVaultJson.length) {
663
+ return false;
664
+ }
665
+ for (let i = 0; i < originalVaultJson.length; i++) {
666
+ if (originalVaultJson[i] !== migratedVaultJson[i]) {
667
+ return false;
668
+ }
669
+ }
670
+ return true;
671
+ } finally {
672
+ if (originalVaultJson) zeroMemory(originalVaultJson);
673
+ if (migratedVaultJson) zeroMemory(migratedVaultJson);
674
+ }
675
+ }
676
+
677
+ export { DEFAULT_VAULT_SETTINGS, PBKDF2_ITERATIONS, addIdentity, createEmptyVault, createIdentity, decryptVault, decryptVaultFromFile, decryptVaultFromJson, deriveKey, downloadVault, encryptVault, encryptVaultToBlob, encryptVaultToDataURL, encryptVaultV2, exportVault, getSession, getVaultKdfInfo, importVault, isV2Vault, isValidEncryptedVaultFile, lockSession, migrateEncryptedVault, needsMigration, removeIdentity, rotateIdentityKeys, setActiveIdentity, setSessionActiveIdentity, unlockSession, updateIdentityMetadata, updateSettings, validateMigration };