/* 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 { 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::>() .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), expected: Cyu) { let (header, frames) = input; let cyu = Cyu::generate(&header, &frames); assert_eq!(1, cyu.len()); assert_eq!(expected, cyu[0]); } }