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}