/** * Secure storage for encrypted passport data */ import type { UserProfile } from './types'; // Storage keys const STORAGE_KEYS = { ENCRYPTED_PASSPORT: 'sharenet_encrypted_passport', STORAGE_PREFERENCE: 'sharenet_storage_preference', SESSION_KEY: 'sharenet_session_key', } as const; type StoragePreference = 'session' | 'persistent'; /** * Generate a random encryption key using Web Crypto API */ async function generateEncryptionKey(): Promise { return await window.crypto.subtle.generateKey( { name: 'AES-GCM', length: 256, }, true, // extractable ['encrypt', 'decrypt'] ); } /** * Encrypt data using AES-GCM */ async function encryptData(data: string, key: CryptoKey): Promise<{ encrypted: ArrayBuffer; iv: Uint8Array }> { const encoder = new TextEncoder(); const encodedData = encoder.encode(data); const iv = window.crypto.getRandomValues(new Uint8Array(12)); const encrypted = await window.crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv, }, key, encodedData ); return { encrypted, iv }; } /** * Decrypt data using AES-GCM */ async function decryptData(encrypted: ArrayBuffer, iv: Uint8Array, key: CryptoKey): Promise { const decrypted = await window.crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv, }, key, encrypted ); const decoder = new TextDecoder(); return decoder.decode(decrypted); } /** * Export key to base64 string for storage */ async function exportKey(key: CryptoKey): Promise { const exported = await window.crypto.subtle.exportKey('raw', key); return btoa(String.fromCharCode(...new Uint8Array(exported))); } /** * Import key from base64 string */ async function importKey(base64Key: string): Promise { const keyData = Uint8Array.from(atob(base64Key), c => c.charCodeAt(0)); return await window.crypto.subtle.importKey( 'raw', keyData, { name: 'AES-GCM', }, true, // extractable ['encrypt', 'decrypt'] ); } /** * Store encrypted passport data */ export async function storeEncryptedPassport( profiles: UserProfile[], currentUser: UserProfile, preference: StoragePreference ): Promise { const dataToStore = { profiles, currentUser, timestamp: Date.now(), }; // Generate encryption key const key = await generateEncryptionKey(); const encryptedData = await encryptData(JSON.stringify(dataToStore), key); const exportedKey = await exportKey(key); // Convert encrypted data to base64 for storage const encryptedBase64 = btoa(String.fromCharCode(...new Uint8Array(encryptedData.encrypted))); const ivBase64 = btoa(String.fromCharCode(...encryptedData.iv)); const storageData = { encrypted: encryptedBase64, iv: ivBase64, key: exportedKey, }; // Store based on preference if (preference === 'session') { sessionStorage.setItem(STORAGE_KEYS.ENCRYPTED_PASSPORT, JSON.stringify(storageData)); } else { localStorage.setItem(STORAGE_KEYS.ENCRYPTED_PASSPORT, JSON.stringify(storageData)); } // Store preference localStorage.setItem(STORAGE_KEYS.STORAGE_PREFERENCE, preference); } /** * Retrieve and decrypt passport data */ export async function retrieveEncryptedPassport(): Promise<{ profiles: UserProfile[]; currentUser: UserProfile; preference: StoragePreference; } | null> { // Get storage preference const preference = localStorage.getItem(STORAGE_KEYS.STORAGE_PREFERENCE) as StoragePreference | null; if (!preference) { return null; } // Get encrypted data from appropriate storage let encryptedData: string | null = null; if (preference === 'session') { encryptedData = sessionStorage.getItem(STORAGE_KEYS.ENCRYPTED_PASSPORT); } else { encryptedData = localStorage.getItem(STORAGE_KEYS.ENCRYPTED_PASSPORT); } if (!encryptedData) { return null; } try { const { encrypted: encryptedBase64, iv: ivBase64, key: exportedKey } = JSON.parse(encryptedData); // Import key const key = await importKey(exportedKey); // Convert from base64 const encryptedArray = Uint8Array.from(atob(encryptedBase64), c => c.charCodeAt(0)); const ivArray = Uint8Array.from(atob(ivBase64), c => c.charCodeAt(0)); // Decrypt data const decryptedData = await decryptData(encryptedArray.buffer, ivArray, key); const { profiles, currentUser } = JSON.parse(decryptedData); return { profiles, currentUser, preference }; } catch (error) { console.error('Failed to decrypt stored passport data:', error); // Clear corrupted data clearStoredPassport(); return null; } } /** * Clear stored passport data */ export function clearStoredPassport(): void { localStorage.removeItem(STORAGE_KEYS.ENCRYPTED_PASSPORT); sessionStorage.removeItem(STORAGE_KEYS.ENCRYPTED_PASSPORT); localStorage.removeItem(STORAGE_KEYS.STORAGE_PREFERENCE); } /** * Check if passport data is stored */ export function hasStoredPassport(): boolean { const preference = localStorage.getItem(STORAGE_KEYS.STORAGE_PREFERENCE) as StoragePreference | null; if (!preference) { return false; } if (preference === 'session') { return sessionStorage.getItem(STORAGE_KEYS.ENCRYPTED_PASSPORT) !== null; } else { return localStorage.getItem(STORAGE_KEYS.ENCRYPTED_PASSPORT) !== null; } } /** * Get storage preference */ export function getStoragePreference(): StoragePreference | null { return localStorage.getItem(STORAGE_KEYS.STORAGE_PREFERENCE) as StoragePreference | null; }