如何检测传入的SSL(https)握手(SSL有线格式)?

pts*_*pts 13 ssl protocols handshake

我正在编写一个接受传入TCP连接的服务器.假设服务器已接受TCP连接,并且已从客户端接收到16个(左右)字节.知道那16个字节服务器如何检测客户端是否想要发起SSL握手?

我做了一个实验,它表明在我的Linux系统上通过SSL连接到localhost(127.0.0.1或AF_UNIX)会使客户端发送以下握手(hexdump),然后是16个看似随机的字节:

8064010301004b0000001000003900003800003500001600001300000a07
00c000003300003200002f03008000000500000401008000001500001200
0009060040000014000011000008000006040080000003020080
Run Code Online (Sandbox Code Playgroud)

服务器应如何探测前几个字节,以便能够确定客户端是否正在发送SSL握手?对于所有有效的SSL握手,探测器必须返回true,并且对于非SSL握手的客户端发送的消息,它必须返回false,概率很高.不允许使用任何库(如OpenSSL)进行探测.探针必须是一个简单的代码(如C或Python中的几十行).

Jak*_*kub 17

客户端总是首先发送所谓的HelloClient消息.它可以是SSL 2格式或SSL 3.0格式(与TLS 1.0,1.1和1.2中的格式相同).

并且SSL 3.0/TLS 1.0/1.1/1.2客户端也可能使用旧格式(SSL 2)发送HelloClient,只是数据中的版本号较高.因此,对于较新的客户端,也需要检测SSL 2 HelloClient.(例如Java SSL实现这样做)

假设'b'是你的缓冲区.我试图绘制消息格式.

SSL 2

+-----------------+------+-------
| 2 byte header   | 0x01 | etc.
+-----------------|------+-------
Run Code Online (Sandbox Code Playgroud)
  • b [0]&0x80 == 0x80(表示b [0]的最高有效位为'1')

  • ((b [0]&0x7f)<< 8 | b [1])> 9(b [0]的低7位和b [1]是数据的长度.你的缓冲区可以少一些因此你无法检查它们.但是从消息格式我们知道有2个字节的3个字段(长度字段),以及密码列表字段中的至少一个项目(大小为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.
+-------+------------------+------------------+--------+------
Run Code Online (Sandbox Code Playgroud)
  • b [0] == 0x16(消息类型"SSL握手")

  • b [1]应该是0x03(目前最新的主要版本,但是谁知道将来?)

  • b [5]必须是0x01(握手协议消息"HelloClient")

供参考,您可以查看http://www.mozilla.org/projects/security/pki/nss/ssl/draft02.htmlhttp://tools.ietf.org/html/rfc4346


pts*_*pts 6

我可以根据http://tlslite.cvs.sourceforge.net/viewvc/tlslite/tlslite/tlslite/messages.py?view=markup中的ClientHello.parse方法实现来解决这个问题.

我在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
Run Code Online (Sandbox Code Playgroud)