ykitaa.dev
約 16 分

https://example.com と打つと何が起きるか — ドメイン・DNS・証明書を一本の線でつなぐ

ブログを公開するためにドメインを取り、DNS レコードを設定し、証明書が自動で発行されるのを眺めていました。手順としては進むのですが、一つひとつが何をしているのかは曖昧なままでした。

ドメインは「サイトの住所」、DNS は「名前を IP に変換するもの」、証明書は「HTTPS にするために要るもの」。どれも間違ってはいません。ただ、なぜそれが必要なのか、3 つがどう噛み合っているのかまで説明できるようになりたい、と思いました。

そこで整理したのがこの記事です。軸は一つだけ、https://example.com と打ってから画面が出るまでに誰が何をしているか。この線を最後まで引くと、ドメインも DNS も証明書も、バラバラの知識ではなく一つの流れの部品として並びます。そして後半で視点を「公開する側」に反転させると、同じ 3 つが今度は自分が用意するものとして再登場します。

なお、TLS の鍵交換アルゴリズムの中身や DNS のパケット構造までは踏み込みません。「誰が何を担当していて、なぜそれが要るのか」が説明できるようになることを目標にしています。

全体像:3 つの工程

ブラウザが https://example.com を開くとき、HTTP のリクエストが飛ぶ前に下ごしらえが 3 つあります。

sequenceDiagram
    autonumber
    participant B as ブラウザ
    participant D as DNS
    participant S as サーバー
    B->>D: example.com の住所は?
    D-->>B: 203.0.113.10
    B->>S: TCP 443 番ポートへ接続
    B->>S: ClientHello(宛先の名前 SNI + 鍵の材料)
    S-->>B: ServerHello(鍵の材料)
    rect rgba(127, 209, 166, 0.14)
        note over B,S: この枠の中は暗号化される(経路上からは読めない)
        S-->>B: Certificate(サーバーの証明書)
        S-->>B: Finished
        B->>S: Finished(証明書を検証し、鍵が確定)
        B->>S: HTTP リクエスト(中身は平文の HTTP のまま)
        S-->>B: HTTP レスポンス
    end

やっていることを言葉にすると、こうなります。

  1. 名前を引く — example.com という名前から、実際の接続先(IP アドレス)を調べる
  2. 相手を確かめる — 接続した相手が本当に example.com の持ち主か確認する
  3. 暗号で喋る — 盗み見られない鍵を決めて、以後の通信を包む

この 2 と 3 をまとめて引き受けているのが TLS(Transport Layer Security) です。通信を暗号化し、相手が本物かを確かめるための仕組みで、HTTP とは独立した一つの層になっています。メールでもデータベース接続でも使われていて、HTTP 専用のものではありません。

だから、HTTPS は HTTP とは別のプロトコルではありません。HTTP はもともと、文字列でリクエストとレスポンスをやり取りするだけの、暗号化の機能を持たないプロトコルです。そのままだと経路上で丸見えなので、TLS が用意した「保護された通り道」の中を通すことにした。それが HTTPS です。

      HTTP          文字列でやり取りする。暗号化の機能は持たない
  --------------
       TLS          通り道を暗号化し、相手が本物かを確かめる
  --------------
       TCP          接続を作る

図の最後で HTTP のやり取りが暗号化の枠の中にあるのは、このためです。HTTP 自身は何も変わっておらず、外側を TLS が包んでいます。

図の ClientHello から Finished までが、この準備のやり取りで、TLS ハンドシェイクと呼ばれます。ここで一つ、直感に反する点があります。順番です。

最初の 2 通(ClientHello / ServerHello)で、両者は暗号化に使う鍵の材料を送り合います。ここはまだ暗号化できないので平文です。ところが鍵が決まった瞬間から、残りのやり取りは暗号化されます。サーバーの証明書が届くのはその後なので、証明書は暗号化された中で運ばれてくることになります(TLS 1.3 の場合。RFC 8446 に「ServerHello 以降はすべて暗号化される」と定められています)。

「まず相手が本物か確かめて、それから暗号化する」と考えたくなりますが、実際は逆です。先に暗号化された通り道を作り、その中で相手の身元を確認する。順番が逆でも困らないのは、たとえ攻撃者と鍵を交換してしまっても、直後の証明書の確認で気づけるからです。そこで気づけばブラウザは接続をやめ、警告を出します。

以降はこの 3 つを順に見ていきます。

名前を引く:ドメインと DNS

なぜ名前が要るのか

IP アドレスを直接打てば済む話に思えますが、名前が必要な理由は 3 つあります。

覚えられないことは、実は一番小さな理由です。より効くのは、IP アドレスが変わること。サーバーを引っ越しても、名前が同じなら利用者は何も知らずに済みます。そして 1 つのサービスが 1 台とは限らないこと。アクセスを複数台に分散したり、利用者に近い拠点へ案内したりできるのは、名前と実体の間に一段の変換が挟まっているからです。

名前は「住所そのもの」ではなく、住所を引くための見出しです。この間接性が、あとで証明書の話にも効いてきます。

誰がその対応表を持っているのか

DNS は巨大な分散データベースですが、「どこかに全部入りの表がある」わけではありません。名前ごとに、答えを持っているサーバーが分かれています。

example.com について正しい答えを持っているのは、そのドメインのために立てられた DNS サーバーです。これを権威 DNS サーバー(authoritative name server)と呼びます。「権威」というのは偉いという意味ではなく、そのドメインに関しては、ここの答えが正本であるという意味です。他のサーバーが返す答えは、すべてここから取ってきたコピーです。

ブラウザは直接そこへ行くのではなく、リゾルバ(プロバイダのものや 8.8.8.8 のような公開のもの)に尋ねます。リゾルバは答えを持っていなければ、上から順に辿って探しに行きます。

sequenceDiagram
    autonumber
    participant B as ブラウザ
    participant R as リゾルバ
    participant Ro as ルートサーバー
    participant C as .com のサーバー
    participant A as example.com の権威サーバー
    B->>R: example.com の住所は?
    R->>Ro: .com はどこが知っている?
    Ro-->>R: .com のサーバーはこちら
    R->>C: example.com はどこが知っている?
    C-->>R: 権威サーバーはこちら
    R->>A: example.com の A レコードは?
    A-->>R: 203.0.113.10
    R-->>B: 203.0.113.10

どの段階も「答えそのもの」ではなく「次に聞くべき相手」を返しているのが分かります。最後の権威サーバーだけが実際の住所を知っていて、リゾルバはその答えを持ち帰り、しばらくキャッシュします。

キャッシュがあるおかげで毎回ルートから辿らずに済みますが、代わりに設定を変えてもすぐには全員に届きません。レコードに付いている TTL の間、世界中のリゾルバは古い答えを返し続けます。

最小限のレコード

では、その権威サーバーは何を返しているのか。答えの中身がレコードです。用途ごとに何種類もありますが、「名前から接続先を引く」という目的に絞れば、まずは 2 つで足ります。

A レコードは、名前から IP アドレスへの対応です(IPv6 なら AAAA)。example.com → 203.0.113.10 のように、線の終点を直接書きます。

CNAME レコードは、名前から別の名前への転送です。www.example.com → example.pages.dev のように書くと、引いた相手がさらにその名前を引きに行きます。

一見すると A のほうが素直ですが、実務では CNAME が重要です。CDN や PaaS にサイトを載せると、利用者である私たちに渡されるのは IP アドレスではなく、example.pages.dev のようなサービス側のドメイン名であることがほとんどだからです。

これは、サービス側が IP アドレスを固定できない(あるいはしたくない)ためです。名前で受け取っておけば、サービス側が裏で IP を入れ替えても、CNAME を張った側は何もせずに追従できます。A レコードで IP を直に書いてしまうと、相手の都合で切り替わった瞬間に繋がらなくなります。

ひとつ罠があって、ドメインそのもの(example.com)には CNAME を置けません。仕様上、他のレコードと共存できないためです(RFC 1034 / RFC 2181)。この制約は各社が独自に回避しており(ALIAS、ANAME、CNAME フラットニングなどと呼ばれます)、「www ありなら動くのに www なしだと設定できない」という現象の正体はこれです。

なお名前は、住所を引くためだけでなく接続のときにも使われます。1 つの IP アドレスに複数のサイトが同居している場合(共有ホスティングや CDN では普通のことです)、IP だけではどのサイト宛てか分からないためです。そこで HTTP では Host ヘッダ、TLS では SNI(RFC 6066)で、接続の最初に宛先の名前を伝えます。冒頭の図の ClientHello に名前が載っていたのはこのためです。

暗号で喋る:HTTPS が守っているもの

ここまでで接続先は決まりました。次は中身の話です。

HTTPS は、先ほど書いたとおり HTTP を TLS で包んだものです。包むことで守られるのは 3 つあります。

盗聴されないこと(内容を読まれない)、改竄されないこと(途中で書き換えられたら気づける)、そしてなりすまされないこと(相手が本物だと確認できる)。

前の 2 つは暗号技術そのもので解決します。鍵を知らない第三者には読めず、鍵を知らずに書き換えれば検証に失敗します。

ところが、3 つ目だけは暗号化では解決しません。これがこの記事で一番大事な点です。

考えてみると分かります。暗号化とは「鍵を共有した相手との通信を第三者から守る」技術です。では、その鍵を共有した相手が攻撃者だったらどうなるか。通信は完璧に暗号化されたまま、攻撃者に筒抜けになります。

相手を確かめる:なぜ証明書が要るのか

暗号化されていても盗める

ここで考えるのは、証明書という仕組みがなかったら何が起きるかです。カフェの Wi-Fi に悪意のある機器がいて、DNS の応答を偽ったとします。example.com の住所を尋ねたブラウザは、攻撃者の IP を受け取ります。ブラウザは疑わずに接続し、攻撃者と鍵を交換し、暗号化された通信を始めます。攻撃者は受け取った内容を復号し、本物の example.com に中継して、返ってきた応答をまた暗号化してブラウザに返す。

flowchart LR
    B[ブラウザ] <-->|暗号化| A[攻撃者]
    A <-->|暗号化| S[本物の example.com]
    A -.- N([ここでは平文で読める])

両側とも完璧に暗号化されているのに、真ん中で全部読まれています。これが中間者攻撃です。「暗号化されているか」と「相手が本物か」は、まったく別の問題だということがここで分かります。

必要なのは、鍵を交換する前に「お前は本当に example.com なのか」を確かめる手段です。しかし相手が自分で「私は example.com です」と名乗るだけなら、攻撃者も同じことを言えます。自己申告では解決しないので、第三者に保証してもらう。それが証明書です。

証明書は何を言っているのか

サーバー証明書は、おおまかに言えば「この公開鍵は、example.com というドメイン名の持ち主のものである」という宣言に、CA(認証局)が電子署名したものです。

ブラウザは受け取った証明書について、少なくとも次を確認します。署名が正しいか、名前が一致しているか(アクセスした example.com がその証明書に含まれているか)、有効期限が切れていないか。

ここで当然の疑問が出ます。その CA が本物かどうかは、誰が保証するのか。

信頼の連鎖

答えは「ブラウザと OS が、信頼するルート CA の一覧をあらかじめ持っている」です。

flowchart LR
    R[ルート CA] -->|署名| I[中間 CA] -->|署名| L[example.com の証明書]
    R -.- N([OS / ブラウザに最初から入っている])

サーバーが提示するのは自分の証明書と中間 CA の証明書で、ブラウザはその署名を辿っていき、手元のルート証明書に行き着けば信頼するという判断をします。ルートは署名で保証されているのではなく、「最初から入っている」ことで信頼の出発点になっています。

つまり HTTPS の信用は、最終的にブラウザや OS のベンダーが誰を信じているかに帰着します。ここは技術というより制度の話で、実際、不正な証明書を発行した CA がルートストアから外される、という事件は何度も起きています。

不正な発行を見つけるために、発行された証明書をすべて公開ログに記録する証明書透明性(RFC 9162)という仕組みもあります。信用の出発点が「最初から入っている」ことである以上、後から検証できるようにしておく、という補強です。

証明書が証明していないこと

もう一点、誤解しやすいところがあります。証明書が保証しているのは、そのドメイン名の持ち主であることだけです。

運営会社が実在するかどうかも、そのサイトが安全かどうかも、証明書は何も言っていません。フィッシングサイトでも、自分が用意したドメインの証明書は正規に取得できます。鍵マークが意味するのは「今つないでいる相手は、確かにこのドメイン名の持ち主で、通信は暗号化されている」であって、「このサイトは信頼できる」ではありません。

(より厳格に組織の実在性まで確認する証明書も存在しますが、現在のブラウザの表示上はほとんど区別されなくなっています。)

だからこそ、発行にあたって CA が確認するのはドメインの所有権です。ここで、2 章で扱った「名前」の話と繋がります。

公開する側から見る

ここで視点を反転させます。ここまでは https://example.com を訪れる側の話でした。同じ 3 つを、自分が公開する側から見ると、そのまま作業手順になります。

flowchart LR
    Me[自分]
    Me -->|① 名前を借りる| Rg[レジストラ]
    Rg --> Ry[レジストリ]
    Me -->|② レコードを置く| DNS[権威 DNS サーバー]
    Me <-->|③ 所有権を証明して発行| CA[CA]
    Me -->|④ 証明書を置く| Srv[自分のサーバー]
    V[訪問者] -->|名前を引く| DNS
    V -->|接続して証明書を確認| Srv

右下の 2 本が、前半で見た訪問者側の動きです。自分が①〜④で用意したものを、訪問者がそのまま使うという関係になっています。

① ドメインを取る

まず名前が要ります。「ドメインを買う」と言いますが、正確には買っていません。.com や .dev といった名前空間はそれぞれレジストリが管理していて、私たちが得るのはその名前を使う権利を、期限付きで借りることです。レジストラ(お名前.com や Cloudflare Registrar など)は、その窓口にあたります。

だから毎年の更新料があり、更新を忘れれば他人が取得できる状態に戻ります。所有権ではなく使用権だ、という理解があると、この挙動が腑に落ちます。

② DNS に載せる

名前を手に入れただけでは、誰も住所を答えてくれません。その名前について答える権威サーバーを決め、そこにレコードを置く必要があります。

レジストラで設定する NS レコードが「この名前については、あのサーバーに聞いてくれ」という委任の指定です。委任先は、レジストラが提供する DNS でも、Cloudflare や Route 53 のような別のサービスでも構いません。そこに A や CNAME を置いて、初めて名前が引けるようになります。

③ 証明書を発行してもらう

次は「本物である」ことの証明です。CA に申請すると、あなたが本当にそのドメインの持ち主かを確認されます。確認方法は主に 2 つで、どちらも「持ち主にしかできないこと」をやらせる仕組みです。

HTTP 検証は、指定されたパス(/.well-known/acme-challenge/...)に指定された文字列を置かせ、CA がそれを取りに来ます。そこに置けるのはサーバーを支配している人だけ、という理屈です。

DNS 検証は、_acme-challenge.example.com のような名前に、指定された TXT レコードを置かせます。DNS に書き込めるのは名前の持ち主だけ、という理屈です。こちらはサーバーを公開する前でも使え、*.example.com のようなワイルドカード証明書にも対応できます。

この 2 つを図にすると、こうなります。

sequenceDiagram
    autonumber
    participant Me as 自分(ACME クライアント)
    participant CA as CA(Let's Encrypt など)
    participant W as DNS / Web サーバー
    Me->>CA: example.com の証明書がほしい
    CA-->>Me: ではこのトークンを置いてください
    Me->>W: TXT レコード(DNS 検証)または指定パス(HTTP 検証)に置く
    Me->>CA: 置いたので確認してください
    CA->>W: 本当に置いてあるか、外から取りに行く
    W-->>CA: 置いてある
    CA-->>Me: 所有権を確認。これが証明書です

肝は 5 番です。CA が外から取りに来るので、そのドメインの DNS やサーバーを支配している人にしか応じられません。これが「所有権の証明」の中身です。

この一連のやり取りは ACME(RFC 8555)というプロトコルで自動化されており、Let's Encrypt はこれを無料で提供しています(検証方法の一覧)。証明書の有効期間は短くなる一方で、手作業での更新はもはや現実的ではありません。自動更新の仕組みを持つことが前提です。

④ サーバーに置く

最後に、発行された証明書と秘密鍵を配置します。以降、サーバーは TLS ハンドシェイクのたびにこの証明書を提示し、ブラウザが検証し、暗号化された通信が始まります。冒頭の図の (3) 以降です。

ただし置き場所は、必ずしも自分のサーバーとは限りません。CDN やロードバランサで TLS を終端する構成なら、証明書を持つのは前段です。この選択肢は後の章でまとめて扱います。

ドメインが必要な、もう一つの理由

ここまで来ると、最初に挙げた「名前が要る理由」にもう一つ追加できます。

証明書は、基本的にドメイン名に対して発行されるものだからです。

正確に言えば、IP アドレスの証明書も取得できます(ただし有効期間は約 6 日と短い)。それでも実質的にドメインが要るのは、冒頭で書いた「IP アドレスは変わる」が効いてくるからです。名前を持たないということは、住所が変わるたびに利用者への案内も証明書も作り直すということです。制度上できないのではなく、運用が成立しない。

「覚えやすくするため」だと思っていたものが、実は信用を担保するための土台でもあった、というのが、この線を最後まで引いてみて一番の収穫でした。

何を肩代わりしてもらうか

自分でドメインを用意し、自分で証明書を管理する必要は、必ずしもありません。選択肢は「どこまで他人に任せるか」で整理できます。

構成 ドメイン 証明書 向いている場面
共有ドメインに乗る(*.vercel.app など) 借りる。URL は自分のものにならない 付いてくる 試作、社内向け、検証環境
自分のドメイン + PaaS / CDN 自分のもの 前段が自動で用意する 個人サイト、小規模なサービス
自分のドメイン + Let's Encrypt 自分のもの 自分で ACME を回す 自前のサーバーで運用する場合
自己署名 不要 自分で作る 社内や開発用。公開には使えない

いちばん上と下は、それぞれ理由がはっきりしています。共有ドメインは何も考えずに済む代わりに、サービスを乗り換えれば URL が変わり、それまでのリンクも検索結果も失われます。自己署名は CA の署名がないので、ブラウザが警告を出します。

CDN やプロキシを前段に置く構成では、証明書は前段が持ちます。その場合、前段と自分のサーバーの間をどう保護するかは別の設定であり、ここを暗号化しないままにすると「ブラウザから見れば HTTPS だが、途中は平文」という状態になります。鍵マークが付いているからといって、経路全体が保護されているとは限りません。

このブログの場合

最後に、実際にやったことを書いておきます。このブログ(ykitaa.dev)は次の構成です。

ドメインは Cloudflare Registrar で取得しました。ここで .dev を選んだことには副作用があります。この TLD は HSTS の preload リストに TLD ごと登録済みで、ブラウザは .dev のドメインに対して最初から HTTPS でしか接続しません。http:// と打っても平文のリクエストは飛びません。ドメインの選択が、そのまま HTTPS の強制に直結するわけです。

DNS と証明書は、自分では何もしていません。Cloudflare Workers にサイトを載せ、管理画面でカスタムドメインとして ykitaa.dev を追加しただけです。ドメインとホスティングが同じアカウントにあるので、必要な DNS レコードは自動で作られ、証明書も自動で発行・更新されます。

つまり、この記事で書いた ②DNS と ③証明書の手続きを、まるごと肩代わりしてもらっています。省けたのは、権威 DNS の管理、ACME クライアントの設置と運用、そして更新失敗の監視です。最後のものが地味に大きく、証明書の有効期間が短くなっている現在、自前で回すなら「更新が止まっていることに期限切れまで気づかない」事故をどう防ぐかまで考える必要があります。

実際にどうなっているかは、外から確認できます。証明書は Google Trust Services から出ていて、中間 CA を挟んでルートに繋がっています。

$ openssl s_client -connect ykitaa.dev:443 -servername ykitaa.dev
  Protocol : TLSv1.3
  0 s:/CN=ykitaa.dev
  1 s:/C=US/O=Google Trust Services/CN=WE1
  2 s:/C=US/O=Google Trust Services LLC/CN=GTS Root R4

代わりに預けたものもあります。ドメイン・DNS・証明書・配信がすべて 1 社に集中しているので、移行するときはまとめて動かすことになります。個人ブログであれば、手間が消えることのほうが価値が大きいと判断しました。

おわりに

整理してみると、3 つはこういう関係でした。

ドメインは、レジストリから期限付きで借りている名前です。DNS は、その名前を実際の接続先に変換する仕組みで、権威サーバーが答え、リゾルバがキャッシュします。証明書は、「この鍵はその名前の持ち主のものだ」と第三者が署名したもので、暗号化では解決できない「なりすまし」を防ぎます。

そして 3 つは、名前という一点で繋がっています。名前があるから住所を引ける(DNS)。名前の持ち主であることを証明できるから、信用が成立する(証明書)。冒頭で「ドメインはサイトの住所」と思っていたのは半分で、残り半分は信用の宛先でした。

ブラウザのアドレスバーにある鍵マークが言っているのは、結局この一文だけです。「今つないでいる相手は、確かにこのドメイン名の持ち主です」。それ以上でも以下でもない、と分かったことが、今回いちばんすっきりした点でした。

参考

この記事の記述は、次の一次情報と各サービスの公式ドキュメントに基づいています。手元で確認した証明書チェーンは、本文中に openssl の出力として載せています。

仕様

  • RFC 1034 / RFC 1035 — DNS の基本概念とレコード
  • RFC 2181 — DNS 仕様の明確化(CNAME が他のレコードと共存できない件)
  • RFC 6066 — TLS 拡張。SNI の定義
  • RFC 8446 — TLS 1.3。ServerHello 以降が暗号化される
  • RFC 8555 — ACME。証明書発行の自動化
  • RFC 9162 — Certificate Transparency 2.0
  • RFC 6797 — HTTP Strict Transport Security(HSTS)

各サービスのドキュメント・告知