En bref. Depuis mars 2024, le CA/Browser Forum impose aux AC de publier des CRL et rend OCSP facultatif. Let’s Encrypt a retiré les URL OCSP de ses certificats en mai 2025 et a arrêté ses répondeurs en août 2025. Les navigateurs n’interrogent plus les AC sur chaque certificat. Firefox consulte un filtre CRLite local, Chrome le CRLSet qui lui est transmis, et Apple utilise sa propre liste agrégée. La plupart des bibliothèques et des outils en ligne de commande (Java, .NET, Go, Python, Node.js, curl) ne vérifient pas du tout la révocation par défaut. Tous ont accepté un certificat Let’s Encrypt révoqué lors de nos tests. Si votre client doit rejeter les certificats révoqués, activez explicitement la vérification des CRL et préparez-vous à des durées de vie de certificats courtes. C’est de toute façon dans cette direction que va le secteur.
Pourquoi la révocation devait être repensée
Un certificat peut être révoqué avant son expiration, généralement parce que sa clé a fuité ou qu’il a été émis par erreur. Il existait deux moyens classiques d’en informer les clients :
- CRL (Certificate Revocation List, ou liste de révocation de certificats, RFC 5280) : l’AC publie une liste signée des numéros de série révoqués. Le certificat y renvoie dans son extension CRL Distribution Points.
- OCSP (RFC 6960) : le client interroge le répondeur de l’AC sur un certificat précis et reçoit en retour un statut signé, « good » (non révoqué), « revoked » (révoqué) ou « unknown » (inconnu). L’URL figure dans l’extension Authority Information Access.
Les deux méthodes posaient des problèmes sur le Web. Les CRL pouvaient être volumineuses. OCSP révélait à l’AC chaque site visité par un utilisateur, ajoutait de la latence, et les navigateurs devaient se contenter d’un échec non bloquant (« soft-fail ») lorsque le répondeur était injoignable. Un attaquant capable de bloquer la requête OCSP rend alors la vérification inutile. L’agrafage OCSP (OCSP stapling) a résolu le problème de confidentialité en confiant au serveur le soin de récupérer la réponse et de la joindre au handshake, mais il ne s’est jamais généralisé. Must-Staple, un indicateur de certificat qui exige une réponse agrafée, n’a guère été pris en charge par les navigateurs.
Ce que disent les règles aujourd’hui
Le vote SC-063 (« Rendre OCSP facultatif, exiger les CRL et encourager l’automatisation », en vigueur depuis le 15 mars 2024) a modifié les Baseline Requirements :
- Les CRL sont obligatoires. Une AC doit publier une CRL complète ou fragmentée dans les 24 heures qui suivent l’émission de son premier certificat. Elle doit mettre la CRL à jour au moins tous les 7 jours si tous ses certificats comportent une URL OCSP, tous les 4 jours dans le cas contraire, ainsi que dans les 24 heures qui suivent la révocation d’un certificat (§4.9.7). Pour les certificats d’abonné,
nextUpdatepeut être postérieur d’au plus 10 jours àthisUpdate(§7.2). - OCSP est facultatif. Si un certificat comporte une URL OCSP, le répondeur doit répondre, et la validité de ses réponses doit être comprise entre 8 heures et 10 jours (§4.9.9). Un certificat sans URL OCSP doit comporter un point de distribution de CRL (§7.1.2.11.2).
- Les certificats de courte durée n’ont pas besoin d’informations de révocation. Un « Short-lived Subscriber Certificate » (certificat d’abonné de courte durée) est valable 7 jours au plus s’il est émis à partir du 15 mars 2026 (10 jours s’il a été émis avant cette date). L’AC peut en permettre la révocation, et le point de distribution de CRL est facultatif.
- Délais de révocation. 24 heures en cas de compromission de clé et dans les cas similaires, 5 jours pour les autres (§4.9.1.1).
Let’s Encrypt a ensuite abandonné complètement OCSP, « principalement parce qu’il représente un risque considérable pour la confidentialité sur Internet » :
- 30 janvier 2025 : les nouvelles demandes Must-Staple échouent.
- 7 mai 2025 : les URL OCSP disparaissent des nouveaux certificats, qui comportent à la place des URL de CRL.
- 6 août 2025 : les répondeurs OCSP sont arrêtés.
Ce que contiennent les certificats aujourd’hui
Vérifier sur quelques sites, le 20 octobre 2026, la présence d’une URL OCSP, d’une URL de CRL et d’une réponse OCSP agrafée donne un tableau contrasté :
letsencrypt.org Let's Encrypt OCSP=- CRL=http://ye2.c.lencr.org/96.crl
staple: OCSP response: no response sent
www.google.com Google Trust Services OCSP=- CRL=http://c.pki.goog/we2/xuzt3PU9F_w.crl
staple: OCSP response: no response sent
github.com Sectigo Limited OCSP=http://ocsp.sectigo.com CRL=-
staple: OCSP response: no response sent
www.digicert.com DigiCert Inc OCSP=http://ocsp.digicert.com CRL=http://crl3.digicert.com/DigiCertEVRSACAG2.crl
staple: OCSP Response Status: successful (0x0)
www.microsoft.com Microsoft Corporation OCSP=http://oneocsp.microsoft.com/ocsp CRL=http://www.microsoft.com/pkiops/crl/partition/Microsoft%20TLS%20G2%20RSA%20CA%20OCSP%2004_Partition00093.crl
staple: OCSP Response Status: successful (0x0)
www.amazon.com Amazon OCSP=http://ocsp.r2m04.amazontrust.com CRL=http://crl.r2m04.amazontrust.com/r2m04.crl
staple: OCSP Response Status: successful (0x0)
Let’s Encrypt et Google Trust Services émettent des certificats qui n’indiquent qu’une CRL. Le certificat de GitHub, émis par Sectigo, illustre le cas inverse, OCSP uniquement, ce que les BR autorisent. Un client qui ne prend en charge que l’une des deux méthodes ne peut pas vérifier tous ces certificats.
Les CRL ne sont pas petites, mais elles restent gérables :
http://yr1.c.lencr.org/46.crl 192226 bytes 5094 entries Oct 20 12:26:45 2026 GMT -> Oct 29 12:26:44 2026 GMT
http://c.pki.goog/we2/xuzt3PU9F_w.crl 87562 bytes 2456 entries Oct 20 11:12:29 2026 GMT -> Oct 30 10:12:28 2026 GMT
http://crl3.digicert.com/DigiCertEVRSACAG2.crl 635286 bytes 17091 entries Oct 20 06:24:08 2026 GMT -> Oct 27 06:24:08 2026 GMT
Let’s Encrypt fragmente ses CRL : chaque certificat pointe vers l’un des nombreux fichiers numérotés, et l’extension Issuing Distribution Point de la CRL désigne ce fragment. Le décodeur de CRL lit n’importe lequel de ces fichiers, vérifie sa signature avec le certificat de l’émetteur et vous indique si un certificat donné y figure.
Comment les navigateurs vérifient la révocation
Aucun des grands navigateurs n’envoie plus de requête OCSP pour un certificat ordinaire :
- Firefox : CRLite. Firefox télécharge un encodage compressé de tous les certificats révoqués qui figurent dans les journaux CT, le met à jour toutes les 12 heures et le consulte localement. Cela représente en moyenne environ 300 Ko de données par jour, selon Mozilla. CRLite est activé pour tous les utilisateurs sur ordinateur depuis Firefox 137. Firefox 142 a cessé d’utiliser OCSP pour les certificats validés par domaine, et Firefox pour Android a adopté CRLite dans sa version 145.
- Chrome : CRLSets. Chrome diffuse une liste construite à partir des CRL des AC, mais seulement « un sous-ensemble des certificats identifiés comme révoqués », et « Chrome n’effectue généralement pas de vérifications en ligne (c’est-à-dire OCSP et CRL) » (Chromium). Lors de notre test, Chromium 156 a chargé
https://revoked-isrgrootx1.letsencrypt.org/avec une réponse HTTP 200. - Apple : une liste agrégée. Apple collecte les données de révocation auprès des AC et les distribue aux appareils. En décembre 2025, le support technique d’Apple pour les développeurs a expliqué que seuls les certificats révoqués pour des motifs tels qu’une compromission de clé « finissent sur la liste “noire” d’Apple », et que les certificats Let’s Encrypt ne font l’objet d’aucune vérification OCSP secondaire. Un certificat révoqué pour un motif ordinaire peut donc toujours se charger dans Safari.
Concrètement, révoquer un certificat n’a rien d’un arrêt d’urgence immédiat dans les navigateurs. Firefox prend en compte les révocations grâce aux mises à jour CRLite effectuées toutes les 12 heures. Chrome et Safari peuvent ne jamais bloquer un certificat révoqué pour un motif ordinaire. La véritable limite pour une clé qui a fuité, c’est la durée de vie restante du certificat, et c’est l’une des raisons du passage aux certificats de 47 jours.
Ce que vérifient par défaut les bibliothèques et les outils
Chacun des clients ci-dessous s’est connecté le 20 octobre 2026 à https://revoked-isrgrootx1.letsencrypt.org/ (révoqué le 7 octobre 2026) et à https://valid-isrgrootx1.letsencrypt.org/. Le certificat révoqué comporte une URL de CRL et aucune URL OCSP.
| Client | Par défaut, sur le site révoqué | Vérification de la révocation qui a fonctionné |
|---|---|---|
| Java 25 (JSSE) | HTTP 200 | -Dcom.sun.net.ssl.checkRevocation=true -Dcom.sun.security.enableCRLDP=true |
.NET 10 HttpClient | HTTP 200 | SslOptions.CertificateRevocationCheckMode = X509RevocationMode.Online |
Go 1.27 crypto/tls | 200 OK | Votre propre vérification dans tls.Config.VerifyConnection |
Python 3.13 ssl | handshake réussi | VERIFY_CRL_CHECK_LEAF et la CRL chargée dans le contexte |
Node.js 24 tls | handshake réussi | Option crl avec une CRL par élément du tableau, pour chaque niveau de la chaîne |
| curl 8.14 (OpenSSL) | HTTP 200 | --crlfile avec les CRL de chaque niveau de la chaîne |
| Chromium 156 | HTTP 200 | Aucune (CRLSet uniquement) |
Vous trouverez ci-dessous les détails client par client.
Java
JSSE ne vérifie pas la révocation tant que com.sun.net.ssl.checkRevocation ne vaut pas true. Même dans ce cas, il n’utilise qu’OCSP, sauf si la prise en charge des CRL Distribution Points est elle aussi activée. C’est pourquoi un certificat Let’s Encrypt est rejeté comme inconnu et non comme révoqué :
--- 1. default
HTTP 200
--- 2. -Dcom.sun.net.ssl.checkRevocation=true
javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path validation failed: java.security.cert.CertPathValidatorException: Could not determine revocation status
--- 3. checkRevocation + -Dcom.sun.security.enableCRLDP=true (revoked site)
javax.net.ssl.SSLHandshakeException: (certificate_revoked) PKIX path validation failed: java.security.cert.CertPathValidatorException: Certificate has been revoked, reason: UNSPECIFIED, revocation date: Wed Oct 07 18:37:19 UTC 2026, authority: CN=YR1, O=Let's Encrypt, C=US, extension OIDs: []
--- 4. same, valid site
HTTP 200
Lorsque la vérification est activée, le validateur PKIX échoue en mode bloquant s’il ne parvient pas à déterminer un statut. C’est prudent, mais une panne du serveur de CRL devient alors votre propre panne. java.security.cert.PKIXRevocationChecker permet de choisir, connexion par connexion, l’ordre entre OCSP et CRL ainsi que les options d’échec non bloquant (soft-fail).
.NET
HttpClient avec SocketsHttpHandler ne vérifie pas la révocation par défaut. Avec le mode Online, .NET récupère lui-même la CRL ou la réponse OCSP. Sous Linux, il le fait au moyen de sa propre implémentation reposant sur OpenSSL :
var handler = new SocketsHttpHandler();
handler.SslOptions.CertificateRevocationCheckMode = X509RevocationMode.Online;
using var client = new HttpClient(handler);
https://revoked-isrgrootx1.letsencrypt.org/ revocation=default -> 200
https://revoked-isrgrootx1.letsencrypt.org/ revocation=Online -> The remote certificate is invalid because of errors in the certificate chain: Revoked
https://valid-isrgrootx1.letsencrypt.org/ revocation=default -> 200
https://valid-isrgrootx1.letsencrypt.org/ revocation=Online -> 200
Go
crypto/tls vérifie la chaîne et le nom, mais jamais la révocation. Si vous en avez besoin, contrôlez vous-même la CRL une fois la vérification terminée. x509.ParseRevocationList et CheckSignatureFrom se chargent des parties délicates :
func checkCRL(cs tls.ConnectionState) error {
chain := cs.VerifiedChains[0]
leaf, issuer := chain[0], chain[1]
if len(leaf.CRLDistributionPoints) == 0 {
return errors.New("certificate has no CRL distribution point")
}
resp, err := http.Get(leaf.CRLDistributionPoints[0])
if err != nil {
return err
}
defer resp.Body.Close()
der, err := io.ReadAll(io.LimitReader(resp.Body, 64<<20))
if err != nil {
return err
}
crl, err := x509.ParseRevocationList(der)
if err != nil {
return err
}
if err := crl.CheckSignatureFrom(issuer); err != nil {
return fmt.Errorf("CRL signature: %w", err)
}
if time.Now().After(crl.NextUpdate) {
return errors.New("CRL is past its nextUpdate")
}
for _, entry := range crl.RevokedCertificateEntries {
if entry.SerialNumber.Cmp(leaf.SerialNumber) == 0 {
return fmt.Errorf("certificate %X revoked at %s", leaf.SerialNumber, entry.RevocationTime.UTC())
}
}
return nil
}
// client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{VerifyConnection: checkCRL}}}
https://revoked-isrgrootx1.letsencrypt.org/ -> 200 OK
https://revoked-isrgrootx1.letsencrypt.org/ -> Get "https://revoked-isrgrootx1.letsencrypt.org/": certificate 67E51010C975E496C8EB800F3EF1BAB3F2B revoked at 2026-10-07 18:37:19 +0000 UTC
https://valid-isrgrootx1.letsencrypt.org/ -> 200 OK
https://valid-isrgrootx1.letsencrypt.org/ -> 200 OK
L’exemple ne vérifie que le certificat feuille et télécharge la CRL à chaque handshake. En production, mettez chaque CRL en cache jusqu’à son NextUpdate, et décidez de ce qui doit se passer lorsque le serveur de CRL est indisponible.
Python, Node.js et curl
Tous trois s’appuient sur la vérification des CRL d’OpenSSL et attendent que vous leur fournissiez les CRL. Ils ne les téléchargent jamais :
python 3.13.5 default: handshake ok, TLSv1.3
python 3.13.5 VERIFY_CRL_CHECK_LEAF: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate revoked (_ssl.c:1029)
node v24.22.0 default: handshake ok
node v24.22.0 crl: UNABLE_TO_GET_CRL unable to get certificate CRL
L’option ssl.VERIFY_CRL_CHECK_LEAF de Python ne vérifie que le certificat feuille : il suffit donc de charger la CRL de ce certificat avec ctx.load_verify_locations(cafile=...). Node.js et curl vérifient chaque certificat de la chaîne, si bien qu’une CRL pour le seul certificat feuille donne UNABLE_TO_GET_CRL (Node) ou unable to get certificate CRL (curl), même sur un site valide. Fournissez-leur la CRL de chaque certificat de la chaîne. Pour Node.js 24, passez-les sous forme de tableau, avec une chaîne de caractères PEM par CRL. Lors de notre test, un tampon unique contenant les trois CRL concaténées échouait toujours avec UNABLE_TO_GET_CRL :
revoked-isrgrootx1.letsencrypt.org (one buffer): UNABLE_TO_GET_CRL unable to get certificate CRL
revoked-isrgrootx1.letsencrypt.org (array): CERT_REVOKED certificate revoked
valid-isrgrootx1.letsencrypt.org (one buffer): UNABLE_TO_GET_CRL unable to get certificate CRL
valid-isrgrootx1.letsencrypt.org (array): handshake ok
Les deux options de curl se comportent différemment. --cert-status exige une réponse OCSP agrafée : elle échoue donc sur tous les serveurs qui ne pratiquent pas l’agrafage, y compris tous les sites Let’s Encrypt. --crlfile fonctionne avec un fichier contenant les CRL de toute la chaîne :
$ curl -s -o /dev/null -w "HTTP %{http_code}\n" https://revoked-isrgrootx1.letsencrypt.org/
HTTP 200
$ curl -sS -o /dev/null --cert-status https://revoked-isrgrootx1.letsencrypt.org/
curl: (91) No OCSP response received
$ curl -sS -o /dev/null --crlfile revoked-chain-crls.pem https://revoked-isrgrootx1.letsencrypt.org/
curl: (60) SSL certificate problem: certificate revoked
$ curl -s -o /dev/null -w "HTTP %{http_code}\n" --crlfile valid-chain-crls.pem https://valid-isrgrootx1.letsencrypt.org/
HTTP 200
$ curl -sS -o /dev/null --crlfile leaf-crl-only.pem https://valid-isrgrootx1.letsencrypt.org/
curl: (60) SSL certificate problem: unable to get certificate CRL
Ce script récupère la CRL de chaque certificat envoyé par un serveur :
#!/bin/bash
host="$1"; out="$2"
dir=$(mktemp -d)
openssl s_client -connect "$host:443" -servername "$host" -showcerts </dev/null 2>/dev/null \
| awk -v d="$dir" '/BEGIN CERTIFICATE/{n++} n>0{print > (d "/cert" n ".pem")}'
: > "$out"
for cert in "$dir"/cert*.pem; do
url=$(openssl x509 -in "$cert" -noout -ext crlDistributionPoints 2>/dev/null | grep -o 'http[^ ]*' | head -1)
[ -n "$url" ] && curl -s "$url" | openssl crl -inform DER >> "$out"
done
rm -r "$dir"
echo "$host: $(grep -c 'BEGIN X509 CRL' "$out") CRLs"
Vérifier un certificat à la main
Avec OpenSSL, vous pouvez voir où se trouve le statut d’un certificat et le vérifier directement. Commencez par examiner les extensions :
$ openssl x509 -in leaf.pem -noout -ext crlDistributionPoints,authorityInfoAccess
Authority Information Access:
CA Issuers - URI:http://yr1.i.lencr.org/
X509v3 CRL Distribution Points:
Full Name:
URI:http://yr1.c.lencr.org/44.crl
Téléchargez ensuite la CRL et vérifiez le certificat par rapport à cette CRL. -untrusted fournit les certificats intermédiaires envoyés par le serveur :
$ curl -s -o crl.der http://yr1.c.lencr.org/44.crl
$ openssl crl -inform DER -in crl.der -out crl.pem
$ openssl verify -crl_check -CRLfile crl.pem -untrusted chain.pem leaf.pem
CN=revoked-isrgrootx1.letsencrypt.org
error 23 at 0 depth lookup: certificate revoked
error leaf.pem: verification failed
$ openssl crl -in crl.pem -noout -text | grep -A1 "Serial Number: 067E51010C975E496C8EB800F3EF1BAB3F2B"
Serial Number: 067E51010C975E496C8EB800F3EF1BAB3F2B
Revocation Date: Oct 7 18:37:19 2026 GMT
Pour une AC qui exploite encore OCSP, interrogez directement le répondeur ou examinez la réponse agrafée :
$ openssl ocsp -issuer issuer.pem -cert leaf.pem -url http://ocsp.digicert.com -resp_text -noverify
OCSP Response Status: successful (0x0)
Cert Status: good
This Update: Oct 19 22:09:00 2026 GMT
Next Update: Oct 26 21:09:00 2026 GMT
leaf.pem: good
This Update: Oct 19 22:09:00 2026 GMT
Next Update: Oct 26 21:09:00 2026 GMT
$ openssl s_client -connect www.digicert.com:443 -servername www.digicert.com -status </dev/null
OCSP Response Status: successful (0x0)
Cert Status: good
-noverify omet la vérification de la signature de la réponse, ce qui convient pour un rapide coup d’œil, mais pas pour prendre une décision. Le décodeur OCSP analyse les requêtes et les réponses, vérifie la signature du répondeur et son autorisation à répondre, et peut interroger un répondeur à votre place.
Que faire dans votre service
- Demandez-vous si vous avez réellement besoin de vérifier la révocation. Pour les appels sortants vers des API publiques, le modèle des navigateurs (durées de vie courtes, CT, réponse des AC aux incidents) suffit généralement. Pour les connexions à forte valeur, comme les callbacks de paiement, le mTLS entre organisations ou la signature de code et de documents, vérifiez explicitement la révocation.
- Prenez d’abord en charge les CRL. Les AC publiques doivent les publier, et certaines n’exploitent plus OCSP. Un client qui ne gère qu’OCSP ne pourra pas trancher pour les certificats de Let’s Encrypt et de Google Trust Services.
- Mettez les CRL en cache et actualisez-les selon leur calendrier. Récupérez chaque CRL une seule fois, conservez-la jusqu’à
nextUpdateet actualisez-la en arrière-plan, pour qu’un handshake TLS n’attende jamais un téléchargement. - Choisissez en connaissance de cause entre fail-open et fail-closed. En fail-closed (refus en cas d’échec), un problème de CDN chez l’AC devient votre panne. En fail-open (acceptation en cas d’échec), la vérification devient facultative pour quiconque peut bloquer le téléchargement de la CRL. Journalisez les deux issues.
- Supprimez les dépendances à Must-Staple et à l’agrafage OCSP. Let’s Encrypt rejette les demandes Must-Staple, et l’agrafage n’est utile qu’avec les AC qui exploitent encore OCSP.
- La PKI privée, c’est vous qui la concevez. Publiez des CRL avec un
nextUpdatecourt, ou émettez des certificats de courte durée et renoncez à la révocation, comme les BR le permettent désormais pour les certificats de 7 jours ou moins.
Sources
- CA/Browser Forum, Baseline Requirements v2.3.1 : §4.9.1.1 (délais de révocation), §4.9.7 (fréquence des CRL), §4.9.9 (OCSP), §7.1.2.11.2 (points de distribution de CRL), §7.2 (profil des CRL), définition du Short-lived Subscriber Certificate. Vote SC-063.
- RFC 5280 (CRL) et RFC 6960 (OCSP).
- Let’s Encrypt, Fin de la prise en charge d’OCSP en 2025.
- Mozilla, CRLite : une vérification rapide, confidentielle et exhaustive de la révocation des certificats dans Firefox (août 2025), et les notes de version de Firefox pour Android 145.
- Chromium, CRLSets.
- Apple Developer Forums, discussion 808875 (décembre 2025).
Les tests ont été effectués le 20 octobre 2026 dans node:24.22.0-trixie (OpenSSL 3.5.7, curl 8.14.1, Node.js 24.22.0, Python 3.13.5), eclipse-temurin:25.0.4.1_1-jdk-noble, golang:1.27.3-trixie, mcr.microsoft.com/dotnet/sdk:10.0.402-noble et mcr.microsoft.com/playwright:v1.64.1-noble (Chromium 156).