Как определить входящее SSL (https) рукопожатие (формат проводов SSL)?

Я пишу сервер, который принимает входящие TCP-соединения. Предположим, что сервер принял TCP-соединение и уже получил 16 (или так) байтов от клиента. Зная эти 16 байт, как сервер может определить, хочет ли клиент инициировать SSL-квитирование?

Я сделал эксперимент, который показал, что на моей Linux-системе, подключающейся к localhost (127.0.0.1 или AF_UNIX) через SSL, клиент отправляет следующее рукопожатие (hexdump), за которым следуют 16 кажущихся случайными байты:

8064010301004b0000001000003900003800003500001600001300000a07
00c000003300003200002f03008000000500000401008000001500001200
0009060040000014000011000008000006040080000003020080

Как сервер должен проверять эти первые несколько байтов, только чтобы определить, отправляет ли клиент SSL-квитирование? Зонд должен возвращать значение true для всех действительных SSL-кодов, и он должен с высокой вероятностью возвращать значение false для сообщения, отправленного клиентом, который не является квитированием SSL. Запрещается использовать любые библиотеки (например, OpenSSL) для зонда. Зонд должен быть простым кодом (например, несколько десятков строк на C или Python).

Ответы

Ответ 1

Я мог бы понять это на основе реализации метода ClientHello.parse в http://tlslite.cvs.sourceforge.net/viewvc/tlslite/tlslite/tlslite/messages.py?view=markup

Я предлагаю два решения здесь в Python. IsSSlClientHandshakeSimple - простое регулярное выражение, которое может дать некоторые ложные срабатывания довольно легко; IsSslClientHandshake сложнее: он проверяет согласованность длин и диапазон некоторых других чисел.

import re

def IsSslClientHandshakeSimple(buf):
  return bool(re.match(r'(?s)\A(?:\x80[\x0f-\xff]\x01[\x00-\x09][\x00-\x1f]'
                       r'[\x00-\x05].\x00.\x00.|'
                       r'\x16[\x2c-\xff]\x01\x00[\x00-\x05].'
                       r'[\x00-\x09][\x00-\x1f])', buf))

def IsSslClientHandshake(buf):
  if len(buf) < 2:  # Missing record header.
    return False
  if len(buf) < 2 + ord(buf[1]):  # Incomplete record body.
    return False
  # TODO(pts): Support two-byte lengths in buf[1].
  if ord(buf[0]) == 0x80:  # SSL v2.
    if ord(buf[1]) < 9:  # Message body too short.
      return False
    if ord(buf[2]) != 0x01:  # Not client_hello.
      return False
    if ord(buf[3]) > 9:  # Client major version too large. (Good: 0x03)
      return False
    if ord(buf[4]) > 31:  # Client minor version too large. (Good: 0x01)
      return False
    cipher_specs_size = ord(buf[5]) << 8 | ord(buf[6])
    session_id_size = ord(buf[7]) << 8 | ord(buf[8])
    random_size = ord(buf[9]) << 8 | ord(buf[10])
    if ord(buf[1]) < 9 + cipher_specs_size + session_id_size + random_size:
      return False
    if cipher_specs_size % 3 != 0:  # Cipher specs not a multiple of 3 bytes.
      return False
  elif ord(buf[0]) == 0x16:  # SSL v1.
    # TODO(pts): Test this.
    if ord(buf[1]) < 39:  # Message body too short.
      return False
    if ord(buf[2]) != 0x01:  # Not client_hello.
      return False
    head_size = ord(buf[3]) << 16 | ord(buf[4]) << 8 | ord(buf[5])
    if ord(buf[1]) < head_size + 4:  # Head doesn't fit in message body.
      return False
    if ord(buf[6]) > 9:  # Client major version too large. (Good: 0x03)
      return False
    if ord(buf[7]) > 31:  # Client minor version too large. (Good: 0x01)
      return False
    # random is at buf[8 : 40]
    session_id_size = ord(buf[40])
    i = 41 + session_id_size
    if ord(buf[1]) < i + 2:  # session_id + cipher_suites_size doesn't fit.
      return False
    cipher_specs_size = ord(buf[i]) << 8 | ord(buf[i + 1])
    if cipher_specs_size % 2 != 0:
      return False
    i += 2 + cipher_specs_size
    if ord(buf[1]) < i + 1: # cipher_specs + c..._methods_size doesn't fit.
      return False
    if ord(buf[1]) < i + 1 + ord(buf[i]): # compression_methods doesn't fit.
      return False
  else:  # Not SSL v1 or SSL v2.
    return False
return True

Ответ 2

Клиент всегда отправляет так называемое сообщение HelloClient. Он может быть в формате SSL 2 или в формате SSL 3.0 (тот же формат, что и в TLS 1.0, 1.1 и 1.2).

Также существует вероятность того, что клиенты SSL 3.0/TLS 1.0/1.1/1.2 отправят HelloClient с более старым форматом (SSL 2), только с более высоким номером версии в данных. Таким образом, обнаружение SSL 2 HelloClient необходимо для более новых клиентов. (Например, реализация Java SSL делает это)

Скажем, 'b' - ваш буфер. Я попытался отобразить формат сообщения.

SSL 2

+-----------------+------+-------
| 2 byte header   | 0x01 | etc.
+-----------------|------+-------
  • b [0] и 0x80 == 0x80 (это означает, что самый старший бит b [0] равен '1')

  • ((b [0] и 0x7f) < 8 | b [1]) > 9 (Он обозначает минимальные 7 бит b [0] вместе с b [1] - это длина данных Вы можете иметь меньше в своем буфере, поэтому вы не можете их проверить, но из формата сообщения мы знаем, что есть 3 поля по 2 байта (поля длины) и по крайней мере один элемент в поле списка шифров (размером 3). должно быть не менее 9 байтов (длина данных >= 9).

  • b [2] должно быть 0x01 (тип сообщения "ClientHello" )

SSL 3.0 или TLS 1.0, 1.1 и 1.2

+-------+------------------+------------------+--------+------
| 0x16  | 2 bytes version  |  2 bytes length  |  0x01  |  etc.
+-------+------------------+------------------+--------+------
  • b [0] == 0x16 (тип сообщения "SSL-квитирование" )

  • b [1] должен быть 0x03 (в настоящее время самая новая версия, но кто знает в будущем?)

  • b [5] должно быть 0x01 (сообщение протокола подтверждения связи "HelloClient" )

Для справки вы можете увидеть http://www.mozilla.org/projects/security/pki/nss/ssl/draft02.html и http://tools.ietf.org/html/rfc4346