Encrypted SNIの効果と韓国でのブロッキングの回避方法について

## はじめに

以前の記事[DNS over HTTPSの必要性について](https://qiita.com/migimigi_/items/1ca81163a79f4e154cdf)で触れたEncrypted SNIについて、その有効性と現在韓国で行われているSNIフィールドを利用したブロッキングを回避する方法についての説明です。

## Encrypted SNIとは

Encrypted SNIは、現在広く使われているTLS1.2以下のハンドシェイク時、接続したいドメインが「平文」で流れ、盗聴による接続先の推測やブロッキングが行われてしまうという問題を解決するための手法です。Encrypted SNIの手法については当初の案と現在Cloudflare社がサービスを運用している案で大きく異なるため、簡単に説明します。

### トンネリング方式

TLS接続時に、まず中継サーバへTLS接続し、そのTLSコネクションの中で目的のドメインまでTLS接続を行う方式です。"TLS in TLS"と呼ばれています。
![enc-sni4.png](https://qiita-image-store.s3.amazonaws.com/0/87092/750fdfb0-4c29-2c73-6bc6-237dae0d9b32.png)
[TLS 1.3 Encrypted SNI拡張の議論](https://lepidum.co.jp/blog/2015-12-06/tls-enc-sni/)より引用。
ドラフトとしては[draft-ietf-tls-sni-encryption-04](https://datatracker.ietf.org/doc/draft-ietf-tls-sni-encryption/)が定義されています。
メリットとしてTLS1.2でも運用できるというものがありますが、ゲートウェイのコストを調達するのが難しいからか実際に運用されている例を私は見つけることが出来ませんでした。

### TLS拡張方式

TLS1.3の拡張として、現在使われている平文の`server_name`の代わりに暗号化されたSNIフィールド`encrypted_server_name`を使用する方式です。
ドラフトとしては[draft-ietf-tls-esni-02](https://datatracker.ietf.org/doc/draft-ietf-tls-esni/)が定義されています。
現在Cloudflare社が提供しているEncrypted SNIはこの方式です。暗号化するための公開鍵はDNSを用いて、アクセスしたいドメインの先頭に`_esni.`を追加したドメインのTXTレコードを用いて公開します。これによりアクセスしたいドメインに対応する公開鍵を入手することが出来ます。例としてwww.cloudflare.comにアクセスする場合の公開鍵をdigを使って表示させてみましょう。

```
$ dig @1.1.1.1 _esni.www.cloudflare.com txt

; <<>> DiG 9.10.6 <<>> @1.1.1.1 _esni.www.cloudflare.com txt
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2903
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1452
;; QUESTION SECTION:
;_esni.www.cloudflare.com.      IN      TXT

;; ANSWER SECTION:
_esni.www.cloudflare.com. 2980  IN      TXT     "/wF0rervACQAHQAg5LqXYLGnCc0e+H8+gGGpSqbl9/LRjrS9y/IfTT7jBEYAAhMBAQQAAAAAXGkGcAAAAABccO9wAAA="

;; Query time: 5 msec
;; SERVER: 1.1.1.1#53(1.1.1.1)
;; WHEN: Wed Feb 20 17:13:14 JST 2019
;; MSG SIZE  rcvd: 158
```

今回は平文のDNSで公開鍵を取得しましたが、悪意のある第三者が偽の公開鍵を本物の応答より先に返してくることが可能であるため、実際にはDNS over HTTPSもしくはDNS over TLSを用いて取得する必要があります。

Encrypted SNIに対応したサーバとクライアントでHTTPS接続をしたときのパケットキャプチャを見ると
![capture.png](https://qiita-image-store.s3.amazonaws.com/0/87092/be9db4b4-6a54-1797-d73f-cfbd40c19afa.png)
このように、`encrypted_server_name`拡張が使用されていることが確認できました。
ちなみにTLS1.3のEncrypted SNI拡張をWiresharkで表示させるには現在のリリース版のv2.6.6ではなくRC版のv3.0.0rc1が必要でした。

## Encrypted SNIの効果

現在Encrypted SNIの実装は

- サーバ
    - Cloudflare社のサービスを使用しているWebサイト
- クライアント
    - Firefoxのリリース版（65.0.1で確認）
        - about:configでの設定が必要 [Firefox65.0b8でDNS over HTTPSやDNSSEC、Encrypted SNIを有効にする方法](https://qiita.com/Nelson605/items/933ad2ea34e5e7a69b74)
        - 設定項目の詳細は [INSIDE FIREFOX’S DOH ENGINE](https://daniel.haxx.se/blog/2018/06/03/inside-firefoxs-doh-engine/)が詳しい

で行われています。

これらの実装を使用すれば、韓国で行われているSNIフィールドを用いたブロッキングは回避できるのではないかと以前の記事で書いたのですが、現実はEncrypted SNIが全てブロッキングされてしまっているようです。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">And it looks like they&#39;re blocking encrypted SNI outright (according to accounts on the ground). In some ways, this is our fault for not agreeing on a final spec and pushing it out to more clients faster. The politics around network privacy engineering are tricky. Cliffs abound. <a href="https://t.co/INnCS25akO">https://t.co/INnCS25akO</a></p>&mdash; Nick Sullivan (@grittygrease) <a href="https://twitter.com/grittygrease/status/1095530153319358465?ref_src=twsrc%5Etfw">2019年2月13日</a></blockquote>

このツイートはCloudflare社の暗号部門のヘッドであるNick Sullivan氏のものです。
先程のパケットキャプチャの画面を見れば分かるようににEncrypted SNIを使用すると、どのドメインに接続しようとするかは暗号化されますが、その接続がEncrypted SNIを使用しているか否かは第三者が判別することは可能です。ここまで強力なブロッキングを実施している場合では、例えEncrypted SNIの実装がサーバとクライアント共に広まったとしても韓国のユーザは「Encrypted SNIを使用すると全てブロックされてしまうが、今までどおりの平文のSNIを使用すれば政府がブロックしていないサイトは閲覧できる」とEncrypted SNIを使用しないよう設定することが広まってしまうのではないかと予想しています。

## Encrypted SNI以外のブロッキング回避方法

現在韓国のユーザのSNSやブログではSNIフィールドを利用したブロッキングを回避するための情報がいくつか公開されています。主な種類として

- 海外のVPNサービスを利用する
- Client Helloメッセージを断片化させる

がありました。VPNサービスは予想していたのですが、Client Helloメッセージの断片化については初めて知ったので、調べました。

SNIブロックをバイパスするためのmtuの設定方法です。

>2019年02月11日からdnsブロックに続いてsniブロック（httpsブロック）が開始されました。
複数バイパス方法がありますが、その中mtuの設定を変更してバイパスする方法です。MTU（Maximum Transmission Unit）は、通信を行うときに送信するパケットの最大転送単位を意味します。
通信を行うとき、全体のデータ量が大きいため、一度に転送することができなくて切って転送をすべきなのにこの時に使用する切断ユニットがmtu値です。 
一般的に、mtu値が大きい場合は、一度に多くの量を処理するための速度に利点がありますが、回線が不安定な場合の損失が生じるリスクが増加することになります。
最近では、回線の状態が良いので、デフォルトで1500の多くを使用します。
sniブロックをバイパスする
原理を簡単に説明すると内容を小さな単位に切って、パケットの内容が公開されていたよもフィルタリングにかからないようにする方法です。
例えば、mtuが1500のときに「rootblog」という内容を一度に送信することができた場合、500に設定すると、 "roo"、 "tbl"、 "og "に分けて送りされて内容が露出しても何の内容なのか知ることができなくてフィルタリングをせよという意味です。バイパスが可能なmtu値は、クロムが400以下エッジやその他のブラウザが220以下で知られています。

https://rootblog.tistory.com/142 より引用、Google翻訳を使用。

このブログ等を参考にMTU値を極めて小さくし、Client Helloメッセージを断片化させてみたのが以下です。
![mtu.png](https://qiita-image-store.s3.amazonaws.com/0/87092/9592f945-6b1f-d309-15e7-78d68dd6bb52.png)

これによりブロッキングを回避できるかどうかを検証したかったのですが、ブロッキングされる環境を用意することが出来なかったため検証は出来ていません。
（AWS ソウルリージョンのEC2インスタンスから規制対象とされているサイトへアクセスしましたが問題ありませんでした）
この回避方法が有効である場合、韓国のブロッキング用サーバでは、Client Helloメッセージが1パケットに収まっていることを前提とした構成になっていると推測できます。この前提によりブロッキング用サーバはパケットを1つ見るだけでそのTCP接続を妨害するか否かを決定することが出来ます。韓国のインターネットユーザ全員を対象としたブロッキングであるため、処理の高速化が求められたのではないでしょうか。

MTU値の書き換えによる断片化等を自動で設定するGoodbyeDPIというツールも紹介されていました。（これは元々ロシアでのブロッキングに対抗するために開発されたツールと思われます）
https://github.com/ValdikSS/GoodbyeDPI

また、別の方法としてTCP接続を切断するためのRSTフラグが立ったパケットをDROPするという方法もPoCとしてですが開発されていました。
YouTube動画 [SNI 차단 우회(2)](https://www.youtube.com/watch?v=OHHQb-OMZJQ)

動画ではシンプルにRSTフラグが立ったパケットを全てDROPしているようですが、今後この方法を洗練させてブロッキング用のRSTフラグが立ったパケットのみを叩き落とすようなツールが開発されるかもしれません。

今後もブロッキングとその回避方法についてはイタチごっこが続くでしょう。また大きな動きがあったらまとめたいと思います。

## 参考文献
[政府によるインターネットの検閲とSNIについて](https://medium.com/@catatsuy/%E6%94%BF%E5%BA%9C%E3%81%AB%E3%82%88%E3%82%8B%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%8D%E3%83%83%E3%83%88%E3%81%AE%E6%A4%9C%E9%96%B2%E3%81%A8sni%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6-5339da2fad7c)