WiresharkのTLS1.3対応 動いた

TLS1.3動いたシリーズの第3回目(?)


WiresharkTLSの実装一覧ページ(URL)では、以前よりTLS1.3対応をうたっていたがあまり出来は芳しくなかった。しかし、先日 TLS1.3絡みのコミットが幾つか入ったので、実際に改善されていることを確認した


コミットログ(URL)を見る限りdecryptにも対応しているようなので、復号もできそうだが、TLS1.3になって復号に必要なパラメータが変わったため SSLKEYLOGFILEまわりの仕組みがどうなってるかはよくわからない。picotlsだと、うまくいくかもしれない(URL)

2019年5月追記: すでに復号対応しております。SSLKEYLOGFILEを食わせれば復号できる。

ビルド

今回は簡単に、CUI版であるTsharkをtrustyでビルドする

sudo apt-get install -y flex libglib2.0-dev libpcap-dev bison
git clone https://github.com/wireshark/wireshark.git --depth=1
cd ./wireshark
./autogen.sh
./configure --disable-wireshark  --enable-tshark  --enable-ipv6 --disable-gtktest\
   --disable-glibtest  --disable-editcap --disable-capinfos  --disable-mergecap --disable-reordercap\
   --disable-text2pcap --disable-dftest  --disable-randpkt --disable-airpcap  --disable-rawshark\
   --enable-pcap-ng-default  --without-lua 
make

パケットをパースする

NginxでTLS1.3 動いた(OpenSSL)」で取得したパケットを、Tsharkで読み込む。

ClientHello (一部省略)

vagrant@vagrant:~$ ./wireshark/tshark -r ./tls.pcap   -V
Secure Sockets Layer
    TLSv1 Record Layer: Handshake Protocol: Client Hello
        Content Type: Handshake (22)
        Version: TLS 1.0 (0x0301)
        Length: 512
        Handshake Protocol: Client Hello
            Handshake Type: Client Hello (1)
            Length: 508
            Version: TLS 1.2 (0x0303)
            Random: ba9f24242b401e562d6c529d6ea5cf...
                GMT Unix Time: Mar 20, 2069 07:54:12.000000000 UTC
                Random Bytes: 2b401e562d6c529d6ea5cf631acccb7...
            Session ID Length: 0
            Cipher Suites Length: 32
            Cipher Suites (16 suites)
                Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
                Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 (0x1303)
...
            Extension: elliptic_curves (len=14)
            Extension: key_share (len=107)
            Extension: supported_versions (len=9)
                Type: supported_versions (43)
                Length: 9
                Supported Versions length: 8
                Supported Versions: TLS 1.3 (draft 18) (0x7f12)
                Supported Versions: TLS 1.2 (0x0303)
                Supported Versions: TLS 1.1 (0x0302)
                Supported Versions: TLS 1.0 (0x0301)
            Extension: psk_key_exchange_modes (len=2)
            Extension: signature_algorithms (len=24)

TLS1.3のCipher Suitesや、supported_versionsを始めとしたTLS1.3の拡張がちゃんとパースされる


同様にServer Hello

Secure Sockets Layer
    TLSv1 Record Layer: Handshake Protocol: Server Hello
        Content Type: Handshake (22)
        Version: TLS 1.0 (0x0301)
        Length: 115
        Handshake Protocol: Server Hello
            Handshake Type: Server Hello (2)
            Length: 111
            Version: TLS 1.3 (draft 18) (0x7f12)
            Random: 218aacf7524e2cc7edbb75f985bf651d1243ec09f638ad05...
            Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302)
            Extensions Length: 73
            Extension: key_share (len=69)
                Type: key_share (40)
                Length: 69
                Key Share extension
                    Key Share Entry: Group: secp256r1, Key Exchange length: 65
                        Group: secp256r1 (23)
                        Key Exchange Length: 65
                        Key Exchange: 041433c8bcef4c30966cb6dbeb9...
    TLSv1.3 Record Layer: Application Data Protocol: http-over-tls
        Opaque Type: Application Data (23)
        Version: TLS 1.0 (0x0301)
        Length: 38
        Encrypted Application Data: a1d0f18331e621b995503baa963613de56a19d546a6abaf4...
...

ちゃんと各種パースされている。

Content-Encoding: aes128gcm とは (RFC8188)

20170624追記
RFC8188 として標準化されました


HTTPリクエスト・レスポンスのボディを暗号化する、「Content-Encoding: aes128gcm」を新しく標準化する「Encrypted Content-Encoding for HTTP」という仕様が議論されております。


提案自体は数年前に行われており、すでにWGドラフトになっています。

Encrypted Content-Encoding for HTTP

この仕様では、HTTPのボディをAEAD_AES_128_GCMで暗号化します。HTTPSと違って通信路だけでなく、アップロードファイル・ダウンロードファイル自体が暗号化されているため、ファイルが保存される場合も鍵を知っている人(ソフトウェア)のみがその内容を読むことができます。


仕様上では上記のような例があげられておりますが、IETFで同様に議論されているPush通知の標準プロトコル Web Push でこの仕様を使用します。「Message Encryption for Web Push」という仕様で、Content-Encoding: aes128gcmを使ってPush通知の暗号化プロトコルが実現されています。

暗号化概要

この仕様では、暗号化されたデータの他にも幾つかのパラメータをHTTPボディに含めて送信します。


具体的には下記のパラメータが暗号データ以外に送信されます

+-----------+--------+-----------+---------------+
| salt (16) | rs (4) | idlen (1) | keyid (idlen) |
+-----------+--------+-----------+---------------+
  • salt: ソルト
  • rs: レコードサイズ
  • idlen: keyidの長さ
  • keyid: keying materialの識別子


特徴として鍵交換の具体的方法は定義されておりません、別途別の手段で交換する必要があります。この仕様ではお互いにどのkeying materialを使用しているか識別するためのkeyidを通知するだけになります。


実際に暗号化に使用する鍵は、keying materialより導出されます。

鍵の導出

送信したパラメータと、keying materialより暗号化に用いる鍵を導出します
input keying material (IKM), pseudorandom key (PRK), content encryption key (CEK)

   PRK = HMAC-SHA-256(salt, IKM)
   cek_info = "Content-Encoding: aes128gcm" || 0x00
   CEK = HMAC-SHA-256(PRK, cek_info || 0x01)

( || は結合)

ノンスの導出

同様にノンスも導出します
The record sequence number (SEQ)

   nonce_info = "Content-Encoding: nonce" || 0x00
   NONCE = HMAC-SHA-256(PRK, nonce_info || 0x01) XOR SEQ

レコードのシーケンス番号がつくことで、順番の入れ替えや欠落を検出できるようになっております。

パディングの順番が変更になりそうですので、その例でどのように暗号化されるか見ていきます。
( https://github.com/httpwg/http-extensions/blob/0f3ff05b3679f9ead35c1be800723e73915b6aaf/draft-ietf-httpbis-encryption-encoding.md )


この例では、暗号化するデータは「I am the walrus」(UTF-8)、keying materialは「B33e_VeFrOyIHwFTIfmesA」(base64)、keyidは空文字列

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 54
Content-Encoding: aes128gcm

I1BsxtFttlv3u_Oo94xnmwAAEAAA-NAVub2qFgBEuQKRapoZu-IxkIva3MEB1PD-
ly8Thjg
  • salt (from header) = I1BsxtFttlv3u_Oo94xnmw
  • PRK = zyeH5phsIsgUyd4oiSEIy35x-gIi4aM7y0hCF8mwn9g
  • CEK = _wniytB-ofscZDh4tbSjHw
  • NONCE = Bcs8gkIRKLI8GeI8
  • plaintext = SSBhbSB0aGUgd2FscnVzAg

QUICのヘッダ圧縮QPACKとは (旧QPACK)

20190408追記
最新版について記事を書きました
asnokaze.hatenablog.com


QUICのヘッダ圧縮であるQPACKについて


現在のQUICの策定中仕様の一つである「Hypertext Transfer Protocol (HTTP) over QUIC」では、QUICでもHTTPヘッダの圧縮にHPACKを使用することになっていますが、新しくQUIC用のヘッダ圧縮方式として「HTTP over QUIC - Mapping and Header Compression」という仕様でQPACKなるものが提案されています。

背景 QUICとHPACK

HTTP/2ではヘッダ圧縮方式としてHPACKと呼ばれる方式を利用します。HPACKはRFC 7541「HPACK: Header Compression for HTTP/2」で標準化されています。


HPACKはヘッダ名とヘッダ値をハフマン符号化するほか、静的テーブル・動的テーブル呼ばれる辞書を用いてHTTPヘッダを表現します。特に、一つのコネクションで一度使用したHTTPヘッダは動的テーブルに追加され、次のHTTPリクエスト・レスポンスではindex(1バイト)でそのヘッダ名・ヘッダ値を表現することができます。


QUICでもこのHPACKを使用するのですが、困ったことがあります。前提として、QUICはUDPを使用することで、HTTP2 over TCPの時にあったTCPレイヤのヘッドオブラインブロッキングを克服しています。


どういうことかというと、TCPではパケットの欠損や順番の入れ替わりがあるとそれらが解決出来るまで、すでに届いてるパケット(HTTPリクエスト・HTTPレスポンス)を処理することができません。欠損しているパケットの後続すべてが待たされてしまいます。


QUICではUDPを使用しており、一つのパケットロスなどにより他の処理がブロックされることはないようになっております。QUICが持つストリーム毎にパケットロスの回復や順番の並び替えを行うため、一つのストリームでのパケットロスは他のストリームに影響しません。


しかし、HPACKの動的ヘッダテーブルはテーブルへの挿入・エビクションが発生するため結局は順番通りにしか処理できないという欠点があります。つまり、結局パケットロスにより後続の処理はブロクッされてしまいます。


そこでHPACKが持つヘッドオブラインブロッキングを解消するために、QPACKが提案されているということになります。

QPACK

QPACKでは上記のような、HPACKが持つヘッドオブラインブロッキング問題を解決するために、動的テーブルの扱いに大きな変更が入っています。HPACKでは、動的テーブルへの追加する際にインデックス番号がずれていく他、追加・エビクションの際のインデックス番号は暗黙的でした。


QPACKの動的テーブルは下記のような特徴を持っています。

  • 追加する際はインデックス番号を明示する(静的テーブルの一番大きいインデックス番号より大きいこと)
  • エビクションは発生しない(テーブル上限を超えるとFatal error)
  • すでに使用されているインデックス番号は、明示的に削除するまで変更できない
  • 明示的に削除するDeletionを使用する
  • そのインデックスを参照するフレームをすべて処理し終わったら、QPACK-ACKフレームを送信して削除できるようになる


このようにして、一度追加されたインデックスはお互いにもう使用しないことが確認でき削除されるまで変更されないようになっています。


もちろんQUICのパケットは入れ替わるため、インデックスへ追加するパケットとインデックスを参照するパケットの順番が入れ替わることがあります。その時はインデックスへ追加するパケットが到着するまで待つことになります。


これを避けるために、QPACKは同じインデックス番号に同じ値を追加する命令は何度でも行って良いことになっており、複数のHTTPリクエストにおいてインデックスの追加命令を行うことで、待つパケットを減らすことができます(ただし、圧縮効率は落ちます)。また、部分的なHeader Block Fragmentsが受信できた際は処理をすすめることができ、この部分にテーブルへの追加がアレば実行して良いことになっています。


削除もお互いに合意できて(QPACK-ACKを受け取って)やっと削除ができます。つまり、そのインデックスを参照するパケットをすべて処理し終わったタイミングです。テーブルサイズの上限まで使用してると、なかなか合意が得られず削除できないこともあるので、仕様では上限になる前に送信側が削除を試みることを推奨してます。


その他仕様的な話

上記での説明と重複もあるが、以下のことについて書かれている

  • 静的テーブルや、文字列リテラル表現、動的ヘッダテーブルを更新しないヘッダ表現は変更はない
  • Literal Header Field with Incremental Indexingがなくなり、Literal Header Field with Indexingが新しく定義され、明示的にインデックス番号を指定するようになる
  • Dynamic Table Size Updateがなくなり、明示的に削除を行うDeletionになる
  • テーブルサイズ管理
  • 動的テーブルの伸長・縮小についての変更
  • この仕様によりQUICのHEADERSフレーム・PUSH_PROMISEフレームのシーケンス番号が不要になるので削除
  • お互いに削除出来ることが確認できるまでの4つの段階の定義
  • Performance Considerations, Security Considerations

翻訳

翻訳というのには気が引けるが


一応ざっと目を通したので、QPACKの翻訳をひっそり置いておく。
(読み返したら微妙だったので、後日直します....ます....)


https://github.com/flano-yuki/my-quic-spec-translation/blob/master/draft-bishop-quic-http-and-qpack/draft-bishop-quic-http-and-qpack-01.md

NginxでTLS1.3 動いた(OpenSSL)

昨日書いた「OpenSSLのTLS1.3対応」の続き


特に何かしたわけではないが、OpenSSLのTLS1.3対応が進んでいたのでインストールしてNginxで動かす。

ビルド

openssl
今回はインストールしてしまう。

$ git clone https://github.com/openssl/openssl.git
$ cd ./openssl
$ git log --oneline |head -n1
ef3f621 Fix man3 reference to CRYPTO_secure_used

$ ./config enable-tls1_3
$ make
$ sudo make install

nginx (openssl1.1.0対応が終わってるものなら大丈夫かと)

$ git clone https://github.com/nginx/nginx.git
$ cd ./nginx
$ ./auto/configure --with-http_ssl_module
$ make
$ make install

(conf修正と、証明書は各自)

試す

Firefox Nightlyで、about:configよりsecurity.tls.version.maxを5に設定しておく。
アクセスし、デベロッパーツールのセキュリティパネルより確認する。


この通りTLS1.3と表示される


Nginx側のログも、この通りTLS1.3であることが確認できる

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      'TLS:$ssl_protocol';
192.168.0.105 - - [19/Jan/2017:15:11:54 +0000] "GET / HTTP/1.1" 200 0 "-" TLS:TLSv1.3


実装としてはやはりまだ不安定でChrome Canaryだと通信に失敗する
ちなみに、ALPNも動いてるようでhttp2も普通に喋れている。


ベンチでも取りたかったが、abがopenssl 1.1.0でのインターフェイス変更に対応してるかちょっと怪しかったので断念。


ゆくゆくは何も意識せずTLS1.3が使われるようになるだろう。

PerformancePaintTiming API(First Paint Timing API)とは

昨今、「Navigation Timing API」といったクライアント側でのページの処理時間を取得する機能が充実してきております。


W3CのWeb Performance WGでは、Navigation Timing、User Timing、Resource TimingそれぞれLevel2, Level3が改訂作業が行われております。(ドラフト状況)


また、昨年末頃より「PerformancePaintTiming」APIの議論が行われているように感じます。簡単な仕様はgithubのwicg organization以下に公開されております ( https://github.com/WICG/paint-timing )。これは、より多くの情報を取得できるようにする一環で、実際の描画時間が重要度が高いためのようだ。


数年前に、ふろしきさんが「Webページ遷移時間のパフォーマンス「First Paint」を計測する方法」で書かれている通り、window.chrome.loadTimes()より、firstPaintがとれましたが、より現代的な標準APIが検討されております。


PerformancePaintTiming

リポジトリの使い方とサンプルを見るのがわかりやすいかと思います。
PerformanceObserverから、firstPaintエントリより最初の描画タイミングが取得できます。

var observer = new PerformanceObserver(function(list) {
  var perfEntries = list.getEntries();
  for (var i = 0; i < perfEntries.length; i++) {
     // Process entries
     // report back for analytics and monitoring
     // ...
  }
});

// register observer for long task notifications
observer.observe({entryTypes: ["firstPaint"]});



(https://github.com/WICG/paint-timing#examples

  • first-paint: ナビゲーション後のcontaining blockを最初に描画した時間(背景を除く)
  • first-contentful-paint: 最初の、テキスト、画像、キャンバス、SVGを描画した時間

Improving accuracy of FP / FCP

Chromeでは、内部的にFPとFCPの時間を取得しており stable版で情報収集をしている。
https://docs.google.com/document/d/1XVP9jaZT7acQtK5O6vO-mIz31i5iB-pZn1S41_YP6p0/edit#


その中で、さらに細かくどこまでの時間をFP, FCPに含めるべきなのかというレポートを出している。どこまで仕様化できるかはわからないが、今後共WebPerf WGで議論が続けられる模様である。

OpenSSLのTLS1.3対応 喋れる

追記 20170120


NginxでTLS1.3 動いた(OpenSSL)
後日Nginxでも動いたので、上記記事に記載


セキュリティとパフォーマンスが向上したTLS1.3の登場が待ち望まれております。


標準化としては、現在IETFTLSワーキンググループではWGラストコールという段階に入っており、2月頃にはIESGに提出され標準化は最終段階となる見込みになっております(結構そっから議論があったりはするんですが)。


すでに、実装としてはChrome, Firefoxといったブラウザもオプションで有効化に出来る他、BoringSSL, picotls, nssといった実装も出てきており、相互通信テストなども行われております。


昨年行われたIETF97時点ですが、下記のような実装で通信テストに成功しています。

https://www.ietf.org/proceedings/97/slides/slides-97-tls-tls-13-00.pdf


さて、OpenSSLはと思うところですが、同じくIETF97時点ですが、OpenSSLの状況が報告されております。すべての作業はgithub上でプルリクが出されており、次のリリースで出るとのことでした。
https://www.ietf.org/proceedings/97/slides/slides-97-tls-openssl-update-00.pdf


TLS1.3の中身の話は、kazuhoさんやkazuさんの資料がわかりやすいかと思います

とりあえず、現状で試してみる

OpenSSLはまだまだ開発中で動かないかもと思いながらも、試してみる
./config時にenable-tls1_3を与える

$ git clone https://github.com/openssl/openssl.git
$ cd ./openssl
$ git log --oneline |head -n1
ef3f621 Fix man3 reference to CRYPTO_secure_used

$ ./config enable-tls1_3
$ make


既存実装のリストは下記にあるので、公開URLのものから幾つか繋いで見る
https://github.com/tlswg/tls13-spec/wiki/Implementations

クライアントで試す

他のバージョンと同様 --tls1_3を与える

$ LD_LIBRARY_PATH=./ ./apps/openssl s_client -connect franziskuskiefer.de:9913 --tls1_3
...
New, TLSv1.3, Cipher is TLS13-AES-128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.3
    Cipher    : TLS13-AES-128-GCM-SHA256
    Session-ID:
    Session-ID-ctx:
    Master-Key: AC496ABF3FE5EF1C197CA5B825746CD58A05E86494288C82057604F819C2B757
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1484672332
    Timeout   : 7200 (sec)
    Verify return code: 20 (unable to get local issuer certificate)
    Extended master secret: no
---
GET / HTTP/1.1
host:test

HTTP/1.1 200 OK
Date: Tue, 17 Jan 2017 16:58:56 GMT
Server: Apache
Upgrade: h2
Connection: Upgrade, close
Last-Modified: Fri, 03 Jun 2016 08:50:46 GMT
ETag: "d5f-5345bd194edaf"
Accept-Ranges: bytes
Content-Length: 3423
Content-Type: text/html; charset=UTF-8

<!DOCTYPE html>
...


他のは繋がらなかったものの、つながるサーバもあった。
(どこが悪いのかまだちゃんと見てない...

サーバ側で試す

Chrome canaryと通信させる

$ LD_LIBRARY_PATH=./ ./apps/openssl s_server -cert ../server.crt -key ../server.key  -tls1_3
...
-----BEGIN SSL SESSION PARAMETERS-----
MGUCAQECAgMEBAITAQQgMuuXEJUnlwWHomt0g/L89WPXiudXU2AHGFd6NdSKBPYE
IIKAszcJj6hsNaEPwtZz6t/IskTrQB5QnBvCCV9YjxZ+oQYCBFh+TwiiBAICHCCk
BgQEAQAAAA==
-----END SSL SESSION PARAMETERS-----
Shared ciphers:TLS13-AES-128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA:AES256-SHA
Signature Algorithms: ECDSA+SHA256:RSA+SHA256:RSA+SHA256:ECDSA+SHA384:RSA+SHA384:RSA+SHA384:RSA+SHA512:RSA+SHA512:RSA+SHA1
Shared Signature Algorithms: ECDSA+SHA256:RSA+SHA256:RSA+SHA256:ECDSA+SHA384:RSA+SHA384:RSA+SHA384:RSA+SHA512:RSA+SHA512:RSA+SHA1
Supported Elliptic Groups: 0x9A9A:X25519:P-256:P-384
Shared Elliptic groups: X25519:P-256:P-384
CIPHER is TLS13-AES-128-GCM-SHA256
Secure Renegotiation IS NOT supported
GET / HTTP/1.1
Host: 192.168.0.179:4433
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2984.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: ja,en-US;q=0.8,en;q=0.6

なんか、怪しいけど、HTTPリクエストが来てることは確認できた。

2016年振り返り

早いもので2016年も終わりですね。


今年は、毎月コンスタントにブログ記事を書けたもの、記事数39と去年よりも少なくなってしまいました。internet-draft絡みも9本でした。


ただ反面、OSSにPR投げてマージされたり、英語でissue投げてみたり、Nginxのモジュール書いてみたり出来たのは良かったなと思いました。


昨年同様 IETFにも行けましたし、来年も行けるように頑張ります。
来年も、微力ながら誰かのお役に立てるように精進していければと思います。


今年もありがとうございました。