@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.
- package/LICENSE +22 -0
- package/README.md +543 -0
- package/dist/crypto/index.cjs +807 -0
- package/dist/crypto/index.d.cts +641 -0
- package/dist/crypto/index.d.ts +641 -0
- package/dist/crypto/index.js +716 -0
- package/dist/decrypt-eSHlbh1j.d.cts +321 -0
- package/dist/decrypt-eSHlbh1j.d.ts +321 -0
- package/dist/fs/index.cjs +1168 -0
- package/dist/fs/index.d.cts +400 -0
- package/dist/fs/index.d.ts +400 -0
- package/dist/fs/index.js +1091 -0
- package/dist/index.cjs +2160 -0
- package/dist/index.d.cts +282 -0
- package/dist/index.d.ts +282 -0
- package/dist/index.js +2031 -0
- package/dist/integrity-CCYjrap3.d.ts +31 -0
- package/dist/integrity-Dx9jukMH.d.cts +31 -0
- package/dist/types-61c7Q9ri.d.ts +134 -0
- package/dist/types-Ch0y-n7K.d.cts +134 -0
- package/dist/utils/index.cjs +129 -0
- package/dist/utils/index.d.cts +49 -0
- package/dist/utils/index.d.ts +49 -0
- package/dist/utils/index.js +114 -0
- package/dist/vault/index.cjs +713 -0
- package/dist/vault/index.d.cts +237 -0
- package/dist/vault/index.d.ts +237 -0
- package/dist/vault/index.js +677 -0
- package/dist/version-BygzPVGs.d.cts +55 -0
- package/dist/version-BygzPVGs.d.ts +55 -0
- package/package.json +86 -0
- package/src/crypto/dilithium.ts +233 -0
- package/src/crypto/hybrid.ts +358 -0
- package/src/crypto/index.ts +181 -0
- package/src/crypto/kyber.ts +199 -0
- package/src/crypto/nacl.ts +204 -0
- package/src/crypto/primitives/blake3.ts +141 -0
- package/src/crypto/primitives/chacha.ts +211 -0
- package/src/crypto/primitives/hkdf.ts +192 -0
- package/src/crypto/primitives/index.ts +54 -0
- package/src/crypto/primitives.ts +144 -0
- package/src/crypto/x25519.ts +134 -0
- package/src/fs/aes.ts +343 -0
- package/src/fs/argon2.ts +184 -0
- package/src/fs/browser.ts +408 -0
- package/src/fs/decrypt.ts +320 -0
- package/src/fs/encrypt.ts +324 -0
- package/src/fs/format.ts +425 -0
- package/src/fs/index.ts +144 -0
- package/src/fs/types.ts +304 -0
- package/src/index.ts +414 -0
- package/src/kdf/index.ts +311 -0
- package/src/runtime/crypto.ts +16 -0
- package/src/security/index.ts +345 -0
- package/src/tunnel/index.ts +39 -0
- package/src/tunnel/session.ts +229 -0
- package/src/tunnel/types.ts +115 -0
- package/src/utils/entropy.ts +128 -0
- package/src/utils/index.ts +25 -0
- package/src/utils/integrity.ts +95 -0
- package/src/vault/decrypt.ts +167 -0
- package/src/vault/encrypt.ts +207 -0
- package/src/vault/index.ts +71 -0
- package/src/vault/manager.ts +327 -0
- package/src/vault/migrate.ts +190 -0
- package/src/vault/types.ts +177 -0
- package/src/version.ts +304 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Omnituum PQC Shared - Vault Manager
|
|
3
|
+
*
|
|
4
|
+
* High-level operations for managing the PQC identity vault.
|
|
5
|
+
* Handles identity creation, rotation, import/export, and session management.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
OmnituumVault,
|
|
10
|
+
HybridIdentityRecord,
|
|
11
|
+
VaultSettings,
|
|
12
|
+
VaultSession,
|
|
13
|
+
} from './types';
|
|
14
|
+
import { encryptVaultToBlob } from './encrypt';
|
|
15
|
+
import { decryptVaultFromFile } from './decrypt';
|
|
16
|
+
import { computeIntegrityHash } from '../utils/integrity';
|
|
17
|
+
import { generateId } from '../utils/entropy';
|
|
18
|
+
import { generateX25519Keypair, generateKyberKeypair, toHex } from '../crypto';
|
|
19
|
+
import { VAULT_VERSION } from '../version';
|
|
20
|
+
|
|
21
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
22
|
+
// VAULT CREATION
|
|
23
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a new empty vault.
|
|
27
|
+
*/
|
|
28
|
+
export function createEmptyVault(): OmnituumVault {
|
|
29
|
+
const now = new Date().toISOString();
|
|
30
|
+
return {
|
|
31
|
+
version: VAULT_VERSION,
|
|
32
|
+
identities: [],
|
|
33
|
+
settings: {
|
|
34
|
+
autoUnlock: false,
|
|
35
|
+
lockTimeout: 15,
|
|
36
|
+
showFingerprints: true,
|
|
37
|
+
},
|
|
38
|
+
integrityHash: computeIntegrityHash([]),
|
|
39
|
+
createdAt: now,
|
|
40
|
+
modifiedAt: now,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
45
|
+
// IDENTITY MANAGEMENT
|
|
46
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create a new hybrid identity.
|
|
50
|
+
*/
|
|
51
|
+
export async function createIdentity(name: string): Promise<HybridIdentityRecord | null> {
|
|
52
|
+
// Generate X25519 keypair
|
|
53
|
+
const x25519 = generateX25519Keypair();
|
|
54
|
+
|
|
55
|
+
// Generate Kyber keypair
|
|
56
|
+
const kyber = await generateKyberKeypair();
|
|
57
|
+
if (!kyber) {
|
|
58
|
+
console.error('Kyber key generation failed');
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const now = new Date().toISOString();
|
|
63
|
+
const deviceFingerprint = await getDeviceFingerprint();
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
id: generateId(),
|
|
67
|
+
name,
|
|
68
|
+
x25519PubHex: x25519.publicHex,
|
|
69
|
+
x25519SecHex: x25519.secretHex,
|
|
70
|
+
kyberPubB64: kyber.publicB64,
|
|
71
|
+
kyberSecB64: kyber.secretB64,
|
|
72
|
+
createdAt: now,
|
|
73
|
+
rotationCount: 0,
|
|
74
|
+
deviceFingerprint,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Add an identity to the vault.
|
|
80
|
+
*/
|
|
81
|
+
export function addIdentity(vault: OmnituumVault, identity: HybridIdentityRecord): OmnituumVault {
|
|
82
|
+
const identities = [...vault.identities, identity];
|
|
83
|
+
return {
|
|
84
|
+
...vault,
|
|
85
|
+
identities,
|
|
86
|
+
integrityHash: computeIntegrityHash(identities),
|
|
87
|
+
modifiedAt: new Date().toISOString(),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Remove an identity from the vault.
|
|
93
|
+
*/
|
|
94
|
+
export function removeIdentity(vault: OmnituumVault, identityId: string): OmnituumVault {
|
|
95
|
+
const identities = vault.identities.filter(i => i.id !== identityId);
|
|
96
|
+
return {
|
|
97
|
+
...vault,
|
|
98
|
+
identities,
|
|
99
|
+
integrityHash: computeIntegrityHash(identities),
|
|
100
|
+
modifiedAt: new Date().toISOString(),
|
|
101
|
+
settings: {
|
|
102
|
+
...vault.settings,
|
|
103
|
+
lastUsedIdentity: vault.settings.lastUsedIdentity === identityId
|
|
104
|
+
? undefined
|
|
105
|
+
: vault.settings.lastUsedIdentity,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Rotate keys for an identity (regenerate Kyber + X25519).
|
|
112
|
+
*/
|
|
113
|
+
export async function rotateIdentityKeys(
|
|
114
|
+
vault: OmnituumVault,
|
|
115
|
+
identityId: string
|
|
116
|
+
): Promise<OmnituumVault | null> {
|
|
117
|
+
const index = vault.identities.findIndex(i => i.id === identityId);
|
|
118
|
+
if (index === -1) return null;
|
|
119
|
+
|
|
120
|
+
const existing = vault.identities[index];
|
|
121
|
+
|
|
122
|
+
// Generate new X25519 keypair
|
|
123
|
+
const x25519 = generateX25519Keypair();
|
|
124
|
+
|
|
125
|
+
// Generate new Kyber keypair
|
|
126
|
+
const kyber = await generateKyberKeypair();
|
|
127
|
+
if (!kyber) return null;
|
|
128
|
+
|
|
129
|
+
const now = new Date().toISOString();
|
|
130
|
+
|
|
131
|
+
const updated: HybridIdentityRecord = {
|
|
132
|
+
...existing,
|
|
133
|
+
x25519PubHex: x25519.publicHex,
|
|
134
|
+
x25519SecHex: x25519.secretHex,
|
|
135
|
+
kyberPubB64: kyber.publicB64,
|
|
136
|
+
kyberSecB64: kyber.secretB64,
|
|
137
|
+
lastRotatedAt: now,
|
|
138
|
+
rotationCount: existing.rotationCount + 1,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const identities = [...vault.identities];
|
|
142
|
+
identities[index] = updated;
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
...vault,
|
|
146
|
+
identities,
|
|
147
|
+
integrityHash: computeIntegrityHash(identities),
|
|
148
|
+
modifiedAt: now,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Update identity metadata.
|
|
154
|
+
*/
|
|
155
|
+
export function updateIdentityMetadata(
|
|
156
|
+
vault: OmnituumVault,
|
|
157
|
+
identityId: string,
|
|
158
|
+
updates: Partial<Pick<HybridIdentityRecord, 'name' | 'metadata'>>
|
|
159
|
+
): OmnituumVault {
|
|
160
|
+
const identities = vault.identities.map(i =>
|
|
161
|
+
i.id === identityId ? { ...i, ...updates } : i
|
|
162
|
+
);
|
|
163
|
+
return {
|
|
164
|
+
...vault,
|
|
165
|
+
identities,
|
|
166
|
+
integrityHash: computeIntegrityHash(identities),
|
|
167
|
+
modifiedAt: new Date().toISOString(),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
172
|
+
// VAULT SETTINGS
|
|
173
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Update vault settings.
|
|
177
|
+
*/
|
|
178
|
+
export function updateSettings(
|
|
179
|
+
vault: OmnituumVault,
|
|
180
|
+
settings: Partial<VaultSettings>
|
|
181
|
+
): OmnituumVault {
|
|
182
|
+
return {
|
|
183
|
+
...vault,
|
|
184
|
+
settings: { ...vault.settings, ...settings },
|
|
185
|
+
modifiedAt: new Date().toISOString(),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Set the active identity.
|
|
191
|
+
*/
|
|
192
|
+
export function setActiveIdentity(vault: OmnituumVault, identityId: string): OmnituumVault {
|
|
193
|
+
return updateSettings(vault, { lastUsedIdentity: identityId });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
197
|
+
// IMPORT / EXPORT
|
|
198
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Export vault to encrypted file.
|
|
202
|
+
*/
|
|
203
|
+
export async function exportVault(vault: OmnituumVault, password: string): Promise<Blob> {
|
|
204
|
+
return encryptVaultToBlob(vault, password);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Import vault from encrypted file.
|
|
209
|
+
*/
|
|
210
|
+
export async function importVault(file: File, password: string): Promise<OmnituumVault> {
|
|
211
|
+
return decryptVaultFromFile(file, password);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Trigger download of encrypted vault.
|
|
216
|
+
*/
|
|
217
|
+
export async function downloadVault(vault: OmnituumVault, password: string): Promise<void> {
|
|
218
|
+
const blob = await exportVault(vault, password);
|
|
219
|
+
const url = URL.createObjectURL(blob);
|
|
220
|
+
const a = document.createElement('a');
|
|
221
|
+
a.href = url;
|
|
222
|
+
a.download = `omnituum_vault_${new Date().toISOString().split('T')[0]}.enc`;
|
|
223
|
+
document.body.appendChild(a);
|
|
224
|
+
a.click();
|
|
225
|
+
document.body.removeChild(a);
|
|
226
|
+
URL.revokeObjectURL(url);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
230
|
+
// SESSION MANAGEMENT
|
|
231
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
232
|
+
|
|
233
|
+
let currentSession: VaultSession = {
|
|
234
|
+
unlocked: false,
|
|
235
|
+
sessionKey: null,
|
|
236
|
+
unlockedAt: null,
|
|
237
|
+
activeIdentityId: null,
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Get current session state.
|
|
242
|
+
*/
|
|
243
|
+
export function getSession(): VaultSession {
|
|
244
|
+
return { ...currentSession };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Unlock vault and store session key in memory.
|
|
249
|
+
*/
|
|
250
|
+
export async function unlockSession(password: string, vault: OmnituumVault): Promise<boolean> {
|
|
251
|
+
try {
|
|
252
|
+
// Verify password by attempting to encrypt/decrypt
|
|
253
|
+
const salt = globalThis.crypto.getRandomValues(new Uint8Array(32));
|
|
254
|
+
const passwordKey = await globalThis.crypto.subtle.importKey(
|
|
255
|
+
'raw',
|
|
256
|
+
new TextEncoder().encode(password),
|
|
257
|
+
'PBKDF2',
|
|
258
|
+
false,
|
|
259
|
+
['deriveBits', 'deriveKey']
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const sessionKey = await globalThis.crypto.subtle.deriveKey(
|
|
263
|
+
{
|
|
264
|
+
name: 'PBKDF2',
|
|
265
|
+
salt,
|
|
266
|
+
iterations: 100000, // Faster for session key
|
|
267
|
+
hash: 'SHA-256',
|
|
268
|
+
},
|
|
269
|
+
passwordKey,
|
|
270
|
+
{ name: 'AES-GCM', length: 256 },
|
|
271
|
+
false,
|
|
272
|
+
['encrypt', 'decrypt']
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
currentSession = {
|
|
276
|
+
unlocked: true,
|
|
277
|
+
sessionKey,
|
|
278
|
+
unlockedAt: Date.now(),
|
|
279
|
+
activeIdentityId: vault.settings.lastUsedIdentity || vault.identities[0]?.id || null,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
return true;
|
|
283
|
+
} catch {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Lock the session.
|
|
290
|
+
*/
|
|
291
|
+
export function lockSession(): void {
|
|
292
|
+
currentSession = {
|
|
293
|
+
unlocked: false,
|
|
294
|
+
sessionKey: null,
|
|
295
|
+
unlockedAt: null,
|
|
296
|
+
activeIdentityId: null,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Set active identity in session.
|
|
302
|
+
*/
|
|
303
|
+
export function setSessionActiveIdentity(identityId: string): void {
|
|
304
|
+
if (currentSession.unlocked) {
|
|
305
|
+
currentSession.activeIdentityId = identityId;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
310
|
+
// DEVICE FINGERPRINT
|
|
311
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Generate a device fingerprint for identity tracking.
|
|
315
|
+
*/
|
|
316
|
+
async function getDeviceFingerprint(): Promise<string> {
|
|
317
|
+
const data = [
|
|
318
|
+
navigator.userAgent,
|
|
319
|
+
navigator.language,
|
|
320
|
+
screen.width,
|
|
321
|
+
screen.height,
|
|
322
|
+
new Date().getTimezoneOffset(),
|
|
323
|
+
].join('|');
|
|
324
|
+
|
|
325
|
+
const hash = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(data));
|
|
326
|
+
return toHex(new Uint8Array(hash)).slice(0, 16);
|
|
327
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Omnituum PQC Shared - Vault Migration
|
|
3
|
+
*
|
|
4
|
+
* One-way migration from v1 (PBKDF2) to v2 (Argon2id) encrypted vaults.
|
|
5
|
+
* Includes memory hygiene for sensitive data.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
OmnituumVault,
|
|
10
|
+
EncryptedVaultFile,
|
|
11
|
+
EncryptedVaultFileV1,
|
|
12
|
+
EncryptedVaultFileV2,
|
|
13
|
+
} from './types';
|
|
14
|
+
import { decryptVault } from './decrypt';
|
|
15
|
+
import { encryptVaultV2 } from './encrypt';
|
|
16
|
+
import { zeroMemory } from '../security';
|
|
17
|
+
import {
|
|
18
|
+
VAULT_ENCRYPTED_VERSION,
|
|
19
|
+
VAULT_ENCRYPTED_VERSION_V2,
|
|
20
|
+
} from '../version';
|
|
21
|
+
|
|
22
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
23
|
+
// MIGRATION TYPES
|
|
24
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
25
|
+
|
|
26
|
+
export interface MigrationOptions {
|
|
27
|
+
/** Source encrypted vault */
|
|
28
|
+
encryptedVault: EncryptedVaultFile;
|
|
29
|
+
/** Vault password */
|
|
30
|
+
password: string;
|
|
31
|
+
/** Keep backup of original vault data (default: false) */
|
|
32
|
+
keepBackup?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface MigrationResult {
|
|
36
|
+
/** New v2 encrypted vault */
|
|
37
|
+
encryptedVault: EncryptedVaultFileV2;
|
|
38
|
+
/** Original vault (only if keepBackup was true) */
|
|
39
|
+
backup?: EncryptedVaultFile;
|
|
40
|
+
/** Source version */
|
|
41
|
+
sourceVersion: string;
|
|
42
|
+
/** Target version */
|
|
43
|
+
targetVersion: string;
|
|
44
|
+
/** Migration timestamp */
|
|
45
|
+
migratedAt: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
49
|
+
// VERSION DETECTION
|
|
50
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check if vault needs migration (is v1 format).
|
|
54
|
+
*/
|
|
55
|
+
export function needsMigration(encryptedVault: EncryptedVaultFile): boolean {
|
|
56
|
+
return encryptedVault.version === VAULT_ENCRYPTED_VERSION;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if vault is already v2 format.
|
|
61
|
+
*/
|
|
62
|
+
export function isV2Vault(encryptedVault: EncryptedVaultFile): boolean {
|
|
63
|
+
return encryptedVault.version === VAULT_ENCRYPTED_VERSION_V2;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get vault KDF info for display.
|
|
68
|
+
*/
|
|
69
|
+
export function getVaultKdfInfo(encryptedVault: EncryptedVaultFile): {
|
|
70
|
+
kdf: string;
|
|
71
|
+
version: string;
|
|
72
|
+
isSecure: boolean;
|
|
73
|
+
recommendation?: string;
|
|
74
|
+
} {
|
|
75
|
+
if (encryptedVault.version === VAULT_ENCRYPTED_VERSION_V2) {
|
|
76
|
+
const v2 = encryptedVault as EncryptedVaultFileV2;
|
|
77
|
+
return {
|
|
78
|
+
kdf: `Argon2id (${v2.memoryCost / 1024}MB, ${v2.timeCost} iterations)`,
|
|
79
|
+
version: 'v2',
|
|
80
|
+
isSecure: true,
|
|
81
|
+
};
|
|
82
|
+
} else {
|
|
83
|
+
const v1 = encryptedVault as EncryptedVaultFileV1;
|
|
84
|
+
return {
|
|
85
|
+
kdf: `PBKDF2-SHA256 (${v1.iterations.toLocaleString()} iterations)`,
|
|
86
|
+
version: 'v1',
|
|
87
|
+
isSecure: true, // Still secure, just not as modern
|
|
88
|
+
recommendation: 'Consider upgrading to Argon2id for stronger protection',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
94
|
+
// MIGRATION
|
|
95
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Migrate an encrypted vault from v1 (PBKDF2) to v2 (Argon2id).
|
|
99
|
+
*
|
|
100
|
+
* This is a ONE-WAY migration. The original vault remains unchanged,
|
|
101
|
+
* but a new v2 encrypted vault is returned.
|
|
102
|
+
*
|
|
103
|
+
* Memory hygiene: Sensitive data (decrypted vault) is zeroed after use.
|
|
104
|
+
*
|
|
105
|
+
* @param options - Migration options
|
|
106
|
+
* @returns Migration result with new v2 vault
|
|
107
|
+
* @throws Error if decryption fails or vault is already v2
|
|
108
|
+
*/
|
|
109
|
+
export async function migrateEncryptedVault(
|
|
110
|
+
options: MigrationOptions
|
|
111
|
+
): Promise<MigrationResult> {
|
|
112
|
+
const { encryptedVault, password, keepBackup = false } = options;
|
|
113
|
+
|
|
114
|
+
// Check if already v2
|
|
115
|
+
if (isV2Vault(encryptedVault)) {
|
|
116
|
+
throw new Error('Vault is already v2 format, no migration needed');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Track sensitive data for cleanup
|
|
120
|
+
let decryptedVaultJson: Uint8Array | null = null;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
// Decrypt the vault
|
|
124
|
+
const vault = await decryptVault(encryptedVault, password);
|
|
125
|
+
|
|
126
|
+
// Serialize for memory tracking (so we can zero it)
|
|
127
|
+
const vaultJson = JSON.stringify(vault);
|
|
128
|
+
decryptedVaultJson = new TextEncoder().encode(vaultJson);
|
|
129
|
+
|
|
130
|
+
// Re-encrypt with v2 (Argon2id)
|
|
131
|
+
const newEncryptedVault = await encryptVaultV2(vault, password);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
encryptedVault: newEncryptedVault,
|
|
135
|
+
backup: keepBackup ? encryptedVault : undefined,
|
|
136
|
+
sourceVersion: encryptedVault.version,
|
|
137
|
+
targetVersion: VAULT_ENCRYPTED_VERSION_V2,
|
|
138
|
+
migratedAt: new Date().toISOString(),
|
|
139
|
+
};
|
|
140
|
+
} finally {
|
|
141
|
+
// Zero sensitive data
|
|
142
|
+
if (decryptedVaultJson) {
|
|
143
|
+
zeroMemory(decryptedVaultJson);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Validate migration by decrypting both versions and comparing.
|
|
150
|
+
* Used for testing migration integrity.
|
|
151
|
+
*
|
|
152
|
+
* @param original - Original encrypted vault
|
|
153
|
+
* @param migrated - Migrated encrypted vault
|
|
154
|
+
* @param password - Vault password
|
|
155
|
+
* @returns true if vaults contain identical data
|
|
156
|
+
*/
|
|
157
|
+
export async function validateMigration(
|
|
158
|
+
original: EncryptedVaultFile,
|
|
159
|
+
migrated: EncryptedVaultFileV2,
|
|
160
|
+
password: string
|
|
161
|
+
): Promise<boolean> {
|
|
162
|
+
let originalVaultJson: Uint8Array | null = null;
|
|
163
|
+
let migratedVaultJson: Uint8Array | null = null;
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const originalVault = await decryptVault(original, password);
|
|
167
|
+
const migratedVault = await decryptVault(migrated, password);
|
|
168
|
+
|
|
169
|
+
// Serialize for comparison
|
|
170
|
+
originalVaultJson = new TextEncoder().encode(JSON.stringify(originalVault));
|
|
171
|
+
migratedVaultJson = new TextEncoder().encode(JSON.stringify(migratedVault));
|
|
172
|
+
|
|
173
|
+
// Compare serialized JSON
|
|
174
|
+
if (originalVaultJson.length !== migratedVaultJson.length) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
for (let i = 0; i < originalVaultJson.length; i++) {
|
|
179
|
+
if (originalVaultJson[i] !== migratedVaultJson[i]) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return true;
|
|
185
|
+
} finally {
|
|
186
|
+
// Zero sensitive data
|
|
187
|
+
if (originalVaultJson) zeroMemory(originalVaultJson);
|
|
188
|
+
if (migratedVaultJson) zeroMemory(migratedVaultJson);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Omnituum PQC Shared - Vault Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the PQC identity vault.
|
|
5
|
+
* FROZEN CONTRACTS - see pqc-docs/specs/vault.v1.md
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
VAULT_VERSION,
|
|
10
|
+
VAULT_ENCRYPTED_VERSION,
|
|
11
|
+
VAULT_ENCRYPTED_VERSION_V2,
|
|
12
|
+
VAULT_KDF,
|
|
13
|
+
VAULT_KDF_V2,
|
|
14
|
+
VAULT_ALGORITHM,
|
|
15
|
+
} from '../version';
|
|
16
|
+
|
|
17
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
18
|
+
// IDENTITY TYPES
|
|
19
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
20
|
+
|
|
21
|
+
export interface HybridIdentityRecord {
|
|
22
|
+
/** Unique identity ID */
|
|
23
|
+
id: string;
|
|
24
|
+
/** Display name */
|
|
25
|
+
name: string;
|
|
26
|
+
/** X25519 public key (hex) */
|
|
27
|
+
x25519PubHex: string;
|
|
28
|
+
/** X25519 secret key (hex) - encrypted in vault */
|
|
29
|
+
x25519SecHex: string;
|
|
30
|
+
/** Kyber public key (base64) */
|
|
31
|
+
kyberPubB64: string;
|
|
32
|
+
/** Kyber secret key (base64) - encrypted in vault */
|
|
33
|
+
kyberSecB64: string;
|
|
34
|
+
/** Creation timestamp */
|
|
35
|
+
createdAt: string;
|
|
36
|
+
/** Last rotation timestamp */
|
|
37
|
+
lastRotatedAt?: string;
|
|
38
|
+
/** Device fingerprint */
|
|
39
|
+
deviceFingerprint?: string;
|
|
40
|
+
/** Key rotation count */
|
|
41
|
+
rotationCount: number;
|
|
42
|
+
/** Identity metadata */
|
|
43
|
+
metadata?: {
|
|
44
|
+
label?: string;
|
|
45
|
+
notes?: string;
|
|
46
|
+
tags?: string[];
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
51
|
+
// VAULT TYPES
|
|
52
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
53
|
+
|
|
54
|
+
export interface VaultSettings {
|
|
55
|
+
/** Auto-unlock on return (session memory) */
|
|
56
|
+
autoUnlock: boolean;
|
|
57
|
+
/** Last used identity ID */
|
|
58
|
+
lastUsedIdentity?: string;
|
|
59
|
+
/** Lock timeout in minutes (0 = never auto-lock) */
|
|
60
|
+
lockTimeout: number;
|
|
61
|
+
/** Show key fingerprints in UI */
|
|
62
|
+
showFingerprints: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface OmnituumVault {
|
|
66
|
+
/** Vault format version (FROZEN - see pqc-docs/specs/vault.v1.md) */
|
|
67
|
+
version: typeof VAULT_VERSION;
|
|
68
|
+
/** Stored identities */
|
|
69
|
+
identities: HybridIdentityRecord[];
|
|
70
|
+
/** Vault settings */
|
|
71
|
+
settings: VaultSettings;
|
|
72
|
+
/** SHA-256 hash of serialized identities (integrity check) */
|
|
73
|
+
integrityHash: string;
|
|
74
|
+
/** Vault creation timestamp */
|
|
75
|
+
createdAt: string;
|
|
76
|
+
/** Last modified timestamp */
|
|
77
|
+
modifiedAt: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
81
|
+
// ENCRYPTED VAULT TYPES
|
|
82
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
83
|
+
|
|
84
|
+
/** V1 encrypted vault (PBKDF2) */
|
|
85
|
+
export interface EncryptedVaultFileV1 {
|
|
86
|
+
/** File format version (FROZEN - see pqc-docs/specs/vault.v1.md) */
|
|
87
|
+
version: typeof VAULT_ENCRYPTED_VERSION;
|
|
88
|
+
/** Key derivation function */
|
|
89
|
+
kdf: typeof VAULT_KDF;
|
|
90
|
+
/** PBKDF2 iterations */
|
|
91
|
+
iterations: number;
|
|
92
|
+
/** Salt (base64) */
|
|
93
|
+
salt: string;
|
|
94
|
+
/** AES-GCM IV (base64) */
|
|
95
|
+
iv: string;
|
|
96
|
+
/** Encrypted vault (base64) */
|
|
97
|
+
ciphertext: string;
|
|
98
|
+
/** Auth tag included in ciphertext (AES-GCM) */
|
|
99
|
+
algorithm: typeof VAULT_ALGORITHM;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** V2 encrypted vault (Argon2id) */
|
|
103
|
+
export interface EncryptedVaultFileV2 {
|
|
104
|
+
/** File format version */
|
|
105
|
+
version: typeof VAULT_ENCRYPTED_VERSION_V2;
|
|
106
|
+
/** Key derivation function */
|
|
107
|
+
kdf: typeof VAULT_KDF_V2;
|
|
108
|
+
/** Argon2id memory cost (KiB) */
|
|
109
|
+
memoryCost: number;
|
|
110
|
+
/** Argon2id time cost (iterations) */
|
|
111
|
+
timeCost: number;
|
|
112
|
+
/** Argon2id parallelism */
|
|
113
|
+
parallelism: number;
|
|
114
|
+
/** Salt (base64) */
|
|
115
|
+
salt: string;
|
|
116
|
+
/** AES-GCM IV (base64) */
|
|
117
|
+
iv: string;
|
|
118
|
+
/** Encrypted vault (base64) */
|
|
119
|
+
ciphertext: string;
|
|
120
|
+
/** Auth tag included in ciphertext (AES-GCM) */
|
|
121
|
+
algorithm: typeof VAULT_ALGORITHM;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Union type for any encrypted vault version */
|
|
125
|
+
export type EncryptedVaultFile = EncryptedVaultFileV1 | EncryptedVaultFileV2;
|
|
126
|
+
|
|
127
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
128
|
+
// HEALTH CHECK TYPES
|
|
129
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
130
|
+
|
|
131
|
+
export type HealthStatus = 'healthy' | 'needs-rotation' | 'warning' | 'error';
|
|
132
|
+
|
|
133
|
+
export interface IdentityHealth {
|
|
134
|
+
/** Overall health status */
|
|
135
|
+
status: HealthStatus;
|
|
136
|
+
/** Entropy score (0-100) */
|
|
137
|
+
entropyScore: number;
|
|
138
|
+
/** Integrity verified */
|
|
139
|
+
integrityValid: boolean;
|
|
140
|
+
/** SHA-256 fingerprint */
|
|
141
|
+
fingerprint: string;
|
|
142
|
+
/** Days since last rotation */
|
|
143
|
+
daysSinceRotation: number;
|
|
144
|
+
/** Kyber key valid */
|
|
145
|
+
kyberValid: boolean;
|
|
146
|
+
/** X25519 key valid */
|
|
147
|
+
x25519Valid: boolean;
|
|
148
|
+
/** Recommendations */
|
|
149
|
+
recommendations: string[];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
153
|
+
// SESSION TYPES
|
|
154
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
155
|
+
|
|
156
|
+
export interface VaultSession {
|
|
157
|
+
/** Session active */
|
|
158
|
+
unlocked: boolean;
|
|
159
|
+
/** Derived encryption key (in memory only) */
|
|
160
|
+
sessionKey: CryptoKey | null;
|
|
161
|
+
/** Unlock timestamp */
|
|
162
|
+
unlockedAt: number | null;
|
|
163
|
+
/** Active identity ID */
|
|
164
|
+
activeIdentityId: string | null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
168
|
+
// DEFAULT VALUES
|
|
169
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
170
|
+
|
|
171
|
+
export const DEFAULT_VAULT_SETTINGS: VaultSettings = {
|
|
172
|
+
autoUnlock: false,
|
|
173
|
+
lockTimeout: 15,
|
|
174
|
+
showFingerprints: true,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const PBKDF2_ITERATIONS = 600000; // OWASP 2023 recommendation
|