feat: Permettre la révocation des certificats intermédiaires

Implémente la fonctionnalité de révocation pour les certificats de type 'intermédiaire'.

Modifications principales :
- CertificateController.php :
  - Suppression de l'interdiction de révoquer les certificats intermédiaires.
  - Ajout d'une logique spécifique pour révoquer un certificat intermédiaire en utilisant la configuration et la CRL du CA Racine.
  - Les certificats 'simple' continuent d'être révoqués via le script revoke_cert.sh.
- app/src/Views/certificates/index.php :
  - Le bouton 'Révoquer' est maintenant affiché pour les certificats intermédiaires non révoqués.
- app/src/Lang/fr.json :
  - Ajout de nouvelles clés de traduction pour les messages relatifs à la révocation des certificats intermédiaires.
  - Modification de la clé 'cert_revoke_error_ca_revocation' pour indiquer que seuls les certificats ROOT ne peuvent être révoqués via l'interface.

Ces modifications permettent une gestion plus complète des certificats intermédiaires directement depuis l'interface utilisateur.
This commit is contained in:
google-labs-jules[bot]
2025-06-15 18:01:13 +00:00
parent 72d100d5a4
commit 797267d41e
3 changed files with 78 additions and 27 deletions

View File

@ -238,16 +238,14 @@ class CertificateController
exit(); exit();
} }
// Empêcher la révocation des certificats Root ou Intermédiaires via l'interface // Empêcher la révocation des certificats Root via l'interface
if ($cert['type'] === 'root' || $cert['type'] === 'intermediate') { if ($cert['type'] === 'root') {
$_SESSION['error'] = $this->langService->__('cert_revoke_error_ca_revocation'); $_SESSION['error'] = $this->langService->__('cert_revoke_error_ca_revocation'); // Peut-être une clé dédiée pour root si le message doit être différent
header('Location: /certificates'); header('Location: /certificates');
exit(); exit();
} }
// Préparer le nom de base du certificat pour le script (sans l'extension .pem) $functionalPerimeterName = $cert['perimeter_name']; // Déjà récupéré plus haut, mais utile ici aussi
$certBaseName = str_replace('.cert.pem', '.cert', $cert['name']);
$functionalPerimeterName = $cert['perimeter_name'];
// Vérifier si le certificat n'est pas déjà révoqué dans la DB // Vérifier si le certificat n'est pas déjà révoqué dans la DB
if ($cert['is_revoked']) { if ($cert['is_revoked']) {
@ -256,29 +254,79 @@ class CertificateController
exit(); exit();
} }
// Appeler le script shell de révocation if ($cert['type'] === 'intermediate') {
$command = escapeshellcmd(SCRIPTS_PATH . '/revoke_cert.sh') . ' ' . // Logique de révocation pour les certificats intermédiaires
escapeshellarg($certBaseName) . ' ' . $intermediateCertPath = "/opt/tls/intermediate/" . $functionalPerimeterName . "/certs/" . $cert['name'];
escapeshellarg($functionalPerimeterName); $rootCaConfigPath = "/opt/tls/root/openssl.cnf"; // Chemin vers la configuration OpenSSL du CA Racine
$rootCaCrlPath = "/opt/tls/root/crl/crl.pem"; // Chemin vers la CRL du CA Racine
$this->logService->log('info', "Tentative de révocation du certificat '{$cert['name']}' pour le périmètre '$functionalPerimeterName'. Commande: '$command'", $userId, $ipAddress); // Commande pour révoquer le certificat intermédiaire avec le CA Racine
$revokeCmd = sprintf(
"openssl ca -batch -config %s -revoke %s",
escapeshellarg($rootCaConfigPath),
escapeshellarg($intermediateCertPath)
);
$output = shell_exec($command . ' 2>&1'); $this->logService->log('info', "Tentative de révocation du certificat intermédiaire '{$cert['name']}' pour le périmètre '$functionalPerimeterName'. Commande: '$revokeCmd'", $userId, $ipAddress);
$outputRevoke = shell_exec($revokeCmd . ' 2>&1');
if (strpos($output, "Certificat '$certBaseName' révoqué avec succès.") !== false) { if (strpos($outputRevoke, "Data Base Updated") !== false || strpos($outputRevoke, "Successfully revoked certificate") !== false) {
// Mettre à jour le statut du certificat dans la base de données // Commande pour régénérer la CRL du CA Racine
$stmt = $this->db->prepare("UPDATE certificates SET is_revoked = TRUE, revoked_at = NOW() WHERE id = ?"); $generateCrlCmd = sprintf(
$stmt->execute([$certificateId]); "openssl ca -batch -config %s -gencrl -out %s",
escapeshellarg($rootCaConfigPath),
escapeshellarg($rootCaCrlPath)
);
$this->logService->log('info', "Révocation réussie. Tentative de mise à jour de la CRL du CA Racine. Commande: '$generateCrlCmd'", $userId, $ipAddress);
$outputCrl = shell_exec($generateCrlCmd . ' 2>&1');
// Vérifier si la CRL a été générée et si le fichier existe
if ((strpos($outputCrl, "CRL Generated") !== false || strpos($outputCrl, "CRL generated") !== false) && file_exists($rootCaCrlPath)) {
// Mettre à jour le statut du certificat dans la base de données
$stmt_update = $this->db->prepare("UPDATE certificates SET is_revoked = TRUE, revoked_at = NOW() WHERE id = ?");
$stmt_update->execute([$certificateId]);
$this->logService->log('info', "Certificat intermédiaire '{$cert['name']}' révoqué et CRL du CA Racine mise à jour.", $userId, $ipAddress);
$_SESSION['success'] = $this->langService->__('cert_revoke_success_intermediate', ['name' => $cert['name']]);
} else {
$this->logService->log('error', "Échec de la mise à jour de la CRL du CA Racine pour le cert intermédiaire '{$cert['name']}'. Output CRL: $outputCrl. Output Revoke: $outputRevoke", $userId, $ipAddress);
$_SESSION['error'] = $this->langService->__('cert_revoke_warn_crl_update_failed_intermediate', ['name' => $cert['name']]);
}
} else {
$_SESSION['error'] = $this->langService->__('cert_revoke_error_intermediate', ['name' => $cert['name'], 'output' => htmlspecialchars($outputRevoke)]);
$this->logService->log('error', "Échec de la révocation du certificat intermédiaire '{$cert['name']}'. Output: $outputRevoke", $userId, $ipAddress);
}
header('Location: /certificates');
exit();
$this->logService->log('info', "Certificat '{$cert['name']}' révoqué et enregistré en DB.", $userId, $ipAddress);
$_SESSION['success'] = $this->langService->__('cert_revoke_success');
} else { } else {
$_SESSION['error'] = $this->langService->__('cert_revoke_error', ['output' => htmlspecialchars($output)]); // Logique existante pour les certificats 'simple'
$this->logService->log('error', "Échec révocation certificat '{$cert['name']}': $output", $userId, $ipAddress); $certBaseName = str_replace('.cert.pem', '.cert', $cert['name']);
}
header('Location: /certificates'); // Appeler le script shell de révocation
exit(); $command = escapeshellcmd(SCRIPTS_PATH . '/revoke_cert.sh') . ' ' .
escapeshellarg($certBaseName) . ' ' .
escapeshellarg($functionalPerimeterName);
$this->logService->log('info', "Tentative de révocation du certificat simple '{$cert['name']}' pour le périmètre '$functionalPerimeterName'. Commande: '$command'", $userId, $ipAddress);
$output = shell_exec($command . ' 2>&1');
if (strpos($output, "Certificat '$certBaseName' révoqué avec succès.") !== false) {
// Mettre à jour le statut du certificat dans la base de données
$stmt_update = $this->db->prepare("UPDATE certificates SET is_revoked = TRUE, revoked_at = NOW() WHERE id = ?");
$stmt_update->execute([$certificateId]);
$this->logService->log('info', "Certificat simple '{$cert['name']}' révoqué et enregistré en DB.", $userId, $ipAddress);
$_SESSION['success'] = $this->langService->__('cert_revoke_success');
} else {
$_SESSION['error'] = $this->langService->__('cert_revoke_error', ['output' => htmlspecialchars($output)]);
$this->logService->log('error', "Échec révocation certificat simple '{$cert['name']}': $output", $userId, $ipAddress);
}
header('Location: /certificates');
exit();
}
} }
/** /**

View File

@ -61,7 +61,7 @@
"cert_create_error": "Erreur lors de la création du certificat: {output}", "cert_create_error": "Erreur lors de la création du certificat: {output}",
"cert_revoke_error_id_missing": "ID du certificat manquant pour la révocation.", "cert_revoke_error_id_missing": "ID du certificat manquant pour la révocation.",
"cert_revoke_error_not_found": "Certificat introuvable pour la révocation.", "cert_revoke_error_not_found": "Certificat introuvable pour la révocation.",
"cert_revoke_error_ca_revocation": "Les certificats ROOT et INTERMÉDIAIRES ne peuvent pas être révoqués via l'interface pour des raisons de sécurité PKI.", "cert_revoke_error_ca_revocation": "Les certificats ROOT ne peuvent pas être révoqués via l'interface pour des raisons de sécurité PKI.",
"cert_revoke_error_already_revoked": "Ce certificat est déjà révoqué.", "cert_revoke_error_already_revoked": "Ce certificat est déjà révoqué.",
"cert_revoke_success": "Certificat révoqué avec succès.", "cert_revoke_success": "Certificat révoqué avec succès.",
"cert_revoke_error": "Erreur lors de la révocation du certificat: {output}", "cert_revoke_error": "Erreur lors de la révocation du certificat: {output}",
@ -80,5 +80,8 @@
"user_delete_success": "Utilisateur '{username}' supprimé avec succès.", "user_delete_success": "Utilisateur '{username}' supprimé avec succès.",
"user_delete_error_not_found": "Utilisateur introuvable pour la suppression.", "user_delete_error_not_found": "Utilisateur introuvable pour la suppression.",
"user_delete_error_db": "Erreur lors de la suppression de l'utilisateur dans la base de données.", "user_delete_error_db": "Erreur lors de la suppression de l'utilisateur dans la base de données.",
"self_delete_not_allowed": "Vous ne pouvez pas vous supprimer vous-même." "self_delete_not_allowed": "Vous ne pouvez pas vous supprimer vous-même.",
"cert_revoke_success_intermediate": "Le certificat intermédiaire '{name}' a été révoqué avec succès et la CRL du CA Racine a été mise à jour.",
"cert_revoke_warn_crl_update_failed_intermediate": "Le certificat intermédiaire '{name}' a été révoqué, mais la mise à jour de la CRL du CA Racine a rencontré un problème. Veuillez contacter un administrateur.",
"cert_revoke_error_intermediate": "Erreur lors de la révocation du certificat intermédiaire '{name}': {output}"
} }

View File

@ -49,8 +49,8 @@ require_once APP_ROOT_DIR . '/src/Views/shared/header.php';
</td> </td>
<td> <td>
<?php <?php
// Seuls les certificats 'simple' et non révoqués peuvent être révoqués via l'interface // Les certificats 'simple' ou 'intermediate' non révoqués peuvent être révoqués
if (!$cert['is_revoked'] && $cert['type'] === 'simple'): ?> if (!$cert['is_revoked'] && ($cert['type'] === 'simple' || $cert['type'] === 'intermediate')): ?>
<form action="/certificates/revoke" method="post" class="inline-form" onsubmit="return confirm('<?= htmlspecialchars($translations['confirm_revoke']) ?>');"> <form action="/certificates/revoke" method="post" class="inline-form" onsubmit="return confirm('<?= htmlspecialchars($translations['confirm_revoke']) ?>');">
<input type="hidden" name="certificate_id" value="<?= htmlspecialchars($cert['id']) ?>"> <input type="hidden" name="certificate_id" value="<?= htmlspecialchars($cert['id']) ?>">
<button type="submit" class="button danger-button"><?= htmlspecialchars($translations['revoke_certificate']) ?></button> <button type="submit" class="button danger-button"><?= htmlspecialchars($translations['revoke_certificate']) ?></button>