mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 14:48:21 +00:00
Merge remote-tracking branch 'origin/topic/awelzel/4405-quic-fragmented-crypto'
* origin/topic/awelzel/4405-quic-fragmented-crypto:
Bump external/zeek-testing
QUIC: Extract reset_crypto() function
QUIC: Rename ConnectionIDInfo to Context
QUIC: Switch initial_destination_conn_id to optional
QUIC: Use initial destination conn_id for decryption
QUIC: Handle CRYPTO frames across multiple INITIAL packets
QUIC: Do not consume EncryptedLongPacketPayload
QUIC: Fix ACK frame parsing
(cherry picked from commit 50ac8d1468
)
This commit is contained in:
parent
e712461719
commit
ec18da8baa
23 changed files with 288 additions and 110 deletions
|
@ -65,6 +65,8 @@ have_2nd = "have_2nd"
|
||||||
ot1 = "ot1"
|
ot1 = "ot1"
|
||||||
ot2 = "ot2"
|
ot2 = "ot2"
|
||||||
uses_seh = "uses_seh"
|
uses_seh = "uses_seh"
|
||||||
|
ect0 = "ect0"
|
||||||
|
ect1 = "ect1"
|
||||||
|
|
||||||
[default.extend-words]
|
[default.extend-words]
|
||||||
caf = "caf"
|
caf = "caf"
|
||||||
|
|
50
CHANGES
50
CHANGES
|
@ -1,3 +1,53 @@
|
||||||
|
7.0.6-4 | 2025-05-05 12:56:17 -0700
|
||||||
|
|
||||||
|
* QUIC: Extract reset_crypto() function (Arne Welzel, Corelight)
|
||||||
|
|
||||||
|
(cherry picked from commit 50ac8d1468603c710e109f1c050b3966dd91deda)
|
||||||
|
|
||||||
|
* QUIC: Rename ConnectionIDInfo to Context (Arne Welzel, Corelight)
|
||||||
|
|
||||||
|
Lets just call it what it is given that it contains more than just
|
||||||
|
connection IDs.
|
||||||
|
|
||||||
|
(cherry picked from commit 50ac8d1468603c710e109f1c050b3966dd91deda)
|
||||||
|
|
||||||
|
* QUIC: Switch initial_destination_conn_id to optional (Arne Welzel, Corelight)
|
||||||
|
|
||||||
|
(cherry picked from commit 50ac8d1468603c710e109f1c050b3966dd91deda)
|
||||||
|
|
||||||
|
* QUIC: Use initial destination conn_id for decryption (Arne Welzel, Corelight)
|
||||||
|
|
||||||
|
Ensure the client side also uses the initial destination connection ID
|
||||||
|
for decryption purposes instead of the one from the current long header
|
||||||
|
packet. PCAP from local WiFi hotspot.
|
||||||
|
|
||||||
|
(cherry picked from commit 50ac8d1468603c710e109f1c050b3966dd91deda)
|
||||||
|
|
||||||
|
* QUIC: Handle CRYPTO frames across multiple INITIAL packets (Arne Welzel, Corelight)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
(cherry picked from commit 50ac8d1468603c710e109f1c050b3966dd91deda)
|
||||||
|
|
||||||
|
* QUIC: Do not consume EncryptedLongPacketPayload (Arne Welzel, Corelight)
|
||||||
|
|
||||||
|
The payload is already consumed within the InitialPacket unit. Consuming
|
||||||
|
it again resulted in UDP datagrams with multiple packets to ignore
|
||||||
|
the remaining packets in the same UDP datagram. The baseline changes
|
||||||
|
showing I being followed by a new H indicates that the INITIAL packet
|
||||||
|
was followed by a HANDSHAKE packet, but previously Zeek discarded
|
||||||
|
these.
|
||||||
|
|
||||||
|
(cherry picked from commit 50ac8d1468603c710e109f1c050b3966dd91deda)
|
||||||
|
|
||||||
|
* QUIC: Fix ACK frame parsing (Arne Welzel, Corelight)
|
||||||
|
|
||||||
|
(cherry picked from commit 50ac8d1468603c710e109f1c050b3966dd91deda)
|
||||||
|
|
||||||
7.0.6-3 | 2025-05-05 12:54:30 -0700
|
7.0.6-3 | 2025-05-05 12:54:30 -0700
|
||||||
|
|
||||||
* broker/main: Adapt enum values to agree with comm.bif (Arne Welzel, Corelight)
|
* broker/main: Adapt enum values to agree with comm.bif (Arne Welzel, Corelight)
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
7.0.6-3
|
7.0.6-4
|
||||||
|
|
|
@ -17,27 +17,104 @@ public function decrypt_crypto_payload(
|
||||||
): bytes &cxxname="QUIC_decrypt_crypto_payload";
|
): bytes &cxxname="QUIC_decrypt_crypto_payload";
|
||||||
|
|
||||||
|
|
||||||
##############
|
|
||||||
## Context - tracked in one connection
|
|
||||||
##############
|
|
||||||
|
|
||||||
# Can we decrypt?
|
# Can we decrypt?
|
||||||
function can_decrypt(long_header: LongHeaderPacket, context: ConnectionIDInfo, is_client: bool): bool {
|
function can_decrypt(long_header: LongHeaderPacket, context: Context, crypto: CryptoSinkUnit&): bool {
|
||||||
|
|
||||||
if ( ! long_header.is_initial )
|
if ( ! long_header.is_initial )
|
||||||
return False;
|
return False;
|
||||||
|
|
||||||
if ( is_client )
|
if ( crypto == Null )
|
||||||
return ! context.client_initial_processed;
|
return False;
|
||||||
|
|
||||||
# This is the responder, can only decrypt if we have an initial
|
# Can only decrypt the responder if we've seen the initial destination conn id.
|
||||||
# destination_id from the client
|
if ( ! crypto.is_orig && ! context.initial_destination_conn_id )
|
||||||
return context.client_initial_processed
|
return False;
|
||||||
&& |context.initial_destination_conn_id| > 0
|
|
||||||
&& ! context.server_initial_processed;
|
# Only attempt decryption if we haven't flushed some SSL data yet.
|
||||||
|
return ! crypto.finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectionIDInfo = struct {
|
function reset_crypto(context: Context&) {
|
||||||
|
# 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;
|
||||||
|
context.initial_destination_conn_id = Null;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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: Context&) {
|
||||||
|
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<uint32>($$[0]) << 16) + (cast<uint32>($$[1]) << 8) + cast<uint32>($$[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<uint16>(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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
##############
|
||||||
|
## Context
|
||||||
|
##############
|
||||||
|
type Context = struct {
|
||||||
client_cid_len: uint8;
|
client_cid_len: uint8;
|
||||||
server_cid_len: uint8;
|
server_cid_len: uint8;
|
||||||
|
|
||||||
|
@ -46,26 +123,13 @@ type ConnectionIDInfo = struct {
|
||||||
# will make life miserable.
|
# will make life miserable.
|
||||||
#
|
#
|
||||||
# https://quicwg.org/base-drafts/rfc9001.html#appendix-A
|
# https://quicwg.org/base-drafts/rfc9001.html#appendix-A
|
||||||
initial_destination_conn_id: bytes;
|
initial_destination_conn_id: optional<bytes>;
|
||||||
|
|
||||||
# Currently, this analyzer assumes that ClientHello
|
# Track crypto state.
|
||||||
# and ServerHello fit into the first INITIAL packet (and
|
client_crypto: CryptoSinkUnit&;
|
||||||
# that there is only one that we're interested in.
|
client_sink: sink&;
|
||||||
#
|
server_crypto: CryptoSinkUnit&;
|
||||||
# But minimally the following section sounds like this might not
|
server_sink: sink&;
|
||||||
# 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;
|
|
||||||
|
|
||||||
ssl_handle: zeek::ProtocolHandle &optional;
|
ssl_handle: zeek::ProtocolHandle &optional;
|
||||||
};
|
};
|
||||||
|
@ -272,16 +336,28 @@ public type LongHeaderPacket = unit {
|
||||||
};
|
};
|
||||||
|
|
||||||
# A QUIC Frame.
|
# 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<FrameType>($$);
|
frame_type : uint8 &convert=cast<FrameType>($$);
|
||||||
|
|
||||||
# TODO: add other FrameTypes as well
|
# TODO: add other FrameTypes as well
|
||||||
switch ( self.frame_type ) {
|
switch ( self.frame_type ) {
|
||||||
FrameType::ACK1 -> a: ACKPayload;
|
FrameType::ACK1 -> a: ACKPayload(FrameType::ACK1);
|
||||||
FrameType::ACK2 -> b: ACKPayload;
|
FrameType::ACK2 -> b: ACKPayload(FrameType::ACK2);
|
||||||
FrameType::CRYPTO -> c: CRYPTOPayload(from_client) {
|
FrameType::CRYPTO -> c: CRYPTOPayload(from_client) {
|
||||||
# Have the sink re-assemble potentially out-of-order cryptodata
|
# Have the sink re-assemble potentially out-of-order cryptodata
|
||||||
crypto_sink.write(self.c.cryptodata, self.c.offset.result_);
|
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::CONNECTION_CLOSE1 -> : ConnectionClosePayload(header);
|
||||||
FrameType::PADDING -> : skip /\x00*/; # eat the padding
|
FrameType::PADDING -> : skip /\x00*/; # eat the padding
|
||||||
|
@ -298,11 +374,26 @@ type CRYPTOPayload = unit(from_client: bool) {
|
||||||
cryptodata: bytes &size=self.length.result_;
|
cryptodata: bytes &size=self.length.result_;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ACKPayload = unit {
|
# https://datatracker.ietf.org/doc/html/rfc9000#ack-ranges
|
||||||
|
type ACKRange = unit {
|
||||||
|
gap: VariableLengthInteger;
|
||||||
|
ack_range_length: VariableLengthInteger;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ACKECNCounts = unit {
|
||||||
|
ect0: VariableLengthInteger;
|
||||||
|
ect1: VariableLengthInteger;
|
||||||
|
ecn_ce: VariableLengthInteger;
|
||||||
|
};
|
||||||
|
|
||||||
|
# https://datatracker.ietf.org/doc/html/rfc9000#name-ack-frames
|
||||||
|
type ACKPayload = unit(frame_type: FrameType) {
|
||||||
latest_ack: VariableLengthInteger;
|
latest_ack: VariableLengthInteger;
|
||||||
ack_delay: VariableLengthInteger;
|
ack_delay: VariableLengthInteger;
|
||||||
ack_range_count: VariableLengthInteger;
|
ack_range_count: VariableLengthInteger;
|
||||||
first_ack_range: VariableLengthInteger;
|
first_ack_range: VariableLengthInteger;
|
||||||
|
ack_ranges: ACKRange[self.ack_range_count.result_];
|
||||||
|
ecn_counts: ACKECNCounts if(frame_type == FrameType::ACK2);
|
||||||
};
|
};
|
||||||
|
|
||||||
type ConnectionClosePayload = unit(header: LongHeaderPacket) {
|
type ConnectionClosePayload = unit(header: LongHeaderPacket) {
|
||||||
|
@ -393,35 +484,18 @@ public type ShortPacketPayload = unit {
|
||||||
payload: skip bytes &eod;
|
payload: skip bytes &eod;
|
||||||
};
|
};
|
||||||
|
|
||||||
# TODO: investigate whether we can do something useful with this
|
|
||||||
public type EncryptedLongPacketPayload = 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
|
# QUIC packet parsing
|
||||||
#
|
#
|
||||||
# A UDP datagram contains one or more QUIC packets.
|
# A UDP datagram contains one or more QUIC packets.
|
||||||
##############
|
##############
|
||||||
type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
|
type Packet = unit(from_client: bool, context: Context&) {
|
||||||
var decrypted_data: bytes;
|
var decrypted_data: bytes;
|
||||||
var packet_size: uint64 = 0;
|
var packet_size: uint64 = 0;
|
||||||
var start: iterator<stream>;
|
var start: iterator<stream>;
|
||||||
|
|
||||||
sink crypto_sink;
|
var crypto: CryptoSinkUnit&;
|
||||||
var crypto_buffer: CryptoBuffer&;
|
var crypto_sink: sink&;
|
||||||
|
|
||||||
# Attach an SSL analyzer to this connection once.
|
# Attach an SSL analyzer to this connection once.
|
||||||
on %init {
|
on %init {
|
||||||
|
@ -430,6 +504,26 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.start = self.input();
|
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.
|
# Peek into the first byte and determine the header type.
|
||||||
|
@ -443,7 +537,6 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
|
||||||
self.set_input(self.start); # rewind
|
self.set_input(self.start); # rewind
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Depending on the header, parse it and update the src/dest ConnectionID's
|
# Depending on the header, parse it and update the src/dest ConnectionID's
|
||||||
switch ( self.first_byte.header_form ) {
|
switch ( self.first_byte.header_form ) {
|
||||||
HeaderForm::SHORT -> short_header: ShortHeader(context.client_cid_len);
|
HeaderForm::SHORT -> short_header: ShortHeader(context.client_cid_len);
|
||||||
|
@ -453,19 +546,16 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
|
||||||
# If we see a retry packet from the responder, reset the decryption
|
# 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.
|
# context such that the next DCID from the client is used for decryption.
|
||||||
if ( self.long_header.is_retry ) {
|
if ( self.long_header.is_retry ) {
|
||||||
context.client_initial_processed = False;
|
reset_crypto(context);
|
||||||
context.server_initial_processed = False;
|
|
||||||
context.initial_destination_conn_id = b"";
|
|
||||||
|
|
||||||
# Allow re-opening the SSL analyzer the next time around.
|
self.crypto = Null;
|
||||||
zeek::protocol_handle_close(context.ssl_handle);
|
self.crypto_sink = Null;
|
||||||
unset context.ssl_handle;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
: void {
|
: 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,
|
# If we have parsed an initial packet that we can decrypt the payload,
|
||||||
# determine the size to store into a buffer.
|
# determine the size to store into a buffer.
|
||||||
self.packet_size = self.offset();
|
self.packet_size = self.offset();
|
||||||
|
@ -473,30 +563,29 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
|
||||||
|
|
||||||
# Buffer the whole packet if we determined we have a chance to decrypt.
|
# 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 ) {
|
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 ) {
|
if ( from_client ) {
|
||||||
context.server_cid_len = self.long_header.dest_conn_id_len;
|
context.server_cid_len = self.long_header.dest_conn_id_len;
|
||||||
context.client_cid_len = self.long_header.src_conn_id_len;
|
context.client_cid_len = self.long_header.src_conn_id_len;
|
||||||
|
|
||||||
|
# This is the first INITIAL packet we attempt to decrypt and it is
|
||||||
|
# coming from the client. Use its destination connection ID for
|
||||||
|
# decryption purposes.
|
||||||
|
if ( ! context.initial_destination_conn_id ) {
|
||||||
|
context.initial_destination_conn_id = self.long_header.dest_conn_id;
|
||||||
|
}
|
||||||
|
|
||||||
# This means that here, we can try to decrypt the initial packet!
|
# This means that here, we can try to decrypt the initial packet!
|
||||||
# All data is accessible via the `long_header` unit
|
# All data is accessible via the `long_header` unit
|
||||||
self.decrypted_data = decrypt_crypto_payload(
|
self.decrypted_data = decrypt_crypto_payload(
|
||||||
self.long_header.version,
|
self.long_header.version,
|
||||||
self.packet_data,
|
self.packet_data,
|
||||||
self.long_header.dest_conn_id,
|
*context.initial_destination_conn_id,
|
||||||
self.long_header.encrypted_offset,
|
self.long_header.encrypted_offset,
|
||||||
self.long_header.payload_length,
|
self.long_header.payload_length,
|
||||||
from_client
|
from_client
|
||||||
);
|
);
|
||||||
|
|
||||||
# Assuming that the client set up the connection, this can be considered the first
|
|
||||||
# received Initial from the client. So disable change of ConnectionID's afterwards
|
|
||||||
if ( |context.initial_destination_conn_id| == 0 ) {
|
|
||||||
context.initial_destination_conn_id = self.long_header.dest_conn_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
context.server_cid_len = self.long_header.src_conn_id_len;
|
context.server_cid_len = self.long_header.src_conn_id_len;
|
||||||
context.client_cid_len = self.long_header.dest_conn_id_len;
|
context.client_cid_len = self.long_header.dest_conn_id_len;
|
||||||
|
@ -504,7 +593,7 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
|
||||||
self.decrypted_data = decrypt_crypto_payload(
|
self.decrypted_data = decrypt_crypto_payload(
|
||||||
self.long_header.version,
|
self.long_header.version,
|
||||||
self.packet_data,
|
self.packet_data,
|
||||||
context.initial_destination_conn_id,
|
*context.initial_destination_conn_id,
|
||||||
self.long_header.encrypted_offset,
|
self.long_header.encrypted_offset,
|
||||||
self.long_header.payload_length,
|
self.long_header.payload_length,
|
||||||
from_client
|
from_client
|
||||||
|
@ -521,51 +610,24 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
|
||||||
spicy::accept_input();
|
spicy::accept_input();
|
||||||
}
|
}
|
||||||
|
|
||||||
# Depending on the type of header and whether we were able to decrypt
|
# If this packet has a SHORT header, consume until &eod, there's nothing
|
||||||
# some of it, parse the remaining payload.
|
# we can do with it anyhow.
|
||||||
: ShortPacketPayload if (self.first_byte.header_form == HeaderForm::SHORT);
|
: ShortPacketPayload if (self.first_byte.header_form == HeaderForm::SHORT);
|
||||||
: EncryptedLongPacketPayload if (self.first_byte.header_form == HeaderForm::LONG && |self.decrypted_data| == 0);
|
|
||||||
|
|
||||||
# If this was packet with a long header and decrypted data exists, attempt
|
# If this was packet with a long header and decrypted data exists, attempt
|
||||||
# to parse the plain QUIC frames from it.
|
# 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);
|
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);
|
||||||
|
|
||||||
# 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<uint16>(|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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
##############
|
##############
|
||||||
# Entrypoints
|
# Entrypoints
|
||||||
##############
|
##############
|
||||||
public type RequestFrame = unit {
|
public type RequestFrame = unit {
|
||||||
%context = ConnectionIDInfo;
|
%context = Context;
|
||||||
: Packet(True, self.context())[];
|
: Packet(True, self.context())[];
|
||||||
};
|
};
|
||||||
|
|
||||||
public type ResponseFrame = unit {
|
public type ResponseFrame = unit {
|
||||||
%context = ConnectionIDInfo;
|
%context = Context;
|
||||||
: Packet(False, self.context())[];
|
: Packet(False, self.context())[];
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,5 +7,5 @@
|
||||||
#open XXXX-XX-XX-XX-XX-XX
|
#open XXXX-XX-XX-XX-XX-XX
|
||||||
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version client_initial_dcid client_scid server_scid server_name client_protocol history
|
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version client_initial_dcid client_scid server_scid server_name client_protocol history
|
||||||
#types time string addr port addr port string string string string string string string
|
#types time string addr port addr port string string string string string string string
|
||||||
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 172.17.0.2 34347 64.233.166.94 443 1 815d62c70884f4b51e8ccadd5beed372 e5ec6b26584229be98a164349ae910351c40d10b c15d62c70884f4b5 www.google.de h3 ISishIhHhhH
|
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 172.17.0.2 34347 64.233.166.94 443 1 815d62c70884f4b51e8ccadd5beed372 e5ec6b26584229be98a164349ae910351c40d10b c15d62c70884f4b5 www.google.de h3 ISishIHhHhhH
|
||||||
#close XXXX-XX-XX-XX-XX-XX
|
#close XXXX-XX-XX-XX-XX-XX
|
||||||
|
|
|
@ -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
|
|
@ -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 www.google.de ZZZIiIIIISiIIIiiiiiishIIHH
|
|
@ -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 TLSv13 TLS_AES_128_GCM_SHA256 X25519MLKEM768 www.google.de T - - F Cs
|
|
@ -47,3 +47,4 @@ zerortt.pcap
|
||||||
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
|
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
|
||||||
1.0, initial_packet, CtPZjS20MLrsMUOJi2, T, 1, 3ec82f67,
|
1.0, initial_packet, CtPZjS20MLrsMUOJi2, T, 1, 3ec82f67,
|
||||||
1.0, handshake_packet, T, CtPZjS20MLrsMUOJi2, 1, 3ec82f67,
|
1.0, handshake_packet, T, CtPZjS20MLrsMUOJi2, 1, 3ec82f67,
|
||||||
|
1.0, handshake_packet, T, CtPZjS20MLrsMUOJi2, 1, 3ec82f67,
|
||||||
|
|
|
@ -7,6 +7,6 @@
|
||||||
#open XXXX-XX-XX-XX-XX-XX
|
#open XXXX-XX-XX-XX-XX-XX
|
||||||
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version client_initial_dcid client_scid server_scid server_name client_protocol history
|
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version client_initial_dcid client_scid server_scid server_name client_protocol history
|
||||||
#types time string addr port addr port string string string string string string string
|
#types time string addr port addr port string string string string string string string
|
||||||
1.000000 CtPZjS20MLrsMUOJi2 193.167.0.100 49394 193.167.100.100 443 1 15ae5e5e4962163f410b5529fc125bbc (empty) e483a751 server4:443 hq-interop ISZishZZZZZZZZZZZZZZZZZZZZZZZZZZZIH
|
1.000000 CtPZjS20MLrsMUOJi2 193.167.0.100 49394 193.167.100.100 443 1 15ae5e5e4962163f410b5529fc125bbc (empty) e483a751 server4:443 hq-interop ISZishZZZZZZZZZZZZZZZZZZZZZZZZZZZIHH
|
||||||
1.000000 C4J4Th3PJpwUYZZ6gc 193.167.0.100 60492 193.167.100.100 443 1 b7c7841c64883e3261d840 (empty) 8d2041ac server4:443 hq-interop ISishhIH
|
1.000000 C4J4Th3PJpwUYZZ6gc 193.167.0.100 60492 193.167.100.100 443 1 b7c7841c64883e3261d840 (empty) 8d2041ac server4:443 hq-interop ISishhIH
|
||||||
#close XXXX-XX-XX-XX-XX-XX
|
#close XXXX-XX-XX-XX-XX-XX
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -7,5 +7,5 @@
|
||||||
#open XXXX-XX-XX-XX-XX-XX
|
#open XXXX-XX-XX-XX-XX-XX
|
||||||
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version client_initial_dcid client_scid server_scid server_name client_protocol history
|
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version client_initial_dcid client_scid server_scid server_name client_protocol history
|
||||||
#types time string addr port addr port string string string string string string string
|
#types time string addr port addr port string string string string string string string
|
||||||
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 49320 127.0.0.1 443 quicv2 fa603212c8688817af3d3238735bc7 (empty) b168b5cc localhost quic-echo-example ISIIishIH
|
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 49320 127.0.0.1 443 quicv2 fa603212c8688817af3d3238735bc7 (empty) b168b5cc localhost quic-echo-example ISIIishIHH
|
||||||
#close XXXX-XX-XX-XX-XX-XX
|
#close XXXX-XX-XX-XX-XX-XX
|
||||||
|
|
BIN
testing/btest/Traces/quic/quic-decrypt-fail-google-de-51833.pcap
Normal file
BIN
testing/btest/Traces/quic/quic-decrypt-fail-google-de-51833.pcap
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,12 @@
|
||||||
|
# @TEST-DOC: PCAP for which decryption failed due to not using the initial destination connection ID consistently.
|
||||||
|
|
||||||
|
# @TEST-REQUIRES: ${SCRIPTS}/have-spicy
|
||||||
|
# @TEST-EXEC: zeek -Cr $TRACES/quic/quic-decrypt-fail-google-de-51833.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
|
|
@ -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
|
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue