Architecture Nitrokey Distribuée - Clés Physiques Distantes

Vue d'Ensemble de l'Architecture Corrigée

🎯 Contraintes Techniques Réelles

  • Nitrokeys sur postes clients (non connectées au serveur PKIaaS)
  • Accès PKCS#11 local via OpenSC sur chaque poste client
  • Communication sécurisée entre clients et serveur pour parties Shamir
  • Interface web pour orchestration et génération des parts
  • Génération distribuée via interface web, déploiement sur sites distants

1. Architecture Système Distribuée

                    ┌──────────────────────────────────┐
                              PKIaaS Server           
                                                      
                      ┌─────────────────────────────┐ 
                         Shamir Generator Web UI    
                         - Génération parts CA      
                         - Attribution Nitrokeys    
                         - Packages déploiement     
                      └─────────────────────────────┘ 
                                                      
                      ┌─────────────────────────────┐ 
                        Signing Orchestrator        
                         - Sessions de signature    
                         - Collecte parts Shamir    
                         - Reconstruction clé       
                      └─────────────────────────────┘ 
                    └──────────────┬───────────────────┘
                                   
                          HTTPS + WebSockets
                                   
        ┌──────────────────────────┼──────────────────────────┐
                                                            
                                                            
┌───────▼────────┐        ┌────────▼────────┐        ┌───────▼────────┐
   Client A                Client B                 Client N     
                                                                 
 ┌────────────┐          ┌─────────────┐          ┌────────────┐ 
  Nitrokey              Nitrokey               Nitrokey    
  HSM NK001             HSM NK002              HSM NK00N   
                                                           
  Part 1/N              Part 2/N               Part N/N    
 └────────────┘          └─────────────┘          └────────────┘ 
                                                                 
 ┌────────────┐          ┌─────────────┐          ┌────────────┐ 
 Client Agent          Client Agent           Client Agent 
 PKCS#11     │ │        │ │PKCS#11      │ │        │ │PKCS#11     │ │
 OpenSC                OpenSC                 OpenSC       
 └────────────┘          └─────────────┘          └────────────┘ 
└────────────────┘        └─────────────────┘        └────────────────┘

      Site Paris              Site Lyon                Site Remote

2. Composants de l'Architecture

🌐 2.1 Interface Web de Génération Shamir

Contrôleur Principal

<?php

namespace App\Http\Controllers;

use App\Services\ShamirSecretService;
use App\Services\NitrokeyDeploymentService;
use Illuminate\Http\Request;

class NitrokeyDistributedCAController extends Controller
{
    private ShamirSecretService $shamirService;
    private NitrokeyDeploymentService $deploymentService;

    public function __construct(
        ShamirSecretService $shamirService,
        NitrokeyDeploymentService $deploymentService
    ) {
        $this->shamirService = $shamirService;
        $this->deploymentService = $deploymentService;
    }

    /**
     * Interface de génération CA distribuée
     */
    public function generateDistributedCA(Request $request)
    {
        $validated = $request->validate([
            'ca_name' => 'required|string|max:255',
            'organization' => 'required|string|max:255',
            'country' => 'required|string|size:2',
            'key_size' => 'required|integer|in:2048,3072,4096',
            'validity_years' => 'required|integer|min:1|max:30',
            'threshold' => 'required|integer|min:2|max:15',
            'total_shares' => 'required|integer|min:3|max:20',
            'nitrokey_assignments' => 'required|array',
            'nitrokey_assignments.*.nitrokey_id' => 'required|string|regex:/^NK\d{3}$/',
            'nitrokey_assignments.*.holder_name' => 'required|string|max:255',
            'nitrokey_assignments.*.holder_email' => 'required|email',
            'nitrokey_assignments.*.location' => 'required|string|max:255',
        ]);

        // 1. Générer CA Master Private Key
        $masterKeyPair = $this->generateCAKeyPair($validated['key_size']);

        // 2. Créer le certificat CA auto-signé
        $caCertificate = $this->createSelfSignedCACertificate(
            $masterKeyPair,
            $validated
        );

        // 3. Appliquer Shamir Secret Sharing sur la clé privée
        $shamirShares = $this->shamirService->splitSecret(
            $masterKeyPair['private_key'],
            $validated['threshold'],
            $validated['total_shares']
        );

        // 4. Créer les packages de déploiement
        $deploymentPackages = [];
        foreach ($shamirShares as $index => $share) {
            $nitrokeyAssignment = $validated['nitrokey_assignments'][$index];

            $deploymentPackages[] = $this->deploymentService->createDeploymentPackage([
                'nitrokey_id' => $nitrokeyAssignment['nitrokey_id'],
                'share_data' => $share,
                'ca_metadata' => [
                    'ca_id' => $caCertificate['ca_id'],
                    'ca_name' => $validated['ca_name'],
                    'threshold' => $validated['threshold'],
                    'total_shares' => $validated['total_shares'],
                ],
                'holder_info' => $nitrokeyAssignment
            ]);
        }

        // 5. Sauvegarder CA en base (SANS la clé privée maître)
        $ca = $this->saveCertificateAuthority($caCertificate, $validated);

        // 6. Sauvegarder métadonnées Shamir
        $this->saveShamirMetadata($ca->id, $validated, $deploymentPackages);

        // 7. EFFACEMENT SÉCURISÉ de la clé maître
        sodium_memzero($masterKeyPair['private_key']);
        unset($masterKeyPair);

        return response()->json([
            'success' => true,
            'ca_id' => $ca->id,
            'deployment_packages' => $deploymentPackages,
            'next_steps' => [
                'download_packages' => 'Télécharger les packages de déploiement',
                'distribute_nitrokeys' => 'Distribuer physiquement les Nitrokeys',
                'install_client_agents' => 'Installer agents clients sur postes',
                'test_signing' => 'Tester une première signature distribuée'
            ]
        ]);
    }

    /**
     * API pour télécharger package de déploiement spécifique
     */
    public function downloadDeploymentPackage(string $caId, string $nitrokeyId)
    {
        $package = $this->deploymentService->getDeploymentPackage($caId, $nitrokeyId);

        if (!$package) {
            abort(404, 'Package de déploiement non trouvé');
        }

        // Générer archive sécurisée
        $archive = $this->deploymentService->generateSecureArchive($package);

        return response()->download($archive['path'], $archive['filename'])
            ->deleteFileAfterSend();
    }
}

Service de Génération Shamir

<?php

namespace App\Services;

class ShamirSecretService
{
    /**
     * Divise un secret en N parts avec seuil M
     */
    public function splitSecret(string $secret, int $threshold, int $totalShares): array
    {
        // Convertir la clé privée en nombre pour Shamir
        $secretNumber = $this->convertSecretToNumber($secret);

        // Générer polynôme aléatoire de degré (threshold - 1)
        $polynomial = $this->generatePolynomial($secretNumber, $threshold);

        // Générer les parts
        $shares = [];
        for ($x = 1; $x <= $totalShares; $x++) {
            $y = $this->evaluatePolynomial($polynomial, $x);
            $shares[] = [
                'x' => $x,
                'y' => $y,
                'threshold' => $threshold,
                'total_shares' => $totalShares,
                'share_index' => $x - 1
            ];
        }

        return $shares;
    }

    /**
     * Reconstruit le secret à partir de M parts
     */
    public function reconstructSecret(array $shares, int $threshold): string
    {
        if (count($shares) < $threshold) {
            throw new \InvalidArgumentException(
                "Besoin d'au moins {$threshold} parts, seulement " . count($shares) . " fournies"
            );
        }

        // Prendre seulement le nombre requis de parts
        $selectedShares = array_slice($shares, 0, $threshold);

        // Appliquer interpolation de Lagrange
        $secretNumber = $this->lagrangeInterpolation($selectedShares);

        // Reconvertir en clé privée
        return $this->convertNumberToSecret($secretNumber);
    }

    private function convertSecretToNumber(string $secret): \GMP
    {
        // Convertir la clé privée PEM en nombre GMP
        $binaryData = hash('sha256', $secret, true);
        return gmp_import($binaryData);
    }

    private function convertNumberToSecret(\GMP $number): string
    {
        // Cette fonction nécessite une implémentation plus sophistiquée
        // pour reconvertir correctement le nombre en clé privée
        throw new \Exception("Implémentation complète requise pour production");
    }

    private function generatePolynomial(\GMP $secret, int $threshold): array
    {
        $polynomial = [$secret]; // Terme constant = secret

        // Générer coefficients aléatoires pour les autres termes
        for ($i = 1; $i < $threshold; $i++) {
            $coefficient = gmp_random_bits(256);
            $polynomial[] = $coefficient;
        }

        return $polynomial;
    }

    private function evaluatePolynomial(array $polynomial, int $x): \GMP
    {
        $result = gmp_init(0);
        $xPower = gmp_init(1);

        foreach ($polynomial as $coefficient) {
            $term = gmp_mul($coefficient, $xPower);
            $result = gmp_add($result, $term);
            $xPower = gmp_mul($xPower, $x);
        }

        return $result;
    }

    private function lagrangeInterpolation(array $shares): \GMP
    {
        $result = gmp_init(0);

        foreach ($shares as $i => $share) {
            $numerator = gmp_init(1);
            $denominator = gmp_init(1);

            foreach ($shares as $j => $otherShare) {
                if ($i !== $j) {
                    $numerator = gmp_mul($numerator, gmp_neg($otherShare['x']));
                    $denominator = gmp_mul($denominator, gmp_sub($share['x'], $otherShare['x']));
                }
            }

            $lagrangeCoeff = gmp_div($numerator, $denominator);
            $term = gmp_mul($share['y'], $lagrangeCoeff);
            $result = gmp_add($result, $term);
        }

        return $result;
    }
}

💾 2.2 Service de Déploiement Nitrokey

<?php

namespace App\Services;

use Illuminate\Support\Facades\Storage;
use ZipArchive;

class NitrokeyDeploymentService
{
    /**
     * Créer package de déploiement pour une Nitrokey
     */
    public function createDeploymentPackage(array $config): array
    {
        $package = [
            'nitrokey_id' => $config['nitrokey_id'],
            'deployment_id' => $this->generateDeploymentId(),
            'ca_metadata' => $config['ca_metadata'],
            'holder_info' => $config['holder_info'],
            'share_data' => $this->encryptShareForTransport($config['share_data']),
            'deployment_scripts' => $this->generateDeploymentScripts($config),
            'verification_data' => $this->generateVerificationData($config),
            'created_at' => now()->toISOString()
        ];

        return $package;
    }

    /**
     * Générer archive sécurisée pour déploiement
     */
    public function generateSecureArchive(array $package): array
    {
        $tempDir = storage_path('temp/deployments/' . $package['deployment_id']);
        if (!is_dir($tempDir)) {
            mkdir($tempDir, 0700, true);
        }

        // 1. Script de déploiement principal
        $deploymentScript = $this->generateDeploymentScript($package);
        file_put_contents($tempDir . '/deploy.sh', $deploymentScript);
        chmod($tempDir . '/deploy.sh', 0700);

        // 2. Agent client (binaire ou source)
        $this->copyClientAgent($tempDir);

        // 3. Configuration chiffrée
        $configFile = $this->generateEncryptedConfig($package);
        file_put_contents($tempDir . '/config.enc', $configFile);

        // 4. Instructions de déploiement
        $instructions = $this->generateDeploymentInstructions($package);
        file_put_contents($tempDir . '/INSTRUCTIONS.md', $instructions);

        // 5. Code QR pour vérification
        $qrCode = $this->generateVerificationQR($package);
        file_put_contents($tempDir . '/verification.png', $qrCode);

        // 6. Créer archive ZIP protégée par mot de passe
        $archivePath = $tempDir . '.zip';
        $password = $this->generateSecurePassword();

        $zip = new ZipArchive();
        if ($zip->open($archivePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) === TRUE) {
            $zip->setPassword($password);
            $zip->setEncryptionName('deploy.sh', ZipArchive::EM_AES_256);
            $zip->addFile($tempDir . '/deploy.sh', 'deploy.sh');

            // Ajouter autres fichiers...
            $files = ['config.enc', 'INSTRUCTIONS.md', 'verification.png'];
            foreach ($files as $file) {
                $zip->setEncryptionName($file, ZipArchive::EM_AES_256);
                $zip->addFile($tempDir . '/' . $file, $file);
            }

            $zip->close();
        }

        // 7. Nettoyer répertoire temporaire
        $this->cleanupTempDirectory($tempDir);

        return [
            'path' => $archivePath,
            'filename' => "nitrokey-{$package['nitrokey_id']}-deployment.zip",
            'password' => $password,
            'holder_email' => $package['holder_info']['holder_email']
        ];
    }

    /**
     * Générer script de déploiement spécifique à la Nitrokey
     */
    private function generateDeploymentScript(array $package): string
    {
        return <<<BASH
#!/bin/bash
# Script de déploiement Nitrokey {$package['nitrokey_id']}
# CA: {$package['ca_metadata']['ca_name']}
# Responsable: {$package['holder_info']['holder_name']}

set -e

NITROKEY_ID="{$package['nitrokey_id']}"
CA_ID="{$package['ca_metadata']['ca_id']}"

echo "=== Déploiement Nitrokey HSM \$NITROKEY_ID ==="

# Vérifier prérequis
check_prerequisites() {
    echo "Vérification des prérequis..."

    # OpenSC installé
    if ! command -v pkcs11-tool &> /dev/null; then
        echo "ERREUR: OpenSC non installé. Installer avec:"
        echo "  Ubuntu/Debian: apt-get install opensc"
        echo "  RHEL/CentOS: yum install opensc"
        exit 1
    fi

    # Nitrokey détectée
    if ! pkcs11-tool --module /usr/lib/opensc-pkcs11.so --list-slots | grep -q "Nitrokey"; then
        echo "ERREUR: Nitrokey HSM non détectée"
        echo "Vérifier que la Nitrokey est connectée et initialisée"
        exit 1
    fi

    echo "✓ Prérequis validés"
}

# Déployer la part Shamir
deploy_shamir_share() {
    echo "Déploiement de la part Shamir..."

    # Demander PIN utilisateur
    echo -n "PIN utilisateur Nitrokey: "
    read -s USER_PIN
    echo

    # Décrypter configuration
    openssl enc -aes-256-cbc -d -in config.enc -out config.json -k "\$USER_PIN"

    if [ \$? -ne 0 ]; then
        echo "ERREUR: PIN incorrect ou configuration corrompue"
        exit 1
    fi

    # Stocker données sur Nitrokey via PKCS#11
    pkcs11-tool --module /usr/lib/opensc-pkcs11.so \\
        --login --pin "\$USER_PIN" \\
        --write-object config.json \\
        --type data \\
        --id "CA\$CA_ID" \\
        --label "ShamirShare-\$NITROKEY_ID"

    if [ \$? -eq 0 ]; then
        echo "✓ Part Shamir déployée avec succès"
        rm -f config.json  # Nettoyage
    else
        echo "ERREUR: Échec du déploiement"
        exit 1
    fi
}

# Installation agent client
install_client_agent() {
    echo "Installation de l'agent client..."

    # Copier binaire agent
    sudo cp nitrokey-client-agent /usr/local/bin/
    sudo chmod +x /usr/local/bin/nitrokey-client-agent

    # Créer service systemd
    sudo tee /etc/systemd/system/nitrokey-agent.service > /dev/null <<EOF
[Unit]
Description=Nitrokey PKI Client Agent
After=network.target

[Service]
Type=simple
User=\$USER
ExecStart=/usr/local/bin/nitrokey-client-agent --config /home/\$USER/.nitrokey/config.json
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

    sudo systemctl daemon-reload
    sudo systemctl enable nitrokey-agent

    echo "✓ Agent client installé"
}

# Exécution principale
main() {
    check_prerequisites
    deploy_shamir_share
    install_client_agent

    echo
    echo "=== Déploiement terminé ==="
    echo "Nitrokey \$NITROKEY_ID prête pour signature distribuée"
    echo "Agent client: sudo systemctl start nitrokey-agent"
}

main
BASH;
    }
}

🖥️ 2.3 Agent Client Nitrokey

Application Client Standalone

<?php
// /usr/local/bin/nitrokey-client-agent (PHP CLI app)

namespace NitrokeyAgent;

class ClientAgent
{
    private $config;
    private $pkcs11Module = '/usr/lib/opensc-pkcs11.so';
    private $serverEndpoint;
    private $nitrokeyId;

    public function __construct(string $configPath)
    {
        $this->config = json_decode(file_get_contents($configPath), true);
        $this->serverEndpoint = $this->config['server_endpoint'];
        $this->nitrokeyId = $this->config['nitrokey_id'];
    }

    /**
     * Démarrer agent en mode daemon
     */
    public function startDaemon(): void
    {
        echo "Démarrage Nitrokey Agent {$this->nitrokeyId}\n";

        // Vérifier Nitrokey disponible
        if (!$this->checkNitrokeyAvailable()) {
            throw new \Exception("Nitrokey HSM non disponible");
        }

        // WebSocket ou polling pour écouter demandes serveur
        $this->listenForSigningRequests();
    }

    /**
     * Participer à une session de signature
     */
    public function participateInSigning(array $signingSession): bool
    {
        echo "Participation à signature session: {$signingSession['session_id']}\n";

        try {
            // 1. Vérifier autorisation
            if (!$this->isAuthorizedForSigning($signingSession)) {
                throw new \Exception("Non autorisé pour cette session");
            }

            // 2. Demander PIN utilisateur
            $pin = $this->promptUserForPIN($signingSession);

            // 3. Connecter à Nitrokey avec PIN
            $this->authenticateWithNitrokey($pin);

            // 4. Récupérer part Shamir
            $shamirShare = $this->retrieveShamirShare($signingSession['ca_id']);

            // 5. Chiffrer et transmettre au serveur
            return $this->transmitShamirShare($signingSession, $shamirShare);

        } catch (\Exception $e) {
            echo "Erreur participation signature: " . $e->getMessage() . "\n";
            return false;
        }
    }

    private function checkNitrokeyAvailable(): bool
    {
        $cmd = "pkcs11-tool --module {$this->pkcs11Module} --list-slots";
        $output = shell_exec($cmd);

        return strpos($output, 'Nitrokey') !== false;
    }

    private function promptUserForPIN(array $session): string
    {
        // Interface graphique ou console
        echo "\n=== Demande de signature PKI ===\n";
        echo "CA: {$session['ca_name']}\n";
        echo "Demandeur: {$session['requester']}\n";
        echo "Domaine: {$session['domain']}\n";
        echo "===================================\n";

        echo -n "PIN Nitrokey pour autoriser signature: ";
        system('stty -echo');
        $pin = trim(fgets(STDIN));
        system('stty echo');
        echo "\n";

        return $pin;
    }

    private function authenticateWithNitrokey(string $pin): void
    {
        // Test authentification
        $cmd = "pkcs11-tool --module {$this->pkcs11Module} --login --pin " . escapeshellarg($pin) . " --list-objects";
        $output = shell_exec($cmd . ' 2>&1');

        if (strpos($output, 'error') !== false || strpos($output, 'failed') !== false) {
            throw new \Exception("Authentification Nitrokey échouée");
        }
    }

    private function retrieveShamirShare(string $caId): array
    {
        $objectLabel = "ShamirShare-{$this->nitrokeyId}";
        $objectId = "CA{$caId}";

        $cmd = "pkcs11-tool --module {$this->pkcs11Module} " .
               "--login --pin {$this->getStoredPin()} " .
               "--read-object --id {$objectId} --type data";

        $shamirData = shell_exec($cmd);

        if (empty($shamirData)) {
            throw new \Exception("Part Shamir non trouvée sur Nitrokey");
        }

        return json_decode($shamirData, true);
    }

    private function transmitShamirShare(array $session, array $shamirShare): bool
    {
        // Chiffrer avec clé session
        $encryptedShare = $this->encryptShareForTransmission($shamirShare, $session['session_key']);

        // HTTP POST vers serveur
        $curl = curl_init();
        curl_setopt_array($curl, [
            CURLOPT_URL => "{$this->serverEndpoint}/api/signing-sessions/{$session['session_id']}/contribute",
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => json_encode([
                'nitrokey_id' => $this->nitrokeyId,
                'encrypted_share' => $encryptedShare,
                'signature' => $this->signContribution($encryptedShare)
            ]),
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'Authorization: Bearer ' . $this->config['api_token']
            ],
            CURLOPT_RETURNTRANSFER => true,
        ]);

        $response = curl_exec($curl);
        $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        curl_close($curl);

        return $httpCode === 200;
    }

    private function listenForSigningRequests(): void
    {
        // Implémentation WebSocket ou polling HTTP
        while (true) {
            $requests = $this->checkForSigningRequests();

            foreach ($requests as $request) {
                $this->participateInSigning($request);
            }

            sleep(5); // Polling every 5 seconds
        }
    }
}

// Point d'entrée CLI
if ($argc < 2) {
    echo "Usage: nitrokey-client-agent --config /path/to/config.json\n";
    exit(1);
}

$configPath = null;
for ($i = 1; $i < $argc; $i++) {
    if ($argv[$i] === '--config' && isset($argv[$i + 1])) {
        $configPath = $argv[$i + 1];
        break;
    }
}

if (!$configPath || !file_exists($configPath)) {
    echo "Fichier de configuration non trouvé: $configPath\n";
    exit(1);
}

try {
    $agent = new ClientAgent($configPath);
    $agent->startDaemon();
} catch (\Exception $e) {
    echo "Erreur agent: " . $e->getMessage() . "\n";
    exit(1);
}

3. Protocole de Signature Distribuée

🔄 Séquence Complète de Signature

sequenceDiagram
    participant Admin as Administrateur PKI
    participant Server as PKIaaS Server
    participant Client1 as Client A (NK001)
    participant Client2 as Client B (NK002)
    participant Client3 as Client N (NK00N)

    Admin->>Server: Demande signature CSR
    Server->>Server: Créer session signature temporaire
    Server->>Client1: Notification signature requise
    Server->>Client2: Notification signature requise
    Server->>Client3: Notification signature requise

    Client1->>Client1: Demander PIN utilisateur
    Client1->>Client1: Authentifier Nitrokey
    Client1->>Client1: Récupérer part Shamir
    Client1->>Server: Transmettre part chiffrée

    Client2->>Client2: Demander PIN utilisateur
    Client2->>Client2: Authentifier Nitrokey
    Client2->>Client2: Récupérer part Shamir
    Client2->>Server: Transmettre part chiffrée

    Client3->>Client3: Demander PIN utilisateur
    Client3->>Client3: Authentifier Nitrokey
    Client3->>Client3: Récupérer part Shamir
    Client3->>Server: Transmettre part chiffrée

    Server->>Server: Vérifier seuil atteint (3/5)
    Server->>Server: Reconstituer clé privée
    Server->>Server: Signer certificat
    Server->>Server: Effacer clé reconstruite
    Server->>Admin: Retourner certificat signé

4. Sécurité de l'Architecture

🔐 Mesures de Sécurité Implémentées

4.1 Transport des Parts Shamir

class SecureShareTransport
{
    public function encryptShareForTransmission(array $share, string $sessionKey): string
    {
        // Utilisation ChaCha20-Poly1305 pour transport
        $nonce = random_bytes(SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES);
        $additionalData = hash('sha256', json_encode([
            'timestamp' => time(),
            'nitrokey_id' => $this->nitrokeyId,
            'session_id' => $this->currentSessionId
        ]));

        $encrypted = sodium_crypto_aead_chacha20poly1305_encrypt(
            json_encode($share),
            $additionalData,
            $nonce,
            $sessionKey
        );

        return base64_encode($nonce . $encrypted);
    }

    public function validateShareContribution(string $encryptedShare, string $signature): bool
    {
        // Vérifier signature avec certificat Nitrokey
        $nitrokeyCert = $this->getNitrokeyCertificate($this->nitrokeyId);
        return openssl_verify($encryptedShare, base64_decode($signature), $nitrokeyCert, OPENSSL_ALGO_SHA256);
    }
}

4.2 Gestion des Sessions Temporaires

class SigningSessionManager
{
    private $sessionTTL = 300; // 5 minutes

    public function createSigningSession(string $caId, string $csr): string
    {
        $sessionId = bin2hex(random_bytes(16));
        $sessionKey = random_bytes(32);

        // Stocker en Redis avec expiration automatique
        Redis::setex("signing_session:{$sessionId}", $this->sessionTTL, json_encode([
            'ca_id' => $caId,
            'csr' => $csr,
            'session_key' => base64_encode($sessionKey),
            'required_nitrokeys' => $this->getRequiredNitrokeys($caId),
            'received_shares' => [],
            'status' => 'waiting_contributions',
            'created_at' => time(),
            'expires_at' => time() + $this->sessionTTL
        ]));

        return $sessionId;
    }

    public function cleanupExpiredSessions(): void
    {
        // Automatic cleanup par expiration Redis
        // + nettoyage explicite des données sensibles
        $expiredSessions = Redis::keys('signing_session:*');

        foreach ($expiredSessions as $sessionKey) {
            $sessionData = Redis::get($sessionKey);
            if ($sessionData) {
                $session = json_decode($sessionData, true);
                if ($session['expires_at'] < time()) {
                    // Effacement sécurisé des parts reçues
                    if (isset($session['received_shares'])) {
                        foreach ($session['received_shares'] as &$share) {
                            sodium_memzero($share);
                        }
                    }
                    Redis::del($sessionKey);
                }
            }
        }
    }
}

5. Configuration et Déploiement

⚙️ Configuration .env Mise à Jour

# Nitrokey HSM Configuration
NITROKEY_ENABLED=true
NITROKEY_THRESHOLD=3
NITROKEY_TOTAL_SHARES=5
NITROKEY_MODE=distributed

# PKCS#11 Library Paths
NITROKEY_PKCS11_MODULE=/usr/lib/opensc-pkcs11.so
NITROKEY_PKCS11_SLOTS_AUTO_DETECT=true

# Distributed Signing Configuration
SIGNING_SESSION_TTL=300
SIGNING_WEBSOCKET_ENABLED=true
SIGNING_WEBSOCKET_PORT=8080

# Client Agent Configuration
CLIENT_AGENT_ENDPOINT=wss://pkiaas.domain.com:8080/ws/nitrokey
CLIENT_AGENT_HEARTBEAT_INTERVAL=30
CLIENT_AGENT_RECONNECT_ATTEMPTS=5

Cette architecture corrigée respecte les contraintes réelles des Nitrokeys distantes et utilise OpenSC/PKCS#11 pour l'accès local aux HSM, tout en maintenant un niveau de sécurité élevé pour la communication distribuée.

[{"content": "Analyser architecture Nitrokey avec cl\u00e9s physiques distantes", "status": "completed", "activeForm": "Analyzing architecture Nitrokey avec cl\u00e9s physiques distantes"}, {"content": "\u00c9valuer int\u00e9gration OpenSC/PKCS#11 pour acc\u00e8s distant", "status": "completed", "activeForm": "Evaluating int\u00e9gration OpenSC/PKCS#11 pour acc\u00e8s distant"}, {"content": "Concevoir interface web g\u00e9n\u00e9ration parts Shamir", "status": "completed", "activeForm": "Designing interface web g\u00e9n\u00e9ration parts Shamir"}, {"content": "Impl\u00e9menter syst\u00e8me de challenges N-of-M", "status": "in_progress", "activeForm": "Implementing syst\u00e8me de challenges N-of-M"}, {"content": "Corriger vuln\u00e9rabilit\u00e9s stockage cl\u00e9s priv\u00e9es", "status": "pending", "activeForm": "Correcting vuln\u00e9rabilit\u00e9s stockage cl\u00e9s priv\u00e9es"}]