Skip to main content

iris_core/protocols/stream/tls/
mod.rs

1//! TLS handshake parsing.
2
3mod handshake;
4pub 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, Clone)]
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    /// Offset of the start of ciphertext (end of headers) in last-processed segment.
48    /// This will only be (possibly) relevant for last packet in the TLS handshake.
49    /// This can be inaccurate under 0-RTT est. or unsupported extensions.
50    #[serde(skip)]
51    last_body_offset: Option<usize>,
52}
53
54impl Tls {
55    /// Returns the version identifier specified in the ClientHello, or `0` if no ClientHello was
56    /// observed in the handshake.
57    ///
58    /// ## Remarks
59    /// This method returns the message protocol version identifier sent in the ClientHello message,
60    /// not the record protocol version. This value may also differ from the negotiated handshake
61    /// version, such as in the case of TLS 1.3.
62    pub fn client_version(&self) -> u16 {
63        match &self.client_hello {
64            Some(client_hello) => client_hello.version.0,
65            None => 0,
66        }
67    }
68
69    /// Returns the hex-encoded client random, or `""` if no ClientHello was observed in the
70    /// handshake.
71    pub fn client_random(&self) -> String {
72        match &self.client_hello {
73            Some(client_hello) => hex::encode(&client_hello.random),
74            None => "".to_string(),
75        }
76    }
77
78    /// Returns the list of cipher suite names supported by the client.
79    ///
80    /// See [Transport Layer Security (TLS)
81    /// Parameters](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml) for a list
82    /// of TLS cipher suites.
83    pub fn client_ciphers(&self) -> Vec<String> {
84        match &self.client_hello {
85            Some(client_hello) => client_hello
86                .cipher_suites
87                .iter()
88                .map(|c| format!("{}", c))
89                .collect(),
90            None => vec![],
91        }
92    }
93
94    /// Returns the list of compression method identifiers supported by the client.
95    pub fn client_compression_algs(&self) -> Vec<u8> {
96        match &self.client_hello {
97            Some(client_hello) => client_hello.compression_algs.iter().map(|c| c.0).collect(),
98            None => vec![],
99        }
100    }
101
102    /// Returns the list of ALPN protocol names supported by the client.
103    pub fn client_alpn_protocols(&self) -> &[String] {
104        match &self.client_hello {
105            Some(client_hello) => client_hello.alpn_protocols.as_slice(),
106            None => &[],
107        }
108    }
109
110    /// Returns the list of signature algorithm names supported by the client.
111    ///
112    /// See [Transport Layer Security (TLS)
113    /// Parameters](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml) for a list
114    /// of TLS signature algorithms.
115    pub fn client_signature_algs(&self) -> Vec<String> {
116        match &self.client_hello {
117            Some(client_hello) => client_hello
118                .signature_algs
119                .iter()
120                .map(|s| format!("{}", s))
121                .collect(),
122            None => vec![],
123        }
124    }
125
126    /// Returns the list of extension names sent by the client.
127    ///
128    /// See [Transport Layer Security (TLS)
129    /// Extensions](https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml)
130    /// for a list of TLS extensions.
131    pub fn client_extensions(&self) -> Vec<String> {
132        match &self.client_hello {
133            Some(client_hello) => client_hello
134                .extension_list
135                .iter()
136                .map(|e| e.to_string())
137                .collect(),
138            None => vec![],
139        }
140    }
141
142    /// Returns the name of the server the client is trying to connect to.
143    ///
144    /// ## Remarks
145    /// This method returns the first server name in the server name list.
146    pub fn sni(&self) -> &str {
147        match &self.client_hello {
148            Some(client_hello) => match &client_hello.server_name {
149                Some(sni) => sni.as_str(),
150                None => "",
151            },
152            None => "",
153        }
154    }
155
156    /// Returns the version identifier specified in the ServerHello, or `0` if no ServerHello was
157    /// observed in the handshake.
158    ///
159    /// ## Remarks
160    /// This method returns the message protocol version identifier sent in the ServerHello message,
161    /// not the record protocol version. This value may also differ from the negotiated handshake
162    /// version, such as in the case of TLS 1.3.
163    pub fn server_version(&self) -> u16 {
164        match &self.server_hello {
165            Some(server_hello) => server_hello.version.0,
166            None => 0,
167        }
168    }
169
170    /// Returns the hex-encoded server random, or `""` if no ServerHello was observed in the
171    /// handshake.
172    pub fn server_random(&self) -> String {
173        match &self.server_hello {
174            Some(server_hello) => hex::encode(&server_hello.random),
175            None => "".to_string(),
176        }
177    }
178
179    /// Returns the cipher suite name chosen by the server, or `""` if no ServerHello was observed
180    /// in the handshake.
181    pub fn cipher(&self) -> String {
182        match &self.server_hello {
183            Some(server_hello) => format!("{}", server_hello.cipher_suite),
184            None => "".to_string(),
185        }
186    }
187
188    /// Returns the cipher suite chosen by the server, or `None` if no ServerHello was observed in
189    /// the handshake.
190    pub fn cipher_suite(&self) -> Option<&'static TlsCipherSuite> {
191        match &self.server_hello {
192            Some(server_hello) => server_hello.cipher_suite.get_ciphersuite(),
193            None => None,
194        }
195    }
196
197    /// Returns the compression method identifier chosen by the server, or `0` if no ServerHello was
198    /// observed in the handshake.
199    pub fn compression_alg(&self) -> u8 {
200        match &self.server_hello {
201            Some(server_hello) => server_hello.compression_alg.0,
202            None => 0,
203        }
204    }
205
206    /// Returns the list of extension names sent by the server.
207    ///
208    /// See [Transport Layer Security (TLS)
209    /// Extensions](https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml)
210    /// for a list of TLS extensions.
211    pub fn server_extensions(&self) -> Vec<String> {
212        match &self.server_hello {
213            Some(server_hello) => server_hello
214                .extension_list
215                .iter()
216                .map(|e| e.to_string())
217                .collect(),
218            None => vec![],
219        }
220    }
221
222    /// Returns the negotiated TLS handshake version identifier, or `0` if none was identified.
223    ///
224    /// ## Remarks
225    /// Iris supports parsing SSL 3.0 up to TLS 1.3. This method returns the negotiated handshake
226    /// version identifier, even if it does not correspond to a major TLS version (e.g., a draft or
227    /// bespoke version number).
228    pub fn version(&self) -> u16 {
229        match (&self.client_hello, &self.server_hello) {
230            (_ch, Some(sh)) => match sh.selected_version {
231                Some(version) => version.0,
232                None => sh.version.0,
233            },
234            (Some(ch), None) => ch.version.0,
235            (None, None) => 0,
236        }
237    }
238
239    /// Returns the client JA3 string, or `""` if no ClientHello was observed.
240    ///
241    /// ## Remarks
242    /// The JA3 string is defined as the concatenation of:
243    /// `TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats`. See
244    /// [salesforce/ja3](https://github.com/salesforce/ja3) for more details.
245    pub fn ja3_str(&self) -> String {
246        match &self.client_hello {
247            Some(ch) => {
248                format!(
249                    "{},{},{},{},{}",
250                    ch.version.0,
251                    ch.cipher_suites
252                        .iter()
253                        .map(|x| x.0)
254                        .filter(|x| !GREASE_TABLE.contains(x))
255                        .join("-"),
256                    ch.extension_list
257                        .iter()
258                        .map(|x| x.0)
259                        .filter(|x| !GREASE_TABLE.contains(x))
260                        .join("-"),
261                    ch.supported_groups
262                        .iter()
263                        .map(|x| x.0)
264                        .filter(|x| !GREASE_TABLE.contains(x))
265                        .join("-"),
266                    ch.ec_point_formats.iter().join("-"),
267                )
268            }
269            None => "".to_string(),
270        }
271    }
272
273    /// Returns the server JA3S string, or `""` if no ServerHello was observed.
274    ///
275    /// ## Remarks
276    /// The JA3S string is defined as the concatenation of: `TLSVersion,Cipher,Extensions`. See
277    /// [salesforce/ja3](https://github.com/salesforce/ja3) for more details.
278    pub fn ja3s_str(&self) -> String {
279        match &self.server_hello {
280            Some(sh) => {
281                format!(
282                    "{},{},{}",
283                    sh.version.0,
284                    sh.cipher_suite.0,
285                    sh.extension_list
286                        .iter()
287                        .map(|x| x.0)
288                        .filter(|x| !GREASE_TABLE.contains(x))
289                        .join("-")
290                )
291            }
292            None => "".to_string(),
293        }
294    }
295
296    /// Returns the JA3 fingerprint.
297    pub fn ja3_hash(&self) -> String {
298        format!("{:x}", md5::compute(self.ja3_str()))
299    }
300
301    /// Returns the JA3S fingerprint.
302    pub fn ja3s_hash(&self) -> String {
303        format!("{:x}", md5::compute(self.ja3s_str()))
304    }
305}