Skip to main content

leyline/audit/
ja3.rs

1use crate::audit::{non_grease_cipher_ids, non_grease_curve_ids, non_grease_ext_ids};
2
3#[derive(Debug)]
4pub struct Ja3Input<'a> {
5    pub ciphers: &'a [String],
6    pub curves: &'a [String],
7    pub extension_ids: &'a [u16],
8    pub tls_record_version: u16,
9}
10
11pub fn compute_ja3(input: &Ja3Input<'_>) -> String {
12    let raw = input.compute_ja3_raw();
13    use md5::Digest as _;
14    format!("{:x}", md5::Md5::digest(raw.as_bytes()))
15}
16
17impl Ja3Input<'_> {
18    pub(crate) fn compute_ja3_raw(&self) -> String {
19        let version = self.tls_record_version;
20
21        let ciphers: String = non_grease_cipher_ids(self.ciphers)
22            .iter()
23            .map(|id| id.to_string())
24            .collect::<Vec<_>>()
25            .join("-");
26
27        let extensions: String = non_grease_ext_ids(self.extension_ids)
28            .iter()
29            .map(|id| id.to_string())
30            .collect::<Vec<_>>()
31            .join("-");
32
33        let curves: String = non_grease_curve_ids(self.curves)
34            .iter()
35            .map(|id| id.to_string())
36            .collect::<Vec<_>>()
37            .join("-");
38
39        let point_formats = "0";
40
41        format!("{version},{ciphers},{extensions},{curves},{point_formats}")
42    }
43}
44
45#[cfg(test)]
46mod tests;