retina_core/protocols/stream/tls/mod.rs
1//! TLS handshake parsing.
2
3mod handshake;
4pub(crate) mod parser;
5
6pub use self::handshake::*;
7
8use itertools::Itertools;
9use serde::Serialize;
10use tls_parser::{TlsCipherSuite, TlsState};
11
12/// GREASE values. See [RFC 8701](https://datatracker.ietf.org/doc/html/rfc8701).
13const GREASE_TABLE: &[u16] = &[
14 0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a, 0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a, 0x8a8a, 0x9a9a, 0xaaaa, 0xbaba,
15 0xcaca, 0xdada, 0xeaea, 0xfafa,
16];
17
18/// Parsed TLS handshake contents.
19#[derive(Debug, Default, Serialize)]
20pub struct Tls {
21 /// ClientHello message.
22 pub client_hello: Option<ClientHello>,
23 /// ServerHello message.
24 pub server_hello: Option<ServerHello>,
25
26 /// Server Certificate chain.
27 pub server_certificates: Vec<Certificate>,
28 /// Client Certificate chain.
29 pub client_certificates: Vec<Certificate>,
30
31 /// ServerKeyExchange message (TLS 1.2 or earlier).
32 pub server_key_exchange: Option<ServerKeyExchange>,
33 /// ClientKeyExchange message (TLS 1.2 or earlier).
34 pub client_key_exchange: Option<ClientKeyExchange>,
35
36 /// TLS state.
37 #[serde(skip)]
38 state: TlsState,
39 /// TCP chunks defragmentation buffer. Defragments TCP segments that arrive over multiple
40 /// packets.
41 #[serde(skip)]
42 tcp_buffer: Vec<u8>,
43 /// TLS record defragmentation buffer. Defragments TLS records that arrive over multiple
44 /// segments.
45 #[serde(skip)]
46 record_buffer: Vec<u8>,
47}
48
49impl Tls {
50 /// Returns the version identifier specified in the ClientHello, or `0` if no ClientHello was
51 /// observed in the handshake.
52 ///
53 /// ## Remarks
54 /// This method returns the message protocol version identifier sent in the ClientHello message,
55 /// not the record protocol version. This value may also differ from the negotiated handshake
56 /// version, such as in the case of TLS 1.3.
57 pub fn client_version(&self) -> u16 {
58 match &self.client_hello {
59 Some(client_hello) => client_hello.version.0,
60 None => 0,
61 }
62 }
63
64 /// Returns the hex-encoded client random, or `""` if no ClientHello was observed in the
65 /// handshake.
66 pub fn client_random(&self) -> String {
67 match &self.client_hello {
68 Some(client_hello) => hex::encode(&client_hello.random),
69 None => "".to_string(),
70 }
71 }
72
73 /// Returns the list of cipher suite names supported by the client.
74 ///
75 /// See [Transport Layer Security (TLS)
76 /// Parameters](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml) for a list
77 /// of TLS cipher suites.
78 pub fn client_ciphers(&self) -> Vec<String> {
79 match &self.client_hello {
80 Some(client_hello) => client_hello
81 .cipher_suites
82 .iter()
83 .map(|c| format!("{}", c))
84 .collect(),
85 None => vec![],
86 }
87 }
88
89 /// Returns the list of compression method identifiers supported by the client.
90 pub fn client_compression_algs(&self) -> Vec<u8> {
91 match &self.client_hello {
92 Some(client_hello) => client_hello.compression_algs.iter().map(|c| c.0).collect(),
93 None => vec![],
94 }
95 }
96
97 /// Returns the list of ALPN protocol names supported by the client.
98 pub fn client_alpn_protocols(&self) -> &[String] {
99 match &self.client_hello {
100 Some(client_hello) => client_hello.alpn_protocols.as_slice(),
101 None => &[],
102 }
103 }
104
105 /// Returns the list of signature algorithm names supported by the client.
106 ///
107 /// See [Transport Layer Security (TLS)
108 /// Parameters](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml) for a list
109 /// of TLS signature algorithms.
110 pub fn client_signature_algs(&self) -> Vec<String> {
111 match &self.client_hello {
112 Some(client_hello) => client_hello
113 .signature_algs
114 .iter()
115 .map(|s| format!("{}", s))
116 .collect(),
117 None => vec![],
118 }
119 }
120
121 /// Returns the list of extension names sent by the client.
122 ///
123 /// See [Transport Layer Security (TLS)
124 /// Extensions](https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml)
125 /// for a list of TLS extensions.
126 pub fn client_extensions(&self) -> Vec<String> {
127 match &self.client_hello {
128 Some(client_hello) => client_hello
129 .extension_list
130 .iter()
131 .map(|e| e.to_string())
132 .collect(),
133 None => vec![],
134 }
135 }
136
137 /// Returns the name of the server the client is trying to connect to.
138 ///
139 /// ## Remarks
140 /// This method returns the first server name in the server name list.
141 pub fn sni(&self) -> &str {
142 match &self.client_hello {
143 Some(client_hello) => match &client_hello.server_name {
144 Some(sni) => sni.as_str(),
145 None => "",
146 },
147 None => "",
148 }
149 }
150
151 /// Returns the version identifier specified in the ServerHello, or `0` if no ServerHello was
152 /// observed in the handshake.
153 ///
154 /// ## Remarks
155 /// This method returns the message protocol version identifier sent in the ServerHello message,
156 /// not the record protocol version. This value may also differ from the negotiated handshake
157 /// version, such as in the case of TLS 1.3.
158 pub fn server_version(&self) -> u16 {
159 match &self.server_hello {
160 Some(server_hello) => server_hello.version.0,
161 None => 0,
162 }
163 }
164
165 /// Returns the hex-encoded server random, or `""` if no ServerHello was observed in the
166 /// handshake.
167 pub fn server_random(&self) -> String {
168 match &self.server_hello {
169 Some(server_hello) => hex::encode(&server_hello.random),
170 None => "".to_string(),
171 }
172 }
173
174 /// Returns the cipher suite name chosen by the server, or `""` if no ServerHello was observed
175 /// in the handshake.
176 pub fn cipher(&self) -> String {
177 match &self.server_hello {
178 Some(server_hello) => format!("{}", server_hello.cipher_suite),
179 None => "".to_string(),
180 }
181 }
182
183 /// Returns the cipher suite chosen by the server, or `None` if no ServerHello was observed in
184 /// the handshake.
185 pub fn cipher_suite(&self) -> Option<&'static TlsCipherSuite> {
186 match &self.server_hello {
187 Some(server_hello) => server_hello.cipher_suite.get_ciphersuite(),
188 None => None,
189 }
190 }
191
192 /// Returns the compression method identifier chosen by the server, or `0` if no ServerHello was
193 /// observed in the handshake.
194 pub fn compression_alg(&self) -> u8 {
195 match &self.server_hello {
196 Some(server_hello) => server_hello.compression_alg.0,
197 None => 0,
198 }
199 }
200
201 /// Returns the list of extension names sent by the server.
202 ///
203 /// See [Transport Layer Security (TLS)
204 /// Extensions](https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml)
205 /// for a list of TLS extensions.
206 pub fn server_extensions(&self) -> Vec<String> {
207 match &self.server_hello {
208 Some(server_hello) => server_hello
209 .extension_list
210 .iter()
211 .map(|e| e.to_string())
212 .collect(),
213 None => vec![],
214 }
215 }
216
217 /// Returns the negotiated TLS handshake version identifier, or `0` if none was identified.
218 ///
219 /// ## Remarks
220 /// Retina supports parsing SSL 3.0 up to TLS 1.3. This method returns the negotiated handshake
221 /// version identifier, even if it does not correspond to a major TLS version (e.g., a draft or
222 /// bespoke version number).
223 pub fn version(&self) -> u16 {
224 match (&self.client_hello, &self.server_hello) {
225 (_ch, Some(sh)) => match sh.selected_version {
226 Some(version) => version.0,
227 None => sh.version.0,
228 },
229 (Some(ch), None) => ch.version.0,
230 (None, None) => 0,
231 }
232 }
233
234 /// Returns the client JA3 string, or `""` if no ClientHello was observed.
235 ///
236 /// ## Remarks
237 /// The JA3 string is defined as the concatenation of:
238 /// `TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats`. See
239 /// [salesforce/ja3](https://github.com/salesforce/ja3) for more details.
240 pub fn ja3_str(&self) -> String {
241 match &self.client_hello {
242 Some(ch) => {
243 format!(
244 "{},{},{},{},{}",
245 ch.version.0,
246 ch.cipher_suites
247 .iter()
248 .map(|x| x.0)
249 .filter(|x| !GREASE_TABLE.contains(x))
250 .join("-"),
251 ch.extension_list
252 .iter()
253 .map(|x| x.0)
254 .filter(|x| !GREASE_TABLE.contains(x))
255 .join("-"),
256 ch.supported_groups
257 .iter()
258 .map(|x| x.0)
259 .filter(|x| !GREASE_TABLE.contains(x))
260 .join("-"),
261 ch.ec_point_formats.iter().join("-"),
262 )
263 }
264 None => "".to_string(),
265 }
266 }
267
268 /// Returns the server JA3S string, or `""` if no ServerHello was observed.
269 ///
270 /// ## Remarks
271 /// The JA3S string is defined as the concatenation of: `TLSVersion,Cipher,Extensions`. See
272 /// [salesforce/ja3](https://github.com/salesforce/ja3) for more details.
273 pub fn ja3s_str(&self) -> String {
274 match &self.server_hello {
275 Some(sh) => {
276 format!(
277 "{},{},{}",
278 sh.version.0,
279 sh.cipher_suite.0,
280 sh.extension_list
281 .iter()
282 .map(|x| x.0)
283 .filter(|x| !GREASE_TABLE.contains(x))
284 .join("-")
285 )
286 }
287 None => "".to_string(),
288 }
289 }
290
291 /// Returns the JA3 fingerprint.
292 pub fn ja3_hash(&self) -> String {
293 format!("{:x}", md5::compute(self.ja3_str()))
294 }
295
296 /// Returns the JA3S fingerprint.
297 pub fn ja3s_hash(&self) -> String {
298 format!("{:x}", md5::compute(self.ja3s_str()))
299 }
300}