05 — PKI, certificats X.509 et rotation#
La Public Key Infrastructure (PKI) constitue le socle de confiance de l’internet moderne : elle lie des identités à des clés publiques au moyen de certificats signés par des autorités de certification. Comprendre sa structure, ses failles potentielles et les stratégies de rotation zero-downtime est indispensable pour toute architecture sécurisée.
Prérequis
Ce chapitre suppose la maîtrise des primitives cryptographiques (RSA, ECDSA, AES-GCM) vues aux chapitres précédents, ainsi qu’une familiarité avec Linux et les outils réseau TLS.
Structure d’un certificat X.509 v3#
Un certificat X.509 v3 est encodé en DER (binaire) ou PEM (base64). Sa structure ASN.1 distingue trois blocs :
TBSCertificate — la partie signée#
TBSCertificate (« To Be Signed ») contient tous les champs sur lesquels porte la signature de la CA :
Champ |
Rôle |
|---|---|
|
Toujours 2 (v3) pour les certificats modernes |
|
Identifiant unique au sein de la CA |
|
Algorithme de signature (ex. |
|
DN de la CA émettrice |
|
|
|
DN du titulaire (CN, O, C…) |
|
Algorithme + clé publique |
|
Extensions v3 (SAN, Key Usage, EKU…) |
Extensions critiques#
Les extensions marquées critical: true doivent être comprises par le vérificateur (sinon rejet).
Subject Alternative Name (SAN) — remplace le CN pour identifier les hôtes depuis RFC 2818 :
X509v3 Subject Alternative Name:
DNS:api.example.com, DNS:*.example.com, IP:192.168.1.1
Key Usage — usages cryptographiques autorisés de la clé :
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
Extended Key Usage (EKU) — usages applicatifs :
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
OCSP / CRL Distribution Points — comment vérifier la révocation :
Authority Information Access:
OCSP - URI:http://ocsp.example.com/
X509v3 CRL Distribution Points:
Full Name: URI:http://crl.example.com/ca.crl
Basic Constraints — distingue CA (cA: TRUE) de certificat feuille (cA: FALSE) :
X509v3 Basic Constraints: critical
CA:FALSE
Chaîne de certification#
Architecture hiérarchique#
Root CA (offline, HSM)
└── Intermediate CA (online)
├── Certificat feuille A (serveur TLS)
└── Certificat feuille B (client mTLS)
La Root CA est gardée hors ligne (air-gap) : sa clé privée ne signe que les certificats d’intermédiaires, rarement. En cas de compromission d’une CA intermédiaire, seule cette branche est révoquée — la racine reste saine.
Vérification de la chaîne#
L’algorithme de vérification (RFC 5280) parcourt la chaîne de bas en haut :
Vérifier la signature de chaque certificat avec la clé publique de son émetteur.
Vérifier les dates de validité (
notBefore ≤ now ≤ notAfter).Vérifier que
Basic Constraints: cA:TRUEest présent sur chaque CA intermédiaire.Vérifier le
pathLen(profondeur maximale de chaîne autorisée).Vérifier que le certificat feuille n’est pas révoqué (OCSP / CRL).
Vérifier que l’émetteur du premier certificat est un trust anchor (Root CA dans le magasin de confiance).
Let’s Encrypt et ACME v2#
ACME (RFC 8555) automatise l’émission et le renouvellement de certificats DV (Domain Validated).
Challenge HTTP-01#
Le client ACME place un token sous /.well-known/acme-challenge/<token>. Le serveur ACME vérifie que http://<domain>/.well-known/acme-challenge/<token> répond avec la valeur attendue.
Avantage : simple, pas d’accès DNS requis.
Limite : ne fonctionne pas pour les wildcards, requiert le port 80 accessible.
Challenge DNS-01#
Le client crée un enregistrement TXT _acme-challenge.<domain> avec la valeur du challenge. Permet :
Les wildcards (
*.example.com).Les machines sans port 80 exposé.
L’automatisation via API DNS (Route53, Cloudflare…).
Automatisation avec certbot#
# HTTP-01, renouvellement automatique via systemd timer
certbot certonly --nginx -d api.example.com --non-interactive --agree-tos -m admin@example.com
# DNS-01 wildcard avec plugin Cloudflare
certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials ~/.secrets/cloudflare.ini \
-d "*.example.com" -d example.com
# Vérification de la chaîne émise
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt \
/etc/letsencrypt/live/example.com/fullchain.pem
PKI interne#
CFSSL (Cloudflare)#
# Initialiser la Root CA
cfssl gencert -initca root-csr.json | cfssljson -bare root-ca
# Émettre un certificat intermédiaire
cfssl gencert -ca root-ca.pem -ca-key root-ca-key.pem \
-config cfssl-config.json -profile intermediate \
intermediate-csr.json | cfssljson -bare intermediate-ca
# Émettre un certificat serveur
cfssl gencert -ca intermediate-ca.pem -ca-key intermediate-ca-key.pem \
-config cfssl-config.json -profile server \
server-csr.json | cfssljson -bare server
step-ca (Smallstep)#
# Initialiser une PKI
step ca init --name "MyOrg Internal CA" \
--dns ca.internal --address :9000 \
--provisioner admin@myorg.com
# Émettre un certificat avec durée courte (24h)
step ca certificate api.internal api.crt api.key \
--ca-url https://ca.internal:9000 --root root_ca.crt \
--not-after 24h
# Renouvellement automatique (daemon)
step ca renew --daemon --exec "systemctl reload nginx" api.crt api.key
Vault PKI Engine#
# Activer le moteur PKI
vault secrets enable -path=pki pki
vault secrets tune -max-lease-ttl=87600h pki
# Générer la Root CA interne
vault write pki/root/generate/internal \
common_name="MyOrg Root CA" ttl=87600h
# Configurer les URLs
vault write pki/config/urls \
issuing_certificates="https://vault.internal:8200/v1/pki/ca" \
crl_distribution_points="https://vault.internal:8200/v1/pki/crl"
# Créer un rôle (TTL court = rotation fréquente)
vault write pki/roles/web-server \
allowed_domains="example.com" allow_subdomains=true \
max_ttl=72h key_type=ec key_bits=256
# Émettre un certificat
vault write pki/issue/web-server \
common_name="api.example.com" ttl=24h
Les TTL courts (24h–72h) dans Vault PKI forcent une rotation fréquente, réduisant drastiquement la fenêtre d’exposition en cas de compromission.
Rotation de certificats et zero-downtime#
Stratégie overlapping#
Pour éviter toute interruption, on émet le nouveau certificat avant l’expiration de l’ancien :
Ancien cert |=============================|
Nouveau cert |=============================|
↑ Renouvellement à 2/3 de la durée
Pendant la période de chevauchement, les deux certificats sont valides. Le trafic bascule progressivement vers le nouveau (rolling restart Nginx, rotation Kubernetes…).
cert-manager Kubernetes#
# Émission automatique via Vault PKI
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: api-tls
namespace: production
spec:
secretName: api-tls-secret
duration: 24h
renewBefore: 8h # Renouvellement 8h avant expiration
subject:
organizations: ["MyOrg"]
dnsNames:
- api.example.com
issuerRef:
name: vault-issuer
kind: ClusterIssuer
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: vault-issuer
spec:
vault:
server: https://vault.internal:8200
path: pki/sign/web-server
auth:
kubernetes:
mountPath: /v1/auth/kubernetes
role: cert-manager
OCSP vs CRL — révocation#
CRL (Certificate Revocation List)#
Fichier signé par la CA listant les numéros de série révoqués. Problèmes : taille croissante, fréquence de mise à jour limitée (24h–7j), téléchargement complet à chaque vérification.
OCSP (Online Certificate Status Protocol)#
Requête HTTP en temps réel : le client envoie le numéro de série, le répondeur OCSP répond good / revoked / unknown.
OCSP Stapling — le serveur TLS inclut une réponse OCSP pré-signée dans le handshake TLS, évitant que le client contacte le répondeur OCSP directement :
# Nginx avec OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/ssl/certs/chain.pem;
resolver 8.8.8.8 1.1.1.1 valid=300s;
OCSP Must-Staple
L’extension TLS Feature: status_request (OCSP Must-Staple) force le client à rejeter la connexion si le staple OCSP est absent. Idéal pour les certificats haute sécurité.
HSM — Hardware Security Module#
Un HSM est un composant matériel conçu pour générer, stocker et utiliser des clés cryptographiques sans jamais les exposer en clair à l’extérieur.
Rôle dans une PKI#
Protège la clé privée de la Root CA (et des CA intermédiaires critiques).
Opérations cryptographiques effectuées dans le HSM, la clé ne quitte jamais l’enveloppe sécurisée.
Audit log immuable de toutes les opérations.
FIPS 140-2 / 140-3#
Standard NIST définissant les niveaux de sécurité :
Niveau 1 : algorithmes validés, logiciel pur acceptable.
Niveau 2 : preuves d’altération physique (scellés, revêtements).
Niveau 3 : résistance active à l’intrusion physique, effacement des clés si tentative.
Niveau 4 : protection complète contre toute intrusion physique ou environnementale.
SoftHSM2 pour le développement#
# Installation et initialisation
apt install softhsm2
softhsm2-util --init-token --slot 0 --label "DevPKI" \
--pin 1234 --so-pin 5678
# Générer une clé RSA dans SoftHSM2
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \
--login --pin 1234 --keypairgen \
--key-type RSA:4096 --id 01 --label "RootCA"
# Utiliser avec OpenSSL via engine
openssl req -engine pkcs11 -keyform engine \
-key "pkcs11:token=DevPKI;object=RootCA;type=private" \
-new -x509 -days 3650 -out root-ca.pem
Certificate Transparency#
CT (RFC 6962) impose que tout certificat DV/OV/EV émis par une CA publique soit enregistré dans des logs CT publics et vérifiables avant d’être accepté par Chrome/Firefox.
Structure des logs#
Les logs CT sont des Merkle trees append-only :
Chaque feuille contient un certificat (ou pré-certificat).
La racine du Merkle tree (STH — Signed Tree Head) est signée périodiquement par le log.
Toute insertion produit un SCT (Signed Certificate Timestamp) que le serveur TLS présente lors du handshake.
Audit et monitoring#
# Rechercher tous les certificats émis pour un domaine
curl "https://crt.sh/?q=example.com&output=json" | jq '.[].name_value' | sort -u
# Surveiller les nouvelles émissions (defensive monitoring)
# Via certspotter, Facebook CT Monitor, etc.
Surveillance CT pour la sécurité
Un attaquant qui obtient frauduleusement un certificat pour votre domaine (via une CA compromise) sera détecté par CT monitoring. Configurez des alertes sur crt.sh ou un service équivalent pour votre domaine.
Cellules Python#
Génération et parsing d’un certificat X.509 auto-signé#
# --- Génération d'un certificat X.509 auto-signé ---
# 1. Générer une paire de clés ECDSA P-256
private_key = ec.generate_private_key(ec.SECP256R1())
public_key = private_key.public_key()
# 2. Construire le sujet / émetteur (auto-signé ⟹ identiques)
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "FR"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Alkimya Sécurité"),
x509.NameAttribute(NameOID.COMMON_NAME, "api.alkimya.fr"),
])
now = datetime.datetime.utcnow()
# 3. Assembler le certificat avec extensions v3
cert = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.public_key(public_key)
.serial_number(x509.random_serial_number())
.not_valid_before(now)
.not_valid_after(now + datetime.timedelta(days=90))
# SAN : noms DNS + IP
.add_extension(
x509.SubjectAlternativeName([
x509.DNSName("api.alkimya.fr"),
x509.DNSName("*.alkimya.fr"),
x509.IPAddress(ipaddress.IPv4Address("192.168.1.10")),
]),
critical=False,
)
# Key Usage
.add_extension(
x509.KeyUsage(
digital_signature=True, key_encipherment=False,
content_commitment=False, key_agreement=True,
key_cert_sign=False, crl_sign=False,
data_encipherment=False, encipher_only=False, decipher_only=False,
),
critical=True,
)
# Extended Key Usage
.add_extension(
x509.ExtendedKeyUsage([
ExtendedKeyUsageOID.SERVER_AUTH,
ExtendedKeyUsageOID.CLIENT_AUTH,
]),
critical=False,
)
# Basic Constraints : pas une CA
.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
.sign(private_key, hashes.SHA256())
)
# 4. Afficher les champs principaux
print("=== Certificat X.509 v3 ===")
print(f"Sujet : {cert.subject.rfc4514_string()}")
print(f"Émetteur : {cert.issuer.rfc4514_string()}")
print(f"Numéro série: {cert.serial_number}")
print(f"Algorithme : {cert.signature_algorithm_oid.dotted_string}")
print(f"Valide du : {cert.not_valid_before_utc}")
print(f"Valide au : {cert.not_valid_after_utc}")
print(f"Courbe : {cert.public_key().curve.name}")
# Extensions
san = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
print(f"\nSAN : {[str(n) for n in san.value]}")
ku = cert.extensions.get_extension_for_class(x509.KeyUsage)
print(f"Key Usage : digital_signature={ku.value.digital_signature}, key_agreement={ku.value.key_agreement}")
eku = cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage)
print(f"EKU : {[o.dotted_string for o in eku.value]}")
bc = cert.extensions.get_extension_for_class(x509.BasicConstraints)
print(f"Basic Const.: cA={bc.value.ca}")
=== Certificat X.509 v3 ===
Sujet : CN=api.alkimya.fr,O=Alkimya Sécurité,C=FR
Émetteur : CN=api.alkimya.fr,O=Alkimya Sécurité,C=FR
Numéro série: 562908955252750508441051188008630667492958815848
Algorithme : 1.2.840.10045.4.3.2
Valide du : 2026-03-26 11:51:07+00:00
Valide au : 2026-06-24 11:51:07+00:00
Courbe : secp256r1
SAN : ["<DNSName(value='api.alkimya.fr')>", "<DNSName(value='*.alkimya.fr')>", '<IPAddress(value=192.168.1.10)>']
Key Usage : digital_signature=True, key_agreement=True
EKU : ['1.3.6.1.5.5.7.3.1', '1.3.6.1.5.5.7.3.2']
Basic Const.: cA=False
/tmp/ipykernel_19150/416709031.py:14: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
now = datetime.datetime.utcnow()
Simulation d’une chaîne PKI complète#
# --- Chaîne PKI : Root CA → CA intermédiaire → Certificat feuille ---
def create_ca(name: str, issuer_cert=None, issuer_key=None, path_length: int = 0):
"""Crée une CA (auto-signée si issuer_cert est None)."""
key = ec.generate_private_key(ec.SECP256R1())
subject = x509.Name([
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Alkimya PKI"),
x509.NameAttribute(NameOID.COMMON_NAME, name),
])
actual_issuer = issuer_cert.subject if issuer_cert else subject
actual_issuer_key = issuer_key if issuer_key else key
now = datetime.datetime.utcnow()
cert = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(actual_issuer)
.public_key(key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(now)
.not_valid_after(now + datetime.timedelta(days=3650))
.add_extension(x509.BasicConstraints(ca=True, path_length=path_length), critical=True)
.add_extension(
x509.KeyUsage(
digital_signature=True, key_cert_sign=True, crl_sign=True,
key_encipherment=False, content_commitment=False,
key_agreement=False, data_encipherment=False,
encipher_only=False, decipher_only=False,
),
critical=True,
)
.add_extension(x509.SubjectKeyIdentifier.from_public_key(key.public_key()), critical=False)
.sign(actual_issuer_key, hashes.SHA256())
)
return cert, key
def create_leaf(cn: str, dns_names: list, ca_cert, ca_key):
"""Crée un certificat feuille signé par la CA fournie."""
key = ec.generate_private_key(ec.SECP256R1())
now = datetime.datetime.utcnow()
cert = (
x509.CertificateBuilder()
.subject_name(x509.Name([
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Alkimya"),
x509.NameAttribute(NameOID.COMMON_NAME, cn),
]))
.issuer_name(ca_cert.subject)
.public_key(key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(now)
.not_valid_after(now + datetime.timedelta(days=90))
.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
.add_extension(
x509.SubjectAlternativeName([x509.DNSName(d) for d in dns_names]),
critical=False,
)
.add_extension(
x509.KeyUsage(
digital_signature=True, key_encipherment=False,
content_commitment=False, key_agreement=True,
key_cert_sign=False, crl_sign=False,
data_encipherment=False, encipher_only=False, decipher_only=False,
),
critical=True,
)
.sign(ca_key, hashes.SHA256())
)
return cert, key
# Construire la chaîne
root_cert, root_key = create_ca("Alkimya Root CA", path_length=1)
inter_cert, inter_key = create_ca("Alkimya Intermediate CA", root_cert, root_key, path_length=0)
leaf_cert, leaf_key = create_leaf("api.alkimya.fr", ["api.alkimya.fr", "www.alkimya.fr"], inter_cert, inter_key)
# Vérification manuelle de la chaîne
def verify_chain(leaf, intermediate, root):
"""Vérification basique de la chaîne de certification."""
results = {}
# 1. Le certificat feuille est signé par l'intermédiaire
try:
intermediate.public_key().verify(
leaf.signature, leaf.tbs_certificate_bytes,
ec.ECDSA(hashes.SHA256())
)
results["feuille ← intermédiaire"] = "✓ Signature valide"
except Exception as e:
results["feuille ← intermédiaire"] = f"✗ {e}"
# 2. L'intermédiaire est signé par la Root CA
try:
root.public_key().verify(
intermediate.signature, intermediate.tbs_certificate_bytes,
ec.ECDSA(hashes.SHA256())
)
results["intermédiaire ← root"] = "✓ Signature valide"
except Exception as e:
results["intermédiaire ← root"] = f"✗ {e}"
# 3. La Root est auto-signée
try:
root.public_key().verify(
root.signature, root.tbs_certificate_bytes,
ec.ECDSA(hashes.SHA256())
)
results["root ← auto-signée"] = "✓ Signature valide"
except Exception as e:
results["root ← auto-signée"] = f"✗ {e}"
# 4. Basic Constraints
def get_bc(cert):
return cert.extensions.get_extension_for_class(x509.BasicConstraints).value
bc_root = get_bc(root)
bc_inter = get_bc(intermediate)
bc_leaf = get_bc(leaf)
results["Root CA cA=True"] = "✓" if bc_root.ca else "✗"
results["Intermediate cA=True"] = "✓" if bc_inter.ca else "✗"
results["Feuille cA=False"] = "✓" if not bc_leaf.ca else "✗"
return results
results = verify_chain(leaf_cert, inter_cert, root_cert)
print("=== Vérification de la chaîne PKI ===")
for check, status in results.items():
print(f" {check:40s} {status}")
print("\n=== Résumé de la chaîne ===")
for label, cert in [("Root CA", root_cert), ("Intermediate CA", inter_cert), ("Feuille", leaf_cert)]:
bc = cert.extensions.get_extension_for_class(x509.BasicConstraints).value
validity = cert.not_valid_after_utc - cert.not_valid_before_utc
print(f" {label:18s} CN={cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value:35s} "
f"cA={bc.ca} durée={validity.days}j")
=== Vérification de la chaîne PKI ===
feuille ← intermédiaire ✓ Signature valide
intermédiaire ← root ✓ Signature valide
root ← auto-signée ✓ Signature valide
Root CA cA=True ✓
Intermediate cA=True ✓
Feuille cA=False ✓
=== Résumé de la chaîne ===
Root CA CN=Alkimya Root CA cA=True durée=3650j
Intermediate CA CN=Alkimya Intermediate CA cA=True durée=3650j
Feuille CN=api.alkimya.fr cA=False durée=90j
/tmp/ipykernel_19150/3214195908.py:13: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
now = datetime.datetime.utcnow()
/tmp/ipykernel_19150/3214195908.py:41: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
now = datetime.datetime.utcnow()
Timeline de rotation de certificats avec fenêtres overlapping#
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
fig, ax = plt.subplots(figsize=(13, 5))
# Paramètres : durée 90 jours, renouvellement à 2/3 (jour 60)
duree = 90
renouvellement_fraction = 2 / 3
renewal_day = int(duree * renouvellement_fraction) # Jour 60
certs = [
{"label": "Cert v1", "start": 0, "end": 90, "y": 2},
{"label": "Cert v2", "start": 60, "end": 150, "y": 3},
{"label": "Cert v3", "start": 120, "end": 210, "y": 4},
]
colors = sns.color_palette("muted", 3)
for i, c in enumerate(certs):
width = c["end"] - c["start"]
rect = FancyBboxPatch(
(c["start"], c["y"] - 0.35), width, 0.70,
boxstyle="round,pad=0.05",
facecolor=colors[i], edgecolor="white", linewidth=1.5, alpha=0.85,
)
ax.add_patch(rect)
mid = (c["start"] + c["end"]) / 2
ax.text(mid, c["y"], c["label"], ha="center", va="center",
fontsize=10, fontweight="bold", color="white")
# Zones d'overlap
overlap_zones = [(60, 90, "Overlap 1\n(v1 + v2)"), (120, 150, "Overlap 2\n(v2 + v3)")]
for x0, x1, lbl in overlap_zones:
ax.axvspan(x0, x1, alpha=0.15, color="gold", zorder=0)
ax.text((x0 + x1) / 2, 4.7, lbl, ha="center", va="center",
fontsize=8, color="goldenrod", fontstyle="italic")
# Lignes de renouvellement
for day, label in [(60, "Renouvellement\nv1→v2"), (120, "Renouvellement\nv2→v3")]:
ax.axvline(x=day, color="crimson", linestyle="--", linewidth=1.5)
ax.text(day, 1.3, label, ha="center", va="top", fontsize=8, color="crimson")
# Ligne "aujourd'hui"
ax.axvline(x=75, color="steelblue", linestyle=":", linewidth=2)
ax.text(75, 4.85, "Aujourd'hui", ha="center", va="bottom", fontsize=8, color="steelblue")
ax.set_xlim(-5, 215)
ax.set_ylim(0.8, 5.3)
ax.set_xlabel("Jours depuis l'émission de Cert v1", fontsize=11)
ax.set_title(
"Rotation zero-downtime de certificats TLS\n"
"Renouvellement à 2/3 de la durée de validité (jour 60 sur 90)",
fontsize=12, fontweight="bold",
)
ax.set_yticks([])
ax.xaxis.set_major_locator(matplotlib.ticker.MultipleLocator(30))
ax.grid(axis="x", linestyle="--", alpha=0.4)
# Légende
legend_handles = [
mpatches.Patch(facecolor=colors[i], label=f"Cert v{i+1}") for i in range(3)
]
legend_handles.append(mpatches.Patch(facecolor="gold", alpha=0.4, label="Fenêtre d'overlap"))
ax.legend(handles=legend_handles, loc="lower right", fontsize=9)
plt.savefig("rotation_certs.png", dpi=120, bbox_inches="tight")
plt.show()
print("Timeline de rotation générée.")
Timeline de rotation générée.
Résumé#
Structure X.509 v3 : le certificat est un objet ASN.1 signé par une CA. Les extensions (SAN, Key Usage, EKU, Basic Constraints) définissent précisément l’identité et les usages autorisés.
Chaîne de certification : la Root CA reste hors ligne. La vérification RFC 5280 remonte la chaîne en validant signatures, dates, contraintes de base et révocation.
Let’s Encrypt / ACME v2 : automatise l’émission DV. HTTP-01 pour les serveurs publics, DNS-01 pour les wildcards et les environnements internes.
PKI interne : CFSSL, step-ca et Vault PKI permettent d’émettre des certificats à TTL court (24h–72h), réduisant la surface d’exposition en cas de compromission.
Rotation zero-downtime : les fenêtres d’overlap garantissent qu’aucune interruption de service n’accompagne le renouvellement. cert-manager automatise ce processus dans Kubernetes.
Révocation : OCSP (temps réel) préféré à CRL (fichier volumeux). OCSP Stapling déplace la charge de vérification côté serveur, améliorant performance et confidentialité.
HSM : seule solution garantissant l’inviolabilité des clés privées des CA racines. SoftHSM2 réplique le comportement pour le développement et les tests.
Certificate Transparency : les logs CT forment un registre public append-only permettant de détecter toute émission frauduleuse de certificat pour un domaine surveillé.