Skip to content

Commit d63f54e

Browse files
kayoch1ngpotter2
andauthored
Begin implementing QUIC / QUIC TP Parameters (#4614)
* Add QUIC transport parameter to tls extensions * Use objects from the typing module and update testcases for a better coverage * Merge with beggining of QUIC implementation. --------- Co-authored-by: gpotter2 <[email protected]>
1 parent 20a3468 commit d63f54e

File tree

6 files changed

+683
-2
lines changed

6 files changed

+683
-2
lines changed

scapy/fields.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1696,7 +1696,7 @@ def __init__(
16961696
cbk(pkt:Packet,
16971697
lst:List[Packet],
16981698
cur:Optional[Packet],
1699-
remain:str
1699+
remain:bytes,
17001700
) -> Optional[Type[Packet]]
17011701
17021702
The pkt argument contains a reference to the Packet instance

scapy/layers/quic.py

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
# This file is part of Scapy
3+
# See https://scapy.net/ for more information
4+
# Copyright (C) Gabriel Potter <gabriel[]potter[]fr>
5+
6+
"""
7+
QUIC
8+
9+
The draft of a very basic implementation of the structures from [RFC 9000].
10+
This isn't binded to UDP by default as currently too incomplete.
11+
12+
TODO:
13+
- payloads.
14+
- encryption.
15+
- automaton.
16+
- etc.
17+
"""
18+
19+
import struct
20+
21+
from scapy.packet import (
22+
Packet,
23+
)
24+
from scapy.fields import (
25+
_EnumField,
26+
BitEnumField,
27+
BitField,
28+
ByteEnumField,
29+
ByteField,
30+
EnumField,
31+
Field,
32+
FieldLenField,
33+
FieldListField,
34+
IntField,
35+
MultipleTypeField,
36+
ShortField,
37+
StrLenField,
38+
)
39+
40+
# Typing imports
41+
from typing import (
42+
Any,
43+
Optional,
44+
Tuple,
45+
)
46+
47+
# RFC9000 table 3
48+
_quic_payloads = {
49+
0x00: "PADDING",
50+
0x01: "PING",
51+
0x02: "ACK",
52+
0x04: "RESET_STREAM",
53+
0x05: "STOP_SENDING",
54+
0x06: "CRYPTO",
55+
0x07: "NEW_TOKEN",
56+
0x08: "STREAM",
57+
0x10: "MAX_DATA",
58+
0x11: "MAX_STREAM_DATA",
59+
0x12: "MAX_STREAMS",
60+
0x14: "DATA_BLOCKED",
61+
0x15: "STREAM_DATA_BLOCKED",
62+
0x16: "STREAMS_BLOCKED",
63+
0x18: "NEW_CONNECTION_ID",
64+
0x19: "RETIRE_CONNECTION_ID",
65+
0x1A: "PATH_CHALLENGE",
66+
0x1B: "PATH_RESPONSE",
67+
0x1C: "CONNECTION_CLOSE",
68+
0x1E: "HANDSHAKE_DONE",
69+
}
70+
71+
72+
# RFC9000 sect 16
73+
class QuicVarIntField(Field[int, int]):
74+
def addfield(self, pkt: Packet, s: bytes, val: Optional[int]):
75+
val = self.i2m(pkt, val)
76+
if val < 0 or val > 0x3FFFFFFFFFFFFFFF:
77+
raise struct.error("requires 0 <= number <= 4611686018427387903")
78+
if val < 0x40:
79+
return s + struct.pack("!B", val)
80+
elif val < 0x4000:
81+
return s + struct.pack("!H", val | 0x4000)
82+
elif val < 0x40000000:
83+
return s + struct.pack("!I", val | 0x40000000)
84+
else:
85+
return s + struct.pack("!Q", val | 0x4000000000000000)
86+
87+
def getfield(self, pkt: Packet, s: bytes) -> Tuple[bytes, int]:
88+
length = (s[0] & 0xC0) >> 6
89+
if length == 0:
90+
return s[1:], struct.unpack("!B", s[:1])[0] & 0x3F
91+
elif length == 1:
92+
return s[2:], struct.unpack("!H", s[:2])[0] & 0x3FFF
93+
elif length == 2:
94+
return s[4:], struct.unpack("!I", s[:4])[0] & 0x3FFFFFFF
95+
elif length == 3:
96+
return s[8:], struct.unpack("!Q", s[:8])[0] & 0x3FFFFFFFFFFFFFFF
97+
else:
98+
raise Exception("Impossible.")
99+
100+
101+
class QuicVarLenField(FieldLenField, QuicVarIntField):
102+
pass
103+
104+
105+
class QuicVarEnumField(QuicVarIntField, _EnumField[int]):
106+
__slots__ = EnumField.__slots__
107+
108+
def __init__(self, name, default, enum):
109+
# type: (str, Optional[int], Any, int) -> None
110+
_EnumField.__init__(self, name, default, enum) # type: ignore
111+
QuicVarIntField.__init__(self, name, default)
112+
113+
def any2i(self, pkt, x):
114+
# type: (Optional[Packet], Any) -> int
115+
return _EnumField.any2i(self, pkt, x) # type: ignore
116+
117+
def i2repr(
118+
self,
119+
pkt, # type: Optional[Packet]
120+
x, # type: int
121+
):
122+
# type: (...) -> Any
123+
return _EnumField.i2repr(self, pkt, x)
124+
125+
126+
# -- Headers --
127+
128+
129+
# RFC9000 sect 17.2
130+
_quic_long_hdr = {
131+
0: "Short",
132+
1: "Long",
133+
}
134+
135+
_quic_long_pkttyp = {
136+
# RFC9000 table 5
137+
0x00: "Initial",
138+
0x01: "0-RTT",
139+
0x02: "Handshake",
140+
0x03: "Retry",
141+
}
142+
143+
# RFC9000 sect 17 abstraction
144+
145+
146+
class QUIC(Packet):
147+
match_subclass = True
148+
149+
@classmethod
150+
def dispatch_hook(cls, _pkt=None, *args, **kargs):
151+
"""
152+
Returns the right class for the given data.
153+
"""
154+
if _pkt:
155+
hdr = _pkt[0]
156+
if hdr & 0x80:
157+
# Long Header packets
158+
if hdr & 0x40 == 0:
159+
return QUIC_Version
160+
else:
161+
typ = (hdr & 0x30) >> 4
162+
return {
163+
0: QUIC_Initial,
164+
1: QUIC_0RTT,
165+
2: QUIC_Handshake,
166+
3: QUIC_Retry,
167+
}[typ]
168+
else:
169+
# Short Header packets
170+
return QUIC_1RTT
171+
return QUIC_Initial
172+
173+
def mysummary(self):
174+
return self.name
175+
176+
177+
# RFC9000 sect 17.2.1
178+
179+
180+
class QUIC_Version(QUIC):
181+
name = "QUIC - Version Negotiation"
182+
fields_desc = [
183+
BitEnumField("HeaderForm", 1, 1, _quic_long_hdr),
184+
BitField("Unused", 0, 7),
185+
IntField("Version", 0),
186+
FieldLenField("DstConnIDLen", None, length_of="DstConnID", fmt="B"),
187+
StrLenField("DstConnID", "", length_from=lambda pkt: pkt.DstConnIDLen),
188+
FieldLenField("SrcConnIDLen", None, length_of="DstConnID", fmt="B"),
189+
StrLenField("SrcConnID", "", length_from=lambda pkt: pkt.SrcConnIDLen),
190+
FieldListField("SupportedVersions", [], IntField("", 0)),
191+
]
192+
193+
194+
# RFC9000 sect 17.2.2
195+
196+
QuicPacketNumberField = lambda name, default: MultipleTypeField(
197+
[
198+
(ByteField(name, default), lambda pkt: pkt.PacketNumberLen == 0),
199+
(ShortField(name, default), lambda pkt: pkt.PacketNumberLen == 1),
200+
(IntField(name, default), lambda pkt: pkt.PacketNumberLen == 2),
201+
],
202+
ByteField(name, default),
203+
)
204+
205+
206+
class QuicPacketNumberBitFieldLenField(BitField):
207+
def i2m(self, pkt, x):
208+
if x is None and pkt is not None:
209+
PacketNumber = pkt.PacketNumber or 0
210+
if PacketNumber < 0 or PacketNumber > 0xFFFFFFFF:
211+
raise struct.error("requires 0 <= number <= 0xFFFFFFFF")
212+
if PacketNumber < 0x100:
213+
return 0
214+
elif PacketNumber < 0x10000:
215+
return 1
216+
elif PacketNumber < 0x100000000:
217+
return 2
218+
else:
219+
return 3
220+
elif x is None:
221+
return 0
222+
return x
223+
224+
225+
class QUIC_Initial(QUIC):
226+
name = "QUIC - Initial"
227+
Version = 0x00000001
228+
fields_desc = (
229+
[
230+
BitEnumField("HeaderForm", 1, 1, _quic_long_hdr),
231+
BitField("FixedBit", 1, 1),
232+
BitEnumField("LongPacketType", 0, 2, _quic_long_pkttyp),
233+
BitField("Reserved", 0, 2),
234+
QuicPacketNumberBitFieldLenField("PacketNumberLen", None, 2),
235+
]
236+
+ QUIC_Version.fields_desc[2:7]
237+
+ [
238+
QuicVarLenField("TokenLen", None, length_of="Token"),
239+
StrLenField("Token", "", length_from=lambda pkt: pkt.TokenLen),
240+
QuicVarIntField("Length", 0),
241+
QuicPacketNumberField("PacketNumber", 0),
242+
]
243+
)
244+
245+
246+
# RFC9000 sect 17.2.3
247+
class QUIC_0RTT(QUIC):
248+
name = "QUIC - 0-RTT"
249+
LongPacketType = 1
250+
fields_desc = QUIC_Initial.fields_desc[:10] + [
251+
QuicVarIntField("Length", 0),
252+
QuicPacketNumberField("PacketNumber", 0),
253+
]
254+
255+
256+
# RFC9000 sect 17.2.4
257+
class QUIC_Handshake(QUIC):
258+
name = "QUIC - Handshake"
259+
LongPacketType = 2
260+
fields_desc = QUIC_0RTT.fields_desc
261+
262+
263+
# RFC9000 sect 17.2.5
264+
class QUIC_Retry(QUIC):
265+
name = "QUIC - Retry"
266+
LongPacketType = 3
267+
Version = 0x00000001
268+
fields_desc = (
269+
QUIC_Initial.fields_desc[:3]
270+
+ [
271+
BitField("Unused", 0, 4),
272+
]
273+
+ QUIC_Version.fields_desc[2:7]
274+
)
275+
276+
277+
# RFC9000 sect 17.3
278+
class QUIC_1RTT(QUIC):
279+
name = "QUIC - 1-RTT"
280+
fields_desc = [
281+
BitEnumField("HeaderForm", 0, 1, _quic_long_hdr),
282+
BitField("FixedBit", 1, 1),
283+
BitField("SpinBit", 0, 1),
284+
BitField("Reserved", 0, 2),
285+
BitField("KeyPhase", 0, 1),
286+
QuicPacketNumberBitFieldLenField("PacketNumberLen", None, 2),
287+
# FIXME - Destination Connection ID
288+
QuicPacketNumberField("PacketNumber", 0),
289+
]
290+
291+
292+
# RFC9000 sect 19.1
293+
class QUIC_PADDING(Packet):
294+
fields_desc = [
295+
ByteEnumField("Type", 0x00, _quic_payloads),
296+
]
297+
298+
299+
# RFC9000 sect 19.2
300+
class QUIC_PING(Packet):
301+
fields_desc = [
302+
ByteEnumField("Type", 0x01, _quic_payloads),
303+
]
304+
305+
306+
# RFC9000 sect 19.3
307+
class QUIC_ACK(Packet):
308+
fields_desc = [
309+
ByteEnumField("Type", 0x02, _quic_payloads),
310+
]
311+
312+
313+
# Bindings
314+
# bind_bottom_up(UDP, QUIC, dport=443)
315+
# bind_bottom_up(UDP, QUIC, sport=443)
316+
# bind_layers(UDP, QUIC, dport=443, sport=443)

scapy/layers/tls/extensions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from scapy.layers.tls.session import _GenericTLSSessionInheritance
3636
from scapy.layers.tls.crypto.groups import _tls_named_groups
3737
from scapy.layers.tls.crypto.suites import _tls_cipher_suites
38+
from scapy.layers.tls.quic import _QuicTransportParametersField
3839
from scapy.themes import AnsiColorTheme
3940
from scapy.compat import raw
4041
from scapy.config import conf
@@ -93,6 +94,7 @@
9394
0x31: "post_handshake_auth",
9495
0x32: "signature_algorithms_cert",
9596
0x33: "key_share",
97+
0x39: "quic_transport_parameters", # RFC 9000
9698
0x3374: "next_protocol_negotiation",
9799
# RFC-draft-agl-tls-nextprotoneg-03
98100
0xff01: "renegotiation_info", # RFC 5746
@@ -697,6 +699,15 @@ class TLS_Ext_RecordSizeLimit(TLS_Ext_Unknown): # RFC 8449
697699
ShortField("record_size_limit", None)]
698700

699701

702+
class TLS_Ext_QUICTransportParameters(TLS_Ext_Unknown): # RFC9000
703+
name = "TLS Extension - QUIC Transport Parameters"
704+
fields_desc = [ShortEnumField("type", 0x39, _tls_ext),
705+
FieldLenField("len", None, length_of="params"),
706+
_QuicTransportParametersField("params",
707+
None,
708+
length_from=lambda pkt: pkt.len)]
709+
710+
700711
_tls_ext_cls = {0: TLS_Ext_ServerName,
701712
1: TLS_Ext_MaxFragLen,
702713
2: TLS_Ext_ClientCertURL,
@@ -730,6 +741,7 @@ class TLS_Ext_RecordSizeLimit(TLS_Ext_Unknown): # RFC 8449
730741
0x33: TLS_Ext_KeyShare,
731742
# 0x2f: TLS_Ext_CertificateAuthorities, #XXX
732743
# 0x30: TLS_Ext_OIDFilters, #XXX
744+
0x39: TLS_Ext_QUICTransportParameters,
733745
0x3374: TLS_Ext_NPN,
734746
0xff01: TLS_Ext_RenegotiationInfo,
735747
0xffce: TLS_Ext_EncryptedServerName

0 commit comments

Comments
 (0)