From ccd5da4173bdfad0ba0a7fa21c36a3800b65669c Mon Sep 17 00:00:00 2001 From: Jackson Sippe Date: Fri, 12 Jul 2024 18:31:52 +0000 Subject: [PATCH 01/12] BUG: Add further validation to token_len calculation on retry packets --- core/src/protocols/stream/quic/parser.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/protocols/stream/quic/parser.rs b/core/src/protocols/stream/quic/parser.rs index ab327cbe..2833429d 100644 --- a/core/src/protocols/stream/quic/parser.rs +++ b/core/src/protocols/stream/quic/parser.rs @@ -266,7 +266,11 @@ impl QuicPacket { } LongHeaderPacketType::Retry => { packet_len = None; - token_len = Some((data.len() - offset - 16) as u64); + if data.len() > (offset + 16) { + token_len = Some((data.len() - offset - 16) as u64); + } else { + return Err(QuicError::PacketTooShort); + } // Parse retry token let token_bytes = QuicPacket::access_data( data, From 8be2df6ab6687f0b20cf393db9527f64d20f4bbe Mon Sep 17 00:00:00 2001 From: Jackson Sippe Date: Mon, 15 Jul 2024 22:09:03 +0000 Subject: [PATCH 02/12] First attempt at QUIC payload decryption --- core/Cargo.toml | 2 + core/src/protocols/stream/quic/crypto.rs | 295 +++++++++++++++++++++++ core/src/protocols/stream/quic/mod.rs | 4 + core/src/protocols/stream/quic/parser.rs | 89 ++++++- 4 files changed, 378 insertions(+), 12 deletions(-) create mode 100644 core/src/protocols/stream/quic/crypto.rs diff --git a/core/Cargo.toml b/core/Cargo.toml index c1e32141..71a42790 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -46,6 +46,8 @@ thiserror = "1.0" tls-parser = { git = "https://github.com/thegwan/tls-parser.git" } toml = "0.5.11" x509-parser = "0.13.2" +rust-crypto="0.2.36" +ring = "0.17.8" [features] timing = [] diff --git a/core/src/protocols/stream/quic/crypto.rs b/core/src/protocols/stream/quic/crypto.rs new file mode 100644 index 00000000..ced3782d --- /dev/null +++ b/core/src/protocols/stream/quic/crypto.rs @@ -0,0 +1,295 @@ +// This is heavily based on Cloudflare's Rust implementation of QUIC, known as Quiche. +// Therefore, the original license from https://github.com/cloudflare/quiche/blob/master/quiche/src/crypto/mod.rs is below: + +// Copyright (C) 2018-2019, Cloudflare, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::iter::repeat; + +use crypto::aead::AeadDecryptor; +use crypto::aes::KeySize; +use crypto::aes_gcm::AesGcm; +use ring::aead; +use ring::hkdf; + +use crate::protocols::stream::quic::parser::QuicError; + +#[derive(Copy, Clone, Debug)] +pub enum Algorithm { + AES128GCM, +} + +impl Algorithm { + fn get_ring_hp(self) -> &'static aead::quic::Algorithm { + match self { + Algorithm::AES128GCM => &aead::quic::AES_128, + } + } + + fn get_ring_digest(self) -> hkdf::Algorithm { + match self { + Algorithm::AES128GCM => hkdf::HKDF_SHA256, + } + } + + pub fn key_len(self) -> usize { + match self { + Algorithm::AES128GCM => 16, + } + } + + pub fn tag_len(self) -> usize { + match self { + Algorithm::AES128GCM => 16, + } + } + + pub fn nonce_len(self) -> usize { + match self { + Algorithm::AES128GCM => 12, + } + } + + pub fn get_key_len(self) -> Option { + match self { + Algorithm::AES128GCM => Some(KeySize::KeySize128), + } + } +} + +pub struct Open { + alg: Algorithm, + + key_len: Option, + + initial_key: Vec, + + hp_key: aead::quic::HeaderProtectionKey, + + iv: Vec, +} + +impl Open { + pub fn new(alg: Algorithm, key: &[u8], iv: &[u8], hp_key: &[u8]) -> Result { + Ok(Open { + alg, + + key_len: alg.get_key_len(), + + initial_key: key.to_vec(), + + hp_key: aead::quic::HeaderProtectionKey::new(alg.get_ring_hp(), hp_key) + .map_err(|_| QuicError::CryptoFail)?, + + iv: iv.to_vec(), + }) + } + + pub fn open_with_u64_counter( + &self, + counter: u64, + ad: &[u8], + buf: &mut [u8], + tag: &[u8], + ) -> Result, QuicError> { + let nonce = make_nonce(&self.iv, counter); + let mut cipher = match self.alg { + Algorithm::AES128GCM => { + AesGcm::new(self.key_len.unwrap(), &self.initial_key, &nonce, ad) + } + }; + + let mut out: Vec = repeat(0).take(buf.len()).collect(); + + let rc = cipher.decrypt(buf, &mut out, tag); + + if !rc { + return Err(QuicError::CryptoFail); + } + + Ok(out) + } + + pub fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5], QuicError> { + let mask = self + .hp_key + .new_mask(sample) + .map_err(|_| QuicError::CryptoFail)?; + + Ok(mask) + } + + pub fn alg(&self) -> Algorithm { + self.alg + } + + pub fn sample_len(&self) -> usize { + self.hp_key.algorithm().sample_len() + } +} +impl std::fmt::Debug for Open { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Point") + .field("alg", &self.alg) + .field("iv", &self.iv) + .finish() + } +} + +pub fn calc_init_keys(cid: &[u8], version: u32) -> Result<[Open; 2], QuicError> { + let aead = Algorithm::AES128GCM; + let key_len = aead.key_len(); + let nonce_len = aead.nonce_len(); + let initial_secret = derive_initial_secret(cid, version); + + let mut secret = [0; 32]; + let mut client_key = vec![0; key_len]; + let mut client_iv = vec![0; nonce_len]; + let mut client_hp_key = vec![0; key_len]; + + derive_client_initial_secret(&initial_secret, &mut secret)?; + derive_pkt_key(aead, &secret, &mut client_key)?; + derive_pkt_iv(aead, &secret, &mut client_iv)?; + derive_hdr_key(aead, &secret, &mut client_hp_key)?; + + // Server. + let mut server_key = vec![0; key_len]; + let mut server_iv = vec![0; nonce_len]; + let mut server_hp_key = vec![0; key_len]; + + derive_server_initial_secret(&initial_secret, &mut secret)?; + derive_pkt_key(aead, &secret, &mut server_key)?; + derive_pkt_iv(aead, &secret, &mut server_iv)?; + derive_hdr_key(aead, &secret, &mut server_hp_key)?; + + Ok([ + Open::new(aead, &client_key, &client_iv, &client_hp_key)?, + Open::new(aead, &server_key, &server_iv, &server_hp_key)?, + ]) +} + +fn derive_initial_secret(secret: &[u8], version: u32) -> hkdf::Prk { + const INITIAL_SALT: [u8; 20] = [ + 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, + 0xad, 0xcc, 0xbb, 0x7f, 0x0a, + ]; + + let salt = match version { + _ => &INITIAL_SALT, + }; + + let salt = hkdf::Salt::new(hkdf::HKDF_SHA256, salt); + salt.extract(secret) +} + +fn derive_client_initial_secret(prk: &hkdf::Prk, out: &mut [u8]) -> Result<(), QuicError> { + const LABEL: &[u8] = b"client in"; + hkdf_expand_label(prk, LABEL, out) +} + +fn derive_server_initial_secret(prk: &hkdf::Prk, out: &mut [u8]) -> Result<(), QuicError> { + const LABEL: &[u8] = b"server in"; + hkdf_expand_label(prk, LABEL, out) +} + +pub fn derive_hdr_key(aead: Algorithm, secret: &[u8], out: &mut [u8]) -> Result<(), QuicError> { + const LABEL: &[u8] = b"quic hp"; + + let key_len = aead.key_len(); + + if key_len > out.len() { + return Err(QuicError::CryptoFail); + } + + let secret = hkdf::Prk::new_less_safe(aead.get_ring_digest(), secret); + hkdf_expand_label(&secret, LABEL, &mut out[..key_len]) +} + +pub fn derive_pkt_key(aead: Algorithm, secret: &[u8], out: &mut [u8]) -> Result<(), QuicError> { + const LABEL: &[u8] = b"quic key"; + + let key_len = aead.key_len(); + + if key_len > out.len() { + return Err(QuicError::CryptoFail); + } + + let secret = hkdf::Prk::new_less_safe(aead.get_ring_digest(), secret); + hkdf_expand_label(&secret, LABEL, &mut out[..key_len]) +} + +pub fn derive_pkt_iv(aead: Algorithm, secret: &[u8], out: &mut [u8]) -> Result<(), QuicError> { + const LABEL: &[u8] = b"quic iv"; + + let nonce_len = aead.nonce_len(); + + if nonce_len > out.len() { + return Err(QuicError::CryptoFail); + } + + let secret = hkdf::Prk::new_less_safe(aead.get_ring_digest(), secret); + hkdf_expand_label(&secret, LABEL, &mut out[..nonce_len]) +} + +fn hkdf_expand_label(prk: &hkdf::Prk, label: &[u8], out: &mut [u8]) -> Result<(), QuicError> { + const LABEL_PREFIX: &[u8] = b"tls13 "; + + let out_len = (out.len() as u16).to_be_bytes(); + let label_len = (LABEL_PREFIX.len() + label.len()) as u8; + + let info = [&out_len, &[label_len][..], LABEL_PREFIX, label, &[0][..]]; + + prk.expand(&info, ArbitraryOutputLen(out.len())) + .map_err(|_| QuicError::CryptoFail)? + .fill(out) + .map_err(|_| QuicError::CryptoFail)?; + + Ok(()) +} + +fn make_nonce(iv: &[u8], counter: u64) -> [u8; aead::NONCE_LEN] { + let mut nonce = [0; aead::NONCE_LEN]; + nonce.copy_from_slice(iv); + + // XOR the last bytes of the IV with the counter. This is equivalent to + // left-padding the counter with zero bytes. + for (a, b) in nonce[4..].iter_mut().zip(counter.to_be_bytes().iter()) { + *a ^= b; + } + + nonce +} + +// The ring HKDF expand() API does not accept an arbitrary output length, so we +// need to hide the `usize` length as part of a type that implements the trait +// `ring::hkdf::KeyType` in order to trick ring into accepting it. +struct ArbitraryOutputLen(usize); + +impl hkdf::KeyType for ArbitraryOutputLen { + fn len(&self) -> usize { + self.0 + } +} diff --git a/core/src/protocols/stream/quic/mod.rs b/core/src/protocols/stream/quic/mod.rs index 0bb2067a..1cc0e413 100644 --- a/core/src/protocols/stream/quic/mod.rs +++ b/core/src/protocols/stream/quic/mod.rs @@ -26,6 +26,7 @@ pub use self::header::{QuicLongHeader, QuicShortHeader}; use header::LongHeaderPacketType; use parser::QuicError; use serde::Serialize; +pub(crate) mod crypto; pub(crate) mod header; /// Parsed Quic Packet contents @@ -39,6 +40,9 @@ pub struct QuicPacket { /// The number of bytes contained in the estimated payload pub payload_bytes_count: Option, + + // The decrypted QUIC packet payload + pub decrypted_payload: Option>, } impl QuicPacket { diff --git a/core/src/protocols/stream/quic/parser.rs b/core/src/protocols/stream/quic/parser.rs index 2833429d..86895158 100644 --- a/core/src/protocols/stream/quic/parser.rs +++ b/core/src/protocols/stream/quic/parser.rs @@ -2,6 +2,7 @@ //! Custom Quic Parser with many design choices borrowed from //! [Wireshark Quic Disector](https://gitlab.com/wireshark/wireshark/-/blob/master/epan/dissectors/packet-quic.c) //! +use crate::protocols::stream::quic::crypto::calc_init_keys; use crate::protocols::stream::quic::header::{ LongHeaderPacketType, QuicLongHeader, QuicShortHeader, }; @@ -9,6 +10,7 @@ use crate::protocols::stream::quic::QuicPacket; use crate::protocols::stream::{ ConnParsable, ConnState, L4Pdu, ParseResult, ProbeResult, Session, SessionData, }; +use byteorder::{BigEndian, ByteOrder}; use std::collections::HashMap; #[derive(Default, Debug)] @@ -129,6 +131,8 @@ pub enum QuicError { NoLongHeader, UnsupportedVarLen, InvalidDataIndices, + CryptoFail, + FailedHeaderProtection, } impl QuicPacket { @@ -178,7 +182,7 @@ impl QuicPacket { } /// Parses Quic packet from bytes - pub fn parse_from(data: &[u8]) -> Result { + pub fn parse_from(data: &[u8], cnt: usize) -> Result { let mut offset = 0; let packet_header_byte = QuicPacket::access_data(data, offset, offset + 1)?[0]; offset += 1; @@ -219,6 +223,7 @@ impl QuicPacket { let token; let packet_len; let retry_tag; + let decrypted_payload; // Parse packet type specific fields match packet_type { LongHeaderPacketType::Initial => { @@ -227,11 +232,9 @@ impl QuicPacket { let token_len_len = QuicPacket::get_var_len( QuicPacket::access_data(data, offset, offset + 1)?[0], )?; - token_len = Some(QuicPacket::slice_to_u64(QuicPacket::access_data( - data, - offset, - offset + token_len_len, - )?)?); + let token_len_bytes = + QuicPacket::access_data(data, offset, offset + token_len_len)?; + token_len = Some(QuicPacket::slice_to_u64(token_len_bytes)?); offset += token_len_len; let token_bytes = QuicPacket::access_data( data, @@ -244,16 +247,75 @@ impl QuicPacket { let packet_len_len = QuicPacket::get_var_len( QuicPacket::access_data(data, offset, offset + 1)?[0], )?; - packet_len = Some(QuicPacket::slice_to_u64(QuicPacket::access_data( - data, - offset, - offset + packet_len_len, - )?)?); + let packet_len_bytes = + QuicPacket::access_data(data, offset, offset + packet_len_len)?; + packet_len = Some(QuicPacket::slice_to_u64(packet_len_bytes)?); + offset += packet_len_len; + if cnt == 0 { + // Derive initial keys + let [client_opener, server_opener] = calc_init_keys(dcid_bytes, version)?; + // Calculate HP + let sample_len = client_opener.sample_len(); + let hp_sample = + QuicPacket::access_data(data, offset + 4, offset + 4 + sample_len)?; + let mask = client_opener.new_mask(hp_sample)?; + // Remove HP from packet header byte + let unprotected_header = packet_header_byte ^ (mask[0] & 0b00001111); + if (unprotected_header >> 2) & 0b00000011 != 0 { + return Err(QuicError::FailedHeaderProtection); + } + // Parse packet number + let packet_num_len = ((unprotected_header & 0b00000011) + 1) as usize; + let packet_number_bytes = + QuicPacket::access_data(data, offset, offset + packet_num_len)?; + let mut packet_number = [0; 4]; + for i in 0..packet_num_len { + packet_number[4 - (i + 1)] = packet_number_bytes[i] ^ mask[i + 1]; + } + let initial_packet_number_bytes = &packet_number[4 - packet_num_len..]; + let packet_number_int = BigEndian::read_i32(&packet_number); + offset += packet_num_len; + // Parse the encrypted payload + let tag_len = client_opener.alg().tag_len(); + if (packet_len.unwrap() as usize) < (tag_len + packet_num_len) { + return Err(QuicError::PacketTooShort); + } + let cipher_text_len = + packet_len.unwrap() as usize - tag_len - packet_num_len; + let mut encrypted_payload = + QuicPacket::access_data(data, offset, offset + cipher_text_len)? + .to_vec(); + offset += cipher_text_len; + // Parse auth tag + let tag = QuicPacket::access_data(data, offset, offset + tag_len)?; + offset += tag_len; + // Reconstruct authenticated data + let mut ad = Vec::new(); + ad.append(&mut [unprotected_header].to_vec()); + ad.append(&mut version_bytes.to_vec()); + ad.append(&mut [dcid_len].to_vec()); + ad.append(&mut dcid_bytes.to_vec()); + ad.append(&mut [scid_len].to_vec()); + ad.append(&mut scid_bytes.to_vec()); + ad.append(&mut token_len_bytes.to_vec()); + ad.append(&mut token_bytes.to_vec()); + ad.append(&mut packet_len_bytes.to_vec()); + ad.append(&mut initial_packet_number_bytes.to_vec()); + decrypted_payload = Some(client_opener.open_with_u64_counter( + packet_number_int as u64, + &ad, + &mut encrypted_payload, + tag, + )?); + } else { + decrypted_payload = None; + } } LongHeaderPacketType::ZeroRTT | LongHeaderPacketType::Handshake => { token_len = None; token = None; retry_tag = None; + decrypted_payload = None; // Parse payload length let packet_len_len = QuicPacket::get_var_len( QuicPacket::access_data(data, offset, offset + 1)?[0], @@ -266,6 +328,7 @@ impl QuicPacket { } LongHeaderPacketType::Retry => { packet_len = None; + decrypted_payload = None; if data.len() > (offset + 16) { token_len = Some((data.len() - offset - 16) as u64); } else { @@ -300,6 +363,7 @@ impl QuicPacket { token, retry_tag, }), + decrypted_payload, }) } else { // Short Header @@ -319,6 +383,7 @@ impl QuicPacket { }), long_header: None, payload_bytes_count, + decrypted_payload: None, }) } } @@ -326,7 +391,7 @@ impl QuicPacket { impl QuicParser { fn process(&mut self, data: &[u8]) -> ParseResult { - if let Ok(quic) = QuicPacket::parse_from(data) { + if let Ok(quic) = QuicPacket::parse_from(data, self.cnt) { let session_id = self.cnt; self.sessions.insert(session_id, quic); self.cnt += 1; From 477c711c42cf3e4e7382cc8e53f8608108cd0b44 Mon Sep 17 00:00:00 2001 From: Jackson Sippe Date: Tue, 16 Jul 2024 22:09:50 +0000 Subject: [PATCH 03/12] Frame parsing and refactor of QuicError --- core/src/protocols/stream/quic/crypto.rs | 2 +- core/src/protocols/stream/quic/frame.rs | 127 +++++++++++++++++++++++ core/src/protocols/stream/quic/header.rs | 2 +- core/src/protocols/stream/quic/mod.rs | 22 +++- core/src/protocols/stream/quic/parser.rs | 34 +++--- 5 files changed, 162 insertions(+), 25 deletions(-) create mode 100644 core/src/protocols/stream/quic/frame.rs diff --git a/core/src/protocols/stream/quic/crypto.rs b/core/src/protocols/stream/quic/crypto.rs index ced3782d..7021ca24 100644 --- a/core/src/protocols/stream/quic/crypto.rs +++ b/core/src/protocols/stream/quic/crypto.rs @@ -35,7 +35,7 @@ use crypto::aes_gcm::AesGcm; use ring::aead; use ring::hkdf; -use crate::protocols::stream::quic::parser::QuicError; +use crate::protocols::stream::quic::QuicError; #[derive(Copy, Clone, Debug)] pub enum Algorithm { diff --git a/core/src/protocols/stream/quic/frame.rs b/core/src/protocols/stream/quic/frame.rs new file mode 100644 index 00000000..570da01d --- /dev/null +++ b/core/src/protocols/stream/quic/frame.rs @@ -0,0 +1,127 @@ +// QUIC Frame types and parsing +// Implemented per RFC 9000: https://datatracker.ietf.org/doc/html/rfc9000#name-frame-types-and-formats + +use serde::Serialize; + +use crate::protocols::stream::quic::QuicError; +use crate::protocols::stream::quic::QuicPacket; + +// Types of supported QUIC frames +// Currently only includes those seen in the Init and Handshake packets +#[derive(Debug, Serialize, Clone)] +pub enum QuicFrame { + Padding { length: usize }, + Ping, + Ack { largest_acknowledged: u64, ack_delay: u64, first_ack_range: u64, ack_ranges: Vec, ecn_counts: Option }, + Crypto { offset: u64, data: Vec }, +} + +// ACK Range field, part of ACK frame +// https://datatracker.ietf.org/doc/html/rfc9000#ack-range-format +#[derive(Debug, Serialize, Clone)] +pub struct AckRange { + gap: u64, + ack_range_len: u64, +} + +// ECN Counts field, part of some ACK frames +// https://datatracker.ietf.org/doc/html/rfc9000#ecn-count-format +#[derive(Debug, Serialize, Clone)] +pub struct EcnCounts { + ect0_count: u64, + ect1_count: u64, + ecn_ce_count: u64, +} + +impl QuicFrame { + // parse_frames takes the plaintext QUIC packet payload and parses the frame list + pub fn parse_frames(data: &[u8]) -> Result, QuicError> { + let mut frames: Vec = Vec::new(); + let mut offset = 0; + // Iterate over plaintext payload bytes, this is a list of frames + while offset < data.len() { + // Parse frame type + let frame_type_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; + let frame_type = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+frame_type_len)?)?; + offset += frame_type_len; + match frame_type { + 0x00 => { + // Handle PADDING + let mut length = 0; + while offset+length+1 < data.len() && QuicPacket::access_data(data, offset+length, offset+length+1)?[0] == 0 { + length += 1; + } + offset += length; + length += frame_type_len; // Add the original frame type bytes to length. Wireshark also does this + frames.push(QuicFrame::Padding { length: length }); + } + 0x01 => { + // Handle PING + frames.push(QuicFrame::Ping); + }, + 0x02 | 0x03 => { + // Handle ACK + // Parse Largest Acknowledged + let largest_acknowledged_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; + let largest_acknowledged = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+largest_acknowledged_len)?)?; + offset += largest_acknowledged_len; + // Parse ACK Delay + let ack_delay_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; + let ack_delay = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+ack_delay_len)?)?; + offset += ack_delay_len; + // Parse ACK Range Count + let ack_range_count_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; + let ack_range_count = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+ack_range_count_len)?)?; + offset += ack_range_count_len; + // Parse First ACK Range + let first_ack_range_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; + let first_ack_range = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+first_ack_range_len)?)?; + // Parse ACK Range list field + let mut ack_ranges = Vec::new(); + for _ in 0..ack_range_count { + let gap_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; + let gap = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+gap_len)?)?; + offset += gap_len; + let ack_range_len_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; + let ack_range_len = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+ack_range_len_len)?)?; + offset += ack_range_len_len; + ack_ranges.push(AckRange{gap, ack_range_len}) + } + // Parse ECN Counts, if the ACK frame contains them + let ecn_counts: Option; + if frame_type == 0x03 { + let ect0_count_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; + let ect0_count = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+ect0_count_len)?)?; + offset += ect0_count_len; + let ect1_count_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; + let ect1_count = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+ect1_count_len)?)?; + offset += ect1_count_len; + let ecn_ce_count_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; + let ecn_ce_count = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+ecn_ce_count_len)?)?; + ecn_counts = Some(EcnCounts{ect0_count, ect1_count, ecn_ce_count}); + } else { + ecn_counts = None; + } + frames.push(QuicFrame::Ack { largest_acknowledged, ack_delay, first_ack_range, ack_ranges, ecn_counts }) + } + 0x06 => { + // Handle CRYPTO frame + // Parse offset + let crypto_offset_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; + let crypto_offset = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+crypto_offset_len)?)?; + offset += crypto_offset_len; + // Parse length + let crypto_len_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; + let crypto_len = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+crypto_len_len)?)? as usize; + offset += crypto_len_len; + // Parse data + let crypto_data = QuicPacket::access_data(data, offset, offset+crypto_len)?; + frames.push(QuicFrame::Crypto{offset: crypto_offset, data: crypto_data.to_vec()}); + offset += crypto_len; + }, + _ => return Err(QuicError::UnknownFrameType), + } + } + Ok(frames) + } +} \ No newline at end of file diff --git a/core/src/protocols/stream/quic/header.rs b/core/src/protocols/stream/quic/header.rs index f9386c3a..934e89d4 100644 --- a/core/src/protocols/stream/quic/header.rs +++ b/core/src/protocols/stream/quic/header.rs @@ -2,7 +2,7 @@ use serde::Serialize; -use crate::protocols::stream::quic::parser::QuicError; +use crate::protocols::stream::quic::QuicError; /// Quic Long Header #[derive(Debug, Serialize, Clone)] diff --git a/core/src/protocols/stream/quic/mod.rs b/core/src/protocols/stream/quic/mod.rs index 1cc0e413..023e5cff 100644 --- a/core/src/protocols/stream/quic/mod.rs +++ b/core/src/protocols/stream/quic/mod.rs @@ -23,11 +23,28 @@ TODO: support HTTP/3 pub(crate) mod parser; pub use self::header::{QuicLongHeader, QuicShortHeader}; +use frame::QuicFrame; use header::LongHeaderPacketType; -use parser::QuicError; use serde::Serialize; pub(crate) mod crypto; pub(crate) mod header; +pub(crate) mod frame; + +/// Errors Thrown throughout QUIC parsing. These are handled by retina and used to skip packets. +#[derive(Debug)] +pub enum QuicError { + FixedBitNotSet, + PacketTooShort, + UnknownVersion, + ShortHeader, + UnknowLongHeaderPacketType, + NoLongHeader, + UnsupportedVarLen, + InvalidDataIndices, + CryptoFail, + FailedHeaderProtection, + UnknownFrameType, +} /// Parsed Quic Packet contents #[derive(Debug, Serialize, Clone)] @@ -41,8 +58,7 @@ pub struct QuicPacket { /// The number of bytes contained in the estimated payload pub payload_bytes_count: Option, - // The decrypted QUIC packet payload - pub decrypted_payload: Option>, + pub frames: Option>, } impl QuicPacket { diff --git a/core/src/protocols/stream/quic/parser.rs b/core/src/protocols/stream/quic/parser.rs index 86895158..35a03ac5 100644 --- a/core/src/protocols/stream/quic/parser.rs +++ b/core/src/protocols/stream/quic/parser.rs @@ -6,10 +6,11 @@ use crate::protocols::stream::quic::crypto::calc_init_keys; use crate::protocols::stream::quic::header::{ LongHeaderPacketType, QuicLongHeader, QuicShortHeader, }; -use crate::protocols::stream::quic::QuicPacket; +use crate::protocols::stream::quic::{QuicPacket, QuicError}; use crate::protocols::stream::{ ConnParsable, ConnState, L4Pdu, ParseResult, ProbeResult, Session, SessionData, }; +use crate::protocols::stream::quic::frame::QuicFrame; use byteorder::{BigEndian, ByteOrder}; use std::collections::HashMap; @@ -120,21 +121,6 @@ impl QuicVersion { } } -/// Errors Thrown by Quic Parser. These are handled by retina and used to skip packets. -#[derive(Debug)] -pub enum QuicError { - FixedBitNotSet, - PacketTooShort, - UnknownVersion, - ShortHeader, - UnknowLongHeaderPacketType, - NoLongHeader, - UnsupportedVarLen, - InvalidDataIndices, - CryptoFail, - FailedHeaderProtection, -} - impl QuicPacket { /// Processes the connection ID bytes array to a hex string pub fn vec_u8_to_hex_string(vec: &[u8]) -> String { @@ -158,7 +144,7 @@ impl QuicPacket { } // Masks variable length encoding and returns u64 value for remainder of field - fn slice_to_u64(data: &[u8]) -> Result { + pub fn slice_to_u64(data: &[u8]) -> Result { if data.len() > 8 { return Err(QuicError::UnsupportedVarLen); } @@ -171,7 +157,7 @@ impl QuicPacket { Ok(result) } - fn access_data(data: &[u8], start: usize, end: usize) -> Result<&[u8], QuicError> { + pub fn access_data(data: &[u8], start: usize, end: usize) -> Result<&[u8], QuicError> { if end < start { return Err(QuicError::InvalidDataIndices); } @@ -348,6 +334,14 @@ impl QuicPacket { } } + let frames: Option>; + // If decrypted payload is not None, parse the frames + if let Some(frame_bytes) = decrypted_payload { + frames = Some(QuicFrame::parse_frames(&frame_bytes)?); + } else { + frames = None; + } + Ok(QuicPacket { payload_bytes_count: packet_len, short_header: None, @@ -363,7 +357,7 @@ impl QuicPacket { token, retry_tag, }), - decrypted_payload, + frames: frames, }) } else { // Short Header @@ -383,7 +377,7 @@ impl QuicPacket { }), long_header: None, payload_bytes_count, - decrypted_payload: None, + frames: None, }) } } From f243f89c22e8ff69be865ece1856f89a02040fd6 Mon Sep 17 00:00:00 2001 From: Jackson Sippe Date: Tue, 16 Jul 2024 22:25:27 +0000 Subject: [PATCH 04/12] Formatting and clippy --- core/src/protocols/stream/quic/frame.rs | 181 +++++++++++++++++------ core/src/protocols/stream/quic/mod.rs | 2 +- core/src/protocols/stream/quic/parser.rs | 6 +- 3 files changed, 143 insertions(+), 46 deletions(-) diff --git a/core/src/protocols/stream/quic/frame.rs b/core/src/protocols/stream/quic/frame.rs index 570da01d..0c10fae1 100644 --- a/core/src/protocols/stream/quic/frame.rs +++ b/core/src/protocols/stream/quic/frame.rs @@ -10,10 +10,21 @@ use crate::protocols::stream::quic::QuicPacket; // Currently only includes those seen in the Init and Handshake packets #[derive(Debug, Serialize, Clone)] pub enum QuicFrame { - Padding { length: usize }, + Padding { + length: usize, + }, Ping, - Ack { largest_acknowledged: u64, ack_delay: u64, first_ack_range: u64, ack_ranges: Vec, ecn_counts: Option }, - Crypto { offset: u64, data: Vec }, + Ack { + largest_acknowledged: u64, + ack_delay: u64, + first_ack_range: u64, + ack_ranges: Vec, + ecn_counts: Option, + }, + Crypto { + offset: u64, + data: Vec, + }, } // ACK Range field, part of ACK frame @@ -41,87 +52,173 @@ impl QuicFrame { // Iterate over plaintext payload bytes, this is a list of frames while offset < data.len() { // Parse frame type - let frame_type_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; - let frame_type = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+frame_type_len)?)?; + let frame_type_len = + QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset + 1)?[0])?; + let frame_type = QuicPacket::slice_to_u64(QuicPacket::access_data( + data, + offset, + offset + frame_type_len, + )?)?; offset += frame_type_len; match frame_type { 0x00 => { // Handle PADDING let mut length = 0; - while offset+length+1 < data.len() && QuicPacket::access_data(data, offset+length, offset+length+1)?[0] == 0 { - length += 1; + while offset + length + 1 < data.len() + && QuicPacket::access_data(data, offset + length, offset + length + 1)?[0] + == 0 + { + length += 1; } offset += length; length += frame_type_len; // Add the original frame type bytes to length. Wireshark also does this - frames.push(QuicFrame::Padding { length: length }); + frames.push(QuicFrame::Padding { length }); } 0x01 => { // Handle PING frames.push(QuicFrame::Ping); - }, + } 0x02 | 0x03 => { // Handle ACK // Parse Largest Acknowledged - let largest_acknowledged_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; - let largest_acknowledged = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+largest_acknowledged_len)?)?; + let largest_acknowledged_len = QuicPacket::get_var_len( + QuicPacket::access_data(data, offset, offset + 1)?[0], + )?; + let largest_acknowledged = QuicPacket::slice_to_u64(QuicPacket::access_data( + data, + offset, + offset + largest_acknowledged_len, + )?)?; offset += largest_acknowledged_len; // Parse ACK Delay - let ack_delay_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; - let ack_delay = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+ack_delay_len)?)?; + let ack_delay_len = QuicPacket::get_var_len( + QuicPacket::access_data(data, offset, offset + 1)?[0], + )?; + let ack_delay = QuicPacket::slice_to_u64(QuicPacket::access_data( + data, + offset, + offset + ack_delay_len, + )?)?; offset += ack_delay_len; // Parse ACK Range Count - let ack_range_count_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; - let ack_range_count = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+ack_range_count_len)?)?; + let ack_range_count_len = QuicPacket::get_var_len( + QuicPacket::access_data(data, offset, offset + 1)?[0], + )?; + let ack_range_count = QuicPacket::slice_to_u64(QuicPacket::access_data( + data, + offset, + offset + ack_range_count_len, + )?)?; offset += ack_range_count_len; // Parse First ACK Range - let first_ack_range_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; - let first_ack_range = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+first_ack_range_len)?)?; + let first_ack_range_len = QuicPacket::get_var_len( + QuicPacket::access_data(data, offset, offset + 1)?[0], + )?; + let first_ack_range = QuicPacket::slice_to_u64(QuicPacket::access_data( + data, + offset, + offset + first_ack_range_len, + )?)?; // Parse ACK Range list field let mut ack_ranges = Vec::new(); for _ in 0..ack_range_count { - let gap_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; - let gap = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+gap_len)?)?; + let gap_len = QuicPacket::get_var_len( + QuicPacket::access_data(data, offset, offset + 1)?[0], + )?; + let gap = QuicPacket::slice_to_u64(QuicPacket::access_data( + data, + offset, + offset + gap_len, + )?)?; offset += gap_len; - let ack_range_len_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; - let ack_range_len = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+ack_range_len_len)?)?; + let ack_range_len_len = QuicPacket::get_var_len( + QuicPacket::access_data(data, offset, offset + 1)?[0], + )?; + let ack_range_len = QuicPacket::slice_to_u64(QuicPacket::access_data( + data, + offset, + offset + ack_range_len_len, + )?)?; offset += ack_range_len_len; - ack_ranges.push(AckRange{gap, ack_range_len}) + ack_ranges.push(AckRange { gap, ack_range_len }) } // Parse ECN Counts, if the ACK frame contains them - let ecn_counts: Option; - if frame_type == 0x03 { - let ect0_count_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; - let ect0_count = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+ect0_count_len)?)?; + let ecn_counts: Option = if frame_type == 0x03 { + let ect0_count_len = QuicPacket::get_var_len( + QuicPacket::access_data(data, offset, offset + 1)?[0], + )?; + let ect0_count = QuicPacket::slice_to_u64(QuicPacket::access_data( + data, + offset, + offset + ect0_count_len, + )?)?; offset += ect0_count_len; - let ect1_count_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; - let ect1_count = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+ect1_count_len)?)?; + let ect1_count_len = QuicPacket::get_var_len( + QuicPacket::access_data(data, offset, offset + 1)?[0], + )?; + let ect1_count = QuicPacket::slice_to_u64(QuicPacket::access_data( + data, + offset, + offset + ect1_count_len, + )?)?; offset += ect1_count_len; - let ecn_ce_count_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; - let ecn_ce_count = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+ecn_ce_count_len)?)?; - ecn_counts = Some(EcnCounts{ect0_count, ect1_count, ecn_ce_count}); + let ecn_ce_count_len = QuicPacket::get_var_len( + QuicPacket::access_data(data, offset, offset + 1)?[0], + )?; + let ecn_ce_count = QuicPacket::slice_to_u64(QuicPacket::access_data( + data, + offset, + offset + ecn_ce_count_len, + )?)?; + Some(EcnCounts { + ect0_count, + ect1_count, + ecn_ce_count, + }) } else { - ecn_counts = None; - } - frames.push(QuicFrame::Ack { largest_acknowledged, ack_delay, first_ack_range, ack_ranges, ecn_counts }) + None + }; + frames.push(QuicFrame::Ack { + largest_acknowledged, + ack_delay, + first_ack_range, + ack_ranges, + ecn_counts, + }) } 0x06 => { // Handle CRYPTO frame // Parse offset - let crypto_offset_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; - let crypto_offset = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+crypto_offset_len)?)?; + let crypto_offset_len = QuicPacket::get_var_len( + QuicPacket::access_data(data, offset, offset + 1)?[0], + )?; + let crypto_offset = QuicPacket::slice_to_u64(QuicPacket::access_data( + data, + offset, + offset + crypto_offset_len, + )?)?; offset += crypto_offset_len; // Parse length - let crypto_len_len = QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset+1)?[0])?; - let crypto_len = QuicPacket::slice_to_u64(QuicPacket::access_data(data, offset, offset+crypto_len_len)?)? as usize; + let crypto_len_len = QuicPacket::get_var_len( + QuicPacket::access_data(data, offset, offset + 1)?[0], + )?; + let crypto_len = QuicPacket::slice_to_u64(QuicPacket::access_data( + data, + offset, + offset + crypto_len_len, + )?)? as usize; offset += crypto_len_len; // Parse data - let crypto_data = QuicPacket::access_data(data, offset, offset+crypto_len)?; - frames.push(QuicFrame::Crypto{offset: crypto_offset, data: crypto_data.to_vec()}); + let crypto_data = QuicPacket::access_data(data, offset, offset + crypto_len)?; + frames.push(QuicFrame::Crypto { + offset: crypto_offset, + data: crypto_data.to_vec(), + }); offset += crypto_len; - }, + } _ => return Err(QuicError::UnknownFrameType), } } Ok(frames) } -} \ No newline at end of file +} diff --git a/core/src/protocols/stream/quic/mod.rs b/core/src/protocols/stream/quic/mod.rs index 023e5cff..aca292c0 100644 --- a/core/src/protocols/stream/quic/mod.rs +++ b/core/src/protocols/stream/quic/mod.rs @@ -27,8 +27,8 @@ use frame::QuicFrame; use header::LongHeaderPacketType; use serde::Serialize; pub(crate) mod crypto; -pub(crate) mod header; pub(crate) mod frame; +pub(crate) mod header; /// Errors Thrown throughout QUIC parsing. These are handled by retina and used to skip packets. #[derive(Debug)] diff --git a/core/src/protocols/stream/quic/parser.rs b/core/src/protocols/stream/quic/parser.rs index 35a03ac5..bd8f775a 100644 --- a/core/src/protocols/stream/quic/parser.rs +++ b/core/src/protocols/stream/quic/parser.rs @@ -3,14 +3,14 @@ //! [Wireshark Quic Disector](https://gitlab.com/wireshark/wireshark/-/blob/master/epan/dissectors/packet-quic.c) //! use crate::protocols::stream::quic::crypto::calc_init_keys; +use crate::protocols::stream::quic::frame::QuicFrame; use crate::protocols::stream::quic::header::{ LongHeaderPacketType, QuicLongHeader, QuicShortHeader, }; -use crate::protocols::stream::quic::{QuicPacket, QuicError}; +use crate::protocols::stream::quic::{QuicError, QuicPacket}; use crate::protocols::stream::{ ConnParsable, ConnState, L4Pdu, ParseResult, ProbeResult, Session, SessionData, }; -use crate::protocols::stream::quic::frame::QuicFrame; use byteorder::{BigEndian, ByteOrder}; use std::collections::HashMap; @@ -357,7 +357,7 @@ impl QuicPacket { token, retry_tag, }), - frames: frames, + frames, }) } else { // Short Header From 67004f6f9b8525bfd44062fcc975449c1e04a0a4 Mon Sep 17 00:00:00 2001 From: Jackson Sippe Date: Fri, 19 Jul 2024 19:50:43 +0000 Subject: [PATCH 05/12] Add ClientHello parsing and CRYPTO frame reassembly --- core/src/protocols/stream/quic/crypto.rs | 37 +++++++++++++++-- core/src/protocols/stream/quic/frame.rs | 21 +++++++--- core/src/protocols/stream/quic/mod.rs | 8 +++- core/src/protocols/stream/quic/parser.rs | 49 +++++++++++++++++------ core/src/subscription/quic_stream.rs | 2 +- traces/README.md | 11 ++--- traces/quic_xargs.pcap | Bin 0 -> 4579 bytes 7 files changed, 100 insertions(+), 28 deletions(-) create mode 100644 traces/quic_xargs.pcap diff --git a/core/src/protocols/stream/quic/crypto.rs b/core/src/protocols/stream/quic/crypto.rs index 7021ca24..bf5af73a 100644 --- a/core/src/protocols/stream/quic/crypto.rs +++ b/core/src/protocols/stream/quic/crypto.rs @@ -1,4 +1,7 @@ -// This is heavily based on Cloudflare's Rust implementation of QUIC, known as Quiche. +// crypto.rs contains the cryptograpic functions needed to derive QUIC +// initial keys. These keys can be used to remove header protection and +// decrypt QUIC initial packets. This file is heavily based on Cloudflare's +// crypto module in their Rust implementation of QUIC, known as Quiche. // Therefore, the original license from https://github.com/cloudflare/quiche/blob/master/quiche/src/crypto/mod.rs is below: // Copyright (C) 2018-2019, Cloudflare, Inc. @@ -35,8 +38,12 @@ use crypto::aes_gcm::AesGcm; use ring::aead; use ring::hkdf; +use crate::protocols::stream::quic::parser::QuicVersion; use crate::protocols::stream::quic::QuicError; +// The algorithm enum defines the available +// cryptographic algorithms used to secure +// QUIC packets. #[derive(Copy, Clone, Debug)] pub enum Algorithm { AES128GCM, @@ -80,6 +87,9 @@ impl Algorithm { } } +// The Open struct gives a return value +// that contains all of the components +// needed for HP removal and decryption pub struct Open { alg: Algorithm, @@ -192,13 +202,32 @@ pub fn calc_init_keys(cid: &[u8], version: u32) -> Result<[Open; 2], QuicError> } fn derive_initial_secret(secret: &[u8], version: u32) -> hkdf::Prk { - const INITIAL_SALT: [u8; 20] = [ + const INITIAL_SALT_RFC9000: [u8; 20] = [ 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a, ]; - let salt = match version { - _ => &INITIAL_SALT, + const INITIAL_SALT_RFC9369: [u8; 20] = [ + 0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, + 0xcb, 0xf9, 0xbd, 0x2e, 0xd9, + ]; + + const INITIAL_SALT_DRAFT29: [u8; 20] = [ + 0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, + 0xe0, 0x43, 0x90, 0xa8, 0x99, + ]; + + const INITIAL_SALT_DRAFT27: [u8; 20] = [ + 0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4, 0x63, + 0x65, 0xbe, 0xf9, 0xf5, 0x02, + ]; + + let salt = match QuicVersion::from_u32(version) { + QuicVersion::Rfc9000 => &INITIAL_SALT_RFC9000, + QuicVersion::Rfc9369 => &INITIAL_SALT_RFC9369, + QuicVersion::Draft29 => &INITIAL_SALT_DRAFT29, + QuicVersion::Draft27 | QuicVersion::Draft28 | QuicVersion::Mvfst27 => &INITIAL_SALT_DRAFT27, + _ => &INITIAL_SALT_RFC9000, }; let salt = hkdf::Salt::new(hkdf::HKDF_SHA256, salt); diff --git a/core/src/protocols/stream/quic/frame.rs b/core/src/protocols/stream/quic/frame.rs index 0c10fae1..f66d6cef 100644 --- a/core/src/protocols/stream/quic/frame.rs +++ b/core/src/protocols/stream/quic/frame.rs @@ -2,6 +2,7 @@ // Implemented per RFC 9000: https://datatracker.ietf.org/doc/html/rfc9000#name-frame-types-and-formats use serde::Serialize; +use std::collections::BTreeMap; use crate::protocols::stream::quic::QuicError; use crate::protocols::stream::quic::QuicPacket; @@ -23,7 +24,6 @@ pub enum QuicFrame { }, Crypto { offset: u64, - data: Vec, }, } @@ -46,8 +46,9 @@ pub struct EcnCounts { impl QuicFrame { // parse_frames takes the plaintext QUIC packet payload and parses the frame list - pub fn parse_frames(data: &[u8]) -> Result, QuicError> { + pub fn parse_frames(data: &[u8]) -> Result<(Vec, Vec), QuicError> { let mut frames: Vec = Vec::new(); + let mut crypto_map: BTreeMap> = BTreeMap::new(); let mut offset = 0; // Iterate over plaintext payload bytes, this is a list of frames while offset < data.len() { @@ -209,16 +210,26 @@ impl QuicFrame { )?)? as usize; offset += crypto_len_len; // Parse data - let crypto_data = QuicPacket::access_data(data, offset, offset + crypto_len)?; + let crypto_data = + QuicPacket::access_data(data, offset, offset + crypto_len)?.to_vec(); + crypto_map.entry(crypto_offset).or_insert(crypto_data); frames.push(QuicFrame::Crypto { offset: crypto_offset, - data: crypto_data.to_vec(), }); offset += crypto_len; } _ => return Err(QuicError::UnknownFrameType), } } - Ok(frames) + let mut reassembled_crypto: Vec = Vec::new(); + let mut expected_offset: u64 = 0; + for (crypto_offset, crypto_data) in crypto_map { + if crypto_offset != expected_offset { + return Err(QuicError::MissingCryptoFrames); + } + reassembled_crypto.extend(crypto_data); + expected_offset = reassembled_crypto.len() as u64; + } + Ok((frames, reassembled_crypto)) } } diff --git a/core/src/protocols/stream/quic/mod.rs b/core/src/protocols/stream/quic/mod.rs index aca292c0..e86c37a7 100644 --- a/core/src/protocols/stream/quic/mod.rs +++ b/core/src/protocols/stream/quic/mod.rs @@ -26,6 +26,8 @@ pub use self::header::{QuicLongHeader, QuicShortHeader}; use frame::QuicFrame; use header::LongHeaderPacketType; use serde::Serialize; + +use super::tls::ClientHello; pub(crate) mod crypto; pub(crate) mod frame; pub(crate) mod header; @@ -44,10 +46,12 @@ pub enum QuicError { CryptoFail, FailedHeaderProtection, UnknownFrameType, + TlsParseFail, + MissingCryptoFrames, } /// Parsed Quic Packet contents -#[derive(Debug, Serialize, Clone)] +#[derive(Debug, Serialize)] pub struct QuicPacket { /// Quic Short header pub short_header: Option, @@ -59,6 +63,8 @@ pub struct QuicPacket { pub payload_bytes_count: Option, pub frames: Option>, + + pub tls: Option, } impl QuicPacket { diff --git a/core/src/protocols/stream/quic/parser.rs b/core/src/protocols/stream/quic/parser.rs index bd8f775a..b51df868 100644 --- a/core/src/protocols/stream/quic/parser.rs +++ b/core/src/protocols/stream/quic/parser.rs @@ -8,11 +8,13 @@ use crate::protocols::stream::quic::header::{ LongHeaderPacketType, QuicLongHeader, QuicShortHeader, }; use crate::protocols::stream::quic::{QuicError, QuicPacket}; +use crate::protocols::stream::tls::{ClientHello, Tls}; use crate::protocols::stream::{ ConnParsable, ConnState, L4Pdu, ParseResult, ProbeResult, Session, SessionData, }; use byteorder::{BigEndian, ByteOrder}; use std::collections::HashMap; +use tls_parser::parse_tls_message_handshake; #[derive(Default, Debug)] pub struct QuicParser { @@ -31,7 +33,7 @@ impl ConnParsable for QuicParser { } if let Ok(data) = (pdu.mbuf_ref()).get_data_slice(offset, length) { - self.process(data) + self.process(data, pdu) } else { log::warn!("Malformed packet on parse"); ParseResult::Skipped @@ -103,19 +105,28 @@ impl ConnParsable for QuicParser { /// Supported Quic Versions #[derive(Debug, PartialEq, Eq, Hash)] -enum QuicVersion { +#[repr(u32)] +pub enum QuicVersion { ReservedNegotiation = 0x00000000, Rfc9000 = 0x00000001, // Quic V1 Rfc9369 = 0x6b3343cf, // Quic V2 + Draft27 = 0xff00001b, // Quic draft 27 + Draft28 = 0xff00001c, // Quic draft 28 + Draft29 = 0xff00001d, // Quic draft 29 + Mvfst27 = 0xfaceb002, // Facebook Implementation of draft 27 Unknown, } impl QuicVersion { - fn from_u32(version: u32) -> Self { + pub fn from_u32(version: u32) -> Self { match version { 0x00000000 => QuicVersion::ReservedNegotiation, 0x00000001 => QuicVersion::Rfc9000, 0x6b3343cf => QuicVersion::Rfc9369, + 0xff00001b => QuicVersion::Draft27, + 0xff00001c => QuicVersion::Draft28, + 0xff00001d => QuicVersion::Draft29, + 0xfaceb002 => QuicVersion::Mvfst27, _ => QuicVersion::Unknown, } } @@ -168,7 +179,7 @@ impl QuicPacket { } /// Parses Quic packet from bytes - pub fn parse_from(data: &[u8], cnt: usize) -> Result { + pub fn parse_from(data: &[u8], cnt: usize, dir: bool) -> Result { let mut offset = 0; let packet_header_byte = QuicPacket::access_data(data, offset, offset + 1)?[0]; offset += 1; @@ -239,7 +250,7 @@ impl QuicPacket { offset += packet_len_len; if cnt == 0 { // Derive initial keys - let [client_opener, server_opener] = calc_init_keys(dcid_bytes, version)?; + let [client_opener, _server_opener] = calc_init_keys(dcid_bytes, version)?; // Calculate HP let sample_len = client_opener.sample_len(); let hp_sample = @@ -274,7 +285,9 @@ impl QuicPacket { offset += cipher_text_len; // Parse auth tag let tag = QuicPacket::access_data(data, offset, offset + tag_len)?; - offset += tag_len; + // Commenting out the offset increase for tag_len to make clippy happy + // Will be needed when handling multiple QUIC packets in single datagram + // offset += tag_len; // Reconstruct authenticated data let mut ad = Vec::new(); ad.append(&mut [unprotected_header].to_vec()); @@ -334,12 +347,22 @@ impl QuicPacket { } } - let frames: Option>; + let mut frames: Option> = None; + let mut ch: Option = None; // If decrypted payload is not None, parse the frames if let Some(frame_bytes) = decrypted_payload { - frames = Some(QuicFrame::parse_frames(&frame_bytes)?); - } else { - frames = None; + let (q_frames, ch_bytes) = QuicFrame::parse_frames(&frame_bytes)?; + frames = Some(q_frames); + match parse_tls_message_handshake(&ch_bytes) { + Ok((_, msg)) => { + let mut tls = Tls::new(); + tls.parse_message_level(&msg, dir); + if let Some(client_hello) = tls.client_hello { + ch = Some(client_hello); + } + } + Err(_) => return Err(QuicError::TlsParseFail), + } } Ok(QuicPacket { @@ -358,6 +381,7 @@ impl QuicPacket { retry_tag, }), frames, + tls: ch, }) } else { // Short Header @@ -378,14 +402,15 @@ impl QuicPacket { long_header: None, payload_bytes_count, frames: None, + tls: None, }) } } } impl QuicParser { - fn process(&mut self, data: &[u8]) -> ParseResult { - if let Ok(quic) = QuicPacket::parse_from(data, self.cnt) { + fn process(&mut self, data: &[u8], pdu: &L4Pdu) -> ParseResult { + if let Ok(quic) = QuicPacket::parse_from(data, self.cnt, pdu.dir) { let session_id = self.cnt; self.sessions.insert(session_id, quic); self.cnt += 1; diff --git a/core/src/subscription/quic_stream.rs b/core/src/subscription/quic_stream.rs index 84762e8f..0cb5aa6b 100644 --- a/core/src/subscription/quic_stream.rs +++ b/core/src/subscription/quic_stream.rs @@ -121,7 +121,7 @@ impl Trackable for TrackedQuic { fn on_match(&mut self, session: Session, subscription: &Subscription) { if let SessionData::Quic(quic) = session.data { - let mut quic_clone = (*quic).clone(); + let mut quic_clone = *quic; if let Some(long_header) = &quic_clone.long_header { if long_header.dcid_len > 0 { diff --git a/traces/README.md b/traces/README.md index e5c94e44..af1985b8 100644 --- a/traces/README.md +++ b/traces/README.md @@ -2,8 +2,9 @@ A collection of sample packet captures pulled from a variety of sources. -| Trace | Source | Description | -|--------------------|-------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------| -| `small_flows.pcap` | [Tcpreplay Sample Captures](https://tcpreplay.appneta.com/wiki/captures.html) | A synthetic combination of a few different applications and protocols at a relatively low network traffic rate. | -| `tls_ciphers.pcap` | [Wireshark Sample Captures](https://wiki.wireshark.org/SampleCaptures) | OpenSSL client/server GET requests over TLS 1.2 with 73 different cipher suites. | -| `quic_retry.pcapng`| [Wireshark Issue](https://gitlab.com/wireshark/wireshark/-/issues/18757) | An example of a QUIC Retry Packet. Original Pcap modified to remove CookedLinux and add Ether | \ No newline at end of file +| Trace | Source | Description | +|--------------------|-------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------| +| `small_flows.pcap` | [Tcpreplay Sample Captures](https://tcpreplay.appneta.com/wiki/captures.html) | A synthetic combination of a few different applications and protocols at a relatively low network traffic rate. | +| `tls_ciphers.pcap` | [Wireshark Sample Captures](https://wiki.wireshark.org/SampleCaptures) | OpenSSL client/server GET requests over TLS 1.2 with 73 different cipher suites. | +| `quic_retry.pcapng`| [Wireshark Issue](https://gitlab.com/wireshark/wireshark/-/issues/18757) | An example of a QUIC Retry Packet. Original Pcap modified to remove CookedLinux and add Ether | +| `quic_xargs.pcap` | [illustrated-quic GitHub](https://github.com/syncsynchalt/illustrated-quic/blob/main/captures/capture.pcap) | The pcap used in the creation of [The Illustrated QUIC Connection](https://quic.xargs.org). | \ No newline at end of file diff --git a/traces/quic_xargs.pcap b/traces/quic_xargs.pcap new file mode 100644 index 0000000000000000000000000000000000000000..dbd17fdcac3addd76a5a8f545ba47f51bf5c6355 GIT binary patch literal 4579 zcmeHLS5Q;y8r=k9fPnNSU5W;!BT5fQ72$voIwBD1B3(L!P>!@CC>;Tj-a-$dhR^Uhmw&r!#XO?gQVr6*h^0Qr4DzBJYT!-V+LlXwbbzf&s2h0T?UTznpRsq3(N;(P`?DRQiN5XkwBTY)l%Fq?f7vH9C#}Nra_k@- zoWkc@uTDixrToGij?3+Z?S^5IQJ))>q)itz;sMphhBzSd;MaVg+fhSp8Qx4iQ%joNC-2EBL6ZIF-N zBO>d>A#5yKa&U6somDiSu#ISAX&d}1^^Vz{HHK=X^i1TC7IGi%^tFbmkbgixzYwM< zS#h&meC=3EqW>nMOphIRFN0Z;O>XuI%IRS2xxL1bk^yP_b?J+g9p`dUyvM)uefNP#rOklw1lts=wbCf@Vfd7(Y=i% zn^lUU8FQ~+?Mq(j*nagMpMS4+F}EA*c&D)<9+o6;PF4iIo1VlxW<;<5$js>T_}#@t zH}CCx)GlyEtNe_mzY_!HD1_18MH zI+iitm|Le}@^mb%UuLq@dgVE^c+A2qF5p(&(S>iq2IUhxW8O8z6hb5l@soqoe`{T_ zZ-f3Vl@o1ZPQDM_XCzi*rLZjQn4pD9h3CBoCA~=abDBdVj>7uS%n}8}R{rs2_Gn-j>R~FoVruFXzs4}Dpn{S2j`O00@ zOjRn9smv7tX+>6zaBCu{>c=NNvOO1CSE-Z{*Oto35&jXma!(=j zR#}Y~Ew#<0r$U*qT=|kMrRpo!J-exZObSDt^U%u0<-Zir_Ni*~8#0v;w_+Y0Bro=% z^PYp{JEn`y&LlAlyS)4z)+^02#y2rg3rbqK)UPTH*ZfPHsN+k?**$d`1!OeUoi1?z zyqY{irJqsnghYALJu!-%kYrLrR`rq^h`7SRMo#z;SbnVCsfGn?~pm*JtMR-7gZUbYv_?#{E=sA7U^(XIlJQp7^uuf?&CCwy{3k4=3q3 z5h21ErS`Y5cW3ky7qHPXWF$65GjA`AztxM}oe=0mNkw^tps#P*cM`Lw_?qp=(-ecr z3(BUy`dEz*>YCJ^XAy$SiV)K*&*89Cj0~XX5Ih?XixkLJmG(Cc?uw-oR(-;~y}7i%PhPMr&cQEh)N-=>Atu3#l>dsI#H?soUBB@k z5eq2-CEH$!rTvJ=fW&7vIL!m@KhK^>V7=$zy~CbOntcmWACm<#+7A+*rd+o~^9PyV zn7aLp2f(#^EG5gbPHQug9*yM%1gg+=QQ$i2*sWQ{sXZ#0=2`Ub({Y>|hhmBgv}>+j z%qeK4%>m<`B9z4D3MDLA+hh&!E|s0W#!j4iZLnITY?DjB2wpVtT;^*??BfG0WlL3L z_4Ff(KMwRnAJ|HZ)x{gve3E}wN^m+k83;ge9PkJ@o#_Dl=}bp(I$#BVIDgt;;61-7 zbM3L%u_;hd3y5qlsJ)a*ZPss$4OS7ANO62Ak^;6;+NxWyYWO}UEWcS>AHfi(rk)w< zv!roZJ6T~EEJkkiJ-~L^=ur}xQyvjfZ>61igc^#*n@)O|=}OS<3i=RLKwm%Ommr>&bV{mX^4&~E18jkL3E)YZN6V2y3c_f|ZH}o#PDv?G=V-alERykg10aoLX4QzXa|CTl zWdF$)_?dTmPP94k|9^*FIFo@hXYv}S9~jY%MITI>-f`0mfPKZxemUI=XN;~$;B4)p z)fEgJq33vQSV!`SYE{?txbLja6gX4hOo9Ko0tBa?J#+(RKKE{lPtf)X$p6QZT>{XA`fX@dSCqF2< zQ<1cA!*=wtRyxeFC^~D!S(VQt5YMWBf`$2BJS9v`gF^s= Date: Wed, 24 Jul 2024 19:33:51 +0000 Subject: [PATCH 06/12] First take on multi packet connection tracking --- core/src/protocols/stream/mod.rs | 5 +- core/src/protocols/stream/quic/crypto.rs | 6 +- core/src/protocols/stream/quic/header.rs | 3 - core/src/protocols/stream/quic/mod.rs | 15 +++- core/src/protocols/stream/quic/parser.rs | 90 +++++++++++++++++------- core/src/subscription/quic_stream.rs | 50 ++----------- 6 files changed, 92 insertions(+), 77 deletions(-) diff --git a/core/src/protocols/stream/mod.rs b/core/src/protocols/stream/mod.rs index 676c7dea..14464d60 100644 --- a/core/src/protocols/stream/mod.rs +++ b/core/src/protocols/stream/mod.rs @@ -11,7 +11,7 @@ pub mod tls; use self::dns::{parser::DnsParser, Dns}; use self::http::{parser::HttpParser, Http}; -use self::quic::{parser::QuicParser, QuicPacket}; +use self::quic::parser::QuicParser; use self::tls::{parser::TlsParser, Tls}; use crate::conntrack::conn::conn_info::ConnState; use crate::conntrack::conn_id::FiveTuple; @@ -22,6 +22,7 @@ use crate::subscription::*; use std::str::FromStr; use anyhow::{bail, Result}; +use quic::QuicConn; use strum_macros::EnumString; /// Represents the result of parsing one packet as a protocol message. @@ -195,7 +196,7 @@ pub enum SessionData { Tls(Box), Dns(Box), Http(Box), - Quic(Box), + Quic(Box), Null, } diff --git a/core/src/protocols/stream/quic/crypto.rs b/core/src/protocols/stream/quic/crypto.rs index bf5af73a..fd465186 100644 --- a/core/src/protocols/stream/quic/crypto.rs +++ b/core/src/protocols/stream/quic/crypto.rs @@ -37,6 +37,7 @@ use crypto::aes::KeySize; use crypto::aes_gcm::AesGcm; use ring::aead; use ring::hkdf; +use serde::Serialize; use crate::protocols::stream::quic::parser::QuicVersion; use crate::protocols::stream::quic::QuicError; @@ -44,7 +45,7 @@ use crate::protocols::stream::quic::QuicError; // The algorithm enum defines the available // cryptographic algorithms used to secure // QUIC packets. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Serialize)] pub enum Algorithm { AES128GCM, } @@ -90,13 +91,16 @@ impl Algorithm { // The Open struct gives a return value // that contains all of the components // needed for HP removal and decryption +#[derive(Serialize)] pub struct Open { alg: Algorithm, + #[serde(skip_serializing)] key_len: Option, initial_key: Vec, + #[serde(skip_serializing)] hp_key: aead::quic::HeaderProtectionKey, iv: Vec, diff --git a/core/src/protocols/stream/quic/header.rs b/core/src/protocols/stream/quic/header.rs index 934e89d4..b53e0d57 100644 --- a/core/src/protocols/stream/quic/header.rs +++ b/core/src/protocols/stream/quic/header.rs @@ -23,9 +23,6 @@ pub struct QuicLongHeader { #[derive(Debug, Serialize, Clone)] pub struct QuicShortHeader { pub dcid: Option, // optional. If not pre-existing cid then none. - - #[serde(skip)] - pub dcid_bytes: Vec, } // Long Header Packet Types from RFC 9000 Table 5 diff --git a/core/src/protocols/stream/quic/mod.rs b/core/src/protocols/stream/quic/mod.rs index e86c37a7..e38c6ed8 100644 --- a/core/src/protocols/stream/quic/mod.rs +++ b/core/src/protocols/stream/quic/mod.rs @@ -22,12 +22,15 @@ TODO: support HTTP/3 */ pub(crate) mod parser; +use std::collections::HashSet; + pub use self::header::{QuicLongHeader, QuicShortHeader}; +use crypto::Open; use frame::QuicFrame; use header::LongHeaderPacketType; use serde::Serialize; -use super::tls::ClientHello; +use super::tls::{ClientHello, Tls}; pub(crate) mod crypto; pub(crate) mod frame; pub(crate) mod header; @@ -50,6 +53,16 @@ pub enum QuicError { MissingCryptoFrames, } +/// Parsed Quic Packet contents +#[derive(Debug, Serialize)] +pub struct QuicConn { + pub packets: Vec, + pub cids: HashSet, + pub tls: Tls, + pub client_opener: Option, + pub server_opener: Option, +} + /// Parsed Quic Packet contents #[derive(Debug, Serialize)] pub struct QuicPacket { diff --git a/core/src/protocols/stream/quic/parser.rs b/core/src/protocols/stream/quic/parser.rs index b51df868..e539ad1c 100644 --- a/core/src/protocols/stream/quic/parser.rs +++ b/core/src/protocols/stream/quic/parser.rs @@ -13,15 +13,26 @@ use crate::protocols::stream::{ ConnParsable, ConnState, L4Pdu, ParseResult, ProbeResult, Session, SessionData, }; use byteorder::{BigEndian, ByteOrder}; -use std::collections::HashMap; +use std::collections::HashSet; use tls_parser::parse_tls_message_handshake; -#[derive(Default, Debug)] +use super::QuicConn; + +#[derive(Debug)] pub struct QuicParser { - /// Maps session ID to Quic transaction - sessions: HashMap, - /// Total sessions ever seen (Running session ID) - cnt: usize, + // /// Maps session ID to Quic transaction + // sessions: HashMap, + // /// Total sessions ever seen (Running session ID) + // cnt: usize, + sessions: Vec, +} + +impl Default for QuicParser { + fn default() -> Self { + QuicParser { + sessions: vec![QuicConn::new()], + } + } } impl ConnParsable for QuicParser { @@ -33,7 +44,7 @@ impl ConnParsable for QuicParser { } if let Ok(data) = (pdu.mbuf_ref()).get_data_slice(offset, length) { - self.process(data, pdu) + self.sessions[0].parse_packet(data, pdu.dir) } else { log::warn!("Malformed packet on parse"); ParseResult::Skipped @@ -79,7 +90,7 @@ impl ConnParsable for QuicParser { } fn remove_session(&mut self, session_id: usize) -> Option { - self.sessions.remove(&session_id).map(|quic| Session { + self.sessions.pop().map(|quic| Session { data: SessionData::Quic(Box::new(quic)), id: session_id, }) @@ -87,10 +98,10 @@ impl ConnParsable for QuicParser { fn drain_sessions(&mut self) -> Vec { self.sessions - .drain() - .map(|(session_id, quic)| Session { + .drain(..) + .map(|quic| Session { data: SessionData::Quic(Box::new(quic)), - id: session_id, + id: 0, }) .collect() } @@ -179,7 +190,11 @@ impl QuicPacket { } /// Parses Quic packet from bytes - pub fn parse_from(data: &[u8], cnt: usize, dir: bool) -> Result { + pub fn parse_from( + conn: &mut QuicConn, + data: &[u8], + dir: bool, + ) -> Result { let mut offset = 0; let packet_header_byte = QuicPacket::access_data(data, offset, offset + 1)?[0]; offset += 1; @@ -208,12 +223,18 @@ impl QuicPacket { offset += 1; let dcid_bytes = QuicPacket::access_data(data, offset, offset + dcid_len as usize)?; let dcid = QuicPacket::vec_u8_to_hex_string(dcid_bytes); + if dcid_len > 0 && !conn.cids.contains(&dcid) { + conn.cids.insert(dcid.clone()); + } offset += dcid_len as usize; // Parse SCID let scid_len = QuicPacket::access_data(data, offset, offset + 1)?[0]; offset += 1; let scid_bytes = QuicPacket::access_data(data, offset, offset + scid_len as usize)?; let scid = QuicPacket::vec_u8_to_hex_string(scid_bytes); + if scid_len > 0 && !conn.cids.contains(&scid) { + conn.cids.insert(scid.clone()); + } offset += scid_len as usize; let token_len; @@ -248,9 +269,9 @@ impl QuicPacket { QuicPacket::access_data(data, offset, offset + packet_len_len)?; packet_len = Some(QuicPacket::slice_to_u64(packet_len_bytes)?); offset += packet_len_len; - if cnt == 0 { + if conn.client_opener.is_none() { // Derive initial keys - let [client_opener, _server_opener] = calc_init_keys(dcid_bytes, version)?; + let [client_opener, server_opener] = calc_init_keys(dcid_bytes, version)?; // Calculate HP let sample_len = client_opener.sample_len(); let hp_sample = @@ -306,6 +327,8 @@ impl QuicPacket { &mut encrypted_payload, tag, )?); + conn.client_opener = Some(client_opener); + conn.server_opener = Some(server_opener); } else { decrypted_payload = None; } @@ -390,15 +413,22 @@ impl QuicPacket { max_dcid_len = data.len() - 1; } // Parse DCID - let dcid_bytes = QuicPacket::access_data(data, offset, offset + max_dcid_len)?.to_vec(); + let dcid_hex = QuicPacket::vec_u8_to_hex_string(QuicPacket::access_data( + data, + offset, + offset + max_dcid_len, + )?); + let mut dcid = None; + for cid in &conn.cids { + if dcid_hex.starts_with(cid) { + dcid = Some(cid.clone()); + } + } offset += max_dcid_len; // Counts all bytes remaining let payload_bytes_count = Some((data.len() - offset) as u64); Ok(QuicPacket { - short_header: Some(QuicShortHeader { - dcid: None, - dcid_bytes, - }), + short_header: Some(QuicShortHeader { dcid }), long_header: None, payload_bytes_count, frames: None, @@ -408,13 +438,21 @@ impl QuicPacket { } } -impl QuicParser { - fn process(&mut self, data: &[u8], pdu: &L4Pdu) -> ParseResult { - if let Ok(quic) = QuicPacket::parse_from(data, self.cnt, pdu.dir) { - let session_id = self.cnt; - self.sessions.insert(session_id, quic); - self.cnt += 1; - ParseResult::Done(session_id) +impl QuicConn { + pub(crate) fn new() -> QuicConn { + QuicConn { + packets: Vec::new(), + cids: HashSet::new(), + tls: Tls::new(), + client_opener: None, + server_opener: None, + } + } + + fn parse_packet(&mut self, data: &[u8], direction: bool) -> ParseResult { + if let Ok(quic) = QuicPacket::parse_from(self, data, direction) { + self.packets.push(quic); + ParseResult::Continue(0) } else { ParseResult::Skipped } diff --git a/core/src/subscription/quic_stream.rs b/core/src/subscription/quic_stream.rs index 0cb5aa6b..41e44d1e 100644 --- a/core/src/subscription/quic_stream.rs +++ b/core/src/subscription/quic_stream.rs @@ -3,25 +3,14 @@ //! This is a session-level subscription that delivers parsed QUIC stream records and associated //! connection metadata. //! -//! ## Example -//! Prints QUIC connections that use long headers: -//! ``` -//! #[filter("quic.header_type = 'long'")] -//! fn main() { -//! let config = default_config(); -//! let cb = |quic: QuicStream| { -//! println!("{}", quic.data); -//! }; -//! let mut runtime = Runtime::new(config, filter, cb).unwrap(); -//! runtime.run(); -//! } use crate::conntrack::conn_id::FiveTuple; use crate::conntrack::pdu::{L4Context, L4Pdu}; use crate::conntrack::ConnTracker; use crate::filter::FilterResult; use crate::memory::mbuf::Mbuf; -use crate::protocols::stream::quic::{parser::QuicParser, QuicPacket}; +use crate::protocols::stream::quic::parser::QuicParser; +use crate::protocols::stream::quic::QuicConn; use crate::protocols::stream::{ConnParser, Session, SessionData}; use crate::subscription::{Level, Subscribable, Subscription, Trackable}; use std::collections::HashSet; @@ -34,7 +23,7 @@ use std::net::SocketAddr; #[derive(Debug, Serialize)] pub struct QuicStream { pub five_tuple: FiveTuple, - pub data: QuicPacket, + pub data: QuicConn, } impl QuicStream { @@ -95,18 +84,6 @@ pub struct TrackedQuic { connection_id: HashSet, } -impl TrackedQuic { - fn get_connection_id(&self, dcid_bytes: &[u8]) -> Option { - let dcid_hex = QuicPacket::vec_u8_to_hex_string(dcid_bytes); - for dcid in &self.connection_id { - if dcid_hex.starts_with(dcid) { - return Some(dcid.clone()); - } - } - None - } -} - impl Trackable for TrackedQuic { type Subscribed = QuicStream; @@ -121,24 +98,9 @@ impl Trackable for TrackedQuic { fn on_match(&mut self, session: Session, subscription: &Subscription) { if let SessionData::Quic(quic) = session.data { - let mut quic_clone = *quic; - - if let Some(long_header) = &quic_clone.long_header { - if long_header.dcid_len > 0 { - self.connection_id.insert(long_header.dcid.clone()); - } - if long_header.scid_len > 0 { - self.connection_id.insert(long_header.scid.clone()); - } - } else { - if let Some(ref mut short_header_value) = quic_clone.short_header { - short_header_value.dcid = - self.get_connection_id(&short_header_value.dcid_bytes); - } - return subscription.invoke(QuicStream { - five_tuple: self.five_tuple, - data: quic_clone, - }); + let quic_clone = *quic; + for cid in &quic_clone.cids { + self.connection_id.insert(cid.to_string()); } subscription.invoke(QuicStream { From 455d2e0cd17ada915c997a071d6f5cd869bd6014 Mon Sep 17 00:00:00 2001 From: Jackson Sippe Date: Wed, 24 Jul 2024 20:22:21 +0000 Subject: [PATCH 07/12] Parse ServerHello, move all TLS parsing to QuicConn Tls object --- core/src/protocols/stream/quic/frame.rs | 2 + core/src/protocols/stream/quic/mod.rs | 4 +- core/src/protocols/stream/quic/parser.rs | 138 ++++++++++++----------- 3 files changed, 74 insertions(+), 70 deletions(-) diff --git a/core/src/protocols/stream/quic/frame.rs b/core/src/protocols/stream/quic/frame.rs index f66d6cef..c85a046d 100644 --- a/core/src/protocols/stream/quic/frame.rs +++ b/core/src/protocols/stream/quic/frame.rs @@ -46,6 +46,7 @@ pub struct EcnCounts { impl QuicFrame { // parse_frames takes the plaintext QUIC packet payload and parses the frame list + // it also returns the reassembled CRYPTO frame bytes as a Vec pub fn parse_frames(data: &[u8]) -> Result<(Vec, Vec), QuicError> { let mut frames: Vec = Vec::new(); let mut crypto_map: BTreeMap> = BTreeMap::new(); @@ -120,6 +121,7 @@ impl QuicFrame { offset, offset + first_ack_range_len, )?)?; + offset += first_ack_range_len; // Parse ACK Range list field let mut ack_ranges = Vec::new(); for _ in 0..ack_range_count { diff --git a/core/src/protocols/stream/quic/mod.rs b/core/src/protocols/stream/quic/mod.rs index e38c6ed8..e5c9a41a 100644 --- a/core/src/protocols/stream/quic/mod.rs +++ b/core/src/protocols/stream/quic/mod.rs @@ -30,7 +30,7 @@ use frame::QuicFrame; use header::LongHeaderPacketType; use serde::Serialize; -use super::tls::{ClientHello, Tls}; +use super::tls::Tls; pub(crate) mod crypto; pub(crate) mod frame; pub(crate) mod header; @@ -76,8 +76,6 @@ pub struct QuicPacket { pub payload_bytes_count: Option, pub frames: Option>, - - pub tls: Option, } impl QuicPacket { diff --git a/core/src/protocols/stream/quic/parser.rs b/core/src/protocols/stream/quic/parser.rs index e539ad1c..b7a946ab 100644 --- a/core/src/protocols/stream/quic/parser.rs +++ b/core/src/protocols/stream/quic/parser.rs @@ -8,7 +8,7 @@ use crate::protocols::stream::quic::header::{ LongHeaderPacketType, QuicLongHeader, QuicShortHeader, }; use crate::protocols::stream::quic::{QuicError, QuicPacket}; -use crate::protocols::stream::tls::{ClientHello, Tls}; +use crate::protocols::stream::tls::Tls; use crate::protocols::stream::{ ConnParsable, ConnState, L4Pdu, ParseResult, ProbeResult, Session, SessionData, }; @@ -272,65 +272,76 @@ impl QuicPacket { if conn.client_opener.is_none() { // Derive initial keys let [client_opener, server_opener] = calc_init_keys(dcid_bytes, version)?; - // Calculate HP - let sample_len = client_opener.sample_len(); - let hp_sample = - QuicPacket::access_data(data, offset + 4, offset + 4 + sample_len)?; - let mask = client_opener.new_mask(hp_sample)?; - // Remove HP from packet header byte - let unprotected_header = packet_header_byte ^ (mask[0] & 0b00001111); - if (unprotected_header >> 2) & 0b00000011 != 0 { - return Err(QuicError::FailedHeaderProtection); - } - // Parse packet number - let packet_num_len = ((unprotected_header & 0b00000011) + 1) as usize; - let packet_number_bytes = - QuicPacket::access_data(data, offset, offset + packet_num_len)?; - let mut packet_number = [0; 4]; - for i in 0..packet_num_len { - packet_number[4 - (i + 1)] = packet_number_bytes[i] ^ mask[i + 1]; - } - let initial_packet_number_bytes = &packet_number[4 - packet_num_len..]; - let packet_number_int = BigEndian::read_i32(&packet_number); - offset += packet_num_len; - // Parse the encrypted payload - let tag_len = client_opener.alg().tag_len(); - if (packet_len.unwrap() as usize) < (tag_len + packet_num_len) { - return Err(QuicError::PacketTooShort); - } - let cipher_text_len = - packet_len.unwrap() as usize - tag_len - packet_num_len; - let mut encrypted_payload = - QuicPacket::access_data(data, offset, offset + cipher_text_len)? - .to_vec(); - offset += cipher_text_len; - // Parse auth tag - let tag = QuicPacket::access_data(data, offset, offset + tag_len)?; - // Commenting out the offset increase for tag_len to make clippy happy - // Will be needed when handling multiple QUIC packets in single datagram - // offset += tag_len; - // Reconstruct authenticated data - let mut ad = Vec::new(); - ad.append(&mut [unprotected_header].to_vec()); - ad.append(&mut version_bytes.to_vec()); - ad.append(&mut [dcid_len].to_vec()); - ad.append(&mut dcid_bytes.to_vec()); - ad.append(&mut [scid_len].to_vec()); - ad.append(&mut scid_bytes.to_vec()); - ad.append(&mut token_len_bytes.to_vec()); - ad.append(&mut token_bytes.to_vec()); - ad.append(&mut packet_len_bytes.to_vec()); - ad.append(&mut initial_packet_number_bytes.to_vec()); - decrypted_payload = Some(client_opener.open_with_u64_counter( - packet_number_int as u64, - &ad, - &mut encrypted_payload, - tag, - )?); conn.client_opener = Some(client_opener); conn.server_opener = Some(server_opener); + } + // Calculate HP + let sample_len = conn.client_opener.as_ref().unwrap().sample_len(); + let hp_sample = + QuicPacket::access_data(data, offset + 4, offset + 4 + sample_len)?; + let mask = if dir { + conn.client_opener.as_ref().unwrap().new_mask(hp_sample)? + } else { + conn.server_opener.as_ref().unwrap().new_mask(hp_sample)? + }; + // Remove HP from packet header byte + let unprotected_header = packet_header_byte ^ (mask[0] & 0b00001111); + if (unprotected_header >> 2) & 0b00000011 != 0 { + return Err(QuicError::FailedHeaderProtection); + } + // Parse packet number + let packet_num_len = ((unprotected_header & 0b00000011) + 1) as usize; + let packet_number_bytes = + QuicPacket::access_data(data, offset, offset + packet_num_len)?; + let mut packet_number = [0; 4]; + for i in 0..packet_num_len { + packet_number[4 - (i + 1)] = packet_number_bytes[i] ^ mask[i + 1]; + } + let initial_packet_number_bytes = &packet_number[4 - packet_num_len..]; + let packet_number_int = BigEndian::read_i32(&packet_number); + offset += packet_num_len; + // Parse the encrypted payload + let tag_len = conn.client_opener.as_ref().unwrap().alg().tag_len(); + if (packet_len.unwrap() as usize) < (tag_len + packet_num_len) { + return Err(QuicError::PacketTooShort); + } + let cipher_text_len = packet_len.unwrap() as usize - tag_len - packet_num_len; + let mut encrypted_payload = + QuicPacket::access_data(data, offset, offset + cipher_text_len)?.to_vec(); + offset += cipher_text_len; + // Parse auth tag + let tag = QuicPacket::access_data(data, offset, offset + tag_len)?; + // Commenting out the offset increase for tag_len to make clippy happy + // Will be needed when handling multiple QUIC packets in single datagram + // offset += tag_len; + // Reconstruct authenticated data + let mut ad = Vec::new(); + ad.append(&mut [unprotected_header].to_vec()); + ad.append(&mut version_bytes.to_vec()); + ad.append(&mut [dcid_len].to_vec()); + ad.append(&mut dcid_bytes.to_vec()); + ad.append(&mut [scid_len].to_vec()); + ad.append(&mut scid_bytes.to_vec()); + ad.append(&mut token_len_bytes.to_vec()); + ad.append(&mut token_bytes.to_vec()); + ad.append(&mut packet_len_bytes.to_vec()); + ad.append(&mut initial_packet_number_bytes.to_vec()); + if dir { + decrypted_payload = + Some(conn.client_opener.as_ref().unwrap().open_with_u64_counter( + packet_number_int as u64, + &ad, + &mut encrypted_payload, + tag, + )?); } else { - decrypted_payload = None; + decrypted_payload = + Some(conn.server_opener.as_ref().unwrap().open_with_u64_counter( + packet_number_int as u64, + &ad, + &mut encrypted_payload, + tag, + )?); } } LongHeaderPacketType::ZeroRTT | LongHeaderPacketType::Handshake => { @@ -371,18 +382,13 @@ impl QuicPacket { } let mut frames: Option> = None; - let mut ch: Option = None; // If decrypted payload is not None, parse the frames if let Some(frame_bytes) = decrypted_payload { - let (q_frames, ch_bytes) = QuicFrame::parse_frames(&frame_bytes)?; + let (q_frames, crypto_bytes) = QuicFrame::parse_frames(&frame_bytes)?; frames = Some(q_frames); - match parse_tls_message_handshake(&ch_bytes) { + match parse_tls_message_handshake(&crypto_bytes) { Ok((_, msg)) => { - let mut tls = Tls::new(); - tls.parse_message_level(&msg, dir); - if let Some(client_hello) = tls.client_hello { - ch = Some(client_hello); - } + conn.tls.parse_message_level(&msg, dir); } Err(_) => return Err(QuicError::TlsParseFail), } @@ -404,7 +410,6 @@ impl QuicPacket { retry_tag, }), frames, - tls: ch, }) } else { // Short Header @@ -432,7 +437,6 @@ impl QuicPacket { long_header: None, payload_bytes_count, frames: None, - tls: None, }) } } From c829e5c896092ecfd1bdf52bd2d418246af8c087 Mon Sep 17 00:00:00 2001 From: Jackson Sippe Date: Wed, 24 Jul 2024 21:36:42 +0000 Subject: [PATCH 08/12] Parse multiple QUIC packets from single UDP datagram --- core/src/protocols/stream/quic/parser.rs | 98 ++++++++++++++---------- 1 file changed, 58 insertions(+), 40 deletions(-) diff --git a/core/src/protocols/stream/quic/parser.rs b/core/src/protocols/stream/quic/parser.rs index b7a946ab..1f46722b 100644 --- a/core/src/protocols/stream/quic/parser.rs +++ b/core/src/protocols/stream/quic/parser.rs @@ -193,9 +193,9 @@ impl QuicPacket { pub fn parse_from( conn: &mut QuicConn, data: &[u8], + mut offset: usize, dir: bool, - ) -> Result { - let mut offset = 0; + ) -> Result<(QuicPacket, usize), QuicError> { let packet_header_byte = QuicPacket::access_data(data, offset, offset + 1)?[0]; offset += 1; // Check the fixed bit @@ -311,6 +311,7 @@ impl QuicPacket { offset += cipher_text_len; // Parse auth tag let tag = QuicPacket::access_data(data, offset, offset + tag_len)?; + offset += tag_len; // Commenting out the offset increase for tag_len to make clippy happy // Will be needed when handling multiple QUIC packets in single datagram // offset += tag_len; @@ -358,6 +359,8 @@ impl QuicPacket { offset, offset + packet_len_len, )?)?); + offset += packet_len_len; + offset += packet_len.unwrap() as usize; } LongHeaderPacketType::Retry => { packet_len = None; @@ -378,6 +381,7 @@ impl QuicPacket { // Parse retry tag let retry_tag_bytes = QuicPacket::access_data(data, offset, offset + 16)?; retry_tag = Some(QuicPacket::vec_u8_to_hex_string(retry_tag_bytes)); + offset += 16; } } @@ -386,58 +390,68 @@ impl QuicPacket { if let Some(frame_bytes) = decrypted_payload { let (q_frames, crypto_bytes) = QuicFrame::parse_frames(&frame_bytes)?; frames = Some(q_frames); - match parse_tls_message_handshake(&crypto_bytes) { - Ok((_, msg)) => { - conn.tls.parse_message_level(&msg, dir); + if !crypto_bytes.is_empty() { + match parse_tls_message_handshake(&crypto_bytes) { + Ok((_, msg)) => { + conn.tls.parse_message_level(&msg, dir); + } + Err(_) => return Err(QuicError::TlsParseFail), } - Err(_) => return Err(QuicError::TlsParseFail), } } - Ok(QuicPacket { - payload_bytes_count: packet_len, - short_header: None, - long_header: Some(QuicLongHeader { - packet_type, - type_specific, - version, - dcid_len, - dcid, - scid_len, - scid, - token_len, - token, - retry_tag, - }), - frames, - }) + Ok(( + QuicPacket { + payload_bytes_count: packet_len, + short_header: None, + long_header: Some(QuicLongHeader { + packet_type, + type_specific, + version, + dcid_len, + dcid, + scid_len, + scid, + token_len, + token, + retry_tag, + }), + frames, + }, + offset, + )) } else { // Short Header - let mut max_dcid_len = 20; - if data.len() < 1 + max_dcid_len { - max_dcid_len = data.len() - 1; + let mut dcid_len = 20; + if data.len() < 1 + dcid_len { + dcid_len = data.len() - 1; } // Parse DCID let dcid_hex = QuicPacket::vec_u8_to_hex_string(QuicPacket::access_data( data, offset, - offset + max_dcid_len, + offset + dcid_len, )?); let mut dcid = None; for cid in &conn.cids { if dcid_hex.starts_with(cid) { + dcid_len = cid.chars().count() / 2; dcid = Some(cid.clone()); } } - offset += max_dcid_len; + offset += dcid_len; // Counts all bytes remaining - let payload_bytes_count = Some((data.len() - offset) as u64); - Ok(QuicPacket { - short_header: Some(QuicShortHeader { dcid }), - long_header: None, - payload_bytes_count, - frames: None, - }) + let payload_bytes_count = (data.len() - offset) as u64; + offset += payload_bytes_count as usize; + Ok(( + QuicPacket { + short_header: Some(QuicShortHeader { dcid }), + long_header: None, + payload_bytes_count: Some(payload_bytes_count), + frames: None, + }, + offset, + )) } } } @@ -454,11 +468,15 @@ impl QuicConn { } fn parse_packet(&mut self, data: &[u8], direction: bool) -> ParseResult { - if let Ok(quic) = QuicPacket::parse_from(self, data, direction) { - self.packets.push(quic); - ParseResult::Continue(0) - } else { - ParseResult::Skipped + let mut offset = 0; + while data.len() > offset { + if let Ok((quic, off)) = QuicPacket::parse_from(self, data, offset, direction) { + self.packets.push(quic); + offset = off; + } else { + return ParseResult::Skipped; + } } + ParseResult::Continue(0) } } From f86ff7594fd6d0d028ca2ada1db84a74a67c50d3 Mon Sep 17 00:00:00 2001 From: Jackson Sippe Date: Thu, 25 Jul 2024 06:03:02 +0000 Subject: [PATCH 09/12] Add CRYPTO buffers allowing reassembly of long TLS messages --- core/src/protocols/stream/quic/frame.rs | 14 +++++++++----- core/src/protocols/stream/quic/mod.rs | 4 ++++ core/src/protocols/stream/quic/parser.rs | 19 +++++++++++++++---- traces/README.md | 3 ++- traces/quic_kyber.pcapng | Bin 0 -> 14580 bytes 5 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 traces/quic_kyber.pcapng diff --git a/core/src/protocols/stream/quic/frame.rs b/core/src/protocols/stream/quic/frame.rs index c85a046d..5825ab2c 100644 --- a/core/src/protocols/stream/quic/frame.rs +++ b/core/src/protocols/stream/quic/frame.rs @@ -47,9 +47,12 @@ pub struct EcnCounts { impl QuicFrame { // parse_frames takes the plaintext QUIC packet payload and parses the frame list // it also returns the reassembled CRYPTO frame bytes as a Vec - pub fn parse_frames(data: &[u8]) -> Result<(Vec, Vec), QuicError> { + pub fn parse_frames( + data: &[u8], + mut expected_offset: usize, + ) -> Result<(Vec, Vec), QuicError> { let mut frames: Vec = Vec::new(); - let mut crypto_map: BTreeMap> = BTreeMap::new(); + let mut crypto_map: BTreeMap> = BTreeMap::new(); let mut offset = 0; // Iterate over plaintext payload bytes, this is a list of frames while offset < data.len() { @@ -214,7 +217,9 @@ impl QuicFrame { // Parse data let crypto_data = QuicPacket::access_data(data, offset, offset + crypto_len)?.to_vec(); - crypto_map.entry(crypto_offset).or_insert(crypto_data); + crypto_map + .entry(crypto_offset as usize) + .or_insert(crypto_data); frames.push(QuicFrame::Crypto { offset: crypto_offset, }); @@ -224,13 +229,12 @@ impl QuicFrame { } } let mut reassembled_crypto: Vec = Vec::new(); - let mut expected_offset: u64 = 0; for (crypto_offset, crypto_data) in crypto_map { if crypto_offset != expected_offset { return Err(QuicError::MissingCryptoFrames); } + expected_offset += crypto_data.len(); reassembled_crypto.extend(crypto_data); - expected_offset = reassembled_crypto.len() as u64; } Ok((frames, reassembled_crypto)) } diff --git a/core/src/protocols/stream/quic/mod.rs b/core/src/protocols/stream/quic/mod.rs index e5c9a41a..5b2a1160 100644 --- a/core/src/protocols/stream/quic/mod.rs +++ b/core/src/protocols/stream/quic/mod.rs @@ -61,6 +61,10 @@ pub struct QuicConn { pub tls: Tls, pub client_opener: Option, pub server_opener: Option, + #[serde(skip_serializing)] + pub client_buffer: Vec, + #[serde(skip_serializing)] + pub server_buffer: Vec, } /// Parsed Quic Packet contents diff --git a/core/src/protocols/stream/quic/parser.rs b/core/src/protocols/stream/quic/parser.rs index 1f46722b..cb858c7d 100644 --- a/core/src/protocols/stream/quic/parser.rs +++ b/core/src/protocols/stream/quic/parser.rs @@ -293,10 +293,11 @@ impl QuicPacket { let packet_num_len = ((unprotected_header & 0b00000011) + 1) as usize; let packet_number_bytes = QuicPacket::access_data(data, offset, offset + packet_num_len)?; - let mut packet_number = [0; 4]; + let mut packet_number = vec![0; 4 - packet_num_len]; for i in 0..packet_num_len { - packet_number[4 - (i + 1)] = packet_number_bytes[i] ^ mask[i + 1]; + packet_number.push(packet_number_bytes[i] ^ mask[i + 1]); } + let initial_packet_number_bytes = &packet_number[4 - packet_num_len..]; let packet_number_int = BigEndian::read_i32(&packet_number); offset += packet_num_len; @@ -386,14 +387,22 @@ impl QuicPacket { } let mut frames: Option> = None; + let crypto_buffer: &mut Vec = if dir { + conn.client_buffer.as_mut() + } else { + conn.server_buffer.as_mut() + }; // If decrypted payload is not None, parse the frames if let Some(frame_bytes) = decrypted_payload { - let (q_frames, crypto_bytes) = QuicFrame::parse_frames(&frame_bytes)?; + let (q_frames, mut crypto_bytes) = + QuicFrame::parse_frames(&frame_bytes, crypto_buffer.len())?; frames = Some(q_frames); if !crypto_bytes.is_empty() { - match parse_tls_message_handshake(&crypto_bytes) { + crypto_buffer.append(&mut crypto_bytes); + match parse_tls_message_handshake(crypto_buffer) { Ok((_, msg)) => { conn.tls.parse_message_level(&msg, dir); + crypto_buffer.clear(); } Err(_) => return Err(QuicError::TlsParseFail), } @@ -464,6 +473,8 @@ impl QuicConn { tls: Tls::new(), client_opener: None, server_opener: None, + client_buffer: Vec::new(), + server_buffer: Vec::new(), } } diff --git a/traces/README.md b/traces/README.md index af1985b8..99f3f07f 100644 --- a/traces/README.md +++ b/traces/README.md @@ -7,4 +7,5 @@ A collection of sample packet captures pulled from a variety of sources. | `small_flows.pcap` | [Tcpreplay Sample Captures](https://tcpreplay.appneta.com/wiki/captures.html) | A synthetic combination of a few different applications and protocols at a relatively low network traffic rate. | | `tls_ciphers.pcap` | [Wireshark Sample Captures](https://wiki.wireshark.org/SampleCaptures) | OpenSSL client/server GET requests over TLS 1.2 with 73 different cipher suites. | | `quic_retry.pcapng`| [Wireshark Issue](https://gitlab.com/wireshark/wireshark/-/issues/18757) | An example of a QUIC Retry Packet. Original Pcap modified to remove CookedLinux and add Ether | -| `quic_xargs.pcap` | [illustrated-quic GitHub](https://github.com/syncsynchalt/illustrated-quic/blob/main/captures/capture.pcap) | The pcap used in the creation of [The Illustrated QUIC Connection](https://quic.xargs.org). | \ No newline at end of file +| `quic_xargs.pcap` | [illustrated-quic GitHub](https://github.com/syncsynchalt/illustrated-quic/blob/main/captures/capture.pcap) | The pcap used in the creation of [The Illustrated QUIC Connection](https://quic.xargs.org). | +| `quic_kyber.pcap` | Captured from Chrome 124 | A QUIC packet demonstrating the use of the Kyber keyshare, exceeding MTU, and requiring CRYPTO buffers. | \ No newline at end of file diff --git a/traces/quic_kyber.pcapng b/traces/quic_kyber.pcapng new file mode 100644 index 0000000000000000000000000000000000000000..476ca356f38626923f256ebe8eef03096d01c861 GIT binary patch literal 14580 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 Zhy4g~5D)Tc*Z$)f3i@gPZ~1ED{{Xf*kcR*O literal 0 HcmV?d00001 From 1f6c0172d37fd4c0ef14ab01cbe83c60f53cb60d Mon Sep 17 00:00:00 2001 From: Jackson Sippe Date: Thu, 25 Jul 2024 18:39:32 +0000 Subject: [PATCH 10/12] Comments --- core/src/protocols/stream/quic/mod.rs | 15 ++++++++++++++- core/src/protocols/stream/quic/parser.rs | 14 +++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/core/src/protocols/stream/quic/mod.rs b/core/src/protocols/stream/quic/mod.rs index 5b2a1160..38bf254a 100644 --- a/core/src/protocols/stream/quic/mod.rs +++ b/core/src/protocols/stream/quic/mod.rs @@ -53,16 +53,29 @@ pub enum QuicError { MissingCryptoFrames, } -/// Parsed Quic Packet contents +/// Parsed Quic connections #[derive(Debug, Serialize)] pub struct QuicConn { + // All packets associated with the connection pub packets: Vec, + + // All cids, both src and destination, seen in Long Header packets pub cids: HashSet, + + // Parsed TLS messsages pub tls: Tls, + + // Crypto needed to decrypt initial packets sent by client pub client_opener: Option, + + // Crypto needed to decrypt initial packets sent by server pub server_opener: Option, + + // Client buffer for multi-packet TLS messages #[serde(skip_serializing)] pub client_buffer: Vec, + + // Server buffer for multi-packet TLS messages #[serde(skip_serializing)] pub server_buffer: Vec, } diff --git a/core/src/protocols/stream/quic/parser.rs b/core/src/protocols/stream/quic/parser.rs index cb858c7d..e3566bea 100644 --- a/core/src/protocols/stream/quic/parser.rs +++ b/core/src/protocols/stream/quic/parser.rs @@ -313,9 +313,6 @@ impl QuicPacket { // Parse auth tag let tag = QuicPacket::access_data(data, offset, offset + tag_len)?; offset += tag_len; - // Commenting out the offset increase for tag_len to make clippy happy - // Will be needed when handling multiple QUIC packets in single datagram - // offset += tag_len; // Reconstruct authenticated data let mut ad = Vec::new(); ad.append(&mut [unprotected_header].to_vec()); @@ -328,6 +325,7 @@ impl QuicPacket { ad.append(&mut token_bytes.to_vec()); ad.append(&mut packet_len_bytes.to_vec()); ad.append(&mut initial_packet_number_bytes.to_vec()); + // Decrypt payload with proper keys based on traffic direction if dir { decrypted_payload = Some(conn.client_opener.as_ref().unwrap().open_with_u64_counter( @@ -387,6 +385,7 @@ impl QuicPacket { } let mut frames: Option> = None; + // Grab the proper buffer for CRYPTO frame data let crypto_buffer: &mut Vec = if dir { conn.client_buffer.as_mut() } else { @@ -394,11 +393,17 @@ impl QuicPacket { }; // If decrypted payload is not None, parse the frames if let Some(frame_bytes) = decrypted_payload { + // Get frames and reassembled CRYPTO data + // Pass the buffer's current length as starting offset for CRYPTO frames let (q_frames, mut crypto_bytes) = QuicFrame::parse_frames(&frame_bytes, crypto_buffer.len())?; frames = Some(q_frames); if !crypto_bytes.is_empty() { crypto_buffer.append(&mut crypto_bytes); + // Attempt to parse CRYPTO buffer + // clear on success + // TODO: This naive buffer will not work for out of order frames + // across packets or multiple messages in the same buffer match parse_tls_message_handshake(crypto_buffer) { Ok((_, msg)) => { conn.tls.parse_message_level(&msg, dir); @@ -480,6 +485,9 @@ impl QuicConn { fn parse_packet(&mut self, data: &[u8], direction: bool) -> ParseResult { let mut offset = 0; + // Iterate over all of the data in the datagram + // Parse as many QUIC packets as possible + // TODO: identify padding appended to datagram while data.len() > offset { if let Ok((quic, off)) = QuicPacket::parse_from(self, data, offset, direction) { self.packets.push(quic); From ec8461766f87ee4f9ccd9284eda53c679833a77b Mon Sep 17 00:00:00 2001 From: Jackson Sippe Date: Fri, 26 Jul 2024 19:17:01 +0000 Subject: [PATCH 11/12] Stop parsing connection after first short header packet --- core/src/protocols/stream/quic/parser.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/src/protocols/stream/quic/parser.rs b/core/src/protocols/stream/quic/parser.rs index e3566bea..a374741e 100644 --- a/core/src/protocols/stream/quic/parser.rs +++ b/core/src/protocols/stream/quic/parser.rs @@ -44,7 +44,10 @@ impl ConnParsable for QuicParser { } if let Ok(data) = (pdu.mbuf_ref()).get_data_slice(offset, length) { - self.sessions[0].parse_packet(data, pdu.dir) + if !self.sessions.is_empty() { + return self.sessions[0].parse_packet(data, pdu.dir) + } + return ParseResult::Skipped } else { log::warn!("Malformed packet on parse"); ParseResult::Skipped @@ -496,6 +499,9 @@ impl QuicConn { return ParseResult::Skipped; } } + if self.packets.last().is_some_and(|p| p.short_header.is_some()) { + return ParseResult::Done(0) + } ParseResult::Continue(0) } } From 84d63ad48ae1470f517e07c1a3b00cb485252721 Mon Sep 17 00:00:00 2001 From: Jackson Sippe Date: Fri, 26 Jul 2024 19:18:19 +0000 Subject: [PATCH 12/12] Clippy --- core/src/protocols/stream/quic/parser.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/core/src/protocols/stream/quic/parser.rs b/core/src/protocols/stream/quic/parser.rs index a374741e..73334acc 100644 --- a/core/src/protocols/stream/quic/parser.rs +++ b/core/src/protocols/stream/quic/parser.rs @@ -45,9 +45,9 @@ impl ConnParsable for QuicParser { if let Ok(data) = (pdu.mbuf_ref()).get_data_slice(offset, length) { if !self.sessions.is_empty() { - return self.sessions[0].parse_packet(data, pdu.dir) + return self.sessions[0].parse_packet(data, pdu.dir); } - return ParseResult::Skipped + ParseResult::Skipped } else { log::warn!("Malformed packet on parse"); ParseResult::Skipped @@ -499,8 +499,12 @@ impl QuicConn { return ParseResult::Skipped; } } - if self.packets.last().is_some_and(|p| p.short_header.is_some()) { - return ParseResult::Done(0) + if self + .packets + .last() + .is_some_and(|p| p.short_header.is_some()) + { + return ParseResult::Done(0); } ParseResult::Continue(0) }