SSLハンドシェイクにおいて基本となるのは、通信処理の最初に実施する『ClientHello(クライアントハロー)』と『ServerHello(サーバハロー)』です。
今回は、このSSLハンドシェイクの挙動をJavaのDebugログで出力して、ClientHelloとServerHelloの動きを確認していこうと思います。
環境情報
- OS:Windows10
- Java:Java1.8.0_281
SSLハンドシェイクの実験プログラム
SSLハンドシェイクを確認するために、Javaの実験プログラムを作成して、実行結果をログ出力してみます。
実験プログラムは、ポータルサイトである「Excite」にアクセスしているのみです。
import java.io.FileOutputStream; import java.io.PrintStream; import java.net.HttpURLConnection; import java.net.URL; public class HTTPReq { public static void main(String[] args){ try { FileOutputStream fos = new FileOutputStream("out.txt"); PrintStream ps = new PrintStream(fos); System.setOut(ps); System.out.print("start: main\r\n"); // 1回目コネクション URL url = new URL("https://www.excite.co.jp/"); HttpURLConnection con = null; con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("POST"); con.connect(); int statusCode = con.getResponseCode(); System.out.print("statusCode=" + Integer.toString(statusCode) + "\r\n"); } catch (Exception e) { System.out.print("例外が発生!"); e.printStackTrace(); } finally { } System.out.print("end: main\r\n"); } }
Javaの実行バッチは以下になります。
@echo on set CLASSPATH=. java -Djavax.net.debug=all HTTPReq >> result.txt 2>&1
Javaのデバッグログは、「debug=all」オプションを付けることで出力されます。
-Djavax.net.debug=all
Javaの標準出力は「out.txt」に出力し、デバッグログは「result.txt」に出力しています。
ClientHello
ClientHelloからServerHelloの流れは、SSLハンドシェイクの開始部分となります。
開始部分であっても、Javaのデバッグログには大量の情報が出力されます。
ClientHelloで、クライアントからサーバに対して送信するデータに以下があります。
- SSL/TLSのバージョン
- CipherSuiteのリスト
- セッションID
- ランダム値
- 圧縮アルゴリズム
- TLS拡張情報
実際に出力されるのは以下のような情報となります。
"ClientHello": { "client version" : "TLSv1.2", "random" : "XXX", "session id" : "", "cipher suites" : "[TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xC02C), ・・・]", "compression methods" : "00", "extensions" : [ "server_name (0)": { type=host_name (0), value=www.excite.co.jp }, "supported_groups (10)": { "versions": [secp256r1, ・・・] }, "ec_point_formats (11)": { "formats": [uncompressed] }, "signature_algorithms (13)": { "signature schemes": [ecdsa_secp256r1_sha256, ・・・] }, "signature_algorithms_cert (50)": { "signature schemes": [ecdsa_secp256r1_sha256, ・・・] }, "extended_master_secret (23)": { }, "supported_versions (43)": { "versions": [TLSv1.2, TLSv1.1, TLSv1] } ] }
SSL/TLSのバージョンのバージョンは、クライアントで使用できるTLSバージョンをサーバに通知しています。
TLS1、1.1、1.2などがあります。
CipherSuiteのリストは、クライアントで使用できるCipherSuiteをリストとしてサーバに通知しています。
このログ以前にも様々な情報が出力されるのですが、クライアントで使用できるCipherSuiteのリストを算出し、サーバに通知します。
セッションIDはクライアント側で保持しているセッションのIDです。
複数の通信をおこなう場合、クライアントはセッションで保持している情報を使用してSSLハンドシェイクをおこないます。
セッションIDを使用した場合、SSLハンドシェイクの手順をスキップすることができます。
ServerHello
ClientHelloに対するサーバ側のレスポンスがServerHelloになります。
ServerHelloで、サーバからクライアントに対して送信するデータには以下の種類があります。
- 合意したSSL/TLSのバージョン
- ランダム値
- セッションID
- 合意したCipherSuite
- 圧縮アルゴリズム
- TLS拡張情報
"ServerHello": { "server version" : "TLSv1.2", "random" : "XXX", "session id" : "YYY", "cipher suite" : "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xC02F)", "compression methods" : "00", "extensions" : [ "renegotiation_info (65,281)": { "renegotiated connection": [] }, "server_name (0)": { }, "ec_point_formats (11)": { "formats": [uncompressed, ansiX962_compressed_prime, ansiX962_compressed_char2] }, "extended_master_secret (23)": { } ] }
SSL/TLSのバージョンのバージョンは、クライアント側から提案したTLSのバージョンに対するサーバ側の返却になります。
このバージョンが一致しなければ、SSLハンドシェイクは成立しません。
サーバ側はTLS1.2で要求しているのにサーバ側がTLS1.0のみを許可している、といった場合がそのパターンになります。
合意したCipherSuiteは、クライアント側から提案したCipherSuiteのリストを受信したサーバ側が、そのリストから使用するCipherSuiteを決定します。
最終的に、ServerHelloDoneが出力されれば、ClientHelloからServerHelloの一連の動きは成功となります。
javax.net.ssl|FINE|01|main|2021-04-24 23:11:34.992 JST|ServerHelloDone.java:151|Consuming ServerHelloDone handshake message ( )