- ブラウザURL入力今ここ
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口
- アプリ処理
- DB保存
前回は、会話の記録に署名することを見ました。証明書では、両端が共有しているのは起点だけです。残りをどう埋めるかは、受け取る側に任されています。
まず結論
通信の仕組みは、両端が何を共有しているかで決まります。証明書の検証で両端が前もって共有しているのは起点(トラストアンカー)で、TLS はそれを送らずに済ませてよいと定めています。
起点から相手の証明書までの道は、受け取る側が手元の材料で組み立てます。組み立て方は TLS も RFC 5280 も定めていないので、中間証明書を送らないサーバに対して、成功する検証器と失敗する検証器が分かれます。
ルート→中間→末端の3段で証明書を自作し、末端だけを送る(中間を送らない)サーバを手元に立てました。openssl s_client で2回接続します。1回目は手元にルートだけを持たせ、2回目はルートと中間を持たせます。結果はどうなるでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
TLS が決めているのは、並べ方まで
サーバは Certificate メッセージで証明書を送ります。並べ方について、TLS 1.3 の現行仕様 RFC 9846(2026年7月。TLS 1.3 を定めていた RFC 8446 を廃止した改訂版)§4.5.1 はこう書きます。RFC 8446 では §4.4.2 で、同文です。
The sender's certificate MUST come in the first CertificateEntry in the list. Each following certificate SHOULD directly certify the one immediately preceding it. Because certificate validation requires that trust anchors be distributed independently, a certificate that specifies a trust anchor MAY be omitted from the chain, provided that supported peers are known to possess any omitted certificates.
MUST は自分の証明書を先頭に置くこと。以降の並びは SHOULD。MAY で省けるのはトラストアンカーを指す証明書だけで、相手が持っていると分かっている場合に限られます。中間を省いてよいとは書いていません。
省ける理由も書いてあります。トラストアンカーは distributed independently、TLS の外で前もって配られている。両端が共有しているものは運ばなくてよい——このコースの芯が、そのまま条文になっています。
では、届いた証明書から道を作るのは誰か。§4.5.1.2 です。
If the receiver cannot construct an acceptable chain using the provided certificates and decides to abort the handshake, then it MUST abort the handshake with an appropriate certificate-related alert
construct(組み立てる)の主語は受け取る側です。RFC 8446 の同じ文(§4.4.2.2)は client でしたが、9846 でサーバとクライアントの節がまとめられ、receiver になりました。
組み立て方はどこにも決まっていません。§4.5.1.3(RFC 8446 では §4.4.2.4)は detailed certificate validation procedures are out of scope for TLS (see [RFC5280]) と RFC 5280 に預け、その RFC 5280 §6.1 は The procedure performed to obtain this sequence of certificates is outside the scope of this specification. と書きます。組み立てを扱う RFC 4158 は Informational で、要件を持ちません。
- ・TLS の並びは末端が先頭。RFC 5280 §6.1 の番号は逆で、certificate 1 がアンカーの発行したもの、certificate n が末端
- ・手元の材料からどう道を組むかは、TLS も RFC 5280 も定めていない
送るものは同じで、手元だけを変える
P-256 でルート・中間・末端(CN=localhost)を自作し、openssl s_server を2つ立てました。18443 は -cert_chain inter.pem で中間も送り、18444 は末端だけを送ります。OpenSSL 3.6.3 の s_client にはルートだけを持たせます(-CAfile root.pem -verify_return_error)。
--- 18443(中間も送る)Certificate chain0 s:CN=localhosti:CN=L7 Test Intermediate1 s:CN=L7 Test Intermediatei:CN=L7 Test RootVerify return code: 0 (ok)--- 18444(末端だけ)verify error:num=20:unable to get local issuer certificateCertificate chain0 s:CN=localhosti:CN=L7 Test IntermediateVerify return code: 20 (unable to get local issuer certificate)
18443 には 1 番として中間が載り、18444 には載っていません。末端は発行者が L7 Test Intermediate だと名乗っていますが、受け取る側の手元にそれが無いので、unable to get local issuer certificate で止まりました。
同じ 18444 に、ルートと中間を連結した root+inter.pem を持たせると、こうなります。
depth=2 CN=L7 Test Rootdepth=1 CN=L7 Test Intermediatedepth=0 CN=localhostVerification: OKVerify return code: 0 (ok)
サーバが送ったのは末端1枚のままです。受け取る側が手元の中間を使って3段の道を組み、成功しました。なお s_client 3.6.3 には中間だけを別枠で渡す -untrusted が無かったため、中間も -CAfile に入れています。
中間を送らないサーバ(18444)に、s_client でルートだけを持たせると 20、ルートと中間を持たせると 0 でした。ここから言えることはどれですか。
共有していないものは、運ぶか、取りに行く
トラストアンカーは両端が共有しているので省けます。中間はそうとは限らないので、送る側が運びます。運ばれなかったとき、受け取る側に残る手は、手元の材料で組むか、どこかへ取りに行くかです。
取りに行く先を証明書自身に書いておく仕組みが、RFC 5280 §4.2.2.1 の Authority Information Access(AIA)拡張の caIssuers です。
The referenced CA issuers description is intended to aid certificate users in the selection of a certification path that terminates at a point trusted by the certificate user.
is intended to aid(助けるためのもの)であって、取りに行けとは書いていません。行き着く先も a point trusted by the certificate user、受け取る側が信頼する起点です。取りに行けば、別のサーバへの往復が増えます。共有していなければ往復が要るの往復が、ここに現れます。
RFC 4158 §2 も Different implementations can build the certification path in different ways と書いています。取りに行くかどうかも、実装ごとに違ってよいのです。
同じ末端1枚に、取りに行く検証器と行かない検証器
末端に AIA(caIssuers;URI:http://127.0.0.1:18081/…)を付けて作り直し、そこへの要求を記録する小さな HTTP サーバを立てました。TLS サーバは中間を送りません。キャッシュを避けるため、試行ごとにポートと URL を変えています。
macOS 標準の curl 8.7.1 を、環境変数 CURL_SSL_BACKEND=secure-transport で SecureTransport に切り替えて(既定では使われません)接続すると 200 が返り、記録にはこう残りました。
2026-09-24T01:51:11.171Z AIA GET /aiaST.der com.apple.trustd/3.02026-09-24T01:51:13.256Z AIA GET /aiaLS.der com.apple.trustd/3.0
要求したのは curl ではなく macOS の trustd でした。OpenSSL 3.6.3 の s_client で同じ形のサーバ(AIA 付き・中間なし)につなぐと 20 のままで、記録は空でした。AIA も中間も無い 18444 には、SecureTransport でも RecoverableTrustFailure で失敗します。SecureTransport と s_client の対比に限れば、違いは取りに行ったかどうかです。ただし同じ curl を既定のバックエンドのまま使うと、2行目のとおり trustd は取得したのに unable to get local issuer certificate で失敗しました(理由は確かめられていません)。取りに行くことは、成功を保証しません。なお SecureTransport の接続は TLS 1.2 でした。
末端に AIA を付けると、SecureTransport 経由の curl は成功し、s_client は失敗しました。AIA の位置づけとして正しいのはどれですか。
macOS curl 8.7.1(SecureTransport)
末端の AIA を見て trustd が中間を取りに行き、道が組めた。成功の代わりに、別サーバへの往復が増えている。
http_code=200
OpenSSL 3.6.3 の s_client
AIA を取りに行かず、手元の root.pem だけで組もうとして発行者が見つからず止まった。AIA 配布サーバの記録は空だった。
depth=0 CN=localhostverify error:num=20:unable to get local issuer certificateVerify return code: 20 (unable to get local issuer certificate)
サーバの形は同じなのに、結果が逆になりました。何がそれを分けたのか、両方に共通する構造で答えてください。
受け取る側が、何を前もって持っていて、足りないものをどうしたかを並べてみてください。
この章のまとめ
- TLS の並べ方: 自分の証明書が先頭(MUST)、以降は直前を直接証明(SHOULD)、トラストアンカーを指す証明書だけ省いてよい(MAY)。中間は省いてよいと書かれていない。
- 省ける理由は、トラストアンカーが前もって配られ、両端が共有しているから。
- 組み立てる主語は受け取る側(RFC 9846 §4.5.1.2 の
receiver)。組み立て方は TLS も RFC 5280 も定めず、RFC 4158 は要件を持たない。 - 同じサーバでも、受け取る側の手元の材料と組み立て方で成否が変わる。AIA を取りに行くかは実装次第で、取りに行けば往復が増える。
- サーバの設定を確かめるなら、成否ではなく送っている並びを見る。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
RFC 9846 §4.5.1 が「チェーンから省いてよい(MAY)」としているのはどれですか。
用語集(3語)
- トラストアンカー
- 検証する側が前もって信頼すると決めている起点(ルート CA の証明書や公開鍵)。TLS の外で配られるので、相手が持っていると分かっている場合に限り、チェーンから省いてよい(MAY)。
- 中間証明書
- 起点と末端のあいだに入る CA の証明書。受け取る側が持っていなければ、送る側が運ぶか、受け取る側が補うしかない。
- AIA(caIssuers)
- RFC 5280 §4.2.2.1 の証明書拡張。発行者の証明書の取得先を書いておける。経路の選択を助けるためのもので、取得は義務ではない。