証明書とPKI

このページの目次

証明書は、公開鍵をアイデンティティ、用途、有効期限と結びつけ、発行者によって署名します。クライアントは証明書を受け入れる際に、信頼できるパスとアプリケーションによるアイデンティティ検証が必要です。さらに、TLSハンドシェイクによって、相手が対応する秘密鍵を保持していることを証明します。証明書を読み出せたり、自己署名を検証できたりしたからといって、相手の正体が分かっているわけではありません。

https://api.example.com:8443/orders を通して例を示します:接続先のアドレスは解決によって得られ、参照アイデンティティはアプリケーションがアクセスするターゲットから来て、証明書は検証可能なアイデンティティの宣言を提供します。DNSがどのIPを返したか、サーバーがどの名前を送信したかによって、クライアントが誰を信頼すべきかが決まるわけではありません。

解決すべき問題参照箇所
SAN、CN、ポート、ワイルドカードのマッチング方法サービスアイデンティティ
ルート証明書があるのにチェーンが不完全と報告される理由信頼パス
DNS-01で実際にどのようなTXTレコードを書くべきかACMEの3種類チャレンジ
証明書が期限切れでないのに拒否すべき理由失効、用途、クライアントポリシー
CTが誤った発行を阻止できるか公開監査
更新は成功したが、オンラインではまだ期限切れ公開の閉ループ

4つのオブジェクトをまず区別する

オブジェクト含めるもの代替できないもの
秘密鍵署名やその他の秘密鍵演算に必要な秘密情報証明書と共に公開配布することはできない
証明書公開鍵、アイデンティティ、制約、発行者の署名現在の相手が秘密鍵を保持していることを証明しない
CSR署名申請情報と鍵申請の署名CAによって承認された証明書ではない
信頼ストアクライアントが承認するアンカーと関連ポリシーサーバーが随意に提供する証明書の集合ではない

証明書は通常DERバイナリエンコーディングされ、PEMは境界マーカ付きのテキストラッパーです。ファイル名が .pem であっても、中に証明書、秘密鍵、あるいは複数の証明書が含まれているかは判断できません。実際のオブジェクトを読み取る必要があります。証明書自体の署名アルゴリズムは発行者によって使用され、SubjectPublicKeyInfo は保持者の公開鍵アルゴリズムを記述します。これら2つは必ずしも同じである必要はありません。

サービスアイデンティティ:まずアクセス先を確定し、SANを比較する

本稿のHTTPSシナリオでは、SANの dNSName または iPAddress にマッチングします。Subject CNを使ってDNSアイデンティティを偽装してフォールバックしないでください。ドメイン名は適用ルールに従って大文字小文字と国際化名前を処理します。ワイルドカードは左端の完全なラベルのみを占め、1レベルのみをカバーします。

証明書 SANアクセスターゲット名前結果
DNS:api.example.comhttps://api.example.com:8443/マッチ;ポートはDNS SANに書き込まれない
DNS:*.example.comhttps://api.example.com/マッチ
DNS:*.example.comhttps://example.com/マッチせず(裸のドメイン)
DNS:*.example.comhttps://v2.api.example.com/マッチせず(マルチレベル)
DNS:api.example.comhttps://192.0.2.10/マッチせず;適切なIP SANが必要

SNIはサーバーが仮想ホストの証明書を選択するために使用しますが、クライアントによる名前検証を代行するものではありません。DNS CNAMEがホスティングプロバイダーを指していても、ホスティングプロバイダーの任意の名前で元のサービスアイデンティティを代替できるわけではありません。ここでは名前マッチングの結果を示していますが、最終的な認証はチェーンとその他のチェックに依存します。RFC 9525:サービスアイデンティティ

信頼パス:署名の有効性は条件の一つに過ぎない

信頼はローカルのアンカーから始まる;サーバーが送信するルート証明書は信頼を自ら確立できない ローカル信頼アンカー クライアントポリシーで設定 中間CA 上位によって制約される サービス証明書 api.example.com 署名検証に加え、時間、CA資格、用途、名前制約などをチェックする必要がある サービスアイデンティティマッチ + TLS秘密鍵保持の証明 チェーンの信頼性、正しい名前、ハンドシェイクの証明は異なる役割を担う パスの構築には複数の候補が存在する可能性があります。同じ公開鍵のクロス署名証明書は、異なる信頼アンカーに至る可能性があります。

典型的なサーバーはリーフ証明書と必要な中間証明書を送信し、通常はルートを送信する必要はありません。クライアントはローカルの信頼材料を使って候補パスを構築し、署名、有効期限、クリティカル拡張、制約を検証します。Issuerテキストが別の証明書のSubjectと等しいからといって、署名検証をスキップすることはできません。AKI/SKIは候補の選択に役立ちますが、それ自体が独立した信頼源ではありません。

フィールドまたは拡張読むべき重点
serialNumber発行者と組み合わせて証明書を識別する。インターネット全体で一意のIDではない
BasicConstraintsCA資格;pathLenConstraintは、パス内の自己署名でない中間CAの数を制限する
KeyUsagedigitalSignature、keyCertSignなど。名称で随意に汎用化できない
ExtendedKeyUsageserverAuth、clientAuthなど。KUと同時に存在する場合は両方を満たす必要がある
NameConstraints下位が使用できる名前の範囲を制限する
critical 拡張処理できないクリティカルな制約は直接無視できない

中間CAの pathLenConstraint=0 でもリーフ証明書を発行できます。これは「どの証明書も署名できない」という意味ではありません。ルート証明書は自己署名されることが多いですが、信頼は外部設定から来ており、自分の署名を循環検証することから来るものではありません。クロス署名によりパス構築に複数の候補が生じるため、あるクライアントでは動作し、別のクライアントでは失敗することがあります。これはアンカー、パス構築能力、またはポリシーの違いに起因する可能性があります。RFC 5280:証明書とパス

例えばサーバーがリーフ証明書のみを送信する場合:ブラウザは既に中間証明書をキャッシュしており接続に成功するかもしれませんが、新しいコンテナでは失敗します。サービスチェーンを補完する必要があり、「このブラウザなら開ける」ということをチェーンが完全である証拠としてはいけません。公開Web PKIと自社構築企業PKIの信頼範囲も異なります。企業ルートをインストールすると、そのクライアントが受け入れる発行者の範囲が広がります。

チェーンは繋がっているのに、なぜ拒否するのか?

まず裸のドメイン example.com やマルチレベル v2.api.example.com を入力し、ワイルドカードがそれらをカバーできないことを観察します。名前を復元すると、pathLen=0 でも直接リーフ証明書の署名を許可します。CAを1つ挿入して初めてこの制約がトリガーされます。他のチェックは独立して残ります。

図を準備しています
チェーンは繋がっているのに、なぜ拒否するのか?

パス構築で候補関係が見つかったからといって、パス検証が成功するわけではない。名前マッチ、用途、時間、CA制約は同時に満たす必要のある条件であり、1つの有効な署名で代替できるものではない。

ACME:ドメイン制御権を証明し、申請公開鍵の署名を取得する

ACMEアカウント鍵はアカウントリクエストの認証に使用され、証明書鍵はサービスハンドシェイクに使用されます。これらは異なる役割を担います。注文は識別子を記述し、authorization/challenge記録は制御権の検証を行い、CSRは署名待ちの公開鍵を提出します。注文がready状態になるとfinalizeされ、処理が完了してvalid状態になってから証明書をダウンロードできます。1回のPOST成功がすべてのステップの終了を意味するわけではありません。

keyAuthorization = token + "." + accountKeyThumbprint とし、ここでthumbprintはアカウント公開鍵JWKの正規化されたフィンガープリントです。2つの基本チャレンジの格納は以下の通りです:

チャレンジCAがチェックする場所配置する値
HTTP-01対象ドメインのHTTP 80ポート、規定のchallengeパスkeyAuthorizationの原文
DNS-01_acme-challenge.<ドメイン> のTXTbase64url(SHA256(keyAuthorization))、パディングなし

*.example.com を申請する場合、DNS検証では _acme-challenge.example.com が使用されます。同じ名前で複数のチャレンジTXTが存在する可能性があるため、自動化は各自の値に従って維持し、並列注文が互いに上書きしないようにする必要があります。公開後は権威応答と検証の可視性を確認する必要があり、ローカル再帰キャッシュのヒットをグローバルな準備完了と見なしてはいけません。RFC 8555:注文とチャレンジ

TLS-ALPN-01は443ポートを使用し、acme-tls/1 をネゴシエーションします。証明材料は専用自己署名証明書のクリティカルな acmeIdentifier 拡張に格納され、keyAuthorizationのSHA256ダイジェストです。ALPN文字列自体はチャレンジ値を運びません。この専用証明書には対応する単一のDNS SANが必要であり、通常のサービス証明書で直接代替することはできません。RFC 8737

実行可能な例:TXTを推測するのではなくチャレンジバイト列を確認する

以下では、教学用トークンと与えられたフィンガープリントを使用して、3種類の格納の違いを示します。JWKの正規化、署名付きリクエスト、または実際のCA検証は実装しません。

import base64
import hashlib

def b64url(data):
    return base64.urlsafe_b64encode(data).rstrip(b'=').decode('ascii')

token = 'teaching-token'
thumbprint = b64url(hashlib.sha256(b'teaching-account-key').digest())
key_auth = (token + '.' + thumbprint).encode('ascii')
digest = hashlib.sha256(key_auth).digest()
dns_txt = b64url(digest)
# acmeIdentifier の extnValue 内層は DER OCTET STRING:tag + length + 32 B。
acme_identifier_der = bytes([0x04, 0x20]) + digest
assert len(digest) == 32 and len(dns_txt) == 43
assert base64.urlsafe_b64decode(dns_txt + '=') == digest
assert acme_identifier_der[:2] == bytes([4, 32])
assert dns_txt != key_auth.decode('ascii')
assert hashlib.sha256(key_auth + b' ').digest() != digest
print('HTTP 原文、DNS ダイジェストエンコーディング、TLS 拡張内層長さ:', len(key_auth), len(dns_txt), len(acme_identifier_der))

有効期限、失効、ステータスの鮮度

証明書が期限切れでないからといって、秘密鍵が漏洩していないことを意味しません。失効は発行後に承認を取り消すことを表します。クライアントがステータスをどのように取得し、強制執行するかは、プロトコルの能力と製品ポリシーによって共同で決定されます。

メカニズム取得・検証するもの留意すべき境界
CRL署名付きリストとその範囲、更新時間パーティション化されているか、インクリメンタルである場合があり、必ずしもCA単一の巨大テーブルではない
OCSP指定された発行者/シリアル番号に対する署名ステータスgood は完全な証明書有効性の証明ではない
OCSP StaplingサーバーがキャッシュされたOCSP応答を添付署名、証明書関連性、鮮度の検証が必要
短期有効期限更新されない証明書の生存ウィンドウを短縮漏洩の瞬間に既存の資格情報を無効化しない

OCSPの good/revoked/unknown は「安全/不安全/ネットワーク異常」に単純化できません。good は少なくとも失効されていないことを示しますが、名前、用途、チェーンがすべて有効であることを保証するものではありません。キャッシュされた応答にも時間的な境界があります。仮に thisUpdate が 10:00、nextUpdate が 12:00 で、11:00 にそれを受け取った場合、ステータスが11:00に再生成されたことを意味しません。12:30 に受け取ったからといって、まだ鮮度がある肯定証拠にはなりません。RFC 6960

TLS 1.2 の stapling は CertificateStatus メッセージに一般的に見られます。TLS 1.3 では CertificateEntry の拡張を使用し、同じメッセージ位置にそのまま持ち込むことはできません。Must-Staple は TLS Feature 拡張の使用であり、コード署名用のCTではありません。その実際の制約効果はクライアントのサポートに依存します。RFC 7633、TLS 1.3 証明書メッセージ

2026年9月現在、Let's Encryptの旧OCSPアドレスを一般的なデプロイメント例として使用することはできません。そのOCSPサービスは2025年8月に終了し、CRL公開に移行しました。特定のCAがどのステータスメカニズムをサポートしているかは、現在の証明書拡張とそのポリシーを読む必要があります。Let's Encrypt 公告

CT:発行を監査可能にし、ドメイン検証に代えない

ログはSCTを返し、規定の期間内に提出項目をログに組み込むことを約束します。SCTを受け取ったからといって、inclusion proof をすでに取得したわけではありません。Merkle inclusion proof は、ある項目が特定のツリーヘッドに属していることを証明し、consistency proof はログの追従整合性を証明するために使用されます。監視者はこれに基づいて異常な発行を発見し、調査、失効、または信頼ポリシー処理に進みます。ログ自体がドメイン所有者に発行を認可するものではありません。RFC 9162:CT v2

プロトコルバージョンとブラウザのデプロイメントポリシーは別々に確認する必要があります。「すべての証明書にSCTを2つ固定する」とは書かないでください。例えば、現在のChromeの埋め込みSCTパスでは、180日以内のライフサイクルでは少なくとも2つの異なるログが必要で、180日を超える場合は3つ必要であり、運用者の多様性とログの状態条件があります。TLS伝送パスには別の条件があります。企業プライベート信頼と公開Web PKIに同じ結論を直接適用することはできません。Chrome CT Policy

CTログは公開情報源であり、申請内の内部命名が公開証明書に入ると、可視記録が形成されます。発見可能性は向上しますが、ドメインを隠すことはできず、悪意のある証明書が一時的に使用されるのを単独で阻止することはできません。

更新は公開の閉ループである

更新完了の合図:各サービスエントリが実際に新しい証明書を送信していること 発行成功 注文 valid、証明書をダウンロード 公開待機材料の検証 名前 / 有効期限 / 鍵の一致 配布とインスタンスのリロード 失敗処理、ロールバック、権限 実際のハンドシェイクを検証 各エントリでシリアル番号と有効期限をチェック CAが成功を返したのは第一步のみ;ロードバランサー、エッジノード、ロングコネクションにより新旧の状態が併存する可能性がある。

有効期限はX.509の永久に固定された定数ではなく、あるCAのある製品の期間をプロトコルの上限として書くことはできません。2026年9月現在、公開TLS Subscriber Certificateのベースライン要件は、2026-03-15から2027-03-15の間に発行された証明書に対して最大200日を規定しています。今後のフェーズではさらに緊縮される計画があり、特定の製品ではこれより短くなる可能性があります。自社構築PKIはこの数値を無差別に適用すべきではありません。CA/B Forum:6.3.2

教学用の証明書が残り20日であると仮定し、自動タスクが24時間ごとに実行され、正常な配布に2時間かかるとします。利用可能な回復ウィンドウは「20回確実に成功する更新」ではありません。チャレンジシステム、発行レートリミット、DNS権限、配布サービスは障害を共有する可能性があります。実際のサービス証明書の残り時間、最近の成功した公開、各エントリの整合性を監視し、処理失敗に必要な余裕を同時に保持する必要があります。

材料の検証が完了したら、ファイルをアトミックに置き換え、ユーザーをリロードし、各エントリから実際に送信された証明書を確認します。証明書ファイルの更新がプロセスの読み込みを意味するわけではなく、1つのノードの更新がクラスター全体の完了を意味するわけでもなく、既存の接続が自動的に再接続ハンドシェイクを行うわけでもありません。証明書の更新と秘密鍵のローテーションは別々に記録し、新しい証明書が必ずしも新しい鍵を意味すると誤解しないようにしてください。

さらに参照する