日々是精進。(はてな館)

日々ネットで調べたり、付箋に書き留めたものをアップしています。子育てで中断しながらも、年に数回投稿しています。皆様の恩恵に感謝しつつ。

日々の記録を残しています。皆様の恩恵に感謝しつつ。

社内勉強会でゆるくSSL/TLSのお話をしてみました。

はてな記事の5つめは、7月末に職場でお話した、社内のインフラ勉強会の内容を少し調整してみたものとなります。 ゆるく語っていましたが、だいたいこんな流れで話しましたよ、というところもなにか参考になりましたら幸いです。

はじめに

きっかけは

Chrome68から、標準のブラウジング用は http:// ではなく、https:// となりました。

暗号化されていないサイトにアクセスすると、こんなふうに表示されちゃいます。 (なお、こちらはローカル開発環境でrails s でrailsアプリケーションを起動した例です)

f:id:akiko-pusu:20180803020353p:plain
rails sでhttp://localhost:3000/ にアクセスしてみました。

  • 従来はhttpでのアクセスは、警告はありませんでした
    • https化されている場合でも、自己証明書の場合は警告が出ていました(こちらについては、現在も同じです)

ちょうどこのChrome68からの変更が7月からでしたので、SSL/TLSのお話をしてみようと思った次第です。

インフラ勉強会でお話しを伺ったこと

実際のところ、通信の仕組みは雰囲気で理解していて、殊に暗号化については、あまり突っ込んで考えたり学んだりしたことがありませんでした。 質問されたらお茶を濁すしかなく。

ですが、今年の春から時折お話しを聞いているインフラ勉強会で、SSL/TLSのお話しを伺う機会がありました。 相変わらずわたしは暗号化や数学については壊滅的なので、難しいことは言えないのですが、スピーカーの halu834 さまのお話と資料がとても面白く、聴きながらメモをとるといったことをしてみました。

暗号化にいたるまでのやりとりが面白く、細かいプロトコルの手順は理解しきってはいないけれどパケットキャプチャをしながらであれば説明できるかも?ということで、流れを考えてみた次第です。

以下、やってみた内容を代替の流れに沿って書いてみます。

あなたと内緒で話したい 〜 SSL/TLS について簡単に

テーマは、httpsなサイトと暗号化された通信でやりとりするまでになります。 まずはじめに、esaにざっくり書いた文章と、ホワイトボードでの簡単な図を交えてお話ししました。

SSL/TLS

TLSOSI 参照モデルで言うところのトランスポート層からプレゼンテーション層に当たります。

  • 厳密に「この層!」ってのは難しいのです
  • なぜならOSIのレイヤーはモデルであって、実際のインターネットの仕組みとちょっと離れているから

守れるものは何?

TLS は4〜7層での通信のためのセキュリティを提供します。 逆に言えばトランスポート層より下の層、ネットワーク層から下のセキュリティに関しては TLS の担当外になります。

  • 通信が「どことどこがやりとりしているか」はわかります
  • このあたりは、パケットキャプチャで実際に確認してみます

かいつまんでの暗号化までの道のり

  • TCP 3 ウェイハンドシェイクを行い、 TCP コネクションを確立する
  • これは通信の基本
  • わたしの回の前の週に、同僚の方のお話しででてきましたので、割愛
  • TCPペイロードにデータを乗せて通信できるようになります!
  • この段階では道が繋がったようなもの
  • TCP コネクション上で TLS フルハンドシェイクを行い、 TLS コネクションを確立する

またまた出てきた「ハンドシェイク」。 ほんとうに行われているのか、TCPの3ウェイハンドシェイクとどう違うのかというのを、実際にWiresharkを使ったパケットキャプチャでみて見ることにしました。

みんな大好きWiresharkで!

確認対象はこのブログ

さて、キャプチャする通信相手ですが、このブログに設定してみます。 今回は、TLS化する前とした後の違いを眺めるため、http でアクセスした通信と、httpsでアクセスした通信があればわかりやすそう。

ちょうどこのブログも httpでアクセスするとhttpsにリダイレクトされるようになっているので、素材としては良さそうです。 Wiresharkでのフィルタだと、手っ取り早くアドレスでのフィルタが良いので、まずIPを調べます。

digコマンドの結果

digで確認すると、IPが2つ返ってくることが分かります。 返ってきたIPの1つを、dig -x で逆引きして見ると、AWSインスタンスが返ってきます。

~ $ dig http://daily-postit.hatenablog.com

; <<>> DiG 9.10.6 <<>> http://daily-postit.hatenablog.com
;; global options: +cmd

-- [ 中略 ] --

;; ANSWER SECTION:
http://daily-postit.hatenablog.com. 47 IN A 13.115.18.61
http://daily-postit.hatenablog.com. 47 IN A 13.230.115.161

-- [ 以下略 ] --

~ $ dig -x 13.230.115.161

; <<>> DiG 9.10.6 <<>> -x 13.230.115.161
;; global options: +cmd

-- [ 中略 ] --

;; ANSWER SECTION:
161.115.230.13.in-addr.arpa. 150 IN PTR ec2-13-230-115-161.ap-northeast-1.compute.amazonaws.com.

-- [ 以下略 ] --

Wiresharkのフィルタ

IPを調べたところ、ロードバランサで負荷分散されているようなので、リクエストによってはどちらにつながるかわかりません。 ということで、両方のIPをフィルタに指定してみます。

  • (ip.addr == 13.115.18.61) || (ip.addr == 13.230.115.161)
    • ロードバランサのIP(今回どれにあたるかわからないので or で指定)
    • TCPおよびSSL/TLS通信を行うので、プロトコルは指定しないでおく

curlでリクエストしてみます!

では早速、リクエストを送信しつつ、キャプチャしてみることにします。 ブラウザだと基本のHTML以外に、いろいろと埋め込まれた画像やJSも呼び出されてしまうので、余計な通信は発生させないように、curlでアクセスさせてみます。

また、暗号化の有無でどう通信が変わるかもみてみたいので、まずはhttpでアクセスさせ、リダイレクト -> httpsになるようにしてみます。

  • curlコマンドを利用
    • -v オプションで httpプロトコルレベルでのヘッダのやり取りを表示
    • -L オプションでリダイレクトに対応

まず、curlでの結果は以下の通り。

  • 一度httpでのGETを実行したあと、サーバから 301 Moved Permanently が返る
  • Locationヘッダに転送先が指定されてくる
  • 改めて https://daily-postit.hatenablog.com/ に接続に行く (Port: 443)
  • サーバの証明書を取得
  • そのあとTLS handshakeが開始される
  • 暗号化されたレスポンスを受け取る

以下、curl側の出力と、Wiresharkのキャプチャをのせてみます。

curlの出力結果

$ curl -L -v -o result.html http://daily-postit.hatenablog.com
* Rebuilt URL to: http://daily-postit.hatenablog.com/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 13.115.18.61...
* TCP_NODELAY set
* Connected to daily-postit.hatenablog.com (13.115.18.61) port 80 (#0)
> GET / HTTP/1.1
> Host: daily-postit.hatenablog.com
> User-Agent: curl/7.54.0
> Accept: */*
> 
< HTTP/1.1 301 Moved Permanently
< Server: nginx
< Date: Sun, 05 Aug 2018 06:46:18 GMT
< Content-Type: text/html; charset=utf-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Cache-Control: private
< Location: https://daily-postit.hatenablog.com/
< Vary: User-Agent, X-Forwarded-Host, X-Device-Type
< X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
< X-Revision: f51b63b716e1031ba03285b94fe98188
< X-XSS-Protection: 1
< X-Runtime: 0.037435
< X-Varnish: 603807048
< Age: 0
< Via: 1.1 varnish-v4
< X-Cache: MISS
< 
* Ignoring the response-body
{ [215 bytes data]
100   204    0   204    0     0   1182      0 --:--:-- --:--:-- --:--:--  1186
* Connection #0 to host daily-postit.hatenablog.com left intact
* Issue another request to this URL: 'https://daily-postit.hatenablog.com/'
*   Trying 13.115.18.61...
* TCP_NODELAY set
* Connected to daily-postit.hatenablog.com (13.115.18.61) port 443 (#1)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* TLSv1.2 (IN), TLS handshake, Server hello (2):
{ [108 bytes data]
* TLSv1.2 (IN), TLS handshake, Certificate (11):
{ [5510 bytes data]
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
{ [413 bytes data]
* TLSv1.2 (IN), TLS handshake, Server finished (14):
{ [4 bytes data]
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
} [150 bytes data]
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
} [1 bytes data]
* TLSv1.2 (OUT), TLS handshake, Finished (20):
} [16 bytes data]
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
{ [1 bytes data]
* TLSv1.2 (IN), TLS handshake, Finished (20):
{ [16 bytes data]
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: OU=Domain Control Validated; OU=PositiveSSL Multi-Domain; CN=hatenablog.com
*  start date: Sep 25 00:00:00 2017 GMT
*  expire date: Sep 25 23:59:59 2018 GMT
*  subjectAltName: host "daily-postit.hatenablog.com" matched cert's "*.hatenablog.com"
*  issuer: C=GB; ST=Greater Manchester; L=Salford; O=COMODO CA Limited; CN=COMODO RSA Domain Validation Secure Server CA
*  SSL certificate verify ok.
> GET / HTTP/1.1
> Host: daily-postit.hatenablog.com
> User-Agent: curl/7.54.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sun, 05 Aug 2018 06:46:18 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 73636
< Connection: keep-alive
< Vary: Accept-Encoding
< Vary: User-Agent, X-Forwarded-Host, X-Device-Type
< Access-Control-Allow-Origin: *
< Content-Security-Policy-Report-Only: block-all-mixed-content; report-uri https://blog.hatena.ne.jp/api/csp_report
< P3P: CP="OTI CUR OUR BUS STA"
< X-Cache-Only-Varnish: 1
< X-Content-Type-Options: nosniff
< X-Dispatch: Hatena::Epic::Web::Blogs::Index#index
< X-Frame-Options: DENY
< X-Page-Cache: hit
< X-Revision: f51b63b716e1031ba03285b94fe98188
< X-XSS-Protection: 1
< X-Runtime: 0.033699
< X-Varnish: 613392844 619775732
< Age: 36
< Via: 1.1 varnish-v4
< X-Cache: HIT
< Cache-Control: private
< Accept-Ranges: bytes
< 
{ [15613 bytes data]
100 73636  100 73636    0     0   216k      0 --:--:-- --:--:-- --:--:--  216k
* Connection #1 to host daily-postit.hatenablog.com left intact

TCP 3wayハンドシェイクの部分

TCPの通信が開いてから、httpでサーバにリクエストを送信。 すると、サーバから301 でhttpsにリダイレクトが指定されます。

f:id:akiko-pusu:20180805204400p:plain

SSL (TLS) ハンドシェイクの部分

リダイレクトされて、まずはおなじくTCPの通信を開始したあと、暗号化通信のための手続きが始まります。 最初は平文(暗号化されていない状態)で、クライアント側で「このバージョンでこの方法で暗号化通信したいんです」といった情報を送ります。 (Client Helloにあたる部分です)

f:id:akiko-pusu:20180805204703p:plain

SSLハンドシェイク中

サーバ側がClient Helloの中身をチェック、今度はサーバ側から「OKです、この方式でいこう」といったやりとりをします。 最終的には、お互い同じ鍵(共通鍵)を使って暗号化しますが、そもそもまだ通信は「平文」で暗号化されていません。 その上に共通鍵を送受信すると、簡単に鍵を盗まれてしまいます。 そこで、お互いの公開鍵をやりとりし、お互いに相手の公開鍵を使って共通鍵の「タネ」を暗号化して取り交わします。

最終的に、自分の暗号化のタネと、相手の暗号化のタネをつかって、自分の手元で共通鍵を作成します。

f:id:akiko-pusu:20180805205056p:plain

暗号化したあと

Wiresharkで見ると、暗号化されたあとは、httpの時と違い、アプリケーション層のプロトコルが表示されません。 TLSv1.2とは表示されるものの、中身自体が暗号化されているので、Wiresharkでも中身がよくわからない状態になっています。 (ポート443でやりとりということは分かるので、多分https... という想像はつきますが)

f:id:akiko-pusu:20180805205630p:plain

逆に、curlやブラウザに届いた段階で復号化されるので、curlの出力結果やブラウザでは、その内容が人が見てもわかるようになっています。

暗号スイート情報

暗号化の技術、方式はいろんなものがあります。 いくつかの方法をまとめた、「暗号化手法のおすすめセット」「暗号スイート」と言います。

ここでは、AWSのロードバランサを使って負荷分散を行い、ロードバランサにhttps設定、証明書の紐付けをしている例を見てみます。

f:id:akiko-pusu:20180805211224p:plain

TLS1.0 がダメ & SHAR-1 (SHA) が非推奨な理由を簡単に

さて、単純に暗号化できれば良い...といったわけではなく、やはりバージョンが上がるからにはなにかしらの理由があります。 あまり詳しく突っ込めませんが、簡単に述べるとこのような感じ。

SHA-1

  • Secure Hash Algorithm の略。MD5 を元に設計され、ハッシュ値の長さは 160bit。
  • 40 桁の 16 進数で表されます。
  • 理論的な脆弱性が指摘されているため SHA-2 へ移行することが推奨されています。
  • 近年、証明書の署名に使われるハッシュ関数SHA-1 から SHA-2 に移行されようとしていることからもわかります。

SHA-2

  • SHA-1 をより安全になるように改良したものです。
  • SHA-2 の中には SHA-224、SHA-256、SHA-384、HA-512、SHA-512 / 224、SHA-512 / 256 という 6 種類のバリエーションが存在しておりそれぞれハッシュ値の長さが違います。

TLSのバージョンについて

TLS 1.0/1.0にはPOODLEといった脆弱性があり、一定の条件下であれば暗号解読が可能であると言われています。 そのため、今すぐ攻撃が成功する可能性は低いですが利用は非推奨とされています。その中で、PCI DSSとよばれるクレジットカード業界のグローバルセキュリティ基準において、2018年6月30日をもってTLS 1.0の利用を停止するように2015年に基準が改定されました。 このタイミングでインターネット業界においてもTLS 1.0の廃止の動きが広がりつつあるのは、このPCI DSSに依拠したものになります。

あえてTLS1.0で叩いてみる

さて、上記でcurlで接続した場合は、出力から TLS1.2が利用されたのがわかります。

SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384

ただしく通信ができたようですが、では、脆弱性のあるバージョンでの接続はどうなるでしょうか?

ということで、curlのオプションで、あえてTLS1.0を利用して接続を試してみます。 ここでは、github.comをTLS1.0で叩いてみますが....

$ curl -L --tlsv1.0 -vvv -o result.html https://github.com/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 192.30.255.112...
* Connected to github.com (192.30.255.112) port 443 (#0)
* Unknown SSL protocol error in connection to github.com:-9836
* Closing connection 0
curl: (35) Unknown SSL protocol error in connection to github.com:-9836

このように、接続ができませんでした。 ということは、古いブラウザや古いバージョンのSSL/TLSしか利用できないクライアントは、接続できないということも起こります。

インフラ勉強会での図

さて、お話の中で、最初に触れた通り SSL/TLSのお話し を伺ったさいに、聞きながらメモしていたものも添えています。 こちらの記事にも載せておきます。

  • おもにSSL/TLSのハンドシェイクの図です

f:id:akiko-pusu:20180805215707j:plain

https以外でもつかわれているよ!

さて、SSL/TLSは暗号化のための技術なので、https以外にもいくつかで利用されています。 主なものはLDAPIMAPといったもの。 インフラが社内に無い、雲の上で生活しててグループウェアもブラウザで完結する生活だと、あんまり意識しないかもですが...

お話のなかでは、Gmailを手元のメールクライアントで読む際の通信をキャプチャして、こちらも暗号化されているのを見てもらいました。 (画像は割愛します)

  • Whiresharkのフィルタ例 / (tcp.port == 993) || (ip.addr == 108.177.97.108)

ホワイトボードでの図

説明にあたっては、これもいつも通りホワイトボードをたくさん利用しました。 当日のものを添えておきますので、何かの参考になれば幸いです。

f:id:akiko-pusu:20180805220057j:plain

デモに関してのメモ

うまく説明しずらいところは、Wiresharkcurlの出力を見てもらって、なんとか逃れた感があります...。 ただ、やはり通信の中身を見てみるというのは面白かったようです。

また、デモにあたっては、実際は同じミーティングルームだったのですが、CrankWheelを使ってわたしの操作画面をメンバーに共有してみました。 このあたりのやり方も、インフラ勉強会で教わったものを取り入れております。

あらためまして、インフラ勉強会と、インフラ勉強会でお話してくださった halu834 さま、本当にありがとうございます! *1

*1:誤字脱字、不備がございましたら、真摯に粛々と訂正させていただきます....