15

TLS Client Hello メッセージから Server Name Indication をどのように抽出しますか。私は現在、SNI が定義されている TLS 拡張に関するこの非常に不可解なRFC 3546を理解するのに苦労しています。

私がこれまでに理解したこと:

  • ホストは utf8 でエンコードされており、バッファーを utf8 でエンコードすると読み取り可能になります。
  • ホストの前に 1 バイトがあり、それが長さを決定します。

その長さバイトの正確な位置がわかれば、SNI の抽出は非常に簡単になります。しかし、そもそもそのバイトに到達するにはどうすればよいでしょうか?

4

4 に答える 4

39

私はsniproxyでこれを行い、Wireshark で TLS クライアントの hello パケットを調べながら、RFC がかなり良い方法であることを読みました。それほど難しくはありません。多くの可変長フィールドをスキップして、正しい要素タイプがあるかどうかを確認する必要があるだけです。

私は現在テストに取り組んでおり、役立つかもしれないこの注釈付きのサンプル パケットがあります。

const unsigned char good_data_2[] = {
    // TLS record
    0x16, // Content Type: Handshake
    0x03, 0x01, // Version: TLS 1.0
    0x00, 0x6c, // Length (use for bounds checking)
        // Handshake
        0x01, // Handshake Type: Client Hello
        0x00, 0x00, 0x68, // Length (use for bounds checking)
        0x03, 0x03, // Version: TLS 1.2
        // Random (32 bytes fixed length)
        0xb6, 0xb2, 0x6a, 0xfb, 0x55, 0x5e, 0x03, 0xd5,
        0x65, 0xa3, 0x6a, 0xf0, 0x5e, 0xa5, 0x43, 0x02,
        0x93, 0xb9, 0x59, 0xa7, 0x54, 0xc3, 0xdd, 0x78,
        0x57, 0x58, 0x34, 0xc5, 0x82, 0xfd, 0x53, 0xd1,
        0x00, // Session ID Length (skip past this much)
        0x00, 0x04, // Cipher Suites Length (skip past this much)
            0x00, 0x01, // NULL-MD5
            0x00, 0xff, // RENEGOTIATION INFO SCSV
        0x01, // Compression Methods Length (skip past this much)
            0x00, // NULL
        0x00, 0x3b, // Extensions Length (use for bounds checking)
            // Extension
            0x00, 0x00, // Extension Type: Server Name (check extension type)
            0x00, 0x0e, // Length (use for bounds checking)
            0x00, 0x0c, // Server Name Indication Length
                0x00, // Server Name Type: host_name (check server name type)
                0x00, 0x09, // Length (length of your data)
                // "localhost" (data your after)
                0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74,
            // Extension
            0x00, 0x0d, // Extension Type: Signature Algorithms (check extension type)
            0x00, 0x20, // Length (skip past since this is the wrong extension)
            // Data
            0x00, 0x1e, 0x06, 0x01, 0x06, 0x02, 0x06, 0x03,
            0x05, 0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01,
            0x04, 0x02, 0x04, 0x03, 0x03, 0x01, 0x03, 0x02,
            0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03,
            // Extension
            0x00, 0x0f, // Extension Type: Heart Beat (check extension type)
            0x00, 0x01, // Length (skip past since this is the wrong extension)
            0x01 // Mode: Peer allows to send requests
};
于 2014-02-21T06:32:14.780 に答える
7

WireShark を使用し、フィルターを追加して TLS (SSL) パッケージのみをキャプチャしますtcp port 443。次に、「Client Hello」メッセージを見つけます。その生データを以下に示します。

拡大 すると が表示されます。Handshake パッケージのサーバー名は暗号化されていません。Secure Socket Layer->TLSv1.2 Record Layer: Handshake Protocol: Client Hello->...
Extension: server_name->Server Name Indication extension

http://i.stack.imgur.com/qt0gu.png

于 2015-03-28T15:07:53.860 に答える
3

興味のある方のために説明すると、これは C/C++ コードの暫定版です。これまでのところ機能しています。この関数は、Client Hello を含むバイト配列内のサーバー名の位置と、lenパラメーター内の名前の長さを返します。

char *get_TLS_SNI(unsigned char *bytes, int* len)
{
    unsigned char *curr;
    unsigned char sidlen = bytes[43];
    curr = bytes + 1 + 43 + sidlen;
    unsigned short cslen = ntohs(*(unsigned short*)curr);
    curr += 2 + cslen;
    unsigned char cmplen = *curr;
    curr += 1 + cmplen;
    unsigned char *maxchar = curr + 2 + ntohs(*(unsigned short*)curr);
    curr += 2;
    unsigned short ext_type = 1;
    unsigned short ext_len;
    while(curr < maxchar && ext_type != 0)
    {
        ext_type = ntohs(*(unsigned short*)curr);
        curr += 2;
        ext_len = ntohs(*(unsigned short*)curr);
        curr += 2;
        if(ext_type == 0)
        {
            curr += 3;
            unsigned short namelen = ntohs(*(unsigned short*)curr);
            curr += 2;
            *len = namelen;
            return (char*)curr;
        }
        else curr += ext_len;
    }
    if (curr != maxchar) throw std::exception("incomplete SSL Client Hello");
    return NULL; //SNI was not present
}
于 2016-12-27T03:09:14.487 に答える
2

ドメインの前に常に 2 つのゼロ バイトと 1 つの長さバイトが追加されていることに気付きました。符号なしの 24 ビット整数かもしれませんが、DNS サーバーが 77 文字を超えるドメイン名を許可しないため、テストできません。

その知識に基づいて、この (Node.js) コードを思いつきました。

function getSNI(buf) {
  var sni = null
    , regex = /^(?:[a-z0-9-]+\.)+[a-z]+$/i;
  for(var b = 0, prev, start, end, str; b < buf.length; b++) {
    if(prev === 0 && buf[b] === 0) {
      start = b + 2;
      end   = start + buf[b + 1];
      if(start < end && end < buf.length) {
        str = buf.toString("utf8", start, end);
        if(regex.test(str)) {
          sni = str;
          continue;
        }
      }
    }
    prev = buf[b];
  }
  return sni;
}

このコードは、2 つのゼロ バイトのシーケンスを探します。見つかった場合は、次のバイトが長さパラメーターであると想定します。長さがバッファの境界内にあるかどうかをチェックし、そうである場合は、バイト シーケンスを UTF-8 として読み取ります。後で、配列を正規表現してドメインを抽出できます。

驚くほどうまく機能します!それでも、私は奇妙なことに気づきました。

'�\n�\u0014\u0000�\u0000�\u00009\u00008�\u000f�\u0005\u0000�\u00005�\u0007�\t�\u0011�\u0013\u0000E\u0000D\u0000f\u00003\u00002�\f�\u000e�\u0002�\u0004\u0000�\u0000A\u0000\u0005\u0000\u0004\u0000/�\b�\u0012\u0000\u0016\u0000\u0013�\r�\u0003��\u0000\n'
'\u0000\u0015\u0000\u0000\u0012test.cubixcraft.de'
'test.cubixcraft.de'
'\u0000\b\u0000\u0006\u0000\u0017\u0000\u0018\u0000\u0019'
'\u0000\u0005\u0001\u0000\u0000'

どのサブドメインを選択しても、そのドメインは常に 2 回ターゲットにされます。SNI フィールドが別のフィールド内にネストされているようです。

私は提案と改善を受け入れます!:)

私はこれを、気になるすべての人のために Node モジュールに変えました: sni

于 2013-07-24T11:52:37.710 に答える