@msdis/shield 0.2.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 (92) hide show
  1. package/LICENSE +140 -0
  2. package/README.md +106 -0
  3. package/dist/aead/index.d.ts +37 -0
  4. package/dist/aead/index.js +7 -0
  5. package/dist/aead/index.js.map +1 -0
  6. package/dist/asymmetric/index.d.ts +32 -0
  7. package/dist/asymmetric/index.js +6 -0
  8. package/dist/asymmetric/index.js.map +1 -0
  9. package/dist/chunk-3DQPQCAR.js +114 -0
  10. package/dist/chunk-3DQPQCAR.js.map +1 -0
  11. package/dist/chunk-3HCT6A2P.js +55 -0
  12. package/dist/chunk-3HCT6A2P.js.map +1 -0
  13. package/dist/chunk-AB2WZ7Y2.js +57 -0
  14. package/dist/chunk-AB2WZ7Y2.js.map +1 -0
  15. package/dist/chunk-BUFRR5PB.js +9 -0
  16. package/dist/chunk-BUFRR5PB.js.map +1 -0
  17. package/dist/chunk-CYIGDF63.js +30 -0
  18. package/dist/chunk-CYIGDF63.js.map +1 -0
  19. package/dist/chunk-EOXWR7DS.js +153 -0
  20. package/dist/chunk-EOXWR7DS.js.map +1 -0
  21. package/dist/chunk-FUDDBD2G.js +43 -0
  22. package/dist/chunk-FUDDBD2G.js.map +1 -0
  23. package/dist/chunk-JSKIWIEC.js +56 -0
  24. package/dist/chunk-JSKIWIEC.js.map +1 -0
  25. package/dist/chunk-JVFP2GAO.js +66 -0
  26. package/dist/chunk-JVFP2GAO.js.map +1 -0
  27. package/dist/chunk-KNCZMIZA.js +55 -0
  28. package/dist/chunk-KNCZMIZA.js.map +1 -0
  29. package/dist/chunk-MJO7IJZC.js +44 -0
  30. package/dist/chunk-MJO7IJZC.js.map +1 -0
  31. package/dist/chunk-MPWYZXW7.js +66 -0
  32. package/dist/chunk-MPWYZXW7.js.map +1 -0
  33. package/dist/chunk-OA5ARYJM.js +73 -0
  34. package/dist/chunk-OA5ARYJM.js.map +1 -0
  35. package/dist/chunk-OPHN2B3N.js +147 -0
  36. package/dist/chunk-OPHN2B3N.js.map +1 -0
  37. package/dist/chunk-RTAJJZKO.js +116 -0
  38. package/dist/chunk-RTAJJZKO.js.map +1 -0
  39. package/dist/chunk-SCHZI6YY.js +35 -0
  40. package/dist/chunk-SCHZI6YY.js.map +1 -0
  41. package/dist/chunk-T3IV7SHD.js +388 -0
  42. package/dist/chunk-T3IV7SHD.js.map +1 -0
  43. package/dist/chunk-U65A4HIY.js +133 -0
  44. package/dist/chunk-U65A4HIY.js.map +1 -0
  45. package/dist/core/index.d.ts +20 -0
  46. package/dist/core/index.js +6 -0
  47. package/dist/core/index.js.map +1 -0
  48. package/dist/encoding-B-cb7Duu.d.ts +37 -0
  49. package/dist/errors-C79jA9vX.d.ts +65 -0
  50. package/dist/file-encryption/index.d.ts +135 -0
  51. package/dist/file-encryption/index.js +11 -0
  52. package/dist/file-encryption/index.js.map +1 -0
  53. package/dist/format-versioning/index.d.ts +37 -0
  54. package/dist/format-versioning/index.js +4 -0
  55. package/dist/format-versioning/index.js.map +1 -0
  56. package/dist/index.d.ts +27 -0
  57. package/dist/index.js +24 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/integrity/index.d.ts +46 -0
  60. package/dist/integrity/index.js +6 -0
  61. package/dist/integrity/index.js.map +1 -0
  62. package/dist/kdf/index.d.ts +104 -0
  63. package/dist/kdf/index.js +7 -0
  64. package/dist/kdf/index.js.map +1 -0
  65. package/dist/key-management/index.d.ts +69 -0
  66. package/dist/key-management/index.js +10 -0
  67. package/dist/key-management/index.js.map +1 -0
  68. package/dist/migrations/index.d.ts +41 -0
  69. package/dist/migrations/index.js +4 -0
  70. package/dist/migrations/index.js.map +1 -0
  71. package/dist/post-quantum/index.d.ts +153 -0
  72. package/dist/post-quantum/index.js +3 -0
  73. package/dist/post-quantum/index.js.map +1 -0
  74. package/dist/random/index.d.ts +28 -0
  75. package/dist/random/index.js +5 -0
  76. package/dist/random/index.js.map +1 -0
  77. package/dist/secure-memory/index.d.ts +40 -0
  78. package/dist/secure-memory/index.js +5 -0
  79. package/dist/secure-memory/index.js.map +1 -0
  80. package/dist/signing/index.d.ts +41 -0
  81. package/dist/signing/index.js +5 -0
  82. package/dist/signing/index.js.map +1 -0
  83. package/dist/totp/index.d.ts +69 -0
  84. package/dist/totp/index.js +4 -0
  85. package/dist/totp/index.js.map +1 -0
  86. package/dist/vault-crypto/index.d.ts +225 -0
  87. package/dist/vault-crypto/index.js +465 -0
  88. package/dist/vault-crypto/index.js.map +1 -0
  89. package/dist/vault-encryption/index.d.ts +40 -0
  90. package/dist/vault-encryption/index.js +9 -0
  91. package/dist/vault-encryption/index.js.map +1 -0
  92. package/package.json +137 -0
@@ -0,0 +1,225 @@
1
+ export { b as base64ToBytes, c as bytesToBase64 } from '../encoding-B-cb7Duu.js';
2
+ import { KdfParams as KdfParams$1 } from '../kdf/index.js';
3
+ import { UserKeyBundle as UserKeyBundle$1 } from '../key-management/index.js';
4
+ import { SecureBuffer } from '../secure-memory/index.js';
5
+
6
+ /**
7
+ * dis-vault-crypto — the Singra Vault *application crypto profile*.
8
+ *
9
+ * This module is the stable, named API that Singra Vault (and, transitively,
10
+ * Singra Premium) consume. It does NOT re-implement any primitive: every
11
+ * operation is composed from the audited DIS modules (`kdf`, `aead`,
12
+ * `vault-encryption`, `key-management`, `asymmetric`, `post-quantum`). What it
13
+ * adds is the *application-specific composition and versioned envelope formats*
14
+ * that Singra has always used:
15
+ *
16
+ * - device-key strengthened Argon2id derivation (HKDF info `SINGRA_DEVICE_KEY_V1`)
17
+ * - the `sv-vault-v1:` vault-item envelope (entry-id AAD)
18
+ * - the two-tier UserKey model (`usk-wrap-v2:`) and private-key wrapping (`usk-v1:`)
19
+ * - sharing / emergency-access key material (`pq-v2:` hybrid keypair envelope)
20
+ * - KDF auto-upgrade, verification hashes, and re-encryption helpers
21
+ *
22
+ * Keeping these names and formats here means applications need ZERO crypto
23
+ * code of their own — they import this profile and nothing else. Every byte
24
+ * format is covered by golden-vector tests proving compatibility with the
25
+ * pre-extraction Singra implementation.
26
+ */
27
+
28
+ /** The latest KDF version. Newly set-up accounts use this version. */
29
+ declare const CURRENT_KDF_VERSION = 2;
30
+ /** Argon2id parameter set for a given KDF version. */
31
+ type KdfParams = KdfParams$1;
32
+ /**
33
+ * KDF parameter sets indexed by version number. Byte-compatible with Singra.
34
+ * Exposed as a plain record for call sites that look up params by version.
35
+ */
36
+ declare const KDF_PARAMS: Record<number, KdfParams>;
37
+ /** Versioned envelope prefix for vault item payloads. */
38
+ declare const VAULT_ITEM_ENVELOPE_V1_PREFIX = "sv-vault-v1:";
39
+ type UserKeyBundle = UserKeyBundle$1;
40
+ /** Sensitive vault item data that gets encrypted. */
41
+ interface VaultItemData {
42
+ title?: string;
43
+ websiteUrl?: string;
44
+ itemType?: 'password' | 'note' | 'totp' | 'card';
45
+ isFavorite?: boolean;
46
+ categoryId?: string | null;
47
+ username?: string;
48
+ password?: string;
49
+ notes?: string;
50
+ totpSecret?: string;
51
+ totpIssuer?: string;
52
+ totpLabel?: string;
53
+ totpAlgorithm?: 'SHA1' | 'SHA256' | 'SHA512';
54
+ totpDigits?: 6 | 8;
55
+ totpPeriod?: number;
56
+ customFields?: Record<string, string>;
57
+ /** Internal marker for duress/decoy items (never exposed to UI). */
58
+ _duress?: boolean;
59
+ }
60
+ /** Generates a cryptographically secure random salt (base64, 128-bit). */
61
+ declare function generateSalt(): string;
62
+ /**
63
+ * Derives raw AES-256 key bytes from a master password using Argon2id.
64
+ * When a deviceKey is provided, the result is strengthened via HKDF-Expand
65
+ * with the device key as salt. Caller owns/must wipe the returned buffer.
66
+ */
67
+ declare function deriveRawKey(masterPassword: string, saltBase64: string, kdfVersion?: number, deviceKey?: Uint8Array): Promise<Uint8Array>;
68
+ /**
69
+ * Derives raw key bytes wrapped in a SecureBuffer for safer handling.
70
+ * The SecureBuffer auto-zeros on destroy. Caller MUST call `.destroy()`.
71
+ */
72
+ declare function deriveRawKeySecure(masterPassword: string, saltBase64: string, kdfVersion?: number, deviceKey?: Uint8Array): Promise<SecureBuffer>;
73
+ /** Derives an AES-256-GCM CryptoKey from a master password. */
74
+ declare function deriveKey(masterPassword: string, saltBase64: string, kdfVersion?: number, deviceKey?: Uint8Array): Promise<CryptoKey>;
75
+ /** Imports raw 256-bit key bytes into a non-extractable AES-GCM CryptoKey. */
76
+ declare function importMasterKey(keyBytes: Uint8Array | BufferSource): Promise<CryptoKey>;
77
+ /** Encrypts a UTF-8 string with AES-256-GCM. Optional AAD binds context. */
78
+ declare function encrypt(plaintext: string, key: CryptoKey, aad?: string): Promise<string>;
79
+ /** Encrypts binary data with AES-256-GCM. Caller owns/wipes `plaintextBytes`. */
80
+ declare function encryptBytes(plaintextBytes: Uint8Array, key: CryptoKey, aad?: string): Promise<string>;
81
+ /** Decrypts AES-256-GCM data to a UTF-8 string. */
82
+ declare function decrypt(encryptedBase64: string, key: CryptoKey, aad?: string): Promise<string>;
83
+ /** Decrypts AES-256-GCM data to plaintext bytes (secret — caller must wipe). */
84
+ declare function decryptBytes(encryptedBase64: string, key: CryptoKey, aad?: string): Promise<Uint8Array>;
85
+ /** Encrypts a vault item, binding the ciphertext to `entryId` via AAD. */
86
+ declare function encryptVaultItem(data: VaultItemData, key: CryptoKey, entryId: string): Promise<string>;
87
+ /**
88
+ * Decrypts a vault item. Versioned payloads are read with `entryId` as AAD and
89
+ * fail closed for the oldest no-AAD payloads unless the explicit migration
90
+ * fallback is requested.
91
+ */
92
+ declare function decryptVaultItem(encryptedData: string, key: CryptoKey, entryId: string, options?: {
93
+ allowLegacyNoAadFallback?: boolean;
94
+ }): Promise<VaultItemData>;
95
+ interface VaultItemMigrationDecryptResult {
96
+ data: VaultItemData;
97
+ legacyEnvelopeUsed: boolean;
98
+ legacyNoAadFallbackUsed: boolean;
99
+ }
100
+ /**
101
+ * Decrypts an item only for an explicit migration path. Runtime reads must use
102
+ * decryptVaultItem(), which fails closed for legacy no-AAD payloads.
103
+ */
104
+ declare function decryptVaultItemForMigration(encryptedData: string, key: CryptoKey, entryId: string): Promise<VaultItemMigrationDecryptResult>;
105
+ /**
106
+ * True if `encryptedData` is a current versioned vault-item envelope.
107
+ *
108
+ * Fails closed (throws) on an unknown in-family version (`sv-vault-v<n>:`) so a
109
+ * future format can never be silently treated as legacy by migration code —
110
+ * matching the Singra contract. Callers that need a non-throwing predicate must
111
+ * wrap this explicitly.
112
+ */
113
+ declare function isCurrentVaultItemEnvelope(encryptedData: string): boolean;
114
+ /** Creates a password verification hash (v3: encrypts a known constant). */
115
+ declare function createVerificationHash(key: CryptoKey): Promise<string>;
116
+ /** Verifies that `key` can decrypt the stored verification hash. */
117
+ declare function verifyKey(verificationHash: string, key: CryptoKey): Promise<boolean>;
118
+ interface KdfUpgradeResult {
119
+ upgraded: boolean;
120
+ newKey?: CryptoKey;
121
+ oldKey?: CryptoKey;
122
+ newVerifier?: string;
123
+ activeVersion: number;
124
+ newEncryptedUserKey?: string;
125
+ }
126
+ /**
127
+ * Attempts to upgrade KDF parameters to the latest version after unlock.
128
+ * USK path only re-wraps the 32-byte UserKey (no vault re-encryption). Legacy
129
+ * path returns old+new keys so the caller can re-encrypt vault data.
130
+ */
131
+ declare function attemptKdfUpgrade(masterPassword: string, saltBase64: string, currentVersion: number, deviceKey?: Uint8Array, encryptedUserKey?: string, existingKdfOutputBytes?: Uint8Array): Promise<KdfUpgradeResult>;
132
+ /** Re-encrypts a single encrypted string from oldKey to newKey. */
133
+ declare function reEncryptString(encryptedBase64: string, oldKey: CryptoKey, newKey: CryptoKey, aad?: string): Promise<string>;
134
+ interface ReEncryptionResult {
135
+ itemsReEncrypted: number;
136
+ categoriesReEncrypted: number;
137
+ itemUpdates: Array<{
138
+ id: string;
139
+ encrypted_data: string;
140
+ }>;
141
+ categoryUpdates: Array<{
142
+ id: string;
143
+ name: string;
144
+ icon: string | null;
145
+ color: string | null;
146
+ }>;
147
+ legacyItemsFound: number;
148
+ }
149
+ /**
150
+ * Re-encrypts all vault items and encrypted category fields from an old key to
151
+ * a new key (required during KDF upgrades). Pure: no DB side effects.
152
+ */
153
+ declare function reEncryptVault(items: Array<{
154
+ id: string;
155
+ encrypted_data: string;
156
+ }>, categories: Array<{
157
+ id: string;
158
+ name: string;
159
+ icon: string | null;
160
+ color: string | null;
161
+ }>, oldKey: CryptoKey, newKey: CryptoKey): Promise<ReEncryptionResult>;
162
+ /**
163
+ * Clears sensitive references from a VaultItemData object. NOTE: JS strings are
164
+ * immutable; this only drops references so the GC can reclaim them sooner.
165
+ */
166
+ declare function clearReferences(data: VaultItemData): void;
167
+ /** @deprecated Use clearReferences. secureClear implies wiping JS cannot do. */
168
+ declare const secureClear: typeof clearReferences;
169
+ declare function generateRSAKeyPair(): Promise<CryptoKeyPair>;
170
+ declare function exportPublicKey(key: CryptoKey): Promise<JsonWebKey>;
171
+ declare function importPublicKey(jwk: JsonWebKey): Promise<CryptoKey>;
172
+ declare function importPrivateKey(jwk: JsonWebKey): Promise<CryptoKey>;
173
+ declare function exportPrivateKey(key: CryptoKey): Promise<JsonWebKey>;
174
+ declare function encryptRSA(plaintext: string, publicKey: CryptoKey): Promise<string>;
175
+ declare function decryptRSA(ciphertextBase64: string, privateKey: CryptoKey): Promise<string>;
176
+ /**
177
+ * Generates a user's asymmetric key material for shared collections.
178
+ * v1: RSA-only wrapping. v2: hybrid PQ+RSA (`pq-v2:` envelope).
179
+ */
180
+ declare function generateUserKeyPair(masterPassword: string, version?: 1 | 2): Promise<{
181
+ publicKey: string;
182
+ encryptedPrivateKey: string;
183
+ pqPublicKey?: string;
184
+ }>;
185
+ /** Migrates RSA-only wrapping key material to hybrid PQ+RSA key material. */
186
+ declare function migrateToHybridKeyPair(encryptedPrivateKey: string, masterPassword: string): Promise<{
187
+ publicKey: string;
188
+ encryptedPrivateKey: string;
189
+ pqPublicKey: string;
190
+ } | null>;
191
+ /** Generates a random shared encryption key for a collection (AES-256 JWK). */
192
+ declare function generateSharedKey(): Promise<string>;
193
+ /** Encrypts vault item data with a shared key. Optional AAD binds context. */
194
+ declare function encryptWithSharedKey(data: VaultItemData, sharedKey: string, aad?: string): Promise<string>;
195
+ interface SharedKeyDecryptOptions {
196
+ /** Allows reading pre-AAD shared ciphertexts during explicit migration only. */
197
+ allowLegacyNoAadFallback?: boolean;
198
+ }
199
+ /** Decrypts vault item data with a shared key. Fails closed by default. */
200
+ declare function decryptWithSharedKey(encryptedData: string, sharedKey: string, aad?: string, options?: SharedKeyDecryptOptions): Promise<VaultItemData>;
201
+ /** Creates a new random UserKey wrapped under a KEK from the KDF output. */
202
+ declare function createEncryptedUserKey(kdfOutputBytes: Uint8Array): Promise<UserKeyBundle>;
203
+ /** Derives a deterministic UserKey from the KDF output and wraps it (migration). */
204
+ declare function migrateToUserKey(kdfOutputBytes: Uint8Array): Promise<UserKeyBundle>;
205
+ /** Decrypts the stored encryptedUserKey to obtain the UserKey CryptoKey. */
206
+ declare function unwrapUserKey(encryptedUserKey: string, kdfOutputBytes: Uint8Array): Promise<CryptoKey>;
207
+ /** Decrypts the stored encryptedUserKey and returns the raw UserKey bytes. */
208
+ declare function unwrapUserKeyBytes(encryptedUserKey: string, kdfOutputBytes: Uint8Array): Promise<Uint8Array>;
209
+ /** Re-wraps an existing UserKey under a new KDF output. UserKey unchanged. */
210
+ declare function rewrapUserKey(encryptedUserKey: string, oldKdfOutputBytes: Uint8Array, newKdfOutputBytes: Uint8Array): Promise<string>;
211
+ /** Encrypts a private key (RSA JWK / PQ base64) with the UserKey (`usk-v1:`). */
212
+ declare function wrapPrivateKeyWithUserKey(privateKeyMaterial: string, userKey: CryptoKey): Promise<string>;
213
+ /** Decrypts a private key wrapped with wrapPrivateKeyWithUserKey. */
214
+ declare function unwrapPrivateKeyWithUserKey(wrappedKey: string, userKey: CryptoKey): Promise<string>;
215
+ /**
216
+ * Decrypts a legacy private key encrypted with its own KDF derivation.
217
+ * Handles `kdfVersion:salt:enc`, `salt:enc`, and `pq-v2:kdfVersion:salt:encRsa:encPq`.
218
+ */
219
+ declare function decryptPrivateKeyLegacy(encryptedPrivateKey: string, masterPassword: string, extractPqPart?: boolean): Promise<string>;
220
+ /** Decrypts a stored RSA private key, dispatching on format sentinel. */
221
+ declare function getDecryptedRsaPrivateKey(encryptedPrivateKey: string, userKey: CryptoKey | null, masterPassword: string): Promise<string>;
222
+ /** Decrypts a stored PQ (ML-KEM-768) private key, dispatching on format sentinel. */
223
+ declare function getDecryptedPqPrivateKey(encryptedPqPrivateKey: string, userKey: CryptoKey | null, masterPassword: string): Promise<string>;
224
+
225
+ export { CURRENT_KDF_VERSION, KDF_PARAMS, type KdfParams, type KdfUpgradeResult, type ReEncryptionResult, type SharedKeyDecryptOptions, type UserKeyBundle, VAULT_ITEM_ENVELOPE_V1_PREFIX, type VaultItemData, type VaultItemMigrationDecryptResult, attemptKdfUpgrade, clearReferences, createEncryptedUserKey, createVerificationHash, decrypt, decryptBytes, decryptPrivateKeyLegacy, decryptRSA, decryptVaultItem, decryptVaultItemForMigration, decryptWithSharedKey, deriveKey, deriveRawKey, deriveRawKeySecure, encrypt, encryptBytes, encryptRSA, encryptVaultItem, encryptWithSharedKey, exportPrivateKey, exportPublicKey, generateRSAKeyPair, generateSalt, generateSharedKey, generateUserKeyPair, getDecryptedPqPrivateKey, getDecryptedRsaPrivateKey, importMasterKey, importPrivateKey, importPublicKey, isCurrentVaultItemEnvelope, migrateToHybridKeyPair, migrateToUserKey, reEncryptString, reEncryptVault, rewrapUserKey, secureClear, unwrapPrivateKeyWithUserKey, unwrapUserKey, unwrapUserKeyBytes, verifyKey, wrapPrivateKeyWithUserKey };
@@ -0,0 +1,465 @@
1
+ import { generateRsaOaepKeyPair, exportJwk, importRsaOaepPublicKey, importRsaOaepPrivateKey, rsaOaepEncrypt, rsaOaepDecrypt } from '../chunk-KNCZMIZA.js';
2
+ import { generateAesGcmKeyJwk, importAesGcmKeyFromJwk, createWrappedUserKey, createDeterministicWrappedUserKey, unwrapUserKey, unwrapUserKeyBytes, rotateWrappedKey } from '../chunk-3DQPQCAR.js';
3
+ import { generatePQKeyPair } from '../chunk-T3IV7SHD.js';
4
+ import '../chunk-3HCT6A2P.js';
5
+ import { SecureBuffer } from '../chunk-RTAJJZKO.js';
6
+ import { DEFAULT_KDF_PARAMS, CURRENT_KDF_VERSION, generateSalt, deriveRawKey, importAesGcmKey } from '../chunk-OPHN2B3N.js';
7
+ import { VAULT_ITEM_ENVELOPE_V1_PREFIX, encryptVaultEntry, decryptVaultEntryForMigration, decryptVaultEntry, VAULT_ITEM_ENVELOPE_SPEC } from '../chunk-JVFP2GAO.js';
8
+ import { encryptString, encryptBytes, decryptString, decryptBytes } from '../chunk-U65A4HIY.js';
9
+ import '../chunk-BUFRR5PB.js';
10
+ export { base64ToBytes, bytesToBase64 } from '../chunk-JSKIWIEC.js';
11
+ import '../chunk-CYIGDF63.js';
12
+ import { parseEnvelope } from '../chunk-SCHZI6YY.js';
13
+ import '../chunk-MJO7IJZC.js';
14
+
15
+ // src/vault-crypto/index.ts
16
+ var CURRENT_KDF_VERSION2 = CURRENT_KDF_VERSION;
17
+ var KDF_PARAMS = { ...DEFAULT_KDF_PARAMS };
18
+ var VAULT_ITEM_ENVELOPE_V1_PREFIX2 = VAULT_ITEM_ENVELOPE_V1_PREFIX;
19
+ var DEVICE_KEY_HKDF_INFO = "SINGRA_DEVICE_KEY_V1";
20
+ var VERIFICATION_CONSTANT_V3 = "SINGRA_VAULT_VERIFY_V3";
21
+ var ENCRYPTED_CATEGORY_PREFIX = "enc:cat:v1:";
22
+ var USK_V1_PREFIX = "usk-v1:";
23
+ var _legacyDecryptCount = 0;
24
+ function generateSalt2() {
25
+ return generateSalt();
26
+ }
27
+ function strengthenOptions(deviceKey) {
28
+ return deviceKey ? { hkdfSalt: deviceKey, info: DEVICE_KEY_HKDF_INFO } : void 0;
29
+ }
30
+ async function deriveRawKey2(masterPassword, saltBase64, kdfVersion = CURRENT_KDF_VERSION2, deviceKey) {
31
+ return deriveRawKey(masterPassword, saltBase64, {
32
+ version: kdfVersion,
33
+ strengthen: strengthenOptions(deviceKey)
34
+ });
35
+ }
36
+ async function deriveRawKeySecure(masterPassword, saltBase64, kdfVersion = CURRENT_KDF_VERSION2, deviceKey) {
37
+ const rawBytes = await deriveRawKey2(masterPassword, saltBase64, kdfVersion, deviceKey);
38
+ const secure = SecureBuffer.fromBytes(rawBytes);
39
+ rawBytes.fill(0);
40
+ return secure;
41
+ }
42
+ async function deriveKey(masterPassword, saltBase64, kdfVersion = CURRENT_KDF_VERSION2, deviceKey) {
43
+ const keyBytes = await deriveRawKey2(masterPassword, saltBase64, kdfVersion, deviceKey);
44
+ try {
45
+ return await importMasterKey(keyBytes);
46
+ } finally {
47
+ keyBytes.fill(0);
48
+ }
49
+ }
50
+ async function importMasterKey(keyBytes) {
51
+ return importAesGcmKey(keyBytes);
52
+ }
53
+ async function encrypt(plaintext, key, aad) {
54
+ return encryptString(plaintext, key, aad);
55
+ }
56
+ async function encryptBytes2(plaintextBytes, key, aad) {
57
+ return encryptBytes(plaintextBytes, key, aad);
58
+ }
59
+ async function decrypt(encryptedBase64, key, aad) {
60
+ return decryptString(encryptedBase64, key, aad);
61
+ }
62
+ async function decryptBytes2(encryptedBase64, key, aad) {
63
+ return decryptBytes(encryptedBase64, key, aad);
64
+ }
65
+ async function encryptVaultItem(data, key, entryId) {
66
+ return encryptVaultEntry(data, key, entryId);
67
+ }
68
+ async function decryptVaultItem(encryptedData, key, entryId, options = {}) {
69
+ if (options.allowLegacyNoAadFallback) {
70
+ const result = await decryptVaultEntryForMigration(encryptedData, key, entryId);
71
+ if (result.legacyNoAadFallbackUsed) {
72
+ console.warn(`Legacy entry without AAD detected: ${entryId}`);
73
+ _legacyDecryptCount++;
74
+ }
75
+ return result.data;
76
+ }
77
+ return await decryptVaultEntry(encryptedData, key, entryId);
78
+ }
79
+ async function decryptVaultItemForMigration(encryptedData, key, entryId) {
80
+ const result = await decryptVaultEntryForMigration(encryptedData, key, entryId);
81
+ if (result.legacyNoAadFallbackUsed) {
82
+ console.warn(`Legacy entry without AAD detected: ${entryId}`);
83
+ _legacyDecryptCount++;
84
+ }
85
+ return {
86
+ data: result.data,
87
+ legacyEnvelopeUsed: result.legacyEnvelopeUsed,
88
+ legacyNoAadFallbackUsed: result.legacyNoAadFallbackUsed
89
+ };
90
+ }
91
+ function isCurrentVaultItemEnvelope(encryptedData) {
92
+ return parseEnvelope(VAULT_ITEM_ENVELOPE_SPEC, encryptedData).version === 1;
93
+ }
94
+ async function createVerificationHash(key) {
95
+ const encrypted = await encrypt(VERIFICATION_CONSTANT_V3, key);
96
+ return `v3:${encrypted}`;
97
+ }
98
+ async function verifyKey(verificationHash, key) {
99
+ try {
100
+ if (verificationHash.startsWith("v3:")) {
101
+ const encrypted = verificationHash.slice(3);
102
+ const decrypted2 = await decrypt(encrypted, key);
103
+ return decrypted2 === VERIFICATION_CONSTANT_V3;
104
+ }
105
+ if (verificationHash.startsWith("v2:")) {
106
+ const parts = verificationHash.split(":");
107
+ if (parts.length !== 3) {
108
+ return false;
109
+ }
110
+ const challenge = parts[1];
111
+ const encryptedChallenge = parts[2];
112
+ const decrypted2 = await decrypt(encryptedChallenge, key);
113
+ return decrypted2 === challenge;
114
+ }
115
+ const decrypted = await decrypt(verificationHash, key);
116
+ return decrypted === "SINGRA_PW_VERIFICATION";
117
+ } catch {
118
+ return false;
119
+ }
120
+ }
121
+ async function attemptKdfUpgrade(masterPassword, saltBase64, currentVersion, deviceKey, encryptedUserKey, existingKdfOutputBytes) {
122
+ if (currentVersion >= CURRENT_KDF_VERSION2) {
123
+ return { upgraded: false, activeVersion: currentVersion };
124
+ }
125
+ try {
126
+ if (encryptedUserKey) {
127
+ const ownedOldBytes = existingKdfOutputBytes ? null : await deriveRawKey2(masterPassword, saltBase64, currentVersion, deviceKey);
128
+ const oldKdfOutputBytes = existingKdfOutputBytes ?? ownedOldBytes;
129
+ const newKdfOutputBytes = await deriveRawKey2(
130
+ masterPassword,
131
+ saltBase64,
132
+ CURRENT_KDF_VERSION2,
133
+ deviceKey
134
+ );
135
+ try {
136
+ const newEncryptedUserKey = await rewrapUserKey(
137
+ encryptedUserKey,
138
+ oldKdfOutputBytes,
139
+ newKdfOutputBytes
140
+ );
141
+ const newUserKey = await unwrapUserKey2(newEncryptedUserKey, newKdfOutputBytes);
142
+ const newVerifier2 = await createVerificationHash(newUserKey);
143
+ return {
144
+ upgraded: true,
145
+ newVerifier: newVerifier2,
146
+ newEncryptedUserKey,
147
+ activeVersion: CURRENT_KDF_VERSION2
148
+ };
149
+ } finally {
150
+ ownedOldBytes?.fill(0);
151
+ newKdfOutputBytes.fill(0);
152
+ }
153
+ }
154
+ const newKey = await deriveKey(masterPassword, saltBase64, CURRENT_KDF_VERSION2, deviceKey);
155
+ const oldKey = await deriveKey(masterPassword, saltBase64, currentVersion, deviceKey);
156
+ const newVerifier = await createVerificationHash(newKey);
157
+ return { upgraded: true, newKey, oldKey, newVerifier, activeVersion: CURRENT_KDF_VERSION2 };
158
+ } catch (err) {
159
+ console.warn(
160
+ `KDF upgrade from v${currentVersion} to v${CURRENT_KDF_VERSION2} failed (likely OOM), staying on v${currentVersion}:`,
161
+ err
162
+ );
163
+ return { upgraded: false, activeVersion: currentVersion };
164
+ }
165
+ }
166
+ async function reEncryptString(encryptedBase64, oldKey, newKey, aad) {
167
+ let plaintext;
168
+ if (aad) {
169
+ try {
170
+ plaintext = await decrypt(encryptedBase64, oldKey, aad);
171
+ } catch {
172
+ plaintext = await decrypt(encryptedBase64, oldKey);
173
+ }
174
+ } else {
175
+ plaintext = await decrypt(encryptedBase64, oldKey);
176
+ }
177
+ return encrypt(plaintext, newKey, aad);
178
+ }
179
+ async function reEncryptVault(items, categories, oldKey, newKey) {
180
+ const itemUpdates = [];
181
+ for (const item of items) {
182
+ try {
183
+ const plaintext = await decryptVaultItem(item.encrypted_data, oldKey, item.id, {
184
+ allowLegacyNoAadFallback: true
185
+ });
186
+ const newEncrypted = await encryptVaultItem(plaintext, newKey, item.id);
187
+ itemUpdates.push({ id: item.id, encrypted_data: newEncrypted });
188
+ } catch (err) {
189
+ throw new Error(`Failed to re-encrypt vault item ${item.id}: ${err}`);
190
+ }
191
+ }
192
+ const categoryUpdates = [];
193
+ for (const cat of categories) {
194
+ let newName = cat.name;
195
+ let newIcon = cat.icon;
196
+ let newColor = cat.color;
197
+ let changed = false;
198
+ if (cat.name.startsWith(ENCRYPTED_CATEGORY_PREFIX)) {
199
+ try {
200
+ const encPart = cat.name.slice(ENCRYPTED_CATEGORY_PREFIX.length);
201
+ const reEncrypted = await reEncryptString(encPart, oldKey, newKey);
202
+ newName = `${ENCRYPTED_CATEGORY_PREFIX}${reEncrypted}`;
203
+ changed = true;
204
+ } catch (err) {
205
+ throw new Error(`Failed to re-encrypt category name ${cat.id}: ${err}`);
206
+ }
207
+ }
208
+ if (cat.icon && cat.icon.startsWith(ENCRYPTED_CATEGORY_PREFIX)) {
209
+ try {
210
+ const encPart = cat.icon.slice(ENCRYPTED_CATEGORY_PREFIX.length);
211
+ const reEncrypted = await reEncryptString(encPart, oldKey, newKey);
212
+ newIcon = `${ENCRYPTED_CATEGORY_PREFIX}${reEncrypted}`;
213
+ changed = true;
214
+ } catch (err) {
215
+ throw new Error(`Failed to re-encrypt category icon ${cat.id}: ${err}`);
216
+ }
217
+ }
218
+ if (cat.color && cat.color.startsWith(ENCRYPTED_CATEGORY_PREFIX)) {
219
+ try {
220
+ const encPart = cat.color.slice(ENCRYPTED_CATEGORY_PREFIX.length);
221
+ const reEncrypted = await reEncryptString(encPart, oldKey, newKey);
222
+ newColor = `${ENCRYPTED_CATEGORY_PREFIX}${reEncrypted}`;
223
+ changed = true;
224
+ } catch (err) {
225
+ throw new Error(`Failed to re-encrypt category color ${cat.id}: ${err}`);
226
+ }
227
+ }
228
+ if (changed) {
229
+ categoryUpdates.push({ id: cat.id, name: newName, icon: newIcon, color: newColor });
230
+ }
231
+ }
232
+ const legacyFound = _legacyDecryptCount;
233
+ _legacyDecryptCount = 0;
234
+ return {
235
+ itemsReEncrypted: itemUpdates.length,
236
+ categoriesReEncrypted: categoryUpdates.length,
237
+ itemUpdates,
238
+ categoryUpdates,
239
+ legacyItemsFound: legacyFound
240
+ };
241
+ }
242
+ function clearReferences(data) {
243
+ if (data.title) data.title = "";
244
+ if (data.websiteUrl) data.websiteUrl = "";
245
+ if (data.itemType) data.itemType = "password";
246
+ if (typeof data.isFavorite === "boolean") data.isFavorite = false;
247
+ if (typeof data.categoryId !== "undefined") data.categoryId = null;
248
+ if (data.username) data.username = "";
249
+ if (data.password) data.password = "";
250
+ if (data.notes) data.notes = "";
251
+ if (data.totpSecret) data.totpSecret = "";
252
+ if (data.totpIssuer) data.totpIssuer = "";
253
+ if (data.totpLabel) data.totpLabel = "";
254
+ if (data.customFields) {
255
+ Object.keys(data.customFields).forEach((key) => {
256
+ data.customFields[key] = "";
257
+ });
258
+ }
259
+ }
260
+ var secureClear = clearReferences;
261
+ async function generateRSAKeyPair() {
262
+ return generateRsaOaepKeyPair();
263
+ }
264
+ async function exportPublicKey(key) {
265
+ return exportJwk(key);
266
+ }
267
+ async function importPublicKey(jwk) {
268
+ return importRsaOaepPublicKey(jwk);
269
+ }
270
+ async function importPrivateKey(jwk) {
271
+ return importRsaOaepPrivateKey(jwk);
272
+ }
273
+ async function exportPrivateKey(key) {
274
+ return exportJwk(key);
275
+ }
276
+ async function encryptRSA(plaintext, publicKey) {
277
+ return rsaOaepEncrypt(plaintext, publicKey);
278
+ }
279
+ async function decryptRSA(ciphertextBase64, privateKey) {
280
+ return rsaOaepDecrypt(ciphertextBase64, privateKey);
281
+ }
282
+ async function generateUserKeyPair(masterPassword, version = 2) {
283
+ if (version === 1) {
284
+ const keyPair = await generateRsaOaepKeyPair();
285
+ const publicKey = JSON.stringify(await exportJwk(keyPair.publicKey));
286
+ const privateKey = JSON.stringify(await exportJwk(keyPair.privateKey));
287
+ const salt2 = generateSalt2();
288
+ const kdfVersion2 = CURRENT_KDF_VERSION2;
289
+ const key2 = await deriveKey(masterPassword, salt2, kdfVersion2);
290
+ const encryptedPrivateKey2 = await encrypt(privateKey, key2);
291
+ return {
292
+ publicKey,
293
+ encryptedPrivateKey: `${kdfVersion2}:${salt2}:${encryptedPrivateKey2}`
294
+ };
295
+ }
296
+ const rsaKeyPair = await generateRsaOaepKeyPair();
297
+ const pqKeyPair = generatePQKeyPair();
298
+ const { publicKey: pqPublicKeyBase64, secretKey: pqSecretKeyBase64 } = pqKeyPair;
299
+ const rsaPublicKey = JSON.stringify(await exportJwk(rsaKeyPair.publicKey));
300
+ const rsaPrivateKey = JSON.stringify(await exportJwk(rsaKeyPair.privateKey));
301
+ const salt = generateSalt2();
302
+ const kdfVersion = CURRENT_KDF_VERSION2;
303
+ const key = await deriveKey(masterPassword, salt, kdfVersion);
304
+ const encryptedRsaKey = await encrypt(rsaPrivateKey, key);
305
+ const encryptedPqKey = await encrypt(pqSecretKeyBase64, key);
306
+ const encryptedPrivateKey = `pq-v2:${kdfVersion}:${salt}:${encryptedRsaKey}:${encryptedPqKey}`;
307
+ return { publicKey: rsaPublicKey, encryptedPrivateKey, pqPublicKey: pqPublicKeyBase64 };
308
+ }
309
+ async function migrateToHybridKeyPair(encryptedPrivateKey, masterPassword) {
310
+ try {
311
+ if (encryptedPrivateKey.startsWith("pq-v2:")) {
312
+ return null;
313
+ }
314
+ const parts = encryptedPrivateKey.split(":");
315
+ let kdfVersion = 1;
316
+ let salt;
317
+ let encryptedData;
318
+ if (parts.length === 2) {
319
+ salt = parts[0];
320
+ encryptedData = parts[1];
321
+ } else if (parts.length === 3) {
322
+ kdfVersion = parseInt(parts[0], 10);
323
+ salt = parts[1];
324
+ encryptedData = parts[2];
325
+ } else {
326
+ throw new Error("Invalid encrypted private key format");
327
+ }
328
+ const key = await deriveKey(masterPassword, salt, kdfVersion);
329
+ const rsaPrivateKey = await decrypt(encryptedData, key);
330
+ const rsaPrivateKeyJwk = JSON.parse(rsaPrivateKey);
331
+ const rsaPublicKeyJwk = {
332
+ ...rsaPrivateKeyJwk,
333
+ d: void 0,
334
+ dp: void 0,
335
+ dq: void 0,
336
+ p: void 0,
337
+ q: void 0,
338
+ qi: void 0,
339
+ key_ops: ["encrypt"]
340
+ };
341
+ const rsaPublicKey = JSON.stringify(rsaPublicKeyJwk);
342
+ const pqKeyPair = generatePQKeyPair();
343
+ const { publicKey: pqPublicKey, secretKey: pqSecretKey } = pqKeyPair;
344
+ const newSalt = generateSalt2();
345
+ const newKdfVersion = CURRENT_KDF_VERSION2;
346
+ const newKey = await deriveKey(masterPassword, newSalt, newKdfVersion);
347
+ const encryptedRsaKey = await encrypt(rsaPrivateKey, newKey);
348
+ const encryptedPqKey = await encrypt(pqSecretKey, newKey);
349
+ const hybridEncryptedKey = `pq-v2:${newKdfVersion}:${newSalt}:${encryptedRsaKey}:${encryptedPqKey}`;
350
+ return { publicKey: rsaPublicKey, encryptedPrivateKey: hybridEncryptedKey, pqPublicKey };
351
+ } catch (err) {
352
+ console.error("Failed to migrate to hybrid key pair:", err);
353
+ return null;
354
+ }
355
+ }
356
+ async function generateSharedKey() {
357
+ return generateAesGcmKeyJwk();
358
+ }
359
+ async function encryptWithSharedKey(data, sharedKey, aad) {
360
+ const key = await importAesGcmKeyFromJwk(sharedKey, ["encrypt"]);
361
+ return encrypt(JSON.stringify(data), key, aad);
362
+ }
363
+ async function decryptWithSharedKey(encryptedData, sharedKey, aad, options = {}) {
364
+ const key = await importAesGcmKeyFromJwk(sharedKey, ["decrypt"]);
365
+ let json;
366
+ if (aad) {
367
+ try {
368
+ json = await decrypt(encryptedData, key, aad);
369
+ } catch {
370
+ if (!options.allowLegacyNoAadFallback) {
371
+ throw new Error("Shared item decryption failed with the required AAD context.");
372
+ }
373
+ _legacyDecryptCount++;
374
+ json = await decrypt(encryptedData, key);
375
+ }
376
+ } else {
377
+ json = await decrypt(encryptedData, key);
378
+ }
379
+ return JSON.parse(json);
380
+ }
381
+ async function createEncryptedUserKey(kdfOutputBytes) {
382
+ return createWrappedUserKey(kdfOutputBytes);
383
+ }
384
+ async function migrateToUserKey(kdfOutputBytes) {
385
+ return createDeterministicWrappedUserKey(kdfOutputBytes);
386
+ }
387
+ async function unwrapUserKey2(encryptedUserKey, kdfOutputBytes) {
388
+ return unwrapUserKey(encryptedUserKey, kdfOutputBytes);
389
+ }
390
+ async function unwrapUserKeyBytes2(encryptedUserKey, kdfOutputBytes) {
391
+ return unwrapUserKeyBytes(encryptedUserKey, kdfOutputBytes);
392
+ }
393
+ async function rewrapUserKey(encryptedUserKey, oldKdfOutputBytes, newKdfOutputBytes) {
394
+ return rotateWrappedKey(encryptedUserKey, oldKdfOutputBytes, newKdfOutputBytes);
395
+ }
396
+ async function wrapPrivateKeyWithUserKey(privateKeyMaterial, userKey) {
397
+ const enc = await encrypt(privateKeyMaterial, userKey);
398
+ return `${USK_V1_PREFIX}${enc}`;
399
+ }
400
+ async function unwrapPrivateKeyWithUserKey(wrappedKey, userKey) {
401
+ if (!wrappedKey.startsWith(USK_V1_PREFIX)) {
402
+ throw new Error("unwrapPrivateKeyWithUserKey: unexpected format (missing usk-v1: prefix)");
403
+ }
404
+ return decrypt(wrappedKey.slice(USK_V1_PREFIX.length), userKey);
405
+ }
406
+ async function decryptPrivateKeyLegacy(encryptedPrivateKey, masterPassword, extractPqPart = false) {
407
+ if (encryptedPrivateKey.startsWith("pq-v2:")) {
408
+ const rest = encryptedPrivateKey.slice("pq-v2:".length);
409
+ const colonIdx1 = rest.indexOf(":");
410
+ const colonIdx2 = rest.indexOf(":", colonIdx1 + 1);
411
+ const colonIdx3 = rest.indexOf(":", colonIdx2 + 1);
412
+ if (colonIdx1 < 0 || colonIdx2 < 0 || colonIdx3 < 0) {
413
+ throw new Error("decryptPrivateKeyLegacy: invalid pq-v2 format");
414
+ }
415
+ const kdfVersion2 = parseInt(rest.slice(0, colonIdx1), 10);
416
+ const salt2 = rest.slice(colonIdx1 + 1, colonIdx2);
417
+ const encRsaKey = rest.slice(colonIdx2 + 1, colonIdx3);
418
+ const encPqKey = rest.slice(colonIdx3 + 1);
419
+ const key2 = await deriveKey(masterPassword, salt2, kdfVersion2);
420
+ return extractPqPart ? decrypt(encPqKey, key2) : decrypt(encRsaKey, key2);
421
+ }
422
+ const parts = encryptedPrivateKey.split(":");
423
+ let kdfVersion = 1;
424
+ let salt;
425
+ let encData;
426
+ if (parts.length === 2) {
427
+ salt = parts[0];
428
+ encData = parts[1];
429
+ } else if (parts.length === 3) {
430
+ kdfVersion = parseInt(parts[0], 10);
431
+ salt = parts[1];
432
+ encData = parts[2];
433
+ } else {
434
+ throw new Error(
435
+ `decryptPrivateKeyLegacy: unrecognised format (${parts.length} colon-separated parts)`
436
+ );
437
+ }
438
+ const key = await deriveKey(masterPassword, salt, kdfVersion);
439
+ return decrypt(encData, key);
440
+ }
441
+ async function getDecryptedRsaPrivateKey(encryptedPrivateKey, userKey, masterPassword) {
442
+ if (encryptedPrivateKey.startsWith(USK_V1_PREFIX)) {
443
+ if (!userKey) {
444
+ throw new Error("getDecryptedRsaPrivateKey: UserKey required for usk-v1 format");
445
+ }
446
+ return unwrapPrivateKeyWithUserKey(encryptedPrivateKey, userKey);
447
+ }
448
+ return decryptPrivateKeyLegacy(encryptedPrivateKey, masterPassword, false);
449
+ }
450
+ async function getDecryptedPqPrivateKey(encryptedPqPrivateKey, userKey, masterPassword) {
451
+ if (encryptedPqPrivateKey.startsWith(USK_V1_PREFIX)) {
452
+ if (!userKey) {
453
+ throw new Error("getDecryptedPqPrivateKey: UserKey required for usk-v1 format");
454
+ }
455
+ return unwrapPrivateKeyWithUserKey(encryptedPqPrivateKey, userKey);
456
+ }
457
+ if (encryptedPqPrivateKey.startsWith("pq-v2:")) {
458
+ return decryptPrivateKeyLegacy(encryptedPqPrivateKey, masterPassword, true);
459
+ }
460
+ return decryptPrivateKeyLegacy(encryptedPqPrivateKey, masterPassword, false);
461
+ }
462
+
463
+ export { CURRENT_KDF_VERSION2 as CURRENT_KDF_VERSION, KDF_PARAMS, VAULT_ITEM_ENVELOPE_V1_PREFIX2 as VAULT_ITEM_ENVELOPE_V1_PREFIX, attemptKdfUpgrade, clearReferences, createEncryptedUserKey, createVerificationHash, decrypt, decryptBytes2 as decryptBytes, decryptPrivateKeyLegacy, decryptRSA, decryptVaultItem, decryptVaultItemForMigration, decryptWithSharedKey, deriveKey, deriveRawKey2 as deriveRawKey, deriveRawKeySecure, encrypt, encryptBytes2 as encryptBytes, encryptRSA, encryptVaultItem, encryptWithSharedKey, exportPrivateKey, exportPublicKey, generateRSAKeyPair, generateSalt2 as generateSalt, generateSharedKey, generateUserKeyPair, getDecryptedPqPrivateKey, getDecryptedRsaPrivateKey, importMasterKey, importPrivateKey, importPublicKey, isCurrentVaultItemEnvelope, migrateToHybridKeyPair, migrateToUserKey, reEncryptString, reEncryptVault, rewrapUserKey, secureClear, unwrapPrivateKeyWithUserKey, unwrapUserKey2 as unwrapUserKey, unwrapUserKeyBytes2 as unwrapUserKeyBytes, verifyKey, wrapPrivateKeyWithUserKey };
464
+ //# sourceMappingURL=index.js.map
465
+ //# sourceMappingURL=index.js.map