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プロパティにより、復号化対象のホストを絞り込める

