Skip to content
5 changes: 5 additions & 0 deletions scapy/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,9 @@ class Conf(ConfClass):
#: When 1, print some TLS session secrets when they are computed, and
#: warn about the session recognition.
debug_tls = False
#: When 1, print some QUIC secrets when they are computed, and
#: warn about the session recognition.
debug_quic = False
wepkey = ""
#: holds the Scapy interface list and manager
ifaces = None # type: 'scapy.interfaces.NetworkInterfaceDict'
Expand Down Expand Up @@ -1138,6 +1141,8 @@ class Conf(ConfClass):
max_list_count = 100
#: When the TLS module is loaded (not by default), the following turns on sessions
tls_session_enable = False
#: When the QUIC module is loaded (not by default), the following turns on sessions
quic_session_enable = False
#: Filename containing NSS Keys Log
tls_nss_filename = Interceptor(
"tls_nss_filename",
Expand Down
13 changes: 13 additions & 0 deletions scapy/layers/quic/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0-only
# This file is part of Scapy
# See https://scapy.net/ for more information

"""
[QUIC] Tools for handling QUIC packets and sessions.

TODO:
- Rework organization of QUIC layers. (e.g., layers for QUIC packet, QUIC payload, QUIC frame, etc.)
- Implement cryptographic features for QUIC, including initial encryption contexts based on QUIC Version.
- Implement automaton for Handshake, sessions, etc.
- And more...
"""
10 changes: 10 additions & 0 deletions scapy/layers/quic/all.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-only
# This file is part of Scapy
# See https://scapy.net/ for more information

"""
Aggregate top level objects from all QUIC modules.
"""

from scapy.layers.quic.packet import *
from scapy.layers.quic.session import *
203 changes: 28 additions & 175 deletions scapy/layers/quic.py → scapy/layers/quic/basefields.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,27 @@
# SPDX-License-Identifier: GPL-2.0-only
# This file is part of Scapy
# See https://scapy.net/ for more information
# Copyright (C) Gabriel Potter <gabriel[]potter[]fr>

"""
QUIC

The draft of a very basic implementation of the structures from [RFC 9000].
This isn't binded to UDP by default as currently too incomplete.

TODO:
- payloads.
- encryption.
- automaton.
- etc.
QUIC base fields, used for QUIC packet parsing/building.
"""

import struct

from scapy.packet import (
Packet,
)

from scapy.fields import (
_EnumField,
BitEnumField,
BitField,
ByteEnumField,
ByteField,
EnumField,
Field,
FieldLenField,
FieldListField,
IntField,
MultipleTypeField,
PacketListField,
ShortField,
StrLenField,
ThreeBytesField,
)

Expand All @@ -46,7 +33,7 @@
)

# RFC9000 table 3
_quic_payloads = {
_quic_frames = {
0x00: "PADDING",
0x01: "PING",
0x02: "ACK",
Expand All @@ -69,6 +56,14 @@
0x1E: "HANDSHAKE_DONE",
}

# QUIC Versions
# https://www.iana.org/assignments/quic/quic.xhtml#quic-versions
_quic_versions = {
0x00000000: "QUIC Version Negotiaion", # RFC9000 sect 17.2.1
0x00000001: "QUIC v1", # RFC9000 sect 15
0x6b3342cf: "QUIC v2", # RFC9369 sect 3.1
}


# RFC9000 sect 16
class QuicVarIntField(Field[int, int]):
Expand Down Expand Up @@ -144,53 +139,24 @@ def i2repr(
# RFC9000 sect 17 abstraction


class QUIC(Packet):
match_subclass = True

@classmethod
def dispatch_hook(cls, _pkt=None, *args, **kargs):
"""
Returns the right class for the given data.
"""
if _pkt:
hdr = _pkt[0]
if hdr & 0x80:
# Long Header packets
if hdr & 0x40 == 0:
return QUIC_Version
else:
typ = (hdr & 0x30) >> 4
return {
0: QUIC_Initial,
1: QUIC_0RTT,
2: QUIC_Handshake,
3: QUIC_Retry,
}[typ]
class QuicPacketNumberBitFieldLenField(BitField):
def i2m(self, pkt, x):
if x is None and pkt is not None:
PacketNumber = pkt.PacketNumber or 0
if PacketNumber < 0 or PacketNumber > 0xFFFFFFFF:
raise struct.error("requires 0 <= number <= 0xFFFFFFFF")
if PacketNumber < 0x100:
return 0
elif PacketNumber < 0x10000:
return 1
elif PacketNumber < 0x1000000:
return 2
else:
# Short Header packets
return QUIC_1RTT
return QUIC_Initial

def mysummary(self):
return self.name


# RFC9000 sect 17.2.1


class QUIC_Version(QUIC):
name = "QUIC - Version Negotiation"
fields_desc = [
BitEnumField("HeaderForm", 1, 1, _quic_long_hdr),
BitField("Unused", 0, 7),
IntField("Version", 0),
FieldLenField("DstConnIDLen", None, length_of="DstConnID", fmt="B"),
StrLenField("DstConnID", "", length_from=lambda pkt: pkt.DstConnIDLen),
FieldLenField("SrcConnIDLen", None, length_of="SrcConnID", fmt="B"),
StrLenField("SrcConnID", "", length_from=lambda pkt: pkt.SrcConnIDLen),
FieldListField("SupportedVersions", [], IntField("", 0)),
]

return 3
elif x is None:
return 0
return x

# RFC9000 sect 17.2.2

Expand Down Expand Up @@ -227,116 +193,3 @@ class QUIC_Version(QUIC):
],
ByteField(name, default),
)


class QuicPacketNumberBitFieldLenField(BitField):
def i2m(self, pkt, x):
if x is None and pkt is not None:
PacketNumber = pkt.PacketNumber or 0
if PacketNumber < 0 or PacketNumber > 0xFFFFFFFF:
raise struct.error("requires 0 <= number <= 0xFFFFFFFF")
if PacketNumber < 0x100:
return 0
elif PacketNumber < 0x10000:
return 1
elif PacketNumber < 0x1000000:
return 2
else:
return 3
elif x is None:
return 0
return x


class QUIC_Initial(QUIC):
name = "QUIC - Initial"
Version = 0x00000001
fields_desc = (
[
BitEnumField("HeaderForm", 1, 1, _quic_long_hdr),
BitField("FixedBit", 1, 1),
BitEnumField("LongPacketType", 0, 2, _quic_long_pkttyp),
BitField("Reserved", 0, 2),
QuicPacketNumberBitFieldLenField("PacketNumberLen", None, 2),
]
+ QUIC_Version.fields_desc[2:7]
+ [
QuicVarLenField("TokenLen", None, length_of="Token"),
StrLenField("Token", "", length_from=lambda pkt: pkt.TokenLen),
QuicVarIntField("Length", 0),
QuicPacketNumberField("PacketNumber", 0),
]
)


# RFC9000 sect 17.2.3
class QUIC_0RTT(QUIC):
name = "QUIC - 0-RTT"
LongPacketType = 1
fields_desc = QUIC_Initial.fields_desc[:10] + [
QuicVarIntField("Length", 0),
QuicPacketNumberField("PacketNumber", 0),
]


# RFC9000 sect 17.2.4
class QUIC_Handshake(QUIC):
name = "QUIC - Handshake"
LongPacketType = 2
fields_desc = QUIC_0RTT.fields_desc


# RFC9000 sect 17.2.5
class QUIC_Retry(QUIC):
name = "QUIC - Retry"
LongPacketType = 3
Version = 0x00000001
fields_desc = (
QUIC_Initial.fields_desc[:3]
+ [
BitField("Unused", 0, 4),
]
+ QUIC_Version.fields_desc[2:7]
)


# RFC9000 sect 17.3
class QUIC_1RTT(QUIC):
name = "QUIC - 1-RTT"
fields_desc = [
BitEnumField("HeaderForm", 0, 1, _quic_long_hdr),
BitField("FixedBit", 1, 1),
BitField("SpinBit", 0, 1),
BitField("Reserved", 0, 2),
BitField("KeyPhase", 0, 1),
QuicPacketNumberBitFieldLenField("PacketNumberLen", None, 2),
# FIXME - Destination Connection ID
QuicPacketNumberField("PacketNumber", 0),
]


# RFC9000 sect 19.1
class QUIC_PADDING(Packet):
fields_desc = [
ByteEnumField("Type", 0x00, _quic_payloads),
]


# RFC9000 sect 19.2
class QUIC_PING(Packet):
fields_desc = [
ByteEnumField("Type", 0x01, _quic_payloads),
]


# RFC9000 sect 19.3
class QUIC_ACK(Packet):
fields_desc = [
ByteEnumField("Type", 0x02, _quic_payloads),
]


# Bindings
# bind_bottom_up(UDP, QUIC, dport=443)
# bind_bottom_up(UDP, QUIC, sport=443)
# bind_layers(UDP, QUIC, dport=443, sport=443)
Loading