diff --git a/api_server.py b/api_server.py index 57b1adc..b2adba7 100644 --- a/api_server.py +++ b/api_server.py @@ -96,12 +96,11 @@ class UserKeyManager: user_data = self.keys_db[user_hash] last_rotation = datetime.fromisoformat(user_data['last_rotation']) - # Rotation automatique si plus de 1 heure - if current_time - last_rotation > timedelta(hours=1): - self._rotate_user_key(user_hash, user_id) - # Recharger la base de données après rotation - self.keys_db = self._load_keys_db() - user_data = self.keys_db[user_hash] + # Rotation automatique à chaque requête (sécurité maximale) + self._rotate_user_key(user_hash, user_id) + # Recharger la base de données après rotation + self.keys_db = self._load_keys_db() + user_data = self.keys_db[user_hash] return base64.b64decode(user_data['current_key']) @@ -287,9 +286,9 @@ class SecureVaultAPI: processed_content = self.env_processor.process_content(file_content) # Chiffrement avec la clé utilisateur pour cet environnement - encrypted_content = self._encrypt_with_user_key(processed_content, user_id, env) + encrypted_content, next_key = self._encrypt_with_user_key_and_next(processed_content, user_id, env) - # Retour de la réponse chiffrée + # Retour du contenu chiffré avec la prochaine clé response = Response( encrypted_content, mimetype='application/octet-stream', @@ -297,7 +296,9 @@ class SecureVaultAPI: 'X-Encryption-Type': 'quantum-resistant', 'X-Algorithm': 'X25519-ChaCha20-Poly1305', 'X-User-ID': user_id, - 'X-Key-Rotation': 'enabled' + 'X-Key-Rotation': 'per-request', + 'X-Next-Key': next_key, + 'X-Content-Processed': 'true' } ) @@ -363,25 +364,59 @@ class SecureVaultAPI: logger.error(f"Erreur lors de la lecture du fichier {env}/{file_path}: {e}") return None - def _encrypt_with_user_key(self, content: str, user_id: str, environment: str) -> bytes: - """Chiffre le contenu avec la clé utilisateur pour un environnement spécifique""" + def _encrypt_with_user_key_and_next(self, content: str, user_id: str, environment: str) -> tuple[bytes, str]: + """Chiffre le contenu avec la clé utilisateur et génère la prochaine clé""" try: # Création du gestionnaire de clés pour cet environnement key_manager = UserKeyManager(environment) - # Création ou récupération de la clé utilisateur + # Création ou récupération de la clé utilisateur (avec rotation automatique) current_key = key_manager.get_or_create_user_key(user_id) + # Génération de la prochaine clé pour la requête suivante + next_key = key_manager._generate_user_key(user_id) + next_key_b64 = base64.b64encode(next_key).decode() + # Chiffrement avec la clé actuelle - return self._encrypt_content(content, current_key, user_id, environment) + encrypted_content = self._encrypt_content_with_next_key(content, current_key, next_key, user_id, environment) + + return encrypted_content, next_key_b64 except Exception as e: logger.error(f"Erreur de chiffrement pour l'utilisateur {user_id} dans l'environnement {environment}: {e}") # Fallback: retour du contenu en base64 sans chiffrement - return base64.b64encode(content.encode('utf-8')) + return base64.b64encode(content.encode('utf-8')), "" + + def _encrypt_with_user_key(self, content: str, user_id: str, environment: str) -> bytes: + """Chiffre le contenu avec la clé utilisateur pour un environnement spécifique (legacy)""" + encrypted_content, _ = self._encrypt_with_user_key_and_next(content, user_id, environment) + return encrypted_content + + def _encrypt_content_with_next_key(self, content: str, current_key: bytes, next_key: bytes, user_id: str, environment: str) -> bytes: + """Chiffre le contenu avec la clé actuelle et inclut la prochaine clé""" + cipher = ChaCha20Poly1305(current_key) + nonce = secrets.token_bytes(12) + encrypted_content = cipher.encrypt(nonce, content.encode('utf-8'), None) + + # Métadonnées de chiffrement avec la prochaine clé + metadata = { + 'user_id': user_id, + 'environment': environment, + 'timestamp': datetime.now().isoformat(), + 'key_version': 'current', + 'algorithm': 'ChaCha20-Poly1305', + 'next_key': base64.b64encode(next_key).decode(), + 'rotation': 'per-request' + } + + # Encodage: nonce + métadonnées + contenu chiffré + metadata_json = json.dumps(metadata).encode('utf-8') + full_payload = nonce + len(metadata_json).to_bytes(4, 'big') + metadata_json + encrypted_content + + return base64.b64encode(full_payload) def _encrypt_content(self, content: str, key: bytes, user_id: str, environment: str, is_previous: bool = False) -> bytes: - """Chiffre le contenu avec une clé spécifique""" + """Chiffre le contenu avec une clé spécifique (legacy)""" cipher = ChaCha20Poly1305(key) nonce = secrets.token_bytes(12) encrypted_content = cipher.encrypt(nonce, content.encode('utf-8'), None) diff --git a/sdk-client/src/index.ts b/sdk-client/src/index.ts index 5f9528e..537766d 100644 --- a/sdk-client/src/index.ts +++ b/sdk-client/src/index.ts @@ -144,8 +144,8 @@ export class SecureVaultClient { const keyRotation = response.headers.get('X-Key-Rotation') || undefined; const algorithm = response.headers.get('X-Algorithm') || undefined; - // Déchiffrement du contenu - const decryptedContent = this.decryptContent(Buffer.from(encryptedData)); + // Déchiffrement du contenu avec les headers pour la prochaine clé + const decryptedContent = this.decryptContent(Buffer.from(encryptedData), response.headers); return { content: decryptedContent, @@ -259,9 +259,9 @@ export class SecureVaultClient { } /** - * Déchiffre le contenu avec les métadonnées utilisateur + * Déchiffre le contenu avec les métadonnées utilisateur et gère la prochaine clé */ - private decryptContent(encryptedData: Buffer): string { + private decryptContent(encryptedData: Buffer, responseHeaders?: Headers): string { try { // Décoder le base64 const decoded = Buffer.from(encryptedData.toString(), 'base64'); @@ -287,18 +287,22 @@ export class SecureVaultClient { ); } - // Note: Le déchiffrement nécessiterait la clé utilisateur - // qui est gérée côté serveur. Dans cette implémentation, - // nous simulons le déchiffrement pour la démonstration. - // En production, il faudrait implémenter un échange de clés sécurisé. + // Récupération de la prochaine clé depuis les headers ou les métadonnées + const nextKey = responseHeaders?.get('X-Next-Key') || metadata.next_key; + if (nextKey) { + // Sauvegarder la prochaine clé pour les requêtes suivantes + this.updateNextKey(nextKey); + } // Pour la démonstration, on retourne un message indiquant - // que le déchiffrement nécessite une clé côté serveur + // que le déchiffrement nécessite la clé utilisateur return `[CONTENU CHIFFRÉ - DÉCHIFFREMENT NÉCESSAIRE]\n` + `Utilisateur: ${metadata.user_id}\n` + `Version de clé: ${metadata.key_version}\n` + `Timestamp: ${metadata.timestamp}\n` + `Algorithme: ${metadata.algorithm}\n` + + `Rotation: ${metadata.rotation || 'unknown'}\n` + + `Prochaine clé: ${nextKey ? 'disponible' : 'non disponible'}\n` + `Taille chiffrée: ${ciphertext.length} bytes`; } catch (error) { @@ -311,6 +315,16 @@ export class SecureVaultClient { } } + /** + * Met à jour la prochaine clé pour les requêtes suivantes + */ + private updateNextKey(nextKey: string): void { + // Dans une implémentation complète, on sauvegarderait cette clé + // pour l'utiliser dans la prochaine requête + console.log(`🔑 Prochaine clé reçue: ${nextKey.substring(0, 20)}...`); + // TODO: Implémenter la sauvegarde et l'utilisation de la clé + } + /** * Effectue une requête vers l'API avec authentification */ diff --git a/sdk-client/test.js b/sdk-client/test.js new file mode 100644 index 0000000..73e5b06 --- /dev/null +++ b/sdk-client/test.js @@ -0,0 +1 @@ +const { createSecureVaultClient } = require('./dist/index.js'); const client = createSecureVaultClient('https://localhost:6666', 'demo_user_001'); client.getFile('dev', 'bitcoin/bitcoin.conf').then(result => { console.log('✅ Succès:', result.filename); console.log('🔑 Contenu:', result.content.substring(0, 200)); console.log('📊 Métadonnées:', { algorithm: result.algorithm, user_id: result.user_id, key_version: result.key_version }); }).catch(err => console.error('❌ Erreur:', err.message));