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

Поля сертификата X.509: разбор на реальных сертификатах

20 октября 2026 года мы получили цепочки сертификатов letsencrypt.org, github.com и one.one.one.one и прочитали их поле за полем с помощью OpenSSL 3.6.5, сверяясь с RFC 5280 и Базовыми требованиями (Baseline Requirements) CA/Browser Forum версии 2.3.1, которые определяют, что должно содержаться в каждом поле.

Коротко. TLS-клиент идентифицирует сервер только по альтернативному имени субъекта (Subject Alternative Name, SAN), то есть по записям dNSName и iPAddress, и никогда — по общему имени (Common Name, CN) субъекта. Key Usage (использование ключа) определяет, что ключу разрешено делать математически (подписывать, шифровать, подписывать сертификаты), а Extended Key Usage (расширенное использование ключа, EKU) — для каких применений (serverAuth, clientAuth, подпись кода). Право выпускать другие сертификаты даёт сертификату именно значение CA:TRUE в расширении Basic Constraints (основные ограничения); у конечного сертификата там стоит CA:FALSE. Authority Key Identifier и Subject Key Identifier — подсказки, которые помогают клиенту найти издателя, а не проверки безопасности. CRL Distribution Points и Authority Information Access сообщают клиенту, где проверить отзыв и откуда скачать недостающий промежуточный сертификат; в сертификатах Let's Encrypt URL OCSP больше нет. SCT доказывают, что сертификат записан в журналы Certificate Transparency, а этого требуют Chrome и Safari.

Где взять настоящий сертификат для разбора

openssl s_client -showcerts выводит все сертификаты, которые отдаёт сервер, а openssl x509 -text декодирует один из них. Все эксперименты выполнялись в debian:forky-20261005-slim с OpenSSL 3.6.5 и pyca/cryptography 49.0.0:

Сохранение цепочки, которую отдаёт сервер, по одному PEM-файлу на сертификат
openssl s_client -connect letsencrypt.org:443 -servername letsencrypt.org -showcerts </dev/null 2>/dev/null \
  | awk '/-----BEGIN CERTIFICATE-----/{n++; f=sprintf("letsencrypt.org-%d.pem", n); p=1} p{print >> f} /-----END CERTIFICATE-----/{p=0; close(f)}'
openssl x509 -in letsencrypt.org-1.pem -noout -text

-servername передаёт SNI: без него многие серверы возвращают сертификат по умолчанию, выпущенный на другое имя. Вот конечный сертификат letsencrypt.org в том виде, в каком сервер отдавал его в тот день (открытый ключ показан целиком, две подписи SCT сокращены):

openssl x509 -text, конечный сертификат letsencrypt.org (подписи SCT сокращены)
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            05:43:93:3b:e3:86:b3:a2:b3:ad:d5:34:f2:10:3b:c8:b6:5f
        Signature Algorithm: ecdsa-with-SHA384
        Issuer: C=US, O=Let's Encrypt, CN=YE2
        Validity
            Not Before: Sep  4 14:34:32 2026 GMT
            Not After : Dec  3 14:34:31 2026 GMT
        Subject: CN=letsencrypt.org
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:e9:e5:40:7b:43:1e:46:b9:8e:45:e4:aa:cb:40:
                    ab:e6:3f:99:27:b8:b7:81:e3:32:56:e7:14:74:5f:
                    fa:43:5b:52:c6:a6:b5:66:c5:56:0e:07:21:56:e3:
                    4c:fe:35:55:4f:f0:92:2f:eb:22:4a:f1:7a:39:51:
                    39:04:c2:b3:0d
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier: 
                16:68:8D:1E:2A:32:48:8C:B5:F1:10:0A:16:08:37:08:3B:9D:30:23
            X509v3 Authority Key Identifier: 
                B9:59:F2:8E:CF:22:F0:86:D3:37:48:FF:76:14:18:BA:82:D8:55:87
            Authority Information Access: 
                CA Issuers - URI:http://ye2.i.lencr.org/
            X509v3 Subject Alternative Name: 
                DNS:cp.letsencrypt.org, DNS:cp.root-x1.letsencrypt.org, DNS:cps.letsencrypt.org, DNS:cps.root-x1.letsencrypt.org, DNS:lencr.org, DNS:letsencrypt.com, DNS:letsencrypt.org, DNS:www.lencr.org, DNS:www.letsencrypt.com, DNS:www.letsencrypt.org
            X509v3 Certificate Policies: 
                Policy: 2.23.140.1.2.1
            X509v3 CRL Distribution Points: 
                Full Name:
                  URI:http://ye2.c.lencr.org/96.crl

            CT Precertificate SCTs: 
                Signed Certificate Timestamp:
                    Version   : v1 (0x0)
                    Log ID    : D8:09:55:3B:94:4F:7A:FF:C8:16:19:6F:94:4F:85:AB:
                                B0:F8:FC:5E:87:55:26:0F:15:D1:2E:72:BB:45:4B:14
                    Timestamp : Sep  4 15:33:02.377 2026 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:46:02:21:00:D0:2E:25:A4:9D:7F:C8:18:D6:7B:5E:...
                Signed Certificate Timestamp:
                    Version   : v1 (0x0)
                    Log ID    : 46:AF:86:3D:3B:3E:E5:9F:A5:77:DE:A8:24:5D:36:B0:
                                D9:ED:22:A2:23:F4:61:77:41:22:94:52:EE:95:50:5F
                    Timestamp : Sep  4 15:33:02.552 2026 GMT
                    Extensions: 00:00:05:00:24:DF:DB:0C
                    Signature : ecdsa-with-SHA256
                                30:44:02:20:31:2F:83:D6:42:65:26:0D:58:D7:DD:C3:...
    Signature Algorithm: ecdsa-with-SHA384
    Signature Value:
        30:65:02:31:00:ee:1d:b0:7c:35:88:98:15:57:b8:88:73:23:
        92:a9:bf:40:bf:e8:ae:02:48:e6:f4:7a:2d:18:fc:43:52:d3:
        80:9a:ff:ce:e0:2a:0d:4a:29:e6:80:a2:94:f5:4f:14:d9:02:
        30:35:08:99:3b:bc:89:5b:23:a0:ef:64:24:e2:b2:b3:b8:c2:
        d8:d5:95:45:de:f3:03:e8:e7:82:78:a5:9e:c2:1f:ad:eb:14:
        fc:39:51:c7:e1:18:e9:16:b4:a5:48:de:09

Дальше мы разберём его сверху вниз. Саму структуру определяет RFC 5280 (поля описаны в § 4.1, расширения — в § 4.2), а что может содержать публично доверенный TLS-сертификат, сказано в Базовых требованиях (Baseline Requirements, BR) CA/Browser Forum, версия 2.3.1 от 4 октября 2026 года, § 7.1.

Версия, серийный номер, алгоритм подписи, срок действия

  • Версия 3 — единственная версия с расширениями; BR требуют именно её.
  • Серийный номер. Уникален в пределах одного издателя, а у публичных CA это «непоследовательное число больше нуля (0) и меньше 2¹⁵⁹, содержащее не менее 64 бит выходных данных CSPRNG» (BR, § 7.1.2.7). Случайность здесь защищает от атак на хеш подписи, основанных на коллизиях с выбранным префиксом (chosen-prefix collision); из-за неё серийные номера и выглядят как шум. Именно серийные номера перечисляются в CRL.
  • Signature Algorithm (алгоритм подписи) встречается дважды: внутри подписанной части и рядом с самой подписью, и оба значения должны совпадать байт в байт. ecdsa-with-SHA384 здесь говорит о ключе издателя (у YE2 ключ P-384), а не субъекта. Значение подписи в конце — подпись ECDSA в DER; этот формат разобран в статье Форматы подписей ECDSA.
  • Срок действия. Моменты notBefore и notAfter входят в срок действия. Этот сертификат действует 90 дней; максимум по BR — 200 дней для сертификатов, выпущенных с 15 марта 2026 года, 100 дней для выпущенных с 15 марта 2027 года и 47 дней для выпущенных с 15 марта 2029 года (BR, § 6.3.2). Let's Encrypt уже выпускает 45-дневные сертификаты в профиле tlsserver, который нужно выбрать явно, и планирует сократить срок по умолчанию до 64 дней 10 февраля 2027 года и до 45 дней 16 февраля 2028 года.

Субъект и альтернативное имя субъекта

Субъект — это отличительное имя (distinguished name, DN), здесь в нём только CN=letsencrypt.org. В сертификатах с проверкой домена больше ничего нет, потому что ничего другого CA и не проверял. Имена, которые сверяет клиент, находятся в альтернативном имени субъекта (SAN): у этого сертификата там 10 записей DNS:.

CN давно перестал учитываться. RFC 9525 (2023), заменивший RFC 6125, гласит: «RDN Common Name не должен (MUST NOT) использоваться для идентификации сервиса». Chrome перестал сверять имя с CN в версии 58 (2017), а Go отклоняет сертификаты, в которых есть только CN, с ошибкой x509: certificate relies on legacy Common Name field. BR оставляют CN только как копию: если он есть, он «должен (MUST) содержать ровно одну запись, совпадающую с одним из значений расширения subjectAltName сертификата» (§ 7.1.4.3).

В SAN можно указывать и IP-адреса. Сертификат DNS-резолвера Cloudflare:

openssl x509 -noout -subject -ext subjectAltName (конечный сертификат one.one.one.one)
subject=C=US, ST=California, L=San Francisco, O=Cloudflare, Inc., CN=cloudflare-dns.com
X509v3 Subject Alternative Name: 
    DNS:cloudflare-dns.com, DNS:*.cloudflare-dns.com, IP Address:1.0.0.1, IP Address:1.1.1.1, IP Address:162.159.36.1, IP Address:162.159.46.1, IP Address:2606:4700:4700:0:0:0:0:1001, IP Address:2606:4700:4700:0:0:0:0:1111, IP Address:2606:4700:4700:0:0:0:0:64, IP Address:2606:4700:4700:0:0:0:0:6400, DNS:one.one.one.one

https://1.1.1.1 работает, потому что 1.1.1.1 записан как iPAddress; запись DNS:1.1.1.1 не совпала бы с IP-адресом — об этом и говорит ошибка Go cannot validate certificate for … because it doesn't contain any IP SANs. В субъекте здесь указаны ещё организация и местонахождение, а сертификат содержит идентификатор политики OV 2.23.140.1.2.2 (см. DV, OV или EV); браузер ничего из этого не показывает. Сертификат выпущен 31 декабря 2025 года на 355 дней, ещё до того, как вступило в силу ограничение в 200 дней.

Wildcard-имя вида *.cloudflare-dns.com соответствует ровно одной метке: a.cloudflare-dns.com, но не самому cloudflare-dns.com и не a.b.cloudflare-dns.com (RFC 9525, § 6.3). OpenSSL сверяет имена так же:

openssl x509 -noout -checkhost <name> (конечный сертификат letsencrypt.org)
Hostname www.lencr.org does match certificate
Hostname lencr.org.evil.test does NOT match certificate
Hostname mail.letsencrypt.org does NOT match certificate

Открытый ключ субъекта (SPKI)

Поле subjectPublicKeyInfo (SPKI) содержит алгоритм и сам ключ — несжатую точку P-256 (04, за которым следуют 32-байтовые координаты x и y). Это единственная часть сертификата, с которой связан закрытый ключ субъекта, поэтому при закреплении открытого ключа хешируют SPKI, а не весь сертификат: после продления с тем же ключом пин остаётся прежним (см. статью Закрепление сертификатов в 2026 году).

Key Usage и Extended Key Usage

Эти расширения отвечают на два разных вопроса:

  • Key Usage (RFC 5280, § 4.2.1.3) ограничивает криптографические операции ключа: digitalSignature, keyEncipherment, keyAgreement, keyCertSign, cRLSign и несколько более редких битов. BR требуют, чтобы в конечном сертификате с ключом ECDSA был digitalSignature, и запрещают keyEncipherment (ключом EC нельзя шифровать). В конечный сертификат с ключом RSA можно добавить keyEncipherment для устаревшего обмена ключами RSA в TLS 1.2 — BR называют это поддержкой «небезопасных наборов шифров» (insecure ciphersuites). Кроме того, по BR это расширение следует (SHOULD) включать и помечать как критическое, как здесь.
  • Extended Key Usage (§ 4.2.1.12) перечисляет применения: serverAuth (1.3.6.1.5.5.7.3.1), clientAuth (…3.2), codeSigning (…3.3), emailProtection (…3.4), timeStamping (…3.8). Серверный TLS-сертификат обязан (MUST) содержать serverAuth и не должен (MUST NOT) содержать назначения для подписи кода, электронной почты, меток времени и подписи ответов OCSP, а также anyExtendedKeyUsage (BR, § 7.1.2.7.10).

Изменилось именно то, что касается clientAuth. BR по-прежнему его разрешают (MAY), но политика Chrome Root Program версии 1.8 требует иерархий, «выделенных для аутентификации TLS-серверов»: подчинённые CA, сведения о которых раскрыты начиная с 15 июня 2026 года, могут указывать только serverAuth, а с 15 марта 2027 года то же обязательно и для выпускаемых сертификатов подписчиков. Let's Encrypt убрал clientAuth из профиля по умолчанию 11 февраля 2026 года и полностью перестал выпускать сертификаты с ним 8 июля 2026 года; в конечном сертификате letsencrypt.org выше есть только serverAuth. Если ваш сервис аутентифицировал клиентов по публичным TLS-сертификатам, ему нужен частный CA; подробнее — в статье Взаимный TLS (mTLS) для API.

Проверяющие стороны действительно учитывают EKU. Вот проверка сертификата, выпущенного одноразовым тестовым CA, с единственным значением EKU clientAuth:

openssl verify -CAfile root.pem -purpose sslserver worker-7.pem, затем -purpose sslclient
CN=worker-7
error 26 at 0 depth lookup: unsuitable certificate purpose
error worker-7.pem: verification failed
worker-7.pem: OK

Это ошибка 26 OpenSSL; Go в том же случае выдаёт ошибку x509: certificate specifies an incompatible key usage.

Basic Constraints: кто может выпускать сертификаты

Basic Constraints содержит один логический флаг и одно число. CA:TRUE разрешает ключу сертификата подписывать другие сертификаты, а pathlen ограничивает, сколько промежуточных CA может следовать ниже него. RFC 5280, § 4.2.1.9, прямо говорит, что без установленного cA «заверенный открытый ключ не должен (MUST NOT) использоваться для проверки подписей сертификатов». Вот как это выглядит вверх по цепочке letsencrypt.org:

СертификатBasic ConstraintsKey Usage
CN=letsencrypt.orgкритическое, CA:FALSEDigital Signature
CN=YE2 (промежуточный)критическое, CA:TRUE, pathlen:0Digital Signature, Certificate Sign, CRL Sign
CN=Root YE (с перекрёстной подписью ISRG Root X2)критическое, CA:TRUECertificate Sign, CRL Sign

pathlen:0 у YE2 означает, что он может выпускать только конечные сертификаты. Чтобы посмотреть, как соблюдаются эти правила, мы подписали другой сертификат ключом обычного серверного сертификата (CA:FALSE, Key Usage digitalSignature):

openssl verify -CAfile root.pem -untrusted api.example.test.pem forged.example.test.pem
CN=api.example.test
error 79 at 1 depth lookup: invalid CA certificate
CN=api.example.test
error 32 at 1 depth lookup: key usage does not include certificate signing
error forged.example.test.pem: verification failed

Срабатывают оба правила: ошибка 79 из-за отсутствия CA:TRUE и ошибка 32 из-за отсутствия keyCertSign. Именно из-за пропуска этой проверки Internet Explorer в 2002 году принимал любой конечный сертификат в качестве CA. Python начиная с версии 3.13 включает и строгий режим OpenSSL, который дополнительно отклоняет сертификаты CA, где расширение Basic Constraints не помечено как критическое.

Идентификаторы ключа издателя и субъекта

Subject Key Identifier (SKI, идентификатор ключа субъекта) указывает на собственный ключ сертификата, а Authority Key Identifier (AKI, идентификатор ключа издателя) — на ключ, которым сертификат подписан. Каждый AKI равен SKI следующего сертификата вверх по цепочке:

Субъект, SKI и AKI каждого сертификата, который отдаёт letsencrypt.org
  subject=CN=letsencrypt.org
  16:68:8D:1E:2A:32:48:8C:B5:F1:10:0A:16:08:37:08:3B:9D:30:23
  B9:59:F2:8E:CF:22:F0:86:D3:37:48:FF:76:14:18:BA:82:D8:55:87
  subject=C=US, O=Let's Encrypt, CN=YE2
  B9:59:F2:8E:CF:22:F0:86:D3:37:48:FF:76:14:18:BA:82:D8:55:87
  A3:C8:26:5A:8E:A1:4C:D0:35:63:FC:9B:23:C8:3A:AE:56:F3:4F:56
  subject=C=US, O=ISRG, CN=Root YE
  A3:C8:26:5A:8E:A1:4C:D0:35:63:FC:9B:23:C8:3A:AE:56:F3:4F:56
  7C:42:96:AE:DE:4B:48:3B:FA:92:F8:9E:8C:CF:6D:8B:A9:72:37:95
  subject=C=US, O=Internet Security Research Group, CN=ISRG Root X2
  7C:42:96:AE:DE:4B:48:3B:FA:92:F8:9E:8C:CF:6D:8B:A9:72:37:95
  79:B4:59:E6:7B:B6:E5:E4:01:73:80:08:88:C8:1A:58:F6:E9:9B:6E

Идентификаторы ключей помогают проверяющей стороне выбрать нужного издателя, когда у нескольких сертификатов CA одно и то же имя (CA со сменённым ключом или с перекрёстной подписью, как два существующих сертификата ISRG Root X2). Сами они ничего не доказывают: это делает проверка подписи. RFC 5280 требует AKI в каждом сертификате, кроме самоподписанных корневых, и SKI в каждом сертификате CA; RFC 5280 предлагает вычислять SKI, например, как хеш SHA-1 от ключа, но издатели могут использовать любое уникальное значение. BR относят SKI в конечном сертификате к «не рекомендуемым» (NOT RECOMMENDED) полям, но и Let's Encrypt, и Sectigo по-прежнему его включают. Строгие проверяющие стороны отклоняют сертификат CA без AKI, как показывает ошибка Python Missing Authority Key Identifier.

Цепочка показывает и то, что путь, который вы проверяете, не всегда совпадает с тем, что отдаёт сервер. letsencrypt.org отдаёт 4 сертификата, последний из которых — ISRG Root X2 с перекрёстной подписью ISRG Root X1; OpenSSL с хранилищем доверенных сертификатов Debian останавливается на корневом X2, которому уже доверяет:

openssl verify -show_chain -untrusted untrusted.pem letsencrypt.org-1.pem
letsencrypt.org-1.pem: OK
Chain:
depth=0: CN=letsencrypt.org (untrusted)
depth=1: C=US, O=Let's Encrypt, CN=YE2 (untrusted)
depth=2: C=US, O=ISRG, CN=Root YE (untrusted)
depth=3: C=US, O=Internet Security Research Group, CN=ISRG Root X2

CRL Distribution Points, AIA и OCSP

Два расширения содержат URL:

  • CRL Distribution Points (точки распространения CRL, RFC 5280, § 4.2.1.13) указывает, где издатель публикует свой CRL. BR требуют это расширение, кроме случаев, когда сертификат краткосрочный (с 15 марта 2026 года — 7 дней или меньше) или содержит URL OCSP, а сам URL должен быть обычным http: CRL подписан, поэтому TLS ничего не добавляет и создал бы замкнутый круг.
  • Authority Information Access (AIA, § 4.2.2.1): CA Issuers — откуда скачать сертификат выпустившего CA, а OCSP — адрес ответчика OCSP издателя.

В конечном сертификате Let's Encrypt есть URL CRL и нет URL OCSP: в мае 2025 года Let's Encrypt убрал URL OCSP из сертификатов, а 6 августа 2025 года отключил свои ответчики, после того как BR сделали OCSP необязательным. Sectigo по-прежнему поддерживает OCSP; в конечном сертификате github.com есть оба метода доступа и две политики:

openssl x509 -text, конечный сертификат github.com (фрагмент)
            X509v3 Certificate Policies: 
                Policy: 1.3.6.1.4.1.6449.1.2.2.7
                  CPS: https://sectigo.com/CPS
                Policy: 2.23.140.1.2.1
            Authority Information Access: 
                CA Issuers - URI:http://crt.sectigo.com/SectigoPublicServerAuthenticationCADVE36.crt
                OCSP - URI:http://ocsp.sectigo.com

Скачаем CRL, указанный в конечном сертификате letsencrypt.org, и проверим его по сертификату YE2:

Скачивание CRL, указанного в сертификате, и проверка его подписи
crl=$(openssl x509 -in letsencrypt.org-1.pem -noout -ext crlDistributionPoints | grep -o 'http://[^ ]*')
curl -sS -o leaf.crl "$crl"
openssl crl -inform DER -in leaf.crl -noout -issuer -lastupdate -nextupdate
openssl crl -inform DER -in leaf.crl -noout -text | grep -c 'Serial Number:'
openssl crl -inform DER -in leaf.crl -noout -CAfile letsencrypt.org-2.pem
Вывод
issuer=C=US, O=Let's Encrypt, CN=YE2
lastUpdate=Oct 20 15:03:57 2026 GMT
nextUpdate=Oct 29 15:03:56 2026 GMT
1590
verify OK

96.crl в URL — это шард: CA распределяет отозванные сертификаты по множеству небольших CRL и в каждом сертификате указывает нужный, поэтому клиент скачивает 1 590 записей, а не сведения обо всех отозванных сертификатах. Браузеры редко загружают CRL в момент проверки: Chrome и Firefox сами доставляют данные об отзыве на устройства пользователей. Плюсы и минусы обоих подходов разобраны в сравнении CRL или OCSP.

CA Issuers важен, когда сервер забывает отдать свой промежуточный сертификат. Браузеры и системные средства проверки Windows и macOS переходят по этому URL (так называемый AIA chasing); OpenSSL (а вместе с ним curl, Python и PHP в Linux), Go в Linux и Java с настройками по умолчанию — нет, поэтому один и тот же неправильно настроенный сервер работает в браузере, а в коде выдаёт ошибку «unable to get local issuer certificate».

Политики сертификата

Каждый публичный TLS-сертификат содержит ровно один идентификатор политики CA/Browser Forum, который говорит, как проверяли субъекта: 2.23.140.1.2.1 — проверка домена, 2.23.140.1.2.2 — проверка организации, 2.23.140.1.2.3 — проверка физического лица, 2.23.140.1.1 — расширенная проверка (BR, § 7.1.6.1). CA добавляют собственные OID политик и ссылку на свой регламент сертификации (CPS), как Sectigo выше. Клиенты, которые показывают уровень проверки, читают это расширение (Certificate Policies), а не субъект.

Подписанные метки времени сертификата (SCT)

Перед выпуском CA отправляет предсертификат (то же содержимое плюс poison-расширение) в журналы Certificate Transparency, и каждый журнал отвечает подписанным обещанием опубликовать его — SCT. CA встраивает эти SCT в итоговый сертификат (OID 1.3.6.1.4.1.11129.2.4.2, RFC 6962, § 3.3). ID журнала — это хеш ключа журнала; список журналов CT в Chrome сопоставляет ему название:

scts.py: названия журналов CT для каждого встроенного SCT (cryptography 49.0.0)
"""List the SCTs embedded in a certificate and name their logs from Chrome's CT log list."""
import base64
import json
import sys

from cryptography import x509

log_list = json.load(open(sys.argv[1]))
logs = {}
for operator in log_list["operators"]:
    for log in operator["logs"] + operator.get("tiled_logs", []):
        logs[base64.b64decode(log["log_id"])] = (operator["name"], log["description"])

cert = x509.load_pem_x509_certificate(open(sys.argv[2], "rb").read())
scts = cert.extensions.get_extension_for_class(x509.PrecertificateSignedCertificateTimestamps).value
for sct in scts:
    operator, name = logs.get(sct.log_id, ("unknown operator", "unknown log"))
    print(f"{sct.timestamp:%Y-%m-%d %H:%M:%S} UTC  {operator}: {name}  ({sct.entry_type.name})")
python3 scts.py log_list.json letsencrypt.org-1.pem; затем github.com-1.pem
2026-09-04 15:33:02 UTC  Google: Google Xenon2026h2  (PRE_CERTIFICATE)
2026-09-04 15:33:02 UTC  Geomys: Geomys Tuscolo2026h2  (PRE_CERTIFICATE)
2026-09-01 00:10:32 UTC  Google: Google Argon2026h2  (PRE_CERTIFICATE)
2026-09-01 00:10:31 UTC  Sectigo: Sectigo Tiger2026h2  (PRE_CERTIFICATE)

Список журналов скачан с https://www.gstatic.com/ct/log_list/v3/log_list.json в тот же день. В каждом сертификате по два SCT от двух разных операторов — именно этого требует политика CT в Chrome для сертификата со сроком действия не более 180 дней (для более долгих — три). Extensions: 00:00:05:00:24:DF:DB:0C в SCT от Tuscolo — это расширение leaf_index, которое обязаны добавлять журналы, реализующие static CT API: тип 0, длина 5 и 40-битный индекс, здесь это запись 618 650 380 в этом журнале. Благодаря SCT любой может найти все сертификаты, выпущенные для его домена, а ошибочно выпущенный сертификат не может остаться тайной. Декодер SCT декодирует их без командной строки.

Типичные ошибки

  • Имя указано только в CN. Браузеры выдают NET::ERR_CERT_COMMON_NAME_INVALID, Java — No subject alternative names present. Каждое имя, в том числе то, что стоит в CN, должно быть в SAN. Чаще всего на это натыкаются с внутренними CA и однострочными командами openssl req, которые задают только -subj.
  • Нет промежуточного сертификата. Благодаря AIA chasing в браузерах и системных средствах проверки Windows и macOS всё работает, а в клиентах на базе OpenSSL, в Go в Linux и в Java — нет. Отдавайте полную цепочку (первым — конечный сертификат, корневой можно не включать).
  • Расчёт на clientAuth в публичном сертификате. Это значение исчезает из публичных TLS-иерархий; интеграциям с партнёрами, которые рассчитаны на «любой публичный сертификат с clientAuth», нужен частный CA.
  • Сертификат CA с CA:TRUE, но без keyCertSign или без пометки о критичности. Строгие проверяющие стороны (Python 3.13+, Go для некоторых полей) его отклоняют; в Go это ошибка parent certificate cannot sign this kind of certificate.
  • SKI вместо отпечатка. В SKI лежит то, что туда записал CA. Чтобы идентифицировать сертификат, хешируйте весь DER (openssl x509 -fingerprint -sha256), а чтобы идентифицировать ключ — SPKI.
  • Чтение срока действия по местному времени. notBefore и notAfter заданы в UTC. Сервер, часы которого отстают на несколько часов, отклонит только что выпущенный сертификат как ещё не действительный.

Источники