diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
commit | a0aa2307322cd47bbf416810ac0292925e03be87 (patch) | |
tree | 37076262a026c4b48c8a0e84f44ff9187556ca35 /rust/src/quic/cyu.rs | |
parent | Initial commit. (diff) | |
download | suricata-a0aa2307322cd47bbf416810ac0292925e03be87.tar.xz suricata-a0aa2307322cd47bbf416810ac0292925e03be87.zip |
Adding upstream version 1:7.0.3.upstream/1%7.0.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'rust/src/quic/cyu.rs')
-rw-r--r-- | rust/src/quic/cyu.rs | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/rust/src/quic/cyu.rs b/rust/src/quic/cyu.rs new file mode 100644 index 0000000..0264678 --- /dev/null +++ b/rust/src/quic/cyu.rs @@ -0,0 +1,195 @@ +/* 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 super::{ + frames::Frame, + parser::{QuicHeader, QuicVersion}, +}; +use md5::{Digest, Md5}; + +#[derive(Debug, PartialEq, Eq)] +pub struct Cyu { + pub string: String, + pub hash: String, +} + +impl Cyu { + pub(crate) fn new(string: String, hash: String) -> Self { + Self { string, hash } + } + + pub(crate) fn generate(header: &QuicHeader, frames: &[Frame]) -> Vec<Cyu> { + let version = match header.version { + QuicVersion::Q043 => Some("43"), + QuicVersion::Q044 => Some("44"), + QuicVersion::Q045 => Some("44"), + QuicVersion::Q046 => Some("46"), + _ => { + SCLogDebug!( + "Cannot match QUIC version {:?} to CYU version", + header.version + ); + None + } + }; + + let mut cyu_hashes = Vec::new(); + + if let Some(version) = version { + for frame in frames { + if let Frame::Stream(stream) = frame { + if let Some(tags) = &stream.tags { + let tags = tags + .iter() + .map(|(tag, _value)| tag.to_string()) + .collect::<Vec<String>>() + .join("-"); + + let cyu_string = format!("{},{}", version, tags); + + let mut hasher = Md5::new(); + hasher.update(cyu_string.as_bytes()); + let hash = hasher.finalize(); + + let cyu_hash = format!("{:x}", hash); + + cyu_hashes.push(Cyu::new(cyu_string, cyu_hash)); + } + } + } + } + + cyu_hashes + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::quic::frames::{Frame, Stream, StreamTag}; + use crate::quic::parser::{PublicFlags, QuicType}; + use test_case::test_case; + + macro_rules! mock_header_and_frames { + ($version:expr, $($variants:expr),+) => {{ + let header = QuicHeader::new( + PublicFlags::new(0x80), + QuicType::Initial, + $version, + vec![], + vec![], + ); + + let frames = vec![ + Frame::Stream(Stream { + fin: false, + stream_id: vec![], + offset: vec![], + tags: Some(vec![$(($variants, vec![])),*]) + }) + ]; + + (header, frames) + }}; + } + + // Salesforce tests here: + // https://engineering.salesforce.com/gquic-protocol-analysis-and-fingerprinting-in-zeek-a4178855d75f + #[test_case( + mock_header_and_frames!( + // version + QuicVersion::Q046, + // tags + StreamTag::Pad, StreamTag::Sni, + StreamTag::Stk, StreamTag::Ver, + StreamTag::Ccs, StreamTag::Nonc, + StreamTag::Aead, StreamTag::Uaid, + StreamTag::Scid, StreamTag::Tcid, + StreamTag::Pdmd, StreamTag::Smhl, + StreamTag::Icsl, StreamTag::Nonp, + StreamTag::Pubs, StreamTag::Mids, + StreamTag::Scls, StreamTag::Kexs, + StreamTag::Xlct, StreamTag::Csct, + StreamTag::Copt, StreamTag::Ccrt, + StreamTag::Irtt, StreamTag::Cfcw, + StreamTag::Sfcw + ), + Cyu { + string: "46,PAD-SNI-STK-VER-CCS-NONC-AEAD-UAID-SCID-TCID-PDMD-SMHL-ICSL-NONP-PUBS-MIDS-SCLS-KEXS-XLCT-CSCT-COPT-CCRT-IRTT-CFCW-SFCW".to_string(), + hash: "a46560d4548108cf99308319b3b85346".to_string(), + }; "test cyu 1" + )] + #[test_case( + mock_header_and_frames!( + // version + QuicVersion::Q043, + // tags + StreamTag::Pad, StreamTag::Sni, + StreamTag::Ver, StreamTag::Ccs, + StreamTag::Pdmd, StreamTag::Icsl, + StreamTag::Mids, StreamTag::Cfcw, + StreamTag::Sfcw + ), + Cyu { + string: "43,PAD-SNI-VER-CCS-PDMD-ICSL-MIDS-CFCW-SFCW".to_string(), + hash: "e030dea1f2eea44ac7db5fe4de792acd".to_string(), + }; "test cyu 2" + )] + #[test_case( + mock_header_and_frames!( + // version + QuicVersion::Q043, + // tags + StreamTag::Pad, StreamTag::Sni, + StreamTag::Stk, StreamTag::Ver, + StreamTag::Ccs, StreamTag::Scid, + StreamTag::Pdmd, StreamTag::Icsl, + StreamTag::Mids, StreamTag::Cfcw, + StreamTag::Sfcw + ), + Cyu { + string: "43,PAD-SNI-STK-VER-CCS-SCID-PDMD-ICSL-MIDS-CFCW-SFCW".to_string(), + hash: "0811fab28e41e8c8a33e220a15b964d9".to_string(), + }; "test cyu 3" + )] + #[test_case( + mock_header_and_frames!( + // version + QuicVersion::Q043, + // tags + StreamTag::Pad, StreamTag::Sni, + StreamTag::Stk, StreamTag::Ver, + StreamTag::Ccs, StreamTag::Nonc, + StreamTag::Aead, StreamTag::Scid, + StreamTag::Pdmd, StreamTag::Icsl, + StreamTag::Pubs, StreamTag::Mids, + StreamTag::Kexs, StreamTag::Xlct, + StreamTag::Cfcw, StreamTag::Sfcw + ), + Cyu { + string: "43,PAD-SNI-STK-VER-CCS-NONC-AEAD-SCID-PDMD-ICSL-PUBS-MIDS-KEXS-XLCT-CFCW-SFCW".to_string(), + hash: "d8b208b236d176c89407500dbefb04c2".to_string(), + }; "test cyu 4" + )] + fn test_cyu_generate(input: (QuicHeader, Vec<Frame>), expected: Cyu) { + let (header, frames) = input; + + let cyu = Cyu::generate(&header, &frames); + assert_eq!(1, cyu.len()); + assert_eq!(expected, cyu[0]); + } +} |