本文へスキップ
ecdsa.com
← すべての記事

X.509 証明書のフィールドを実際の証明書で解説

2026年10月20日に letsencrypt.org、github.com、one.one.one.one の証明書チェーンを取得し、OpenSSL 3.6.5 でフィールドごとに読み解きました。各フィールドに何を記載しなければならないかを定める RFC 5280 と CA/Browser Forum のベースライン要件 2.3.1 も併せて参照しています。

要点:TLS クライアントがサーバーを識別するのはサブジェクト代替名(SAN)の dNSName と iPAddress のエントリだけで、サブジェクトのコモンネームは決して使いません。鍵用途(Key Usage)は鍵が数学的に何をしてよいか(署名、暗号化、証明書への署名)を示し、拡張鍵用途(Extended Key Usage)はどのアプリケーションに使えるか(serverAuth、clientAuth、コード署名)を示します。証明書がほかの証明書を発行できるかどうかを決めるのは基本制約の CA:TRUE で、リーフ証明書は CA:FALSE です。認証局鍵識別子とサブジェクト鍵識別子は、クライアントが発行者を見つけるためのヒントであり、セキュリティ上のチェックではありません。CRL 配布ポイントと Authority Information Access は、失効をどこで確認し、不足している中間証明書をどこから取得するかをクライアントに伝えます。Let's Encrypt の証明書には、もう OCSP の URL は含まれていません。SCT は、証明書が Certificate Transparency のログに記録されたことを証明するもので、Chrome と Safari は SCT を必須としています。

実際の証明書を取得する

openssl s_client -showcerts はサーバーが送信するすべての証明書を出力し、openssl x509 -text は証明書を 1 つデコードします。実験は debian:forky-20261005-slim 上で、OpenSSL 3.6.5 と pyca/cryptography 49.0.0 を使って行いました。

サーバーが送信するチェーンを、証明書ごとに 1 つの 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 を送信します。SNI がないと、多くのサーバーは別の名前のデフォルト証明書を返します。次に示すのは、当日 letsencrypt.org が提供していたリーフ証明書です(公開鍵は全体を示し、2 つの 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 証明書に何を含めてよいかは、CA/Browser Forum のベースライン要件(BR)、2026年10月4日付のバージョン 2.3.1 の §7.1 に定められています。

バージョン、シリアル番号、署名アルゴリズム、有効期間

  • バージョン 3 は拡張を持つことができる唯一のバージョンで、BR はバージョン 3 を必須としています。
  • シリアル番号:発行者ごとに一意の値で、パブリック CA の場合は「ゼロ(0)より大きく 2¹⁵⁹ 未満の連続しない数値で、CSPRNG の出力を 64 ビット以上含む」ものです(BR §7.1.2.7)。ここでのランダム性は、署名のハッシュに対する選択プレフィックス衝突攻撃への防御策です。シリアル番号がノイズのように見えるのはこのためです。CRL に列挙されるのはシリアル番号です。
  • 署名アルゴリズム(Signature Algorithm)は、署名対象の部分の中と署名の隣の 2 か所に現れ、両者はバイト単位で同一でなければなりません。ここでの ecdsa-with-SHA384 が示しているのは、サブジェクトの鍵ではなく発行者の鍵です(YE2 は P-384 の鍵)。末尾の署名値は DER 形式の ECDSA 署名で、その形式は ECDSA 署名の形式で解説しています。
  • 有効期間:notBefore と notAfter の時刻は、どちらも有効期間に含まれます。この証明書の有効期間は 90 日です。BR の上限は、2026年3月15日以降に発行される証明書では 200 日、2027年3月15日以降は 100 日、2029年3月15日以降は 47 日です(BR §6.3.2)。Let's Encrypt はオプトイン方式の tlsserver プロファイルですでに有効期間 45 日の証明書を発行しており、デフォルトを 2027年2月10日に 64 日、2028年2月16日に 45 日へ短縮する予定です。

サブジェクトとサブジェクト代替名

サブジェクトは識別名で、ここでは CN=letsencrypt.org だけです。ドメイン認証の証明書には、それ以外の情報は含まれません。CA がそれ以外を何も検証していないからです。クライアントが確認する名前はサブジェクト代替名(SAN)にあり、この証明書には DNS: のエントリが 10 個あります。

コモンネームは、ずっと前から照合に使われなくなっています。RFC 6125 を置き換えた RFC 9525(2023年)は、「コモンネームの RDN をサービスの識別に使用してはならない(MUST NOT)」と定めています。Chrome はバージョン 58(2017年)でコモンネームとの照合をやめ、Go は CN しか持たない証明書を x509: certificate relies on legacy Common Name field というエラーで拒否します。BR では、CN は単なるコピーとして残っているにすぎません。CN を含める場合は、「証明書の subjectAltName 拡張に含まれる値のいずれかであるエントリを、ちょうど 1 つ含まなければならない(MUST)」とされています(§7.1.4.3)。

SAN には IP アドレスも記載できます。次に示すのは 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 証明書の比較を参照)。ただし、ブラウザーはそのどれも表示しません。この証明書は、200 日の上限が適用される前の 2025年12月31日に、有効期間 355 日で発行されました。

*.cloudflare-dns.com のようなワイルドカードは、ちょうど 1 つのラベルにだけ一致します。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

サブジェクト公開鍵情報

subjectPublicKeyInfo(SPKI)には、アルゴリズムと鍵が格納されています。鍵は非圧縮形式の P-256 の点(04 の後に 32 バイトの x と y が続く)です。証明書の中で、サブジェクトの秘密鍵と関係するのはこの部分だけです。そのため公開鍵ピンニングでは、証明書全体ではなく SPKI のハッシュを使います。同じ鍵で更新すれば、ピンはそのまま有効です(2026年の証明書ピンニングを参照)。

鍵用途と拡張鍵用途

この 2 つは、別々の問いに答えます。

  • 鍵用途(Key Usage、RFC 5280 §4.2.1.3)は、鍵の暗号操作を制限します。digitalSignature、keyEncipherment、keyAgreement、keyCertSign、cRLSign と、まれに使われるいくつかのビットがあります。ECDSA のリーフ証明書について、BR は digitalSignature を必須とし、keyEncipherment を禁止しています(EC 鍵では暗号化できないため)。RSA のリーフ証明書では、古い TLS 1.2 の RSA 鍵交換のために keyEncipherment を追加してもかまいません。BR はこれを「安全でない暗号スイート」のサポートと呼んでいます。また 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 サーバー証明書は serverAuth を含まなければならず(MUST)、コード署名、メール、タイムスタンプ、OCSP 署名、anyExtendedKeyUsage を含んではいけません(MUST NOT、BR §7.1.2.7.10)。

変化したのは clientAuth です。BR では今も含めてもよい(MAY)とされていますが、Chrome ルートプログラムポリシー 1.8 は「TLS サーバー認証専用」の階層を求めています。2026年6月15日以降に開示される下位 CA が宣言できるのは serverAuth だけで、2027年3月15日以降に発行されるサブスクライバー証明書もそうでなければなりません。Let's Encrypt は 2026年2月11日にデフォルトのプロファイルから clientAuth を削除し、2026年7月8日にはその発行を完全に終了しました。上の letsencrypt.org のリーフ証明書にも serverAuth しかありません。公開 TLS 証明書でクライアントを認証していたサービスには、プライベート CA が必要です(mTLS の解説を参照)。

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

これが OpenSSL のエラー 26 です。Go では、同じ状況が x509: certificate specifies an incompatible key usage というエラーになります。

基本制約:証明書を発行できるのは誰か

基本制約は、1 つの真偽値と 1 つの数値を持ちます。CA:TRUE は、その証明書の鍵でほかの証明書に署名することを許可します。pathlen は、その下に続いてよい中間 CA の数を制限します。RFC 5280 §4.2.1.9 は、cA が設定されていない場合、「証明された公開鍵を証明書の署名の検証に使用してはならない(MUST NOT)」と明記しています。letsencrypt.org のチェーンを上にたどると、次のようになります。

証明書基本制約鍵用途
CN=letsencrypt.orgクリティカル、CA:FALSEデジタル署名
CN=YE2(中間証明書)クリティカル、CA:TRUE, pathlen:0デジタル署名、証明書署名、CRL 署名
CN=Root YE(ISRG Root X2 によるクロス署名)クリティカル、CA:TRUE証明書署名、CRL 署名

YE2 の pathlen:0 は、YE2 がリーフ証明書しか発行できないことを意味します。この制約が強制されることを確かめるため、実験では通常のサーバー証明書(CA:FALSE、鍵用途は 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

両方の規則が働いています。CA:TRUE がないことに対してはエラー 79、keyCertSign がないことに対してはエラー 32 です。2002年に Internet Explorer があらゆるリーフ証明書を CA として受け入れてしまったのは、このチェックを省いていたためです。Python 3.13 以降は OpenSSL の厳格モードも有効にしており、基本制約がクリティカルとしてマークされていない CA 証明書も拒否します。

認証局鍵識別子とサブジェクト鍵識別子

サブジェクト鍵識別子(SKI)は証明書自身の鍵を示し、認証局鍵識別子(AKI)はその証明書に署名した鍵を示します。各証明書の AKI は、1 つ上の証明書の SKI と一致します。

letsencrypt.org が送信する各証明書のサブジェクト、SKI、AKI
  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 やクロス署名された CA で、たとえば現存する 2 つの ISRG Root X2 証明書がこれにあたります)に、検証者が正しい発行者を選ぶのに役立ちます。鍵識別子自体は何も証明しません。証明するのは署名の検証です。RFC 5280 は、自己署名のルートを除くすべての証明書に AKI を、すべての CA 証明書に SKI を求めています。RFC 5280 は SKI の計算方法の 1 つとして鍵の SHA-1 ハッシュを挙げていますが、発行者は一意であればどんな値を使ってもかまいません。BR はリーフ証明書の SKI を「推奨しない(NOT RECOMMENDED)」としていますが、Let's Encrypt も Sectigo も今も SKI を含めています。厳格な検証者では、AKI のない CA 証明書の検証が失敗します。Python の Missing Authority Key Identifier がその例です。

このチェーンからは、検証するパスがサーバーの送信するパスと常に同じとは限らないこともわかります。letsencrypt.org は 4 つの証明書を送信し、その最後は ISRG Root X1 によってクロス署名された ISRG Root X2 です。Debian のトラストストアを使う OpenSSL は、すでに信頼している 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 配布ポイント、AIA、OCSP

2 つの拡張が URL を指しています。

  • CRL 配布ポイント(CRL Distribution Points、RFC 5280 §4.2.1.13):発行者が CRL を公開している場所です。BR は、証明書が短期証明書(2026年3月15日以降は有効期間 7 日以下)である場合か OCSP の URL を含む場合を除いて、この拡張を必須としています。また URL は暗号化されていない http でなければなりません。CRL 自体が署名されているため TLS で保護しても得るものはなく、かえって循環が生じるからです。
  • Authority Information Access(§4.2.2.1):CA Issuers は発行元 CA の証明書をダウンロードできる場所、OCSP は発行者の OCSP レスポンダーです。

Let's Encrypt のリーフ証明書には CRL の URL があり、OCSP の URL はありません。Let's Encrypt は、BR で OCSP が任意になったことを受けて、2025年5月に証明書から OCSP の URL を削除し、2025年8月6日にレスポンダーを停止しました。Sectigo は今も OCSP を運用しており、github.com のリーフ証明書には両方のアクセス方式と 2 つのポリシーがあります。

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

次に、letsencrypt.org のリーフ証明書が指す CRL をダウンロードし、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

URL の 96.crl はシャードです。CA は失効情報を多数の小さな CRL に分割し、各証明書には該当する CRL を記載します。そのためクライアントは、すべての失効情報ではなく 1,590 件のエントリをダウンロードするだけで済みます。ブラウザーが CRL をその場で取得することはまれで、Chrome と Firefox は失効データを自前でクライアントに配信しています。それぞれの長所と短所は CRL と OCSP の比較で解説しています。

CA Issuers が重要になるのは、サーバーが中間証明書の送信を忘れた場合です。ブラウザーと、Windows および macOS のプラットフォームの検証機能はこの URL をたどります(「AIA チェイシング」)。一方、OpenSSL(とそれを使う Linux 上の curl、Python、PHP)、Linux 上の Go、デフォルト設定の Java はたどりません。そのため、設定に誤りのある同じサーバーに、ブラウザーからは問題なく接続できる一方、コードからの接続は「unable to get local issuer certificate」というエラーで失敗します。

証明書ポリシー

すべての公開 TLS 証明書は、サブジェクトをどのように検証したかを示す CA/Browser Forum のポリシー識別子を、ちょうど 1 つ宣言します。2.23.140.1.2.1 はドメイン認証、2.23.140.1.2.2 は組織認証、2.23.140.1.2.3 は個人認証、2.23.140.1.1 は EV 認証(Extended Validation)です(BR §7.1.6.1)。CA は、上の Sectigo のように、独自のポリシー OID と認証局運用規程(CPS)へのリンクを追加します。認証レベルを表示するクライアントは、サブジェクトではなくこの拡張を読み取ります。

署名付き証明書タイムスタンプ

CA は証明書を発行する前に、プレ証明書(同じ内容にポイズン拡張を加えたもの)を Certificate Transparency のログに提出します。各ログは、それを公開するという署名付きの約束、つまり SCT を返します。CA はそれらの SCT を最終的な証明書に埋め込みます(OID 1.3.6.1.4.1.11129.2.4.2、RFC 6962 §3.3)。ログ ID はログの鍵のハッシュで、Chrome のログリストを使うとログ ID から名前がわかります。

scts.py:埋め込まれた各 SCT を発行した CT ログの名前を表示(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 からダウンロードしました。どちらの証明書にも、異なる 2 つの運営者による 2 つの SCT があります。これは、有効期間が 180 日以下の証明書に対して Chrome の CT ポリシーが求める数です(それより長い証明書では 3 つ)。Tuscolo の SCT にある Extensions: 00:00:05:00:24:DF:DB:0C は、Static CT API を実装するログが付加しなければならない leaf_index 拡張です。タイプ 0、長さ 5、40 ビットのインデックスからなり、ここではそのログのエントリ 618,650,380 を指しています。SCT があるからこそ、誰でも自分のドメインに対して発行されたすべての証明書を検索でき、誤発行された証明書を秘密にしておくこともできません。SCT デコーダーを使えば、コマンドラインなしで SCT をデコードできます。

よくある間違い

  • CN にだけ名前がある証明書:ブラウザーは NET::ERR_CERT_COMMON_NAME_INVALID を報告し、Java は No subject alternative names present を返します。CN に記載した名前も含め、すべての名前を SAN に記載する必要があります。この問題が最もよく起きるのは、社内 CA や、-subj だけを指定する openssl req のワンライナーを使った場合です。
  • 中間証明書の欠落:AIA チェイシングのおかげで、ブラウザーや、Windows と macOS のプラットフォームの検証機能では接続できますが、OpenSSL ベースのクライアント、Linux 上の Go、Java では検証に失敗します。完全なチェーン(リーフ証明書が先頭、ルートは任意)を送信してください。
  • 公開証明書の clientAuth に依存する:この EKU は公開 TLS の階層から姿を消しつつあります。「clientAuth を含む任意の公開証明書」を要件として固定していたパートナー連携には、プライベート CA が必要です。
  • CA:TRUE はあるが、keyCertSign がない、または基本制約がクリティカルでない CA 証明書:厳格な検証者(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 です。時計が数時間遅れているサーバーは、発行されたばかりの証明書を「まだ有効ではない」として拒否します。

出典