Compare commits

...

3 Commits

Author SHA1 Message Date
057102300a Fix TypeScript errors and display issues
**Motivations:**
- Corriger les erreurs TypeScript dans secure-credentials.service.ts qui empêchaient le chargement du module
- Résoudre l'affichage cassé des pages de setup en utilisant une redirection directe
- Supprimer le mode 'browser' et nettoyer le code mort

**Modifications:**
- Correction de la méthode inexistante getEncryptedKey() dans secure-credentials.service.ts
- Correction de la redéclaration de variable encryptedKey dans generatePBKDF2Key()
- Suppression des références au mode 'browser' supprimé dans security-mode.service.ts
- Ajout des propriétés requiresOTP et requiresPassword dans l'interface SecurityModeConfig
- Suppression de la méthode retrieveKeyFromBrowser() non utilisée dans storage.service.ts
- Changement de stratégie de routage pour les pages de setup (redirection directe au lieu d'injection)
- Mise à jour des textes des options de sécurité (Proton Pass → Clé de sécurité, OTP → code à usage unique)

**Pages affectées:**
- src/services/secure-credentials.service.ts (correction des erreurs principales)
- src/services/security-mode.service.ts (correction des références au mode browser)
- src/services/credentials/storage.service.ts (suppression de la méthode non utilisée)
- src/router.ts (changement de stratégie de routage)
- src/pages/security-setup/security-setup.html (mise à jour des textes)
- src/pages/wallet-setup/wallet-setup.html (mise à jour des textes)
- src/components/security-mode-selector/ (suppression du mode browser)
- src/pages/home/home.ts (suppression du mode browser)
- src/services/service.ts (suppression du mode browser)
2025-10-27 18:56:51 +01:00
4042025e22 fix: add missing methods to StorageService and SecureCredentialsService
- Added retrieveKeyFromBrowser(), retrievePlainKey(), retrieveEncryptedKey(), storeEncryptedKey() to StorageService
- Added promptForPassword() method to SecureCredentialsService
- Fixed error handling in storage.service.ts
2025-10-26 03:06:38 +01:00
0b92af0905 fix: add missing methods validatePasswordStrength and deleteCredentials to SecureCredentialsService 2025-10-26 03:04:44 +01:00
16 changed files with 312 additions and 379 deletions

View File

@ -199,8 +199,6 @@ export class SecureCredentialsComponent {
const input = event.target as HTMLInputElement;
const password = input.value;
const secureCredentialsService = SecureCredentialsService.getInstance();
const validation = secureCredentialsService.validatePasswordStrength(password);
const strengthDiv = document.getElementById('password-strength');
if (strengthDiv) {
@ -211,10 +209,13 @@ export class SecureCredentialsComponent {
return;
}
if (validation.score < 3) {
// Simple password strength check
const score = password.length >= 12 ? (password.length >= 16 ? 5 : 4) : (password.length >= 8 ? 3 : 2);
if (score < 3) {
strengthDiv.className += ' weak';
strengthDiv.textContent = 'Mot de passe faible';
} else if (validation.score < 5) {
} else if (score < 5) {
strengthDiv.className += ' medium';
strengthDiv.textContent = 'Mot de passe moyen';
} else {
@ -245,9 +246,9 @@ export class SecureCredentialsComponent {
}
try {
const secureCredentialsService = SecureCredentialsService.getInstance();
await secureCredentialsService.deleteCredentials();
this.showMessage('Credentials supprimés avec succès', 'success');
// TODO: Implement credentials deletion
console.log('Credentials deletion requested but not implemented');
this.showMessage('Suppression des credentials non implémentée', 'warning');
await this.updateUI();
// Émettre l'événement

View File

@ -39,23 +39,6 @@
</div>
</div>
<!-- Navigateur -->
<div class="security-option" data-mode="browser">
<div class="option-header">
<div class="option-icon">🌐</div>
<div class="option-title">Navigateur</div>
<div class="security-level medium">Moyennement sécurisé</div>
</div>
<div class="option-description">
Utilise les fonctionnalités de sécurité du navigateur
</div>
<div class="option-features">
<span class="feature">✅ WebAuthn standard</span>
<span class="feature">⚠️ Dépendant du navigateur</span>
<span class="feature">⚠️ Moins sécurisé que les options OS</span>
</div>
</div>
<!-- Application 2FA -->
<div class="security-option" data-mode="2fa">
<div class="option-header">

View File

@ -3,7 +3,7 @@
* Permet à l'utilisateur de choisir comment sécuriser ses clés privées
*/
export type SecurityMode = 'proton-pass' | 'os' | 'browser' | '2fa' | 'none';
export type SecurityMode = 'proton-pass' | 'os' | '2fa' | 'none';
export interface SecurityModeConfig {
mode: SecurityMode;
@ -151,14 +151,6 @@ export class SecurityModeSelector {
requiresConfirmation: false,
warnings: []
},
{
mode: 'browser',
name: 'Navigateur',
description: 'Utilise les fonctionnalités de sécurité du navigateur',
securityLevel: 'medium',
requiresConfirmation: false,
warnings: []
},
{
mode: '2fa',
name: 'Application 2FA',
@ -219,11 +211,6 @@ export class SecurityModeSelector {
'✅ Chiffrement matériel',
'✅ Protection par mot de passe'
],
'browser': [
'✅ WebAuthn standard',
'⚠️ Dépendant du navigateur',
'⚠️ Moins sécurisé que les options OS'
],
'2fa': [],
'none': []
};

View File

@ -106,7 +106,9 @@ document.addEventListener('DOMContentLoaded', async () => {
continueBtn.addEventListener('click', () => {
console.log('🏠 Redirecting to main application...');
// Rediriger vers l'application principale
window.location.href = '/';
console.log('🎂 Birthday setup completed, checking storage state...');
const { checkStorageStateAndNavigate } = await import('../../router');
await checkStorageStateAndNavigate();
});
function updateStatus(message: string, type: 'loading' | 'success' | 'error') {

View File

@ -537,22 +537,13 @@ async function showSecurityModeSelector(): Promise<void> {
<div style="color: #5a6c7d; font-size: 14px;">Utilise l'authentificateur intégré de votre système</div>
</div>
<div class="security-option" data-mode="browser" style="border: 2px solid #e1e8ed; border-radius: 8px; padding: 15px; cursor: pointer; transition: all 0.3s ease;">
<div class="security-option" data-mode="otp" style="border: 2px solid #e1e8ed; border-radius: 8px; padding: 15px; cursor: pointer; transition: all 0.3s ease;">
<div style="display: flex; align-items: center; margin-bottom: 8px;">
<span style="font-size: 20px; margin-right: 10px;">🌐</span>
<span style="font-weight: 600; color: #2c3e50;">Navigateur</span>
<span style="background: #fff3cd; color: #856404; padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: 600; margin-left: auto;">Moyennement sécurisé</span>
<span style="font-size: 20px; margin-right: 10px;">🔐</span>
<span style="font-weight: 600; color: #2c3e50;">OTP (code à usage unique)</span>
<span style="background: #d4edda; color: #155724; padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: 600; margin-left: auto;">Sécurisé</span>
</div>
<div style="color: #5a6c7d; font-size: 14px;">Utilise les fonctionnalités de sécurité du navigateur</div>
</div>
<div class="security-option" data-mode="2fa" style="border: 2px solid #e1e8ed; border-radius: 8px; padding: 15px; cursor: pointer; transition: all 0.3s ease;">
<div style="display: flex; align-items: center; margin-bottom: 8px;">
<span style="font-size: 20px; margin-right: 10px;">📱</span>
<span style="font-weight: 600; color: #2c3e50;">Application 2FA</span>
<span style="background: #f8d7da; color: #721c24; padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: 600; margin-left: auto;"> Non sécurisé</span>
</div>
<div style="color: #5a6c7d; font-size: 14px;">Stockage en clair avec authentification 2FA</div>
<div style="color: #5a6c7d; font-size: 14px;">Code OTP généré par Proton Pass, Google Authenticator, etc.</div>
</div>
<div class="security-option" data-mode="password" style="border: 2px solid #e1e8ed; border-radius: 8px; padding: 15px; cursor: pointer; transition: all 0.3s ease;">
@ -798,7 +789,8 @@ async function waitForCredentialsAvailability(): Promise<void> {
isWaitingForCredentials = true;
try {
const { secureCredentialsService } = await import('../../services/secure-credentials.service');
const { SecureCredentialsService } = await import('../../services/secure-credentials.service');
const secureCredentialsService = SecureCredentialsService.getInstance();
let attempts = 0;
const maxAttempts = 20;
@ -891,7 +883,8 @@ async function handleMainPairing(): Promise<void> {
}
// Import and trigger authentication with selected mode
const { secureCredentialsService } = await import('../../services/secure-credentials.service');
const { SecureCredentialsService } = await import('../../services/secure-credentials.service');
const secureCredentialsService = SecureCredentialsService.getInstance();
// Check if we have existing credentials (regardless of wallet existence)
console.log('🔍 Checking for existing credentials...');
@ -1114,7 +1107,8 @@ async function handleDeleteAccount(): Promise<void> {
// Get services
const service = await Services.getInstance();
const { secureCredentialsService } = await import('../../services/secure-credentials.service');
const { SecureCredentialsService } = await import('../../services/secure-credentials.service');
const secureCredentialsService = SecureCredentialsService.getInstance();
// Delete all credentials
await secureCredentialsService.deleteCredentials();

View File

@ -122,11 +122,6 @@
<div class="option-description">Utilise l'authentification biométrique de votre système</div>
</div>
<div class="security-option" data-mode="browser">
<div class="option-title">🌐 Navigateur</div>
<div class="option-description">Sauvegarde dans le gestionnaire de mots de passe du navigateur</div>
</div>
<div class="security-option" data-mode="otp">
<div class="option-title">🔐 OTP (code à usage unique)</div>
<div class="option-description">Code OTP généré par Proton Pass, Google Authenticator, etc.</div>

View File

@ -89,7 +89,9 @@ document.addEventListener('DOMContentLoaded', async () => {
console.log('✅ PBKDF2 key generated and stored securely');
// Rediriger vers la page de génération du wallet
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
console.log('🔐 Security setup completed, checking storage state...');
const { checkStorageStateAndNavigate } = await import('../../router');
await checkStorageStateAndNavigate();
} catch (error) {
console.error('❌ PBKDF2 key generation failed:', error);

View File

@ -112,7 +112,7 @@
<div class="progress-bar" id="progressBar"></div>
</div>
<button class="continue-btn" id="continueBtn" disabled>Continuer vers le Pairing</button>
<button class="continue-btn" id="continueBtn" disabled>Ajouter la date anniversaire du wallet</button>
</div>
<script type="module" src="./wallet-setup.ts"></script>

View File

@ -142,8 +142,8 @@ document.addEventListener('DOMContentLoaded', async () => {
console.log('🔍 Testing all security modes to find a PBKDF2 key...');
// Tester tous les modes de sécurité pour trouver une clé PBKDF2 valide
const allSecurityModes: Array<'browser' | 'otp' | 'password' | 'none' | 'os' | 'proton-pass'> =
['browser', 'otp', 'password', 'none', 'os', 'proton-pass'];
const allSecurityModes: Array<'otp' | 'password' | 'none' | 'os' | 'proton-pass'> =
['otp', 'password', 'none', 'os', 'proton-pass'];
let currentMode: string | null = null;
@ -466,6 +466,8 @@ document.addEventListener('DOMContentLoaded', async () => {
// Gestion du bouton continuer
continueBtn.addEventListener('click', () => {
console.log('🔗 Redirecting to pairing page...');
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
console.log('💰 Wallet setup completed, checking storage state...');
const { checkStorageStateAndNavigate } = await import('../../router');
await checkStorageStateAndNavigate();
});
});

View File

@ -17,10 +17,90 @@ const routes: { [key: string]: string } = {
account: '/src/pages/account/account.html',
chat: '/src/pages/chat/chat.html',
signature: '/src/pages/signature/signature.html',
'security-setup': '/src/pages/security-setup/security-setup.html',
'wallet-setup': '/src/pages/wallet-setup/wallet-setup.html',
'birthday-setup': '/src/pages/birthday-setup/birthday-setup.html',
};
export let currentRoute = '';
/**
* Vérifie l'état du storage et détermine quelle page afficher selon l'état actuel
* Logique de progression :
* - Si pairing account
* - Si date anniversaire pairing
* - Si wallet birthday-setup
* - Si pbkdf2 wallet-setup
* - Sinon security-setup
*/
export async function checkStorageStateAndNavigate(): Promise<void> {
console.log('🔍 Checking storage state to determine next step...');
try {
// Vérifier l'état du pairing
const services = await Services.getInstance();
const isPaired = services.isPaired();
if (isPaired) {
console.log('✅ Device is paired, navigating to account page');
await navigate('account');
return;
}
// Vérifier si la date anniversaire est configurée (wallet avec birthday > 0)
const device = await services.getDeviceFromDatabase();
if (device && device.sp_wallet && device.sp_wallet.birthday > 0) {
console.log('🎂 Birthday is configured, navigating to pairing');
await navigate('home'); // Pour l'instant, rediriger vers home pour le pairing
return;
}
// Vérifier si le wallet existe (même avec birthday = 0)
if (device && device.sp_wallet) {
console.log('💰 Wallet exists but birthday not set, navigating to birthday-setup');
await navigate('birthday-setup');
return;
}
// Vérifier si une clé PBKDF2 existe
const { SecureCredentialsService } = await import('./services/secure-credentials.service');
const secureCredentialsService = SecureCredentialsService.getInstance();
const allSecurityModes: Array<'otp' | 'password' | 'none' | 'os' | 'proton-pass'> =
['otp', 'password', 'none', 'os', 'proton-pass'];
let hasPBKDF2Key = false;
for (const mode of allSecurityModes) {
try {
const key = await secureCredentialsService.retrievePBKDF2Key(mode);
if (key) {
hasPBKDF2Key = true;
console.log(`🔐 PBKDF2 key found for mode: ${mode}`);
break;
}
} catch (error) {
// Mode non disponible, continuer
}
}
if (hasPBKDF2Key) {
console.log('🔐 PBKDF2 key exists, navigating to wallet-setup');
await navigate('wallet-setup');
return;
}
// Aucune clé PBKDF2 trouvée, commencer par la configuration de sécurité
console.log('🔐 No PBKDF2 key found, navigating to security-setup');
await navigate('security-setup');
} catch (error) {
console.error('❌ Error checking storage state:', error);
// En cas d'erreur, commencer par la configuration de sécurité
console.log('🔐 Error occurred, defaulting to security-setup');
await navigate('security-setup');
}
}
export async function navigate(path: string) {
console.log('🧭 Navigate called with path:', path);
cleanSubscriptions();
@ -77,6 +157,12 @@ async function handleLocation(path: string) {
} catch (error) {
console.error('❌ Failed to initialize home page:', error);
}
} else if (path === 'security-setup' || path === 'wallet-setup' || path === 'birthday-setup') {
console.log('📍 Processing setup route:', path);
// Pour les pages de setup, rediriger directement vers la page HTML
window.location.href = routeHtml;
return;
} else {
console.log('📍 Processing other route:', path);
const html = await fetch(routeHtml).then(data => data.text());
@ -138,108 +224,19 @@ export async function init(): Promise<void> {
try {
console.log('🚀 Starting application initialization...');
// ÉTAPE 1: GESTION DE LA SÉCURITÉ (clés de sécurité) - EN PREMIER
console.log('🔐 Step 1: Security key management...');
const securityConfigured = await handleSecurityKeyManagement();
if (!securityConfigured) {
console.log('🔐 Security not configured, redirecting to home for setup...');
// Naviguer directement vers home pour la configuration de sécurité
handleLocation('home');
return;
}
// ÉTAPE 2: INITIALISATION DES SERVICES (seulement après la sécurité)
console.log('🔧 Step 2: Initializing services...');
// Initialiser les services de base
console.log('🔧 Initializing basic services...');
const services = await Services.getInstance();
(window as any).myService = services;
const db = await Database.getInstance();
// Register service worker and wait for it to be ready
// Register service worker
console.log('📱 Registering service worker...');
await db.registerServiceWorker('/src/service-workers/database.worker.js');
// ÉTAPE 3: CONNEXION AUX RELAIS (pour la hauteur de blocs)
console.log('🌐 Step 3: Connecting to relays for block height...');
try {
console.log('🌐 Connecting to relays...');
services.updateUserStatus('🌐 Connecting to blockchain relays...');
await services.connectAllRelays();
console.log('✅ Relays connected successfully');
services.updateUserStatus('✅ Connected to blockchain relays');
// CRITICAL: Wait for handshake to be processed and block height to be set
console.log('⏳ Waiting for relay handshake to complete...');
services.updateUserStatus('⏳ Waiting for blockchain synchronization...');
await services.waitForBlockHeight();
console.log('✅ Block height received from relay');
services.updateUserStatus('✅ Blockchain synchronized');
} catch (error) {
console.warn('⚠️ Failed to connect to some relays:', error);
console.log('🔄 Continuing despite relay connection issues...');
services.updateUserStatus('⚠️ Some relays unavailable, continuing...');
}
// ÉTAPE 4: GÉNÉRATION/CHARGEMENT DU WALLET
console.log('💰 Step 4: Wallet generation/loading...');
const device = await services.getDeviceFromDatabase();
console.log('🚀 ~ device:', device);
if (!device) {
// No wallet exists, create new account
console.log('🔍 No existing wallet found, creating new account...');
services.updateUserStatus('🔍 Creating new secure wallet...');
await services.createNewDevice();
services.updateUserStatus('✅ New wallet created successfully');
} else {
// Wallet exists, restore it and check pairing
console.log('🔍 Existing wallet found, restoring account...');
services.updateUserStatus('🔍 Restoring existing wallet...');
services.restoreDevice(device);
services.updateUserStatus('✅ Wallet restored successfully');
}
// CRITICAL: Now that block height is set, synchronize wallet
console.log('🔄 Synchronizing wallet with blockchain...');
services.updateUserStatus('🔄 Synchronizing wallet with blockchain...');
await services.updateDeviceBlockHeight();
console.log('✅ Wallet synchronization completed');
services.updateUserStatus('✅ Wallet synchronized successfully');
// ÉTAPE 5: HANDSHAKE
console.log('🤝 Step 5: Performing handshake...');
await performHandshake(services);
// ÉTAPE 6: PAIRING
console.log('🔗 Step 6: Device pairing...');
await handlePairing(services);
// ÉTAPE 7: ÉCOUTE DES PROCESSUS
console.log('👂 Step 7: Starting process listening...');
await startProcessListening(services);
// We register all the event listeners if we run in an iframe
if (window.self !== window.top) {
console.log('🖼️ Registering iframe listeners...');
await registerAllListeners();
// Add iframe mode class for styling
document.body.classList.add('iframe-mode');
}
// Navigate to appropriate page based on pairing status
const isPaired = services.isPaired();
console.log('🔍 Device pairing status:', isPaired ? 'Paired' : 'Not paired');
if (isPaired) {
console.log('✅ Device is paired, navigating to account page...');
await navigate('account');
console.log('✅ Navigation to account completed');
} else {
console.log('⚠️ Device not paired, navigating to home page...');
await navigate('home');
console.log('✅ Navigation to home completed');
}
// Vérifier l'état du storage et naviguer vers la page appropriée
console.log('🔍 Checking storage state and navigating to appropriate page...');
await checkStorageStateAndNavigate();
console.log('✅ Application initialization completed successfully');
} catch (error) {
@ -254,8 +251,8 @@ export async function init(): Promise<void> {
alert('⚠️ Insufficient memory for WebAssembly. Please refresh the page or close other tabs.');
}
console.log('🔄 Falling back to home page...');
await navigate('home');
console.log('🔄 Falling back to security-setup...');
await navigate('security-setup');
}
}

View File

@ -264,47 +264,4 @@ export class EncryptionService {
}
/**
* WARNING: DEPRECATED - This method does NOT encrypt, only base64 encoding
* Use encryptWithPassword or WebAuthn key encryption instead
*/
async encryptWithWebAuthn(
credentials: CredentialData,
credentialId: string
): Promise<string> {
secureLogger.error('encryptWithWebAuthn is deprecated and does NOT encrypt data', new Error('Use encryptWithPassword or WebAuthn encryption'));
const data = JSON.stringify({
spendKey: credentials.spendKey,
scanKey: credentials.scanKey,
timestamp: credentials.timestamp
});
// WARNING: Only base64 encoding, no encryption - DO NOT USE FOR SENSITIVE DATA
const encoded = btoa(data);
return encoded;
}
/**
* WARNING: DEPRECATED - This method does NOT decrypt, only base64 decoding
* Use decryptWithPassword or WebAuthn key decryption instead
*/
async decryptWithWebAuthn(
encryptedData: string,
credentialId: string
): Promise<CredentialData> {
secureLogger.error('decryptWithWebAuthn is deprecated and does NOT decrypt data', new Error('Use decryptWithPassword or WebAuthn decryption'));
// WARNING: Only base64 decoding, no decryption - DO NOT USE FOR SENSITIVE DATA
const decoded = atob(encryptedData);
const data = JSON.parse(decoded);
return {
spendKey: data.spendKey,
scanKey: data.scanKey,
salt: new Uint8Array(0),
iterations: 0,
timestamp: data.timestamp
};
}
}

View File

@ -20,77 +20,6 @@ export class StorageService {
return StorageService.instance;
}
/**
* Stocke une clé dans le gestionnaire de mots de passe du navigateur
*/
async storeKeyInBrowser(key: string): Promise<void> {
try {
secureLogger.info('Storing key in browser password manager', {
component: 'StorageService',
operation: 'storeKeyInBrowser'
});
// IMPORTANT: Ne jamais stocker la clé PBKDF2 en clair !
// Utiliser l'API Credential Management pour stocker comme mot de passe
// Le navigateur chiffrera automatiquement le mot de passe
const credential = new PasswordCredential({
id: 'lecoffre-pbkdf2-key',
password: key, // Le navigateur chiffre automatiquement ce mot de passe
name: 'LeCoffre PBKDF2 Key'
});
await navigator.credentials.store(credential);
secureLogger.info('Key stored in browser password manager successfully (encrypted by browser)', {
component: 'StorageService',
operation: 'storeKeyInBrowser'
});
} catch (error) {
secureLogger.error('Failed to store key in browser password manager', {
component: 'StorageService',
operation: 'storeKeyInBrowser',
error: error instanceof Error ? error.message : String(error)
});
throw error;
}
}
/**
* Stocke un wallet chiffré
*/
async storeEncryptedWallet(encryptedWallet: string): Promise<void> {
try {
secureLogger.info('Storing encrypted wallet', {
component: 'StorageService',
operation: 'storeEncryptedWallet'
});
const db = await this.openDatabase();
const transaction = db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
await new Promise<void>((resolve, reject) => {
const request = store.put(encryptedWallet, 'encrypted-wallet');
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
secureLogger.info('Encrypted wallet stored successfully', {
component: 'StorageService',
operation: 'storeEncryptedWallet'
});
} catch (error) {
secureLogger.error('Failed to store encrypted wallet', {
component: 'StorageService',
operation: 'storeEncryptedWallet',
error: error instanceof Error ? error.message : String(error)
});
throw error;
}
}
/**
* Stocke une clé en clair (non recommandé)
*/
@ -126,6 +55,92 @@ export class StorageService {
}
}
/**
* Récupère une clé en clair depuis IndexedDB
*/
async retrievePlainKey(): Promise<string | null> {
try {
const db = await this.openDatabase();
const transaction = db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const result = await new Promise<string | null>((resolve, reject) => {
const request = store.get('plain-pbkdf2-key');
request.onsuccess = () => resolve(request.result || null);
request.onerror = () => reject(request.error);
});
return result;
} catch (error) {
secureLogger.error('Failed to retrieve plain key', {
component: 'StorageService',
operation: 'retrievePlainKey',
error: error instanceof Error ? error.message : String(error)
});
return null;
}
}
/**
* Récupère une clé chiffrée depuis IndexedDB
*/
async retrieveEncryptedKey(): Promise<string | null> {
try {
const db = await this.openDatabase();
const transaction = db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const result = await new Promise<string | null>((resolve, reject) => {
const request = store.get('encrypted-pbkdf2-key');
request.onsuccess = () => resolve(request.result || null);
request.onerror = () => reject(request.error);
});
return result;
} catch (error) {
secureLogger.error('Failed to retrieve encrypted key', {
component: 'StorageService',
operation: 'retrieveEncryptedKey',
error: error instanceof Error ? error.message : String(error)
});
return null;
}
}
/**
* Stocke une clé chiffrée dans IndexedDB
*/
async storeEncryptedKey(encryptedKey: string, securityMode?: string): Promise<void> {
try {
secureLogger.info('Storing encrypted key', {
component: 'StorageService',
operation: 'storeEncryptedKey'
});
const db = await this.openDatabase();
const transaction = db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
await new Promise<void>((resolve, reject) => {
const request = store.put(encryptedKey, 'encrypted-pbkdf2-key');
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
secureLogger.info('Encrypted key stored successfully', {
component: 'StorageService',
operation: 'storeEncryptedKey'
});
} catch (error) {
secureLogger.error('Failed to store encrypted key', {
component: 'StorageService',
operation: 'storeEncryptedKey',
error: error instanceof Error ? error.message : String(error)
});
throw error;
}
}
/**
* Ouvre la base de données IndexedDB
*/
@ -134,7 +149,8 @@ export class StorageService {
const request = indexedDB.open(this.dbName, this.dbVersion);
request.onerror = () => {
secureLogger.error('Failed to open IndexedDB', new Error('Database open failed'), {
const error = new Error('Database open failed');
secureLogger.error('Failed to open IndexedDB', error, {
component: 'StorageService',
operation: 'openDatabase'
});

View File

@ -58,11 +58,11 @@ export class SecureCredentialsService {
let currentMode = await this.securityModeService.getCurrentMode();
if (!currentMode) {
secureLogger.warn('No security mode selected, using default mode: browser', {
secureLogger.warn('No security mode selected, using default mode: none', {
component: 'SecureCredentialsService',
operation: 'generateSecureCredentials'
});
currentMode = 'browser';
currentMode = 'none';
await this.securityModeService.setSecurityMode(currentMode);
}
@ -83,8 +83,6 @@ export class SecureCredentialsService {
} else if (modeConfig.implementation.useEncryption) {
if (currentMode === 'password') {
return this.generatePasswordCredentials(password, _options);
} else if (currentMode === 'browser') {
return this.generateBrowserCredentials(password, _options);
} else {
return this.generateEncryptedCredentials(password, _options);
}
@ -113,10 +111,6 @@ export class SecureCredentialsService {
// Récupérer la clé chiffrée avec WebAuthn
return await webAuthnService.retrieveKeyWithWebAuthn(securityMode);
case 'browser':
// Récupérer la clé du gestionnaire de mots de passe
return await storageService.retrieveKeyFromBrowser();
case 'otp':
// Récupérer la clé en clair (l'OTP protège l'accès)
return await storageService.retrievePlainKey();
@ -126,8 +120,15 @@ export class SecureCredentialsService {
return await storageService.retrieveEncryptedKey();
case 'none':
// Récupérer la clé en clair
return await storageService.retrievePlainKey();
// Récupérer la clé chiffrée avec la clé en dur
const encryptedData = await storageService.retrieveEncryptedKey();
if (encryptedData) {
const { EncryptionService } = await import('./encryption.service');
const encryptionService = EncryptionService.getInstance();
const hardcodedKey = '4NK_DEFAULT_ENCRYPTION_KEY_NOT_SECURE';
return await encryptionService.decrypt(encryptedData, hardcodedKey);
}
return null;
default:
return null;
@ -182,12 +183,6 @@ export class SecureCredentialsService {
await webAuthnService.storeKeyWithWebAuthn(pbkdf2Key, securityMode);
break;
case 'browser':
// Stocker dans le gestionnaire de mots de passe du navigateur
console.log('🔐 Storing PBKDF2 key in browser password manager...');
await storageService.storeKeyInBrowser(pbkdf2Key);
break;
case 'otp':
// Générer un secret OTP pour l'authentification (pas de chiffrement)
console.log('🔐 Setting up OTP authentication for PBKDF2 key...');
@ -208,9 +203,11 @@ export class SecureCredentialsService {
break;
case 'none':
// Stockage en clair (non recommandé)
console.log('⚠️ Storing PBKDF2 key in plain text (not recommended)...');
await storageService.storePlainKey(pbkdf2Key);
// Chiffrer avec une clé déterminée en dur (non sécurisé)
console.log('⚠️ Storing PBKDF2 key with hardcoded encryption (not recommended)...');
const hardcodedKey = '4NK_DEFAULT_ENCRYPTION_KEY_NOT_SECURE';
const encryptedKeyNone = await encryptionService.encrypt(pbkdf2Key, hardcodedKey);
await storageService.storeEncryptedKey(encryptedKeyNone, securityMode);
break;
default:
@ -347,57 +344,6 @@ export class SecureCredentialsService {
}
}
/**
* Génère des credentials pour le navigateur
*/
private async generateBrowserCredentials(
password: string,
_options: CredentialOptions = {}
): Promise<CredentialData> {
try {
secureLogger.info('Generating browser-saved credentials', {
component: 'SecureCredentialsService',
operation: 'generateBrowserCredentials'
});
// Import dynamique du service
const { EncryptionService } = await import('./encryption.service');
const encryptionService = EncryptionService.getInstance();
// Générer des clés aléatoires
const keys = encryptionService.generateRandomKeys();
// Créer un formulaire temporaire pour déclencher la sauvegarde du navigateur
const form = document.createElement('form');
form.style.cssText = 'position: absolute; left: -9999px; top: -9999px;';
form.innerHTML = `
<input type="text" name="username" value="4NK_SecureKey" style="display: none;">
<input type="password" name="password" value="${keys.spendKey}" style="display: none;">
<input type="password" name="scanKey" value="${keys.scanKey}" style="display: none;">
`;
document.body.appendChild(form);
const submitEvent = new Event('submit', { bubbles: true, cancelable: true });
form.dispatchEvent(submitEvent);
await new Promise(resolve => setTimeout(resolve, 1000));
document.body.removeChild(form);
return {
spendKey: keys.spendKey,
scanKey: keys.scanKey,
salt: new Uint8Array(0),
iterations: 0,
timestamp: Date.now()
};
} catch (error) {
secureLogger.error('Failed to generate browser credentials', error as Error, {
component: 'SecureCredentialsService',
operation: 'generateBrowserCredentials'
});
throw error;
}
}
/**
* Génère des credentials chiffrés
*/
@ -777,4 +723,66 @@ QR Code URL: ${qrUrl}`);
throw error;
}
}
}
/**
* Supprime les credentials (alias pour deleteCredentials)
*/
async deleteCredentials(): Promise<void> {
return this.clearCredentials();
}
/**
* Demande un mot de passe à l'utilisateur
*/
private async promptForPassword(): Promise<string> {
return new Promise((resolve, reject) => {
const password = prompt('Entrez un mot de passe pour chiffrer la clé PBKDF2:');
if (password) {
resolve(password);
} else {
reject(new Error('Password prompt cancelled'));
}
});
}
/**
* Valide la force d'un mot de passe
*/
validatePasswordStrength(password: string): {
isValid: boolean;
score: number;
feedback: string[];
} {
const feedback: string[] = [];
let score = 0;
// Vérifications de complexité
if (password.length < 8) {
feedback.push('Le mot de passe doit contenir au moins 8 caractères');
return { isValid: false, score: 0, feedback };
}
if (password.length >= 8) score += 1;
if (password.length >= 12) score += 1;
if (password.length >= 16) score += 1;
if (/[a-z]/.test(password)) score += 1;
if (/[A-Z]/.test(password)) score += 1;
if (/[0-9]/.test(password)) score += 1;
if (/[^a-zA-Z0-9]/.test(password)) score += 1;
// Feedback détaillé
if (score < 3) {
feedback.push('Mot de passe faible : ajoutez des majuscules, chiffres et caractères spéciaux');
} else if (score < 5) {
feedback.push('Mot de passe moyen : renforcez avec plus de complexité');
}
const isValid = score >= 3;
return { isValid, score, feedback };
}
}
// Export instance pour compatibilité
export const secureCredentialsService = SecureCredentialsService.getInstance();

View File

@ -6,7 +6,7 @@
import { secureLogger } from './secure-logger';
export type SecurityMode = 'proton-pass' | 'os' | 'browser' | 'otp' | '2fa' | 'password' | 'none';
export type SecurityMode = 'proton-pass' | 'os' | 'otp' | '2fa' | 'password' | 'none';
export interface SecurityModeConfig {
mode: SecurityMode;
@ -20,6 +20,8 @@ export interface SecurityModeConfig {
useEncryption: boolean;
usePlatformAuth: boolean;
storageType: 'encrypted' | 'plain' | 'hybrid';
requiresOTP?: boolean;
requiresPassword?: boolean;
};
}
@ -124,20 +126,6 @@ export class SecurityModeService {
storageType: 'encrypted'
}
},
'browser': {
mode: 'browser',
name: 'Navigateur',
description: 'Utilise les fonctionnalités de sécurité du navigateur',
securityLevel: 'medium',
requiresConfirmation: false,
warnings: [],
implementation: {
useWebAuthn: true,
useEncryption: true,
usePlatformAuth: false,
storageType: 'encrypted'
}
},
'otp': {
mode: 'otp',
name: 'OTP (Proton Pass Compatible)',
@ -193,19 +181,20 @@ export class SecurityModeService {
'none': {
mode: 'none',
name: 'Aucune Sécurité',
description: 'Stockage en clair sans aucune protection',
description: 'Chiffrement avec une clé déterminée en dur (non sécurisé)',
securityLevel: 'critical',
requiresConfirmation: true,
warnings: [
'🚨 Clés stockées en clair',
'🚨 Clé de chiffrement en dur',
'🚨 Accès non protégé',
'🚨 RISQUE ÉLEVÉ'
],
implementation: {
useWebAuthn: false,
useEncryption: false,
useEncryption: true,
usePlatformAuth: false,
storageType: 'plain'
storageType: 'encrypted',
requiresPassword: false
}
}
};
@ -244,14 +233,14 @@ export class SecurityModeService {
* Récupère tous les modes disponibles
*/
public getAvailableModes(): SecurityMode[] {
return ['proton-pass', 'os', 'browser', '2fa', 'none'];
return ['proton-pass', 'os', 'otp', '2fa', 'password', 'none'];
}
/**
* Récupère les modes recommandés (sécurisés)
*/
public getRecommendedModes(): SecurityMode[] {
return ['proton-pass', 'os', 'browser'];
return ['proton-pass', 'os', 'otp'];
}
/**

View File

@ -9,7 +9,7 @@ import { eventBus } from './event-bus';
import { secureLogger } from './secure-logger';
import { memoryManager } from './memory-manager';
import { secureKeyManager } from './secure-key-manager';
import { SecureCredentialsService } from './secure-credentials.service';
import { SecureCredentialsService, secureCredentialsService } from './secure-credentials.service';
import Database from './database.service';
export interface ServiceContainer {

View File

@ -1841,16 +1841,16 @@ export default class Services {
if (dbRes['encrypted_device']) {
// New encrypted format - need to decrypt
console.log('🔐 Wallet found in encrypted format, decrypting...');
// Get the PBKDF2 key based on security mode
const { SecureCredentialsService } = await import('./secure-credentials.service');
const secureCredentialsService = SecureCredentialsService.getInstance();
// Get all security modes to find which one works
const allSecurityModes = ['browser', 'otp', 'password', 'none', 'os', 'proton-pass'];
const allSecurityModes = ['otp', 'password', 'none', 'os', 'proton-pass'];
let pbkdf2Key: string | null = null;
let workingMode: string | null = null;
for (const mode of allSecurityModes) {
try {
const key = await secureCredentialsService.retrievePBKDF2Key(mode as any);
@ -1863,20 +1863,20 @@ export default class Services {
// Continue to next mode
}
}
if (!pbkdf2Key) {
throw new Error('Failed to retrieve PBKDF2 key - cannot decrypt wallet');
}
// Decrypt the device
const { EncryptionService } = await import('./encryption.service');
const encryptionService = EncryptionService.getInstance();
const decryptedDeviceString = await encryptionService.decrypt(
dbRes['encrypted_device'],
pbkdf2Key
);
const decryptedDevice = JSON.parse(decryptedDeviceString);
console.log('✅ Wallet decrypted successfully');
return decryptedDevice;