ファイナンス、情報通信技術のスキル・アグリゲーション・サイト

' . iseeit.jp 情報通信技術 . '
 
暗号と通信 ②

証明書は、どう選ばれているのか
1つのIPに、複数のサイト

1行のエラーログから始めます。 SNI が送られず、デフォルトの vhost が選ばれ、 Host ヘッダは別のドメインを指していた。 これは設定ミスではなく、Apache が正しく拒否した記録です。 名前が2回送られる仕組みと、その食い違いを追います。

SNI virtual host AH02032 421

[ 1 ]

1行のログから

運用しているサーバーの error_log に、こんな行が出ていました。

error_log(実際に記録されたもの)
Hostname appw.jp (default host as no SNI was provided)
and hostname www.ibe.tokyo provided via HTTP
have no compatible SSL setup for policy 'secure'

1文ですが、3つのことが書かれています

1
appw.jp (default host as no SNI was provided)
SNI が送られてこなかったので、デフォルトの vhost(appw.jp)が選ばれた。
2
hostname www.ibe.tokyo provided via HTTP
ところが HTTP の Host ヘッダは www.ibe.tokyo だった。別のサイト宛だ。
3
have no compatible SSL setup for policy 'secure'
この食い違いを許さない設定になっているので、接続を拒否した。
これは、正しく動いた記録です。 証明書は appw.jp のものを出したのに、中身は www.ibe.tokyo 宛だった。 Apache はその不一致を検出して、処理を止めました。 エラーログに出ていますが、直すべき設定ミスではありません

なぜこんなことが起きるのか。 前回は利用者から見た証明書を扱いました。 今回はサーバー側から——証明書がどう選ばれているかを見ます。

[ 2 ]

1つのIPで、複数のサイトを動かす

いま運用している VPS では、4つのドメインが同じIPアドレスで動いています。 appw.jp、ibe.tokyo、gwaw.jp、iseeit.jp。

IPアドレスは有限で、サーバーも1台ごとに費用がかかります。 1つのIPで複数サイトを運用するのは、ごく普通のことです。

name-based virtual host

同じIPアドレス・同じポートで、ドメイン名によってサイトを切り替える仕組み。 どのサイト宛かは、リクエストの中に書かれた名前で判断します。

HTTP なら簡単だった

HTTP のリクエスト
GET /index.html HTTP/1.1
Host: www.ibe.tokyo   ← これを見れば分かる

サーバーは Host: を読んで、対応する設定を選べばよい。 それだけで済んでいました

HTTPS で、順序の問題が生じる

前回書いたとおり、 TLS では暗号化の前に証明書を交換します。 しかし Host: ヘッダは暗号化された後に送られる。

順序
① クライアント → サーバー   接続を開始
② サーバー → クライアント   証明書を提示  ← どれを出す?
③ 鍵の交換・暗号化の開始
④ クライアント → サーバー   Host: www.ibe.tokyo  ← ここで初めて分かる

②の時点で、サーバーはどのサイト宛か知りません。 だから SNI が必要になりました。

[ 3 ]

名前は、2回送られる

いつ何を送るか暗号化
TLS接続の最初SNI(server_name 拡張)されない
HTTP暗号化の確立後Host: ヘッダされる

同じドメイン名を、2回伝えています。 ふつうは同じ値ですが、別の値を入れることもできます。 仕組み上、止められません。

正常な接続
[TLS]  SNI: www.ibe.tokyo       → ibe.tokyo の証明書を提示
[HTTP] Host: www.ibe.tokyo      → 一致。処理を続行
実ログのケース
[TLS]  SNI: なし                 → デフォルト(appw.jp)の証明書を提示
[HTTP] Host: www.ibe.tokyo不一致。拒否
[ 4 ]

デモ:証明書が選ばれるまで

SNI と Host の組み合わせを変えると、どの証明書が選ばれ、結果がどうなるかが見られます。 実際の構成に合わせ、appw.jp をデフォルトの vhost としています。

Interactive · vhost selection
SNI と Host の組み合わせ
Apache の vhost 選択を再現しています。SSLStrictSNIVHostCheck は有効(既定)の想定です。
TLS層:SNI(暗号化の前に送られる)
HTTP層:Host ヘッダ(暗号化の後に送られる)
error_log に記録されるもの

「SNIなし・一致」を試してみてください。 SNI が無くても、Host がデフォルトの vhost と同じなら正常に処理されますSNI が無いこと自体はエラーではありません

[ 5 ]

デフォルトの vhost とは

SNI が無いとき、サーバーは何らかの証明書を出さなければなりません。 黙って切ることもできますが、それでは古いクライアントが一切つながらなくなります。

そこでデフォルトの vhost が選ばれます。 Apache では、設定ファイルで最初に現れた vhost(または _default_)です。

確認方法
$ apachectl -S

VirtualHost configuration:
*:443    is a NameVirtualHost
   default server appw.jp (/etc/apache2/sites-enabled/appw.conf:1)
         port 443 namevhost appw.jp
         port 443 namevhost www.ibe.tokyo
         port 443 namevhost www.gwaw.jp
         port 443 namevhost www.iseeit.jp

default server と書かれているものが、SNI 無しのときに選ばれます。 今回の環境では appw.jp でした。 ログの (default host as no SNI was provided) は、これを指しています。

デフォルトは「たまたま最初にある」だけです。 意図して選んだわけではなく、 設定ファイルの読み込み順で決まります。 どれがデフォルトかを把握していないと、 ログを読むときに混乱します
[ 6 ]

なぜ、不一致を拒否するのか

食い違っていても、Host ヘッダのほうを信じて処理を続けることは技術的に可能です。 実際、SSLStrictSNIVHostCheckoff にすれば通ります。

それでも、拒否するべきです。 appw.jp の証明書を出したのに www.ibe.tokyo の中身を返すなら、 証明書の検証に意味がなくなります。 利用者は「appw.jp に接続した」と確認したのに、 別のサイトの内容を受け取ることになる

前回書いたとおり、 証明書が保証するのは「接続先が名乗るドメインの持ち主である」ことだけです。 その保証を成立させるには、 提示した証明書と、返す中身が一致していなければなりません

HTTP/2 では、もう一つ理由がある

HTTP/2 では、1本の接続を複数のドメインで使い回すことがあります。 証明書が両方をカバーしていれば、接続を張り直さずに済むためです。

その際、この接続では扱えないドメイン宛のリクエストが届くことがあります。 そのときサーバーは 421 Misdirected Request を返し、 クライアントに接続を張り直すよう促します

421 は「あなたは間違った宛先に来ている」という意味です。 404(見つからない)でも 403(許可されない)でもありません。 接続そのものが対象外だと伝えています。
[ 7 ]

ログから見える相手

では、SNI を送ってこないのは誰なのか。

送らない相手実情
極めて古いクライアントWindows XP の IE など。現在ではほぼ見かけない
簡易なスクリプトSNI を設定していない自作ツール
スキャン・探索IPアドレスに直接接続し、応答を確かめる

正規のブラウザは、20年以上前から SNI を送っています。 いま SNI 無しで来るのは、ほぼ人間の操作ではありません

今回のログは、スキャンでした

IPアドレスに直接つないで、そこで何が動いているかを探る行為です。 Host ヘッダに適当なドメインを入れて、応答の違いを見ている。

エラーが出ること自体が、防御が働いた証拠です。 証明書と中身の食い違いを許していたら、 スキャン側は別サイトの情報を得られたかもしれません拒否したから、ログに残った

bot の分類でも書きましたが、 名乗りは自己申告です。SNI も Host ヘッダも、クライアントが自由に書けます。 食い違いを検出する仕組みがあって初めて、意味を持ちます

[ 8 ]

運用側で確かめること

確認したいこと方法
デフォルトの vhost はどれかapachectl -S
SNI を送ったときの証明書openssl s_client -servername ドメイン -connect IP:443
SNI 無しのときの証明書openssl s_client -noservername -connect IP:443
厳格チェックの設定SSLStrictSNIVHostCheck(既定は on)
SNI の有無で、返る証明書を比べる
# SNI あり
$ openssl s_client -servername www.ibe.tokyo -connect 203.0.113.10:443 \
    </dev/null 2>/dev/null | openssl x509 -noout -subject
subject=CN = www.ibe.tokyo

# SNI なし
$ openssl s_client -noservername -connect 203.0.113.10:443 \
    </dev/null 2>/dev/null | openssl x509 -noout -subject
subject=CN = appw.jp        ← デフォルトが返る
この2つを比べると、構成が把握できます。 SNI 無しで何が返るかを知っていれば、 ログの「default host」が何を指すかが分かります。 設定を変えなくても、確かめるだけで読み方が変わります
[ 9 ]

まとめ

この記事のまとめ

  • 1つのIPで複数サイトを動かすと、どの証明書を出すかを判断する必要がある。
  • ドメイン名は2回送られる。TLS の SNI(平文)と、HTTP の Host(暗号化後)。
  • SNI が無いとデフォルトの vhost が選ばれる。設定ファイルで最初に現れたもの。
  • SNI 無しが即エラーではない。Host がデフォルトと一致すれば通る。
  • 食い違うと AH02032証明書と中身が違う状態を許さないための拒否。
  • 緩めることはできるが、緩めれば証明書の検証が無意味になる
  • HTTP/2 の接続再利用では 421 を返し、張り直しを促す。
  • いま SNI を送らないのは、ほぼスキャンや自動化ツール。エラーは防御が働いた記録。

関連記事