diff --git a/configure.ac b/configure.ac index 659ace078d..507e813c1a 100644 --- a/configure.ac +++ b/configure.ac @@ -296,7 +296,7 @@ LUA_LIB_NAME="lua-5.1" CFLAGS="${CFLAGS} -DOS_DARWIN" CPPFLAGS="${CPPFLAGS} -I/opt/local/include" - LDFLAGS="${LDFLAGS} -L/opt/local/lib" + LDFLAGS="${LDFLAGS} -L/opt/local/lib -framework Security" ;; *-*-linux*) # Always compile with -fPIC on Linux for shared library support. diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in index abd7fd977d..34dbf29593 100644 --- a/rust/Cargo.toml.in +++ b/rust/Cargo.toml.in @@ -35,6 +35,9 @@ num-traits = "~0.2.14" widestring = "~0.4.3" flate2 = "~1.0.19" brotli = "~3.3.0" +hkdf = "~0.12.3" +aes = "~0.6.0" +aes-gcm = "~0.8.0" sawp-modbus = "~0.11.0" sawp = "~0.11.0" diff --git a/rust/src/quic/crypto.rs b/rust/src/quic/crypto.rs new file mode 100644 index 0000000000..0b3ad5be41 --- /dev/null +++ b/rust/src/quic/crypto.rs @@ -0,0 +1,180 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use aes::cipher::generic_array::GenericArray; +use aes::Aes128; +use aes::BlockCipher; +use aes::NewBlockCipher; +use aes_gcm::AeadInPlace; +use aes_gcm::Aes128Gcm; +use aes_gcm::NewAead; +use hkdf::Hkdf; +use sha2::Sha256; + +pub const AES128_KEY_LEN: usize = 16; +pub const AES128_TAG_LEN: usize = 16; +pub const AES128_IV_LEN: usize = 12; + +pub struct HeaderProtectionKey(Aes128); + +impl HeaderProtectionKey { + fn new(secret: &[u8]) -> Self { + let hk = Hkdf::::from_prk(secret).unwrap(); + let mut secret = [0u8; AES128_KEY_LEN]; + hkdf_expand_label(&hk, b"quic hp", &mut secret, AES128_KEY_LEN as u16); + return Self(Aes128::new(GenericArray::from_slice(&secret))); + } + + pub fn decrypt_in_place( + &self, sample: &[u8], first: &mut u8, packet_number: &mut [u8], + ) -> Result<(), ()> { + let mut mask = GenericArray::clone_from_slice(sample); + self.0.encrypt_block(&mut mask); + + let (first_mask, pn_mask) = mask.split_first().unwrap(); + + let bits = if (*first & 0x80) != 0 { + 0x0f // Long header: 4 bits masked + } else { + 0x1f // Short header: 5 bits masked + }; + + *first ^= first_mask & bits; + let pn_len = (*first & 0x03) as usize + 1; + + for (dst, m) in packet_number.iter_mut().zip(pn_mask).take(pn_len) { + *dst ^= m; + } + + Ok(()) + } +} + +pub struct PacketKey { + key: Aes128Gcm, + iv: [u8; AES128_IV_LEN], +} + +impl PacketKey { + fn new(secret: &[u8]) -> Self { + let hk = Hkdf::::from_prk(secret).unwrap(); + let mut secret = [0u8; AES128_KEY_LEN]; + hkdf_expand_label(&hk, b"quic key", &mut secret, AES128_KEY_LEN as u16); + let key = Aes128Gcm::new(GenericArray::from_slice(&secret)); + + let mut r = PacketKey { + key: key, + iv: [0u8; AES128_IV_LEN], + }; + hkdf_expand_label(&hk, b"quic iv", &mut r.iv, AES128_IV_LEN as u16); + return r; + } + + pub fn decrypt_in_place<'a>( + &self, packet_number: u64, header: &[u8], payload: &'a mut [u8], + ) -> Result<&'a [u8], ()> { + if payload.len() < AES128_TAG_LEN { + return Err(()); + } + let mut nonce = [0; AES128_IV_LEN]; + nonce[4..].copy_from_slice(&packet_number.to_be_bytes()); + for (nonce, inp) in nonce.iter_mut().zip(self.iv.iter()) { + *nonce ^= inp; + } + let tag_pos = payload.len() - AES128_TAG_LEN; + let (buffer, tag) = payload.split_at_mut(tag_pos); + let taga = GenericArray::from_slice(tag); + self.key + .decrypt_in_place_detached(GenericArray::from_slice(&nonce), header, buffer, &taga) + .map_err(|_| ())?; + Ok(&payload[..tag_pos]) + } +} + +pub struct DirectionalKeys { + pub header: HeaderProtectionKey, + pub packet: PacketKey, +} + +impl DirectionalKeys { + fn new(secret: &[u8]) -> Self { + Self { + header: HeaderProtectionKey::new(secret), + packet: PacketKey::new(secret), + } + } +} + +pub struct QuicKeys { + pub local: DirectionalKeys, + pub remote: DirectionalKeys, +} + +fn hkdf_expand_label(hk: &Hkdf, label: &[u8], okm: &mut [u8], olen: u16) { + const LABEL_PREFIX: &[u8] = b"tls13 "; + + let output_len = u16::to_be_bytes(olen); + let label_len = u8::to_be_bytes((LABEL_PREFIX.len() + label.len()) as u8); + let context_len = u8::to_be_bytes(0); + + let info = &[ + &output_len[..], + &label_len[..], + LABEL_PREFIX, + label, + &context_len[..], + ]; + + hk.expand_multi_info(info, okm).unwrap(); +} + +pub fn quic_keys_initial(version: u32, client_dst_connection_id: &[u8]) -> Option { + let salt = match version { + 0x51303530 => &[ + 0x50, 0x45, 0x74, 0xEF, 0xD0, 0x66, 0xFE, 0x2F, 0x9D, 0x94, 0x5C, 0xFC, 0xDB, 0xD3, + 0xA7, 0xF0, 0xD3, 0xB5, 0x6B, 0x45, + ], + 0xff00_001d..=0xff00_0020 => &[ + // https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#section-5.2 + 0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, + 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99, + ], + 0xfaceb002 | 0xff00_0017..=0xff00_001c => &[ + // https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-23#section-5.2 + 0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4, + 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02, + ], + 0x0000_0001 | 0xff00_0021..=0xff00_0022 => &[ + // https://www.rfc-editor.org/rfc/rfc9001.html#name-initial-secrets + 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, + 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a, + ], + _ => { + return None; + } + }; + let hk = Hkdf::::new(Some(salt), client_dst_connection_id); + let mut client_secret = [0u8; 32]; + hkdf_expand_label(&hk, b"client in", &mut client_secret, 32); + let mut server_secret = [0u8; 32]; + hkdf_expand_label(&hk, b"server in", &mut server_secret, 32); + + return Some(QuicKeys { + local: DirectionalKeys::new(&server_secret), + remote: DirectionalKeys::new(&client_secret), + }); +} diff --git a/rust/src/quic/error.rs b/rust/src/quic/error.rs index 4099fe4cab..67f0437553 100644 --- a/rust/src/quic/error.rs +++ b/rust/src/quic/error.rs @@ -24,6 +24,7 @@ pub enum QuicError { StreamTagNoMatch(u32), InvalidPacket, Incomplete, + Unhandled, NomError(ErrorKind), } @@ -45,6 +46,7 @@ impl fmt::Display for QuicError { } QuicError::Incomplete => write!(f, "Incomplete data"), QuicError::InvalidPacket => write!(f, "Invalid packet"), + QuicError::Unhandled => write!(f, "Unhandled packet"), QuicError::NomError(e) => write!(f, "Internal parser error {:?}", e), } } diff --git a/rust/src/quic/frames.rs b/rust/src/quic/frames.rs index 3f880dcd1b..d44e3e1951 100644 --- a/rust/src/quic/frames.rs +++ b/rust/src/quic/frames.rs @@ -16,6 +16,7 @@ */ use super::error::QuicError; +use crate::quic::parser::quic_var_uint; use nom::bytes::complete::take; use nom::combinator::{all_consuming, complete}; use nom::multi::{count, many0}; @@ -24,6 +25,12 @@ use nom::sequence::pair; use nom::IResult; use num::FromPrimitive; use std::fmt; +use tls_parser::TlsMessage::Handshake; +use tls_parser::TlsMessageHandshake::{ClientHello, ServerHello}; +use tls_parser::{ + parse_tls_extensions, parse_tls_message_handshake, TlsCipherSuiteID, TlsExtension, + TlsExtensionType, +}; /// Tuple of StreamTag and offset type TagOffset = (StreamTag, u32); @@ -115,13 +122,131 @@ impl fmt::Display for StreamTag { } } +#[derive(Debug, PartialEq)] +pub(crate) struct Ack { + pub largest_acknowledged: u64, + pub ack_delay: u64, + pub ack_range_count: u64, + pub first_ack_range: u64, +} + +#[derive(Debug, PartialEq)] +pub(crate) struct Crypto { + pub ciphers: Vec, + // We remap the Vec from tls_parser::parse_tls_extensions because of + // the lifetime of TlsExtension due to references to the slice used for parsing + pub extv: Vec, +} + #[derive(Debug, PartialEq)] pub(crate) enum Frame { Padding, + Ping, + Ack(Ack), + Crypto(Crypto), Stream(Stream), Unknown(Vec), } +fn parse_padding_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> { + // nom take_while: cannot infer type for type parameter `Error` declared on the function `take_while` + let mut offset = 0; + while offset < input.len() { + if input[offset] != 0 { + break; + } + offset = offset + 1; + } + return Ok((&input[offset..], Frame::Padding)); +} + +fn parse_ack_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> { + let (rest, largest_acknowledged) = quic_var_uint(input)?; + let (rest, ack_delay) = quic_var_uint(rest)?; + let (rest, ack_range_count) = quic_var_uint(rest)?; + let (mut rest, first_ack_range) = quic_var_uint(rest)?; + + for _ in 0..ack_range_count { + //RFC9000 section 19.3.1. ACK Ranges + let (rest1, _gap) = quic_var_uint(rest)?; + let (rest1, _ack_range_length) = quic_var_uint(rest1)?; + rest = rest1; + } + + Ok(( + rest, + Frame::Ack(Ack { + largest_acknowledged, + ack_delay, + ack_range_count, + first_ack_range, + }), + )) +} + +#[derive(Clone, Debug, PartialEq)] +pub struct QuicTlsExtension { + pub etype: TlsExtensionType, + pub values: Vec>, +} + +// get interesting stuff out of parsed tls extensions +fn quic_get_tls_extensions(input: Option<&[u8]>) -> Vec { + let mut extv = Vec::new(); + if let Some(extr) = input { + if let Ok((_, exts)) = parse_tls_extensions(extr) { + for e in &exts { + let etype = TlsExtensionType::from(e); + let mut values = Vec::new(); + match e { + TlsExtension::SNI(x) => { + for sni in x { + let mut value = Vec::new(); + value.extend_from_slice(sni.1); + values.push(value); + } + } + TlsExtension::ALPN(x) => { + for alpn in x { + let mut value = Vec::new(); + value.extend_from_slice(alpn); + values.push(value); + } + } + _ => {} + } + extv.push(QuicTlsExtension { etype, values }) + } + } + } + return extv; +} + +fn parse_crypto_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> { + let (rest, _offset) = quic_var_uint(input)?; + let (rest, length) = quic_var_uint(rest)?; + let (rest, data) = take(length as usize)(rest)?; + + if let Ok((rest, msg)) = parse_tls_message_handshake(data) { + if let Handshake(hs) = msg { + match hs { + ClientHello(ch) => { + let ciphers = ch.ciphers; + let extv = quic_get_tls_extensions(ch.ext); + return Ok((rest, Frame::Crypto(Crypto { ciphers, extv }))); + } + ServerHello(sh) => { + let ciphers = vec![sh.cipher]; + let extv = quic_get_tls_extensions(sh.ext); + return Ok((rest, Frame::Crypto(Crypto { ciphers, extv }))); + } + _ => {} + } + } + } + return Err(nom::Err::Error(QuicError::InvalidPacket)); +} + fn parse_tag(input: &[u8]) -> IResult<&[u8], StreamTag, QuicError> { let (rest, tag) = be_u32(input)?; @@ -164,8 +289,6 @@ fn parse_crypto_stream(input: &[u8]) -> IResult<&[u8], Vec, QuicError> } fn parse_stream_frame(input: &[u8], frame_ty: u8) -> IResult<&[u8], Frame, QuicError> { - let rest = input; - // 0b1_f_d_ooo_ss let fin = frame_ty & 0x40 == 0x40; let has_data_length = frame_ty & 0x20 == 0x20; @@ -180,7 +303,7 @@ fn parse_stream_frame(input: &[u8], frame_ty: u8) -> IResult<&[u8], Frame, QuicE let stream_id_hdr_length = usize::from((frame_ty & 0x03) + 1); - let (rest, stream_id) = take(stream_id_hdr_length)(rest)?; + let (rest, stream_id) = take(stream_id_hdr_length)(input)?; let (rest, offset) = take(offset_hdr_length)(rest)?; let (rest, data_length) = if has_data_length { @@ -210,6 +333,31 @@ fn parse_stream_frame(input: &[u8], frame_ty: u8) -> IResult<&[u8], Frame, QuicE )) } +fn parse_crypto_stream_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> { + let (rest, _offset) = quic_var_uint(input)?; + let (rest, data_length) = quic_var_uint(rest)?; + if data_length > u32::MAX as u64 { + return Err(nom::Err::Error(QuicError::Unhandled)); + } + let (rest, stream_data) = take(data_length as u32)(rest)?; + + let tags = if let Ok((_, tags)) = all_consuming(parse_crypto_stream)(stream_data) { + Some(tags) + } else { + None + }; + + Ok(( + rest, + Frame::Stream(Stream { + fin: false, + stream_id: Vec::new(), + offset: Vec::new(), + tags, + }), + )) +} + impl Frame { fn decode_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> { let (rest, frame_ty) = be_u8(input)?; @@ -220,7 +368,11 @@ impl Frame { parse_stream_frame(rest, frame_ty)? } else { match frame_ty { - 0x00 => (rest, Frame::Padding), + 0x00 => parse_padding_frame(rest)?, + 0x01 => (rest, Frame::Ping), + 0x02 => parse_ack_frame(rest)?, + 0x06 => parse_crypto_frame(rest)?, + 0x08 => parse_crypto_stream_frame(rest)?, _ => ([].as_ref(), Frame::Unknown(rest.to_vec())), } }; diff --git a/rust/src/quic/logger.rs b/rust/src/quic/logger.rs index e01869503b..8ec70f058e 100644 --- a/rust/src/quic/logger.rs +++ b/rust/src/quic/logger.rs @@ -15,12 +15,79 @@ * 02110-1301, USA. */ +use super::parser::QuicType; use super::quic::QuicTransaction; use crate::jsonbuilder::{JsonBuilder, JsonError}; +fn quic_tls_extension_name(e: u16) -> Option { + match e { + 0 => Some("server_name".to_string()), + 1 => Some("max_fragment_length".to_string()), + 2 => Some("client_certificate_url".to_string()), + 3 => Some("trusted_ca_keys".to_string()), + 4 => Some("truncated_hmac".to_string()), + 5 => Some("status_request".to_string()), + 6 => Some("user_mapping".to_string()), + 7 => Some("client_authz".to_string()), + 8 => Some("server_authz".to_string()), + 9 => Some("cert_type".to_string()), + 10 => Some("supported_groups".to_string()), + 11 => Some("ec_point_formats".to_string()), + 12 => Some("srp".to_string()), + 13 => Some("signature_algorithms".to_string()), + 14 => Some("use_srtp".to_string()), + 15 => Some("heartbeat".to_string()), + 16 => Some("alpn".to_string()), + 17 => Some("status_request_v2".to_string()), + 18 => Some("signed_certificate_timestamp".to_string()), + 19 => Some("client_certificate_type".to_string()), + 20 => Some("server_certificate_type".to_string()), + 21 => Some("padding".to_string()), + 22 => Some("encrypt_then_mac".to_string()), + 23 => Some("extended_master_secret".to_string()), + 24 => Some("token_binding".to_string()), + 25 => Some("cached_info".to_string()), + 26 => Some("tls_lts".to_string()), + 27 => Some("compress_certificate".to_string()), + 28 => Some("record_size_limit".to_string()), + 29 => Some("pwd_protect".to_string()), + 30 => Some("pwd_clear".to_string()), + 31 => Some("password_salt".to_string()), + 32 => Some("ticket_pinning".to_string()), + 33 => Some("tls_cert_with_extern_psk".to_string()), + 34 => Some("delegated_credentials".to_string()), + 35 => Some("session_ticket".to_string()), + 36 => Some("tlmsp".to_string()), + 37 => Some("tlmsp_proxying".to_string()), + 38 => Some("tlmsp_delegate".to_string()), + 39 => Some("supported_ekt_ciphers".to_string()), + 41 => Some("pre_shared_key".to_string()), + 42 => Some("early_data".to_string()), + 43 => Some("supported_versions".to_string()), + 44 => Some("cookie".to_string()), + 45 => Some("psk_key_exchange_modes".to_string()), + 47 => Some("certificate_authorities".to_string()), + 48 => Some("oid_filters".to_string()), + 49 => Some("post_handshake_auth".to_string()), + 50 => Some("signature_algorithms_cert".to_string()), + 51 => Some("key_share".to_string()), + 52 => Some("transparency_info".to_string()), + 53 => Some("connection_id_deprecated".to_string()), + 54 => Some("connection_id".to_string()), + 55 => Some("external_id_hash".to_string()), + 56 => Some("external_session_id".to_string()), + 57 => Some("quic_transport_parameters".to_string()), + 58 => Some("ticket_request".to_string()), + 59 => Some("dnssec_chain".to_string()), + 13172 => Some("next_protocol_negotiation".to_string()), + 65281 => Some("renegotiation_info".to_string()), + _ => None, + } +} + fn log_template(tx: &QuicTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("quic")?; - if tx.header.flags.is_long { + if tx.header.ty != QuicType::Short { js.set_string("version", String::from(tx.header.version).as_str())?; if let Some(sni) = &tx.sni { @@ -41,6 +108,28 @@ fn log_template(tx: &QuicTransaction, js: &mut JsonBuilder) -> Result<(), JsonEr js.close()?; } + if tx.extv.len() > 0 { + js.open_array("extensions")?; + for e in &tx.extv { + js.start_object()?; + let etype = u16::from(e.etype); + if let Some(s) = quic_tls_extension_name(etype) { + js.set_string("name", &s)?; + } + js.set_uint("type", etype.into())?; + + if e.values.len() > 0 { + js.open_array("values")?; + for i in 0..e.values.len() { + js.append_string(&String::from_utf8_lossy(&e.values[i]))?; + } + js.close()?; + } + js.close()?; + } + js.close()?; + } + js.close()?; Ok(()) } diff --git a/rust/src/quic/mod.rs b/rust/src/quic/mod.rs index 5a330e9c1c..91693059dc 100644 --- a/rust/src/quic/mod.rs +++ b/rust/src/quic/mod.rs @@ -15,6 +15,7 @@ * 02110-1301, USA. */ +mod crypto; mod cyu; pub mod detect; mod error; diff --git a/rust/src/quic/parser.rs b/rust/src/quic/parser.rs index f4626563cd..ccafbf9fae 100644 --- a/rust/src/quic/parser.rs +++ b/rust/src/quic/parser.rs @@ -18,8 +18,9 @@ use super::error::QuicError; use super::frames::Frame; use nom::bytes::complete::take; use nom::combinator::{all_consuming, map}; -use nom::number::complete::{be_u32, be_u8}; +use nom::number::complete::{be_u24, be_u32, be_u8}; use nom::IResult; +use std::convert::TryFrom; /* gQUIC is the Google version of QUIC. @@ -98,6 +99,58 @@ impl PublicFlags { } } +pub fn quic_pkt_num(input: &[u8]) -> u64 { + // There must be a more idiomatic way sigh... + match input.len() { + 1 => { + return input[0] as u64; + } + 2 => { + return ((input[0] as u64) << 8) | (input[1] as u64); + } + 3 => { + return ((input[0] as u64) << 16) | ((input[1] as u64) << 8) | (input[2] as u64); + } + 4 => { + return ((input[0] as u64) << 24) + | ((input[1] as u64) << 16) + | ((input[2] as u64) << 8) + | (input[3] as u64); + } + _ => { + // should not be reachable + debug_validate_fail!("Unexpected length for quic pkt num"); + return 0; + } + } +} + +pub fn quic_var_uint(input: &[u8]) -> IResult<&[u8], u64, QuicError> { + let (rest, first) = be_u8(input)?; + let msb = first >> 6; + let lsb = (first & 0x3F) as u64; + match msb { + 3 => { + // nom does not have be_u56 + let (rest, second) = be_u24(rest)?; + let (rest, third) = be_u32(rest)?; + return Ok((rest, (lsb << 56) | ((second as u64) << 32) | (third as u64))); + } + 2 => { + let (rest, second) = be_u24(rest)?; + return Ok((rest, (lsb << 24) | (second as u64))); + } + 1 => { + let (rest, second) = be_u8(rest)?; + return Ok((rest, (lsb << 8) | (second as u64))); + } + _ => { + // only remaining case is msb==0 + return Ok((rest, lsb)); + } + } +} + /// A QUIC packet's header. #[derive(Debug, PartialEq)] pub struct QuicHeader { @@ -107,6 +160,7 @@ pub struct QuicHeader { pub version_buf: Vec, pub dcid: Vec, pub scid: Vec, + pub length: u16, } #[derive(Debug, PartialEq)] @@ -126,6 +180,7 @@ impl QuicHeader { version_buf: Vec::new(), dcid, scid, + length: 0, } } @@ -148,6 +203,7 @@ impl QuicHeader { version_buf: Vec::new(), dcid: dcid.to_vec(), scid: Vec::new(), + length: 0, }, )); } else { @@ -212,15 +268,40 @@ impl QuicHeader { (rest, dcid.to_vec(), scid.to_vec()) }; + let mut has_length = false; let rest = match ty { QuicType::Initial => { - let (rest, _pkt_num) = be_u32(rest)?; - let (rest, _msg_auth_hash) = take(12usize)(rest)?; - - rest + if version.is_gquic() { + let (rest, _pkt_num) = be_u32(rest)?; + let (rest, _msg_auth_hash) = take(12usize)(rest)?; + + rest + } else { + let (rest, token_length) = quic_var_uint(rest)?; + let (rest, _token) = take(token_length as usize)(rest)?; + has_length = true; + rest + } } _ => rest, }; + let (rest, length) = if has_length { + let (rest2, plength) = quic_var_uint(rest)?; + if plength > rest2.len() as u64 { + return Err(nom::Err::Error(QuicError::InvalidPacket)); + } + if let Ok(length) = u16::try_from(plength) { + (rest2, length) + } else { + return Err(nom::Err::Error(QuicError::InvalidPacket)); + } + } else { + if let Ok(length) = u16::try_from(rest.len()) { + (rest, length) + } else { + return Err(nom::Err::Error(QuicError::InvalidPacket)); + } + }; Ok(( rest, @@ -231,6 +312,7 @@ impl QuicHeader { version_buf: version_buf.to_vec(), dcid, scid, + length, }, )) } @@ -275,7 +357,8 @@ mod tests { .to_vec(), scid: hex::decode("09b15e86dd0990aaf906c5de620c4538398ffa58") .unwrap() - .to_vec() + .to_vec(), + length: 1154, }, value ); @@ -295,6 +378,7 @@ mod tests { version_buf: vec![0x51, 0x30, 0x34, 0x34], dcid: hex::decode("05cad2cc06c4d0e4").unwrap().to_vec(), scid: Vec::new(), + length: 1042, }, header ); diff --git a/rust/src/quic/quic.rs b/rust/src/quic/quic.rs index 74ba8e2dd6..b51b4e1681 100644 --- a/rust/src/quic/quic.rs +++ b/rust/src/quic/quic.rs @@ -16,17 +16,20 @@ */ use super::{ + crypto::{quic_keys_initial, QuicKeys, AES128_KEY_LEN}, cyu::Cyu, - frames::{Frame, StreamTag}, - parser::{QuicData, QuicHeader, QuicType}, + frames::{Frame, QuicTlsExtension, StreamTag}, + parser::{quic_pkt_num, QuicData, QuicHeader, QuicType}, }; use crate::applayer::{self, *}; use crate::core::{AppProto, Flow, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_UDP}; use std::ffi::CString; +use tls_parser::TlsExtensionType; static mut ALPROTO_QUIC: AppProto = ALPROTO_UNKNOWN; const DEFAULT_DCID_LEN: usize = 16; +const PKT_NUM_BUF_MAX_LEN: usize = 4; #[derive(Debug)] pub struct QuicTransaction { @@ -35,11 +38,15 @@ pub struct QuicTransaction { pub cyu: Vec, pub sni: Option>, pub ua: Option>, + pub extv: Vec, tx_data: AppLayerTxData, } impl QuicTransaction { - fn new(header: QuicHeader, data: QuicData, sni: Option>, ua: Option>) -> Self { + fn new( + header: QuicHeader, data: QuicData, sni: Option>, ua: Option>, + extv: Vec, + ) -> Self { let cyu = Cyu::generate(&header, &data.frames); QuicTransaction { tx_id: 0, @@ -47,6 +54,7 @@ impl QuicTransaction { cyu, sni, ua, + extv, tx_data: AppLayerTxData::new(), } } @@ -54,6 +62,9 @@ impl QuicTransaction { pub struct QuicState { max_tx_id: u64, + keys: Option, + hello_tc: bool, + hello_ts: bool, transactions: Vec, } @@ -61,6 +72,9 @@ impl Default for QuicState { fn default() -> Self { Self { max_tx_id: 0, + keys: None, + hello_tc: false, + hello_ts: false, transactions: Vec::new(), } } @@ -88,11 +102,12 @@ impl QuicState { fn new_tx( &mut self, header: QuicHeader, data: QuicData, sni: Option>, ua: Option>, - ) -> QuicTransaction { - let mut tx = QuicTransaction::new(header, data, sni, ua); + extb: Vec, + ) { + let mut tx = QuicTransaction::new(header, data, sni, ua, extb); self.max_tx_id += 1; tx.tx_id = self.max_tx_id; - return tx; + self.transactions.push(tx); } fn tx_iterator( @@ -114,44 +129,157 @@ impl QuicState { return None; } - fn parse(&mut self, input: &[u8]) -> bool { - match QuicHeader::from_bytes(input, DEFAULT_DCID_LEN) { - Ok((rest, header)) => match QuicData::from_bytes(rest) { - Ok(data) => { - // no tx for the short header (data) frames - if header.ty != QuicType::Short { - let mut sni: Option> = None; - let mut ua: Option> = None; - for frame in &data.frames { - if let Frame::Stream(s) = frame { - if let Some(tags) = &s.tags { - for (tag, value) in tags { - if tag == &StreamTag::Sni { - sni = Some(value.to_vec()); - } else if tag == &StreamTag::Uaid { - ua = Some(value.to_vec()); - } - if sni.is_some() && ua.is_some() { - break; - } - } - } + fn decrypt<'a>( + &mut self, to_server: bool, header: &QuicHeader, framebuf: &'a [u8], buf: &'a [u8], + hlen: usize, output: &'a mut Vec, + ) -> Result { + if let Some(keys) = &self.keys { + let hkey = if to_server { + &keys.remote.header + } else { + &keys.local.header + }; + if framebuf.len() < PKT_NUM_BUF_MAX_LEN + AES128_KEY_LEN { + return Err(()); + } + let h2len = hlen + usize::from(header.length); + let mut h2 = Vec::with_capacity(h2len); + h2.extend_from_slice(&buf[..h2len]); + let mut h20 = h2[0]; + let mut pktnum_buf = Vec::with_capacity(PKT_NUM_BUF_MAX_LEN); + pktnum_buf.extend_from_slice(&h2[hlen..hlen + PKT_NUM_BUF_MAX_LEN]); + let r1 = hkey.decrypt_in_place( + &h2[hlen + PKT_NUM_BUF_MAX_LEN..hlen + PKT_NUM_BUF_MAX_LEN + AES128_KEY_LEN], + &mut h20, + &mut pktnum_buf, + ); + if !r1.is_ok() { + return Err(()); + } + // mutate one at a time + h2[0] = h20; + let _ = &h2[hlen..hlen + 1 + ((h20 & 3) as usize)] + .copy_from_slice(&pktnum_buf[..1 + ((h20 & 3) as usize)]); + let pkt_num = quic_pkt_num(&h2[hlen..hlen + 1 + ((h20 & 3) as usize)]); + if framebuf.len() < 1 + ((h20 & 3) as usize) { + return Err(()); + } + output.extend_from_slice(&framebuf[1 + ((h20 & 3) as usize)..]); + let pkey = if to_server { + &keys.remote.packet + } else { + &keys.local.packet + }; + let r = pkey.decrypt_in_place(pkt_num, &h2[..hlen + 1 + ((h20 & 3) as usize)], output); + if let Ok(r2) = r { + return Ok(r2.len()); + } + } + return Err(()); + } + + fn handle_frames(&mut self, data: QuicData, header: QuicHeader, to_server: bool) { + let mut sni: Option> = None; + let mut ua: Option> = None; + let mut extv: Vec = Vec::new(); + for frame in &data.frames { + match frame { + Frame::Stream(s) => { + if let Some(tags) = &s.tags { + for (tag, value) in tags { + if tag == &StreamTag::Sni { + sni = Some(value.to_vec()); + } else if tag == &StreamTag::Uaid { + ua = Some(value.to_vec()); + } + if sni.is_some() && ua.is_some() { + break; } } + } + } + Frame::Crypto(c) => { + for e in &c.extv { + if e.etype == TlsExtensionType::ServerName && e.values.len() > 0 { + sni = Some(e.values[0].to_vec()); + } + } + extv.extend_from_slice(&c.extv); + if to_server { + self.hello_ts = true + } else { + self.hello_tc = true + } + } + _ => {} + } + } + self.new_tx(header, data, sni, ua, extv); + } + + fn parse(&mut self, input: &[u8], to_server: bool) -> bool { + // so as to loop over multiple quic headers in one packet + let mut buf = input; + while buf.len() > 0 { + match QuicHeader::from_bytes(buf, DEFAULT_DCID_LEN) { + Ok((rest, header)) => { + if (to_server && self.hello_ts) || (!to_server && self.hello_tc) { + // payload is encrypted, stop parsing here + return true; + } + if header.ty == QuicType::Short { + // nothing to get + return true; + } + + // unprotect/decrypt packet + if self.keys.is_none() && header.ty == QuicType::Initial { + self.keys = quic_keys_initial(u32::from(header.version), &header.dcid); + } + // header.length was checked against rest.len() during parsing + let (mut framebuf, next_buf) = rest.split_at(header.length.into()); + let hlen = buf.len() - rest.len(); + let mut output; + if self.keys.is_some() { + output = Vec::with_capacity(framebuf.len() + 4); + if let Ok(dlen) = + self.decrypt(to_server, &header, framebuf, buf, hlen, &mut output) + { + output.resize(dlen, 0); + } else { + return false; + } + framebuf = &output; + } + buf = next_buf; + + if header.ty != QuicType::Initial { + // only version is interesting, no frames + self.new_tx( + header, + QuicData { frames: Vec::new() }, + None, + None, + Vec::new(), + ); + continue; + } - let transaction = self.new_tx(header, data, sni, ua); - self.transactions.push(transaction); + match QuicData::from_bytes(framebuf) { + Ok(data) => { + self.handle_frames(data, header, to_server); + } + Err(_e) => { + return false; + } } - return true; } Err(_e) => { return false; } - }, - Err(_e) => { - return false; } } + return true; } } @@ -190,17 +318,33 @@ pub unsafe extern "C" fn rs_quic_probing_parser( } #[no_mangle] -pub unsafe extern "C" fn rs_quic_parse( +pub unsafe extern "C" fn rs_quic_parse_tc( + _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, QuicState); + let buf = stream_slice.as_slice(); + + if state.parse(buf, false) { + return AppLayerResult::ok(); + } else { + return AppLayerResult::err(); + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_parse_ts( _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, stream_slice: StreamSlice, _data: *const std::os::raw::c_void, ) -> AppLayerResult { let state = cast_pointer!(state, QuicState); let buf = stream_slice.as_slice(); - if state.parse(buf) { + if state.parse(buf, true) { return AppLayerResult::ok(); + } else { + return AppLayerResult::err(); } - return AppLayerResult::err(); } #[no_mangle] @@ -275,8 +419,8 @@ pub unsafe extern "C" fn rs_quic_register_parser() { state_new: rs_quic_state_new, state_free: rs_quic_state_free, tx_free: rs_quic_state_tx_free, - parse_ts: rs_quic_parse, - parse_tc: rs_quic_parse, + parse_ts: rs_quic_parse_ts, + parse_tc: rs_quic_parse_tc, get_tx_count: rs_quic_state_get_tx_count, get_tx: rs_quic_state_get_tx, tx_comp_st_ts: 1,