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

開発者のための Certificate Transparency:SCT とアラート

公的に信頼されるすべての TLS 証明書は、ブラウザーに受け入れられる前に、追記専用のログで公開されます。そのため、誤発行された証明書が可視化され、自分が保有する証明書の一覧も検索できるようになります。この記事では、各要素がどう組み合わさるのか、ブラウザーが何を要求するのか、自分のドメインをどう監視するのかを解説します。

要点:Certificate Transparency(CT、RFC 6962)は、公的に信頼されるすべての TLS 証明書を、公開された追記専用のログに提出することを CA に義務付けています。各ログは、その証明書を記録するという署名付きの約束、つまり SCT を返し、CA はその SCT を証明書に埋め込みます。公的に信頼される証明書であっても、十分な数の独立したログから十分な数の SCT を得ていなければ、Chrome、Safari、Firefox はその証明書を拒否します。開発者にとって、これには 2 つの意味があります。まず、自分のドメインに対して発行されたすべての証明書を、Cert Spotter、crt.sh、あるいは自前のモニターを通じて誰でも確認できます。そして、これらのログは監視すべきです。自分が要求していない証明書は、DNS の乗っ取り、アカウントの侵害、あるいは不適切な動作をする CA を示す最も早い兆候だからです。

CT の仕組み

CT ログはマークルツリーです。Git や Bitcoin が使っているのと同じ、追記専用のハッシュ構造です。誰でもログをダウンロードし、エントリが削除も変更もされていないことを確かめ、特定の証明書が含まれていることを示す短い証明を得られます。CT のもとでの証明書の発行は、次のように進みます。

  1. CA がプレ証明書を作成します。これは最終的な証明書の内容にクリティカルな「ポイズン」拡張(OID 1.3.6.1.4.1.11129.2.4.3)を加えたもので、この拡張があるため実際の証明書としては使えません。
  2. CA はプレ証明書を複数のログに提出します。各ログは署名付き証明書タイムスタンプ(SCT)、つまりエントリに対する署名とタイムスタンプを返します。SCT は、ログの最大マージ遅延(MMD、Maximum Merge Delay)以内にエントリを記録することを約束するものです。
  3. CA は、SCT を X.509 拡張(OID 1.3.6.1.4.1.11129.2.4.2)に埋め込んだ最終的な証明書を発行します。
  4. ブラウザーは、信頼済みログのリストを使って SCT の署名を検証し、自身のポリシーに照らして SCT の数を数えます。
  5. モニターはログを読み取り、監視対象の名前に対して発行された証明書を報告します。監査者は、ログが一貫性を保ち、約束を守っているかを確認します。

ほとんどの場合、プレ証明書と最終的な証明書の両方がログに記録されます。検索ツールで各証明書がよく 2 回表示されるのはそのためです。

実際の証明書に含まれる SCT

openssl x509 -text は、埋め込まれた SCT をデコードします。次に示すのは、2026年10月20日に letsencrypt.org が提供していた証明書です。

openssl x509 -noout -text(OpenSSL 3.5.7、抜粋)
subject=CN=letsencrypt.org
issuer=C=US, O=Let's Encrypt, CN=YE2
notBefore=Sep  4 14:34:32 2026 GMT
notAfter=Dec  3 14:34:31 2026 GMT
            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:...

ログ ID は、ログの公開鍵の SHA-256 ハッシュです。2 つの ID を Chrome のログリスト(log_list.json、2026年10月19日版)で調べると、次のようになります。

ログ ID(base64)ログ運営者種類状態
2AlVO5RPev/IFhlvlE+Fq7D4/F6HVSYPFdEucrtFSxQ=Xenon2026h2GoogleRFC 6962、MMD 24 時間usable
Rq+GPTs+5Z+ld96oJF02sNntIqIj9GF3QSKUUu6VUF8=Tuscolo2026h2Geomysstatic-ct-api、MMD 60 秒usable

異なる 2 つの運営者による 2 つのログで、これは有効期間 90 日の証明書に必要な最小限の構成です。2 つ目の SCT の拡張は、意味のないデータではありません。static-ct-api に準拠したログは、ログ内でのエントリの位置を SCT に入れます。拡張タイプ 0(leaf_index)、長さ 5、40 ビットのインデックスで、この例では 618,650,380 です。モニターは、そのエントリを探さずに直接取得できます。

SCT デコーダーを使うと、この照合を自動で行えます。各ログの名前、Chrome と Apple のリストでの状態、SCT のセットが各ブラウザーのポリシーを満たしているかどうかが表示されます。証明書デコーダーは、SCT 拡張を証明書の他の部分と合わせて表示します。

SCT がブラウザーに届く経路は 3 つあります。

  • 証明書に埋め込む:上の例がこれにあたります。ほぼすべての CA がこの方法を採っており、こちらで何かを設定する必要はありません。
  • TLS 拡張 signed_certificate_timestamp で送る:サーバーが自分で取得した SCT を送信します。この方法に対応しているサーバーはわずかです。
  • OCSP レスポンスにステープリングする:Chrome がこの方法を受け入れていたのは「バージョン 148 より前のバージョン」だけです。また、Let's Encrypt などが OCSP を廃止しつつあるため、この経路はすたれつつあります(2026年の OCSP と CRL を参照)。

ブラウザーの要件

主要な 3 つのエンジンはいずれも、自身の公開ルートプログラムに含まれる CA が発行した証明書に対して CT を強制しています。

ChromeApple(Safari、iOS、macOS)Firefox
埋め込み SCT、有効期間 180 日以下異なるログから 2 個2 個異なるログから 2 個
埋め込み SCT、それより長い有効期間異なるログから 3 個3 個(181〜398 日)異なるログから 3 個
運営者の多様性異なるログ運営者が 2 つ以上運営者ごとにカウントされる SCT は最大 1 個(180 日以下)または 2 個(それより長い場合)異なるログ運営者が 2 つ以上
その他の条件現在 Qualified、Usable、ReadOnly のいずれかの状態にあるログによる埋め込み SCT が 1 個以上現在承認されているログによる埋め込み SCT が 1 個以上。RFC 6962 ログによる SCT が 1 個以上現在受け入れ可能なログによる SCT が 1 個以上。ログリストは Chromium のものを使用
TLS または OCSP による SCT現在信頼されているログから 2 個、運営者 2 つ。OCSP は Chrome 148 より前のみ現在承認されているログから 2 個、うち 1 個は TLS または OCSP で提供受け入れ可能なログから 2 個、運営者 2 つ
要件を満たさない場合のエラーNET::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED接続に失敗MOZILLA_PKIX_ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY

出典:Chrome の CT ポリシー、Apple の CT ポリシー(2025年4月)、Mozilla の CT のページ。MDN によると、Firefox はデスクトップ版ではバージョン 135(2025年2月)から、Android 版ではバージョン 145(2025年11月)から CT を強制しています。設定項目 security.pki.certificate_transparency.mode でモードを確認でき、2 は強制を意味します。

実務上の影響が 2 つあります。

  • ログリストが古すぎると、ブラウザーは CT を強制しなくなります。Chrome が CT を強制するのは、最新のログリストが公開から 70 日未満の間だけです。Firefox のリストは 10 週間で期限切れになります。何か月も更新されていないマシンは CT をまったく強制しないため、CT の失敗のテストには使わないでください。
  • プライベート CA は対象外です。Chrome の企業向けの説明には、CT は「公的に信頼された CA にのみ適用される」と書かれています。自分でインストールしたルートから発行された証明書には SCT が不要で、そもそもログはそうした証明書を受け付けません。

ログそのもの

2026年10月19日時点の Chrome のログリストには、usable の RFC 6962 ログが 21 個、read-only が 2 個、retired が 3 個あります。static-ct-api ログも、usable が 22 個、qualified が 37 個掲載されています。usable または qualified のログを運営しているのは、Cloudflare、DigiCert、Geomys、GoDaddy、Google、HARICA、IPng Networks、Let's Encrypt、Microsec、Sectigo、TrustAsia の 11 組織です。ログは期間ごとにシャード分割されています。2026h2 という名前のログは、2026年の下半期に有効期限を迎える証明書だけを受け付けます。これにより各ログのサイズに上限が設けられ、そのログの証明書がすべて期限切れになった時点でログを廃止できます。

どちらの種類のログも、同じ RFC 6962 のエンドポイント(add-chain、add-pre-chain)で提出を受け付けます。違いは、モニターがログを読み取る方法です。RFC 6962 ログは、動的な API 呼び出しに応答します。

GET https://ct.googleapis.com/logs/eu1/xenon2026h2/ct/v1/get-sth (2026年10月20日)
{"tree_size":2980148930,"timestamp":1792499093002,"sha256_root_hash":"wr3F5apMl2E7wQHqbAA1yKsqaZbAzBXTT8WTAeRAy8M=","tree_head_signature":"BAMARzBFAiEAsC3Pr7fsPwy0vchrMDIXka7HxrZANu5tcXclDCUqqSYCIGJ64SaIT0tjM5hCGfycNGPfu6FP8q8RvW14Q4SoyvVU"}

半年分のシャード 1 つで、エントリは 3,000,000,000 件近くに達します。static-ct-api(「タイル型」)のログは、署名付きツリーヘッドをプレーンテキストのチェックポイントとして公開し、エントリは CDN でキャッシュできる不変のタイル(1 タイルあたり 256 エントリ)として公開します。

GET https://tuscolo2026h2.skylight.geomys.org/checkpoint (2026年10月20日、署名は短縮)
tuscolo2026h2.sunlight.geomys.org
1007870339
aRhh7dPjYarH0itUM1LD+XJw7CW41z1+9tSnf3AizuY=

— tuscolo2026h2.sunlight.geomys.org W+DWDWi4dqCfFx8da0IS4+jwHKVL6ETEHGQCHn5y8rKflUhLRtLHQ3c6...
— grease.invalid Aui1jDc8Y7Uve7seHd/Pg1p+wHSUmw0+MTNyEKEAAbHaE5bY3Gi/MUZ3zBuk+NxUyhJL...
— tuscolo2026h2.sunlight.geomys.org 7BiSLwAAAaFZRnbMBAMARzBFAiA++3wWdtsK2H8l3IxZh6H/OQml...

先頭の行は、順にオリジン、ツリーサイズ、ルートハッシュです。「—」で始まる行は署名です。監視用の URL(skylight)と提出用の URL(sunlight)が異なる点に注意してください。どちらもログリストに掲載されています。

事実上すべてのログをカバーしている公開検索サービスが 2 つあります。

Cert Spotter API(SSLMate):issuances エンドポイントは、ドメインの期限切れでない証明書を一覧表示します。1 時間あたりのクエリ数に制限はありますが、API キーなしでも使えます。本番環境で使う場合は、アカウント登録をして API キーを取得するよう SSLMate が求めています。ecdsa.com の証明書は次のとおりです。

Cert Spotter:ドメインとそのサブドメインの期限切れでない証明書
curl -s "https://api.certspotter.com/v1/issuances?domain=ecdsa.com&include_subdomains=true&expand=dns_names&expand=issuer"
レスポンスの最初のレコード(2026年10月20日)
{
  "id": "16609031524",
  "tbs_sha256": "8c6f1f0bef5434da2012d5f29eab13f00bcec659f17832c07872ff600f61ecf5",
  "cert_sha256": "1396447e3454a081fe4e48dc1d5de18b7e3cf857ec1d7ff3c987d091c9024b04",
  "dns_names": [
    "ecdsa.com"
  ],
  "pubkey_sha256": "9a1fa381dad4630b83cf3a1a1a7a3da7f04577db8a41933dd9bac49dabfa8c29",
  "issuer": {
    "friendly_name": "Let's Encrypt",
    "pubkey_sha256": "2e8307068b6db620e4a39d068b5dee5d6ef5788cbb2c0b6d23ead84fcc17178c",
    "name": "C=US, O=Let's Encrypt, CN=YR1"
  },
  "not_before": "2026-08-19T14:52:58Z",
  "not_after": "2026-11-17T14:52:57Z",
  "revoked": false
}

まとめると、レスポンスには 5 件の発行が含まれていました。

5 件の発行すべて(1 行に 1 件)
16609031524 2026-08-19 2026-11-17 Let's Encrypt ecdsa.com
16609045002 2026-08-19 2026-11-17 Let's Encrypt ecdsa.com,www.ecdsa.com
17525925247 2026-10-01 2026-12-30 Let's Encrypt *.ecdsa.com,ecdsa.com
17875915585 2026-10-19 2027-01-17 Let's Encrypt ecdsa.com,www.ecdsa.com
17878726033 2026-10-20 2027-01-18 Let's Encrypt ecdsa.com

結果は、SSLMate が証明書を発見した順に返されます。ページをたどるには、最後の id を after= に渡します。空の配列には Retry-After ヘッダーが付くため、このエンドポイントはポーリング用のフィードとして使えます。pubkey_sha256 フィールドで何ができるかにも注目してください。これを自分のサーバーの鍵と比較すると、どの証明書が実際にデプロイされていて、どれが発行されたまま一度も使われていないかがわかります。

crt.sh(Sectigo)には、Web UI と JSON 出力(https://crt.sh/?q=example.com&output=json)があります。無料で、期限切れの証明書を含むきわめて長い履歴をカバーしています。一方で、多くの利用者が共有する混雑したサービスでもあります。2026年10月20日に実行した JSON クエリは、4 回とも 502 Bad Gateway を返しました。便利な補助手段として扱い、監視の依存関係には組み込まないでください。

CT 履歴ツールは、ドメインの証明書を時系列で表示し、それぞれの発行者、有効期間、名前を示します。

ドメインの監視

検索が答えるのは「何が存在するか」です。監視は「何が新しく現れたか」に答え、発行から数分ないし数時間以内にアラートを送ります。選択肢は次のとおりです。

  • ホスト型のモニター:SSLMate がホストする Cert Spotter や、いくつかの CA のダッシュボードは、自分のドメインに新しい証明書が発行されると、メールまたは webhook で通知します。
  • オープンソースの certspotter(v0.25.0、2026年9月):ログを自分で読み取ります。~/.certspotter/watchlist に名前を列挙すると(先頭にドットを付けるとすべてのサブドメインが対象になります。例:.example.com)、一致するたびにメールを送信するか、~/.certspotter/hooks.d のスクリプトを実行します。v0.24 からは certspotter-authorize も備えており、想定どおりの証明書に印を付けておくことで、想定外の証明書だけがアラートの対象になります。
  • 検索 API をフィードとして使う:Cert Spotter の issuances エンドポイントを after= 付きでポーリングし、新しい証明書を 1 つずつ、自分の自動化の仕組みが発行したものと照合します。

アラートの対象にすべきものは次のとおりです。

  1. 使っていない発行者:CAA レコードと照合します。CAA で禁止している CA から発行された証明書は CA のインシデントであり、その CA に報告しなければなりません。
  2. 心当たりのない名前:特に、存在を忘れていたサブドメインの名前です。old-campaign.example.com の証明書は、参照先が消えたまま残っている DNS レコードが乗っ取られたことを意味している可能性があります。
  3. 自分で生成していない公開鍵:自動化の仕組みで管理している名前の証明書が対象です。
  4. ワイルドカード証明書:ポリシー上ワイルドカード証明書を使っていない場合です。

有効期間 47 日の証明書になると、正当な発行も頻繁に行われるようになります。certspotter-authorize も ACME クライアントの記録との照合もなければ、アラート疲れによって、本当に重要な 1 枚の証明書を無視してしまうことになります。

CT と非公開の名前

公的に信頼された証明書に含まれる情報は、すべて公開されます。SAN リストのすべての名前が、永久に公開されるということです。jenkins.corp.example.com や staging-payments.example.com のような内部のホスト名は、公開証明書を取得した瞬間に公開情報となり、攻撃対象を探してスキャンしている人も含め、ログを読む誰もがそれを目にします。Chrome の企業向けの説明と一般的な慣行に基づく選択肢は次のとおりです。

  • 内部の名前にはプライベート CA を使ってください。CT は適用されず、何も公開されません。
  • 公開証明書がどうしても必要な場合は、ワイルドカード証明書(*.corp.example.com)を使ってください。ゾーンは明らかになりますが、個々のホストは明らかになりません。
  • 公開 CA にログへの記録をしないよう依頼することに頼らないでください。そうした証明書は、CT を強制するすべてのブラウザーで検証に失敗します。

よくある間違い

  • SCT が記録の証明だと考える:SCT は、MMD 以内にエントリを記録するという約束です。ログがその約束を守っているかは、監査者とモニターが確認します。
  • 重複して数える:同じ発行に対するプレ証明書と証明書のエントリは、ほとんどの検索ツールで 2 行として表示されます。両者は発行者とシリアル番号が同じなので、証明書を数える前に、その組み合わせで重複を除いてください。
  • CT に失効情報を期待する:失効した証明書も、ログには永久に残ります。失効は別の仕組みです。
  • 古いブラウザーで CT の強制をテストする:ブラウザーのログリストが古くなると、CT の強制は自動的に無効になります。
  • 1 つの無料サービスだけで監視する:crt.sh の障害は実際に起きます。2 つ目の情報源か、自前のモニターを使ってください。

出典

コマンドの出力は、2026年10月20日に node:24.22.0-trixie 上の OpenSSL 3.5.7 と curl 8.14.1 で取得しました。