From ae90524027c80ba7d39998cdd241326262024f43 Mon Sep 17 00:00:00 2001 From: Arne Welzel Date: Wed, 30 Apr 2025 12:39:40 +0200 Subject: [PATCH] QUIC: Handle CRYPTO frames across multiple INITIAL packets Instead of sending the accumulated CRYPTO frames after processing an INITIAL packet, add logic to determine the total length of the TLS Client or Server Hello (by peeking into the first 4 byte). Once all CRYPTO frames have arrived, flush the reassembled data to the TLS analyzer at once. --- src/analyzer/protocol/quic/QUIC.spicy | 203 +++++++++++------- .../conn.log.cut | 3 + .../quic.log.cut | 3 + .../ssl.log.cut | 3 + .../conn.log.cut | 3 + .../quic.log.cut | 3 + .../ssl.log.cut | 3 + ...nitial-fragmented-crypto-only-initial.pcap | Bin 0 -> 3948 bytes ...ic-multiple-initial-fragmented-crypto.pcap | Bin 0 -> 15611 bytes ...nitial-fragmented-crypto-only-initial.zeek | 12 ++ .../multiple-initial-fragmented-crypto.zeek | 12 ++ 11 files changed, 169 insertions(+), 76 deletions(-) create mode 100644 testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto-only-initial/conn.log.cut create mode 100644 testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto-only-initial/quic.log.cut create mode 100644 testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto-only-initial/ssl.log.cut create mode 100644 testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto/conn.log.cut create mode 100644 testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto/quic.log.cut create mode 100644 testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto/ssl.log.cut create mode 100644 testing/btest/Traces/quic/quic-multiple-initial-fragmented-crypto-only-initial.pcap create mode 100644 testing/btest/Traces/quic/quic-multiple-initial-fragmented-crypto.pcap create mode 100644 testing/btest/scripts/base/protocols/quic/multiple-initial-fragmented-crypto-only-initial.zeek create mode 100644 testing/btest/scripts/base/protocols/quic/multiple-initial-fragmented-crypto.zeek diff --git a/src/analyzer/protocol/quic/QUIC.spicy b/src/analyzer/protocol/quic/QUIC.spicy index b6fb76b62c..3954a80977 100644 --- a/src/analyzer/protocol/quic/QUIC.spicy +++ b/src/analyzer/protocol/quic/QUIC.spicy @@ -22,21 +22,88 @@ public function decrypt_crypto_payload( ############## # Can we decrypt? -function can_decrypt(long_header: LongHeaderPacket, context: ConnectionIDInfo, is_client: bool): bool { +function can_decrypt(long_header: LongHeaderPacket, context: ConnectionIDInfo, crypto: CryptoSinkUnit&): bool { if ( ! long_header.is_initial ) return False; - if ( is_client ) - return ! context.client_initial_processed; + if ( crypto == Null ) + return False; - # This is the responder, can only decrypt if we have an initial - # destination_id from the client - return context.client_initial_processed - && |context.initial_destination_conn_id| > 0 - && ! context.server_initial_processed; + # Can only decrypt the responder if we've seen the initial destination conn id. + if ( ! crypto.is_orig && |context.initial_destination_conn_id| == 0 ) + return False; + + # Only attempt decryption if we haven't flushed some SSL data yet. + return ! crypto.finished; } +# This unit is connected with the server and client sinks receiving +# CRYPTO frames and forwards data to the SSL handle in the context. +type CryptoSinkUnit = unit(is_orig: bool, context: ConnectionIDInfo&) { + var buffered: bytes; + var length: uint32 = 0; + var is_orig: bool = is_orig; + var finished: bool; + + # The first 4 bytes of crypto data contain the expected tag and a + # 24bit length from the TLS HandshakeMessage. Extract the length + # so we can determine when all CRYPTO frames have arrived. + # + # https://datatracker.ietf.org/doc/html/rfc8446#section-4 + # + # struct { + # HandshakeType msg_type; /* handshake type */ + # uint24 length; /* remaining bytes in message */ + # ... + # + : uint8 { + self.buffered += $$; + } + + len: uint8[3] { + self.length = (cast($$[0]) << 16) + (cast($$[1]) << 8) + cast($$[2]) + 4; + + self.buffered += $$[0]; + self.buffered += $$[1]; + self.buffered += $$[2]; + } + + : void &requires=(self.length <= 2**14 + 256) { # The length MUST NOT exceed 2^14 + 256 bytes (RFC 8446) + + # The client or server hello data is forwarded to the SSL analyzer as a + # TLSPlaintext record with legacy_record_version set to \x03\x03 (1.3). + # + # enum { + # invalid(0), + # change_cipher_spec(20), + # alert(21), + # handshake(22), + # application_data(23), + # (255) + # } ContentType; + # + # struct { + # ContentType type; + # ProtocolVersion legacy_record_version; + # uint16 length; + # opaque fragment[TLSPlaintext.length]; + # } TLSPlaintext; + # + # https://datatracker.ietf.org/doc/html/rfc8446#section-5.1 + local length_bytes = pack(cast(self.length), spicy::ByteOrder::Big); + zeek::protocol_data_in(is_orig, b"\x16\x03\x03" + length_bytes + self.buffered, context.ssl_handle); + } + + : bytes &chunked &size=(self.length - 4) { + zeek::protocol_data_in(is_orig, $$, context.ssl_handle); + } + + : void { + self.finished = True; + } +}; + type ConnectionIDInfo = struct { client_cid_len: uint8; server_cid_len: uint8; @@ -48,24 +115,11 @@ type ConnectionIDInfo = struct { # https://quicwg.org/base-drafts/rfc9001.html#appendix-A initial_destination_conn_id: bytes; - # Currently, this analyzer assumes that ClientHello - # and ServerHello fit into the first INITIAL packet (and - # that there is only one that we're interested in. - # - # But minimally the following section sounds like this might not - # hold in general and the Wireshark has samples showing - # the handshake spanning across more than two INITIAL packets. - # (quic-fragmented-handshakes.pcapng.gz) - # - # https://datatracker.ietf.org/doc/html/rfc9001#section-4.3 - # - # Possible fix is to buffer up all CRYPTO frames across multiple - # INITIAL packets until we see a non-INITIAL frame. - # - # We also rely heavily on getting originator and responder right. - # - client_initial_processed: bool; - server_initial_processed: bool; + # Track crypto state. + client_crypto: CryptoSinkUnit&; + client_sink: sink&; + server_crypto: CryptoSinkUnit&; + server_sink: sink&; ssl_handle: zeek::ProtocolHandle &optional; }; @@ -272,7 +326,7 @@ public type LongHeaderPacket = unit { }; # A QUIC Frame. -public type Frame = unit(header: LongHeaderPacket, from_client: bool, crypto_sink: sink&) { +public type Frame = unit(header: LongHeaderPacket, from_client: bool, crypto: CryptoSinkUnit, crypto_sink: sink&) { frame_type : uint8 &convert=cast($$); # TODO: add other FrameTypes as well @@ -282,6 +336,18 @@ public type Frame = unit(header: LongHeaderPacket, from_client: bool, crypto_sin FrameType::CRYPTO -> c: CRYPTOPayload(from_client) { # Have the sink re-assemble potentially out-of-order cryptodata crypto_sink.write(self.c.cryptodata, self.c.offset.result_); + + # If the crypto unit has determined a valid length, ensure we + # don't attempt to write more bytes into the sink. If it doesn't, + # use 2000 bytes as an arbitrary limit required to observe the + # length of the contained Client Hello or Server Hello. + if ( crypto.length > 0 ) { + if ( |crypto_sink| > crypto.length ) + throw "too much crypto data received %s > %s" % ( |crypto_sink|, crypto.length); + } else { + if ( |crypto_sink| > 2000 ) + throw "too much crypto data without length received %s" % |crypto_sink|; + } } FrameType::CONNECTION_CLOSE1 -> : ConnectionClosePayload(header); FrameType::PADDING -> : skip /\x00*/; # eat the padding @@ -408,18 +474,6 @@ public type ShortPacketPayload = unit { payload: skip bytes &eod; }; -# Buffer all crypto messages (which might be fragmented and unordered) -# into the following unit. -type CryptoBuffer = unit() { - - var buffered: bytes; - - : bytes &chunked &eod { - self.buffered += $$; - # print "crypto_buffer got data", |$$|, |self.buffered|; - } -}; - ############## # QUIC packet parsing # @@ -430,8 +484,8 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) { var packet_size: uint64 = 0; var start: iterator; - sink crypto_sink; - var crypto_buffer: CryptoBuffer&; + var crypto: CryptoSinkUnit&; + var crypto_sink: sink&; # Attach an SSL analyzer to this connection once. on %init { @@ -440,6 +494,26 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) { } self.start = self.input(); + + # Initialize crypto state in context for both sides if not already done. + if ( context.client_crypto == Null ) { + assert ! context.server_crypto; + context.client_crypto = new CryptoSinkUnit(True, context); + context.client_sink = new sink; + context.client_sink.connect(context.client_crypto); + + context.server_crypto = new CryptoSinkUnit(False, context); + context.server_sink = new sink; + context.server_sink.connect(context.server_crypto); + } + + if ( from_client ) { + self.crypto = context.client_crypto; + self.crypto_sink = context.client_sink; + } else { + self.crypto = context.server_crypto; + self.crypto_sink = context.server_sink; + } } # Peek into the first byte and determine the header type. @@ -453,7 +527,6 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) { self.set_input(self.start); # rewind } - # Depending on the header, parse it and update the src/dest ConnectionID's switch ( self.first_byte.header_form ) { HeaderForm::SHORT -> short_header: ShortHeader(context.client_cid_len); @@ -463,19 +536,25 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) { # If we see a retry packet from the responder, reset the decryption # context such that the next DCID from the client is used for decryption. if ( self.long_header.is_retry ) { - context.client_initial_processed = False; - context.server_initial_processed = False; - context.initial_destination_conn_id = b""; - # Allow re-opening the SSL analyzer the next time around. + # Recreate all the crypto state on the next %init of Packet. zeek::protocol_handle_close(context.ssl_handle); unset context.ssl_handle; + context.client_crypto = Null; + context.server_crypto = Null; + context.client_sink = Null; + context.server_sink = Null; + self.crypto = Null; + self.crypto_sink = Null; + + # Reset crypto state! + context.initial_destination_conn_id = b""; } } }; : void { - if (self?.long_header && can_decrypt(self.long_header, context, from_client)) + if ( self?.long_header && can_decrypt(self.long_header, context, self.crypto ) ) # If we have parsed an initial packet that we can decrypt the payload, # determine the size to store into a buffer. self.packet_size = self.offset(); @@ -483,8 +562,6 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) { # Buffer the whole packet if we determined we have a chance to decrypt. packet_data: bytes &parse-at=self.start &size=self.packet_size if ( self.packet_size > 0 ) { - self.crypto_buffer = new CryptoBuffer(); - self.crypto_sink.connect(self.crypto_buffer); if ( from_client ) { context.server_cid_len = self.long_header.dest_conn_id_len; @@ -537,33 +614,7 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) { # If this was packet with a long header and decrypted data exists, attempt # to parse the plain QUIC frames from it. - frames: Frame(self.long_header, from_client, self.crypto_sink)[] &parse-from=self.decrypted_data if (self.first_byte.header_form == HeaderForm::LONG && |self.decrypted_data| > 0); - - # Once the Packet is fully parsed, pass the accumulated CRYPTO frames - # to the SSL analyzer as handshake data. - on %done { - # print "packet done", zeek::is_orig(), self.first_byte.header_form, |self.decrypted_data|; - - if ( self.crypto_buffer != Null && |self.crypto_buffer.buffered| > 0 ) { - local handshake_data = self.crypto_buffer.buffered; - - # The data is passed to the SSL analyzer as part of a HANDSHAKE (0x16) message with TLS1.3 (\x03\x03). - # The 2 length bytes are also passed, followed by the actual CRYPTO blob which contains a CLIENT HELLO or SERVER HELLO - local length_bytes = pack(cast(|handshake_data|), spicy::ByteOrder::Big); - zeek::protocol_data_in( - from_client - , b"\x16\x03\x03" + length_bytes + handshake_data - , context.ssl_handle - ); - - # Stop decryption attempts after processing the very first INITIAL - # INITIAL packet for which we forwarded data to the SSL analyzer. - if ( from_client ) - context.client_initial_processed = True; - else - context.server_initial_processed = True; - } - } + frames: Frame(self.long_header, from_client, self.crypto, self.crypto_sink)[] &parse-from=self.decrypted_data if (self.first_byte.header_form == HeaderForm::LONG && |self.decrypted_data| > 0); }; ############## diff --git a/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto-only-initial/conn.log.cut b/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto-only-initial/conn.log.cut new file mode 100644 index 0000000000..06445c01a5 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto-only-initial/conn.log.cut @@ -0,0 +1,3 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +ts uid history service +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 D quic,ssl diff --git a/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto-only-initial/quic.log.cut b/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto-only-initial/quic.log.cut new file mode 100644 index 0000000000..13f1e1fa45 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto-only-initial/quic.log.cut @@ -0,0 +1,3 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +ts uid server_name history +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 googleads.g.doubleclick.net IIIS diff --git a/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto-only-initial/ssl.log.cut b/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto-only-initial/ssl.log.cut new file mode 100644 index 0000000000..1929143aa1 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto-only-initial/ssl.log.cut @@ -0,0 +1,3 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +ts uid version cipher curve server_name resumed last_alert next_protocol established ssl_history +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 - - - googleads.g.doubleclick.net F - - F C diff --git a/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto/conn.log.cut b/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto/conn.log.cut new file mode 100644 index 0000000000..46d72b1541 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto/conn.log.cut @@ -0,0 +1,3 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +ts uid history service +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 Dd quic,ssl diff --git a/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto/quic.log.cut b/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto/quic.log.cut new file mode 100644 index 0000000000..b8cd8237eb --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto/quic.log.cut @@ -0,0 +1,3 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +ts uid server_name history +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 googleads.g.doubleclick.net IIISZZZiIiIIIIIIZ diff --git a/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto/ssl.log.cut b/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto/ssl.log.cut new file mode 100644 index 0000000000..1929143aa1 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.quic.multiple-initial-fragmented-crypto/ssl.log.cut @@ -0,0 +1,3 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +ts uid version cipher curve server_name resumed last_alert next_protocol established ssl_history +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 - - - googleads.g.doubleclick.net F - - F C diff --git a/testing/btest/Traces/quic/quic-multiple-initial-fragmented-crypto-only-initial.pcap b/testing/btest/Traces/quic/quic-multiple-initial-fragmented-crypto-only-initial.pcap new file mode 100644 index 0000000000000000000000000000000000000000..aecc0c7eb96a38c4615a1e77d60a85eaf5c04199 GIT binary patch literal 3948 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 MUDbi&)Z71m0ABeFVE_OC literal 0 HcmV?d00001 diff --git a/testing/btest/scripts/base/protocols/quic/multiple-initial-fragmented-crypto-only-initial.zeek b/testing/btest/scripts/base/protocols/quic/multiple-initial-fragmented-crypto-only-initial.zeek new file mode 100644 index 0000000000..0537353ecb --- /dev/null +++ b/testing/btest/scripts/base/protocols/quic/multiple-initial-fragmented-crypto-only-initial.zeek @@ -0,0 +1,12 @@ +# @TEST-DOC: Pcap with CRYPTO frames fragemented over multiple INITIAL packets. The pcap only contains 3 INITIAL packets. Check what logs are created. + +# @TEST-REQUIRES: ${SCRIPTS}/have-spicy +# @TEST-EXEC: zeek -Cr $TRACES/quic/quic-multiple-initial-fragmented-crypto-only-initial.pcap base/protocols/quic +# @TEST-EXEC: test ! -f analyzer.log +# @TEST-EXEC: test ! -f dpd.log +# @TEST-EXEC: zeek-cut -m ts uid history service < conn.log > conn.log.cut +# @TEST-EXEC: btest-diff conn.log.cut +# @TEST-EXEC: zeek-cut -m ts uid server_name history < quic.log > quic.log.cut +# @TEST-EXEC: btest-diff quic.log.cut +# @TEST-EXEC: zeek-cut -m ts uid version cipher curve server_name resumed last_alert next_protocol established ssl_history < ssl.log > ssl.log.cut +# @TEST-EXEC: btest-diff ssl.log.cut diff --git a/testing/btest/scripts/base/protocols/quic/multiple-initial-fragmented-crypto.zeek b/testing/btest/scripts/base/protocols/quic/multiple-initial-fragmented-crypto.zeek new file mode 100644 index 0000000000..98696053bd --- /dev/null +++ b/testing/btest/scripts/base/protocols/quic/multiple-initial-fragmented-crypto.zeek @@ -0,0 +1,12 @@ +# @TEST-DOC: Pcap with CRYPTO frames fragemented over multiple INITIAL packets. + +# @TEST-REQUIRES: ${SCRIPTS}/have-spicy +# @TEST-EXEC: zeek -Cr $TRACES/quic/quic-multiple-initial-fragmented-crypto.pcap base/protocols/quic +# @TEST-EXEC: test ! -f analyzer.log +# @TEST-EXEC: test ! -f dpd.log +# @TEST-EXEC: zeek-cut -m ts uid history service < conn.log > conn.log.cut +# @TEST-EXEC: btest-diff conn.log.cut +# @TEST-EXEC: zeek-cut -m ts uid server_name history < quic.log > quic.log.cut +# @TEST-EXEC: btest-diff quic.log.cut +# @TEST-EXEC: zeek-cut -m ts uid version cipher curve server_name resumed last_alert next_protocol established ssl_history < ssl.log > ssl.log.cut +# @TEST-EXEC: btest-diff ssl.log.cut