diff --git a/scapy/layers/quic/__init__.py b/scapy/layers/quic/__init__.py new file mode 100644 index 00000000000..82ef4fb0741 --- /dev/null +++ b/scapy/layers/quic/__init__.py @@ -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... +""" \ No newline at end of file diff --git a/scapy/layers/quic/all.py b/scapy/layers/quic/all.py new file mode 100644 index 00000000000..2f2701042a6 --- /dev/null +++ b/scapy/layers/quic/all.py @@ -0,0 +1,9 @@ +# 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 * \ No newline at end of file diff --git a/scapy/layers/quic.py b/scapy/layers/quic/basefields.py similarity index 52% rename from scapy/layers/quic.py rename to scapy/layers/quic/basefields.py index 03d2b25ec94..451c5b0cfdf 100644 --- a/scapy/layers/quic.py +++ b/scapy/layers/quic/basefields.py @@ -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 """ -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, ) @@ -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]): @@ -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 @@ -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) diff --git a/scapy/layers/quic/crypto.py b/scapy/layers/quic/crypto.py new file mode 100644 index 00000000000..2b42870a637 --- /dev/null +++ b/scapy/layers/quic/crypto.py @@ -0,0 +1,190 @@ +from cryptography.hazmat.primitives import hashes, hmac +from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand +from cryptography.hazmat.primitives.ciphers import ( + Cipher, algorithms, modes +) +from cryptography.hazmat.backends import default_backend + +class QUICCrypto: + # Initial salt for QUIC version 1 (RFC9001 Section 5.2) + INITIAL_SALTS = { + 1: bytes.fromhex("38762cf7f55934b34d179ae6a4c80cadccbb7f0a"), + } + + def __init__(self, dcid: bytes, version: int): + """ + Initialize the QUIC crypto state. Derives initial keys from DCID and version. + + :param dcid: Destination Connection ID (bytes) + :param version: QUIC Version (int) + """ + if version not in self.INITIAL_SALTS: + raise ValueError(f"No initial salt defined for QUIC version {version}") + + self.version = version + self.dcid = dcid + self.salt = self.INITIAL_SALTS[version] + + # The AEAD for initial keys is AES-128-GCM, and QUIC uses 16-byte keys, 12-byte IVs + # The header protection uses AES-ECB with a 16-byte key + self.aead_key_length = 16 + self.iv_length = 12 + self.hp_key_length = 16 + self.hash_cls = hashes.SHA256 + + # Derive secrets + initial_secret = self._hkdf_extract(self.salt, dcid) + client_initial_secret = self._hkdf_expand_label(initial_secret, b"client in", b"", self.hash_cls().digest_size) + server_initial_secret = self._hkdf_expand_label(initial_secret, b"server in", b"", self.hash_cls().digest_size) + + # Derive client keys + self.client_key = self._hkdf_expand_label(client_initial_secret, b"quic key", b"", self.aead_key_length) + self.client_iv = self._hkdf_expand_label(client_initial_secret, b"quic iv", b"", self.iv_length) + self.client_hp_key = self._hkdf_expand_label(client_initial_secret, b"quic hp", b"", self.hp_key_length) + + # Derive server keys + self.server_key = self._hkdf_expand_label(server_initial_secret, b"quic key", b"", self.aead_key_length) + self.server_iv = self._hkdf_expand_label(server_initial_secret, b"quic iv", b"", self.iv_length) + self.server_hp_key = self._hkdf_expand_label(server_initial_secret, b"quic hp", b"", self.hp_key_length) + + def _hkdf_extract(self, salt: bytes, ikm: bytes) -> bytes: + """HKDF-Extract using SHA-256.""" + # HKDF-Extract is essentially HMAC with salt + hk = hmac.HMAC(salt, self.hash_cls(), backend=default_backend()) + hk.update(ikm) + return hk.finalize() + + def _hkdf_expand_label(self, secret: bytes, label: bytes, context: bytes, length: int) -> bytes: + """ + HKDF-Expand-Label as defined by TLS 1.3 and QUIC. + + HKDF-Expand-Label(Secret, Label, Context, Length) = + HKDF-Expand(Secret, HkdfLabel, Length) + + Where HkdfLabel = length(Label) + Label + length(Context) + Context + """ + full_label = b"tls13 " + label + + # The "HkdfLabel" structure in TLS 1.3 for QUIC: + # struct { + # uint16 length = Length; + # opaque label<0..255> = "quic " + Label; + # opaque context<0..255> = Context; + # } HkdfLabel; + # + # length is a 2-byte integer + # Then a single-byte length for label and context each, followed by label and context bytes + hkdf_label = (length.to_bytes(2, "big") + + bytes([len(full_label)]) + full_label + + bytes([len(context)]) + context) + + hkdf = HKDFExpand( + algorithm=self.hash_cls(), + length=length, + info=hkdf_label, + backend=default_backend() + ) + return hkdf.derive(secret) + + def _aead_encrypt(self, key: bytes, iv: bytes, pn: int, aad: bytes, plaintext: bytes) -> bytes: + """ + AEAD Encrypt using AES-128-GCM. + + :param key: The AEAD key + :param iv: The AEAD IV + :param pn: Packet number (for nonce construction) + :param aad: Additional authenticated data + :param plaintext: The data to encrypt + :return: ciphertext including authentication tag + """ + nonce = self._build_nonce(iv, pn) + encryptor = Cipher(algorithms.AES(key), modes.GCM(nonce), backend=default_backend()).encryptor() + encryptor.authenticate_additional_data(aad) + ciphertext = encryptor.update(plaintext) + encryptor.finalize() + return ciphertext + encryptor.tag + + def _aead_decrypt(self, key: bytes, iv: bytes, pn: int, aad: bytes, ciphertext: bytes, tag: bytes) -> bytes: + """ + AEAD Decrypt using AES-128-GCM. + + :param key: The AEAD key + :param iv: The AEAD IV + :param pn: Packet number (for nonce construction) + :param aad: Additional authenticated data + :param ciphertext: The data to decrypt (including authentication tag) + :return: decrypted plaintext + """ + nonce = self._build_nonce(iv, pn) + decryptor = Cipher(algorithms.AES(key), modes.GCM(nonce, tag), backend=default_backend()).decryptor() + decryptor.authenticate_additional_data(aad) + + # The last 16 bytes of ciphertext are the authentication tag + plaintext = decryptor.update(ciphertext) + decryptor.finalize() + + return plaintext + + def _build_nonce(self, iv: bytes, pn: int) -> bytes: + """ + QUIC constructs the nonce by XORing the packet number with the IV. + The IV length is 12 bytes, and the PN is encoded in a variable-length manner. + """ + pn_bytes = pn.to_bytes(4, "big") + # IV length is 12, PN length might be shorter. Right-align PN in the IV. + padded_pn = (b"\x00" * (len(iv) - len(pn_bytes))) + pn_bytes + return bytes(a ^ b for a, b in zip(iv, padded_pn)) + + def header_protect(self, sample: bytes, first_byte: bytes, pn_bytes: bytes) -> (bytes, bytes): + """ + Apply header protection as defined by QUIC. + + The header protection key is used to create a mask by encrypting a sample of ciphertext. + The first protected byte (one byte from the flags) and the PN bytes are XORed with parts of this mask. + + :param hp_key: the header protection key + :param sample: 16-byte sample from the ciphertext after the header + :param first_byte: the first header byte to protect/unprotect + :param pn_bytes: the packet number bytes to protect/unprotect + :return: (modified_first_byte, modified_pn_bytes) + """ + # QUIC header protection uses AES-ECB for generating a mask. + cipher = Cipher(algorithms.AES(self.client_hp_key), modes.ECB(), backend=default_backend()).encryptor() + mask = cipher.update(sample) + cipher.finalize() + + # Mask the first byte (only the lower 5 bits are protected) + first_byte_masked = bytes([(first_byte ^ (mask[0] & 0x0f))]) + + # Mask the PN bytes + masked_pn = bytes(p ^ m for p, m in zip(pn_bytes, mask[1:1+len(pn_bytes)])) + + return first_byte_masked, masked_pn + + def encrypt_packet(self, is_client: bool, pn: int, recdata: bytes, payload: bytes): + """ + :param is_client: True if sender is client, else server + :param pn: packet number + :param recdata: The QUIC header bytes (unprotected) + :param payload: The plaintext payload + :return: encrypted packet (header + ciphertext) + """ + key = self.client_key if is_client else self.server_key + iv = self.client_iv if is_client else self.server_iv + + ciphertext_with_tag = self._aead_encrypt(key, iv, pn, recdata, payload) + + return ciphertext_with_tag + + def decrypt_packet(self, is_client: bool, pn: int, recdata: bytes, ciphertext: bytes): + """ + :param is_client: True if sender is client, else server + :param pn: packet number + :param recdata: The QUIC header bytes (unprotected) + :param ciphertext: The encrypted payload including tag + :return: decrypted payload + """ + key = self.client_key if is_client else self.server_key + iv = self.client_iv if is_client else self.server_iv + tag = ciphertext[-16:] # Last 16 bytes are the tag + ciphertext = ciphertext[:-16] + # Decrypt the payload + plaintext = self._aead_decrypt(key, iv, pn, recdata, ciphertext, tag) + return plaintext \ No newline at end of file diff --git a/scapy/layers/quic/packet.py b/scapy/layers/quic/packet.py new file mode 100644 index 00000000000..c6aa51860bb --- /dev/null +++ b/scapy/layers/quic/packet.py @@ -0,0 +1,239 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information +# Copyright (C) Gabriel Potter + +""" +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. +""" +from scapy.layers.quic.basefields import ( + _quic_long_hdr, + _quic_long_pkttyp, + _quic_versions, + QuicVarIntField, + QuicVarLenField, + QuicPacketNumberBitFieldLenField, + QuicPacketNumberField, +) + +from scapy.layers.quic.crypto import QUICCrypto +from scapy.packet import ( + Packet, +) + +from scapy.fields import ( + BitEnumField, + BitField, + FieldLenField, + FieldListField, + IntEnumField, + IntField, + StrLenField, +) + + +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 + return QUIC_Long.dispatch_hook(_pkt, *args, **kargs) + else: + # Short Header packets + return QUIC_1RTT + return None + + def mysummary(self): + return self.name + +class QUIC_Long(QUIC): + """ + Base class for QUIC Long Header packets. + """ + name = "QUIC - Long Header" + fields_desc = [ + BitEnumField("HeaderForm", 1, 1, _quic_long_hdr), + BitField("FixedBit", 1, 1), + BitEnumField("LongPacketType", None, 2, _quic_long_pkttyp), + BitField("TypeSpecific", None, 4), + IntEnumField("Version", 1, _quic_versions), + 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), + ] + + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + """ + Returns the right class for the given data. + """ + if _pkt: + if _pkt[1:5] == b"\x00\x00\x00\x00": + # Version Negotiation packet + return QUIC_Version + hdr = _pkt[0] + typ = (hdr & 0x30) >> 4 + return { + 0: QUIC_Initial, + 1: QUIC_0RTT, + 2: QUIC_Handshake, + 3: QUIC_Retry, + }[typ] + +# RFC9000 sect 17.2.1 + + +class QUIC_Version(QUIC): + name = "QUIC - Version Negotiation" + fields_desc = ( + [ + QUIC_Long.fields_desc[0], + BitField("Unused", None, 7), + ] + + QUIC_Long.fields_desc[4:9] + + [ + FieldListField("SupportedVersions", [], IntField("", 0)), + ] + ) + + + +class QUIC_Initial(QUIC_Long): + name = "QUIC - Initial" + LongPacketType = 0 + fields_desc = ( + QUIC_Long.fields_desc[:3] + + [ + BitField("Reserved", 0, 2), + QuicPacketNumberBitFieldLenField("PacketNumberLen", None, 2), + ] + + QUIC_Long.fields_desc[4:9] + + [ + QuicVarLenField("TokenLen", None, length_of="Token"), + StrLenField("Token", "", length_from=lambda pkt: pkt.TokenLen), + QuicVarIntField("Length", 0), + QuicPacketNumberField("PacketNumber", 0), + ] + ) + + def pre_dissect(self, s): + unencrypted_packet = bytearray(s) + s, protected_header = BitField("", 0, 8).getfield(self, s) + s, quic_version = IntEnumField("", 1, _quic_versions).getfield(self, s) + s, dst_conn_id_len = FieldLenField("", None, fmt="B").getfield(self, s) + s, dst_conn_id = StrLenField("", None, length_from=lambda pkt: dst_conn_id_len).getfield(self, s) + s, src_conn_id_len = FieldLenField("", None, length_of="SrcConnID", fmt="B").getfield(self, s) + s, _ = StrLenField("", None, length_from=lambda pkt: src_conn_id_len).getfield(self, s) + s, token_len = QuicVarLenField("", None).getfield(self, s) + s, _ = StrLenField("", "", length_from=lambda pkt: token_len).getfield(self, s) + s, length = QuicVarIntField("", None).getfield(self, s) + packet_number_offset = len(unencrypted_packet) - len(s) + s, protected_packet_number = IntField("", None).getfield(self, s) + _, sample = BitField("", 0, 8*16).getfield(self, s) + sample = sample.to_bytes(16, 'big') + protected_packet_number = protected_packet_number.to_bytes(4, 'big') + self.crypto = QUICCrypto(dst_conn_id, quic_version) + unprotected_header, raw_packet_number = self.crypto.header_protect(sample, protected_header, protected_packet_number) + unencrypted_packet[0] = unprotected_header[0] + unprotected_header, _ = BitField("", None, 1).getfield(self, unprotected_header) + unprotected_header, _ = BitField("", None, 1).getfield(self, unprotected_header) + unprotected_header, _ = BitField("", None, 2).getfield(self, unprotected_header) + unprotected_header, _ = BitField("", None, 2).getfield(self, unprotected_header) + unprotected_header, packet_number_len = QuicPacketNumberBitFieldLenField("", None, 2).getfield(self, unprotected_header) + packet_number_len += 1 + packet_number = raw_packet_number[:packet_number_len] + unencrypted_packet[packet_number_offset:packet_number_offset + packet_number_len] = packet_number + plaintext = self.crypto.decrypt_packet( + is_client=True, + pn=int.from_bytes(packet_number, 'big'), + recdata=bytes(unencrypted_packet[:packet_number_offset+packet_number_len]), + ciphertext=bytes(unencrypted_packet[packet_number_offset+packet_number_len:packet_number_offset+length]) + ) + unencrypted_packet[packet_number_offset+packet_number_len:packet_number_offset+length] = plaintext + return bytes(unencrypted_packet) + + +# RFC9000 sect 17.2.3 +class QUIC_0RTT(QUIC_Long): + name = "QUIC - 0-RTT" + LongPacketType = 1 + fields_desc = ( + QUIC_Long.fields_desc[:3] + + [ + BitField("Reserved", None, 2), + QuicPacketNumberBitFieldLenField("PacketNumberLen", None, 2), + ] + + QUIC_Long.fields_desc[4:9] + + [ + QuicVarIntField("Length", 0), + QuicPacketNumberField("PacketNumber", 0), + ] + ) + + +# RFC9000 sect 17.2.4 +class QUIC_Handshake(QUIC_Long): + name = "QUIC - Handshake" + LongPacketType = 2 + fields_desc = ( + QUIC_Long.fields_desc[:3] + + [ + BitField("Reserved", None, 2), + QuicPacketNumberBitFieldLenField("PacketNumberLen", None, 2), + ] + + QUIC_Long.fields_desc[4:9] + + [ + QuicVarIntField("Length", 0), + QuicPacketNumberField("PacketNumber", 0), + ] + ) + + +# RFC9000 sect 17.2.5 +class QUIC_Retry(QUIC_Long): + name = "QUIC - Retry" + fields_desc = ( + QUIC_Long.fields_desc[:3] + + [ + BitField("Unused", 0, 4), + ] + + [QUIC_Long.fields_desc[4]] + ) + + +# 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), + ] + +# Bindings +# bind_bottom_up(UDP, QUIC, dport=443) +# bind_bottom_up(UDP, QUIC, sport=443) +# bind_layers(UDP, QUIC, dport=443, sport=443) diff --git a/scapy/layers/tls/quic.py b/scapy/layers/tls/quic.py index 6580bd887a1..c4f32bdf085 100644 --- a/scapy/layers/tls/quic.py +++ b/scapy/layers/tls/quic.py @@ -15,7 +15,7 @@ ) from scapy.packet import Packet -from scapy.layers.quic import ( +from scapy.layers.quic.packet import ( QuicVarIntField, QuicVarLenField, QuicVarEnumField, diff --git a/test/scapy/layers/quic.uts b/test/scapy/layers/quic.uts index 4e66957ee0a..9ca40e7b7f1 100644 --- a/test/scapy/layers/quic.uts +++ b/test/scapy/layers/quic.uts @@ -7,7 +7,7 @@ = QUIC - Dissect Client Initial Packet -from scapy.layers.quic import * +from scapy.layers.quic.all import * pkt = QUIC(bytes.fromhex("c00000000108000102030405060705635f636964004103001c36a7ed78716be9711ba498b7ed868443bb2e0c514d4d848eadcc7a00d25ce9f9afa483978088de836be68c0b32a24595d7813ea5414a9199329a6d9f7f760dd8bb249bf3f53d9a77fbb7b395b8d66d7879a51fe59ef9601f79998eb3568e1fdc789f640acab3858a82ef2930fa5ce14b5b9ea0bdb29f4572da85aa3def39b7efafffa074b9267070d50b5d07842e49bba3bc787ff295d6ae3b514305f102afe5a047b3fb4c99eb92a274d244d60492c0e2e6e212cef0f9e3f62efd0955e71c768aa6bb3cd80bbb3755c8b7ebee32712f40f2245119487021b4b84e1565e3ca31967ac8604d4032170dec280aeefa095d08b3b7241ef6646a6c86e5c62ce08be099")) assert QUIC_Initial in pkt @@ -20,7 +20,7 @@ assert pkt.PacketNumber == 0 = QUIC - Dissect Server Initial Packet -from scapy.layers.quic import * +from scapy.layers.quic.all import * pkt = QUIC(bytes.fromhex("c00000000105635f63696405735f63696400407500836855d5d9c823d07c616882ca770279249864b556e51632257e2d8ab1fd0dc04b18b9203fb919d8ef5a33f378a627db674d3c7fce6ca5bb3e8cf90109cbb955665fc1a4b93d05f6eb83252f6631bcadc7402c10f65c52ed15b4429c9f64d84d64fa406cf0b517a926d62a54a9294136b143b033")) assert QUIC_Initial in pkt @@ -33,7 +33,7 @@ assert pkt.PacketNumber == 0 = QUIC - Dissect Server Handshake Packet -from scapy.layers.quic import * +from scapy.layers.quic.all import * pkt = QUIC(bytes.fromhex("e00000000105635f63696405735f63696440cf014420f919681c3f0f102a30f5e647a3399abf54bc8e80453134996ba33099056242f3b8e662bbfce42f3ef2b6ba87159147489f8479e849284e983fd905320a62fc7d67e9587797096ca60101d0b2685d8747811178133ad9172b7ff8ea83fd81a814bae27b953a97d57ebff4b4710dba8df82a6b49d7d7fa3d8179cbdb8683d4bfa832645401e5a56a76535f71c6fb3e616c241bb1f43bc147c296f591402997ed49aa0c55e31721d03e14114af2dc458ae03944de5126fe08d66a6ef3ba2ed1025f98fea6d6024998184687dc06")) assert QUIC_Handshake in pkt @@ -44,9 +44,10 @@ assert pkt.PacketNumber == 1 = QUIC - QuicPacketNumberField / QuicPacketNumberBitFieldLenField - variable lengths -from scapy.layers.quic import * +from scapy.layers.quic.all import * pkt = QUIC_Initial(DstConnID=b'p\xa2\x8e@\x96\xc5}\xd0\xff\xb6\xc3\xd8\x1b\xcaR', SrcConnID=b'\xf7\x10Q', PacketNumber=0xFF) +print(bytes(pkt)) assert bytes(pkt) == b'\xc0\x00\x00\x00\x01\x0fp\xa2\x8e@\x96\xc5}\xd0\xff\xb6\xc3\xd8\x1b\xcaR\x03\xf7\x10Q\x00\x00\xff' pkt = QUIC_Initial(bytes(pkt)) assert pkt.DstConnIDLen == 15 @@ -75,7 +76,7 @@ assert pkt.PacketNumber == 0xFFFFFFFF = QUIC - QuicPacketNumberField / QuicPacketNumberBitFieldLenField - Out of range import struct -from scapy.layers.quic import * +from scapy.layers.quic.all import * try: pkt = QUIC_Initial(PacketNumber=0xFFFFFFFFFF) @@ -86,7 +87,7 @@ except struct.error: = QUIC - QuicVarIntField - variable lengths -from scapy.layers.quic import * +from scapy.layers.quic.all import * pkt = QUIC_Initial(Length=1) assert bytes(pkt) == b'\xc0\x00\x00\x00\x01\x00\x00\x00\x01\x00' @@ -107,7 +108,7 @@ assert QUIC_Initial(bytes(pkt)).Length == 4611686018427387903 = QUIC - QuicVarIntField - Out of range import struct -from scapy.layers.quic import * +from scapy.layers.quic.all import * try: pkt = QUIC_Initial(Length=0xFFFFFFFFFFFFFFFF)