]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
quic: reassemble crypto frames and parse it
authorPhilippe Antoine <pantoine@oisf.net>
Sun, 31 Jul 2022 18:22:59 +0000 (20:22 +0200)
committerPhilippe Antoine <pantoine@oisf.net>
Tue, 2 Aug 2022 12:55:12 +0000 (14:55 +0200)
rust/src/quic/frames.rs

index e1dedba5af9ce1bd6ba7c88ada30d8f2de2f31e4..c115725ace8114fea366ad1fc32cd0faf8544b00 100644 (file)
@@ -29,7 +29,7 @@ use tls_parser::TlsMessage::Handshake;
 use tls_parser::TlsMessageHandshake::{ClientHello, ServerHello};
 use tls_parser::{
     parse_tls_extensions, parse_tls_message_handshake, TlsCipherSuiteID, TlsExtension,
-    TlsExtensionType,
+    TlsExtensionType, TlsMessage,
 };
 
 /// Tuple of StreamTag and offset
@@ -139,12 +139,23 @@ pub(crate) struct Crypto {
     pub ja3: String,
 }
 
+#[derive(Debug, PartialEq)]
+pub(crate) struct CryptoFrag {
+    pub offset: u64,
+    pub length: u64,
+    pub data: Vec<u8>,
+}
+
 #[derive(Debug, PartialEq)]
 pub(crate) enum Frame {
     Padding,
     Ping,
     Ack(Ack),
+    // this is more than a crypto frame : it contains a fully parsed tls hello
     Crypto(Crypto),
+    // this is a regular quic crypto frame : they can be reassembled
+    // in order to parse a tls hello
+    CryptoFrag(CryptoFrag),
     Stream(Stream),
     Unknown(Vec<u8>),
 }
@@ -272,45 +283,77 @@ fn quic_get_tls_extensions(
     return extv;
 }
 
+fn parse_quic_handshake(msg: TlsMessage) -> Option<Frame> {
+    if let Handshake(hs) = msg {
+        match hs {
+            ClientHello(ch) => {
+                let mut ja3 = String::with_capacity(256);
+                ja3.push_str(&u16::from(ch.version).to_string());
+                ja3.push_str(",");
+                let mut dash = false;
+                for c in &ch.ciphers {
+                    if dash {
+                        ja3.push_str("-");
+                    } else {
+                        dash = true;
+                    }
+                    ja3.push_str(&u16::from(*c).to_string());
+                }
+                ja3.push_str(",");
+                let ciphers = ch.ciphers;
+                let extv = quic_get_tls_extensions(ch.ext, &mut ja3, true);
+                return Some(Frame::Crypto(Crypto { ciphers, extv, ja3 }));
+            }
+            ServerHello(sh) => {
+                let mut ja3 = String::with_capacity(256);
+                ja3.push_str(&u16::from(sh.version).to_string());
+                ja3.push_str(",");
+                ja3.push_str(&u16::from(sh.cipher).to_string());
+                ja3.push_str(",");
+                let ciphers = vec![sh.cipher];
+                let extv = quic_get_tls_extensions(sh.ext, &mut ja3, false);
+                return Some(Frame::Crypto(Crypto { ciphers, extv, ja3 }));
+            }
+            _ => {}
+        }
+    }
+    return None;
+}
+
 fn parse_crypto_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> {
-    let (rest, _offset) = quic_var_uint(input)?;
+    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 mut ja3 = String::with_capacity(256);
-                    ja3.push_str(&u16::from(ch.version).to_string());
-                    ja3.push_str(",");
-                    let mut dash = false;
-                    for c in &ch.ciphers {
-                        if dash {
-                            ja3.push_str("-");
-                        } else {
-                            dash = true;
-                        }
-                        ja3.push_str(&u16::from(*c).to_string());
-                    }
-                    ja3.push_str(",");
-                    let ciphers = ch.ciphers;
-                    let extv = quic_get_tls_extensions(ch.ext, &mut ja3, true);
-                    return Ok((rest, Frame::Crypto(Crypto { ciphers, extv, ja3 })));
-                }
-                ServerHello(sh) => {
-                    let mut ja3 = String::with_capacity(256);
-                    ja3.push_str(&u16::from(sh.version).to_string());
-                    ja3.push_str(",");
-                    ja3.push_str(&u16::from(sh.cipher).to_string());
-                    ja3.push_str(",");
-                    let ciphers = vec![sh.cipher];
-                    let extv = quic_get_tls_extensions(sh.ext, &mut ja3, false);
-                    return Ok((rest, Frame::Crypto(Crypto { ciphers, extv, ja3 })));
-                }
-                _ => {}
+    if offset > 0 {
+        return Ok((
+            rest,
+            Frame::CryptoFrag(CryptoFrag {
+                offset,
+                length,
+                data: data.to_vec(),
+            }),
+        ));
+    }
+    // if we have offset 0, try quick path : parse directly
+    match parse_tls_message_handshake(data) {
+        Ok((_, msg)) => {
+            if let Some(c) = parse_quic_handshake(msg) {
+                return Ok((rest, c));
             }
         }
+        Err(nom7::Err::Incomplete(_)) => {
+            // offset 0 but incomplete : save it as a fragment for later reassembly
+            return Ok((
+                rest,
+                Frame::CryptoFrag(CryptoFrag {
+                    offset,
+                    length,
+                    data: data.to_vec(),
+                }),
+            ));
+        }
+        _ => {}
     }
     return Err(nom::Err::Error(QuicError::InvalidPacket));
 }
@@ -449,7 +492,44 @@ impl Frame {
     }
 
     pub(crate) fn decode_frames(input: &[u8]) -> IResult<&[u8], Vec<Frame>, QuicError> {
-        let (rest, frames) = many0(complete(Frame::decode_frame))(input)?;
+        let (rest, mut frames) = many0(complete(Frame::decode_frame))(input)?;
+
+        // reassemble crypto fragments : first find total size
+        let mut crypto_max_size = 0;
+        let mut crypto_total_size = 0;
+        for f in &frames {
+            match f {
+                Frame::CryptoFrag(c) => {
+                    if crypto_max_size < c.offset + c.length {
+                        crypto_max_size = c.offset + c.length;
+                    }
+                    crypto_total_size += c.length;
+                }
+                _ => {}
+            }
+        }
+        if crypto_max_size > 0 && crypto_total_size == crypto_max_size {
+            // we have some, and no gaps from offset 0
+            let mut d = vec![0; crypto_max_size as usize];
+            for f in &frames {
+                match f {
+                    Frame::CryptoFrag(c) => {
+                        d[c.offset as usize..(c.offset + c.length) as usize]
+                            .clone_from_slice(&c.data);
+                    }
+                    _ => {}
+                }
+            }
+            match parse_tls_message_handshake(&d) {
+                Ok((_, msg)) => {
+                    if let Some(c) = parse_quic_handshake(msg) {
+                        // add a parsed crypto frame
+                        frames.push(c);
+                    }
+                }
+                _ => {}
+            }
+        }
 
         Ok((rest, frames))
     }