QUICを組み込み向け TLS で動かしてみる

## 概要

IETFが2022年6月6日にWeb通信における新しい通信プロトコル HTTP/3 を[RFC9114](https://datatracker.ietf.org/doc/html/rfc9114)として発行しました。最大の特徴は、トランスポートのプロトコルに QUIC(Quick UDP Internet Connections) を採用した点です。QUICは[RFC9000](https://datatracker.ietf.org/doc/html/rfc9000) として IETF より 2021 年に発行されている UDP に基づくプロトコルです。QUICはそのプロトコルスタック内で、認証・秘匿性・改ざん検知にTLS 1.3 のハンドシェイクプロトコルを利用しています。

[ngtcp2](https://github.com/ngtcp2/ngtcp2) は、QUIC実装を実現しているオープンソースプロジェクトです。組み込み向け TLS ライブラリの wolfSSL は ngtcp2 への統合をサポートしました。ngtcp2 は wolfSSL を通してQUIC通信を行うことができます。

今回は、ngtcp2 の TLSハンドシェークプロトコルのバックエンドに wolfSSL を利用し HTTP3 の通信をHTTP3テストサーバーに対して行います。また、Wiresharkを使ってそのときの QUIC パケットを覗いてみます。 TLS 1.3 のハンドシェークは `ServerHello` 以降が暗号化されますが、復号化しその中身を覗いてみることにします。HTTP3 の通信には、[nghttp3](https://github.com/ngtcp2/nghttp3) を利用します。下記図は今回使用するモジュールとQUIC/HTTP3のスタック構造を簡単に示しています。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683403/82a4630b-70a2-2776-70d3-522db3bf700c.png)
図 QUIC/HTTP3 のスタック構造とモジュール


## ダウンロード、ビルド、インストール

### 1. wolfSSL のソースコード取得とビルド
最新コードを github から取得します。

```sh
$git clone --depth 1 https://github.com/wolfSSL/wolfssl 
```

コンフィグレーションに "--enable-quic"オプション及び必要な機能を指定し QUIC 機能を wolfSSL に組み込みシステムへインストールします。また、のちほど WireShark でパケット復号のために、HAVE_SECRET_CALLBACK 定義も指定しておきます。このオプションは技術評価などのためのものです。運用システムには組み込まないように注意してください。

```sh
$ ./autogen.sh
$ ./configure --enable-quic --enable-session-ticket --enable-earlydata --enable-psk CFLAGS="-DHAVE_SECRET_CALLBACK"
$ make && make check
$ sudo make install
```

### 2. nghttp3 のソースコード取得とビルド

ngtcp2 の上物として nghttp3 を取得・ビルドします。

#### 2.1 nghttp3 のソースコード取得
github から最新版を clone します。

```sh
git clone https://github.com/ngtcp2/nghttp3
```

#### 2.2 nghttp3 のビルド

以下のコマンドを使って nghttp3 をビルドします。

```sh
$ cd nghttp3
$ autoreconf -i
$ ./configure --prefix=$PWD/build --enable-lib-only
$ make && make install
```

### 3. ngtcp2 のソースコード取得とビルド

#### 3.1 ngtcp2 のソースコード取得
github から最新版を clone します。

```sh
git clone https://github.com/ngtcp2/ngtcp2
```

#### 3.2 ngtcp2 のビルド

以下のコマンドを使って nghttp2 をビルドします。

```sh
$ cd ngtcp2
$ autoreconf -i
$ ./configure PKG_CONFIG_PATH=$PWD/../nghttp3/build/lib/pkgconfig --with-wolfssl --with-libnghttp3
$ make
```

コンフィグレーション実行時に、Examplesが `yes`　となっていることを確認します。

```
....
      wolfSSL:        yes (CFLAGS='-I/usr/local/include' LIBS='-L/usr/local/lib -lwolfssl')
    Examples:         yes
```

`no` と表示されている場合、`libev`もしくは`C++20 対応 g++` がインストールされていない可能性があります。これらをインストールします。

## サンプルを動かしてみる

サンプルクライアント・サーバープログラムは、`./example/wsslclient` 及び `wsslserver` となります。クライアントプログラムをテストサーバーに対して接続します。以下のような出力が観測されればビルドは成功しています。

```
$ ./examples/wsslclient nghttp2.org 4433
I00000000 0x4251297518ee500bc5917beeecb21cbd82 pkt tx pkn=0 dcid=0x37b3baee728d6ba376ffa62ccbe2d23b65de scid=0x4251297518ee500bc5917beeecb21cbd82 version=0x00000001 type=Initial len=0
I00000000 0x4251297518ee500bc5917beeecb21cbd82 frm tx 0 Initial CRYPTO(0x06) offset=0 len=292
I00000000 0x4251297518ee500bc5917beeecb21cbd82 frm tx 0 Initial PADDING(0x00) len=840
I00000000 0x4251297518ee500bc5917beeecb21cbd82 rcv loss_detection_timer=79637890688438 timeout=999
Sent packet: local=[192.168.11.35]:46111 remote=[139.162.123.134]:4433 ecn=0x2 1200 bytes
Timer has already expired: 0.000854689s
....
I00007440 0x4251297518ee500bc5917beeecb21cbd82 rcv target_cwnd=41962 max_delivery_rate_sec=119941 min_rtt=20972262
I00007440 0x4251297518ee500bc5917beeecb21cbd82 rcv loss detection timer canceled
I00007440 0x4251297518ee500bc5917beeecb21cbd82 pkt read packet 43 left 0
Set timer=0.040819s
Set timer=0.040657s
Set timer=29.957907s
ngtcp2_conn_handle_expiry: ERR_IDLE_CLOSE
```

## Wireshark で QUIC パケットを覗いてみる

無事、テストサーバーに接続できたところで、そのパケットを Wireshark で覗いてみます。Wiresharkを起動し、再度、`wsslclient`を実行します。パケットのフィルターには下記をしています。

```
quic && ip.addr == 139.162.123.134 && udp.port == 4433
```
最初にHandshakeを行っていることが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683403/33e2a015-b9ad-6290-5971-b8f3980f2fbb.png)
パケットの一部を次の図のように拡大してます。UDP上に QUIC パケットが乗り、TLSの `ClientHello` メッセージを送信していることが分かります。`ClientHello` のメッセージそのものは数百バイトですが、QUIC仕様にしたがって 1200 bytes になるようにパディングされています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683403/53977102-0ec3-7dbb-58c0-00a1d1a770ed.png)

QUICは、TLS 1.3 のハンドシェークプロトコルを利用していることから `ServerHello` パケット以降の内容は暗号化されており、詳細を見ることが出来ません。詳細をみるために復号するには SSLKEYLOGFILE 環境変数を以下のように設定します。

```
export SSLKEYLOGFILE=/path/to/quic_keylog_file
```

注意点として、Wireshark は QUIC プロトコルを早くからサポートしていますが Wireshark のバージョンによっては QUIC プロトコルのDraftバージョンのみをサポートし、それより新しいプロトコルバージョンは期待した通り復号できない場合があります。この記事を書いている段階(2022.0923)で Ubuntu 上の Wireshark version 3.6.5 で QUIC 正式バージョンを復号することが出来ました。

Wireshark を起動します。[編集]->[設定]->[Protocols]->[QUIC]のポートに4433 を設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683403/664df9fb-4fe5-235e-1454-03a48de67e9b.png)

次に[編集]->[設定]->[Protocols]->[TLS] (Pre)-Master-Secret log filename を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683403/9d6316db-918e-90da-8922-9738393265c0.png)

設定が終わったら再度 `wsslclient` プログラムを実行します。

```
$ ./examples/wsslclient nghttp2.org 4433
```
`ServerHello` 以降のメッセージが復号されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683403/f65871da-faa7-9ee1-8ea8-35fa9410f5b3.png)
メッセージの内容を拡大表示します。ALPN TLS 拡張子に HTTP3 プロトコルで通信行うことをサーバーが選択し、サーバーからの QUIC パラメータが QUIC Transport Parameters TLS 拡張子に収められていることが分かります。またバイト数の大きな証明書（Certificate）は複数のQUICパケットに分割され送信されています。
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683403/ea7d4f6a-9231-49c5-066f-3ea4fb7005e2.png)


## まとめ
今回は、次世代のインターネット通信規格として注目をあつめる QUIC のTLSハンドシェークプロトコルのバックエンドを wolfSSL で動かしてみました。また、QUIC実装の一部を垣間見るためにTLSハンドシェークプロトコルを復号してみました。
QUICは、今後、組み込む向けにも軽量・高速な通信規格としても注目されるものと思われます。


参照リンク：
ngtcp2  : https://github.com/ngtcp2/ngtcp2
nghttp3 : https://github.com/ngtcp2/nghttp3
wolfBlog : https://wolfssl.jp/wolfblog/2022/08/17/wolfssl-quic-support/
Curl 作者 Daniel Stenberg のブログ : https://daniel.haxx.se/blog/2022/08/15/quic-and-http-3-with-wolfssl/