ihm_client/src/services/secure-key-manager.ts
NicolasCantu aa913ef930 feat: centralize database configuration and fix service worker blocking
**Motivations :**
- Centralize database configuration to prevent version inconsistencies
- Fix service worker blocking during wallet setup
- Ensure all database stores are created at initialization

**Modifications :**
- Created database-config.ts with centralized DATABASE_CONFIG (name, version, stores)
- Updated storage.service.ts to use DATABASE_CONFIG and create all stores on upgrade
- Updated security-setup.ts to initialize database with complete configuration
- Updated wallet-setup.ts to call SDK directly and bypass service worker blocking
- Updated database.service.ts, webauthn.service.ts, and database.worker.js to use DATABASE_CONFIG
- Removed service worker dependency for wallet setup page

**Pages affected :**
- security-setup.html: Initializes database with all stores on page load
- wallet-setup.html: Saves wallet directly to IndexedDB without service worker dependency
2025-10-26 02:19:00 +01:00

222 lines
6.4 KiB
TypeScript

/**
* SecureKeyManager - Gestion sécurisée des clés privées
* Chiffre les clés privées avant stockage et les déchiffre à la demande
*/
export class SecureKeyManager {
private keyStore: CryptoKey | null = null;
private salt: Uint8Array;
private isInitialized: boolean = false;
constructor() {
this.salt = crypto.getRandomValues(new Uint8Array(16));
}
/**
* Initialise le gestionnaire de clés avec un mot de passe
*/
async initialize(password: string): Promise<void> {
try {
const derivedKey = await this.deriveKey(password);
this.keyStore = derivedKey;
this.isInitialized = true;
console.log('🔐 SecureKeyManager initialized');
} catch (error) {
console.error('❌ Failed to initialize SecureKeyManager:', error);
throw new Error('Failed to initialize secure key manager');
}
}
/**
* Stocke une clé privée de manière chiffrée
*/
async storePrivateKey(key: string, password: string): Promise<void> {
if (!this.isInitialized) {
await this.initialize(password);
}
try {
const derivedKey = await this.deriveKey(password);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encryptedKey = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
derivedKey,
new TextEncoder().encode(key)
);
// Stocker la clé chiffrée avec l'IV
const encryptedData = new Uint8Array(iv.length + encryptedKey.byteLength);
encryptedData.set(iv);
encryptedData.set(new Uint8Array(encryptedKey), iv.length);
// Stocker dans IndexedDB de manière sécurisée
await this.storeEncryptedData(encryptedData);
console.log('🔐 Private key stored securely');
} catch (error) {
console.error('❌ Failed to store private key:', error);
throw new Error('Failed to store private key securely');
}
}
/**
* Récupère et déchiffre une clé privée
*/
async getPrivateKey(password: string): Promise<string | null> {
try {
const encryptedData = await this.getEncryptedData();
if (!encryptedData) {return null;}
const derivedKey = await this.deriveKey(password);
const iv = encryptedData.slice(0, 12);
const encryptedKey = encryptedData.slice(12);
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
derivedKey,
encryptedKey
);
return new TextDecoder().decode(decrypted);
} catch (error) {
console.error('❌ Failed to retrieve private key:', error);
return null;
}
}
/**
* Supprime toutes les clés stockées
*/
async clearKeys(): Promise<void> {
try {
await this.clearEncryptedData();
this.keyStore = null;
this.isInitialized = false;
console.log('🔐 All keys cleared');
} catch (error) {
console.error('❌ Failed to clear keys:', error);
}
}
/**
* Vérifie si une clé est stockée
*/
async hasStoredKey(): Promise<boolean> {
try {
const encryptedData = await this.getEncryptedData();
return encryptedData !== null;
} catch {
return false;
}
}
/**
* Dérive une clé de chiffrement à partir du mot de passe
*/
private async deriveKey(password: string): Promise<CryptoKey> {
const keyMaterial = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(password),
'PBKDF2',
false,
['deriveKey']
);
return crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: new Uint8Array(this.salt),
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
/**
* Stocke les données chiffrées dans IndexedDB
*/
private async storeEncryptedData(data: Uint8Array): Promise<void> {
return new Promise((resolve, reject) => {
const request = indexedDB.open('SecureKeyStore', 1);
request.onerror = () => reject(new Error('Failed to open IndexedDB'));
request.onsuccess = () => {
const db = request.result;
const transaction = db.transaction(['keys'], 'readwrite');
const store = transaction.objectStore('keys');
const putRequest = store.put(data, 'encryptedKey');
putRequest.onsuccess = () => resolve();
putRequest.onerror = () => reject(new Error('Failed to store encrypted data'));
};
request.onupgradeneeded = () => {
const db = request.result;
if (!db.objectStoreNames.contains('keys')) {
db.createObjectStore('keys');
}
};
});
}
/**
* Récupère les données chiffrées depuis IndexedDB
*/
private async getEncryptedData(): Promise<Uint8Array | null> {
return new Promise((resolve, reject) => {
const request = indexedDB.open('SecureKeyStore', 1);
request.onerror = () => reject(new Error('Failed to open IndexedDB'));
request.onsuccess = () => {
const db = request.result;
const transaction = db.transaction(['keys'], 'readonly');
const store = transaction.objectStore('keys');
const getRequest = store.get('encryptedKey');
getRequest.onsuccess = () => {
const result = getRequest.result;
resolve(result ? new Uint8Array(result) : null);
};
getRequest.onerror = () => reject(new Error('Failed to retrieve encrypted data'));
};
request.onupgradeneeded = () => {
const db = request.result;
if (!db.objectStoreNames.contains('keys')) {
db.createObjectStore('keys');
}
};
});
}
/**
* Supprime les données chiffrées
*/
private async clearEncryptedData(): Promise<void> {
return new Promise((resolve, reject) => {
const request = indexedDB.open('SecureKeyStore', 1);
request.onerror = () => reject(new Error('Failed to open IndexedDB'));
request.onsuccess = () => {
const db = request.result;
const transaction = db.transaction(['keys'], 'readwrite');
const store = transaction.objectStore('keys');
const deleteRequest = store.delete('encryptedKey');
deleteRequest.onsuccess = () => resolve();
deleteRequest.onerror = () => reject(new Error('Failed to clear encrypted data'));
};
});
}
}
// Instance singleton pour l'application
export const secureKeyManager = new SecureKeyManager();