Skip to main content

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}