use crate::iana::{cipher_name, sigalg_name};
use crate::profile::TlsProfile;
use super::parse_list;
const HASH_LEN: usize = 12;
const SERVER_NAME: u16 = 0x0000;
const ALPN: u16 = 0x0010;
fn is_hash(part: &str) -> bool {
part.len() == HASH_LEN && part.bytes().all(|b| b.is_ascii_hexdigit())
}
impl TlsProfile {
pub(super) fn apply_ja4(&mut self, raw: &str) -> Result<(), String> {
let parts: Vec<&str> = raw.trim().split('_').collect();
let [header, ciphers, extensions, sigalgs] = parts.as_slice() else {
if parts.len() == 3 && parts[1..].iter().all(|part| is_hash(part)) {
return Err(
"this is the hashed JA4 form; its SHA-256 sections cannot be inverted to \
cipher and extension lists. Pass the raw JA4_r (or JA4_ro) string"
.to_owned(),
);
}
return Err(format!(
"expected 4 underscore-separated sections (header_ciphers_extensions_sigalgs), got {}",
parts.len()
));
};
let mut flags = header.chars();
if flags.next() != Some('t') {
return Err(format!(
"header {header:?} is not a TCP TLS fingerprint (it must start with 't')"
));
}
if flags.nth(2) != Some('d') {
return Err(format!(
"header {header:?} has no SNI flag 'd'; leyline always sends server_name"
));
}
self.ciphers =
self.iana_names(&parse_list(ciphers, 16, "cipher")?, cipher_name, "cipher")?;
self.sigalgs = self.iana_names(
&parse_list(sigalgs, 16, "signature algorithm")?,
sigalg_name,
"signature algorithm",
)?;
let mut ids = vec![SERVER_NAME, ALPN];
ids.extend(parse_list(extensions, 16, "extension")?);
self.apply_extensions(&ids)?;
self.extension_permutation = None;
Ok(())
}
}