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 ┌─────────────────┐ ┌─────────────────┐ X-User-ID: your_user_id
│ Client SDK │ ──────────► │ API Vault │ ──► │ Storage │
│ TypeScript │ Port 6666 │ Python Flask │ │ Files │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐
│ Variables │
│ .env │
└─────────────────┘
``` ```
## 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`) ### `GET /info`
- **Port** : 6666 Informations sur l'API avec authentification.
- **Protocole** : HTTPS uniquement
- **Domaine** : vault.4nkweb.com
- **Chiffrement** : ChaCha20-Poly1305 (quantique résistant)
### 2. SDK Client TypeScript (`sdk-client/`) ### `GET /<env>/<file>`
- Client type-safe pour interagir avec l'API Sert un fichier chiffré avec authentification utilisateur.
- Déchiffrement côté client
- Gestion d'erreurs avancée
### 3. Stockage (`storage/`) **Headers requis :**
- Structure : `storage/<env>/<file>` ```
- Variables composites depuis `.env` X-User-ID: your_user_id
- 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
**Exemple :**
```bash ```bash
# Installation des dépendances curl -k -H "X-User-ID: demo_user_001" https://vault.4nkweb.com:6666/dev/bitcoin/bitcoin.conf
pip install -r requirements.txt ```
# 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 ./start_api.sh
# ou directement
# Ou manuellement
source venv_api/bin/activate
python3 api_server.py python3 api_server.py
``` ```
### Utilisation du SDK ### Test
```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
```bash ```bash
# Tester l'API sécurisée
python3 test_api.py python3 test_api.py
``` ```
### Tests du SDK ## 🔐 Sécurité
```bash
cd sdk-client ### Authentification
npm test - **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) L'API est configurée pour fonctionner sur le domaine `vault.4nkweb.com` avec des certificats SSL auto-signés pour la démonstration.
- **Documentation** : [Wiki](https://git.4nkweb.com/4nk/vault/wiki)
- **Email** : support@4nkweb.com
## 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
--- Voir `SECURITY_NOTICE.md` pour les détails sur les limitations de sécurité en mode démonstration.
**Version** : 1.0.0
**API** : vault.4nkweb.com:6666
**Chiffrement** : ChaCha20-Poly1305 (quantique résistant)

View File

@ -1,117 +1,165 @@
#!/usr/bin/env python3 #!/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 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 os
import re import re
import ssl import ssl
import socket import socket
import json
import hashlib
import secrets
import time
from pathlib import Path from pathlib import Path
from typing import Dict, Any from datetime import datetime, timedelta
from flask import Flask, request, Response, jsonify 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.ciphers.aead import ChaCha20Poly1305
from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import x25519 from cryptography.hazmat.primitives.asymmetric import x25519
from cryptography.hazmat.primitives.kdf.hkdf import HKDF from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import base64 import base64
import logging import logging
# Configuration du 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__) logger = logging.getLogger(__name__)
app = Flask(__name__) # Configuration
STORAGE_ROOT = Path('/home/debian/4NK_vault/storage')
ENV_FILE = STORAGE_ROOT / 'dev' / '.env'
class QuantumResistantEncryption: class UserKeyManager:
""" """Gestionnaire de clés par utilisateur et par environnement avec rotation automatique"""
Chiffrement quantique résistant utilisant X25519 + ChaCha20-Poly1305
X25519 est considéré comme résistant aux attaques quantiques à court terme
"""
def __init__(self): def __init__(self, environment: str):
self.private_key = x25519.X25519PrivateKey.generate() self.environment = environment
self.public_key = self.private_key.public_key() 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: def _load_keys_db(self) -> Dict[str, Any]:
"""Chiffre les données avec X25519 + ChaCha20-Poly1305""" """Charge la base de données des clés pour cet environnement"""
# Échange de clés X25519 keys_file = self.keys_dir / 'keys.json'
shared_key = self.private_key.exchange(peer_public_key) 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 def _save_keys_db(self):
derived_key = HKDF( """Sauvegarde la base de données des clés pour cet environnement"""
algorithm=hashes.SHA256(), keys_file = self.keys_dir / 'keys.json'
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"""
try: try:
data = base64.b64decode(encrypted_data) with open(keys_file, 'w') as f:
nonce = data[:12] json.dump(self.keys_db, f, indent=2)
peer_public_bytes = data[12:44] logger.info(f"Clés sauvegardées pour l'environnement {self.environment}")
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)
except Exception as e: except Exception as e:
logger.error(f"Erreur de déchiffrement: {e}") logger.error(f"Erreur lors de la sauvegarde des clés pour {self.environment}: {e}")
raise
class EnvironmentProcessor: def _generate_user_key(self, user_id: str) -> bytes:
"""Traite les variables d'environnement composites""" """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): def _hash_user_id(self, user_id: str) -> str:
self.env_file_path = env_file_path """Hash l'ID utilisateur pour l'utiliser comme clé"""
self.variables = {} return hashlib.sha256(user_id.encode()).hexdigest()
self._load_variables()
def _load_variables(self): def get_or_create_user_key(self, user_id: str) -> bytes:
"""Charge les variables depuis le fichier .env""" """Récupère ou crée une clé pour un utilisateur"""
try: user_hash = self._hash_user_id(user_id)
with open(self.env_file_path, 'r', encoding='utf-8') as f: current_time = datetime.now()
for line in f:
line = line.strip() if user_hash not in self.keys_db:
if line and not line.startswith('#') and '=' in line: # Nouvel utilisateur
key, value = line.split('=', 1) self.keys_db[user_hash] = {
self.variables[key.strip()] = value.strip() 'current_key': base64.b64encode(self._generate_user_key(user_id)).decode(),
except Exception as e: 'previous_key': None,
logger.error(f"Erreur lors du chargement du fichier .env: {e}") '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: 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: if visited is None:
visited = set() visited = set()
@ -120,13 +168,13 @@ class EnvironmentProcessor:
return f"${{{var_name}}}" return f"${{{var_name}}}"
if var_name not in self.variables: 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}}}" return f"${{{var_name}}}"
visited.add(var_name) visited.add(var_name)
value = self.variables[var_name] value = self.variables[var_name]
# Recherche des variables à substituer # Traitement des sous-variables
pattern = r'\$\{([^}]+)\}' pattern = r'\$\{([^}]+)\}'
matches = re.findall(pattern, value) matches = re.findall(pattern, value)
@ -137,212 +185,303 @@ class EnvironmentProcessor:
return value return value
def process_content(self, content: str) -> str: def process_content(self, content: str) -> str:
"""Traite le contenu en remplaçant les variables""" """Traite le contenu en résolvant les variables"""
# Recherche toutes les variables du format ${VAR_NAME}
pattern = r'\$\{([^}]+)\}' pattern = r'\$\{([^}]+)\}'
matches = re.findall(pattern, content) matches = re.findall(pattern, content)
processed_content = content
for var_name in matches: for var_name in matches:
resolved_value = self._resolve_variable(var_name) 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 class SecureVaultAPI:
ENCRYPTION = QuantumResistantEncryption() """API Vault sécurisée avec authentification par clés utilisateur"""
ENV_PROCESSOR = EnvironmentProcessor('/home/debian/4NK_vault/storage/dev/.env')
STORAGE_ROOT = Path('/home/debian/4NK_vault/storage')
@app.route('/<env>/<path:file_path>', methods=['GET']) def __init__(self):
def serve_file(env: str, file_path: str): self.app = Flask(__name__)
""" self.env_processor = EnvProcessor(ENV_FILE)
Sert un fichier depuis storage/<env>/<file_path> self._setup_routes()
Les variables sont remplacées par les valeurs du fichier .env
Le contenu est chiffré avec un algorithme quantique résistant def _setup_routes(self):
""" """Configure les routes de l'API"""
try:
# Construction du chemin du fichier @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 full_path = STORAGE_ROOT / env / file_path
# Vérification de sécurité - empêche l'accès en dehors du répertoire storage # Vérification de sécurité
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
try: try:
with open(full_path, 'r', encoding='utf-8') as f: resolved_path = full_path.resolve()
content = f.read() storage_resolved = STORAGE_ROOT.resolve()
except UnicodeDecodeError:
# Tentative en mode binaire pour les fichiers non-text 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: with open(full_path, 'rb') as f:
content = f.read() 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 # Tentative de décodage UTF-8
processed_content = ENV_PROCESSOR.process_content(content) try:
return content.decode('utf-8')
# Chiffrement du contenu avec clé dynamique sécurisée except UnicodeDecodeError:
# La clé de démonstration est désactivée pour des raisons de sécurité # Fichier binaire, encodage base64
try: return f"BINARY_DATA:{base64.b64encode(content).decode()}"
# 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")
except Exception as e: 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 # 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 def _encrypt_content(self, content: str, key: bytes, user_id: str, environment: str, is_previous: bool = False) -> bytes:
response = Response( """Chiffre le contenu avec une clé spécifique"""
encrypted_content, cipher = ChaCha20Poly1305(key)
mimetype='application/octet-stream', nonce = secrets.token_bytes(12)
headers={ encrypted_content = cipher.encrypt(nonce, content.encode('utf-8'), None)
'Content-Disposition': f'attachment; filename="{file_path}"',
'X-Encryption-Type': 'quantum-resistant',
'X-Algorithm': 'X25519-ChaCha20-Poly1305'
}
)
logger.info(f"Served file: {env}/{file_path}") # Métadonnées de chiffrement
return response metadata = {
'user_id': user_id,
except Exception as e: 'environment': environment,
logger.error(f"Erreur lors du service du fichier {env}/{file_path}: {e}") 'timestamp': datetime.now().isoformat(),
return jsonify({'error': 'Erreur interne du serveur'}), 500 'key_version': 'previous' if is_previous else 'current',
'algorithm': 'ChaCha20-Poly1305'
@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'
} }
})
def create_ssl_context(): # Encodage: nonce + métadonnées + contenu chiffré
"""Crée le contexte SSL pour HTTPS""" metadata_json = json.dumps(metadata).encode('utf-8')
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) full_payload = nonce + len(metadata_json).to_bytes(4, 'big') + metadata_json + encrypted_content
# Configuration SSL sécurisée return base64.b64encode(full_payload)
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 def create_ssl_context(self):
# En production, utiliser des certificats valides """Crée le contexte SSL pour HTTPS"""
try: context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
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
# Génération d'une clé privée RSA # Configuration SSL sécurisée
private_key = rsa.generate_private_key( context.minimum_version = ssl.TLSVersion.TLSv1_2
public_exponent=65537, context.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS')
key_size=2048,
# 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__': if __name__ == '__main__':
# Configuration du serveur api = SecureVaultAPI()
host = '0.0.0.0' api.run()
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
)

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 ## 📚 Documentation disponible
### 🚀 [Guide principal](README.md) ### 🚀 [Guide principal](../README.md)
Vue d'ensemble du projet, architecture et utilisation de base. Vue d'ensemble du projet, architecture sécurisée et utilisation avec authentification.
### 🔧 [Spécification API](api-specification.md) ### 🔧 [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 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) ### 🛡️ [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) ### 🚀 [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 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 ## 🎯 Démarrage rapide
@ -28,112 +41,71 @@ Documentation complète du SDK TypeScript avec exemples et API reference.
```bash ```bash
# 1. Installation des dépendances # 1. Installation des dépendances
cd 4NK_vault
python3 -m venv venv_api
source venv_api/bin/activate
pip install -r requirements.txt pip install -r requirements.txt
# 2. Démarrage de l'API # 2. Démarrage de l'API sécurisée
./start_api.sh ./start_api.sh
# 3. Test de l'API # 3. Test avec authentification
curl -k https://localhost:6666/health curl -k -H "X-User-ID: demo_user_001" https://127.0.0.1:6666/health
# 4. Test du SDK
cd sdk-client
npm install && npm run build
node dist/examples/basic-usage.js
``` ```
### Utilisation basique ### Utilisation avec le SDK TypeScript
```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'); 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... ### Je veux...
- **Comprendre le projet** → [Guide principal](README.md) - **[Comprendre la sécurité](security-model.md)** → Modèle de sécurité complet
- **Utiliser l'API** → [Référence API](api-reference.md) - **[Utiliser l'API](api-reference.md)** → Référence des endpoints sécurisés
- **Déployer le système** → [Guide de déploiement](deployment-guide.md) - **[Développer avec le SDK](sdk-documentation.md)** → SDK TypeScript sécurisé
- **Développer avec le SDK** → [Documentation SDK](sdk-documentation.md) - **[Déployer en production](deployment-guide.md)** → Guide de déploiement sécurisé
- **Comprendre la sécurité** → [Modèle de sécurité](security-model.md) - **[Tester le système](api-specification.md)** → Spécifications techniques
- **Implémenter l'API** → [Spécification API](api-specification.md)
## 🏗️ Architecture du système ## 🔗 Liens utiles
``` - **Repository** : [git.4nkweb.com:4nk/4NK_vault.git](https://git.4nkweb.com:4nk/4NK_vault.git)
┌─────────────────┐ HTTPS ┌─────────────────┐ ┌─────────────────┐ - **Domaine** : vault.4nkweb.com:6666
│ Client SDK │ ──────────► │ API Vault │ ──► │ Storage │ - **Protocole** : HTTPS uniquement
│ TypeScript │ Port 6666 │ Python Flask │ │ Files │ - **Authentification** : Header `X-User-ID` obligatoire
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐
│ 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

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 * 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() { async function basicExample() {
console.log('🔐 Exemple d\'utilisation du client sécurisé Vault'); console.log('🔐 Exemple d\'utilisation du client Vault');
console.log('=' * 60); console.log('=' * 60);
try { 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('\n🔐 Exemple avancé avec gestion d\'erreurs');
console.log('=' * 60); console.log('=' * 60);
@ -145,8 +145,8 @@ async function secureAdvancedExample() {
} }
} }
async function secureErrorHandlingExample() { async function errorHandlingExample() {
console.log('\n🔐 Exemple de gestion d\'erreurs sécurisée'); console.log('\n🔐 Exemple de gestion d\'erreurs');
console.log('=' * 60); console.log('=' * 60);
try { try {
@ -255,9 +255,9 @@ async function main() {
console.log('=' * 80); console.log('=' * 80);
try { try {
await secureBasicExample(); await basicExample();
await secureAdvancedExample(); await advancedExample();
await secureErrorHandlingExample(); await errorHandlingExample();
console.log('\n🎉 Toutes les démonstrations terminées avec succès!'); console.log('\n🎉 Toutes les démonstrations terminées avec succès!');
console.log('\n📝 Points clés du système sécurisé:'); console.log('\n📝 Points clés du système sécurisé:');
@ -279,7 +279,7 @@ if (require.main === module) {
} }
export { export {
secureBasicExample, basicExample,
secureAdvancedExample, advancedExample,
secureErrorHandlingExample 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'; import fetch from 'node-fetch';
// Types TypeScript // Types pour l'API sécurisée
export interface VaultConfig { export interface VaultConfig {
baseUrl: string; baseUrl: string;
verifySsl?: boolean; verifySsl?: boolean;
timeout?: number; timeout?: number;
userId: string; // ID utilisateur obligatoire
} }
export interface VaultFile { export interface VaultFile {
@ -19,6 +14,9 @@ export interface VaultFile {
size: number; size: number;
encrypted: boolean; encrypted: boolean;
algorithm?: string | undefined; algorithm?: string | undefined;
user_id?: string | undefined;
key_version?: string | undefined;
timestamp?: string | undefined;
} }
export interface VaultHealth { export interface VaultHealth {
@ -26,6 +24,9 @@ export interface VaultHealth {
service: string; service: string;
encryption: string; encryption: string;
algorithm: string; algorithm: string;
authentication?: string;
key_rotation?: string;
timestamp?: string;
} }
export interface VaultInfo { export interface VaultInfo {
@ -35,142 +36,73 @@ export interface VaultInfo {
port: number; port: number;
protocol: string; protocol: string;
encryption: string; encryption: string;
endpoints: Record<string, string>; authentication?: string;
key_rotation?: string;
endpoints?: Record<string, string>;
} }
export class VaultApiError extends Error { // Classes d'erreurs personnalisées
constructor( export interface VaultError {
message: string, message: string;
public statusCode?: number, code?: string | undefined;
public endpoint?: string 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); super(message);
this.name = 'VaultApiError'; this.name = 'VaultApiError';
this.code = code;
this.statusCode = statusCode;
} }
} }
export class VaultDecryptionError extends Error { export class VaultDecryptionError extends Error implements VaultError {
constructor(message: string) { public readonly code?: string | undefined;
constructor(message: string, code?: string) {
super(message); super(message);
this.name = 'VaultDecryptionError'; 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 config: VaultConfig;
private _decryptionKey: Buffer; // Conservé pour compatibilité mais non utilisé
constructor(config: VaultConfig, decryptionKey?: string) { constructor(config: VaultConfig) {
this.config = { this.config = {
verifySsl: false, verifySsl: false,
timeout: 30000, timeout: 30000,
...config, ...config,
}; };
// La clé de déchiffrement n'est plus nécessaire avec le nouveau système de clés dynamiques // Validation de l'ID utilisateur
// Elle est conservée pour la compatibilité mais n'est plus utilisée if (!config.userId || config.userId.length < 3 || config.userId.length > 50) {
if (decryptionKey) { throw new Error('ID utilisateur requis (3-50 caractères)');
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);
} }
// Suppression de l'avertissement TypeScript if (!/^[a-zA-Z0-9_-]+$/.test(config.userId)) {
void this._decryptionKey; throw new Error('ID utilisateur invalide - caractères autorisés: a-z, A-Z, 0-9, _, -');
}
/**
* 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'}`
);
} }
} }
@ -178,124 +110,260 @@ export class VaultClient {
* Récupère un fichier depuis l'API Vault * Récupère un fichier depuis l'API Vault
*/ */
async getFile(env: string, filePath: string): Promise<VaultFile> { async getFile(env: string, filePath: string): Promise<VaultFile> {
const endpoint = `/${env}/${filePath}`; const url = `${this.config.baseUrl}/${env}/${filePath}`;
try { try {
const encryptedContent = await this.makeRequest<Buffer>(endpoint); const response = await this._fetchApi(url);
// Vérifier si le contenu est en base64 (chiffré) if (!response.ok) {
let content: string; if (response.status === 401) {
let encrypted = true; throw new VaultAuthenticationError(
'Authentification échouée - vérifiez votre ID utilisateur',
try { 'AUTH_FAILED',
content = this.decryptContent(encryptedContent); response.status
} 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.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 { return {
content, content: decryptedContent,
filename: filePath.split('/').pop() || filePath, filename: filePath.split('/').pop() || filePath,
size: content.length, size: decryptedContent.length,
encrypted, encrypted: true,
algorithm: encrypted ? 'ChaCha20-Poly1305' : undefined, algorithm,
user_id,
key_version: keyRotation,
timestamp: new Date().toISOString()
}; };
} catch (error) { } catch (error) {
if (error instanceof VaultApiError && error.statusCode === 404) { if (error instanceof VaultApiError || error instanceof VaultAuthenticationError) {
throw new VaultApiError(`Fichier non trouvé: ${env}/${filePath}`, 404, endpoint); throw error;
} }
throw error; throw new VaultApiError(
} `Erreur lors de la récupération du fichier: ${error instanceof Error ? error.message : 'Inconnue'}`
} );
/**
* 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;
} }
} }
/** /**
* Récupère plusieurs fichiers en parallèle * Récupère plusieurs fichiers en parallèle
*/ */
async getFiles(requests: Array<{ env: string; filePath: string }>): Promise<VaultFile[]> { async getFiles(env: string, filePaths: string[]): Promise<VaultFile[]> {
const promises = requests.map(req => this.getFile(req.env, req.filePath)); const promises = filePaths.map(filePath => this.getFile(env, filePath));
return await Promise.all(promises); 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[]> { async searchFiles(_env: string, _pattern: string): Promise<VaultFile[]> {
// Note: Cette méthode nécessiterait un endpoint de recherche côté serveur // Cette fonctionnalité nécessiterait une implémentation côté serveur
// Pour l'instant, retourne une liste vide // Pour l'instant, retour d'une erreur explicative
console.warn('La recherche de fichiers n\'est pas encore implémentée côté serveur'); throw new VaultApiError(
return []; '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( export function createSecureVaultClient(baseUrl: string, userId: string): SecureVaultClient {
baseUrl: string = 'https://vault.4nkweb.com:6666', return new SecureVaultClient({
decryptionKey: string = 'quantum_resistant_demo_key_32_bytes!' baseUrl,
): VaultClient { userId,
return new VaultClient({ baseUrl }, decryptionKey); 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 { export function createSecureVaultClientWithConfig(config: VaultConfig): SecureVaultClient {
/** return new SecureVaultClient(config);
* 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 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 #!/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 set -e
# Port 6666, domaine vault.4nkweb.com
echo "🚀 Démarrage de l'API Vault 4NK..." echo "🚀 Démarrage de l'API Vault sécurisée"
echo "📍 Domaine: vault.4nkweb.com" echo "======================================"
echo "🔌 Port: 6666"
echo "🔐 Chiffrement: Quantique résistant (X25519 + ChaCha20-Poly1305)"
echo "📁 Répertoire de stockage: /home/debian/4NK_vault/storage"
echo ""
# Vérification des dépendances Python # Vérification des prérequis
echo "🔍 Vérification des dépendances..." echo "🔍 Vérification des prérequis..."
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 du répertoire de stockage if ! command -v python3 &> /dev/null; then
if [ ! -d "/home/debian/4NK_vault/storage" ]; then echo "❌ Python3 n'est pas installé"
echo "❌ Erreur: Le répertoire de stockage n'existe pas"
exit 1 exit 1
fi fi
# Vérification du fichier .env if ! command -v pip3 &> /dev/null; then
if [ ! -f "/home/debian/4NK_vault/storage/dev/.env" ]; then echo "❌ pip3 n'est pas installé"
echo "⚠️ Avertissement: Fichier .env non trouvé dans storage/dev/" exit 1
fi fi
echo "✅ Démarrage de l'API..." echo "✅ Python3 et pip3 disponibles"
echo "🌐 URL: https://vault.4nkweb.com:6666"
echo "📋 Endpoints disponibles:" # Création de l'environnement virtuel si nécessaire
echo " GET /<env>/<file> - Sert un fichier chiffré" if [ ! -d "venv_secure_api" ]; then
echo " GET /health - Contrôle de santé" echo "📦 Création de l'environnement virtuel..."
echo " GET /info - Informations API" python3 -m venv venv_secure_api
echo "" 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 # 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 #!/usr/bin/env python3
""" """
Script de test pour l'API Vault Test de l'API Vault sécurisée avec authentification par clés utilisateur
Teste les endpoints et le chiffrement quantique résistant
""" """
import requests import requests
import ssl
import json import json
from pathlib import Path import base64
from datetime import datetime
# Configuration # Configuration
API_BASE_URL = "https://vault.4nkweb.com:6666" BASE_URL = 'https://127.0.0.1:6666'
VERIFY_SSL = False # Désactivé pour les certificats auto-signés 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: 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: if response.status_code == 200:
data = response.json() health_data = response.json()
print(f"✅ Santé: {data['status']}") print(f"✅ API en bonne santé")
print(f"🔐 Chiffrement: {data['encryption']}") print(f" Service: {health_data.get('service')}")
print(f"🔧 Algorithme: {data['algorithm']}") 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 return True
else: else:
print(f"❌ Erreur santé: {response.status_code}") print(f"❌ Erreur de santé: {response.status_code}")
print(f" Réponse: {response.text}")
return False return False
except Exception as e: except Exception as e:
print(f"❌ Erreur connexion santé: {e}") print(f"❌ Erreur de connexion: {e}")
return False return False
def test_info_endpoint(): def test_info():
"""Test du endpoint d'information""" """Test de l'endpoint d'informations"""
print("\n🔍 Test du endpoint /info...") print("\n📋 Test des informations API...")
try: 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: if response.status_code == 200:
data = response.json() info_data = response.json()
print(f"✅ API: {data['name']} v{data['version']}") print(f"✅ Informations récupérées")
print(f"🌐 Domaine: {data['domain']}") print(f" Nom: {info_data.get('name')}")
print(f"🔌 Port: {data['port']}") print(f" Version: {info_data.get('version')}")
print(f"📡 Protocole: {data['protocol']}") print(f" Domaine: {info_data.get('domain')}")
print("📋 Endpoints:") print(f" Protocole: {info_data.get('protocol')}")
for endpoint, description in data['endpoints'].items(): print(f" Authentification: {info_data.get('authentication')}")
print(f" {endpoint}: {description}")
return True return True
else: else:
print(f"❌ Erreur info: {response.status_code}") print(f"❌ Erreur d'informations: {response.status_code}")
print(f" Réponse: {response.text}")
return False return False
except Exception as e: except Exception as e:
print(f"❌ Erreur connexion info: {e}") print(f"❌ Erreur de connexion: {e}")
return False return False
def test_file_endpoint(): def test_file_access():
"""Test du endpoint de fichier""" """Test d'accès à un fichier"""
print("\n🔍 Test du endpoint de fichier...") print("\n📁 Test d'accès au fichier...")
# Test avec un fichier existant try:
test_files = [ response = requests.get(
"dev/bitcoin/bitcoin.conf", f"{BASE_URL}/dev/bitcoin/bitcoin.conf",
"dev/tor/torrc", verify=VERIFY_SSL,
"dev/sdk_relay/sdk_relay.conf" 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: for user_id, should_succeed in test_users:
print(f"\n📁 Test du fichier: {file_path}") print(f"\n Test utilisateur: '{user_id}' (attendu: {'succès' if should_succeed else 'échec'})")
try: 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: success = response.status_code == 200
print(f"✅ Fichier servi avec succès") if success == should_succeed:
print(f"📦 Taille: {len(response.content)} bytes") print(f" ✅ Résultat attendu")
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}")
else: else:
print(f"❌ Erreur fichier: {response.status_code}") print(f" ❌ Résultat inattendu: {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')}")
except Exception as e: 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(): def test_https_requirement():
"""Test du traitement des variables d'environnement""" """Test de l'obligation HTTPS"""
print("\n🔍 Test du traitement des variables...") print("\n🔒 Test de l'obligation HTTPS...")
# Création d'un fichier de test avec des variables # Tentative d'accès HTTP (devrait échouer)
test_content = """ http_url = BASE_URL.replace('https://', 'http://')
# 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
"""
test_file_path = Path("/home/debian/4NK_vault/storage/dev/test_variables.conf")
try: try:
# Création du fichier de test response = requests.get(
with open(test_file_path, 'w') as f: f"{http_url}/health",
f.write(test_content) verify=VERIFY_SSL,
headers={'X-User-ID': USER_ID},
print(f"📝 Fichier de test créé: {test_file_path}") timeout=5
)
# Test de l'API print(f"❌ HTTP autorisé (ne devrait pas l'être): {response.status_code}")
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é")
except Exception as e: except Exception as e:
print(f"❌ Erreur test variables: {e}") print(f"✅ HTTP refusé comme attendu: {e}")
if test_file_path.exists():
test_file_path.unlink() 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(): def main():
"""Fonction principale de test""" """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) print("=" * 50)
# Désactivation des avertissements SSL pour les certificats auto-signés
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Tests # Tests
health_ok = test_health_endpoint() tests = [
info_ok = test_info_endpoint() ("Santé de l'API", test_health),
test_file_endpoint() ("Informations API", test_info),
test_variable_processing() ("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("\n" + "=" * 50)
print("📊 Résumé des tests:") print("📊 RÉSUMÉ DES TESTS")
print(f" Santé: {'' if health_ok else ''}") print("=" * 50)
print(f" Info: {'' if info_ok else ''}")
print(" Fichiers: Voir détails ci-dessus")
print(" Variables: Voir détails ci-dessus")
if health_ok and info_ok: passed = 0
print("\n🎉 L'API fonctionne correctement!") 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: 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() 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()