6月 192019
目次
Nekoxy2 で実装している内容。
SSL/TLS 通信の確立
- プロキシで SSL/TLS を復号化するためには、まず CONNECT トンネル内のプロトコルが SSL/TLS であるかを判断する必要がある
- CONNECT トンネルの中身にはプロキシは関知しないため
- SSL/TLS であるかを判別するためには、最低でも最初の 1byte のデータの読み取りは必要となる
- .NET の NetworkStream はバッファーを覗くことができず、1byte でも読んでしまうと再度そのデータを読み取ることはできなくなる
- SSL/TLS 通信のための SslStream を利用するためには当然完全なバイトストリームが必要となる
- BufferedStream を挟むことでバッファーの確保はできるようになるが、バッファーの中身を読み取る手段が用意されていない
- 結局自作の BufferedStream が必要となる
- プロキシで復号化するということは、通常クライアント – サーバー間で確立する SSL/TLS トンネルを、クライアント – プロキシ間、プロキシ – サーバー間の2つに分割して確立する必要が出てくる
- HTTP/2 のネゴシエーションでは ALPN を用いるため、プロキシに置いてもクライアント・サーバー双方に対して ALPN でネゴシエーションを行う必要がある
- クライアントが示したプロトコルリストをサーバーに渡す
- サーバーが選択したプロトコルをクライアントに渡す
- .NET の SslStream の ALPN 対応は、.NET Core 2.1 / .NET Standard 2.1 以上であり、.NET Framework は対応できない
- ALPN に対応していたとしても、プロトコルリストは接続確立後にしか読み取れない
- つまり「クライアントが示すプロトコルリストをサーバーに渡す」ためには SslStream に頼らず自力で ClientHello の ALPN を解析する必要がある
- HTTP/2 のネゴシエーションでは ALPN を用いるため、プロキシに置いてもクライアント・サーバー双方に対して ALPN でネゴシエーションを行う必要がある
- SSL/TLS を解読するためには、クライアントに対してすり替えたサーバー証明書を作成して送信すること、それを作成するためのルート証明書を作り、信頼させることが必要となる
- 驚くべきことに、.NET BCL には証明書を作成する API が存在しない
- Nekoxy2 では、BouncyCastle というライブラリを利用することで解決
- COM を用いる手もあったが、Windows に依存してしまうため不採用
HTTP/2 MITM のための ClientHello の ALPN 解析
参照 RFC7301, 8446, 5246
- 上記の通り、ClientHello の ALPN を解析する必要があった
- ClientHello は以下のようなデータ(TLS 1.3 の場合)
TLSv1.3 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: 0882c722f7c56568e80d01d332838789063b2e51dfb7f4f7…
Session ID Length: 32
Session ID: 16a0be010c7da8d8397fcf6020060d124b95c36a818f3b20…
Cipher Suites Length: 34
Cipher Suites (17 suites)
Compression Methods Length: 1
Compression Methods (1 method)
Extensions Length: 401
Extension: Reserved (GREASE) (len=0)
Extension: server_name (len=18)
Extension: extended_master_secret (len=0)
Extension: renegotiation_info (len=1)
Extension: supported_groups (len=10)
Extension: ec_point_formats (len=2)
Extension: session_ticket (len=0)
Extension: application_layer_protocol_negotiation (len=14)
Type: application_layer_protocol_negotiation (16)
Length: 14
ALPN Extension Length: 12
ALPN Protocol
ALPN string length: 2
ALPN Next Protocol: h2
ALPN string length: 8
ALPN Next Protocol: http/1.1
Extension: status_request (len=5)
Extension: signature_algorithms (len=20)
Extension: signed_certificate_timestamp (len=0)
Extension: key_share (len=43)
Extension: psk_key_exchange_modes (len=2)
Extension: supported_versions (len=11)
Type: supported_versions (43)
Length: 11
Supported Versions length: 10
Supported Version: Unknown (0x6a6a)
Supported Version: TLS 1.3 (0x0304)
Supported Version: TLS 1.2 (0x0303)
Supported Version: TLS 1.1 (0x0302)
Supported Version: TLS 1.0 (0x0301)
Extension: compress_certificate (len=3)
Extension: Reserved (GREASE) (len=1)
Extension: padding (len=203)
- これのうち、
application_layer_protocol_negotiation
拡張のALPN Protocol
の部分がほしい
TLS レコードの構造 (ALPN に必要な部分のみ)
- TLS レコードは、レコードレイヤー(ヘッダー)とペイロードから構成される
- 冒頭の
Content Type
,Version
,Length
が TLS レコードレイヤーであるContent Type
: ペイロードの種類を示すVersion
: TLS バージョンを示す。旧形式であり、TLS 1.0 以上は0x301
(すなわち SSL 3.1) 固定。Length
: ペイロード長
- 冒頭の
- TLS のデータは、複数のレコードに分割可能である
- OpenSSL には以前 ClientHello を分割送信することで TLS 1.0 にダウングレードできてしまう脆弱性があった(CVE-2014-3511)
- Handshake 型のうち、
Handshake Type
,Length
がヘッダーで、残りはデータとなるHandshake Type
: ClientHello や ServerHello と言ったタイプを示すLength
: Handshake ペイロード長
- ClientHello 型のうち、
Version
,Session ID
,Cipher Suites
,Compression Methods
,Extensions Length
がデータとなり、残りは任意の数の拡張であるSession ID
等の項目の前に長さが付随するのは、任意の長さのデータ項目なので先に長さを読み取る必要があるため- ここの
Version
は TLS 1.2 までの旧形式であり、TLS 1.2 以上は0x303
(すなわち SSL 3.3) 固定- TLS1.3 以降は
supported_versions
拡張を用いる
- TLS1.3 以降は
Extensions Length
が拡張全体の長さを示す
- 拡張型のうち、
Type
,Length
がヘッダーとなり、残りはデータとなるType
: 拡張の型を示すLength
: 拡張データ長を示す
application_layer_protocol_negotiation
は、任意の長さの ALPN Protocol 文字列配列を持つ- この拡張自体のデータ長は
Length
で、各配列内の文字列の長さはALPN string length
で分かる - そのため、
ALPN Extension Length
の存在価値が不明。誰か知ってたら教えて欲しい。
- この拡張自体のデータ長は
証明書の生成・ストア・キャッシュをカスタマイズ可能にするための API 設計
- Nekoxy2 の既定は以下の通り
- 証明書生成ロジックは BouncyCastle
- ルート証明書のインストールやサーバー証明書キャッシュに利用するストアは .NET の X509Store
- サーバー証明書キャッシュはストア内の個人
- これらはライブラリ利用者によっては変更したい場合があると考えたため、カスタマイズ可能な設計にした
- ルート証明書は、用意されているユーティリティ(
CertificateUtil
)を利用して生成するか、自身で用意したものを設定する- ルート証明書を検索するロジックは、
RootCertificateResolver
プロパティで変更可能
- ルート証明書を検索するロジックは、
- 証明書生成ロジックは
ICertificateFactory
インターフェイスを実装して置換可能 - ストアは
ICertificateStore
インターフェイスを実装して置換可能 - サーバー証明書キャッシュの保存場所も変更可能
CacheLocations
プロパティにて、メモリ上、ストア上、カスタム場所の3箇所の保存場所を選択できる- 保存は、指定された場所全てに行われる
- カスタム場所の場合、リクエスト時に自動生成されたサーバー証明書が
ServerCertificateCreated
イベントで通知されるのでそれを利用
- カスタム場所の場合、リクエスト時に自動生成されたサーバー証明書が
- 読み取りは、指定された順に探索する
- カスタム場所での検索ロジックは、
ServerCertificateCacheResolver
プロパティに実装する
HostFilterHandler
プロパティにより、復号化対象のホストを絞り込める