如何检测传入的SSL(HTTPS)握手(SSL线格式)?
题
我正在写一台正在接受传入的TCP连接的服务器。我们假设服务器已经接受了TCP连接,并且已经从客户端接收了16个(左右)的字节。知道那些16个字节服务器如何检测客户端是否要启动SSL握手?
我已经进行了一个实验,该实验表明,在我的Linux系统上连接到Localhost(127.0.0.1或AF_UNIX)通过SSL使客户端发送以下握手(HexDump),然后是16个看似随机的字节:
8064010301004b0000001000003900003800003500001600001300000a07
00c000003300003200002f03008000000500000401008000001500001200
0009060040000014000011000008000006040080000003020080
服务器应该如何探测前几个字节,以便能够确定客户端是否发送SSL握手?该探针必须返回所有有效的SSL握手,并且必须返回false,对于客户发送的消息的可能性很高,而不是SSL握手。不允许使用任何库(例如OpenSSL)进行探针。探针必须是一个简单的代码(例如C或Python中的几十行)。
解决方案 2
我可以根据实施 ClientHello.parse
方法INhttp://tlslite.cvs.sourceforge.net/viewvc/tlslite/tlslite/tlslite/tlslite/messages.py?view=markup
我在Python提供了两种解决方案。 IsSSlClientHandshakeSimple
是一个简单的REGEXP,可以很容易地产生一些误报; 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
其他提示
客户端始终首先发送所谓的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.
+-----------------|------+-------
B [0]&0x80 == 0x80(这意味着B [0]的最重要位是'1')
((b [0]&0x7f)<< 8 | b [1])> 9(它含量为B [0]的低7位与B [1]一起是数据长度。您的缓冲区中可以更少有,因此您无法检查它们。但是,从消息格式中,我们知道有3个字段为2个字节(长度字段),并且在密码列表字段中至少有一项(尺寸3)。因此,应该至少有9个字节(数据长度> = 9)。
b [2]必须为0x01(消息类型“客户端hello”)
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