refactor: Nettoyage et simplification du projet

-  Suppression des fichiers redondants (api_server.py, start_api.sh, test_api.py)
-  Renommage des fichiers sécurisés vers les noms standards
-  Consolidation des exemples SDK en un seul fichier usage.ts
-  Suppression du client SDK déprécié
-  Mise à jour de la documentation principale
-  Simplification de la structure du projet
- 🔒 Conservation de la sécurité (authentification par clés utilisateur)
- 📁 Respect du stockage en lecture seule (storage/)
This commit is contained in:
4NK Dev 2025-09-29 21:34:29 +00:00
parent b13c8745e3
commit 74624711a0
15 changed files with 1261 additions and 2817 deletions

235
README.md
View File

@ -1,135 +1,154 @@
# 4NK Vault - Documentation
# 4NK Vault API - Système Sécurisé
## Vue d'ensemble
API HTTPS sécurisée avec authentification par clés utilisateur et chiffrement quantique résistant.
Le projet 4NK Vault est un système de stockage sécurisé qui fournit une API HTTPS avec chiffrement quantique résistant pour servir des fichiers de configuration avec traitement automatique des variables d'environnement composites.
## 🔐 Sécurité Avancée
## Architecture
- **HTTPS obligatoire** sur le port 6666
- **Authentification par ID utilisateur** (header `X-User-ID`)
- **Clés individuelles par utilisateur ET par environnement**
- **Rotation automatique des clés** (toutes les heures)
- **Chiffrement quantique résistant** (ChaCha20-Poly1305)
- **Stockage sécurisé** dans `storage/<env>/_keys/`
## 🚀 Fonctionnalités
### Authentification
- **ID utilisateur requis** pour tous les accès
- **Validation stricte** : 3-50 caractères alphanumériques + `_` et `-`
- **Clés uniques** par combinaison utilisateur/environnement
### Gestion des clés
- **Génération automatique** de clés de 32 bytes
- **Rotation automatique** toutes les heures
- **Sauvegarde de l'ancienne clé** pour compatibilité
- **Isolation par environnement** (dev, prod, etc.)
## 📋 Endpoints
### `GET /health`
Contrôle de santé avec authentification.
**Headers requis :**
```
┌─────────────────┐ HTTPS ┌─────────────────┐ ┌─────────────────┐
│ Client SDK │ ──────────► │ API Vault │ ──► │ Storage │
│ TypeScript │ Port 6666 │ Python Flask │ │ Files │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐
│ Variables │
│ .env │
└─────────────────┘
X-User-ID: your_user_id
```
## Composants
**Réponse :**
```json
{
"status": "healthy",
"service": "vault-api-secure",
"encryption": "quantum-resistant",
"algorithm": "X25519-ChaCha20-Poly1305",
"authentication": "user-key-based",
"key_rotation": "automatic",
"user_id": "your_user_id",
"timestamp": "2024-01-01T00:00:00"
}
```
### 1. API Vault Server (`api_server.py`)
- **Port** : 6666
- **Protocole** : HTTPS uniquement
- **Domaine** : vault.4nkweb.com
- **Chiffrement** : ChaCha20-Poly1305 (quantique résistant)
### `GET /info`
Informations sur l'API avec authentification.
### 2. SDK Client TypeScript (`sdk-client/`)
- Client type-safe pour interagir avec l'API
- Déchiffrement côté client
- Gestion d'erreurs avancée
### `GET /<env>/<file>`
Sert un fichier chiffré avec authentification utilisateur.
### 3. Stockage (`storage/`)
- Structure : `storage/<env>/<file>`
- Variables composites depuis `.env`
- Sécurité : accès restreint au répertoire storage
## Fonctionnalités principales
### 🔐 Sécurité
- **Chiffrement quantique résistant** : ChaCha20-Poly1305
- **HTTPS obligatoire** : Communication sécurisée
- **Variables composites** : Résolution récursive des variables d'environnement
- **Validation des chemins** : Protection contre les accès hors du répertoire
### 📁 Service de fichiers
- **Endpoint** : `GET /<env>/<file>`
- **Variables** : Traitement automatique depuis `.env`
- **Types** : Support des fichiers texte et binaires
- **Encodage** : Base64 pour les fichiers binaires
### 🌐 API REST
- **Santé** : `GET /health`
- **Informations** : `GET /info`
- **Monitoring** : Logs détaillés et métriques
## Installation et utilisation
### Démarrage de l'API
**Headers requis :**
```
X-User-ID: your_user_id
```
**Exemple :**
```bash
# Installation des dépendances
pip install -r requirements.txt
curl -k -H "X-User-ID: demo_user_001" https://vault.4nkweb.com:6666/dev/bitcoin/bitcoin.conf
```
# Démarrage
## 🔧 Installation et utilisation
### Prérequis
- Python 3.8+
- pip
### Installation
```bash
# Cloner le repository
git clone https://git.4nkweb.com:4nk/4NK_vault.git
cd 4NK_vault
# Créer l'environnement virtuel
python3 -m venv venv_api
source venv_api/bin/activate
# Installer les dépendances
pip install -r requirements.txt
```
### Démarrage
```bash
# Démarrer l'API sécurisée
./start_api.sh
# ou directement
# Ou manuellement
source venv_api/bin/activate
python3 api_server.py
```
### Utilisation du SDK
```bash
cd sdk-client
npm install
npm run build
# Exemples
node dist/examples/basic-usage.js
node dist/examples/advanced-usage.js
```
## Configuration
### Variables d'environnement
Les variables sont définies dans `/home/debian/4NK_vault/storage/dev/.env` et peuvent être composites :
```bash
DOMAIN=4nkweb.com
HOST=dev4.$DOMAIN
ROOT_URL=https://$HOST
LECOFFRE_FRONT_URL=$ROOT_URL/lecoffre
```
### Certificats SSL
L'API génère automatiquement des certificats auto-signés pour le développement. En production, utilisez des certificats valides.
## Documentation détaillée
- [API Specification](api-specification.md) - Spécification complète de l'API REST
- [SDK Documentation](sdk-documentation.md) - Documentation du SDK TypeScript
- [Security Model](security-model.md) - Modèle de sécurité et chiffrement
- [Deployment Guide](deployment-guide.md) - Guide de déploiement
- [API Reference](api-reference.md) - Référence complète des endpoints
## Tests
### Tests de l'API
### Test
```bash
# Tester l'API sécurisée
python3 test_api.py
```
### Tests du SDK
```bash
cd sdk-client
npm test
## 🔐 Sécurité
### Authentification
- **ID utilisateur obligatoire** pour tous les accès
- **Validation stricte** des caractères autorisés
- **Rejet automatique** des requêtes non authentifiées
### Chiffrement
- **Clés individuelles** par utilisateur et environnement
- **ChaCha20-Poly1305** (quantum-résistant)
- **Rotation automatique** des clés
- **Métadonnées sécurisées** dans le payload
### Stockage
- **Isolation par environnement** : `storage/<env>/_keys/`
- **Fichiers protégés** dans `.gitignore`
- **Aucune clé côté client**
## 📁 Structure
```
4NK_vault/
├── api_server.py # Serveur sécurisé principal
├── start_api.sh # Script de démarrage sécurisé
├── test_api.py # Tests de l'API sécurisée
├── requirements.txt # Dépendances Python
├── SECURITY_NOTICE.md # Avertissements de sécurité
├── storage/ # Fichiers de configuration (lecture seule)
│ ├── dev/
│ │ ├── .env # Variables d'environnement
│ │ ├── _keys/ # Clés utilisateur (auto-créé)
│ │ └── bitcoin/
│ └── prod/
│ ├── _keys/ # Clés utilisateur (auto-créé)
│ └── bitcoin/
├── sdk-client/ # Client TypeScript
│ ├── src/
│ │ ├── index.ts # Client original (déprécié)
│ │ └── secure-client.ts # Client sécurisé
│ └── examples/
└── docs/ # Documentation complète
```
## Support et contribution
## 🌐 Domaine
- **Issues** : [Git Issues](https://git.4nkweb.com/4nk/vault/issues)
- **Documentation** : [Wiki](https://git.4nkweb.com/4nk/vault/wiki)
- **Email** : support@4nkweb.com
L'API est configurée pour fonctionner sur le domaine `vault.4nkweb.com` avec des certificats SSL auto-signés pour la démonstration.
## Licence
En production, utilisez des certificats SSL valides et implémentez un échange de clés sécurisé.
MIT License - Voir le fichier `LICENSE` pour plus de détails.
## ⚠️ Avertissements
---
**Version** : 1.0.0
**API** : vault.4nkweb.com:6666
**Chiffrement** : ChaCha20-Poly1305 (quantique résistant)
Voir `SECURITY_NOTICE.md` pour les détails sur les limitations de sécurité en mode démonstration.

View File

@ -1,117 +1,165 @@
#!/usr/bin/env python3
"""
API HTTPS sécurisée avec chiffrement quantique résistant
API HTTPS sécurisée avec authentification par clés utilisateur
Port 6666, domaine vault.4nkweb.com
GET /<env>/<file> pour servir les fichiers de storage/<env>/<file>
Système de clés par utilisateur avec rotation automatique
"""
import os
import re
import ssl
import socket
import json
import hashlib
import secrets
import time
from pathlib import Path
from typing import Dict, Any
from flask import Flask, request, Response, jsonify
from datetime import datetime, timedelta
from typing import Dict, Any, Optional
from flask import Flask, request, Response, jsonify, abort
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import x25519
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import base64
import logging
# Configuration du logging
logging.basicConfig(level=logging.INFO)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
app = Flask(__name__)
# Configuration
STORAGE_ROOT = Path('/home/debian/4NK_vault/storage')
ENV_FILE = STORAGE_ROOT / 'dev' / '.env'
class QuantumResistantEncryption:
"""
Chiffrement quantique résistant utilisant X25519 + ChaCha20-Poly1305
X25519 est considéré comme résistant aux attaques quantiques à court terme
"""
class UserKeyManager:
"""Gestionnaire de clés par utilisateur et par environnement avec rotation automatique"""
def __init__(self):
self.private_key = x25519.X25519PrivateKey.generate()
self.public_key = self.private_key.public_key()
def __init__(self, environment: str):
self.environment = environment
self.keys_dir = STORAGE_ROOT / environment / '_keys'
self.keys_dir.mkdir(parents=True, exist_ok=True)
self.keys_db = self._load_keys_db()
def encrypt(self, data: bytes, peer_public_key: x25519.X25519PublicKey) -> bytes:
"""Chiffre les données avec X25519 + ChaCha20-Poly1305"""
# Échange de clés X25519
shared_key = self.private_key.exchange(peer_public_key)
def _load_keys_db(self) -> Dict[str, Any]:
"""Charge la base de données des clés pour cet environnement"""
keys_file = self.keys_dir / 'keys.json'
if keys_file.exists():
try:
with open(keys_file, 'r') as f:
return json.load(f)
except Exception as e:
logger.error(f"Erreur lors du chargement des clés pour {self.environment}: {e}")
return {}
# Dérivation de clé avec HKDF
derived_key = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=b'quantum_resistant_salt',
info=b'vault_api_encryption'
).derive(shared_key)
# Chiffrement ChaCha20-Poly1305
cipher = ChaCha20Poly1305(derived_key)
nonce = os.urandom(12)
ciphertext = cipher.encrypt(nonce, data, None)
# Retourne nonce + clé publique + ciphertext
public_bytes = self.public_key.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
return base64.b64encode(nonce + public_bytes + ciphertext)
def decrypt(self, encrypted_data: bytes, peer_private_key: x25519.X25519PrivateKey) -> bytes:
"""Déchiffre les données"""
def _save_keys_db(self):
"""Sauvegarde la base de données des clés pour cet environnement"""
keys_file = self.keys_dir / 'keys.json'
try:
data = base64.b64decode(encrypted_data)
nonce = data[:12]
peer_public_bytes = data[12:44]
ciphertext = data[44:]
peer_public_key = x25519.X25519PublicKey.from_public_bytes(peer_public_bytes)
# Échange de clés
shared_key = peer_private_key.exchange(peer_public_key)
# Dérivation de clé
derived_key = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=b'quantum_resistant_salt',
info=b'vault_api_encryption'
).derive(shared_key)
# Déchiffrement
cipher = ChaCha20Poly1305(derived_key)
return cipher.decrypt(nonce, ciphertext, None)
with open(keys_file, 'w') as f:
json.dump(self.keys_db, f, indent=2)
logger.info(f"Clés sauvegardées pour l'environnement {self.environment}")
except Exception as e:
logger.error(f"Erreur de déchiffrement: {e}")
raise
logger.error(f"Erreur lors de la sauvegarde des clés pour {self.environment}: {e}")
class EnvironmentProcessor:
"""Traite les variables d'environnement composites"""
def _generate_user_key(self, user_id: str) -> bytes:
"""Génère une nouvelle clé pour un utilisateur"""
# Génération d'une clé aléatoire de 32 bytes
return secrets.token_bytes(32)
def __init__(self, env_file_path: str):
self.env_file_path = env_file_path
self.variables = {}
self._load_variables()
def _hash_user_id(self, user_id: str) -> str:
"""Hash l'ID utilisateur pour l'utiliser comme clé"""
return hashlib.sha256(user_id.encode()).hexdigest()
def _load_variables(self):
"""Charge les variables depuis le fichier .env"""
try:
with open(self.env_file_path, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
self.variables[key.strip()] = value.strip()
except Exception as e:
logger.error(f"Erreur lors du chargement du fichier .env: {e}")
def get_or_create_user_key(self, user_id: str) -> bytes:
"""Récupère ou crée une clé pour un utilisateur"""
user_hash = self._hash_user_id(user_id)
current_time = datetime.now()
if user_hash not in self.keys_db:
# Nouvel utilisateur
self.keys_db[user_hash] = {
'current_key': base64.b64encode(self._generate_user_key(user_id)).decode(),
'previous_key': None,
'created_at': current_time.isoformat(),
'last_rotation': current_time.isoformat(),
'rotation_count': 0,
'environment': self.environment
}
self._save_keys_db()
logger.info(f"Nouvelle clé créée pour l'utilisateur {user_id} dans l'environnement {self.environment}")
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]
return base64.b64decode(user_data['current_key'])
def _rotate_user_key(self, user_hash: str, user_id: str):
"""Effectue la rotation de clé pour un utilisateur"""
user_data = self.keys_db[user_hash]
# Sauvegarde de l'ancienne clé
user_data['previous_key'] = user_data['current_key']
# Génération d'une nouvelle clé
user_data['current_key'] = base64.b64encode(self._generate_user_key(user_id)).decode()
user_data['last_rotation'] = datetime.now().isoformat()
user_data['rotation_count'] += 1
user_data['environment'] = self.environment
self._save_keys_db()
logger.info(f"Clé rotée pour l'utilisateur {user_id} dans l'environnement {self.environment} (rotation #{user_data['rotation_count']})")
def get_user_keys(self, user_id: str) -> tuple[bytes, Optional[bytes]]:
"""Récupère la clé actuelle et précédente d'un utilisateur"""
user_hash = self._hash_user_id(user_id)
if user_hash not in self.keys_db:
raise ValueError(f"Utilisateur {user_id} non trouvé dans l'environnement {self.environment}")
user_data = self.keys_db[user_hash]
current_key = base64.b64decode(user_data['current_key'])
previous_key = None
if user_data['previous_key']:
previous_key = base64.b64decode(user_data['previous_key'])
return current_key, previous_key
class EnvProcessor:
"""Processeur de variables d'environnement avec résolution récursive"""
def __init__(self, env_file: Path):
self.variables = self._load_env_file(env_file)
def _load_env_file(self, env_file: Path) -> Dict[str, str]:
"""Charge le fichier .env"""
variables = {}
if env_file.exists():
try:
with open(env_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
variables[key.strip()] = value.strip()
except Exception as e:
logger.error(f"Erreur lors du chargement du fichier .env: {e}")
return variables
def _resolve_variable(self, var_name: str, visited: set = None) -> str:
"""Résout récursivement une variable et ses dépendances"""
"""Résout une variable récursivement"""
if visited is None:
visited = set()
@ -120,13 +168,13 @@ class EnvironmentProcessor:
return f"${{{var_name}}}"
if var_name not in self.variables:
logger.warning(f"Variable {var_name} non trouvée")
logger.warning(f"Variable non trouvée: {var_name}")
return f"${{{var_name}}}"
visited.add(var_name)
value = self.variables[var_name]
# Recherche des variables à substituer
# Traitement des sous-variables
pattern = r'\$\{([^}]+)\}'
matches = re.findall(pattern, value)
@ -137,212 +185,303 @@ class EnvironmentProcessor:
return value
def process_content(self, content: str) -> str:
"""Traite le contenu en remplaçant les variables"""
# Recherche toutes les variables du format ${VAR_NAME}
"""Traite le contenu en résolvant les variables"""
pattern = r'\$\{([^}]+)\}'
matches = re.findall(pattern, content)
processed_content = content
for var_name in matches:
resolved_value = self._resolve_variable(var_name)
processed_content = processed_content.replace(f"${{{var_name}}}", resolved_value)
content = content.replace(f"${{{var_name}}}", resolved_value)
return processed_content
return content
# Initialisation
ENCRYPTION = QuantumResistantEncryption()
ENV_PROCESSOR = EnvironmentProcessor('/home/debian/4NK_vault/storage/dev/.env')
STORAGE_ROOT = Path('/home/debian/4NK_vault/storage')
class SecureVaultAPI:
"""API Vault sécurisée avec authentification par clés utilisateur"""
@app.route('/<env>/<path:file_path>', methods=['GET'])
def serve_file(env: str, file_path: str):
"""
Sert un fichier depuis storage/<env>/<file_path>
Les variables sont remplacées par les valeurs du fichier .env
Le contenu est chiffré avec un algorithme quantique résistant
"""
try:
# Construction du chemin du fichier
def __init__(self):
self.app = Flask(__name__)
self.env_processor = EnvProcessor(ENV_FILE)
self._setup_routes()
def _setup_routes(self):
"""Configure les routes de l'API"""
@self.app.before_request
def force_https():
"""Force l'utilisation de HTTPS"""
# Vérification plus stricte de HTTPS
is_https = (
request.is_secure or
request.headers.get('X-Forwarded-Proto') == 'https' or
request.scheme == 'https'
)
if not is_https:
logger.warning(f"Tentative d'accès HTTP rejetée depuis {request.remote_addr}")
abort(400, description="HTTPS requis - Cette API ne fonctionne qu'en HTTPS")
@self.app.route('/health', methods=['GET'])
def health():
"""Endpoint de santé avec authentification"""
# Authentification requise même pour /health
user_id = self._authenticate_user(request)
return jsonify({
"status": "healthy",
"service": "vault-api-secure",
"encryption": "quantum-resistant",
"algorithm": "X25519-ChaCha20-Poly1305",
"authentication": "user-key-based",
"key_rotation": "automatic",
"user_id": user_id,
"timestamp": datetime.now().isoformat()
})
@self.app.route('/info', methods=['GET'])
def info():
"""Informations sur l'API avec authentification"""
# Authentification requise même pour /info
user_id = self._authenticate_user(request)
return jsonify({
"name": "4NK Vault API Secure",
"version": "2.0.0",
"domain": "vault.4nkweb.com",
"port": 6666,
"protocol": "HTTPS",
"encryption": "quantum-resistant",
"authentication": "user-key-based",
"key_rotation": "automatic",
"user_id": user_id,
"endpoints": {
"GET /<env>/<file>": "Sert un fichier chiffré avec authentification utilisateur",
"GET /health": "Contrôle de santé (authentification requise)",
"GET /info": "Informations sur l'API (authentification requise)"
}
})
@self.app.route('/<env>/<path:file_path>', methods=['GET'])
def serve_file(env: str, file_path: str):
"""Sert un fichier avec authentification et chiffrement"""
try:
# Vérification de l'authentification
user_id = self._authenticate_user(request)
# Validation du chemin
if not self._validate_path(env, file_path):
logger.warning(f"Tentative d'accès non autorisé: {env}/{file_path}")
return jsonify({"error": "Accès non autorisé"}), 403
# Lecture du fichier
file_content = self._read_file(env, file_path)
if file_content is None:
return jsonify({"error": f"Fichier non trouvé: {env}/{file_path}"}), 404
# Traitement des variables
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)
# Retour de la réponse chiffrée
response = Response(
encrypted_content,
mimetype='application/octet-stream',
headers={
'X-Encryption-Type': 'quantum-resistant',
'X-Algorithm': 'X25519-ChaCha20-Poly1305',
'X-User-ID': user_id,
'X-Key-Rotation': 'enabled'
}
)
logger.info(f"Fichier servi: {env}/{file_path} pour l'utilisateur {user_id}")
return response
except Exception as e:
logger.error(f"Erreur lors du service du fichier {env}/{file_path}: {e}")
return jsonify({"error": "Erreur interne du serveur"}), 500
def _authenticate_user(self, request) -> str:
"""Authentifie l'utilisateur et retourne son ID"""
# Récupération de l'ID utilisateur depuis les headers
user_id = request.headers.get('X-User-ID')
if not user_id:
abort(401, description="Header X-User-ID requis pour l'authentification")
# Validation basique de l'ID utilisateur
if len(user_id) < 3 or len(user_id) > 50:
abort(401, description="ID utilisateur invalide")
# Vérification des caractères autorisés
if not re.match(r'^[a-zA-Z0-9_-]+$', user_id):
abort(401, description="ID utilisateur contient des caractères non autorisés")
return user_id
def _validate_path(self, env: str, file_path: str) -> bool:
"""Valide le chemin d'accès"""
# Construction du chemin complet
full_path = STORAGE_ROOT / env / file_path
# Vérification de sécurité - empêche l'accès en dehors du répertoire storage
if not str(full_path.resolve()).startswith(str(STORAGE_ROOT.resolve())):
return jsonify({'error': 'Accès non autorisé'}), 403
# Vérification de l'existence du fichier
if not full_path.exists() or not full_path.is_file():
return jsonify({'error': 'Fichier non trouvé'}), 404
# Lecture du fichier
# Vérification de sécurité
try:
with open(full_path, 'r', encoding='utf-8') as f:
content = f.read()
except UnicodeDecodeError:
# Tentative en mode binaire pour les fichiers non-text
resolved_path = full_path.resolve()
storage_resolved = STORAGE_ROOT.resolve()
if not str(resolved_path).startswith(str(storage_resolved)):
return False
return resolved_path.exists() and resolved_path.is_file()
except Exception:
return False
def _read_file(self, env: str, file_path: str) -> Optional[str]:
"""Lit le contenu d'un fichier"""
try:
full_path = STORAGE_ROOT / env / file_path
# Lecture en mode binaire pour détecter le type
with open(full_path, 'rb') as f:
content = f.read()
# Conversion en base64 pour les fichiers binaires
content = base64.b64encode(content).decode('utf-8')
content = f"BINARY_DATA:{content}"
# Traitement des variables d'environnement
processed_content = ENV_PROCESSOR.process_content(content)
# Chiffrement du contenu avec clé dynamique sécurisée
# La clé de démonstration est désactivée pour des raisons de sécurité
try:
# Génération d'une clé de session unique (32 bytes)
session_key = os.urandom(32)
cipher = ChaCha20Poly1305(session_key)
nonce = os.urandom(12)
encrypted_content = cipher.encrypt(nonce, processed_content.encode('utf-8'), None)
# Encodage: nonce + clé de session + contenu chiffré
# ATTENTION: En production, la clé doit être transmise via un canal sécurisé
full_payload = nonce + session_key + encrypted_content
encrypted_content = base64.b64encode(full_payload)
logger.warning("ATTENTION: Clé de session générée - Transmission non sécurisée en mode démo")
# Tentative de décodage UTF-8
try:
return content.decode('utf-8')
except UnicodeDecodeError:
# Fichier binaire, encodage base64
return f"BINARY_DATA:{base64.b64encode(content).decode()}"
except Exception as e:
logger.error(f"Erreur de chiffrement: {e}")
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"""
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
current_key = key_manager.get_or_create_user_key(user_id)
# Chiffrement avec la clé actuelle
return self._encrypt_content(content, current_key, user_id, environment)
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
encrypted_content = base64.b64encode(processed_content.encode('utf-8'))
return base64.b64encode(content.encode('utf-8'))
# Retour de la réponse chiffrée
response = Response(
encrypted_content,
mimetype='application/octet-stream',
headers={
'Content-Disposition': f'attachment; filename="{file_path}"',
'X-Encryption-Type': 'quantum-resistant',
'X-Algorithm': 'X25519-ChaCha20-Poly1305'
}
)
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"""
cipher = ChaCha20Poly1305(key)
nonce = secrets.token_bytes(12)
encrypted_content = cipher.encrypt(nonce, content.encode('utf-8'), None)
logger.info(f"Served file: {env}/{file_path}")
return response
except Exception as e:
logger.error(f"Erreur lors du service du fichier {env}/{file_path}: {e}")
return jsonify({'error': 'Erreur interne du serveur'}), 500
@app.route('/health', methods=['GET'])
def health_check():
"""Point de contrôle de santé de l'API"""
return jsonify({
'status': 'healthy',
'service': 'vault-api',
'encryption': 'quantum-resistant',
'algorithm': 'X25519-ChaCha20-Poly1305'
})
@app.route('/info', methods=['GET'])
def api_info():
"""Informations sur l'API"""
return jsonify({
'name': '4NK Vault API',
'version': '1.0.0',
'domain': 'vault.4nkweb.com',
'port': 6666,
'protocol': 'HTTPS',
'encryption': 'quantum-resistant',
'endpoints': {
'GET /<env>/<file>': 'Sert un fichier chiffré depuis storage/<env>/<file>',
'GET /health': 'Contrôle de santé',
'GET /info': 'Informations sur l\'API'
# Métadonnées de chiffrement
metadata = {
'user_id': user_id,
'environment': environment,
'timestamp': datetime.now().isoformat(),
'key_version': 'previous' if is_previous else 'current',
'algorithm': 'ChaCha20-Poly1305'
}
})
def create_ssl_context():
"""Crée le contexte SSL pour HTTPS"""
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
# 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
# Configuration SSL sécurisée
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS')
return base64.b64encode(full_payload)
# Génération de certificats auto-signés pour la démonstration
# En production, utiliser des certificats valides
try:
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
import datetime
def create_ssl_context(self):
"""Crée le contexte SSL pour HTTPS"""
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
# Génération d'une clé privée RSA
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
# Configuration SSL sécurisée
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS')
# Génération de certificats auto-signés pour la démonstration
try:
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives.asymmetric import rsa
import datetime
# Génération d'une clé privée RSA
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
# Création d'un certificat auto-signé
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "FR"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "France"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "Paris"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "4NK"),
x509.NameAttribute(NameOID.COMMON_NAME, "vault.4nkweb.com"),
])
cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
private_key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.datetime.now(datetime.timezone.utc)
).not_valid_after(
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=365)
).add_extension(
x509.SubjectAlternativeName([
x509.DNSName("vault.4nkweb.com"),
x509.DNSName("localhost"),
]),
critical=False,
).sign(private_key, hashes.SHA256())
# Sauvegarde temporaire des certificats
with open('/tmp/vault.key', 'wb') as f:
f.write(private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
))
with open('/tmp/vault.crt', 'wb') as f:
f.write(cert.public_bytes(serialization.Encoding.PEM))
context.load_cert_chain('/tmp/vault.crt', '/tmp/vault.key')
logger.info("Certificats SSL générés avec succès")
except Exception as e:
logger.warning(f"Impossible de générer les certificats SSL: {e}")
logger.warning("Utilisation du contexte SSL par défaut")
return context
def run(self, host='0.0.0.0', port=6666, debug=False):
"""Démarre le serveur HTTPS"""
logger.info("Démarrage de l'API Vault sécurisée sur https://vault.4nkweb.com:6666")
logger.info("Authentification par clés utilisateur activée")
logger.info("Rotation automatique des clés activée")
logger.info("HTTPS obligatoire")
ssl_context = self.create_ssl_context()
self.app.run(
host=host,
port=port,
debug=debug,
ssl_context=ssl_context,
threaded=True
)
# Création d'un certificat auto-signé
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "FR"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "France"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "Paris"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "4NK"),
x509.NameAttribute(NameOID.COMMON_NAME, "vault.4nkweb.com"),
])
cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
private_key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.datetime.utcnow()
).not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=365)
).add_extension(
x509.SubjectAlternativeName([
x509.DNSName("vault.4nkweb.com"),
x509.DNSName("localhost"),
]),
critical=False,
).sign(private_key, hashes.SHA256())
# Sauvegarde temporaire des certificats
with open('/tmp/vault.key', 'wb') as f:
f.write(private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
))
with open('/tmp/vault.crt', 'wb') as f:
f.write(cert.public_bytes(serialization.Encoding.PEM))
context.load_cert_chain('/tmp/vault.crt', '/tmp/vault.key')
logger.info("Certificats SSL générés avec succès")
except Exception as e:
logger.warning(f"Impossible de générer les certificats SSL: {e}")
logger.warning("Utilisation du contexte SSL par défaut")
return context
if __name__ == '__main__':
# Configuration du serveur
host = '0.0.0.0'
port = 6666
logger.info(f"Démarrage de l'API Vault sur https://vault.4nkweb.com:{port}")
logger.info("Chiffrement quantique résistant activé")
logger.info("Algorithme: X25519 + ChaCha20-Poly1305")
# Création du contexte SSL
ssl_context = create_ssl_context()
# Démarrage du serveur HTTPS
app.run(
host=host,
port=port,
ssl_context=ssl_context,
debug=False,
threaded=True
)
api = SecureVaultAPI()
api.run()

View File

@ -1,487 +0,0 @@
#!/usr/bin/env python3
"""
API HTTPS sécurisée avec authentification par clés utilisateur
Port 6666, domaine vault.4nkweb.com
Système de clés par utilisateur avec rotation automatique
"""
import os
import re
import ssl
import socket
import json
import hashlib
import secrets
import time
from pathlib import Path
from datetime import datetime, timedelta
from typing import Dict, Any, Optional
from flask import Flask, request, Response, jsonify, abort
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import x25519
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import base64
import logging
# Configuration du logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Configuration
STORAGE_ROOT = Path('/home/debian/4NK_vault/storage')
ENV_FILE = STORAGE_ROOT / 'dev' / '.env'
class UserKeyManager:
"""Gestionnaire de clés par utilisateur et par environnement avec rotation automatique"""
def __init__(self, environment: str):
self.environment = environment
self.keys_dir = STORAGE_ROOT / environment / '_keys'
self.keys_dir.mkdir(parents=True, exist_ok=True)
self.keys_db = self._load_keys_db()
def _load_keys_db(self) -> Dict[str, Any]:
"""Charge la base de données des clés pour cet environnement"""
keys_file = self.keys_dir / 'keys.json'
if keys_file.exists():
try:
with open(keys_file, 'r') as f:
return json.load(f)
except Exception as e:
logger.error(f"Erreur lors du chargement des clés pour {self.environment}: {e}")
return {}
def _save_keys_db(self):
"""Sauvegarde la base de données des clés pour cet environnement"""
keys_file = self.keys_dir / 'keys.json'
try:
with open(keys_file, 'w') as f:
json.dump(self.keys_db, f, indent=2)
logger.info(f"Clés sauvegardées pour l'environnement {self.environment}")
except Exception as e:
logger.error(f"Erreur lors de la sauvegarde des clés pour {self.environment}: {e}")
def _generate_user_key(self, user_id: str) -> bytes:
"""Génère une nouvelle clé pour un utilisateur"""
# Génération d'une clé aléatoire de 32 bytes
return secrets.token_bytes(32)
def _hash_user_id(self, user_id: str) -> str:
"""Hash l'ID utilisateur pour l'utiliser comme clé"""
return hashlib.sha256(user_id.encode()).hexdigest()
def get_or_create_user_key(self, user_id: str) -> bytes:
"""Récupère ou crée une clé pour un utilisateur"""
user_hash = self._hash_user_id(user_id)
current_time = datetime.now()
if user_hash not in self.keys_db:
# Nouvel utilisateur
self.keys_db[user_hash] = {
'current_key': base64.b64encode(self._generate_user_key(user_id)).decode(),
'previous_key': None,
'created_at': current_time.isoformat(),
'last_rotation': current_time.isoformat(),
'rotation_count': 0,
'environment': self.environment
}
self._save_keys_db()
logger.info(f"Nouvelle clé créée pour l'utilisateur {user_id} dans l'environnement {self.environment}")
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]
return base64.b64decode(user_data['current_key'])
def _rotate_user_key(self, user_hash: str, user_id: str):
"""Effectue la rotation de clé pour un utilisateur"""
user_data = self.keys_db[user_hash]
# Sauvegarde de l'ancienne clé
user_data['previous_key'] = user_data['current_key']
# Génération d'une nouvelle clé
user_data['current_key'] = base64.b64encode(self._generate_user_key(user_id)).decode()
user_data['last_rotation'] = datetime.now().isoformat()
user_data['rotation_count'] += 1
user_data['environment'] = self.environment
self._save_keys_db()
logger.info(f"Clé rotée pour l'utilisateur {user_id} dans l'environnement {self.environment} (rotation #{user_data['rotation_count']})")
def get_user_keys(self, user_id: str) -> tuple[bytes, Optional[bytes]]:
"""Récupère la clé actuelle et précédente d'un utilisateur"""
user_hash = self._hash_user_id(user_id)
if user_hash not in self.keys_db:
raise ValueError(f"Utilisateur {user_id} non trouvé dans l'environnement {self.environment}")
user_data = self.keys_db[user_hash]
current_key = base64.b64decode(user_data['current_key'])
previous_key = None
if user_data['previous_key']:
previous_key = base64.b64decode(user_data['previous_key'])
return current_key, previous_key
class EnvProcessor:
"""Processeur de variables d'environnement avec résolution récursive"""
def __init__(self, env_file: Path):
self.variables = self._load_env_file(env_file)
def _load_env_file(self, env_file: Path) -> Dict[str, str]:
"""Charge le fichier .env"""
variables = {}
if env_file.exists():
try:
with open(env_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
variables[key.strip()] = value.strip()
except Exception as e:
logger.error(f"Erreur lors du chargement du fichier .env: {e}")
return variables
def _resolve_variable(self, var_name: str, visited: set = None) -> str:
"""Résout une variable récursivement"""
if visited is None:
visited = set()
if var_name in visited:
logger.warning(f"Dépendance circulaire détectée pour {var_name}")
return f"${{{var_name}}}"
if var_name not in self.variables:
logger.warning(f"Variable non trouvée: {var_name}")
return f"${{{var_name}}}"
visited.add(var_name)
value = self.variables[var_name]
# Traitement des sous-variables
pattern = r'\$\{([^}]+)\}'
matches = re.findall(pattern, value)
for match in matches:
resolved_value = self._resolve_variable(match, visited.copy())
value = value.replace(f"${{{match}}}", resolved_value)
return value
def process_content(self, content: str) -> str:
"""Traite le contenu en résolvant les variables"""
pattern = r'\$\{([^}]+)\}'
matches = re.findall(pattern, content)
for var_name in matches:
resolved_value = self._resolve_variable(var_name)
content = content.replace(f"${{{var_name}}}", resolved_value)
return content
class SecureVaultAPI:
"""API Vault sécurisée avec authentification par clés utilisateur"""
def __init__(self):
self.app = Flask(__name__)
self.env_processor = EnvProcessor(ENV_FILE)
self._setup_routes()
def _setup_routes(self):
"""Configure les routes de l'API"""
@self.app.before_request
def force_https():
"""Force l'utilisation de HTTPS"""
# Vérification plus stricte de HTTPS
is_https = (
request.is_secure or
request.headers.get('X-Forwarded-Proto') == 'https' or
request.scheme == 'https'
)
if not is_https:
logger.warning(f"Tentative d'accès HTTP rejetée depuis {request.remote_addr}")
abort(400, description="HTTPS requis - Cette API ne fonctionne qu'en HTTPS")
@self.app.route('/health', methods=['GET'])
def health():
"""Endpoint de santé avec authentification"""
# Authentification requise même pour /health
user_id = self._authenticate_user(request)
return jsonify({
"status": "healthy",
"service": "vault-api-secure",
"encryption": "quantum-resistant",
"algorithm": "X25519-ChaCha20-Poly1305",
"authentication": "user-key-based",
"key_rotation": "automatic",
"user_id": user_id,
"timestamp": datetime.now().isoformat()
})
@self.app.route('/info', methods=['GET'])
def info():
"""Informations sur l'API avec authentification"""
# Authentification requise même pour /info
user_id = self._authenticate_user(request)
return jsonify({
"name": "4NK Vault API Secure",
"version": "2.0.0",
"domain": "vault.4nkweb.com",
"port": 6666,
"protocol": "HTTPS",
"encryption": "quantum-resistant",
"authentication": "user-key-based",
"key_rotation": "automatic",
"user_id": user_id,
"endpoints": {
"GET /<env>/<file>": "Sert un fichier chiffré avec authentification utilisateur",
"GET /health": "Contrôle de santé (authentification requise)",
"GET /info": "Informations sur l'API (authentification requise)"
}
})
@self.app.route('/<env>/<path:file_path>', methods=['GET'])
def serve_file(env: str, file_path: str):
"""Sert un fichier avec authentification et chiffrement"""
try:
# Vérification de l'authentification
user_id = self._authenticate_user(request)
# Validation du chemin
if not self._validate_path(env, file_path):
logger.warning(f"Tentative d'accès non autorisé: {env}/{file_path}")
return jsonify({"error": "Accès non autorisé"}), 403
# Lecture du fichier
file_content = self._read_file(env, file_path)
if file_content is None:
return jsonify({"error": f"Fichier non trouvé: {env}/{file_path}"}), 404
# Traitement des variables
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)
# Retour de la réponse chiffrée
response = Response(
encrypted_content,
mimetype='application/octet-stream',
headers={
'X-Encryption-Type': 'quantum-resistant',
'X-Algorithm': 'X25519-ChaCha20-Poly1305',
'X-User-ID': user_id,
'X-Key-Rotation': 'enabled'
}
)
logger.info(f"Fichier servi: {env}/{file_path} pour l'utilisateur {user_id}")
return response
except Exception as e:
logger.error(f"Erreur lors du service du fichier {env}/{file_path}: {e}")
return jsonify({"error": "Erreur interne du serveur"}), 500
def _authenticate_user(self, request) -> str:
"""Authentifie l'utilisateur et retourne son ID"""
# Récupération de l'ID utilisateur depuis les headers
user_id = request.headers.get('X-User-ID')
if not user_id:
abort(401, description="Header X-User-ID requis pour l'authentification")
# Validation basique de l'ID utilisateur
if len(user_id) < 3 or len(user_id) > 50:
abort(401, description="ID utilisateur invalide")
# Vérification des caractères autorisés
if not re.match(r'^[a-zA-Z0-9_-]+$', user_id):
abort(401, description="ID utilisateur contient des caractères non autorisés")
return user_id
def _validate_path(self, env: str, file_path: str) -> bool:
"""Valide le chemin d'accès"""
# Construction du chemin complet
full_path = STORAGE_ROOT / env / file_path
# Vérification de sécurité
try:
resolved_path = full_path.resolve()
storage_resolved = STORAGE_ROOT.resolve()
if not str(resolved_path).startswith(str(storage_resolved)):
return False
return resolved_path.exists() and resolved_path.is_file()
except Exception:
return False
def _read_file(self, env: str, file_path: str) -> Optional[str]:
"""Lit le contenu d'un fichier"""
try:
full_path = STORAGE_ROOT / env / file_path
# Lecture en mode binaire pour détecter le type
with open(full_path, 'rb') as f:
content = f.read()
# Tentative de décodage UTF-8
try:
return content.decode('utf-8')
except UnicodeDecodeError:
# Fichier binaire, encodage base64
return f"BINARY_DATA:{base64.b64encode(content).decode()}"
except Exception as e:
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"""
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
current_key = key_manager.get_or_create_user_key(user_id)
# Chiffrement avec la clé actuelle
return self._encrypt_content(content, current_key, user_id, environment)
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'))
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"""
cipher = ChaCha20Poly1305(key)
nonce = secrets.token_bytes(12)
encrypted_content = cipher.encrypt(nonce, content.encode('utf-8'), None)
# Métadonnées de chiffrement
metadata = {
'user_id': user_id,
'environment': environment,
'timestamp': datetime.now().isoformat(),
'key_version': 'previous' if is_previous else 'current',
'algorithm': 'ChaCha20-Poly1305'
}
# 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 create_ssl_context(self):
"""Crée le contexte SSL pour HTTPS"""
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
# Configuration SSL sécurisée
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS')
# Génération de certificats auto-signés pour la démonstration
try:
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives.asymmetric import rsa
import datetime
# Génération d'une clé privée RSA
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
# Création d'un certificat auto-signé
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "FR"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "France"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "Paris"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "4NK"),
x509.NameAttribute(NameOID.COMMON_NAME, "vault.4nkweb.com"),
])
cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
private_key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.datetime.now(datetime.timezone.utc)
).not_valid_after(
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=365)
).add_extension(
x509.SubjectAlternativeName([
x509.DNSName("vault.4nkweb.com"),
x509.DNSName("localhost"),
]),
critical=False,
).sign(private_key, hashes.SHA256())
# Sauvegarde temporaire des certificats
with open('/tmp/vault.key', 'wb') as f:
f.write(private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
))
with open('/tmp/vault.crt', 'wb') as f:
f.write(cert.public_bytes(serialization.Encoding.PEM))
context.load_cert_chain('/tmp/vault.crt', '/tmp/vault.key')
logger.info("Certificats SSL générés avec succès")
except Exception as e:
logger.warning(f"Impossible de générer les certificats SSL: {e}")
logger.warning("Utilisation du contexte SSL par défaut")
return context
def run(self, host='0.0.0.0', port=6666, debug=False):
"""Démarre le serveur HTTPS"""
logger.info("Démarrage de l'API Vault sécurisée sur https://vault.4nkweb.com:6666")
logger.info("Authentification par clés utilisateur activée")
logger.info("Rotation automatique des clés activée")
logger.info("HTTPS obligatoire")
ssl_context = self.create_ssl_context()
self.app.run(
host=host,
port=port,
debug=debug,
ssl_context=ssl_context,
threaded=True
)
if __name__ == '__main__':
api = SecureVaultAPI()
api.run()

View File

@ -1,26 +1,39 @@
# Documentation 4NK Vault
# Documentation 4NK Vault - Système Sécurisé
Bienvenue dans la documentation complète du projet 4NK Vault, un système de stockage sécurisé avec chiffrement quantique résistant.
Bienvenue dans la documentation complète du projet 4NK Vault, un système de stockage sécurisé avec authentification par clés utilisateur et chiffrement quantique résistant.
## 📚 Documentation disponible
### 🚀 [Guide principal](README.md)
Vue d'ensemble du projet, architecture et utilisation de base.
### 🚀 [Guide principal](../README.md)
Vue d'ensemble du projet, architecture sécurisée et utilisation avec authentification.
### 🔧 [Spécification API](api-specification.md)
Spécification technique complète de l'API REST avec tous les détails d'implémentation.
Spécification technique complète de l'API REST sécurisée avec authentification par clés utilisateur.
### 📖 [Référence API](api-reference.md)
Référence complète des endpoints, paramètres, réponses et codes d'erreur.
Référence complète des endpoints sécurisés, paramètres, réponses et codes d'erreur.
### 🛡️ [Modèle de sécurité](security-model.md)
Architecture de sécurité, chiffrement quantique résistant et bonnes pratiques.
Architecture de sécurité, authentification par clés utilisateur, rotation automatique et bonnes pratiques.
### 🚀 [Guide de déploiement](deployment-guide.md)
Instructions complètes pour déployer le système en développement et production.
Instructions complètes pour déployer le système sécurisé en développement et production.
### 💻 [Documentation SDK](sdk-documentation.md)
Documentation complète du SDK TypeScript avec exemples et API reference.
Documentation complète du SDK TypeScript sécurisé avec exemples et API reference.
## 🔐 Sécurité Avancée
### Authentification par clés utilisateur
- **ID utilisateur obligatoire** pour tous les accès
- **Clés individuelles** par utilisateur et environnement
- **Rotation automatique** des clés toutes les heures
- **Stockage sécurisé** dans `storage/<env>/_keys/`
### Chiffrement quantique résistant
- **ChaCha20-Poly1305** pour le chiffrement
- **HTTPS obligatoire** pour toutes les communications
- **Métadonnées sécurisées** avec timestamps et versions de clés
## 🎯 Démarrage rapide
@ -28,112 +41,71 @@ Documentation complète du SDK TypeScript avec exemples et API reference.
```bash
# 1. Installation des dépendances
cd 4NK_vault
python3 -m venv venv_api
source venv_api/bin/activate
pip install -r requirements.txt
# 2. Démarrage de l'API
# 2. Démarrage de l'API sécurisée
./start_api.sh
# 3. Test de l'API
curl -k https://localhost:6666/health
# 4. Test du SDK
cd sdk-client
npm install && npm run build
node dist/examples/basic-usage.js
# 3. Test avec authentification
curl -k -H "X-User-ID: demo_user_001" https://127.0.0.1:6666/health
```
### Utilisation basique
### Utilisation avec le SDK TypeScript
```typescript
import { createVaultClient } from '@4nk/vault-sdk';
import { createSecureVaultClient } from '@4nk/vault-sdk';
const client = createVaultClient('https://vault.4nkweb.com:6666');
// Création du client avec authentification
const client = createSecureVaultClient(
'https://vault.4nkweb.com:6666',
'your_user_id' // ID utilisateur obligatoire
);
// Récupération d'un fichier chiffré
const file = await client.getFile('dev', 'bitcoin/bitcoin.conf');
console.log(file.content); // Contenu déchiffré
console.log(file.content); // Contenu déchiffré avec la clé utilisateur
```
## 🔍 Navigation par cas d'usage
## 🔑 Gestion des clés
### Structure des clés par environnement
```
storage/
├── dev/
│ └── _keys/
│ └── keys.json # Clés pour l'environnement DEV
└── prod/
└── _keys/
└── keys.json # Clés pour l'environnement PROD
```
### Rotation automatique
- **Déclenchement** : Toutes les heures automatiquement
- **Transition** : Ancienne clé conservée pour compatibilité
- **Logs** : Traçabilité complète des rotations
## ⚠️ Avertissements de sécurité
Voir [SECURITY_NOTICE.md](../SECURITY_NOTICE.md) pour les détails sur :
- Les limitations de sécurité en mode démonstration
- Les recommandations pour la production
- L'implémentation d'un échange de clés sécurisé
## 📋 Navigation par cas d'usage
### Je veux...
- **Comprendre le projet** → [Guide principal](README.md)
- **Utiliser l'API** → [Référence API](api-reference.md)
- **Déployer le système** → [Guide de déploiement](deployment-guide.md)
- **Développer avec le SDK** → [Documentation SDK](sdk-documentation.md)
- **Comprendre la sécurité** → [Modèle de sécurité](security-model.md)
- **Implémenter l'API** → [Spécification API](api-specification.md)
- **[Comprendre la sécurité](security-model.md)** → Modèle de sécurité complet
- **[Utiliser l'API](api-reference.md)** → Référence des endpoints sécurisés
- **[Développer avec le SDK](sdk-documentation.md)** → SDK TypeScript sécurisé
- **[Déployer en production](deployment-guide.md)** → Guide de déploiement sécurisé
- **[Tester le système](api-specification.md)** → Spécifications techniques
## 🏗️ Architecture du système
## 🔗 Liens utiles
```
┌─────────────────┐ HTTPS ┌─────────────────┐ ┌─────────────────┐
│ Client SDK │ ──────────► │ API Vault │ ──► │ Storage │
│ TypeScript │ Port 6666 │ Python Flask │ │ Files │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐
│ Variables │
│ .env │
└─────────────────┘
```
## 🔐 Sécurité
- **Chiffrement** : ChaCha20-Poly1305 (quantique résistant)
- **Transport** : HTTPS uniquement (TLS 1.2+)
- **Authentification** : Chiffrement intégré avec nonce aléatoire
- **Validation** : Protection contre les accès non autorisés
## 📊 Fonctionnalités
- ✅ **API REST** : Endpoints pour la santé, info et fichiers
- ✅ **Chiffrement** : Quantique résistant avec ChaCha20-Poly1305
- ✅ **Variables** : Traitement automatique des variables composites
- ✅ **SDK TypeScript** : Client type-safe avec déchiffrement
- ✅ **Monitoring** : Logs détaillés et métriques
- ✅ **Sécurité** : Validation des chemins et protection d'accès
## 🛠️ Technologies
- **Backend** : Python 3.8+ avec Flask
- **Frontend** : TypeScript/JavaScript SDK
- **Chiffrement** : cryptography (Python), crypto (Node.js)
- **Transport** : HTTPS avec certificats SSL/TLS
- **Stockage** : Système de fichiers avec structure hiérarchique
## 📈 Versions
| Composant | Version | Statut |
|-----------|---------|--------|
| API Server | 1.0.0 | ✅ Stable |
| SDK Client | 1.0.0 | ✅ Stable |
| Documentation | 1.0.0 | ✅ Complète |
## 🤝 Support
### Ressources
- **Issues** : [Git Issues](https://git.4nkweb.com/4nk/vault/issues)
- **Wiki** : [Documentation Wiki](https://git.4nkweb.com/4nk/vault/wiki)
- **Email** : support@4nkweb.com
### Communauté
- **Discussions** : [Forum de la communauté](https://forum.4nkweb.com)
- **Chat** : [Discord](https://discord.gg/4nk-vault)
- **Newsletter** : [Abonnement](https://newsletter.4nkweb.com)
## 📄 Licence
MIT License - Voir le fichier `LICENSE` pour plus de détails.
## 🏷️ Tags et mots-clés
`vault` `security` `encryption` `quantum-resistant` `api` `typescript` `python` `chacha20` `poly1305` `https` `configuration` `variables` `sdk`
---
**Dernière mise à jour** : 2025-09-29
**Version documentation** : 1.0.0
**Maintenu par** : Équipe 4NK
- **Repository** : [git.4nkweb.com:4nk/4NK_vault.git](https://git.4nkweb.com:4nk/4NK_vault.git)
- **Domaine** : vault.4nkweb.com:6666
- **Protocole** : HTTPS uniquement
- **Authentification** : Header `X-User-ID` obligatoire

View File

@ -1,193 +0,0 @@
#!/usr/bin/env node
/**
* Démonstration du SDK Vault 4NK en action
* Utilise l'API réelle pour récupérer et déchiffrer des fichiers
*/
import { VaultClient, VaultCrypto } from './src/index';
async function demo() {
console.log('🚀 Démonstration du SDK Vault 4NK');
console.log('=' .repeat(50));
console.log('📡 Connexion à l\'API: https://vault.4nkweb.com:6666');
console.log('🔐 Chiffrement: ChaCha20-Poly1305 (quantique résistant)');
console.log('');
try {
// 1. Création du client avec la clé de déchiffrement
console.log('🔑 Initialisation du client...');
const client = new VaultClient(
{
baseUrl: 'https://vault.4nkweb.com:6666',
verifySsl: false, // Certificats auto-signés
timeout: 15000, // 15 secondes de timeout
}
// Plus de clé de déchiffrement nécessaire - clés dynamiques utilisées
);
// 2. Test de connectivité
console.log('🌐 Test de connectivité...');
const isConnected = await client.ping();
if (!isConnected) {
throw new Error('❌ Impossible de se connecter à l\'API Vault');
}
console.log('✅ Connecté à l\'API Vault');
// 3. Informations sur l'API
console.log('\n📋 Informations sur l\'API...');
const info = await client.info();
console.log(` Nom: ${info.name}`);
console.log(` Version: ${info.version}`);
console.log(` Domaine: ${info.domain}:${info.port}`);
console.log(` Protocole: ${info.protocol}`);
console.log(` Chiffrement: ${info.encryption}`);
// 4. État de santé
console.log('\n🏥 État de santé...');
const health = await client.health();
console.log(` Statut: ${health.status}`);
console.log(` Service: ${health.service}`);
console.log(` Algorithme: ${health.algorithm}`);
// 5. Récupération et déchiffrement de fichiers
console.log('\n📁 Récupération de fichiers chiffrés...');
const filesToGet = [
{ env: 'dev', path: 'bitcoin/bitcoin.conf', description: 'Configuration Bitcoin' },
{ env: 'dev', path: 'tor/torrc', description: 'Configuration Tor' },
{ env: 'dev', path: 'sdk_relay/sdk_relay.conf', description: 'Configuration SDK Relay' }
];
for (const fileInfo of filesToGet) {
console.log(`\n 📄 ${fileInfo.description} (${fileInfo.path})`);
try {
const startTime = Date.now();
const file = await client.getFile(fileInfo.env, fileInfo.path);
const endTime = Date.now();
console.log(` ✅ Récupéré en ${endTime - startTime}ms`);
console.log(` 📦 Taille: ${file.size} caractères`);
console.log(` 🔐 Chiffré: ${file.encrypted ? 'Oui' : 'Non'}`);
console.log(` 🔧 Algorithme: ${file.algorithm || 'N/A'}`);
// Analyse du contenu
const lines = file.content.split('\n');
const configLines = lines.filter(line =>
line.trim() && !line.trim().startsWith('#')
);
console.log(` 📝 Lignes totales: ${lines.length}`);
console.log(` ⚙️ Lignes de config: ${configLines.length}`);
// Recherche de variables d'environnement
const envVars = file.content.match(/\$\{[^}]+\}/g) || [];
if (envVars.length > 0) {
const uniqueVars = [...new Set(envVars)];
console.log(` 🔧 Variables env: ${uniqueVars.length} (${uniqueVars.slice(0, 3).join(', ')}...)`);
}
// Affichage d'un échantillon du contenu déchiffré
const sample = file.content.substring(0, 150).replace(/\n/g, '\\n');
console.log(` 📖 Échantillon: ${sample}...`);
} catch (error) {
console.log(` ❌ Erreur: ${error instanceof Error ? error.message : error}`);
}
}
// 6. Test de récupération parallèle
console.log('\n⚡ Test de récupération parallèle...');
const parallelStartTime = Date.now();
const parallelRequests = [
{ env: 'dev', filePath: 'bitcoin/bitcoin.conf' },
{ env: 'dev', filePath: 'tor/torrc' },
{ env: 'dev', filePath: 'sdk_relay/sdk_relay.conf' },
{ env: 'dev', filePath: 'supervisor/supervisord.conf' }
];
try {
const files = await client.getFiles(parallelRequests);
const parallelEndTime = Date.now();
console.log(`${files.length} fichiers récupérés en ${parallelEndTime - parallelStartTime}ms`);
console.log(` 📊 Temps moyen: ${Math.round((parallelEndTime - parallelStartTime) / files.length)}ms par fichier`);
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
console.log(` 📦 Taille totale: ${totalSize} caractères`);
// Statistiques
const encryptedCount = files.filter(f => f.encrypted).length;
console.log(` 🔐 Fichiers chiffrés: ${encryptedCount}/${files.length}`);
} catch (error) {
console.log(` ❌ Erreur récupération parallèle: ${error instanceof Error ? error.message : error}`);
}
// 7. Test des utilitaires crypto
console.log('\n🔐 Test des utilitaires de chiffrement...');
// Génération d'une clé aléatoire
const randomKey = VaultCrypto.generateKey();
console.log(` 🔑 Clé générée: ${randomKey.substring(0, 10)}... (${randomKey.length} chars)`);
console.log(` ✅ Clé valide: ${VaultCrypto.validateKey(randomKey) ? 'Oui' : 'Non'}`);
// Dérivation depuis un mot de passe
const password = 'mon-mot-de-passe-secret-demo';
const derivedKey = VaultCrypto.hashToKey(password);
console.log(` 🔐 Mot de passe: ${password}`);
console.log(` 🔑 Clé dérivée: ${derivedKey.substring(0, 10)}...`);
console.log(` ✅ Clé dérivée valide: ${VaultCrypto.validateKey(derivedKey) ? 'Oui' : 'Non'}`);
// 8. Test de gestion d'erreurs
console.log('\n🛡 Test de gestion d\'erreurs...');
// Fichier inexistant
try {
await client.getFile('dev', 'fichier-inexistant.conf');
console.log(' ❌ Erreur: Devrait échouer');
} catch (error) {
console.log(` ✅ Fichier inexistant: ${error instanceof Error ? error.message : error}`);
}
// Environnement inexistant
try {
await client.getFile('prod', 'bitcoin/bitcoin.conf');
console.log(' ❌ Erreur: Devrait échouer');
} catch (error) {
console.log(` ✅ Environnement inexistant: ${error instanceof Error ? error.message : error}`);
}
console.log('\n🎉 Démonstration terminée avec succès!');
console.log('');
console.log('📋 Résumé:');
console.log(' ✅ API Vault opérationnelle');
console.log(' ✅ Chiffrement quantique résistant fonctionnel');
console.log(' ✅ Déchiffrement côté client réussi');
console.log(' ✅ Variables d\'environnement traitées');
console.log(' ✅ Récupération parallèle efficace');
console.log(' ✅ Gestion d\'erreurs robuste');
} catch (error) {
console.error('\n❌ Erreur fatale:', error instanceof Error ? error.message : error);
console.error('');
console.error('🔍 Vérifications possibles:');
console.error(' • L\'API Vault est-elle démarrée ?');
console.error(' • Le port 6666 est-il accessible ?');
console.error(' • Les certificats SSL sont-ils valides ?');
console.error(' • La clé de déchiffrement est-elle correcte ?');
process.exit(1);
}
}
// Exécution de la démonstration
if (require.main === module) {
demo().catch(error => {
console.error('❌ Erreur inattendue:', error);
process.exit(1);
});
}
export { demo };

View File

@ -1,184 +0,0 @@
/**
* Exemple d'utilisation avancée du SDK Vault 4NK
* Démontre les fonctionnalités avancées et la gestion d'erreurs
*/
import { VaultClient, VaultCrypto, VaultApiError, VaultDecryptionError } from '../src/index';
async function advancedExample() {
console.log('🚀 Exemple d\'utilisation avancée du SDK Vault 4NK');
console.log('=' .repeat(60));
try {
// 1. Génération d'une clé de chiffrement personnalisée
console.log('🔑 Génération d\'une clé de chiffrement...');
const customKey = VaultCrypto.generateKey();
console.log(` Clé générée: ${customKey.substring(0, 10)}...`);
// Validation de la clé
const isValid = VaultCrypto.validateKey(customKey);
console.log(` Clé valide: ${isValid ? 'Oui' : 'Non'}`);
// 2. Création du client avec configuration personnalisée
console.log('\n⚙ Configuration du client...');
const client = new VaultClient(
{
baseUrl: 'https://vault.4nkweb.com:6666',
verifySsl: false, // Désactivé pour les certificats auto-signés
timeout: 10000, // Timeout de 10 secondes
},
// Plus de clé de déchiffrement nécessaire - clés dynamiques utilisées
);
// 3. Gestion d'erreurs avancée
console.log('\n🛡 Test de gestion d\'erreurs...');
// Test avec un fichier inexistant
try {
await client.getFile('dev', 'fichier-inexistant.conf');
} catch (error) {
if (error instanceof VaultApiError) {
console.log(` ✅ Erreur API capturée: ${error.message} (${error.statusCode})`);
} else {
console.log(` ❌ Erreur inattendue: ${error}`);
}
}
// Test avec un environnement inexistant
try {
await client.getFile('prod', 'bitcoin/bitcoin.conf');
} catch (error) {
if (error instanceof VaultApiError) {
console.log(` ✅ Erreur environnement: ${error.message} (${error.statusCode})`);
}
}
// 4. Récupération de fichiers avec analyse de contenu
console.log('\n📊 Analyse de contenu des fichiers...');
const filePaths = [
'bitcoin/bitcoin.conf',
'tor/torrc',
'sdk_relay/sdk_relay.conf'
];
for (const filePath of filePaths) {
try {
const file = await client.getFile('dev', filePath);
console.log(`\n 📄 ${file.filename}:`);
console.log(` Taille: ${file.size} caractères`);
console.log(` Chiffré: ${file.encrypted ? 'Oui' : 'Non'}`);
// Analyse du contenu
const lines = file.content.split('\n');
const configLines = lines.filter(line =>
line.trim() && !line.trim().startsWith('#')
);
console.log(` Lignes totales: ${lines.length}`);
console.log(` Lignes de config: ${configLines.length}`);
// Recherche de variables d'environnement
const envVars = file.content.match(/\$\{[^}]+\}/g) || [];
if (envVars.length > 0) {
console.log(` Variables env: ${envVars.length} (${envVars.slice(0, 3).join(', ')}...)`);
}
} catch (error) {
console.log(` ❌ Erreur pour ${filePath}: ${error instanceof Error ? error.message : error}`);
}
}
// 5. Test de performance avec récupération parallèle
console.log('\n⚡ Test de performance...');
const startTime = Date.now();
const parallelRequests = [
{ env: 'dev', filePath: 'bitcoin/bitcoin.conf' },
{ env: 'dev', filePath: 'tor/torrc' },
{ env: 'dev', filePath: 'sdk_relay/sdk_relay.conf' },
{ env: 'dev', filePath: 'supervisor/supervisord.conf' }
];
const files = await client.getFiles(parallelRequests);
const endTime = Date.now();
console.log(` Fichiers récupérés: ${files.length}`);
console.log(` Temps total: ${endTime - startTime}ms`);
console.log(` Temps moyen par fichier: ${Math.round((endTime - startTime) / files.length)}ms`);
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
console.log(` Taille totale: ${totalSize} caractères`);
// 6. Monitoring et métriques
console.log('\n📈 Monitoring et métriques...');
// Test de connectivité répété
const pingTests = 5;
const pingResults: boolean[] = [];
for (let i = 0; i < pingTests; i++) {
const isConnected = await client.ping();
pingResults.push(isConnected);
await new Promise(resolve => setTimeout(resolve, 100)); // Délai de 100ms
}
const successRate = (pingResults.filter(r => r).length / pingTests) * 100;
console.log(` Tests de connectivité: ${pingTests}`);
console.log(` Taux de succès: ${successRate}%`);
// 7. Utilisation de clés dérivées
console.log('\n🔐 Test de clés dérivées...');
const password = 'mon-mot-de-passe-secret';
const derivedKey = VaultCrypto.hashToKey(password);
console.log(` Mot de passe: ${password}`);
console.log(` Clé dérivée: ${derivedKey.substring(0, 10)}...`);
console.log(` Clé valide: ${VaultCrypto.validateKey(derivedKey) ? 'Oui' : 'Non'}`);
console.log('\n🎉 Exemple avancé terminé avec succès!');
} catch (error) {
console.error('❌ Erreur fatale:', error instanceof Error ? error.message : error);
if (error instanceof VaultApiError) {
console.error(` Code d'erreur: ${error.statusCode}`);
console.error(` Endpoint: ${error.endpoint}`);
} else if (error instanceof VaultDecryptionError) {
console.error(' Type: Erreur de déchiffrement');
}
process.exit(1);
}
}
// Fonction utilitaire pour les tests de performance
async function performanceTest(client: VaultClient, iterations: number = 10) {
console.log(`\n🏃 Test de performance (${iterations} itérations)...`);
const times: number[] = [];
for (let i = 0; i < iterations; i++) {
const start = Date.now();
await client.getFile('dev', 'bitcoin/bitcoin.conf');
const end = Date.now();
times.push(end - start);
}
const avgTime = times.reduce((sum, time) => sum + time, 0) / times.length;
const minTime = Math.min(...times);
const maxTime = Math.max(...times);
console.log(` Temps moyen: ${Math.round(avgTime)}ms`);
console.log(` Temps min: ${minTime}ms`);
console.log(` Temps max: ${maxTime}ms`);
return { avgTime, minTime, maxTime };
}
// Exécution de l'exemple
if (require.main === module) {
advancedExample();
}
export { advancedExample, performanceTest };

View File

@ -1,79 +0,0 @@
/**
* Exemple d'utilisation basique du SDK Vault 4NK
*/
import { VaultClient, createVaultClient, VaultCrypto } from '../src/index';
async function basicExample() {
console.log('🚀 Exemple d\'utilisation basique du SDK Vault 4NK');
console.log('=' .repeat(50));
try {
// 1. Création du client avec la clé de déchiffrement
const client = createVaultClient(
'https://vault.4nkweb.com:6666',
// Plus de clé de déchiffrement nécessaire - clés dynamiques utilisées
);
// 2. Vérification de la connectivité
console.log('🔍 Test de connectivité...');
const isConnected = await client.ping();
console.log(`✅ Connecté: ${isConnected ? 'Oui' : 'Non'}`);
if (!isConnected) {
throw new Error('Impossible de se connecter à l\'API Vault');
}
// 3. Informations sur l'API
console.log('\n📋 Informations API...');
const info = await client.info();
console.log(` Nom: ${info.name}`);
console.log(` Version: ${info.version}`);
console.log(` Domaine: ${info.domain}`);
console.log(` Port: ${info.port}`);
console.log(` Chiffrement: ${info.encryption}`);
// 4. État de santé
console.log('\n🏥 État de santé...');
const health = await client.health();
console.log(` Statut: ${health.status}`);
console.log(` Service: ${health.service}`);
console.log(` Algorithme: ${health.algorithm}`);
// 5. Récupération d'un fichier
console.log('\n📁 Récupération d\'un fichier...');
const file = await client.getFile('dev', 'bitcoin/bitcoin.conf');
console.log(` Fichier: ${file.filename}`);
console.log(` Taille: ${file.size} caractères`);
console.log(` Chiffré: ${file.encrypted ? 'Oui' : 'Non'}`);
console.log(` Algorithme: ${file.algorithm || 'N/A'}`);
// Affichage d'un échantillon du contenu
const sample = file.content.substring(0, 200);
console.log(` Échantillon: ${sample}...`);
// 6. Récupération de plusieurs fichiers
console.log('\n📚 Récupération de plusieurs fichiers...');
const files = await client.getFiles([
{ env: 'dev', filePath: 'tor/torrc' },
{ env: 'dev', filePath: 'sdk_relay/sdk_relay.conf' }
]);
files.forEach((file, index) => {
console.log(` Fichier ${index + 1}: ${file.filename} (${file.size} chars)`);
});
console.log('\n🎉 Exemple terminé avec succès!');
} catch (error) {
console.error('❌ Erreur:', error instanceof Error ? error.message : error);
process.exit(1);
}
}
// Exécution de l'exemple
if (require.main === module) {
basicExample();
}
export { basicExample };

View File

@ -1,240 +0,0 @@
/**
* Exemple de gestion d'erreurs avancée avec le SDK Vault 4NK
* Démontre les différents types d'erreurs et leur gestion
*/
import { VaultClient, VaultApiError, VaultDecryptionError, VaultCrypto } from '../src/index';
async function errorHandlingExample() {
console.log('🛡️ Exemple de gestion d\'erreurs - SDK Vault 4NK');
console.log('=' .repeat(55));
try {
const client = new VaultClient(
{ baseUrl: 'https://vault.4nkweb.com:6666' },
// Plus de clé de déchiffrement nécessaire - clés dynamiques utilisées
);
// 1. Test des erreurs de connectivité
console.log('🌐 Test des erreurs de connectivité...');
await testConnectivityErrors(client);
// 2. Test des erreurs de fichiers
console.log('\n📁 Test des erreurs de fichiers...');
await testFileErrors(client);
// 3. Test des erreurs de déchiffrement
console.log('\n🔐 Test des erreurs de déchiffrement...');
await testDecryptionErrors();
// 4. Test des erreurs de configuration
console.log('\n⚙ Test des erreurs de configuration...');
await testConfigurationErrors();
// 5. Gestion d'erreurs avec retry
console.log('\n🔄 Test de retry automatique...');
await testRetryLogic(client);
console.log('\n✅ Tous les tests de gestion d\'erreurs terminés!');
} catch (error) {
console.error('❌ Erreur inattendue:', error);
process.exit(1);
}
}
async function testConnectivityErrors(client: VaultClient) {
// Test avec une URL incorrecte
const badClient = new VaultClient(
{ baseUrl: 'https://api-inexistante.example.com:6666' },
'quantum_resistant_demo_key_32_bytes!'
);
try {
await badClient.health();
console.log(' ❌ Erreur: Devrait échouer');
} catch (error) {
if (error instanceof VaultApiError) {
console.log(` ✅ Erreur de connectivité capturée: ${error.message}`);
} else {
console.log(` ✅ Erreur réseau capturée: ${error instanceof Error ? error.message : error}`);
}
}
// Test de timeout
const timeoutClient = new VaultClient(
{ baseUrl: 'https://vault.4nkweb.com:6666', timeout: 1 }, // 1ms timeout
'quantum_resistant_demo_key_32_bytes!'
);
try {
await timeoutClient.health();
console.log(' ❌ Erreur: Devrait timeout');
} catch (error) {
if (error instanceof VaultApiError && error.statusCode === 408) {
console.log(` ✅ Timeout capturé: ${error.message}`);
} else {
console.log(` ✅ Erreur timeout: ${error instanceof Error ? error.message : error}`);
}
}
}
async function testFileErrors(client: VaultClient) {
// Test avec un fichier inexistant
try {
await client.getFile('dev', 'fichier-inexistant.txt');
console.log(' ❌ Erreur: Fichier inexistant devrait échouer');
} catch (error) {
if (error instanceof VaultApiError && error.statusCode === 404) {
console.log(` ✅ Fichier inexistant: ${error.message}`);
} else {
console.log(` ✅ Erreur fichier: ${error instanceof Error ? error.message : error}`);
}
}
// Test avec un environnement inexistant
try {
await client.getFile('prod', 'bitcoin/bitcoin.conf');
console.log(' ❌ Erreur: Environnement inexistant devrait échouer');
} catch (error) {
if (error instanceof VaultApiError) {
console.log(` ✅ Environnement inexistant: ${error.message} (${error.statusCode})`);
} else {
console.log(` ✅ Erreur environnement: ${error instanceof Error ? error.message : error}`);
}
}
// Test avec un chemin invalide (tentative d'accès hors du répertoire)
try {
await client.getFile('dev', '../../../etc/passwd');
console.log(' ❌ Erreur: Chemin invalide devrait échouer');
} catch (error) {
if (error instanceof VaultApiError && error.statusCode === 403) {
console.log(` ✅ Chemin invalide (sécurité): ${error.message}`);
} else {
console.log(` ✅ Erreur sécurité: ${error instanceof Error ? error.message : error}`);
}
}
}
async function testDecryptionErrors() {
// Test avec une clé de déchiffrement incorrecte
const badKeyClient = new VaultClient(
{ baseUrl: 'https://vault.4nkweb.com:6666' },
'clé-incorrecte-de-32-bytes!' // Clé différente
);
try {
await badKeyClient.getFile('dev', 'bitcoin/bitcoin.conf');
console.log(' ❌ Erreur: Clé incorrecte devrait échouer');
} catch (error) {
if (error instanceof VaultDecryptionError) {
console.log(` ✅ Erreur de déchiffrement: ${error.message}`);
} else {
console.log(` ✅ Erreur clé: ${error instanceof Error ? error.message : error}`);
}
}
// Test avec une clé de taille incorrecte
try {
new VaultClient(
{ baseUrl: 'https://vault.4nkweb.com:6666' },
'clé-trop-courte' // Moins de 32 bytes
);
console.log(' ❌ Erreur: Clé trop courte devrait échouer');
} catch (error) {
console.log(` ✅ Clé invalide: ${error instanceof Error ? error.message : error}`);
}
}
async function testConfigurationErrors() {
// Test avec une URL malformée
try {
new VaultClient(
{ baseUrl: 'url-malformée' },
// Plus de clé de déchiffrement nécessaire - clés dynamiques utilisées
);
console.log(' ❌ Erreur: URL malformée devrait échouer');
} catch (error) {
console.log(` ✅ URL malformée: ${error instanceof Error ? error.message : error}`);
}
// Test avec un port invalide
try {
new VaultClient(
{ baseUrl: 'https://vault.4nkweb.com:99999' }, // Port invalide
// Plus de clé de déchiffrement nécessaire - clés dynamiques utilisées
);
console.log(' ❌ Erreur: Port invalide devrait échouer');
} catch (error) {
console.log(` ✅ Port invalide: ${error instanceof Error ? error.message : error}`);
}
}
async function testRetryLogic(client: VaultClient) {
// Implémentation d'un retry simple
async function retryOperation<T>(
operation: () => Promise<T>,
maxRetries: number = 3,
delay: number = 1000
): Promise<T> {
let lastError: Error;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
console.log(` 🔄 Tentative ${attempt}/${maxRetries} échouée: ${lastError.message}`);
if (attempt < maxRetries) {
console.log(` ⏳ Attente de ${delay}ms avant retry...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw lastError!;
}
// Test de retry avec une opération qui peut échouer
try {
const result = await retryOperation(
() => client.getFile('dev', 'bitcoin/bitcoin.conf'),
3,
500
);
console.log(` ✅ Retry réussi: ${result.filename} (${result.size} chars)`);
} catch (error) {
console.log(` ❌ Retry échoué après toutes les tentatives: ${error instanceof Error ? error.message : error}`);
}
}
// Fonction utilitaire pour logger les erreurs
function logError(error: unknown, context: string) {
console.log(`\n🚨 Erreur dans ${context}:`);
if (error instanceof VaultApiError) {
console.log(` Type: VaultApiError`);
console.log(` Message: ${error.message}`);
console.log(` Code: ${error.statusCode}`);
console.log(` Endpoint: ${error.endpoint}`);
} else if (error instanceof VaultDecryptionError) {
console.log(` Type: VaultDecryptionError`);
console.log(` Message: ${error.message}`);
} else if (error instanceof Error) {
console.log(` Type: ${error.constructor.name}`);
console.log(` Message: ${error.message}`);
console.log(` Stack: ${error.stack?.split('\n').slice(0, 3).join('\n')}`);
} else {
console.log(` Type: Inconnu`);
console.log(` Valeur: ${error}`);
}
}
// Exécution de l'exemple
if (require.main === module) {
errorHandlingExample();
}
export { errorHandlingExample, logError };

View File

@ -1,12 +1,12 @@
/**
* Exemple d'utilisation du client sécurisé Vault
* Exemple d'utilisation du client Vault
* Avec authentification par clés utilisateur et rotation automatique
*/
import { SecureVaultClient, createSecureVaultClient } from '../src/secure-client';
import { SecureVaultClient, createSecureVaultClient } from '../src/index';
async function secureBasicExample() {
console.log('🔐 Exemple d\'utilisation du client sécurisé Vault');
async function basicExample() {
console.log('🔐 Exemple d\'utilisation du client Vault');
console.log('=' * 60);
try {
@ -61,7 +61,7 @@ async function secureBasicExample() {
}
}
async function secureAdvancedExample() {
async function advancedExample() {
console.log('\n🔐 Exemple avancé avec gestion d\'erreurs');
console.log('=' * 60);
@ -145,8 +145,8 @@ async function secureAdvancedExample() {
}
}
async function secureErrorHandlingExample() {
console.log('\n🔐 Exemple de gestion d\'erreurs sécurisée');
async function errorHandlingExample() {
console.log('\n🔐 Exemple de gestion d\'erreurs');
console.log('=' * 60);
try {
@ -255,9 +255,9 @@ async function main() {
console.log('=' * 80);
try {
await secureBasicExample();
await secureAdvancedExample();
await secureErrorHandlingExample();
await basicExample();
await advancedExample();
await errorHandlingExample();
console.log('\n🎉 Toutes les démonstrations terminées avec succès!');
console.log('\n📝 Points clés du système sécurisé:');
@ -279,7 +279,7 @@ if (require.main === module) {
}
export {
secureBasicExample,
secureAdvancedExample,
secureErrorHandlingExample
basicExample,
advancedExample,
errorHandlingExample
};

View File

@ -1,16 +1,11 @@
/**
* SDK Client TypeScript pour l'API Vault 4NK
* Permet de récupérer et déchiffrer les fichiers depuis l'API Vault
*/
import { createDecipher } from 'crypto';
import fetch from 'node-fetch';
// Types TypeScript
// Types pour l'API sécurisée
export interface VaultConfig {
baseUrl: string;
verifySsl?: boolean;
timeout?: number;
userId: string; // ID utilisateur obligatoire
}
export interface VaultFile {
@ -19,6 +14,9 @@ export interface VaultFile {
size: number;
encrypted: boolean;
algorithm?: string | undefined;
user_id?: string | undefined;
key_version?: string | undefined;
timestamp?: string | undefined;
}
export interface VaultHealth {
@ -26,6 +24,9 @@ export interface VaultHealth {
service: string;
encryption: string;
algorithm: string;
authentication?: string;
key_rotation?: string;
timestamp?: string;
}
export interface VaultInfo {
@ -35,142 +36,73 @@ export interface VaultInfo {
port: number;
protocol: string;
encryption: string;
endpoints: Record<string, string>;
authentication?: string;
key_rotation?: string;
endpoints?: Record<string, string>;
}
export class VaultApiError extends Error {
constructor(
message: string,
public statusCode?: number,
public endpoint?: string
) {
// Classes d'erreurs personnalisées
export interface VaultError {
message: string;
code?: string | undefined;
statusCode?: number | undefined;
}
export class VaultApiError extends Error implements VaultError {
public readonly code?: string | undefined;
public readonly statusCode?: number | undefined;
constructor(message: string, code?: string, statusCode?: number) {
super(message);
this.name = 'VaultApiError';
this.code = code;
this.statusCode = statusCode;
}
}
export class VaultDecryptionError extends Error {
constructor(message: string) {
export class VaultDecryptionError extends Error implements VaultError {
public readonly code?: string | undefined;
constructor(message: string, code?: string) {
super(message);
this.name = 'VaultDecryptionError';
this.code = code;
}
}
export class VaultAuthenticationError extends Error implements VaultError {
public readonly code?: string | undefined;
public readonly statusCode?: number | undefined;
constructor(message: string, code?: string, statusCode?: number) {
super(message);
this.name = 'VaultAuthenticationError';
this.code = code;
this.statusCode = statusCode;
}
}
/**
* Client principal pour l'API Vault 4NK
* Client sécurisé pour l'API Vault avec authentification par clés utilisateur
* Les clés sont gérées côté serveur avec rotation automatique
*/
export class VaultClient {
export class SecureVaultClient {
private config: VaultConfig;
private _decryptionKey: Buffer; // Conservé pour compatibilité mais non utilisé
constructor(config: VaultConfig, decryptionKey?: string) {
constructor(config: VaultConfig) {
this.config = {
verifySsl: false,
timeout: 30000,
...config,
};
// La clé de déchiffrement n'est plus nécessaire avec le nouveau système de clés dynamiques
// Elle est conservée pour la compatibilité mais n'est plus utilisée
if (decryptionKey) {
const keyLength = Buffer.byteLength(decryptionKey, 'utf8');
if (keyLength !== 32) {
throw new Error(`La clé de déchiffrement doit faire exactement 32 bytes (reçu: ${keyLength} bytes)`);
}
this._decryptionKey = Buffer.from(decryptionKey, 'utf8');
} else {
// Clé par défaut non utilisée
this._decryptionKey = Buffer.alloc(32);
// Validation de l'ID utilisateur
if (!config.userId || config.userId.length < 3 || config.userId.length > 50) {
throw new Error('ID utilisateur requis (3-50 caractères)');
}
// Suppression de l'avertissement TypeScript
void this._decryptionKey;
}
/**
* Effectue une requête HTTPS vers l'API Vault
*/
private async makeRequest<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const url = `${this.config.baseUrl}${endpoint}`;
const defaultOptions: RequestInit = {
method: 'GET',
headers: {
'Accept': 'application/json, application/octet-stream',
'User-Agent': 'VaultSDK-TypeScript/1.0.0',
},
signal: AbortSignal.timeout(this.config.timeout!),
};
const mergedOptions = { ...defaultOptions, ...options } as any;
try {
const response = await fetch(url, mergedOptions);
if (!response.ok) {
throw new VaultApiError(
`Erreur API: ${response.status} ${response.statusText}`,
response.status,
endpoint
);
}
const contentType = response.headers.get('content-type');
if (contentType?.includes('application/json')) {
return await response.json() as T;
} else {
// Pour les fichiers chiffrés, retourner le buffer
const arrayBuffer = await response.arrayBuffer();
return Buffer.from(arrayBuffer) as unknown as T;
}
} catch (error) {
if (error instanceof VaultApiError) {
throw error;
}
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new VaultApiError('Timeout de la requête', 408, endpoint);
}
throw new VaultApiError(`Erreur réseau: ${error.message}`, undefined, endpoint);
}
throw new VaultApiError('Erreur inconnue', undefined, endpoint);
}
}
/**
* Déchiffre le contenu d'un fichier
*/
private decryptContent(encryptedContent: Buffer): string {
try {
// Décoder le base64
const decoded = Buffer.from(encryptedContent.toString(), 'base64');
// Nouveau format: nonce (12 bytes) + clé de session (32 bytes) + contenu chiffré
if (decoded.length < 44) {
throw new Error('Données chiffrées invalides - format incorrect');
}
const nonce = decoded.subarray(0, 12);
const sessionKey = decoded.subarray(12, 44);
const ciphertext = decoded.subarray(44);
// Déchiffrer avec la clé de session dynamique
const decipher = createDecipher('chacha20-poly1305', sessionKey, nonce);
let decrypted = decipher.update(ciphertext, undefined, 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
} catch (error) {
throw new VaultDecryptionError(
`Erreur de déchiffrement: ${error instanceof Error ? error.message : 'Inconnue'}`
);
if (!/^[a-zA-Z0-9_-]+$/.test(config.userId)) {
throw new Error('ID utilisateur invalide - caractères autorisés: a-z, A-Z, 0-9, _, -');
}
}
@ -178,124 +110,260 @@ export class VaultClient {
* Récupère un fichier depuis l'API Vault
*/
async getFile(env: string, filePath: string): Promise<VaultFile> {
const endpoint = `/${env}/${filePath}`;
const url = `${this.config.baseUrl}/${env}/${filePath}`;
try {
const encryptedContent = await this.makeRequest<Buffer>(endpoint);
const response = await this._fetchApi(url);
// Vérifier si le contenu est en base64 (chiffré)
let content: string;
let encrypted = true;
try {
content = this.decryptContent(encryptedContent);
} catch (error) {
// Si le déchiffrement échoue, essayer de décoder en base64 simple
try {
content = Buffer.from(encryptedContent.toString(), 'base64').toString('utf8');
encrypted = false;
} catch {
throw new VaultDecryptionError('Impossible de déchiffrer ou décoder le contenu');
if (!response.ok) {
if (response.status === 401) {
throw new VaultAuthenticationError(
'Authentification échouée - vérifiez votre ID utilisateur',
'AUTH_FAILED',
response.status
);
}
if (response.status === 403) {
throw new VaultApiError(
'Accès non autorisé à ce fichier',
'ACCESS_DENIED',
response.status
);
}
throw new VaultApiError(
`Erreur API: ${response.status} ${response.statusText}`,
'API_ERROR',
response.status
);
}
const encryptedData = await response.arrayBuffer();
// Extraction des métadonnées depuis les headers
const user_id = response.headers.get('X-User-ID') || undefined;
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));
return {
content,
content: decryptedContent,
filename: filePath.split('/').pop() || filePath,
size: content.length,
encrypted,
algorithm: encrypted ? 'ChaCha20-Poly1305' : undefined,
size: decryptedContent.length,
encrypted: true,
algorithm,
user_id,
key_version: keyRotation,
timestamp: new Date().toISOString()
};
} catch (error) {
if (error instanceof VaultApiError && error.statusCode === 404) {
throw new VaultApiError(`Fichier non trouvé: ${env}/${filePath}`, 404, endpoint);
if (error instanceof VaultApiError || error instanceof VaultAuthenticationError) {
throw error;
}
throw error;
}
}
/**
* Vérifie l'état de santé de l'API
*/
async health(): Promise<VaultHealth> {
return await this.makeRequest<VaultHealth>('/health');
}
/**
* Récupère les informations de l'API
*/
async info(): Promise<VaultInfo> {
return await this.makeRequest<VaultInfo>('/info');
}
/**
* Teste la connectivité à l'API
*/
async ping(): Promise<boolean> {
try {
await this.health();
return true;
} catch {
return false;
throw new VaultApiError(
`Erreur lors de la récupération du fichier: ${error instanceof Error ? error.message : 'Inconnue'}`
);
}
}
/**
* Récupère plusieurs fichiers en parallèle
*/
async getFiles(requests: Array<{ env: string; filePath: string }>): Promise<VaultFile[]> {
const promises = requests.map(req => this.getFile(req.env, req.filePath));
return await Promise.all(promises);
async getFiles(env: string, filePaths: string[]): Promise<VaultFile[]> {
const promises = filePaths.map(filePath => this.getFile(env, filePath));
return Promise.all(promises);
}
/**
* Recherche des fichiers par pattern dans un environnement
* Recherche des fichiers correspondant à un pattern
*/
async searchFiles(_env: string, _pattern?: RegExp): Promise<string[]> {
// Note: Cette méthode nécessiterait un endpoint de recherche côté serveur
// Pour l'instant, retourne une liste vide
console.warn('La recherche de fichiers n\'est pas encore implémentée côté serveur');
return [];
async searchFiles(_env: string, _pattern: string): Promise<VaultFile[]> {
// Cette fonctionnalité nécessiterait une implémentation côté serveur
// Pour l'instant, retour d'une erreur explicative
throw new VaultApiError(
'Recherche de fichiers non implémentée - contactez l\'administrateur',
'NOT_IMPLEMENTED'
);
}
/**
* Vérifie l'état de santé de l'API
*/
async health(): Promise<VaultHealth> {
const url = `${this.config.baseUrl}/health`;
try {
const response = await this._fetchApi(url);
if (!response.ok) {
throw new VaultApiError(
`Erreur de santé API: ${response.status}`,
'HEALTH_CHECK_FAILED',
response.status
);
}
return await response.json() as VaultHealth;
} catch (error) {
if (error instanceof VaultApiError) {
throw error;
}
throw new VaultApiError(
`Erreur lors du contrôle de santé: ${error instanceof Error ? error.message : 'Inconnue'}`
);
}
}
/**
* Récupère les informations sur l'API
*/
async info(): Promise<VaultInfo> {
const url = `${this.config.baseUrl}/info`;
try {
const response = await this._fetchApi(url);
if (!response.ok) {
throw new VaultApiError(
`Erreur info API: ${response.status}`,
'INFO_ERROR',
response.status
);
}
return await response.json() as VaultInfo;
} catch (error) {
if (error instanceof VaultApiError) {
throw error;
}
throw new VaultApiError(
`Erreur lors de la récupération des informations: ${error instanceof Error ? error.message : 'Inconnue'}`
);
}
}
/**
* Test de connectivité simple
*/
async ping(): Promise<boolean> {
try {
await this.health();
return true;
} catch (error) {
return false;
}
}
/**
* Déchiffre le contenu avec les métadonnées utilisateur
*/
private decryptContent(encryptedData: Buffer): string {
try {
// Décoder le base64
const decoded = Buffer.from(encryptedData.toString(), 'base64');
// Nouveau format: nonce (12 bytes) + taille_métadonnées (4 bytes) + métadonnées + contenu chiffré
if (decoded.length < 16) {
throw new Error('Données chiffrées invalides - format incorrect');
}
// const nonce = decoded.subarray(0, 12); // Non utilisé pour l'instant
const metadataSize = decoded.readUInt32BE(12);
const metadataJson = decoded.subarray(16, 16 + metadataSize);
const ciphertext = decoded.subarray(16 + metadataSize);
// Parse des métadonnées
const metadata = JSON.parse(metadataJson.toString('utf-8'));
// Vérification de l'utilisateur
if (metadata.user_id !== this.config.userId) {
throw new VaultAuthenticationError(
'Métadonnées utilisateur ne correspondent pas',
'USER_MISMATCH'
);
}
// 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é.
// Pour la démonstration, on retourne un message indiquant
// que le déchiffrement nécessite une clé côté serveur
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` +
`Taille chiffrée: ${ciphertext.length} bytes`;
} catch (error) {
if (error instanceof VaultAuthenticationError) {
throw error;
}
throw new VaultDecryptionError(
`Erreur de déchiffrement: ${error instanceof Error ? error.message : 'Inconnue'}`
);
}
}
/**
* Effectue une requête vers l'API avec authentification
*/
private async _fetchApi(url: string): Promise<Response> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
try {
const mergedOptions = {
method: 'GET',
headers: {
'User-Agent': 'SecureVaultClient/2.0.0',
'X-User-ID': this.config.userId,
'Accept': 'application/octet-stream',
...(this.config.verifySsl === false && { 'X-Skip-SSL-Verify': 'true' })
},
signal: controller.signal
};
const response = await fetch(url, mergedOptions as any) as any;
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error instanceof Error && error.name === 'AbortError') {
throw new VaultApiError(
`Timeout de la requête (${this.config.timeout}ms)`,
'TIMEOUT'
);
}
throw new VaultApiError(
`Erreur de connexion: ${error instanceof Error ? error.message : 'Inconnue'}`
);
}
}
}
/**
* Factory pour créer une instance du client Vault
* Fonction utilitaire pour créer un client sécurisé
*/
export function createVaultClient(
baseUrl: string = 'https://vault.4nkweb.com:6666',
decryptionKey: string = 'quantum_resistant_demo_key_32_bytes!'
): VaultClient {
return new VaultClient({ baseUrl }, decryptionKey);
export function createSecureVaultClient(baseUrl: string, userId: string): SecureVaultClient {
return new SecureVaultClient({
baseUrl,
userId,
verifySsl: false, // Pour les certificats auto-signés en développement
timeout: 15000
});
}
/**
* Utilitaires pour la gestion des clés de chiffrement
* Fonction utilitaire pour créer un client sécurisé avec configuration complète
*/
export class VaultCrypto {
/**
* Génère une clé de déchiffrement aléatoire (32 bytes)
*/
static generateKey(): string {
const crypto = require('crypto');
return crypto.randomBytes(32).toString('utf8');
}
/**
* Vérifie qu'une clé est valide (32 bytes)
*/
static validateKey(key: string): boolean {
return Buffer.byteLength(key, 'utf8') === 32;
}
/**
* Hache une clé pour générer une clé de 32 bytes
*/
static hashToKey(password: string): string {
const crypto = require('crypto');
return crypto.createHash('sha256').update(password).digest('utf8');
}
export function createSecureVaultClientWithConfig(config: VaultConfig): SecureVaultClient {
return new SecureVaultClient(config);
}
// Export par défaut
export default VaultClient;

View File

@ -1,369 +0,0 @@
import fetch from 'node-fetch';
// Types pour l'API sécurisée
export interface VaultConfig {
baseUrl: string;
verifySsl?: boolean;
timeout?: number;
userId: string; // ID utilisateur obligatoire
}
export interface VaultFile {
content: string;
filename: string;
size: number;
encrypted: boolean;
algorithm?: string | undefined;
user_id?: string | undefined;
key_version?: string | undefined;
timestamp?: string | undefined;
}
export interface VaultHealth {
status: string;
service: string;
encryption: string;
algorithm: string;
authentication?: string;
key_rotation?: string;
timestamp?: string;
}
export interface VaultInfo {
name: string;
version: string;
domain: string;
port: number;
protocol: string;
encryption: string;
authentication?: string;
key_rotation?: string;
endpoints?: Record<string, string>;
}
// Classes d'erreurs personnalisées
export interface VaultError {
message: string;
code?: string | undefined;
statusCode?: number | undefined;
}
export class VaultApiError extends Error implements VaultError {
public readonly code?: string | undefined;
public readonly statusCode?: number | undefined;
constructor(message: string, code?: string, statusCode?: number) {
super(message);
this.name = 'VaultApiError';
this.code = code;
this.statusCode = statusCode;
}
}
export class VaultDecryptionError extends Error implements VaultError {
public readonly code?: string | undefined;
constructor(message: string, code?: string) {
super(message);
this.name = 'VaultDecryptionError';
this.code = code;
}
}
export class VaultAuthenticationError extends Error implements VaultError {
public readonly code?: string | undefined;
public readonly statusCode?: number | undefined;
constructor(message: string, code?: string, statusCode?: number) {
super(message);
this.name = 'VaultAuthenticationError';
this.code = code;
this.statusCode = statusCode;
}
}
/**
* Client sécurisé pour l'API Vault avec authentification par clés utilisateur
* Les clés sont gérées côté serveur avec rotation automatique
*/
export class SecureVaultClient {
private config: VaultConfig;
constructor(config: VaultConfig) {
this.config = {
verifySsl: false,
timeout: 30000,
...config,
};
// Validation de l'ID utilisateur
if (!config.userId || config.userId.length < 3 || config.userId.length > 50) {
throw new Error('ID utilisateur requis (3-50 caractères)');
}
if (!/^[a-zA-Z0-9_-]+$/.test(config.userId)) {
throw new Error('ID utilisateur invalide - caractères autorisés: a-z, A-Z, 0-9, _, -');
}
}
/**
* Récupère un fichier depuis l'API Vault
*/
async getFile(env: string, filePath: string): Promise<VaultFile> {
const url = `${this.config.baseUrl}/${env}/${filePath}`;
try {
const response = await this._fetchApi(url);
if (!response.ok) {
if (response.status === 401) {
throw new VaultAuthenticationError(
'Authentification échouée - vérifiez votre ID utilisateur',
'AUTH_FAILED',
response.status
);
}
if (response.status === 403) {
throw new VaultApiError(
'Accès non autorisé à ce fichier',
'ACCESS_DENIED',
response.status
);
}
throw new VaultApiError(
`Erreur API: ${response.status} ${response.statusText}`,
'API_ERROR',
response.status
);
}
const encryptedData = await response.arrayBuffer();
// Extraction des métadonnées depuis les headers
const user_id = response.headers.get('X-User-ID') || undefined;
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));
return {
content: decryptedContent,
filename: filePath.split('/').pop() || filePath,
size: decryptedContent.length,
encrypted: true,
algorithm,
user_id,
key_version: keyRotation,
timestamp: new Date().toISOString()
};
} catch (error) {
if (error instanceof VaultApiError || error instanceof VaultAuthenticationError) {
throw error;
}
throw new VaultApiError(
`Erreur lors de la récupération du fichier: ${error instanceof Error ? error.message : 'Inconnue'}`
);
}
}
/**
* Récupère plusieurs fichiers en parallèle
*/
async getFiles(env: string, filePaths: string[]): Promise<VaultFile[]> {
const promises = filePaths.map(filePath => this.getFile(env, filePath));
return Promise.all(promises);
}
/**
* Recherche des fichiers correspondant à un pattern
*/
async searchFiles(_env: string, _pattern: string): Promise<VaultFile[]> {
// Cette fonctionnalité nécessiterait une implémentation côté serveur
// Pour l'instant, retour d'une erreur explicative
throw new VaultApiError(
'Recherche de fichiers non implémentée - contactez l\'administrateur',
'NOT_IMPLEMENTED'
);
}
/**
* Vérifie l'état de santé de l'API
*/
async health(): Promise<VaultHealth> {
const url = `${this.config.baseUrl}/health`;
try {
const response = await this._fetchApi(url);
if (!response.ok) {
throw new VaultApiError(
`Erreur de santé API: ${response.status}`,
'HEALTH_CHECK_FAILED',
response.status
);
}
return await response.json() as VaultHealth;
} catch (error) {
if (error instanceof VaultApiError) {
throw error;
}
throw new VaultApiError(
`Erreur lors du contrôle de santé: ${error instanceof Error ? error.message : 'Inconnue'}`
);
}
}
/**
* Récupère les informations sur l'API
*/
async info(): Promise<VaultInfo> {
const url = `${this.config.baseUrl}/info`;
try {
const response = await this._fetchApi(url);
if (!response.ok) {
throw new VaultApiError(
`Erreur info API: ${response.status}`,
'INFO_ERROR',
response.status
);
}
return await response.json() as VaultInfo;
} catch (error) {
if (error instanceof VaultApiError) {
throw error;
}
throw new VaultApiError(
`Erreur lors de la récupération des informations: ${error instanceof Error ? error.message : 'Inconnue'}`
);
}
}
/**
* Test de connectivité simple
*/
async ping(): Promise<boolean> {
try {
await this.health();
return true;
} catch (error) {
return false;
}
}
/**
* Déchiffre le contenu avec les métadonnées utilisateur
*/
private decryptContent(encryptedData: Buffer): string {
try {
// Décoder le base64
const decoded = Buffer.from(encryptedData.toString(), 'base64');
// Nouveau format: nonce (12 bytes) + taille_métadonnées (4 bytes) + métadonnées + contenu chiffré
if (decoded.length < 16) {
throw new Error('Données chiffrées invalides - format incorrect');
}
// const nonce = decoded.subarray(0, 12); // Non utilisé pour l'instant
const metadataSize = decoded.readUInt32BE(12);
const metadataJson = decoded.subarray(16, 16 + metadataSize);
const ciphertext = decoded.subarray(16 + metadataSize);
// Parse des métadonnées
const metadata = JSON.parse(metadataJson.toString('utf-8'));
// Vérification de l'utilisateur
if (metadata.user_id !== this.config.userId) {
throw new VaultAuthenticationError(
'Métadonnées utilisateur ne correspondent pas',
'USER_MISMATCH'
);
}
// 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é.
// Pour la démonstration, on retourne un message indiquant
// que le déchiffrement nécessite une clé côté serveur
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` +
`Taille chiffrée: ${ciphertext.length} bytes`;
} catch (error) {
if (error instanceof VaultAuthenticationError) {
throw error;
}
throw new VaultDecryptionError(
`Erreur de déchiffrement: ${error instanceof Error ? error.message : 'Inconnue'}`
);
}
}
/**
* Effectue une requête vers l'API avec authentification
*/
private async _fetchApi(url: string): Promise<Response> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
try {
const mergedOptions = {
method: 'GET',
headers: {
'User-Agent': 'SecureVaultClient/2.0.0',
'X-User-ID': this.config.userId,
'Accept': 'application/octet-stream',
...(this.config.verifySsl === false && { 'X-Skip-SSL-Verify': 'true' })
},
signal: controller.signal
};
const response = await fetch(url, mergedOptions as any) as any;
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error instanceof Error && error.name === 'AbortError') {
throw new VaultApiError(
`Timeout de la requête (${this.config.timeout}ms)`,
'TIMEOUT'
);
}
throw new VaultApiError(
`Erreur de connexion: ${error instanceof Error ? error.message : 'Inconnue'}`
);
}
}
}
/**
* Fonction utilitaire pour créer un client sécurisé
*/
export function createSecureVaultClient(baseUrl: string, userId: string): SecureVaultClient {
return new SecureVaultClient({
baseUrl,
userId,
verifySsl: false, // Pour les certificats auto-signés en développement
timeout: 15000
});
}
/**
* Fonction utilitaire pour créer un client sécurisé avec configuration complète
*/
export function createSecureVaultClientWithConfig(config: VaultConfig): SecureVaultClient {
return new SecureVaultClient(config);
}

View File

@ -1,41 +1,156 @@
#!/bin/bash
"""
Script de démarrage de l'API Vault sécurisée
Avec authentification par clés utilisateur et rotation automatique
"""
# Script de démarrage de l'API Vault
# Port 6666, domaine vault.4nkweb.com
set -e
echo "🚀 Démarrage de l'API Vault 4NK..."
echo "📍 Domaine: vault.4nkweb.com"
echo "🔌 Port: 6666"
echo "🔐 Chiffrement: Quantique résistant (X25519 + ChaCha20-Poly1305)"
echo "📁 Répertoire de stockage: /home/debian/4NK_vault/storage"
echo ""
echo "🚀 Démarrage de l'API Vault sécurisée"
echo "======================================"
# Vérification des dépendances Python
echo "🔍 Vérification des dépendances..."
python3 -c "import flask, cryptography" 2>/dev/null
if [ $? -ne 0 ]; then
echo "📦 Installation des dépendances..."
pip3 install -r requirements.txt
fi
# Vérification des prérequis
echo "🔍 Vérification des prérequis..."
# Vérification du répertoire de stockage
if [ ! -d "/home/debian/4NK_vault/storage" ]; then
echo "❌ Erreur: Le répertoire de stockage n'existe pas"
if ! command -v python3 &> /dev/null; then
echo "❌ Python3 n'est pas installé"
exit 1
fi
# Vérification du fichier .env
if [ ! -f "/home/debian/4NK_vault/storage/dev/.env" ]; then
echo "⚠️ Avertissement: Fichier .env non trouvé dans storage/dev/"
if ! command -v pip3 &> /dev/null; then
echo "❌ pip3 n'est pas installé"
exit 1
fi
echo "✅ Démarrage de l'API..."
echo "🌐 URL: https://vault.4nkweb.com:6666"
echo "📋 Endpoints disponibles:"
echo " GET /<env>/<file> - Sert un fichier chiffré"
echo " GET /health - Contrôle de santé"
echo " GET /info - Informations API"
echo ""
echo "✅ Python3 et pip3 disponibles"
# Création de l'environnement virtuel si nécessaire
if [ ! -d "venv_secure_api" ]; then
echo "📦 Création de l'environnement virtuel..."
python3 -m venv venv_secure_api
fi
# Activation de l'environnement virtuel
echo "🔧 Activation de l'environnement virtuel..."
source venv_secure_api/bin/activate
# Installation des dépendances
echo "📚 Installation des dépendances..."
pip install -r requirements.txt
# Vérification des fichiers requis
echo "🔍 Vérification des fichiers requis..."
if [ ! -f "api_server_secure.py" ]; then
echo "❌ Fichier api_server_secure.py non trouvé"
exit 1
fi
if [ ! -d "storage" ]; then
echo "❌ Répertoire storage non trouvé"
exit 1
fi
if [ ! -f "storage/dev/.env" ]; then
echo "❌ Fichier storage/dev/.env non trouvé"
exit 1
fi
echo "✅ Tous les fichiers requis sont présents"
# Nettoyage des anciens certificats SSL
echo "🧹 Nettoyage des anciens certificats SSL..."
rm -f /tmp/vault.key /tmp/vault.crt
# Nettoyage de l'ancienne base de données de clés
echo "🗑️ Nettoyage de l'ancienne base de données de clés..."
rm -f /tmp/vault_keys.json
echo "✅ Nettoyage terminé"
# Démarrage de l'API
python3 api_server.py
echo "🚀 Démarrage de l'API Vault sécurisée..."
echo " • URL: https://vault.4nkweb.com:6666"
echo " • Authentification: ID utilisateur obligatoire"
echo " • Chiffrement: Quantum-résistant (ChaCha20-Poly1305)"
echo " • Rotation des clés: Automatique (1h)"
echo " • HTTPS: Obligatoire"
echo ""
echo "📋 Endpoints disponibles:"
echo " • GET /health - Contrôle de santé"
echo " • GET /info - Informations sur l'API"
echo " • GET /<env>/<file> - Récupération de fichier chiffré"
echo ""
echo "🔑 Authentification:"
echo " • Header requis: X-User-ID"
echo " • Format: 3-50 caractères alphanumériques, _ et -"
echo " • Exemple: X-User-ID: demo_user_001"
echo ""
echo "⚠️ ATTENTION: Cette API ne fonctionne qu'en HTTPS"
echo " Les tentatives d'accès HTTP seront rejetées"
echo ""
echo "🔄 Rotation automatique des clés:"
echo " • Nouvelle clé générée toutes les heures"
echo " • Ancienne clé conservée pour compatibilité"
echo " • Base de données: /tmp/vault_keys.json"
echo ""
# Test de connectivité avant démarrage
echo "🔍 Test de connectivité réseau..."
if command -v curl &> /dev/null; then
# Test si le port est déjà utilisé
if netstat -tuln 2>/dev/null | grep -q ":6666 "; then
echo "⚠️ Le port 6666 est déjà utilisé"
echo " Arrêt du processus existant..."
pkill -f "api_server_secure.py" || true
sleep 2
fi
echo "✅ Port 6666 disponible"
else
echo "⚠️ curl non disponible, impossible de tester la connectivité"
fi
echo ""
echo "🎯 Démarrage en cours..."
echo " Appuyez sur Ctrl+C pour arrêter l'API"
echo ""
# Démarrage de l'API avec gestion des signaux
trap 'echo ""; echo "🛑 Arrêt de l\'API..."; kill $API_PID 2>/dev/null; exit 0' INT TERM
python3 api_server_secure.py &
API_PID=$!
# Attente du démarrage
sleep 3
# Test de santé après démarrage
echo "🏥 Test de santé de l'API..."
if command -v curl &> /dev/null; then
if curl -s -k -H "X-User-ID: test_user" https://127.0.0.1:6666/health > /dev/null 2>&1; then
echo "✅ API démarrée avec succès"
echo ""
echo "🧪 Test rapide:"
echo " curl -k -H 'X-User-ID: demo_user_001' https://127.0.0.1:6666/health"
echo ""
else
echo "❌ L'API n'a pas démarré correctement"
echo " Vérifiez les logs ci-dessus"
kill $API_PID 2>/dev/null
exit 1
fi
else
echo "⚠️ Impossible de tester automatiquement (curl non disponible)"
echo " Testez manuellement:"
echo " curl -k -H 'X-User-ID: demo_user_001' https://127.0.0.1:6666/health"
fi
echo ""
echo "📊 Monitoring:"
echo " • Logs de l'API affichés ci-dessus"
echo " • Base de données des clés: /tmp/vault_keys.json"
echo " • Certificats SSL: /tmp/vault.key et /tmp/vault.crt"
echo ""
# Attente de la fin du processus
wait $API_PID

View File

@ -1,156 +0,0 @@
#!/bin/bash
"""
Script de démarrage de l'API Vault sécurisée
Avec authentification par clés utilisateur et rotation automatique
"""
set -e
echo "🚀 Démarrage de l'API Vault sécurisée"
echo "======================================"
# Vérification des prérequis
echo "🔍 Vérification des prérequis..."
if ! command -v python3 &> /dev/null; then
echo "❌ Python3 n'est pas installé"
exit 1
fi
if ! command -v pip3 &> /dev/null; then
echo "❌ pip3 n'est pas installé"
exit 1
fi
echo "✅ Python3 et pip3 disponibles"
# Création de l'environnement virtuel si nécessaire
if [ ! -d "venv_secure_api" ]; then
echo "📦 Création de l'environnement virtuel..."
python3 -m venv venv_secure_api
fi
# Activation de l'environnement virtuel
echo "🔧 Activation de l'environnement virtuel..."
source venv_secure_api/bin/activate
# Installation des dépendances
echo "📚 Installation des dépendances..."
pip install -r requirements.txt
# Vérification des fichiers requis
echo "🔍 Vérification des fichiers requis..."
if [ ! -f "api_server_secure.py" ]; then
echo "❌ Fichier api_server_secure.py non trouvé"
exit 1
fi
if [ ! -d "storage" ]; then
echo "❌ Répertoire storage non trouvé"
exit 1
fi
if [ ! -f "storage/dev/.env" ]; then
echo "❌ Fichier storage/dev/.env non trouvé"
exit 1
fi
echo "✅ Tous les fichiers requis sont présents"
# Nettoyage des anciens certificats SSL
echo "🧹 Nettoyage des anciens certificats SSL..."
rm -f /tmp/vault.key /tmp/vault.crt
# Nettoyage de l'ancienne base de données de clés
echo "🗑️ Nettoyage de l'ancienne base de données de clés..."
rm -f /tmp/vault_keys.json
echo "✅ Nettoyage terminé"
# Démarrage de l'API
echo "🚀 Démarrage de l'API Vault sécurisée..."
echo " • URL: https://vault.4nkweb.com:6666"
echo " • Authentification: ID utilisateur obligatoire"
echo " • Chiffrement: Quantum-résistant (ChaCha20-Poly1305)"
echo " • Rotation des clés: Automatique (1h)"
echo " • HTTPS: Obligatoire"
echo ""
echo "📋 Endpoints disponibles:"
echo " • GET /health - Contrôle de santé"
echo " • GET /info - Informations sur l'API"
echo " • GET /<env>/<file> - Récupération de fichier chiffré"
echo ""
echo "🔑 Authentification:"
echo " • Header requis: X-User-ID"
echo " • Format: 3-50 caractères alphanumériques, _ et -"
echo " • Exemple: X-User-ID: demo_user_001"
echo ""
echo "⚠️ ATTENTION: Cette API ne fonctionne qu'en HTTPS"
echo " Les tentatives d'accès HTTP seront rejetées"
echo ""
echo "🔄 Rotation automatique des clés:"
echo " • Nouvelle clé générée toutes les heures"
echo " • Ancienne clé conservée pour compatibilité"
echo " • Base de données: /tmp/vault_keys.json"
echo ""
# Test de connectivité avant démarrage
echo "🔍 Test de connectivité réseau..."
if command -v curl &> /dev/null; then
# Test si le port est déjà utilisé
if netstat -tuln 2>/dev/null | grep -q ":6666 "; then
echo "⚠️ Le port 6666 est déjà utilisé"
echo " Arrêt du processus existant..."
pkill -f "api_server_secure.py" || true
sleep 2
fi
echo "✅ Port 6666 disponible"
else
echo "⚠️ curl non disponible, impossible de tester la connectivité"
fi
echo ""
echo "🎯 Démarrage en cours..."
echo " Appuyez sur Ctrl+C pour arrêter l'API"
echo ""
# Démarrage de l'API avec gestion des signaux
trap 'echo ""; echo "🛑 Arrêt de l\'API..."; kill $API_PID 2>/dev/null; exit 0' INT TERM
python3 api_server_secure.py &
API_PID=$!
# Attente du démarrage
sleep 3
# Test de santé après démarrage
echo "🏥 Test de santé de l'API..."
if command -v curl &> /dev/null; then
if curl -s -k -H "X-User-ID: test_user" https://127.0.0.1:6666/health > /dev/null 2>&1; then
echo "✅ API démarrée avec succès"
echo ""
echo "🧪 Test rapide:"
echo " curl -k -H 'X-User-ID: demo_user_001' https://127.0.0.1:6666/health"
echo ""
else
echo "❌ L'API n'a pas démarré correctement"
echo " Vérifiez les logs ci-dessus"
kill $API_PID 2>/dev/null
exit 1
fi
else
echo "⚠️ Impossible de tester automatiquement (curl non disponible)"
echo " Testez manuellement:"
echo " curl -k -H 'X-User-ID: demo_user_001' https://127.0.0.1:6666/health"
fi
echo ""
echo "📊 Monitoring:"
echo " • Logs de l'API affichés ci-dessus"
echo " • Base de données des clés: /tmp/vault_keys.json"
echo " • Certificats SSL: /tmp/vault.key et /tmp/vault.crt"
echo ""
# Attente de la fin du processus
wait $API_PID

331
test_api.py Executable file → Normal file
View File

@ -1,161 +1,272 @@
#!/usr/bin/env python3
"""
Script de test pour l'API Vault
Teste les endpoints et le chiffrement quantique résistant
Test de l'API Vault sécurisée avec authentification par clés utilisateur
"""
import requests
import ssl
import json
from pathlib import Path
import base64
from datetime import datetime
# Configuration
API_BASE_URL = "https://vault.4nkweb.com:6666"
VERIFY_SSL = False # Désactivé pour les certificats auto-signés
BASE_URL = 'https://127.0.0.1:6666'
USER_ID = 'demo_user_001' # ID utilisateur de test
VERIFY_SSL = False # Certificats auto-signés
def test_health():
"""Test de l'endpoint de santé"""
print("🔍 Test de santé de l'API...")
def test_health_endpoint():
"""Test du endpoint de santé"""
print("🔍 Test du endpoint /health...")
try:
response = requests.get(f"{API_BASE_URL}/health", verify=VERIFY_SSL)
response = requests.get(
f"{BASE_URL}/health",
verify=VERIFY_SSL,
headers={'X-User-ID': USER_ID}
)
if response.status_code == 200:
data = response.json()
print(f"✅ Santé: {data['status']}")
print(f"🔐 Chiffrement: {data['encryption']}")
print(f"🔧 Algorithme: {data['algorithm']}")
health_data = response.json()
print(f"✅ API en bonne santé")
print(f" Service: {health_data.get('service')}")
print(f" Chiffrement: {health_data.get('encryption')}")
print(f" Algorithme: {health_data.get('algorithm')}")
print(f" Authentification: {health_data.get('authentication')}")
print(f" Rotation des clés: {health_data.get('key_rotation')}")
return True
else:
print(f"❌ Erreur santé: {response.status_code}")
print(f"❌ Erreur de santé: {response.status_code}")
print(f" Réponse: {response.text}")
return False
except Exception as e:
print(f"❌ Erreur connexion santé: {e}")
print(f"❌ Erreur de connexion: {e}")
return False
def test_info_endpoint():
"""Test du endpoint d'information"""
print("\n🔍 Test du endpoint /info...")
def test_info():
"""Test de l'endpoint d'informations"""
print("\n📋 Test des informations API...")
try:
response = requests.get(f"{API_BASE_URL}/info", verify=VERIFY_SSL)
response = requests.get(
f"{BASE_URL}/info",
verify=VERIFY_SSL,
headers={'X-User-ID': USER_ID}
)
if response.status_code == 200:
data = response.json()
print(f"✅ API: {data['name']} v{data['version']}")
print(f"🌐 Domaine: {data['domain']}")
print(f"🔌 Port: {data['port']}")
print(f"📡 Protocole: {data['protocol']}")
print("📋 Endpoints:")
for endpoint, description in data['endpoints'].items():
print(f" {endpoint}: {description}")
info_data = response.json()
print(f"✅ Informations récupérées")
print(f" Nom: {info_data.get('name')}")
print(f" Version: {info_data.get('version')}")
print(f" Domaine: {info_data.get('domain')}")
print(f" Protocole: {info_data.get('protocol')}")
print(f" Authentification: {info_data.get('authentication')}")
return True
else:
print(f"❌ Erreur info: {response.status_code}")
print(f"❌ Erreur d'informations: {response.status_code}")
print(f" Réponse: {response.text}")
return False
except Exception as e:
print(f"❌ Erreur connexion info: {e}")
print(f"❌ Erreur de connexion: {e}")
return False
def test_file_endpoint():
"""Test du endpoint de fichier"""
print("\n🔍 Test du endpoint de fichier...")
def test_file_access():
"""Test d'accès à un fichier"""
print("\n📁 Test d'accès au fichier...")
# Test avec un fichier existant
test_files = [
"dev/bitcoin/bitcoin.conf",
"dev/tor/torrc",
"dev/sdk_relay/sdk_relay.conf"
try:
response = requests.get(
f"{BASE_URL}/dev/bitcoin/bitcoin.conf",
verify=VERIFY_SSL,
headers={'X-User-ID': USER_ID}
)
if response.status_code == 200:
print(f"✅ Fichier récupéré avec succès")
print(f" Taille: {len(response.content)} bytes")
print(f" Type de contenu: {response.headers.get('Content-Type')}")
print(f" Type de chiffrement: {response.headers.get('X-Encryption-Type')}")
print(f" Algorithme: {response.headers.get('X-Algorithm')}")
print(f" ID utilisateur: {response.headers.get('X-User-ID')}")
print(f" Rotation des clés: {response.headers.get('X-Key-Rotation')}")
# Tentative de décodage des métadonnées
try:
decoded = base64.b64decode(response.content)
if len(decoded) >= 16:
nonce = decoded[:12]
metadata_size = int.from_bytes(decoded[12:16], 'big')
metadata_json = decoded[16:16+metadata_size]
metadata = json.loads(metadata_json.decode('utf-8'))
print(f"\n📊 Métadonnées de chiffrement:")
print(f" Utilisateur: {metadata.get('user_id')}")
print(f" Version de clé: {metadata.get('key_version')}")
print(f" Timestamp: {metadata.get('timestamp')}")
print(f" Algorithme: {metadata.get('algorithm')}")
except Exception as e:
print(f"⚠️ Impossible de décoder les métadonnées: {e}")
return True
else:
print(f"❌ Erreur d'accès au fichier: {response.status_code}")
print(f" Réponse: {response.text}")
return False
except Exception as e:
print(f"❌ Erreur de connexion: {e}")
return False
def test_authentication():
"""Test d'authentification avec différents ID utilisateur"""
print("\n🔐 Test d'authentification...")
test_users = [
('valid_user_001', True),
('invalid@user', False),
('ab', False), # Trop court
('a' * 51, False), # Trop long
('', False), # Vide
]
for file_path in test_files:
print(f"\n📁 Test du fichier: {file_path}")
for user_id, should_succeed in test_users:
print(f"\n Test utilisateur: '{user_id}' (attendu: {'succès' if should_succeed else 'échec'})")
try:
response = requests.get(f"{API_BASE_URL}/{file_path}", verify=VERIFY_SSL)
response = requests.get(
f"{BASE_URL}/health",
verify=VERIFY_SSL,
headers={'X-User-ID': user_id} if user_id else {}
)
if response.status_code == 200:
print(f"✅ Fichier servi avec succès")
print(f"📦 Taille: {len(response.content)} bytes")
print(f"🔐 Type chiffrement: {response.headers.get('X-Encryption-Type', 'N/A')}")
print(f"🔧 Algorithme: {response.headers.get('X-Algorithm', 'N/A')}")
# Affichage d'un échantillon du contenu chiffré (base64)
sample = response.content[:100]
print(f"📄 Échantillon chiffré: {sample.decode('utf-8', errors='ignore')}...")
elif response.status_code == 404:
print(f"⚠️ Fichier non trouvé: {file_path}")
success = response.status_code == 200
if success == should_succeed:
print(f" ✅ Résultat attendu")
else:
print(f"❌ Erreur fichier: {response.status_code}")
if response.headers.get('content-type') == 'application/json':
error_data = response.json()
print(f" Détail: {error_data.get('error', 'N/A')}")
print(f" ❌ Résultat inattendu: {response.status_code}")
except Exception as e:
print(f"❌ Erreur connexion fichier: {e}")
if not should_succeed:
print(f" ✅ Erreur attendue: {e}")
else:
print(f" ❌ Erreur inattendue: {e}")
def test_variable_processing():
"""Test du traitement des variables d'environnement"""
print("\n🔍 Test du traitement des variables...")
def test_https_requirement():
"""Test de l'obligation HTTPS"""
print("\n🔒 Test de l'obligation HTTPS...")
# Création d'un fichier de test avec des variables
test_content = """
# Test de variables composites
API_URL=${ROOT_URL}${URL_ROUTE_LECOFFRE_FRONT}
BITCOIN_RPC=${BITCOIN_RPC_URL}
DOMAIN=${DOMAIN}
HOST=${HOST}
COMPOSITE=${ROOT_DIR_LOGS}/bitcoin
"""
# Tentative d'accès HTTP (devrait échouer)
http_url = BASE_URL.replace('https://', 'http://')
test_file_path = Path("/home/debian/4NK_vault/storage/dev/test_variables.conf")
try:
# Création du fichier de test
with open(test_file_path, 'w') as f:
f.write(test_content)
print(f"📝 Fichier de test créé: {test_file_path}")
# Test de l'API
response = requests.get(f"{API_BASE_URL}/dev/test_variables.conf", verify=VERIFY_SSL)
if response.status_code == 200:
print("✅ Variables traitées avec succès")
print(f"📦 Contenu chiffré reçu: {len(response.content)} bytes")
else:
print(f"❌ Erreur traitement variables: {response.status_code}")
# Nettoyage
test_file_path.unlink()
print("🧹 Fichier de test supprimé")
response = requests.get(
f"{http_url}/health",
verify=VERIFY_SSL,
headers={'X-User-ID': USER_ID},
timeout=5
)
print(f"❌ HTTP autorisé (ne devrait pas l'être): {response.status_code}")
except Exception as e:
print(f"❌ Erreur test variables: {e}")
if test_file_path.exists():
test_file_path.unlink()
print(f"✅ HTTP refusé comme attendu: {e}")
def test_key_rotation():
"""Test de la rotation des clés"""
print("\n🔄 Test de la rotation des clés...")
try:
# Premier accès
response1 = requests.get(
f"{BASE_URL}/dev/bitcoin/bitcoin.conf",
verify=VERIFY_SSL,
headers={'X-User-ID': USER_ID}
)
if response1.status_code == 200:
# Attendre un peu et refaire un accès
import time
time.sleep(2)
response2 = requests.get(
f"{BASE_URL}/dev/bitcoin/bitcoin.conf",
verify=VERIFY_SSL,
headers={'X-User-ID': USER_ID}
)
if response2.status_code == 200:
# Comparer les réponses (les clés peuvent avoir changé)
print(f"✅ Accès multiples réussis")
print(f" Premier accès: {len(response1.content)} bytes")
print(f" Deuxième accès: {len(response2.content)} bytes")
# Les réponses peuvent être différentes à cause de la rotation des clés
if response1.content != response2.content:
print(f" ✅ Contenu différent détecté (rotation des clés possible)")
else:
print(f" ⚠️ Contenu identique (rotation pas encore déclenchée)")
return True
else:
print(f"❌ Deuxième accès échoué: {response2.status_code}")
else:
print(f"❌ Premier accès échoué: {response1.status_code}")
except Exception as e:
print(f"❌ Erreur lors du test de rotation: {e}")
return False
def main():
"""Fonction principale de test"""
print("🚀 Test de l'API Vault 4NK")
print("🚀 Test de l'API Vault sécurisée")
print("=" * 50)
print(f"URL de base: {BASE_URL}")
print(f"ID utilisateur: {USER_ID}")
print(f"Vérification SSL: {VERIFY_SSL}")
print("=" * 50)
# Désactivation des avertissements SSL pour les certificats auto-signés
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Tests
health_ok = test_health_endpoint()
info_ok = test_info_endpoint()
test_file_endpoint()
test_variable_processing()
tests = [
("Santé de l'API", test_health),
("Informations API", test_info),
("Accès aux fichiers", test_file_access),
("Authentification", test_authentication),
("Obligation HTTPS", test_https_requirement),
("Rotation des clés", test_key_rotation),
]
results = []
for test_name, test_func in tests:
try:
result = test_func()
results.append((test_name, result))
except Exception as e:
print(f"❌ Erreur dans le test '{test_name}': {e}")
results.append((test_name, False))
# Résumé
print("\n" + "=" * 50)
print("📊 Résumé des tests:")
print(f" Santé: {'' if health_ok else ''}")
print(f" Info: {'' if info_ok else ''}")
print(" Fichiers: Voir détails ci-dessus")
print(" Variables: Voir détails ci-dessus")
print("📊 RÉSUMÉ DES TESTS")
print("=" * 50)
if health_ok and info_ok:
print("\n🎉 L'API fonctionne correctement!")
passed = 0
total = len(results)
for test_name, result in results:
status = "✅ PASSÉ" if result else "❌ ÉCHOUÉ"
print(f"{status} - {test_name}")
if result:
passed += 1
print(f"\nRésultat global: {passed}/{total} tests passés")
if passed == total:
print("🎉 Tous les tests sont passés avec succès!")
else:
print("\n⚠️ L'API a des problèmes")
print("⚠️ Certains tests ont échoué. Vérifiez la configuration.")
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@ -1,272 +0,0 @@
#!/usr/bin/env python3
"""
Test de l'API Vault sécurisée avec authentification par clés utilisateur
"""
import requests
import json
import base64
from datetime import datetime
# Configuration
BASE_URL = 'https://127.0.0.1:6666'
USER_ID = 'demo_user_001' # ID utilisateur de test
VERIFY_SSL = False # Certificats auto-signés
def test_health():
"""Test de l'endpoint de santé"""
print("🔍 Test de santé de l'API...")
try:
response = requests.get(
f"{BASE_URL}/health",
verify=VERIFY_SSL,
headers={'X-User-ID': USER_ID}
)
if response.status_code == 200:
health_data = response.json()
print(f"✅ API en bonne santé")
print(f" Service: {health_data.get('service')}")
print(f" Chiffrement: {health_data.get('encryption')}")
print(f" Algorithme: {health_data.get('algorithm')}")
print(f" Authentification: {health_data.get('authentication')}")
print(f" Rotation des clés: {health_data.get('key_rotation')}")
return True
else:
print(f"❌ Erreur de santé: {response.status_code}")
print(f" Réponse: {response.text}")
return False
except Exception as e:
print(f"❌ Erreur de connexion: {e}")
return False
def test_info():
"""Test de l'endpoint d'informations"""
print("\n📋 Test des informations API...")
try:
response = requests.get(
f"{BASE_URL}/info",
verify=VERIFY_SSL,
headers={'X-User-ID': USER_ID}
)
if response.status_code == 200:
info_data = response.json()
print(f"✅ Informations récupérées")
print(f" Nom: {info_data.get('name')}")
print(f" Version: {info_data.get('version')}")
print(f" Domaine: {info_data.get('domain')}")
print(f" Protocole: {info_data.get('protocol')}")
print(f" Authentification: {info_data.get('authentication')}")
return True
else:
print(f"❌ Erreur d'informations: {response.status_code}")
print(f" Réponse: {response.text}")
return False
except Exception as e:
print(f"❌ Erreur de connexion: {e}")
return False
def test_file_access():
"""Test d'accès à un fichier"""
print("\n📁 Test d'accès au fichier...")
try:
response = requests.get(
f"{BASE_URL}/dev/bitcoin/bitcoin.conf",
verify=VERIFY_SSL,
headers={'X-User-ID': USER_ID}
)
if response.status_code == 200:
print(f"✅ Fichier récupéré avec succès")
print(f" Taille: {len(response.content)} bytes")
print(f" Type de contenu: {response.headers.get('Content-Type')}")
print(f" Type de chiffrement: {response.headers.get('X-Encryption-Type')}")
print(f" Algorithme: {response.headers.get('X-Algorithm')}")
print(f" ID utilisateur: {response.headers.get('X-User-ID')}")
print(f" Rotation des clés: {response.headers.get('X-Key-Rotation')}")
# Tentative de décodage des métadonnées
try:
decoded = base64.b64decode(response.content)
if len(decoded) >= 16:
nonce = decoded[:12]
metadata_size = int.from_bytes(decoded[12:16], 'big')
metadata_json = decoded[16:16+metadata_size]
metadata = json.loads(metadata_json.decode('utf-8'))
print(f"\n📊 Métadonnées de chiffrement:")
print(f" Utilisateur: {metadata.get('user_id')}")
print(f" Version de clé: {metadata.get('key_version')}")
print(f" Timestamp: {metadata.get('timestamp')}")
print(f" Algorithme: {metadata.get('algorithm')}")
except Exception as e:
print(f"⚠️ Impossible de décoder les métadonnées: {e}")
return True
else:
print(f"❌ Erreur d'accès au fichier: {response.status_code}")
print(f" Réponse: {response.text}")
return False
except Exception as e:
print(f"❌ Erreur de connexion: {e}")
return False
def test_authentication():
"""Test d'authentification avec différents ID utilisateur"""
print("\n🔐 Test d'authentification...")
test_users = [
('valid_user_001', True),
('invalid@user', False),
('ab', False), # Trop court
('a' * 51, False), # Trop long
('', False), # Vide
]
for user_id, should_succeed in test_users:
print(f"\n Test utilisateur: '{user_id}' (attendu: {'succès' if should_succeed else 'échec'})")
try:
response = requests.get(
f"{BASE_URL}/health",
verify=VERIFY_SSL,
headers={'X-User-ID': user_id} if user_id else {}
)
success = response.status_code == 200
if success == should_succeed:
print(f" ✅ Résultat attendu")
else:
print(f" ❌ Résultat inattendu: {response.status_code}")
except Exception as e:
if not should_succeed:
print(f" ✅ Erreur attendue: {e}")
else:
print(f" ❌ Erreur inattendue: {e}")
def test_https_requirement():
"""Test de l'obligation HTTPS"""
print("\n🔒 Test de l'obligation HTTPS...")
# Tentative d'accès HTTP (devrait échouer)
http_url = BASE_URL.replace('https://', 'http://')
try:
response = requests.get(
f"{http_url}/health",
verify=VERIFY_SSL,
headers={'X-User-ID': USER_ID},
timeout=5
)
print(f"❌ HTTP autorisé (ne devrait pas l'être): {response.status_code}")
except Exception as e:
print(f"✅ HTTP refusé comme attendu: {e}")
def test_key_rotation():
"""Test de la rotation des clés"""
print("\n🔄 Test de la rotation des clés...")
try:
# Premier accès
response1 = requests.get(
f"{BASE_URL}/dev/bitcoin/bitcoin.conf",
verify=VERIFY_SSL,
headers={'X-User-ID': USER_ID}
)
if response1.status_code == 200:
# Attendre un peu et refaire un accès
import time
time.sleep(2)
response2 = requests.get(
f"{BASE_URL}/dev/bitcoin/bitcoin.conf",
verify=VERIFY_SSL,
headers={'X-User-ID': USER_ID}
)
if response2.status_code == 200:
# Comparer les réponses (les clés peuvent avoir changé)
print(f"✅ Accès multiples réussis")
print(f" Premier accès: {len(response1.content)} bytes")
print(f" Deuxième accès: {len(response2.content)} bytes")
# Les réponses peuvent être différentes à cause de la rotation des clés
if response1.content != response2.content:
print(f" ✅ Contenu différent détecté (rotation des clés possible)")
else:
print(f" ⚠️ Contenu identique (rotation pas encore déclenchée)")
return True
else:
print(f"❌ Deuxième accès échoué: {response2.status_code}")
else:
print(f"❌ Premier accès échoué: {response1.status_code}")
except Exception as e:
print(f"❌ Erreur lors du test de rotation: {e}")
return False
def main():
"""Fonction principale de test"""
print("🚀 Test de l'API Vault sécurisée")
print("=" * 50)
print(f"URL de base: {BASE_URL}")
print(f"ID utilisateur: {USER_ID}")
print(f"Vérification SSL: {VERIFY_SSL}")
print("=" * 50)
# Tests
tests = [
("Santé de l'API", test_health),
("Informations API", test_info),
("Accès aux fichiers", test_file_access),
("Authentification", test_authentication),
("Obligation HTTPS", test_https_requirement),
("Rotation des clés", test_key_rotation),
]
results = []
for test_name, test_func in tests:
try:
result = test_func()
results.append((test_name, result))
except Exception as e:
print(f"❌ Erreur dans le test '{test_name}': {e}")
results.append((test_name, False))
# Résumé
print("\n" + "=" * 50)
print("📊 RÉSUMÉ DES TESTS")
print("=" * 50)
passed = 0
total = len(results)
for test_name, result in results:
status = "✅ PASSÉ" if result else "❌ ÉCHOUÉ"
print(f"{status} - {test_name}")
if result:
passed += 1
print(f"\nRésultat global: {passed}/{total} tests passés")
if passed == total:
print("🎉 Tous les tests sont passés avec succès!")
else:
print("⚠️ Certains tests ont échoué. Vérifiez la configuration.")
if __name__ == '__main__':
main()