本記事は『徹底解剖 TLS 1.3』(古城隆、松尾卓幸、宮崎秀樹、須賀葉子)の「Chapter 1 TLSプロトコルの概要」を抜粋したものです。掲載にあたって一部を編集しています。
1.1 TCPクライアント/サーバー
本章では、TLS(Transport Layer Security)のプログラムとプロトコルがどのように実現されているのかを、簡単なクライアント、サーバープログラムを通して見ていきます。
このプログラムは、TCPないしTLS接続のあと、クライアントからサーバーへ、そしてサーバーからクライアントへという1往復のアプリケーションメッセージを送受信し、接続を解除するだけの単純なものですが、この中にTLSプロトコルを構成する主要な要素のほとんどを見ることができます。
TLSプロトコルはすべて、TCPプロトコルによる接続の上に実現されます。図1.1にTCPだけを使ったネットワーク通信のためのクライアント/サーバーの簡単なプログラムの概略を示します。
プログラム上の前処理などを省略すると、TCP通信ではまず、サーバー側でこのサーバーと通信したい相手(クライアント)からの接続要求を受け付けられるように待ち状態に入ります。今回のようなBSDソケットによるプログラムを例とすると、accept()関数を呼び出します。
一方、クライアントは通信したい相手のサーバーに対して接続要求を出します。BSDソケットではconnect()関数の呼び出しです。この要求がサーバーに受け入れられるとTCP接続が成立し(図1.1①)、クライアントとサーバーの間でTCP通信ができるようになります。
以降、この接続を使ってクライアントとサーバーの間でアプリケーションの必要に応じたメッセージ(アプリケーションデータ)の送信・受信を繰り返します(図1.1②)。
最後に、必要なメッセージの送受信が完了したらTCP接続を切断します(図1.1③)。
1.2 TLS層を追加する
それでは、このTCPクライアント/サーバーのプログラムにTLS層の処理を追加しましょう。図1.2はTLSの処理を追加したプログラムの概略です。
TLSはすべての通信をTCPプロトコル上で行うので、TCPプログラムの接続(図1.2①)、切断処理(図1.2⑥)は図1.1とまったく変わりません。TLSのすべてのレコードはTCP接続されたクライアント、サーバー間のTCPレコードの上に載せて転送することになります。
次に、サーバー側プログラムがTLSレイヤーの接続要求を待つためにSSL_accept()関数を呼び出します。これでサーバー側はクライアントからのTLS接続要求の待ち状態となります。一方、クライアント側プログラムでは接続要求のためにSSL_connect()関数を呼び出します(図1.2②、③)。
この呼び出しにより、クライアントとサーバー間での一連のTLSハンドシェイクが実行されます。ハンドシェイクでは、TLS通信で使用する暗号スイート(暗号アルゴリズムの組み合わせ)を合意し、実際にTLSセッションで使用する暗号鍵を合意します。さらに、正当な相手方であることを認証するなど、安全な通信が確保できることを確認します。これらの手順がすべて正常に完了すればTLS接続が確立します。
TLS接続が確立したら、目的とするアプリケーションデータの送受信を行います(図1.2④)。これはプログラム上ではAPIのSSL_read()/SSL_write()関数を呼び出すことで実現されます。アプリケーションが送信したい平文のメッセージはSSL_write()関数によって暗号化され、さらにSSL_read()関数によって復号されて相手方のアプリケーションに平文で引き渡されます。
このとき、TLSプロトコル処理の一環として、受け取ったメッセージが送信元メッセージから改竄されていないこと、つまり「真正性のチェック」も行います。 最後に、アプリケーションデータの送受信が完了したらTLS、TCPの順に切断します(図1.2⑤、⑥)。
1.3 TLSプロトコルを俯瞰する
それでは、このサンプルプログラムの動作をTLSプロトコルレイヤーで見てみましょう。
Wiresharkに代表されるパケットキャプチャツールを使うと、この様子を見ることができます。ここでは、TLSハンドシェイクだけに注目できるようにWiresharkのフィルターに「tls」を指定します(図1.3)。TLS 1.3では、ハンドシェイクの冒頭部分だけが平文で送受信され、残りはすべて暗号化されたやり取りとなるため、通常のパケットキャプチャでは冒頭の「Client Hello」「Server Hello」しか見ることができません。
TLS 1.3のハンドシェイクでは「Client Hello」「Server Hello」のように名付けられた一連のメッセージのやり取りが行われます(図1.4)。
この例のように、クライアントとサーバーが「通信相手に対する予備知識なしに」初めてTLS接続を確立する場合のハンドシェイクはフルハンドシェイクと呼ばれています。フルハンドシェイクは、
- クライアント側から、サポートしている暗号アルゴリズムその他の方式に関する一連の一覧表を候補として示す
- サーバー側がそれに合意し、それ以後の暗号化メッセージのやり取りのための共通鍵を合意する
という流れとなっています。またその際、ピア認証(サーバー認証/クライアント認証)と呼ばれる、「通信の相手方が正当な相手であることの確認」を公開鍵証明書を使って行います。
比較のため、同様の接続をTLS 1.2で実行した場合の通信の流れを図1.5に示します。TLS 1.2ではハンドシェイクの最終部分で暗号化が開始され、ハンドシェイク中は暗号化されません。また、その内容も暗号化方式の合意部分と鍵合意のパラメーターの授受、サーバー認証部分に分かれていて、メッセージの種類も多くなっていました。メッセージの往復回数もTLS 1.3ではほぼ1往復でハンドシェイクを完了できるようになったのに対して、TLS 1.2以前では2往復が必要でした。
表1.1は、TLS 1.3のハンドシェイクにTLS 1.2までのハンドシェイクメッセージを重ね合わせて、TLS 1.3でどのようにメッセージが整理されたか対応を示しています。
表からわかるように、TLS 1.2の「Client Hello」と「Server Hello」では暗号スイートと呼ばれる暗号化方式に関して合意するだけで、実際に暗号化に必要な情報は次の「Server Key Exchange」と「Client Key Exchange」で受け渡しを行うようになっていました。
TLS 1.3では、古い暗号スイートを廃止・整理したおかげで、これらの情報を「Client Hello」と「Server Hello」の中で一度に受け渡すことが可能になりました。そのおかげで、ハンドシェイクの早い段階から暗号化を開始することができるようになるとともに、中間状態を示す「Server Hello Done」「Change Cipher Spec」のようなメッセージは不要になり、ハンドシェイクの終了を示す「Finished」に統一されてハンドシェイク全体が簡潔に整理されました。
このようなハンドシェイクの整理が可能となった背景には、従来、鍵合意の方式として静的なRSA公開鍵を利用する方法とディフィー・ヘルマン系をベースとした方法の2つがあったのですが、TLS 1.3では静的RSA方式のセキュリティ上のリスク(完全前方秘匿性)が指摘されるようになり、RSA公開鍵を利用する方法は廃止されたことが挙げられます。それにより鍵合意方式がディフィー・ヘルマン系のみとなったことで、合意すべき暗号スイートも単純化され、整理が可能になったのです。
1.3.1 Client Helloメッセージ
ここからは、TLS 1.3のキャプチャについてもう少し詳しく見ていくことにしましょう。
TLSプロトコルは、クライアントからサーバーへのClient HelloメッセージによるTLS接続要求で開始されます。このメッセージの中には、接続したいTLSのバージョン(図1.3の場合は「TLSv1.3」としてTLS 1.3が指定されている)、およびクライアントが使用できる暗号スイートの一覧が含まれています。特にTLS 1.3の場合には、残りのハンドシェイク部分から暗号化が可能なように、key_share拡張に鍵合意のためのクライアント側のパラメーター一式も含まれています。
図1.6はClient Helloメッセージの一部を抜き出したものですが、Cipher Suitesにはクライアントがサポートしている暗号スイートのリストが、supported_versions拡張にはサポートしているTLSのバージョン、supported_groups拡張にはサポートしている楕円曲線暗号の曲線の種類やRSAの鍵長などが示されていることがわかります。
1.3.2 Server Helloメッセージ
クライアントからのClient Helloメッセージに対して、サーバーからはServer Helloメッセージによって接続要求の受け付けが行われます。このメッセージの中には、クライアントが提示した暗号スイートの中からサーバー側が選択したスイートや、鍵合意のためのサーバー側のパラメーター一式などが含まれます。
ここまではTLS 1.2以前とそれほど大きく変わらないのですが、TLS 1.3ではそれらに加えてkey_share拡張として鍵合意に必要なクライアント側の情報が格納されているのが特徴です。これに、対応するServer Helloメッセージのkey_share拡張の情報を合わせることで、この段階で鍵合意が成立して、暗号化が可能となります。これにより、以降のハンドシェイクメッセージは合意した共通鍵によってすべて暗号化されます。
1.3.3 Certificate/Certificate Verifyメッセージ
サーバーからの補足情報が送られたあと、CertificateメッセージとCertificate Verifyメッセージにより「サーバーが正当なサーバーである」ことを示すためのサーバー証明書と検証情報が送られます。そしてこれらを受け取ったクライアント側では、自分の持っているCA証明書を使ってこのサーバーが正当なサーバーであることを確認します。
TLSでは、サーバー認証とクライアント認証の双方向の認証方式について規定していますが、サーバー認証は必須、クライアント認証はオプション(省略可)とされています。そのためこの例では、サーバー認証だけを行っています。
1.3.4 Finishedメッセージ
ここまでの処理が終了すると、両者はFinishedメッセージを送信してハンドシェイクの終了を宣言します。これにより以降はTLS接続が安全に確立したことになります。
1.3.5 Application Dataメッセージ
TLS接続が確立したら、クライアント/サーバー間でアプリケーションデータをやり取りするために、Application Dataメッセージを送受信します。
1.3.6 Alertメッセージ
最後に、TLS接続の終了を示すために、両者はAlert種別が「Close Notify」となるAlertメッセージを送信します。「Alert」というと異常状態を示すように見えますが、種別が「Close Notify」のものはTLSの正常な終了を示します。
まとめると、TLS 1.3ではそれ以前と比較して以下の点が変更されています。
- ハンドシェイクメッセージが整理され、1往復で終了できるようになった
- Cleint Hello/Server Helloメッセージのあとのハンドシェイクメッセージも暗号化されるようになった
- 鍵合意方式が(EC)DHEのみに整理されたことにより、上記が可能になった