6月 192019
 
Pocket

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 を解析する必要がある
  • 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 拡張を用いる
    • 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 プロパティにより、復号化対象のホストを絞り込める

 Leave a Reply