既に良い記事がいっぱいあって自分で新規に書けることはないのですが、自分の知識の整理のために調べたことをメモ。
誤っている部分などあればご指摘ください。
参考
- 食べる!SSL! ―HTTPS環境構築から始めるSSL入門
- Lesson3:実際の通信はどうなってる?やりとりの詳細を見てみよう
- SSL/TLSのシーケンスまとめ
- WiresharkでSSL通信の中身を覗いてみる
- OpenSSLの脆弱性(CVE-2014-3511)でTLSプロトコルの基礎を学ぶ
- SSLとは
環境
- Amazon Linux AMI 2016.03
- OpenSSL 1.0.1k-fips 8 Jan 2015
また、事前にHTTPS環境(オレオレ証明書)は作成されているものとします。作成方法などは以前記載しました。
準備する
Webサーバーがインストールされたサーバー側でtcpdumpを仕掛けるのでインスールします。
$sudo yum install tcpdump -y
また、httpdでSSLSessionを 実験のために 使わないようにします。(SSL Sessionが有効でかつタイムアウト以内の再アクセスされるとhttps通信の一部を簡略化されるため)
以下のようにSSLSessionCacheをnoneとします。
#SSLSessionCache shmcb:/run/httpd/sslcache(512000)
SSLSessionCache none
また、今回は秘密鍵を登録した時にhttpsを復号化したいので 実験のために DH鍵交換をしない設定にします。
SSLCipherSuite kRSA
SSLSessionとDH鍵交換の話は以下が参考になりました。
設定後、httpdを再起動しておきます。
tcpdumpコマンドでキャプチャしてみる
tcpdump
コマンドを使って通信時のパケットを取得し、wiresharkで確認してみます。
まず、サーバーで以下のコマンドを実行して、キャプチャを開始します。
$sudo tcpdump -Z root -i eth0 -s 0 -w /tmp/dump_https.pcap port 443
その後、ブラウザなどでhttps化したサイトにアクセスします。
フォアグラウンドで実行しているtcpdumpコマンドをCTRL+Cで停止させ、その後scpコマンドでローカルPCに/tmp/dump_https.pcap
ファイルを持ってきます。
あとはローカルPCのwiresharkでこのパケットキャプチャを見ていきます。
キャプチャした内容を見てみる
順番に見ていきます。
SSLでは実際に暗号化通信を行う前にSSLハンドシェイクを行う必要があります。
ざっくりだと以下をやるようです。
- Step1 使用するアルゴリズムの合意
- Step2 サーバーの認証
- Step3 データ転送で使用する鍵の確立
- Step4 ハンドシェイクが正しく行われたことの確認
Wiresharkで細かく通信の流れを見ると以下のようになっていました。
(Client HelloなどはTLSプロトコルのハンドシェイクタイプのメッセージタイプ)
- Client->Server:Client Hello
- Sever->Client:Server Hello,Certificate,Sever Hello Done
- Client->Sever:Client Key Exchange
- Clinet->Server:Change Cipher Spec
- Client->Server:Encrypted Handshake Message
- Server-Client:Change Cipher Spec,Encrypted Handshake Message
- (以降ハンドシェイクが確立しているので暗号化通信できる)
Step1.使用するアルゴリズムの合意
まず最初にTLSプロトコルでハンドシェイクタイプのメッセージタイプが「Client Hello」を送信します。ここで以下をクライアントから送信します。
- SSL/TLSのバージョン
- 利用できる暗号化スイート(サーバー認証アルゴリズム、鍵交換アルゴリズム、共通鍵暗号方式アルゴリズム、MACアルゴリズム)
- クライアント側で生成した乱数
実際に見てみます。
上記の部分でフォーカスが当たっている部分で「Version: TLS1.2」ということでクライアントが対応できる最大のSSL/TLSバージョンがTLS1.2であることを送信しています。
「Cipher Suites」という所を確認すると上記のようにずらずらっとどの暗号化スイートが対応しているか送信していることが分かります。
利用する乱数も「Random」という記載で送っています。
上記に対してサーバーではメッセージタイプが「Server Hello」を返却して回答を行います。
返却内容より以下が分かります。
- 「Version: TLS 1.2」と記載され、SSL/TLSバージョンはTLS1.2を使う
- 「Random」を返却し、サーバー側で生成した乱数もクライアントに送信
- 「Cipher Suite:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA」を暗号化スイートとして利用
クライアントとサーバー側で生成した乱数は後で利用します。
Step2 サーバーの認証
サーバーが「Server Hello」を返却するタイミングで合わせてメッセージタイプが「Cerfiticate」ということでサーバーの証明書情報をクライアントに返却します。クライアント側ではこの証明書を持って通信を行っているサーバーが正しいかを確認します。
また、証明書情報に公開鍵情報も入っています。
Step3 データ転送で使用する鍵の確立
クライアントとサーバー側で同じ鍵生成関数を使って、あるランダムな文字列から暗号化鍵とMAC鍵を作成します。
同じ入力と同じ関数を使えば同じ値が生成されるはずということで鍵自体は通信経路には載せません。
上記の「あるランダムな文字列」はクライアントで作成するのですが、これをサーバー側に送信する必要があります。
そこで先ほど取得したサーバー証明書から公開鍵を取得し、これを使って「あるランダムな文字列」を暗号化し、サーバーに送信します。サーバーの公開鍵を使って暗号化されたものはサーバーのみが保持している秘密鍵でしか復号化できないので、安全に情報の送信ができます。
ただ、「あるランダムな文字列」のみを利用して鍵を生成すると異なるセッションでも同じ鍵が生成されてしまうため、Step1でクライアント、サーバーより送信した乱数も合わせて鍵生成時に利用します。つまり、以下3つを入力値として暗号鍵とMAC鍵を作成します。
- クライアントで生成した「あるランダムな文字列」(公開鍵で暗号化して送信)
- クライアント側で生成した乱数(Client Helloで送信)
- サーバー側で生成した乱数(Server Helloで送信)
前置きが長くなってしまいましたが、上記のように鍵を生成するために「あるランダムな文字列」を送っているのが「Client Key Exchange」です。
上記のように「Encrypted PreMaster Secret」の部分が公開鍵で暗号化された「あるランダムな文字列」となっているのかと思います。
Step4 ハンドシェイクが正しく行われたことの確認
最後にStep1-3が正しく行われたことを確認して終了となります。
そのため、Step1-3で送信したメッセージについて作成した暗号鍵・MAC鍵を利用してサーバー、クライアントとお互いに送信して確認します。こちらの処理は「Encrypted Handshake Message」として確認しているのかと思います。(暗号化されているので分かりませんが。。。)
実際のデータ通信
以降の通信ではSSLハンドシェイクが終わり、暗号化されて何らかの通信が行われていることがわかります。(Application Dataという部分)
上記についてはサーバー側の秘密鍵を使って復号化できます。
Wiresharkを開き、Preference->Protocols->SSL->RSA Key List->Editを選択し、以下を指定します。
- IP Address->宛先のIPアドレス
- Port->443
- Protocol->http
- Key File->サーバーの秘密鍵
これでOKを指定すると先ほどは暗号化されていた内容が確認できます。