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 = compute_ja3_raw(input);
13 use md5::Digest as _;
14 format!("{:x}", md5::Md5::digest(raw.as_bytes()))
15}
16
17pub(crate) fn compute_ja3_raw(input: &Ja3Input<'_>) -> String {
18 let version = input.tls_record_version;
19
20 let ciphers: String = non_grease_cipher_ids(input.ciphers)
21 .iter()
22 .map(|id| id.to_string())
23 .collect::<Vec<_>>()
24 .join("-");
25
26 let extensions: String = non_grease_ext_ids(input.extension_ids)
27 .iter()
28 .map(|id| id.to_string())
29 .collect::<Vec<_>>()
30 .join("-");
31
32 let curves: String = non_grease_curve_ids(input.curves)
33 .iter()
34 .map(|id| id.to_string())
35 .collect::<Vec<_>>()
36 .join("-");
37
38 let point_formats = "0";
39
40 format!("{version},{ciphers},{extensions},{curves},{point_formats}")
41}
42
43#[cfg(test)]
44mod tests;