学习:《A Guide to Parsing QUIC Client Hellos for Network Middlebox Vendors》Parsing QUIC Client Hellos - The Chromium Projects
Parsing QUIC Client Hellos
Last updated on 2020-07-31. Abstract
This document is aimed at network middlebox vendors who would like to parse QUIC Client Hellos. The document discusses how best to accomplish that, allowing a network middlebox to parse the QUIC version, server connection ID, and SNI (server name indication), if present. Note that this document only covers packets sent from the client to a server, and does not apply to any packet going in the other direction.
本文档针对希望解析QUIC Client Hello的网络中间盒供应商。 该文档讨论了如何最好地做到这一点,允许网络中间盒解析QUIC版本,服务器连接ID和SNI(服务器名称指示)(如果存在)。 请注意,本文档仅涵盖从客户端发送到服务器的数据包,不适用于另一个方向的数据包。It is important to note that the QUIC protocol is still actively under development, so its internal details may change over time. If you maintain a QUIC parser, please make sure to join the free public mailing list <
proto-quic@chromium.org> to ensure you are made aware of changes. If you have questions or feedback on this document, please email that list.
重要的是要注意,QUIC协议仍在积极开发中,因此其内部细节可能会随着时间而改变。 如果您维护QUIC解析器,请确保加入免费的公共邮件列表<
proto-quic@chromium.org>,以确保您了解更改。 如果您对此文档有疑问或反馈,请通过电子邮件发送该列表。Important Security Caveats Regarding SNI in QUIC
关于QUIC中SNI的重要安全警告This document describes how one might extract the SNI from QUIC traffic. However, QUIC differs significantly from TLS+TCP in ways that impact security properties.
本文描述了如何从QUIC通信中提取SNI。 但是,QUIC与TLS + TCP的不同之处在于影响安全性的方式。In TLS+TCP, a given TLS session is always bound to an underlying TCP connection. For the lifetime of that session, the 5-tuple (IP protocol, IP source and destination addresses, and TCP source and destination ports) will not change. Therefore once the SNI has been extracted, it can be saved next to the 5-tuple so that any subsequent packets can be tied to that SNI.
在TLS + TCP中,给定的TLS会话始终绑定到基础TCP连接。 在该会话的生命周期内,5元组(IP协议,IP源和目标地址以及TCP源和目标端口)将保持不变。 因此,一旦提取了SNI,就可以将其保存在5元组旁边,以便任何后续数据包都可以绑定到该SNI。This is not true when using QUIC. With QUIC, the 5-tuple can change mid-connection. For example, if a QUIC client thinks that its packets are being blackholed, it might switch to a different source port and keep sending on the same connection with the new port. QUIC connections can also migrate across networks, meaning that a QUIC connection could have performed its handshake over Wi-Fi, and then migrated to cellular as the user moved out of their house. When this happens, a network middlebox in the cellular network will not be able to extract the SNI for that flow. A QUIC connection can also change its connection ID mid-connection, so saving the SNI next to the connection ID will not allow a network middlebox to accurately map all packets to that SNI.
使用QUIC时,情况并非如此。 使用QUIC,5元组可以更改中间连接。 例如,如果QUIC客户端认为其数据包被黑洞了,则它可能会切换到其他源端口,并继续与新端口进行相同的连接发送。 QUIC连接还可以跨网络迁移,这意味着QUIC连接可以通过Wi-Fi进行握手,然后在用户离开家时迁移到蜂窝网络。 发生这种情况时,蜂窝网络中的网络中间盒将无法提取该流的SNI。 QUIC连接还可以在连接中间更改其连接ID,因此将SNI保存在连接ID旁边将使网络中间盒无法将所有数据包准确地映射到该SNI。If you are using SNI in TLS+TCP today for security reasons (access control via allowlists and/or blocklists, or to differentiate how to bill traffic based on the SNI), then simply adding the ability to parse SNI in QUIC could lead to security vulnerabilities: the differences in QUIC described above could be used to evade these security properties.
如果出于安全原因(如果您今天在TLS + TCP中使用SNI(通过允许列表和/或阻止列表进行访问控制,或者根据SNI区分流量计费方式)),那么只需在QUIC中添加解析SNI的功能就可以提高安全性。 漏洞:上述QUIC中的差异可用于规避这些安全属性。Notational Conventions
符号约定In this document, we use the following conventions:
array[i] – one byte at index i of arrayarray[i:j] – subset of array starting with index i (inclusive) up to j-1 (inclusive)array[i:] – subset of array starting with index i (inclusive) up to the end of the array在本文档中,我们使用以下约定:
array [i] –数组索引i处的一个字节
array [i:j] –数组的子集,从索引i(含)开始,直至j-1(含)
array [i:] –数组的子集,从索引i(含)开始,一直到数组的末尾A Note about QUIC Versions
关于QUIC版本的注释Conceptually, a QUIC version is an opaque 32bit field. When we refer to a version with four printable characters, we use its ASCII representation: for example, Q050 refers to {'Q', '0', '5', '0'} which is equal to {0x51, 0x30, 0x35, 0x30}. Otherwise, we use its hexadecimal representation: for example, 0xff00001d refers to {0xff, 0x00, 0x00, 0x1d}.
从概念上讲,QUIC版本是不透明的32位字段。 当我们引用具有四个可打印字符的版本时,我们使用其ASCII表示形式:例如,Q050引用的{{Q','0','5','0'}等于{0x51、0x30、0x35 ,0x30}。 否则,我们将使用其十六进制表示形式:例如,0xff00001d表示{0xff,0x00、0x00、0x1d}。QUIC versions that start with 0xff are IETF drafts, they are fully specified in IETF documents. In particular, 0xff00001d is documented by <
https://tools.ietf.org/html/draft-ietf-quic-transport-29> and 0xff00001b is documented by <
https://tools.ietf.org/html/draft-ietf-quic-transport-27>. QUIC versions that start with 0x0000 are reserved for IETF consensus documents, for example the IETF QUIC RFC is expected to use version 0x00000001.
以0xff开头的QUIC版本是IETF草案,在IETF文档中有完整说明。 特别是,<
QUIC: A UDP-Based Multiplexed and Secure Transport>记录了0xff00001d,而<
https://tools.ietf.org/html/draft- ietf-quic-transport-27>。 以0x0000开头的QUIC版本保留用于IETF共识文档,例如,IETF QUIC RFC预期使用版本0x00000001。QUIC versions that start with 'Q' or 'T' followed by three digits are GoogleQUIC versions. Versions up to and including 43 are documented by <
https://docs.google.com/document/d/1WJvyZflAO2pq77yOLbp9NsGjC1CHetAXV8I0fQe-B_U/preview>. Versions Q046, Q050, T050, and T051 are not fully documented, but this document should contain enough information to allow parsing Client Hellos for those versions.
以“ Q”或“ T”开头,后跟三个数字的QUIC版本是GoogleQUIC版本。 <
QUIC Wire Layout Specification>记录了43个以下的版本。 版本Q046,Q050,T050和T051没有完整记录,但是此文档应包含足够的信息以允许为这些版本解析Client Hello。Identifying the QUIC Version of a Packet
识别数据包的QUIC版本This sections contains an algorithm that allows parsing versions from both GoogleQUIC and IETF QUIC, and will become irrelevant once we fully deploy IETF QUIC and deprecate Google QUIC.
本部分包含一种算法,该算法允许解析GoogleQUIC和IETF QUIC的版本,一旦我们完全部署IETF QUIC并弃用Google QUIC,该算法将不再相关。When parsing a packet, the first step is to figure out which QUIC version it uses. First, look at the first byte of the QUIC packet, in other words the first byte of the UDP payload.
解析数据包时,第一步是弄清楚它使用哪个QUIC版本。 首先,查看QUIC数据包的第一个字节,即UDP负载的第一个字节。 first_byte = packet[0]
first_byte_bit1 = ((first_byte & 0x80) != 0)
first_byte_bit2 = ((first_byte & 0x40) != 0)
first_byte_bit3 = ((first_byte & 0x20) != 0)
first_byte_bit4 = ((first_byte & 0x10) != 0)
first_byte_bit5 = ((first_byte & 0x08) != 0)
first_byte_bit6 = ((first_byte & 0x04) != 0)
first_byte_bit7 = ((first_byte & 0x02) != 0)
first_byte_bit8 = ((first_byte & 0x01) != 0)
if (first_byte_bit1) {
version = packet[1:5]
} else if (first_byte_bit5 && !first_byte_bit2) {
if (!first_byte_bit8) {
abort("Packet without version")
}
if (first_byte_bit5) {
version = packet[9:13]
} else {
version = packet[5:9]
}
} else {
abort("Packet without version")
}Extracting the Server Connection ID
提取服务器连接IDNow that we know which version is in use, we can extract the server connection ID. It's important to note that the server connection ID is a variable-length field. We commonly use 64bit connection IDs today but some servers use different lengths.
现在我们知道正在使用哪个版本,我们可以提取服务器连接ID。 请务必注意,服务器连接ID是一个可变长度字段。 今天,我们通常使用64位连接ID,但是某些服务器使用不同的长度。 if (version == Q043) {
if (!first_byte_bit5) {
abort("Packet without server connection ID")
}
server_connection_id_length = 8
server_connection_id = packet[1:9]
} else if (version == Q046) {
if (packet[5] != 0x80) {
abort("Unexpected connection ID length")
}
server_connection_id_length = 8
server_connection_id = packet[6:14]
} else {
server_connection_id_length = packet[5]
server_connection_id = packet[6:6+server_connection_id_length]
}Extracting the Payload from Initial Packets
从初始数据包中提取有效载荷Some versions of QUIC encrypt their initial packets. This applies to all currently supported versions, except for Q043 and Q046.
某些QUIC版本会加密其初始数据包。 这适用于当前所有受支持的版本,但Q043和Q046除外。 if (version == Q043 || version == Q046) {
// Skip decryption because initial packet is not encrypted
if (version == Q043) {
if (first_byte_bit3 != 0 || first_byte_bit4 != 0) {
abort("Unexpected packet number length")
}
payload = packet[26:]
} else { // version == Q046
if (first_byte_bit7 != 0 || first_byte_bit8 != 0) {
abort("Unexpected packet number length")
}
payload = packet[30:]
}
} else {
if (first_byte_bit3 != 0 || first_byte_bit4 != 0) {
// Non-initial packets are encrypted with keys not available
// to anyone except the client and server.
abort("Not an initial packet")
}
if (version == Q050) {
initial_salt = {0x50, 0x45, 0x74, 0xef, 0xd0, 0x66, 0xfe,
0x2f, 0x9d, 0x94, 0x5c, 0xfc, 0xdb, 0xd3,
0xa7, 0xf0, 0xd3, 0xb5, 0x6b, 0x45}
} else if (version == T050) {
initial_salt = {0x7f, 0xf5, 0x79, 0xe5, 0xac, 0xd0, 0x72,
0x91, 0x55, 0x80, 0x30, 0x4c, 0x43, 0xa2,
0x36, 0x7c, 0x60, 0x48, 0x83, 0x10}
} else if (version == T051) {
initial_salt = {0x7a, 0x4e, 0xde, 0xf4, 0xe7, 0xcc, 0xee,
0x5f, 0xa4, 0x50, 0x6c, 0x19, 0x12, 0x4f,
0xc8, 0xcc, 0xda, 0x6e, 0x03, 0x3d}
} else if (version == 0xff00001b) {
initial_salt = {0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb,
0x5a, 0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4,
0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02}
} else if (version == 0xff00001d) {
initial_salt = {0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2,
0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61,
0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
} else {
abort("Unknown version")
}
// Decrypt the payload according to the IETF specification (see below)
payload = Decrypt(packet, initial_salt)
}
To decrypt the packet using the initial_salt, follow the IETF specification in the Packet Protection Section of draft-ietf-quic-tls-29:
要使用initial_salt解密数据包,请遵循draft-ietf-quic-tls-29的Packet Protection部分中的IETF规范:https://tools.ietf.org/html/draft-ietf-quic-tls-29#section-5
(except that you'll use the initial_salt from above instead of the one from that specification).
(除了您将使用上面的initial_salt而不是该规范中的那个)。Extracting Crypto Data from the Payload
从有效载荷中提取加密数据QUIC payloads contain any number of QUIC frames. The Client Hello is contained in what we call the CRYPTO stream, and accessing its crypto data depends on the version. Your parser needs to iterate over the payload and skip all non-CRYPTO-stream frames. In particular, skip over any zero-bytes at the start of the payload as they indicate padding. The algorithm below does not implement a full parser, but should work well enough for the majority of QUIC traffic.
QUIC有效载荷包含任意数量的QUIC帧。 客户端Hello包含在我们所谓的CRYPTO流中,访问其加密数据取决于版本。 您的解析器需要遍历有效负载,并跳过所有非CRYPTO流帧。 特别是,请跳过有效载荷开始处的任何零字节,因为它们表示填充。 以下算法无法实现完整的解析器,但对于大多数QUIC流量而言,应该可以很好地工作。 counter = 0
while (payload[counter] == 0) {
counter += 1
}
first_nonzero_payload_byte = payload[counter]
fnz_payload_byte_bit3 = ((first_nonzero_payload_byte & 0x20) != 0)
if (version == Q043 || version == Q046) {
if ((first_nonzero_payload_byte & 0x9f) != 0) {
abort("Unexpected frame")
}
if (payload[counter+1] != 0x01) {
abort("Unexpected stream ID")
}
counter += 2
if (fnz_payload_byte_bit3) {
stream_data_length = payload[counter:counter+2]
crypto_data = payload[counter+2:counter+2+stream_data_length]
} else {
crypto_data = payload[counter:]
}
} else if (version == Q050 || version == T050 || version == T051) {
if (first_nonzero_payload_byte != 0x08) {
abort("Unexpected frame")
}
if (payload[counter+1] != 0x00) {
abort("Unexpected crypto stream offset")
}
counter += 2
if ((payload[counter] & 0xc0) == 0) {
crypto_data_length = payload[counter]
counter += 1
} else {
crypto_data_length = payload[counter:counter+2]
counter += 2
}
crypto_data = payload[counter:counter+crypto_data_length]
} else { // All other versions
if (first_nonzero_payload_byte != 0x06) {
abort("Unexpected frame")
}
if (payload[counter+1] != 0x00) {
abort("Unexpected crypto stream offset")
}
counter += 2
if ((payload[counter] & 0xc0) == 0) {
crypto_data_length = payload[counter]
counter += 1
} else {
crypto_data_length = payload[counter:counter+2]
counter += 2
}
crypto_data = payload[counter:counter+crypto_data_length]
}Extracting the Client Hello from the Crypto Data
从加密数据中提取客户端HelloThe Client Hello has a different format depending on the version in use. Versions that start with a Q (such as Q043, Q046 and Q050) use the QUIC Crypto format. Other versions use the TLS format.
客户端Hello具有不同的格式,具体取决于使用的版本。 以Q开头的版本(例如Q043,Q046和Q050)使用QUIC加密格式。 其他版本使用TLS格式。 if (version == Q043 || version == Q046 || version == Q050) {
if (crypto_data[0:4] != "CHLO") {
abort("Unexpected handshake message")
src="https://pic1.zhimg.com/v2-0b997df27344d294574db0b7e1722ba0_720w.jpg?source=b555e01d">
英特尔(Intel)哈迪斯冥王峡谷NUC8i7HVK src="https://pica.zhimg.com/v2-ad5b41c820626e86974ca9fdda3d42da_720w.jpg?source=b555e01d">
英特尔(Intel)幽灵峡谷NUC9I9QNX src="https://pic3.zhimg.com/v2-996cf243507e6a5b83728d47fc79842d_720w.jpg?source=b555e01d">
越玩越聪明的数独(升级版 src="https://zhihu-live.zhimg.com/namespace_1001/static/20210302/241545a9b1f94a0a9673708a20912fd1.png">
2
HTTP权威指南(图灵出品)¥ src="https://zhihu-live.zhimg.com/namespace_1001/static/20210302/241545a9b1f94a0a9673708a20912fd1.png">
2
HTTP/2基础教程(图灵出品)¥ src="https://zhihu-live.zhimg.com/namespace_1001/static/20210302/241545a9b1f94a0a9673708a20912fd1.png">
2
从实践中学习Wireshark数据分析¥ src="https://zhihu-live.zhimg.com/namespace_1001/static/20210302/241545a9b1f94a0a9673708a20912fd1.png">
2
计算机组网及Wireshark实验教程¥ src="https://zhihu-live.zhimg.com/namespace_1001/static/20210302/241545a9b1f94a0a9673708a20912fd1.png">
2
手机下载的《博雅四川麻将》不会玩。有几个疑问想问下博彩问答,就是别人打出来的牌不能胡么?既只能自摸?是不是只能用二五八做将博彩问答,其他的不可以?可是问题是我刚才是二万做的将,令的六九万...