/src/suricata7/rust/src/quic/cyu.rs
Line | Count | Source |
1 | | /* Copyright (C) 2021 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | |
18 | | use super::{ |
19 | | frames::Frame, |
20 | | parser::{QuicHeader, QuicVersion}, |
21 | | }; |
22 | | use md5::{Digest, Md5}; |
23 | | |
24 | | #[derive(Debug, PartialEq, Eq)] |
25 | | pub struct Cyu { |
26 | | pub string: String, |
27 | | pub hash: String, |
28 | | } |
29 | | |
30 | | impl Cyu { |
31 | 7.44k | pub(crate) fn new(string: String, hash: String) -> Self { |
32 | 7.44k | Self { string, hash } |
33 | 7.44k | } |
34 | | |
35 | 1.40M | pub(crate) fn generate(header: &QuicHeader, frames: &[Frame]) -> Vec<Cyu> { |
36 | 1.40M | let version = match header.version { |
37 | 7.89k | QuicVersion::Q043 => Some("43"), |
38 | 2.04k | QuicVersion::Q044 => Some("44"), |
39 | 1.13k | QuicVersion::Q045 => Some("44"), |
40 | 7.61k | QuicVersion::Q046 => Some("46"), |
41 | | _ => { |
42 | | SCLogDebug!( |
43 | | "Cannot match QUIC version {:?} to CYU version", |
44 | | header.version |
45 | | ); |
46 | 1.38M | None |
47 | | } |
48 | | }; |
49 | | |
50 | 1.40M | let mut cyu_hashes = Vec::new(); |
51 | | |
52 | 1.40M | if let Some(version) = version { |
53 | 71.0k | for frame in frames { |
54 | 52.4k | if let Frame::Stream(stream) = frame { |
55 | 12.7k | if let Some(tags) = &stream.tags { |
56 | 7.44k | let tags = tags |
57 | 7.44k | .iter() |
58 | 36.5k | .map(|(tag, _value)| tag.to_string()) |
59 | 7.44k | .collect::<Vec<String>>() |
60 | 7.44k | .join("-"); |
61 | | |
62 | 7.44k | let cyu_string = format!("{},{}", version, tags); |
63 | | |
64 | 7.44k | let mut hasher = Md5::new(); |
65 | 7.44k | hasher.update(cyu_string.as_bytes()); |
66 | 7.44k | let hash = hasher.finalize(); |
67 | | |
68 | 7.44k | let cyu_hash = format!("{:x}", hash); |
69 | | |
70 | 7.44k | cyu_hashes.push(Cyu::new(cyu_string, cyu_hash)); |
71 | 5.29k | } |
72 | 39.6k | } |
73 | | } |
74 | 1.38M | } |
75 | | |
76 | 1.40M | cyu_hashes |
77 | 1.40M | } |
78 | | } |
79 | | |
80 | | #[cfg(test)] |
81 | | #[allow(clippy::unused_unit)] |
82 | | mod tests { |
83 | | use super::*; |
84 | | use crate::quic::frames::{Frame, Stream, StreamTag}; |
85 | | use crate::quic::parser::{PublicFlags, QuicType}; |
86 | | use test_case::test_case; |
87 | | |
88 | | macro_rules! mock_header_and_frames { |
89 | | ($version:expr, $($variants:expr),+) => {{ |
90 | | let header = QuicHeader::new( |
91 | | PublicFlags::new(0x80), |
92 | | QuicType::Initial, |
93 | | $version, |
94 | | vec![], |
95 | | vec![], |
96 | | ); |
97 | | |
98 | | let frames = vec![ |
99 | | Frame::Stream(Stream { |
100 | | fin: false, |
101 | | stream_id: vec![], |
102 | | offset: vec![], |
103 | | tags: Some(vec![$(($variants, vec![])),*]) |
104 | | }) |
105 | | ]; |
106 | | |
107 | | (header, frames) |
108 | | }}; |
109 | | } |
110 | | |
111 | | // Salesforce tests here: |
112 | | // https://engineering.salesforce.com/gquic-protocol-analysis-and-fingerprinting-in-zeek-a4178855d75f |
113 | | #[test_case( |
114 | | mock_header_and_frames!( |
115 | | // version |
116 | | QuicVersion::Q046, |
117 | | // tags |
118 | | StreamTag::Pad, StreamTag::Sni, |
119 | | StreamTag::Stk, StreamTag::Ver, |
120 | | StreamTag::Ccs, StreamTag::Nonc, |
121 | | StreamTag::Aead, StreamTag::Uaid, |
122 | | StreamTag::Scid, StreamTag::Tcid, |
123 | | StreamTag::Pdmd, StreamTag::Smhl, |
124 | | StreamTag::Icsl, StreamTag::Nonp, |
125 | | StreamTag::Pubs, StreamTag::Mids, |
126 | | StreamTag::Scls, StreamTag::Kexs, |
127 | | StreamTag::Xlct, StreamTag::Csct, |
128 | | StreamTag::Copt, StreamTag::Ccrt, |
129 | | StreamTag::Irtt, StreamTag::Cfcw, |
130 | | StreamTag::Sfcw |
131 | | ), |
132 | | Cyu { |
133 | | 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(), |
134 | | hash: "a46560d4548108cf99308319b3b85346".to_string(), |
135 | | }; "test cyu 1" |
136 | | )] |
137 | | #[test_case( |
138 | | mock_header_and_frames!( |
139 | | // version |
140 | | QuicVersion::Q043, |
141 | | // tags |
142 | | StreamTag::Pad, StreamTag::Sni, |
143 | | StreamTag::Ver, StreamTag::Ccs, |
144 | | StreamTag::Pdmd, StreamTag::Icsl, |
145 | | StreamTag::Mids, StreamTag::Cfcw, |
146 | | StreamTag::Sfcw |
147 | | ), |
148 | | Cyu { |
149 | | string: "43,PAD-SNI-VER-CCS-PDMD-ICSL-MIDS-CFCW-SFCW".to_string(), |
150 | | hash: "e030dea1f2eea44ac7db5fe4de792acd".to_string(), |
151 | | }; "test cyu 2" |
152 | | )] |
153 | | #[test_case( |
154 | | mock_header_and_frames!( |
155 | | // version |
156 | | QuicVersion::Q043, |
157 | | // tags |
158 | | StreamTag::Pad, StreamTag::Sni, |
159 | | StreamTag::Stk, StreamTag::Ver, |
160 | | StreamTag::Ccs, StreamTag::Scid, |
161 | | StreamTag::Pdmd, StreamTag::Icsl, |
162 | | StreamTag::Mids, StreamTag::Cfcw, |
163 | | StreamTag::Sfcw |
164 | | ), |
165 | | Cyu { |
166 | | string: "43,PAD-SNI-STK-VER-CCS-SCID-PDMD-ICSL-MIDS-CFCW-SFCW".to_string(), |
167 | | hash: "0811fab28e41e8c8a33e220a15b964d9".to_string(), |
168 | | }; "test cyu 3" |
169 | | )] |
170 | | #[test_case( |
171 | | mock_header_and_frames!( |
172 | | // version |
173 | | QuicVersion::Q043, |
174 | | // tags |
175 | | StreamTag::Pad, StreamTag::Sni, |
176 | | StreamTag::Stk, StreamTag::Ver, |
177 | | StreamTag::Ccs, StreamTag::Nonc, |
178 | | StreamTag::Aead, StreamTag::Scid, |
179 | | StreamTag::Pdmd, StreamTag::Icsl, |
180 | | StreamTag::Pubs, StreamTag::Mids, |
181 | | StreamTag::Kexs, StreamTag::Xlct, |
182 | | StreamTag::Cfcw, StreamTag::Sfcw |
183 | | ), |
184 | | Cyu { |
185 | | string: "43,PAD-SNI-STK-VER-CCS-NONC-AEAD-SCID-PDMD-ICSL-PUBS-MIDS-KEXS-XLCT-CFCW-SFCW".to_string(), |
186 | | hash: "d8b208b236d176c89407500dbefb04c2".to_string(), |
187 | | }; "test cyu 4" |
188 | | )] |
189 | | fn test_cyu_generate(input: (QuicHeader, Vec<Frame>), expected: Cyu) { |
190 | | let (header, frames) = input; |
191 | | |
192 | | let cyu = Cyu::generate(&header, &frames); |
193 | | assert_eq!(1, cyu.len()); |
194 | | assert_eq!(expected, cyu[0]); |
195 | | } |
196 | | } |