Skip to main content

TLS と証明書 — 何を信じて暗号化しているか

https の「s」が何をしているのかと問われて、「暗号化している」と答えるのは半分です。暗号化だけなら証明書は要りません。証明書があるのは、暗号化する相手が本当に意図した相手かを確かめるためです。

相手が偽者でも通信は暗号化できます。偽者と安全に会話しても意味がない、というのが証明書の存在理由です。

この章で学ぶこと

  • TLS が保証する 3 つの性質と、証明書が担うのはどれか
  • 証明書チェーンの検証と、中間証明書の配信がサーバーの責任であること
  • TLS 終端の位置が変えるもの
  • 証明書エラーを 5 種類に読み分ける手順
前提知識

TCP/IP の階層モデルと DNS の名前解決を前提にします。TLS は TCP の上、HTTP の下に入ります。

TLS は 3 つを保証する

性質意味破れるとどうなるか
機密性第三者に中身を読まれない通信内容が盗まれる
完全性途中で書き換えられない内容が改竄される
同一性相手が名乗った本人である偽サイトと安全に通信する

証明書が担うのは 3 つ目だけです。1 つ目と 2 つ目は鍵交換と暗号化のアルゴリズムが担います。

3 つ目が無いと、1 つ目と 2 つ目は意味を失います。攻撃者が間に入って正しく暗号化してくれるなら、機密性も完全性も攻撃者に対しては成立しているからです。

ハンドシェイクで何が決まるか

決まるのは 2 つです。使う暗号方式共通鍵。証明書はこの過程で「公開鍵が本当にこのホストのものか」を第三者が保証する紙として使われます。

SNI がなぜ要るか

ClientHello に接続先のホスト名が入っています (SNI = Server Name Indication)。これがないとサーバーはどの証明書を出すべきか分かりません

TCP/IP で見たように、TCP の接続は IP アドレスとポートで識別されます。1 つの IP アドレスの 443 番で複数のホスト名を配信するには、暗号化が始まる前にホスト名を伝える必要があります。

SNI は暗号化前に送られるので平文です。どのサイトへ接続したかは経路上で分かります。中身は分かりませんが、接続先は隠れません。

証明書チェーン

証明書は 1 枚では成立しません。

ルート証明書 (信頼ストアに入っている)
└─ 中間証明書 (認証局が発行)
└─ サーバー証明書 (example.com 用)

検証はサーバー証明書から上へ辿り、信頼ストアにあるルートに到達できるかを確かめます。到達できなければ検証は失敗します。

ルート証明書は OS やブラウザに最初から入っています。中間証明書は入っていません。

中間証明書の配信はサーバーの責任

ここが実務で最も踏む落とし穴です。サーバーは自分の証明書と一緒に中間証明書も送らなければなりません。

送り忘れると、ブラウザでは繋がるのに他のクライアントで落ちるという症状になります。ブラウザは中間証明書を過去の接続からキャッシュしていたり、Authority Information Access を見て自分で取得したりするので、欠けていても補完できることがあります。curl やサーバー間の HTTP クライアントは補完しません。

「ブラウザでは見られるのに API 呼び出しだけ落ちる」は中間証明書の配信漏れを最初に疑います。

何が検証されるか

項目失敗する例
有効期限更新を忘れた
ホスト名www.example.com の証明書で example.com へ接続した
チェーン中間証明書が足りない、自己署名
失効秘密鍵の漏洩で失効させられた証明書

ホスト名の照合には SAN (Subject Alternative Name) を使います。Common Name は使いません — 主要なブラウザは SAN のみを見ます。example.comwww.example.com の両方で使うなら、SAN に両方を列挙します。

失効の確認は仕組みとしては存在しますが、確実には効きません。理由は 2 つあります。

  • **応答が得られないときに通してしまう。**OCSP で問い合わせて返事が来ないとき、クライアントは接続を拒否せず通します。拒否する設計にすると、認証局の障害で無関係なサイトまで一斉に見られなくなるためです。攻撃者は問い合わせを妨害すれば確認を回避できます
  • **そもそも問い合わせないブラウザがある。**Chrome は OCSP による確認をやめ、失効した証明書のリストを自分で配布する方式に切り替えています

失効させても即座には止まらないと考えておきます。

期限が短い証明書が主流になったのは、この事情への対処でもあります。失効が効かないなら期限を短くする、という設計です。

HSTS — 平文でのアクセスを許さない

Strict-Transport-Security: max-age=31536000; includeSubDomains

これを受け取ったブラウザは、指定秒数のあいだそのホストへ http でアクセスしないようになります。ユーザーが http:// と入力しても、リクエストを送る前に https へ書き換えます。

守っているのは最初の 1 回のリダイレクトです。http:// で接続してから 301 で https へ飛ばす設計だと、その 1 回目が平文なので割り込まれます。HSTS はその隙をなくします。

ただし初回のアクセスは守れません。ヘッダーを受け取る前だからです。これを埋めるのがブラウザに最初から埋め込まれたリスト (preload) です。

includeSubDomains を付けるとサブドメインすべてが対象になります。https に対応していないサブドメインがあると到達できなくなるので、max-age を長くする前に全サブドメインを確認します。

TLS 終端の位置

CDN やロードバランサーを挟むと、TLS はそこで終わります。

ブラウザから CDN までは暗号化されていますが、CDN からオリジンまでは別の話です。設定次第で平文にもなります。

この構図は HTTP キャッシュと CDN と同じです。あちらでは「CDN が持つキャッシュはパージできるが、ユーザーの端末に配ったものは回収できない」という非対称が問題でした。ここでは「ブラウザから CDN までの保証は、CDN からオリジンまでを保証しない」という非対称です。中間の装置がある通信では、区間ごとに何が保証されているかを分けて考えます。

証明書を持っているのも CDN です。オリジンサーバーの証明書がどうなっているかは、ブラウザからは見えません。

HTTP/3 が速いのは TLS を統合したから

HTTP で「HTTP/3 は TCP をやめて接続確立を短縮する」と説明しました。短縮の中身は TLS と関係します。

バージョン接続確立に要する往復
HTTP/1.1 / HTTP/2 (TLS 1.2)TCP の 3 ウェイハンドシェイク + TLS のハンドシェイク
HTTP/1.1 / HTTP/2 (TLS 1.3)TCP + TLS 1.3 (往復が 1 回減った)
HTTP/3QUIC が TCP の役割と TLS 1.3 を統合して 1 往復

QUIC は独立した暗号化層を持たず、TLS 1.3 のハンドシェイクを自分の接続確立に組み込んでいます (RFC 9001 がこの統合を定めています)。だから「接続確立とセキュアな接続の確立」が別々の往復にならず、1 回で済みます。

**HTTP/3 に「暗号化なし」の選択肢はありません。**TLS が接続の一部になっているためです。

証明書エラーを読み分ける

ブラウザの警告画面はどれも似ていますが、原因は 5 種類に分かれます。

症状原因直し方の方向
「証明書の有効期限が切れています」更新忘れ更新の自動化と、期限の監視
「この証明書は別のサイトのものです」SAN にホスト名が無いSAN に追加して再発行
ブラウザは通るが curl が落ちる中間証明書の配信漏れサーバーの証明書チェーン設定
「発行元が信頼されていません」自己署名、または信頼されない認証局公的な認証局で発行する
端末の時計がずれている有効期限の判定が狂う端末の時刻同期

最後の 1 つを覚えておくと時間が節約できます。「自分だけ全サイトで証明書エラーが出る」なら、サーバー側ではなく端末の時計を見ます。有効期限は時刻の比較で判定するので、時計が数年ずれていればすべての証明書が期限外になります。

混在コンテンツ

https のページから http のリソースを読み込むと、ブラウザが遮断または警告します。

種類ブラウザの扱い
スクリプト・スタイルシート遮断 (実行されない)
画像・動画遮断または自動で https へ書き換え

これは クッキーSecure 属性と目的が同じです。https で守った通信の隣に平文の経路を残さない、という考え方です。クッキーの側では「平文のときは送らない」で実現し、混在コンテンツの側では「平文のリソースは読み込まない」で実現しています。

よくある誤解

「SSL と TLS は別のもの」 — TLS は SSL の後継です。SSL 3.0 以前は既に使われていません。「SSL 証明書」という呼び方が残っていますが、実体は TLS で使う証明書です。

「https なら中身は誰にも見えない」 — TLS 終端の位置までです。CDN で終端すればそこから先は別の保証になります。SNI と DNS の問い合わせから接続先も分かります。

「証明書は暗号化のためにある」 — 同一性の保証のためにあります。暗号化は証明書なしでもできます。

「証明書を失効させれば即座に信頼されなくなる」 — なりません。失効の確認は確実に効かないので、期限が来るまで使えてしまう可能性があります。

「HSTS を入れれば初回アクセスも守られる」 — 守られません。ヘッダーを受け取る前なので、preload リストに登録しない限り初回は素通しです。

確認問題

問 1. ブラウザでは正常に表示されるサイトが、サーバーからの API 呼び出しでは証明書エラーになります。何を疑いますか。

答え: 中間証明書の配信漏れです。

サーバーは自分の証明書と一緒に中間証明書も送る必要があります。送っていない場合、クライアントはチェーンをルートまで辿れません。

ブラウザは補完できることがあります。

  • 過去に他のサイトへ接続した際に同じ中間証明書をキャッシュしている
  • 証明書に書かれた取得先から自分で取りに行く

curl やサーバー側の HTTP クライアントは補完しないので、欠けていればそのまま失敗します。

ブラウザで確認しても検証にならないという点が要点です。チェーンの完全性は、ブラウザ以外のクライアントか、専用の検証ツールで確かめます。

問 2. CDN の背後にオリジンサーバーを置いています。ブラウザのアドレスバーに鍵アイコンが出ていれば、オリジンまで暗号化されていますか。

答え: いいえ。鍵アイコンが示すのはブラウザと CDN の区間だけです。

TLS は CDN で終端します。CDN からオリジンまでは別の接続で、設定次第で平文にもなります。

区間ごとに分けて考えます。

区間何が保証されるか
ブラウザ → CDN鍵アイコンが示す範囲。CDN の証明書で検証される
CDN → オリジン別の設定。平文にもできる

オリジンとの通信も暗号化するなら、CDN 側でその設定が要ります。さらにオリジンの証明書を検証するかどうかも設定項目です。検証しない設定だと、CDN とオリジンの間に割り込まれても気づけません。

同じ構図が HTTP キャッシュと CDN にもあります。中間に装置がある通信では、どの区間の話をしているかを明示しないと判断を誤ります。

問 3. HSTS を max-age=31536000; includeSubDomains で設定しました。何を事前に確認しますか。

答え: すべてのサブドメインが https で提供されていることです。

includeSubDomains はサブドメインすべてを対象にします。https に対応していないサブドメインがあると、ブラウザはそこへ http でアクセスしなくなり、到達できなくなります

さらに max-age=31536000 は 1 年です。いったんブラウザが記録すると、サーバー側の設定を戻してもそのブラウザは 1 年間 http を使いませんmax-age=0 を配信すれば解除できますが、それを受け取るには https で接続できる必要があります。https が壊れている状態では解除の指示も届きません。

確認する順序があります。

  1. 対象になるサブドメインをすべて列挙する (ワイルドカードの DNS レコードがあれば特に注意)
  2. それぞれが https で応答することを確かめる
  3. max-age を短い値 (数分) で始める
  4. 問題がないことを確認してから伸ばす

短い値から始めるのは、誤りに気づいたときに影響が自然に消えるようにするためです。

まとめ

  • TLS は機密性・完全性・同一性を保証する。証明書が担うのは同一性
  • 同一性がないと、機密性と完全性は攻撃者に対して成立してしまい意味を失う
  • 証明書チェーンはルートまで辿れて初めて有効。中間証明書の配信はサーバーの責任
  • ブラウザは中間証明書を補完することがある。だからブラウザで確認しても検証にならない
  • ホスト名の照合は SAN で行う。Common Name は使われない
  • 失効は確実に効かない。期限を短くするのはその対処
  • TLS 終端の位置で保証が切れる。区間ごとに分けて考える
  • 証明書エラーの原因は 5 種類。全サイトで出るなら端末の時計を疑う
関連リファレンス

次に読む