STARTTLS のひみつ
山本和彦
2026年3月6日
この記事は、用語が古いTLS のひみつ(有害情報対策ポータルサイト、2008年3月)の改訂版である。
SSL(Secure Socket Layer)は、あるアプリケーション・プロトコルの通信を保護するために、平文のポートとは別のポートを利用する。IETFは、SSLの名称を変え、TLS(Transport Layer Security) として標準化した。別ポートを使う TLS は、最近では暗黙の(implicit) TLS と呼ばれているが、この記事では単に TLS と呼ぶ。
標準化の際 IETFは、これまでの平文のポートでもTLSを利用できるように、STARTTLSを考案した。アプリケーションが STARTTLS をサポートすると、たとえば、POP は 110 番ポートのみで暗号路を構築できる。
TLSはラッパー(トンネル)として実装できるが、STARTTLSはラッパーとして実装できないと勘違いしているエンジニアが多い。この記事では、(少なくともクライアント側では) STARTTLS がラッパーとして実装でき、アプリケーション・プログラムの実装を変更しなくてもSTARTTLS が利用できることを示す。
TLS
前述のように、あるアプリケーション・プロトコルを TLS を用いて保護するときは、別途ポートを用意しなければならない。たとえば、HTTP(80) を TLS で守るために、HTTPS (443) が定義されている。
このポートの分離のおかげで、HTTPS を見張るプログラムは通信がはじめから暗号文であると決め打ちできる。同様に、(当たり前ではあるが) HTTPを見張るプログラムは通信がはじめから平文であると決め打ちできる。このような条件下では、TLSをラッパーとして実装するというアイディアを思いつくのは容易い。
ここでいうラッパーとは、平文の通信と TLSの暗号通信を橋渡しするプログラムのことである。ラッパーのおかげで、既存のプログラムを変更することなく、TLSで通信を保護できる。
たとえば、HTTP サーバについて考えよう。管理者は、HTTP サーバと TLSラッパーが通信するためのローカルホスト上のポート番号を任意に選ぶ。ここでは、12345番ポートを選んだとする。
TLS ラッパーは、HTTPS のポートである 443番ポートを見張る。クライアント(ブラウザ)が接続して来た場合、もちろん通信がTLS で暗号化されている。(暗号通信の細かいやりとりは、今回は説明しない。)TLS ラッパーはこれを復号化し、ローカルホストの 12345 番ポートへ平文のデータを転送する。
HTTP サーバは、ローカルホストの 12345番ポートを見張っている。ここには、TLSラッパーが平文で接続してくるため、通常の HTTPとして処理できる。つまり、通常は 80 番ポートを見張る HTTPサーバを、12345 番ポートを見張るように設定するだけで、TLSが利用できる訳である。
HTTP と HTTPS の両方のサービスを提供するには、デフォルトの 80番を利用する HTTP サーバに加えて、12345 番ポートを利用するよう設定したHTTP サーバの両方を設定すればよい。
これまでサーバ側の話をしてきたが、クライアント側でも同様である。
以下に、TLS ラッパーの優れた実装である stunnel を使って、POPのクライアント側を設定する例を示す。
% cat pop3s-client
client=yes
pid=
verify=0
foreground=yes
debug=debug
[12345]
accept=127.0.0.1:12345
connect=pop.example.com:995
% stunnel pop3s-client
設定ファイル pop3s-client では、ローカルの 12345番ポートに対する平文の接続を TLS で暗号化し、pop.example.com の 995番ポート、すなわち POP3S に転送するよう定義してある。
以下のように、ローカルホストの 12345番ポートへ接続してみると、うまく接続が転送されていることが分る。
% telnet 127.0.0.1 12345
S: +OK IIJ POP3 Server (pop.example.com) starting. <14663.1200725428@pop.example.com>
なお、"S:" はサーバの暗号化された出力であることを意味する。
STARTTLS
TLSではポート番号を分離しているおかげで、平文通信と暗号通信を区別できた。STARTTLSでは、通信は平文で始まり、クライアントの合図によって暗号通信へと遷移する。この合図は、アプリケーション・プロトコルごとによって異なるが、総称してSTARTTLS と呼ばれる。
以下に POP で STARTTLS を利用する際のやりとりを示す。
% telnet pop.example.com 110
S| +OK IIJ POP3 Server (pop.example.com) starting. <14663.1200725428@pop.example.com>
C| CAPA
S| +OK Capability list follows
S| STLS
S| .
C| STLS
S| +OK Begin STARTTLS negotiation
C: CAPA
S: +OK Capability list follows
S: .
"S" と "C"は、それぞれサーバとクライアントの出力であることを意味する。"|" は平文、":" は暗号文であることを示す。
まず、POP クライアントは、CAPAコマンドによりサーバの実装機能の一覧を取る。次の行で、POP 上での STARTTLS である STLSをサポートしていると、サーバが応答しているのが分る。クライアントは、STLSコマンドにより暗号通信の開始を告げ、サーバが "+OK"を返すことで、両端で STARTTLS の処理か開始される。
暗号通信が利用可能になると、再びクライアントは CAPAコマンドを実行する。すると、サーバは何も返さないので、STLSコマンドはサポートされていないと分る。(これにより、STLSコマンドが何回も発行されループすることを防止する。)
この例だけ見ると、STARTTLS を利用するには POPのサーバとクライアント両方を改造し、STLSコマンドを実装しなければならないと勘違いするのも無理はない。そして、サーバやクライアントが、低レベルなTLS/STARTTLS のライブラリを呼べない実装である場合、「STARTTLSはサポートできない」と思う。そして、「やっぱり TLSを使おう」ということになってしまうのだ。
しかしながら、STARTTLS は、TLS と同様にラッパーとして実現できる。
ラッパーとしての STARTTLS
論より証拠ということで、ラッパーである stunnel を用いて、STARTTLSを利用してみよう。
% cat pop3-tls-client
client=yes
pid=
verify=0
foreground=yes
debug=debug
[10985]
accept=127.0.0.1:12345
connect=pop.example.com:110
protocol=pop3
% stunnel pop3-tls-clinet
上記の例と変わったのは最後の2行である。
- "connect" のポートが、POP3S の 995 番から、POP の 110 番へ変更された
- "protocol=pop3" が加わった
ここでローカルホストの 12345 番ポートへ接続すると、 pop.example.com の110 番ポートに暗号路が確立された状態で接続される。
% telnet 127.0.0.1 12345
S: +OK IIJ POP3 Server (pop.example.com) starting. <14663.1200725428@pop.example.com>
C: CAPA
S: +OK Capability list follows
S: .
証拠としては弱いが、CAPA コマンドに STLSが返って来てないので、STARTTLS で暗号化されていると分る。心配なら、tcpdumpなどで中身が覗き込めるか検査してみてもいいだろう。
先ほどの通信例とこの通信例を見比べてほしい。前者から "|"の部分を削れば、ただし、サーバの最初の挨拶だけは削らずに "|" から":" へ置き換えれば、後者になる。この意味するところが分るだろうか?
それはつまり、TLS ラッパーに、我々が今削った部分を喋らせれば、STARTTLSラッパーになるということだ。
STARTTLS の設計のキモは、状態の遷移ではなく、セッションのやり直しである。STARTTLSによって暗号通信が開始された後は、アプリケーション・プロトコルが再び最初から始まるのだ。
上記の例では、POP クライアントは最初の CAPAコマンドからやり直していることが分るだろう。(POP が分りにくければ、SMTPで考えてみるとよい。SMTP であれば、EHLO コマンドからやり直す。)
最初からやり直すということは、すなわち、既存のサーバやクライアントを改良せずとも、そのまま利用できるということだ。
クライアント側の TLS ラッパーを STARTTLSラッパーに改造するには、以下の作業を加えればよい。
- サーバの挨拶を保存する
- STARTTLS コマンドをサーバへ送る
- サーバから OK が返されたら STARTTLS の暗号通信を開始する
- 保存したサーバの挨拶をクライアントへ書き出す
STARTTLSコマンドは、アプリケーション・プロトコルごとに異なる。だからこそ、"stunnel"の設定ファイルには "protocol"を指定して、どのアプリケーション・プロトコルかを指示する必要がある。
サーバ側の TLS ラッパーを STARTTLSラッパーに改造するには、以下の点においてクライアント側よりも複雑である。
- STARTTLS が使われる場合と使われない場合の切り分け
- 実装機能一覧(capability)の処理
以下に、実装機能一覧を処理する例を示す。
- STARTTLS が開始されてない平文通信時の実装機能一覧としては、STARTTLS をサポートし、認証は使い捨てパスワードを要求する
- STARTTLS が開始された後の暗号通信の実装機能一覧としては、STARTTLS をサポートしておらず、認証は生パスワードも許可する
なお、stunnel バージョン 4.20 では、これらを実装できてない。
蛇足
重要なプロトコルに対する TLS ポートの割当状況を以下に示す。
| プロトコル | 通常ポート | TLSポート |
|---|---|---|
| HTTP | 80 | 443 |
| POP | 110 | 995 |
| IMAP | 143 | 993 |
| SMTP | 25 | |
| Submission | 587 | 465 |