]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
quic: complete parsing of initial for non gquic
authorPhilippe Antoine <contact@catenacyber.fr>
Mon, 14 Feb 2022 08:38:10 +0000 (09:38 +0100)
committerPhilippe Antoine <pantoine@oisf.net>
Tue, 2 Aug 2022 12:53:59 +0000 (14:53 +0200)
The format of initial packet for quic ietf, ie quic v1,
is described in rfc 9000, section 17.2.2

Parse more frames and logs interesting extensions from crypto frame

Do not try to parse encrypted data, ie after we have seen
a crypto frame in each direction.

Use sni from crypto frame with tls for detection already implemented

Ticket: #4967

configure.ac
rust/Cargo.toml.in
rust/src/quic/crypto.rs [new file with mode: 0644]
rust/src/quic/error.rs
rust/src/quic/frames.rs
rust/src/quic/logger.rs
rust/src/quic/mod.rs
rust/src/quic/parser.rs
rust/src/quic/quic.rs

index 659ace078dacb2f4c096a951f190c3efb137c7cb..507e813c1aa509da8dee7db13b247fe0b161894b 100644 (file)
             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.
index abd7fd977d236bbdccd9c7136bf26951ba85e2cd..34dbf29593150ea2dbb3a11291f10f20b7491ab8 100644 (file)
@@ -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 (file)
index 0000000..0b3ad5b
--- /dev/null
@@ -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::<Sha256>::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::<Sha256>::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<Sha256>, 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<QuicKeys> {
+    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::<Sha256>::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),
+    });
+}
index 4099fe4cab1574b6df86e7e7c7d7d0ece9725bf0..67f0437553248f1939d16b34ff292ecf2bde6be9 100644 (file)
@@ -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),
         }
     }
index 3f880dcd1b968b5599fe3979b0f59dc83cbc7680..d44e3e1951f376d949c0105d24c9d0992c6ca444 100644 (file)
@@ -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<TlsCipherSuiteID>,
+    // We remap the Vec<TlsExtension> from tls_parser::parse_tls_extensions because of
+    // the lifetime of TlsExtension due to references to the slice used for parsing
+    pub extv: Vec<QuicTlsExtension>,
+}
+
 #[derive(Debug, PartialEq)]
 pub(crate) enum Frame {
     Padding,
+    Ping,
+    Ack(Ack),
+    Crypto(Crypto),
     Stream(Stream),
     Unknown(Vec<u8>),
 }
 
+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<Vec<u8>>,
+}
+
+// get interesting stuff out of parsed tls extensions
+fn quic_get_tls_extensions(input: Option<&[u8]>) -> Vec<QuicTlsExtension> {
+    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<TagValue>, 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())),
             }
         };
index e01869503bac230e800c48ce1c8c225a3403ac82..8ec70f058e1de6fa63e3e562babe2ee5e55d52bd 100644 (file)
  * 02110-1301, USA.
  */
 
+use super::parser::QuicType;
 use super::quic::QuicTransaction;
 use crate::jsonbuilder::{JsonBuilder, JsonError};
 
+fn quic_tls_extension_name(e: u16) -> Option<String> {
+    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(())
 }
index 5a330e9c1ce9e0cabf32abf0d76e4a6c7dc8b6a9..91693059dccb5ca9da472034d2f166bd0b5977a9 100644 (file)
@@ -15,6 +15,7 @@
  * 02110-1301, USA.
  */
 
+mod crypto;
 mod cyu;
 pub mod detect;
 mod error;
index f4626563cd7cda9019d3bd337a94188a7b41ea57..ccafbf9faecb49cf9faecd72bae1104520ca1641 100644 (file)
@@ -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<u8>,
     pub dcid: Vec<u8>,
     pub scid: Vec<u8>,
+    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
         );
index 74ba8e2dd602ec2d000e19ad4db08952bc4da04f..b51b4e1681563f1ec1e3546b1f2a641dcbbc98ff 100644 (file)
  */
 
 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<Cyu>,
     pub sni: Option<Vec<u8>>,
     pub ua: Option<Vec<u8>>,
+    pub extv: Vec<QuicTlsExtension>,
     tx_data: AppLayerTxData,
 }
 
 impl QuicTransaction {
-    fn new(header: QuicHeader, data: QuicData, sni: Option<Vec<u8>>, ua: Option<Vec<u8>>) -> Self {
+    fn new(
+        header: QuicHeader, data: QuicData, sni: Option<Vec<u8>>, ua: Option<Vec<u8>>,
+        extv: Vec<QuicTlsExtension>,
+    ) -> 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<QuicKeys>,
+    hello_tc: bool,
+    hello_ts: bool,
     transactions: Vec<QuicTransaction>,
 }
 
@@ -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<Vec<u8>>, ua: Option<Vec<u8>>,
-    ) -> QuicTransaction {
-        let mut tx = QuicTransaction::new(header, data, sni, ua);
+        extb: Vec<QuicTlsExtension>,
+    ) {
+        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<Vec<u8>> = None;
-                        let mut ua: Option<Vec<u8>> = 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<u8>,
+    ) -> Result<usize, ()> {
+        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<Vec<u8>> = None;
+        let mut ua: Option<Vec<u8>> = None;
+        let mut extv: Vec<QuicTlsExtension> = 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,