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