証明書透明性(Certificate Transparency)
概要
証明書透明性(Certificate Transparency) は、認証局(CA)が発行したすべての証明書をパブリックな追記専用ログに記録し、不正発行を検知・監査できるようにする仕組みです。RFC 6962(2013 年)で最初に定義され、現行仕様は RFC 9162(2021 年)です。
CT が登場した背景には、CA の不正発行事件があります。2011 年に DigiNotar が侵害を受け、Google や他の主要ドメインの不正証明書が発行されました。発行から検知まで数週間かかり、その間に中間者攻撃が可能な状態でした。CT はこの問題を構造的に解決します。証明書がログに記録されることで、ドメインオーナーは自分のドメインに対して発行されたすべての証明書を事後的に確認でき、不正発行を早期に発見できます。
仕組み
CT は Merkle ハッシュツリーによる改ざん検知、SCT によるログ登録の証明、ブラウザーのポリシーによる強制の 3 層で動作します。
CT ログの構造
CT ログは追記専用の Merkle ハッシュツリーで管理されています。各ログエントリーは Merkle ツリーのリーフノードとして追加されます。Merkle ツリーの特性上、既存のエントリーを改ざんすると根(ルート)のハッシュが変化するため、改ざんは即座に検出できます。ログ自体の整合性はログオペレーターが定期的に公開する署名付きのツリーヘッドで証明されます。
現在、Google(Google’s Logs)、Cloudflare(Nimbus)、DigiCert、Let’s Encrypt など複数の組織がログを運用しています。
SCT(Signed Certificate Timestamp)
証明書が CT ログに登録されると、ログサーバーは SCT(Signed Certificate Timestamp) を返します。SCT はログサーバーの秘密鍵で署名されたタイムスタンプで、「この証明書を受理し、最大マージ遅延(MMD)以内にツリーに組み込む」という約束です。RFC 6962 では MMD は 24 時間以内と定められています。
SCT はクライアントに 3 つの方法で届けられます。
- 証明書への埋め込み(X.509v3 拡張)— CA が証明書発行前にログに登録し、返ってきた SCT を証明書自体に組み込みます。最も広く使われている方法です
- TLS ハンドシェイク拡張 — サーバーが TLS ハンドシェイク中に
signed_certificate_timestamp拡張として SCT を送ります - OCSP ステープリング — サーバーが OCSP レスポンスに SCT を含めてステープルします
ブラウザーの CT ポリシー
Chrome は 2018 年 4 月 30 日以降に発行されたすべての証明書に CT 準拠を要求しています。Chrome のポリシーでは証明書の有効期間によって必要な SCT 数が異なります。有効期間が 180 日未満では SCT が 2 つ、180 日以上では 3 つ必要です(2022 年 4 月 15 日以降発行分から適用)。
Safari(Apple)も同様のポリシーを適用しており、証明書の有効期間に応じて 2〜3 個の SCT を要求します。Firefox はデスクトップ版 135 以降、Android 版 145 以降で Mozilla のルート CA プログラムに含まれる CA が発行した証明書全てに CT を要求しています。
CT ポリシーを満たしていない証明書は、ブラウザーが接続を拒否します。NET::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED エラーがこれに該当します。
ドメインオーナーによる監査
CT ログはパブリックなため、ドメインオーナーは自分のドメインに対して発行されたすべての証明書を確認できます。crt.sh(Certificate Transparency log aggregator)では次のようにドメインを検索できます。
curl "https://crt.sh/?q=%.example.com&output=json" | jq '.[].name_value' | head -20
このクエリは example.com および全サブドメインに対して発行された証明書のコモンネーム・SAN を一覧表示します。見覚えのない証明書が発行されていた場合は、不正発行の可能性があるため CA に問い合わせます。
設定例
Let’s Encrypt が発行する証明書は CT ログへの登録が自動的に行われます。certbot で証明書を取得すると、SCT が証明書に組み込まれた状態で返ってきます。
発行済み証明書の SCT を確認するには次のコマンドを使います。
# 証明書のテキスト表示(SCT 拡張を含む)
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -text | grep -A 30 "CT Precertificate SCTs"
CT Precertificate SCTs:
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : 3B:53:77:75:3E:2D:B9:80:4E:8B:30:5B:06:FE:40:3B:
67:D8:4F:C3:F4:C7:BD:00:0D:2D:72:6F:E1:FA:D4:17
Timestamp : Apr 11 00:00:00.000 2026 GMT
Extensions: none
Signature : ecdsa-with-SHA256
SCT が 2 つ以上含まれていることを確認します。
確認方法
openssl で証明書の CT 情報を確認するには次のコマンドを使います。
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -text | grep -c "Signed Certificate Timestamp"
出力が 2 以上であれば、Chrome と Safari の CT ポリシーを満たしています。
外部の視点からも確認したい場合は、Labee Dev Toolbox の SSL Cert API を使うと、外部の視点から見た結果を取得できます。
curl "https://labee.dev/api/ssl-cert?hostname=example.com"
{
"success": true,
"data": {
"hostname": "example.com",
"port": 443,
"reachable": true,
"status": 200
},
"error": null,
"meta": { "responseTime": 123 }
}
data.reachable が true であれば HTTPS 接続が確立されており、証明書が有効な状態です。false の場合は error フィールドに接続失敗の原因が含まれます。証明書の CT 非準拠は通常 TLS ハンドシェイクの失敗として現れます。
よくある問題
CT 関連のエラーは SCT の不足やログの状態変更に起因するケースが大半です。
NET::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED
Chrome でこのエラーが表示される場合、証明書に有効な SCT が含まれていないか、数が不足しています。原因は次の 2 つが多いです。
まず、古いカスタム CA や社内 CA が発行した証明書は CT ログへの登録を行わないケースがあります。社内ネットワーク向けの証明書は Chrome の CT ポリシーの適用外になるよう、エンタープライズポリシーで設定することで回避できます。
次に、Let’s Encrypt が廃止した RFC 6962 ベースのログは 2025 年 8 月に EOL を迎えました。これ以前に発行された証明書を継続して使っている場合、ログが有効期限切れになって SCT の検証に失敗することがあります。証明書を再発行することで解決します。
CT ログのダウンによる証明書発行失敗
証明書発行時に CA が接続する CT ログがダウンしていると、SCT を取得できずに発行が失敗することがあります。Let’s Encrypt などの主要 CA は複数の CT ログに冗長登録しているため、単一ログの障害が発行に影響しにくい構成を取っています。自社で証明書発行の自動化を組んでいる場合、ログへの登録失敗をリトライするロジックが必要です。
crt.sh で自社証明書が見えない
証明書を発行した直後は CT ログへの組み込みが MMD(最大 24 時間)以内で行われます。発行直後に crt.sh で検索しても表示されないことがあります。時間をおいて再確認します。