From 1e152d1f1060a5afd39496d4f2556e7159cd22cc Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Mon, 23 Sep 2024 11:30:19 +0200 Subject: [PATCH] ja4: handles non alphanumeric alpn Ticket: 7267 Follows more closely the specification : https://github.com/FoxIO-LLC/ja4/blob/main/technical_details/JA4.md#alpn-extension-value Also fixes the case with a single-char alpn. --- rust/src/ja4.rs | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/rust/src/ja4.rs b/rust/src/ja4.rs index 883c00e8c3..4660f23302 100644 --- a/rust/src/ja4.rs +++ b/rust/src/ja4.rs @@ -27,6 +27,8 @@ use sha2::Sha256; use std::cmp::min; use std::os::raw::c_char; use tls_parser::{TlsCipherSuiteID, TlsExtensionType, TlsVersion}; +#[cfg(feature = "ja4")] +use crate::jsonbuilder::HEX; #[derive(Debug, PartialEq)] pub struct JA4 { @@ -137,7 +139,8 @@ impl JA4 { } pub fn set_alpn(&mut self, alpn: &[u8]) { - if alpn.len() > 1 { + if !alpn.is_empty() { + // If the first ALPN value is only a single character, then that character is treated as both the first and last character. if alpn.len() == 2 { // GREASE values are 2 bytes, so this could be one -- check let v: u16 = (alpn[0] as u16) << 8 | alpn[alpn.len() - 1] as u16; @@ -145,6 +148,12 @@ impl JA4 { return; } } + if !alpn[0].is_ascii_alphanumeric() || !alpn[alpn.len() - 1].is_ascii_alphanumeric() { + // If the first or last byte of the first ALPN is non-alphanumeric (meaning not 0x30-0x39, 0x41-0x5A, or 0x61-0x7A), then we print the first and last characters of the hex representation of the first ALPN instead. + self.alpn[0] = char::from(HEX[(alpn[0] >> 4) as usize]); + self.alpn[1] = char::from(HEX[(alpn[alpn.len() - 1] & 0xF) as usize]); + return + } self.alpn[0] = char::from(alpn[0]); self.alpn[1] = char::from(alpn[alpn.len() - 1]); } @@ -322,15 +331,41 @@ mod tests { fn test_short_alpn() { let mut j = JA4::new(); - j.set_alpn("a".as_bytes()); + j.set_alpn("b".as_bytes()); + let mut s = j.get_hash(); + s.truncate(10); + assert_eq!(s, "t00i0000bb"); + + j.set_alpn("h2".as_bytes()); + let mut s = j.get_hash(); + s.truncate(10); + assert_eq!(s, "t00i0000h2"); + + // from https://github.com/FoxIO-LLC/ja4/blob/main/technical_details/JA4.md#alpn-extension-value + j.set_alpn(&[0xab]); + let mut s = j.get_hash(); + s.truncate(10); + assert_eq!(s, "t00i0000ab"); + + j.set_alpn(&[0xab, 0xcd]); + let mut s = j.get_hash(); + s.truncate(10); + assert_eq!(s, "t00i0000ad"); + + j.set_alpn(&[0x30, 0xab]); + let mut s = j.get_hash(); + s.truncate(10); + assert_eq!(s, "t00i00003b"); + + j.set_alpn(&[0x30, 0x31, 0xab, 0xcd]); let mut s = j.get_hash(); s.truncate(10); - assert_eq!(s, "t00i000000"); + assert_eq!(s, "t00i00003d"); - j.set_alpn("aa".as_bytes()); + j.set_alpn(&[0x30, 0xab, 0xcd, 0x31]); let mut s = j.get_hash(); s.truncate(10); - assert_eq!(s, "t00i0000aa"); + assert_eq!(s, "t00i000001"); } #[test] -- 2.47.2