Перейти к содержимому
ecdsa.com
← Все статьи

OCSP и CRL в 2026 году: как теперь проверяют отзыв

Отзыв сертификатов незаметно изменился. OCSP стал необязательным и постепенно уходит, CRL вернулись, браузеры вместо запросов к CA сверяются с присылаемыми им списками, а большинство HTTP-клиентов по-прежнему ничего не проверяют, если их об этом не попросить. Каждый клиент, о котором идёт речь ниже, мы проверили на отозванном сертификате.

Коротко. С марта 2024 года CA/Browser Forum требует, чтобы центры сертификации (CA) публиковали CRL, а OCSP сделал необязательным. Let's Encrypt убрал URL OCSP из своих сертификатов в мае 2025 года, а в августе 2025 года отключил свои ответчики OCSP (OCSP responders). Браузеры больше не спрашивают CA о каждом сертификате. Firefox сверяется с локальным фильтром CRLite, Chrome — с CRLSet, который ему присылают, а Apple использует собственный сводный список. Большинство библиотек и инструментов командной строки (Java, .NET, Go, Python, Node.js, curl) по умолчанию вообще не проверяют отзыв. В наших тестах все они приняли отозванный сертификат Let's Encrypt. Если ваш клиент должен отклонять отозванные сертификаты, явно включите проверку по CRL и готовьтесь к коротким срокам действия сертификатов. К ним отрасль и так движется.

Почему отзыв пришлось переосмыслить

Сертификат могут отозвать до истечения срока действия, обычно из-за утечки ключа или выпуска по ошибке. Классических способов сообщить об этом клиентам было два:

  • CRL (Certificate Revocation List, список отзыва сертификатов, RFC 5280): CA публикует подписанный список серийных номеров отозванных сертификатов. Сертификат указывает на этот список в расширении CRL Distribution Points.
  • OCSP (RFC 6960): клиент отправляет ответчику CA запрос об одном сертификате и получает подписанный ответ «good», «revoked» или «unknown». URL ответчика записан в расширении Authority Information Access.

В вебе у обоих способов были проблемы. CRL могли быть большими. OCSP раскрывал центру сертификации каждый сайт, который посещал пользователь, добавлял задержку, а когда ответчик был недоступен, браузерам приходилось действовать по принципу soft-fail, то есть всё равно принимать сертификат. Злоумышленник, способный заблокировать запрос OCSP, делает такую проверку бесполезной. OCSP stapling решил проблему конфиденциальности: ответ получает и прикрепляет сам сервер. Но повсеместным этот механизм так и не стал. Must-Staple, флаг сертификата, который требует прикреплённого ответа, почти не получил поддержки в браузерах.

Что теперь говорят правила

Бюллетень SC-063 («Сделать OCSP необязательным, CRL — обязательными и стимулировать автоматизацию», вступил в силу 15 марта 2024 года) изменил Базовые требования (Baseline Requirements, BR):

  • CRL обязательны. CA должен опубликовать полный или разбитый на шарды CRL в течение 24 часов после выпуска своего первого сертификата. Обновлять CRL он обязан не реже чем раз в 7 дней, если все выпущенные им сертификаты содержат URL OCSP, в остальных случаях — не реже чем раз в 4 дня, а также в течение 24 часов после отзыва сертификата (§ 4.9.7). Для конечных сертификатов (subscriber certificates) nextUpdate может отстоять от thisUpdate не более чем на 10 дней (§ 7.2).
  • OCSP необязателен. Если в сертификате указан URL OCSP, ответчик обязан отвечать, а его ответы должны быть действительны от 8 часов до 10 дней (§ 4.9.9). Сертификат без URL OCSP должен содержать точку распространения CRL (§ 7.1.2.11.2).
  • Краткосрочным сертификатам сведения об отзыве не нужны. Краткосрочный сертификат (Short-lived Subscriber Certificate) действует не более 7 дней, если выпущен 15 марта 2026 года или позже (до этой даты — не более 10 дней). CA может поддерживать отзыв такого сертификата, а точка распространения CRL для него необязательна.
  • Сроки отзыва. 24 часа при компрометации ключа и в похожих случаях, 5 дней в остальных (§ 4.9.1.1).

После этого Let's Encrypt полностью отказался от OCSP, «прежде всего потому, что он представляет значительный риск для конфиденциальности в интернете»:

  • 30 января 2025 года: новые запросы сертификатов с Must-Staple завершаются ошибкой.
  • 7 мая 2025 года: новые сертификаты выходят без URL OCSP и содержат вместо них URL CRL.
  • 6 августа 2025 года: ответчики OCSP отключаются.

Что содержат сертификаты сегодня

Если 20 октября 2026 года проверить несколько сайтов на наличие URL OCSP, URL CRL и прикреплённого ответа OCSP, картина получается неоднородной:

ocsp-survey.sh, 20 октября 2026 г. (OpenSSL 3.5.7)
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 и Google Trust Services выпускают сертификаты только с CRL. Сертификат GitHub от Sectigo — зеркальный случай: только OCSP, и BR это допускают. Клиент, который поддерживает лишь один из двух способов, не сможет проверить все эти сертификаты.

CRL не назовёшь маленькими, но их размер вполне приемлем:

Три реальных CRL (20 октября 2026 г.)
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 разбивает свои CRL на шарды: каждый сертификат указывает на один из множества пронумерованных файлов, а расширение Issuing Distribution Point в самом CRL называет этот шард. Декодер CRL читает любой из этих файлов, проверяет его подпись по сертификату издателя и показывает, есть ли в нём нужный сертификат.

Как браузеры проверяют отзыв

Ни один из основных браузеров больше не отправляет запросы OCSP для обычных сертификатов:

  • Firefox: CRLite. Firefox скачивает сжатое представление всех отозванных сертификатов, которые есть в журналах CT, обновляет его каждые 12 часов и проверяет сертификаты локально. По данным Mozilla, это в среднем около 300 КБ данных в день. Начиная с Firefox 137 CRLite включён у всех пользователей настольной версии. Firefox 142 перестал использовать OCSP для сертификатов с проверкой домена (DV), а Firefox для Android перешёл на CRLite в версии 145.
  • Chrome: CRLSets. Chrome рассылает список, собранный из CRL центров сертификации, но в него входит лишь «подмножество сертификатов, признанных отозванными», а «Chrome, как правило, не выполняет онлайн-проверки (т. е. OCSP и CRL)» (Chromium). В нашем тесте Chromium 156 открыл https://revoked-isrgrootx1.letsencrypt.org/ с ответом HTTP 200.
  • Apple: сводный список. Apple собирает данные об отзыве у центров сертификации и распространяет их на устройства. В декабре 2025 года техническая поддержка разработчиков Apple объяснила, что сертификаты «оказываются в „чёрном“ списке Apple» только при отзыве по таким причинам, как компрометация ключа, и что для сертификатов Let's Encrypt дополнительной проверки через OCSP нет. Поэтому Safari может по-прежнему принимать сертификат, отозванный по обычной причине.

На практике это значит, что в браузерах отзыв сертификата не работает как быстрый «аварийный выключатель». Firefox узнаёт об отзыве из обновлений CRLite, которые приходят каждые 12 часов. Chrome и Safari могут так и не заблокировать сертификат, отозванный по обычной причине. Реальный предел для утёкшего ключа — оставшийся срок действия сертификата, и это одна из причин перехода на 47-дневные сертификаты.

Что проверяют библиотеки и инструменты по умолчанию

20 октября 2026 года каждый из клиентов ниже подключался к https://revoked-isrgrootx1.letsencrypt.org/ (сертификат отозван 7 октября 2026 года) и к https://valid-isrgrootx1.letsencrypt.org/. В отозванном сертификате есть URL CRL, но нет URL OCSP.

КлиентПо умолчанию (сайт с отозванным сертификатом)Какая проверка отзыва сработала
Java 25 (JSSE)HTTP 200-Dcom.sun.net.ssl.checkRevocation=true -Dcom.sun.security.enableCRLDP=true
.NET 10 HttpClientHTTP 200SslOptions.CertificateRevocationCheckMode = X509RevocationMode.Online
Go 1.27 crypto/tls200 OKСобственная проверка в tls.Config.VerifyConnection
Python 3.13 sslрукопожатие успешноVERIFY_CRL_CHECK_LEAF и CRL, загруженный в контекст
Node.js 24 tlsрукопожатие успешноПараметр crl, по одному CRL на элемент массива, для каждого уровня цепочки
curl 8.14 (OpenSSL)HTTP 200--crlfile с CRL для каждого уровня цепочки
Chromium 156HTTP 200Нет (только CRLSet)

Ниже — подробности по каждому клиенту.

Java

JSSE не проверяет отзыв, если свойство com.sun.net.ssl.checkRevocation не равно true. Но и тогда он использует только OCSP, если не включить ещё и точки распространения CRL (CRL Distribution Points). Поэтому сертификат Let's Encrypt отклоняется как сертификат с неизвестным статусом, а не как отозванный:

JDK 25.0.4.1, revoked-isrgrootx1.letsencrypt.org
--- 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

Когда проверка включена, валидатор PKIX жёстко отклоняет сертификат, если не может определить его статус. Это безопасно, но тогда сбой сервера CRL становится и вашим сбоем. java.security.cert.PKIXRevocationChecker позволяет для каждого соединения выбрать порядок OCSP и CRL и параметры soft-fail.

.NET

HttpClient с SocketsHttpHandler по умолчанию не проверяет отзыв. Если установить режим Online, .NET будет сам скачивать CRL или ответ OCSP. В Linux он делает это через собственную реализацию поверх OpenSSL:

.NET 10: проверка отзыва для одного HttpClient
var handler = new SocketsHttpHandler();
handler.SslOptions.CertificateRevocationCheckMode = X509RevocationMode.Online;
using var client = new HttpClient(handler);
.NET SDK 10.0.402 в Ubuntu 24.04
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 проверяет цепочку и имя, но отзыв не проверяет никогда. Если такая проверка нужна, сверяйтесь с CRL сами после проверки цепочки. Самую сложную часть работы берут на себя x509.ParseRevocationList и CheckSignatureFrom:

Go 1.27: проверка CRL в VerifyConnection
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}}}
Go 1.27.3: клиент по умолчанию, затем с 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

Пример проверяет только конечный сертификат и скачивает CRL при каждом рукопожатии. В рабочей среде кешируйте каждый CRL до его NextUpdate и решите, что должно происходить, когда сервер CRL недоступен.

Python, Node.js и curl

Все три используют проверку CRL из OpenSSL и рассчитывают, что CRL им передадите вы. Сами они их никогда не скачивают:

Python 3.13 (Debian 13) и Node.js 24.22.0, revoked-isrgrootx1.letsencrypt.org
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

Флаг ssl.VERIFY_CRL_CHECK_LEAF в Python проверяет только конечный сертификат, поэтому достаточно загрузить CRL конечного сертификата через ctx.load_verify_locations(cafile=...). Node.js и curl проверяют каждый сертификат цепочки, поэтому с одним лишь CRL конечного сертификата они выдают UNABLE_TO_GET_CRL (Node) или unable to get certificate CRL (curl) даже на сайте с действительным сертификатом. Передайте им CRL каждого сертификата цепочки. В Node.js 24 передавайте их массивом, по одной PEM-строке на каждый CRL. Один буфер с тремя склеенными CRL в нашем тесте всё равно приводил к ошибке UNABLE_TO_GET_CRL:

Node.js 24.22.0, 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

Два параметра curl ведут себя по-разному. --cert-status требует прикреплённого ответа OCSP, поэтому завершается ошибкой на любом сервере, который не прикрепляет ответ, в том числе на всех сайтах с сертификатами Let's Encrypt. --crlfile работает с файлом, в котором собраны CRL для всей цепочки:

curl 8.14.1 (OpenSSL 3.5.7)
$ 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

Этот скрипт собирает CRL каждого сертификата, который отдаёт сервер:

fetch-crls.sh HOST OUT: CRL всей цепочки сервера в формате PEM
#!/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"

Проверка сертификата вручную

С помощью OpenSSL можно увидеть, где публикуется статус сертификата, и проверить его напрямую. Сначала посмотрите на расширения:

Расширения отозванного тестового сертификата (OpenSSL 3.5.7)
$ 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

Затем скачайте CRL и проверьте по нему сертификат. Параметр -untrusted передаёт промежуточные сертификаты, которые отдал сервер:

openssl verify -crl_check (OpenSSL 3.5.7)
$ 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

Если CA всё ещё поддерживает OCSP, отправьте запрос его ответчику напрямую или посмотрите на прикреплённый ответ:

openssl ocsp и s_client -status, www.digicert.com (вывод отфильтрован через grep)
$ 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 пропускает проверку подписи ответа: для беглого просмотра это допустимо, но не для принятия решения. Декодер OCSP разбирает запросы и ответы, проверяет подпись ответчика и его полномочия, а также может сам отправить запрос ответчику.

Что делать в вашем сервисе

  • Решите, нужны ли вам проверки отзыва вообще. Для исходящих вызовов публичных API обычно достаточно браузерной модели (короткие сроки действия, CT, реакция CA на инциденты). Для особо важных соединений, таких как обратные вызовы платёжных систем, mTLS между организациями или подпись кода и документов, проверяйте отзыв явно.
  • Поддерживайте прежде всего CRL. Публичные CA обязаны их публиковать, а некоторые уже не поддерживают OCSP. Клиент, который умеет только OCSP, не сможет принять решение по сертификатам Let's Encrypt и Google Trust Services.
  • Кешируйте CRL и обновляйте их в сроки, которые задают сами списки. Скачивайте каждый CRL один раз, храните до nextUpdate и обновляйте в фоне, чтобы TLS-рукопожатие никогда не ждало скачивания.
  • Осознанно выбирайте между fail-open (при сбое пропускать) и fail-closed (при сбое отклонять). С fail-closed проблема в CDN центра сертификации становится вашим сбоем. С fail-open проверка становится необязательной для любого, кто может заблокировать скачивание CRL. Записывайте оба исхода в журнал.
  • Уберите зависимости от Must-Staple и OCSP stapling. Let's Encrypt отклоняет запросы с Must-Staple, а stapling полезен только для CA, которые ещё поддерживают OCSP.
  • Частную PKI вы проектируете сами. Публикуйте CRL с коротким nextUpdate или выпускайте краткосрочные сертификаты и обходитесь без отзыва — так, как BR теперь разрешают для сертификатов сроком 7 дней и меньше.

Источники

Тесты выполнены 20 октября 2026 года в контейнерах 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 и mcr.microsoft.com/playwright:v1.64.1-noble (Chromium 156).