From 468a037daae704edcd0a931aa369d6fcf162ffab Mon Sep 17 00:00:00 2001 From: Richard McConnell Date: Wed, 23 Apr 2025 15:44:09 +0100 Subject: [PATCH] tls: Introduce HandshakeParams object for tracking Ticket: 6695 This introduction splits the use of the handshake parameters into their own object, HandshakeParams, which is populated by the TLS decoder. The JA4 object is now very simple. It's a simple String object (the JA4 Hash) which is generated during new(). This introduction is part of a larger idea, which is to enable outputting these raw parameters without JA3/JA4. These handshake parameters are the components used to generate the JA4 hash, thus it makes sense for it to be a user of HandshakeParams. --- rust/src/handshake.rs | 279 +++++++++++++++++++++++++++ rust/src/ja4.rs | 406 ++++++++++++++-------------------------- rust/src/lib.rs | 1 + rust/src/quic/detect.rs | 4 +- rust/src/quic/frames.rs | 35 ++-- rust/src/quic/logger.rs | 2 +- rust/src/quic/quic.rs | 18 +- src/Makefile.am | 1 - src/app-layer-ssl.c | 111 +++++------ src/app-layer-ssl.h | 2 +- src/detect-ja4-hash.c | 6 +- src/output-json-tls.c | 9 +- src/util-ja4.h | 29 --- 13 files changed, 500 insertions(+), 403 deletions(-) create mode 100644 rust/src/handshake.rs delete mode 100644 src/util-ja4.h diff --git a/rust/src/handshake.rs b/rust/src/handshake.rs new file mode 100644 index 0000000000..d8687a0569 --- /dev/null +++ b/rust/src/handshake.rs @@ -0,0 +1,279 @@ +/* Copyright (C) 2025 Open Information Security Foundation +* +* You can copy, redistribute or modify this Program under the terms of +* the GNU General Public License version 2 as published by the Free +* Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* version 2 along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +* 02110-1301, USA. + +// Author: Sascha Steinbiss + +*/ + +use crate::jsonbuilder::HEX; +use libc::c_uchar; +use std::os::raw::c_char; +use tls_parser::{TlsCipherSuiteID, TlsExtensionType, TlsVersion}; + +#[derive(Debug, PartialEq)] +pub struct HandshakeParams { + pub(crate) tls_version: Option, + pub(crate) ciphersuites: Vec, + pub(crate) extensions: Vec, + pub(crate) signature_algorithms: Vec, + pub(crate) domain: bool, + pub(crate) alpn: [char; 2], + pub(crate) quic: bool, +} + +impl Default for HandshakeParams { + fn default() -> Self { + Self::new() + } +} + +impl HandshakeParams { + #[inline] + fn is_grease(val: u16) -> bool { + match val { + 0x0a0a | 0x1a1a | 0x2a2a | 0x3a3a | 0x4a4a | 0x5a5a | 0x6a6a | 0x7a7a | 0x8a8a + | 0x9a9a | 0xaaaa | 0xbaba | 0xcaca | 0xdada | 0xeaea | 0xfafa => true, + _ => false, + } + } + + fn new() -> Self { + Self { + tls_version: None, + ciphersuites: Vec::with_capacity(20), + extensions: Vec::with_capacity(20), + signature_algorithms: Vec::with_capacity(20), + domain: false, + alpn: ['0', '0'], + quic: false, + } + } + + pub(crate) fn set_tls_version(&mut self, version: TlsVersion) { + if Self::is_grease(u16::from(version)) { + return; + } + // Track maximum of seen TLS versions + match self.tls_version { + None => { + self.tls_version = Some(version); + } + Some(cur_version) => { + if u16::from(version) > u16::from(cur_version) { + self.tls_version = Some(version); + } + } + } + } + + pub(crate) fn set_alpn(&mut self, alpn: &[u8]) { + 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; + if Self::is_grease(v) { + 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]); + } + } + + pub(crate) fn add_cipher_suite(&mut self, cipher: TlsCipherSuiteID) { + if Self::is_grease(u16::from(cipher)) { + return; + } + self.ciphersuites.push(cipher); + } + + pub(crate) fn add_extension(&mut self, ext: TlsExtensionType) { + if Self::is_grease(u16::from(ext)) { + return; + } + if ext == TlsExtensionType::ServerName { + self.domain = true; + } + self.extensions.push(ext); + } + + pub(crate) fn add_signature_algorithm(&mut self, sigalgo: u16) { + if Self::is_grease(sigalgo) { + return; + } + self.signature_algorithms.push(sigalgo); + } +} + +#[no_mangle] +pub extern "C" fn SCTLSHandshakeNew() -> *mut HandshakeParams { + let hs = Box::new(HandshakeParams::new()); + Box::into_raw(hs) +} + +#[no_mangle] +pub unsafe extern "C" fn SCTLSHandshakeSetTLSVersion(hs: &mut HandshakeParams, version: u16) { + hs.set_tls_version(TlsVersion(version)); +} + +#[no_mangle] +pub unsafe extern "C" fn SCTLSHandshakeAddCipher(hs: &mut HandshakeParams, cipher: u16) { + hs.add_cipher_suite(TlsCipherSuiteID(cipher)); +} + +#[no_mangle] +pub unsafe extern "C" fn SCTLSHandshakeAddExtension(hs: &mut HandshakeParams, ext: u16) { + hs.add_extension(TlsExtensionType(ext)); +} + +#[no_mangle] +pub unsafe extern "C" fn SCTLSHandshakeAddSigAlgo(hs: &mut HandshakeParams, sigalgo: u16) { + hs.add_signature_algorithm(sigalgo); +} + +#[no_mangle] +pub unsafe extern "C" fn SCTLSHandshakeSetALPN( + hs: &mut HandshakeParams, proto: *const c_char, len: u16, +) { + let b: &[u8] = std::slice::from_raw_parts(proto as *const c_uchar, len as usize); + hs.set_alpn(b); +} + +#[no_mangle] +pub unsafe extern "C" fn SCTLSHandshakeFree(hs: &mut HandshakeParams) { + let hs: Box = Box::from_raw(hs); + std::mem::drop(hs); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_grease() { + let mut alpn = "foobar".as_bytes(); + let mut len = alpn.len(); + let v: u16 = ((alpn[0] as u16) << 8) | alpn[len - 1] as u16; + assert!(!HandshakeParams::is_grease(v)); + + alpn = &[0x0a, 0x0a]; + len = alpn.len(); + let v: u16 = ((alpn[0] as u16) << 8) | alpn[len - 1] as u16; + assert!(HandshakeParams::is_grease(v)); + } + + #[test] + fn test_tlsversion_max() { + let mut hs = HandshakeParams::new(); + assert_eq!(hs.tls_version, None); + hs.set_tls_version(TlsVersion::Ssl30); + assert_eq!(hs.tls_version, Some(TlsVersion::Ssl30)); + hs.set_tls_version(TlsVersion::Tls12); + assert_eq!(hs.tls_version, Some(TlsVersion::Tls12)); + hs.set_tls_version(TlsVersion::Tls10); + assert_eq!(hs.tls_version, Some(TlsVersion::Tls12)); + } + + #[test] + fn test_add_cipher_suite_filters_grease() { + let mut hs = HandshakeParams::new(); + hs.add_cipher_suite(TlsCipherSuiteID(0x1a1a)); // GREASE + assert_eq!(hs.ciphersuites.len(), 0); + } + + #[test] + fn test_add_cipher_suite_accepts_normal() { + let mut hs = HandshakeParams::new(); + hs.add_cipher_suite(TlsCipherSuiteID(0x1301)); + assert_eq!(hs.ciphersuites, &[TlsCipherSuiteID(0x1301)]); + } + + #[test] + fn test_add_cipher_suite_len_tracking() { + let mut hs = HandshakeParams::new(); + hs.add_cipher_suite(TlsCipherSuiteID(0x1301)); + hs.add_cipher_suite(TlsCipherSuiteID(0x1302)); + hs.add_cipher_suite(TlsCipherSuiteID(0x1a1a)); // GREASE + assert_eq!(hs.ciphersuites.len(), 2); + } + + #[test] + fn test_add_extension_sets_domain_for_server_name() { + let mut hs = HandshakeParams::new(); + hs.add_extension(TlsExtensionType::ServerName); + assert!(hs.domain); + } + + #[test] + fn test_add_extension_filters_grease() { + let mut hs = HandshakeParams::new(); + hs.add_extension(TlsExtensionType(0xaaaa)); // GREASE + assert_eq!(hs.extensions.len(), 0); + } + + #[test] + fn test_add_extension_len_tracking() { + let mut hs = HandshakeParams::new(); + hs.add_extension(TlsExtensionType::ClientCertificate); + hs.add_extension(TlsExtensionType::ServerName); + hs.add_extension(TlsExtensionType(0xaaaa)); // GREASE + assert_eq!(hs.extensions.len(), 2); + } + + #[test] + fn test_add_signature_algorithm_filters_grease() { + let mut hs = HandshakeParams::new(); + hs.add_signature_algorithm(0xbaba); // GREASE + assert_eq!(hs.signature_algorithms.len(), 0); + } + + #[test] + fn test_add_signature_algorithm_len_tracking() { + let mut hs = HandshakeParams::new(); + hs.add_signature_algorithm(0x1234); + hs.add_signature_algorithm(0x1235); + hs.add_signature_algorithm(0xbaba); // GREASE + assert_eq!(hs.signature_algorithms.len(), 2); + } + + #[test] + fn test_set_alpn_ascii() { + let mut hs = HandshakeParams::new(); + hs.set_alpn(b"http/1.1"); + assert_eq!(hs.alpn, ['h', '1']); + } + + #[test] + fn test_set_alpn_non_ascii_first_or_last() { + let mut hs = HandshakeParams::new(); + hs.set_alpn(&[0x01, b'T', b'E', 0x7f]); // non-alphanumeric start and end + assert_eq!(hs.alpn, [HEX[0x0], HEX[0xF]].map(|b| b as char)); // 0x01 -> 0, 0x7f -> f + } + + #[test] + fn test_set_alpn_grease_pair_filtered() { + let mut hs = HandshakeParams::new(); + hs.set_alpn(&[0x2a, 0x2a]); // 0x2a2a GREASE + assert_eq!(hs.alpn, ['0', '0']); + } +} diff --git a/rust/src/ja4.rs b/rust/src/ja4.rs index ae76186c90..50e3d5535c 100644 --- a/rust/src/ja4.rs +++ b/rust/src/ja4.rs @@ -17,79 +17,36 @@ // Author: Sascha Steinbiss */ - #[cfg(feature = "ja4")] use digest::Digest; -use libc::c_uchar; #[cfg(feature = "ja4")] use sha2::Sha256; #[cfg(feature = "ja4")] use std::cmp::min; -use std::os::raw::c_char; -use tls_parser::{TlsCipherSuiteID, TlsExtensionType, TlsVersion}; #[cfg(feature = "ja4")] -use crate::jsonbuilder::HEX; +use tls_parser::{TlsExtensionType, TlsVersion}; + +use crate::handshake::HandshakeParams; + +pub const JA4_HEX_LEN: usize = 36; + +pub(crate) trait JA4Impl { + fn try_new(hs: &HandshakeParams) -> Option; +} #[derive(Debug, PartialEq)] pub struct JA4 { - tls_version: Option, - ciphersuites: Vec, - extensions: Vec, - signature_algorithms: Vec, - domain: bool, - alpn: [char; 2], - quic: bool, - // Some extensions contribute to the total count component of the - // fingerprint, yet are not to be included in the SHA256 hash component. - // Let's track the count separately. - nof_exts: u16, + hash: String, } -impl Default for JA4 { - fn default() -> Self { - Self::new() - } -} - -// Stubs for when JA4 is disabled -#[cfg(not(feature = "ja4"))] -impl JA4 { - pub fn new() -> Self { - Self { - tls_version: None, - // Vec::new() does not allocate memory until filled, which we - // will not do here. - ciphersuites: Vec::new(), - extensions: Vec::new(), - signature_algorithms: Vec::new(), - domain: false, - alpn: ['0', '0'], - quic: false, - nof_exts: 0, - } - } - pub fn set_quic(&mut self) {} - pub fn set_tls_version(&mut self, _version: TlsVersion) {} - pub fn set_alpn(&mut self, _alpn: &[u8]) {} - pub fn add_cipher_suite(&mut self, _cipher: TlsCipherSuiteID) {} - pub fn add_extension(&mut self, _ext: TlsExtensionType) {} - pub fn add_signature_algorithm(&mut self, _sigalgo: u16) {} - pub fn get_hash(&self) -> String { - String::new() +impl AsRef for JA4 { + fn as_ref(&self) -> &str { + &self.hash } } #[cfg(feature = "ja4")] impl JA4 { - #[inline] - fn is_grease(val: u16) -> bool { - match val { - 0x0a0a | 0x1a1a | 0x2a2a | 0x3a3a | 0x4a4a | 0x5a5a | 0x6a6a | 0x7a7a | 0x8a8a - | 0x9a9a | 0xaaaa | 0xbaba | 0xcaca | 0xdada | 0xeaea | 0xfafa => true, - _ => false, - } - } - #[inline] fn version_to_ja4code(val: Option) -> &'static str { match val { @@ -103,105 +60,38 @@ impl JA4 { _ => "00", } } +} - pub fn new() -> Self { - Self { - tls_version: None, - ciphersuites: Vec::with_capacity(20), - extensions: Vec::with_capacity(20), - signature_algorithms: Vec::with_capacity(20), - domain: false, - alpn: ['0', '0'], - quic: false, - nof_exts: 0, - } - } - - pub fn set_quic(&mut self) { - self.quic = true; - } - - pub fn set_tls_version(&mut self, version: TlsVersion) { - if JA4::is_grease(u16::from(version)) { - return; - } - // Track maximum of seen TLS versions - match self.tls_version { - None => { - self.tls_version = Some(version); - } - Some(cur_version) => { - if u16::from(version) > u16::from(cur_version) { - self.tls_version = Some(version); - } - } - } - } - - pub fn set_alpn(&mut self, alpn: &[u8]) { - 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; - if JA4::is_grease(v) { - 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]); - } - } - - pub fn add_cipher_suite(&mut self, cipher: TlsCipherSuiteID) { - if JA4::is_grease(u16::from(cipher)) { - return; - } - self.ciphersuites.push(cipher); - } +#[cfg(feature = "ja4")] +impl JA4Impl for JA4 { + fn try_new(hs: &HandshakeParams) -> Option { + // All non-GREASE extensions are stored to produce a more verbose, complete output + // of extensions but we need to omit ALPN & SNI extensions from the JA4_a hash. + let mut exts = hs + .extensions + .iter() + .filter(|&ext| { + *ext != TlsExtensionType::ApplicationLayerProtocolNegotiation + && *ext != TlsExtensionType::ServerName + }) + .collect::>(); - pub fn add_extension(&mut self, ext: TlsExtensionType) { - if JA4::is_grease(u16::from(ext)) { - return; - } - if ext != TlsExtensionType::ApplicationLayerProtocolNegotiation - && ext != TlsExtensionType::ServerName - { - self.extensions.push(ext); - } else if ext == TlsExtensionType::ServerName { - self.domain = true; - } - self.nof_exts += 1; - } + let alpn = hs.alpn; - pub fn add_signature_algorithm(&mut self, sigalgo: u16) { - if JA4::is_grease(sigalgo) { - return; - } - self.signature_algorithms.push(sigalgo); - } - - pub fn get_hash(&self) -> String { // Calculate JA4_a let ja4_a = format!( "{proto}{version}{sni}{nof_c:02}{nof_e:02}{al1}{al2}", - proto = if self.quic { "q" } else { "t" }, - version = JA4::version_to_ja4code(self.tls_version), - sni = if self.domain { "d" } else { "i" }, - nof_c = min(99, self.ciphersuites.len()), - nof_e = min(99, self.nof_exts), - al1 = self.alpn[0], - al2 = self.alpn[1] + proto = if hs.quic { "q" } else { "t" }, + version = Self::version_to_ja4code(hs.tls_version), + sni = if hs.domain { "d" } else { "i" }, + nof_c = min(99, hs.ciphersuites.len()), + nof_e = min(99, hs.extensions.len()), + al1 = alpn[0], + al2 = alpn[1] ); // Calculate JA4_b - let mut sorted_ciphers = self.ciphersuites.to_vec(); + let mut sorted_ciphers = hs.ciphersuites.to_vec(); sorted_ciphers.sort_by(|a, b| u16::from(*a).cmp(&u16::from(*b))); let sorted_cipherstrings: Vec = sorted_ciphers .iter() @@ -214,14 +104,13 @@ impl JA4 { ja4_b.truncate(12); // Calculate JA4_c - let mut sorted_exts = self.extensions.to_vec(); - sorted_exts.sort_by(|a, b| u16::from(*a).cmp(&u16::from(*b))); - let sorted_extstrings: Vec = sorted_exts - .iter() - .map(|v| format!("{:04x}", u16::from(*v))) + exts.sort_by(|&a, &b| u16::from(*a).cmp(&u16::from(*b))); + let sorted_extstrings: Vec = exts + .into_iter() + .map(|&v| format!("{:04x}", u16::from(v))) .collect(); let ja4_c1_raw = sorted_extstrings.join(","); - let unsorted_sigalgostrings: Vec = self + let unsorted_sigalgostrings: Vec = hs .signature_algorithms .iter() .map(|v| format!("{:04x}", (*v))) @@ -232,189 +121,166 @@ impl JA4 { let mut ja4_c = format!("{:x}", sha.finalize()); ja4_c.truncate(12); - return format!("{}_{}_{}", ja4_a, ja4_b, ja4_c); + Some(Self { + hash: format!("{}_{}_{}", ja4_a, ja4_b, ja4_c), + }) } } -#[no_mangle] -pub extern "C" fn SCJA4New() -> *mut JA4 { - let j = Box::new(JA4::new()); - Box::into_raw(j) -} - -#[no_mangle] -pub unsafe extern "C" fn SCJA4SetTLSVersion(j: &mut JA4, version: u16) { - j.set_tls_version(TlsVersion(version)); -} - -#[no_mangle] -pub unsafe extern "C" fn SCJA4AddCipher(j: &mut JA4, cipher: u16) { - j.add_cipher_suite(TlsCipherSuiteID(cipher)); -} - -#[no_mangle] -pub unsafe extern "C" fn SCJA4AddExtension(j: &mut JA4, ext: u16) { - j.add_extension(TlsExtensionType(ext)); -} - -#[no_mangle] -pub unsafe extern "C" fn SCJA4AddSigAlgo(j: &mut JA4, sigalgo: u16) { - j.add_signature_algorithm(sigalgo); -} - -#[no_mangle] -pub unsafe extern "C" fn SCJA4SetALPN(j: &mut JA4, proto: *const c_char, len: u16) { - let b: &[u8] = std::slice::from_raw_parts(proto as *const c_uchar, len as usize); - j.set_alpn(b); -} - -#[no_mangle] -pub unsafe extern "C" fn SCJA4GetHash(j: &mut JA4, out: &mut [u8; 36]) { - let hash = j.get_hash(); - out[0..36].copy_from_slice(hash.as_bytes()); +#[cfg(not(feature = "ja4"))] +impl JA4Impl for JA4 { + fn try_new(_hs: &HandshakeParams) -> Option { + None + } } +// C ABI +#[cfg(feature = "ja4")] #[no_mangle] -pub unsafe extern "C" fn SCJA4Free(j: &mut JA4) { - let ja4: Box = Box::from_raw(j); - std::mem::drop(ja4); +pub unsafe extern "C" fn SCJA4GetHash(hs: &HandshakeParams, out: &mut [u8; JA4_HEX_LEN]) { + if let Some(ja4) = JA4::try_new(hs) { + out[0..JA4_HEX_LEN].copy_from_slice(ja4.as_ref().as_bytes()); + } } -#[cfg(all(test, feature = "ja4"))] +#[cfg(test)] +#[cfg(feature = "ja4")] mod tests { use super::*; + use tls_parser::TlsCipherSuiteID; #[test] - fn test_is_grease() { - let mut alpn = "foobar".as_bytes(); - let mut len = alpn.len(); - let v: u16 = ((alpn[0] as u16) << 8) | alpn[len - 1] as u16; - assert!(!JA4::is_grease(v)); - - alpn = &[0x0a, 0x0a]; - len = alpn.len(); - let v: u16 = ((alpn[0] as u16) << 8) | alpn[len - 1] as u16; - assert!(JA4::is_grease(v)); - } - - #[test] - fn test_tlsversion_max() { - let mut j = JA4::new(); - assert_eq!(j.tls_version, None); - j.set_tls_version(TlsVersion::Ssl30); - assert_eq!(j.tls_version, Some(TlsVersion::Ssl30)); - j.set_tls_version(TlsVersion::Tls12); - assert_eq!(j.tls_version, Some(TlsVersion::Tls12)); - j.set_tls_version(TlsVersion::Tls10); - assert_eq!(j.tls_version, Some(TlsVersion::Tls12)); - } - - #[test] - fn test_get_hash_limit_numbers() { + fn test_hash_limit_numbers() { // Test whether the limitation of the extension and ciphersuite // count to 99 is reflected correctly. - let mut j = JA4::new(); + let mut hs = HandshakeParams::default(); for i in 1..200 { - j.add_cipher_suite(TlsCipherSuiteID(i)); + hs.add_cipher_suite(TlsCipherSuiteID(i)); } for i in 1..200 { - j.add_extension(TlsExtensionType(i)); + hs.add_extension(TlsExtensionType(i)); } - let mut s = j.get_hash(); - s.truncate(10); - assert_eq!(s, "t00i999900"); + let ja4 = JA4::try_new(&hs).expect("JA4 create failure"); + + // Only testing the ja4_a portion of the hash, we we truncate to + // ensure we're only testing this + let mut ja4_hash = ja4.as_ref().to_string(); + ja4_hash.truncate(10); + + assert_eq!(ja4_hash, "t00i999900"); } #[test] fn test_short_alpn() { - let mut j = JA4::new(); + let mut hs = HandshakeParams::default(); - j.set_alpn("b".as_bytes()); - let mut s = j.get_hash(); + hs.set_alpn("b".as_bytes()); + let mut s = JA4::try_new(&hs) + .expect("JA4 create failure") + .as_ref() + .to_string(); s.truncate(10); assert_eq!(s, "t00i0000bb"); - j.set_alpn("h2".as_bytes()); - let mut s = j.get_hash(); + hs.set_alpn("h2".as_bytes()); + let mut s = JA4::try_new(&hs) + .expect("JA4 create failure") + .as_ref() + .to_string(); 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(); + hs.set_alpn(&[0xab]); + let mut s = JA4::try_new(&hs) + .expect("JA4 create failure") + .as_ref() + .to_string(); s.truncate(10); assert_eq!(s, "t00i0000ab"); - j.set_alpn(&[0xab, 0xcd]); - let mut s = j.get_hash(); + hs.set_alpn(&[0xab, 0xcd]); + let mut s = JA4::try_new(&hs) + .expect("JA4 create failure") + .as_ref() + .to_string(); s.truncate(10); assert_eq!(s, "t00i0000ad"); - j.set_alpn(&[0x30, 0xab]); - let mut s = j.get_hash(); + hs.set_alpn(&[0x30, 0xab]); + let mut s = JA4::try_new(&hs) + .expect("JA4 create failure") + .as_ref() + .to_string(); s.truncate(10); assert_eq!(s, "t00i00003b"); - j.set_alpn(&[0x30, 0x31, 0xab, 0xcd]); - let mut s = j.get_hash(); + hs.set_alpn(&[0x30, 0x31, 0xab, 0xcd]); + let mut s = JA4::try_new(&hs) + .expect("JA4 create failure") + .as_ref() + .to_string(); s.truncate(10); assert_eq!(s, "t00i00003d"); - j.set_alpn(&[0x30, 0xab, 0xcd, 0x31]); - let mut s = j.get_hash(); + hs.set_alpn(&[0x30, 0xab, 0xcd, 0x31]); + let mut s = JA4::try_new(&hs) + .expect("JA4 create failure") + .as_ref() + .to_string(); s.truncate(10); assert_eq!(s, "t00i000001"); } #[test] fn test_get_hash() { - let mut j = JA4::new(); + let mut hs = HandshakeParams::default(); // the empty JA4 hash - let s = j.get_hash(); - assert_eq!(s, "t00i000000_e3b0c44298fc_d2e2adf7177b"); + let s = JA4::try_new(&hs).expect("JA4 create failure"); + assert_eq!(s.as_ref(), "t00i000000_e3b0c44298fc_d2e2adf7177b"); // set TLS version - j.set_tls_version(TlsVersion::Tls12); - let s = j.get_hash(); - assert_eq!(s, "t12i000000_e3b0c44298fc_d2e2adf7177b"); + hs.set_tls_version(TlsVersion::Tls12); + let s = JA4::try_new(&hs).expect("JA4 create failure"); + assert_eq!(s.as_ref(), "t12i000000_e3b0c44298fc_d2e2adf7177b"); // set QUIC - j.set_quic(); - let s = j.get_hash(); - assert_eq!(s, "q12i000000_e3b0c44298fc_d2e2adf7177b"); + hs.quic = true; + let s = JA4::try_new(&hs).expect("JA4 create failure"); + assert_eq!(s.as_ref(), "q12i000000_e3b0c44298fc_d2e2adf7177b"); // set GREASE extension, should be ignored - j.add_extension(TlsExtensionType(0x0a0a)); - let s = j.get_hash(); - assert_eq!(s, "q12i000000_e3b0c44298fc_d2e2adf7177b"); + hs.add_extension(TlsExtensionType(0x0a0a)); + let s = JA4::try_new(&hs).expect("JA4 create failure"); + assert_eq!(s.as_ref(), "q12i000000_e3b0c44298fc_d2e2adf7177b"); // set SNI extension, should only increase count and change i->d - j.add_extension(TlsExtensionType(0x0000)); - let s = j.get_hash(); - assert_eq!(s, "q12d000100_e3b0c44298fc_d2e2adf7177b"); + hs.add_extension(TlsExtensionType(0x0000)); + let s = JA4::try_new(&hs).expect("JA4 create failure"); + assert_eq!(s.as_ref(), "q12d000100_e3b0c44298fc_d2e2adf7177b"); // set ALPN extension, should only increase count and set end of JA4_a - j.set_alpn(b"h3-16"); - j.add_extension(TlsExtensionType::ApplicationLayerProtocolNegotiation); - let s = j.get_hash(); - assert_eq!(s, "q12d0002h6_e3b0c44298fc_d2e2adf7177b"); + hs.set_alpn(b"h3-16"); + hs.add_extension(TlsExtensionType::ApplicationLayerProtocolNegotiation); + let s = JA4::try_new(&hs).expect("JA4 create failure"); + assert_eq!(s.as_ref(), "q12d0002h6_e3b0c44298fc_d2e2adf7177b"); // set some ciphers - j.add_cipher_suite(TlsCipherSuiteID(0x1111)); - j.add_cipher_suite(TlsCipherSuiteID(0x0a20)); - j.add_cipher_suite(TlsCipherSuiteID(0xbada)); - let s = j.get_hash(); - assert_eq!(s, "q12d0302h6_f500716053f9_d2e2adf7177b"); + hs.add_cipher_suite(TlsCipherSuiteID(0x1111)); + hs.add_cipher_suite(TlsCipherSuiteID(0x0a20)); + hs.add_cipher_suite(TlsCipherSuiteID(0xbada)); + let s = JA4::try_new(&hs).expect("JA4 create failure"); + assert_eq!(s.as_ref(), "q12d0302h6_f500716053f9_d2e2adf7177b"); // set some extensions and signature algorithms - j.add_extension(TlsExtensionType(0xface)); - j.add_extension(TlsExtensionType(0x0121)); - j.add_extension(TlsExtensionType(0x1234)); - j.add_signature_algorithm(0x6666); - let s = j.get_hash(); - assert_eq!(s, "q12d0305h6_f500716053f9_2debc8880bae"); + hs.add_extension(TlsExtensionType(0xface)); + hs.add_extension(TlsExtensionType(0x0121)); + hs.add_extension(TlsExtensionType(0x1234)); + hs.add_signature_algorithm(0x6666); + let s = JA4::try_new(&hs).expect("JA4 create failure"); + assert_eq!(s.as_ref(), "q12d0305h6_f500716053f9_2debc8880bae"); } } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 4c393383a6..6ed5a2cb67 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -95,6 +95,7 @@ pub mod detect; pub mod utils; pub mod ja4; +pub mod handshake; pub mod lua; diff --git a/rust/src/quic/detect.rs b/rust/src/quic/detect.rs index 11902b71c9..4d23603e95 100644 --- a/rust/src/quic/detect.rs +++ b/rust/src/quic/detect.rs @@ -75,8 +75,8 @@ pub unsafe extern "C" fn SCQuicTxGetJa4( tx: &QuicTransaction, buffer: *mut *const u8, buffer_len: *mut u32, ) -> u8 { if let Some(ja4) = &tx.ja4 { - *buffer = ja4.as_ptr(); - *buffer_len = ja4.len() as u32; + *buffer = ja4.as_ref().as_ptr(); + *buffer_len = ja4.as_ref().len() as u32; 1 } else { *buffer = ptr::null(); diff --git a/rust/src/quic/frames.rs b/rust/src/quic/frames.rs index a4010f4488..bafbdfd589 100644 --- a/rust/src/quic/frames.rs +++ b/rust/src/quic/frames.rs @@ -17,7 +17,7 @@ use super::error::QuicError; use super::quic::QUIC_MAX_CRYPTO_FRAG_LEN; -use crate::ja4::*; +use crate::handshake::HandshakeParams; use crate::quic::parser::quic_var_uint; use nom7::bytes::complete::take; use nom7::combinator::{all_consuming, complete}; @@ -139,7 +139,7 @@ pub(crate) struct Crypto { // the lifetime of TlsExtension due to references to the slice used for parsing pub extv: Vec, pub ja3: Option, - pub ja4: Option, + pub hs: Option, } #[derive(Debug, PartialEq)] @@ -246,7 +246,7 @@ fn quic_tls_ja3_client_extends(ja3: &mut String, exts: Vec) { // get interesting stuff out of parsed tls extensions fn quic_get_tls_extensions( - input: Option<&[u8]>, ja3: &mut String, mut ja4: Option<&mut JA4>, client: bool, + input: Option<&[u8]>, ja3: &mut String, mut hs: Option<&mut HandshakeParams>, client: bool, ) -> Vec { let mut extv = Vec::new(); if let Some(extr) = input { @@ -260,8 +260,8 @@ fn quic_get_tls_extensions( dash = true; } ja3.push_str(&u16::from(etype).to_string()); - if let Some(ref mut ja4) = ja4 { - ja4.add_extension(etype) + if let Some(ref mut hs) = hs { + hs.add_extension(etype) } let mut values = Vec::new(); match e { @@ -270,8 +270,8 @@ fn quic_get_tls_extensions( let mut value = Vec::new(); value.extend_from_slice(version.to_string().as_bytes()); values.push(value); - if let Some(ref mut ja4) = ja4 { - ja4.set_tls_version(*version); + if let Some(ref mut hs) = hs { + hs.set_tls_version(*version); } } } @@ -287,15 +287,15 @@ fn quic_get_tls_extensions( let mut value = Vec::new(); value.extend_from_slice(sigalgo.to_string().as_bytes()); values.push(value); - if let Some(ref mut ja4) = ja4 { - ja4.add_signature_algorithm(*sigalgo) + if let Some(ref mut hs) = hs { + hs.add_signature_algorithm(*sigalgo) } } } TlsExtension::ALPN(x) => { if !x.is_empty() { - if let Some(ref mut ja4) = ja4 { - ja4.set_alpn(x[0]); + if let Some(ref mut hs) = hs { + hs.set_alpn(x[0]); } } for alpn in x { @@ -323,8 +323,7 @@ fn parse_quic_handshake(msg: TlsMessage) -> Option { let mut ja3 = String::with_capacity(256); ja3.push_str(&u16::from(ch.version).to_string()); ja3.push(','); - let mut ja4 = JA4::new(); - ja4.set_quic(); + let mut hs = HandshakeParams { quic: true, ..Default::default() }; let mut dash = false; for c in &ch.ciphers { if dash { @@ -333,11 +332,11 @@ fn parse_quic_handshake(msg: TlsMessage) -> Option { dash = true; } ja3.push_str(&u16::from(*c).to_string()); - ja4.add_cipher_suite(*c); + hs.add_cipher_suite(*c); } ja3.push(','); let ciphers = ch.ciphers; - let extv = quic_get_tls_extensions(ch.ext, &mut ja3, Some(&mut ja4), true); + let extv = quic_get_tls_extensions(ch.ext, &mut ja3, Some(&mut hs), true); return Some(Frame::Crypto(Crypto { ciphers, extv, @@ -346,8 +345,8 @@ fn parse_quic_handshake(msg: TlsMessage) -> Option { } else { None }, - ja4: if cfg!(feature = "ja4") { - Some(ja4) + hs: if cfg!(feature = "ja4") { + Some(hs) } else { None }, @@ -369,7 +368,7 @@ fn parse_quic_handshake(msg: TlsMessage) -> Option { } else { None }, - ja4: None, + hs: None, })); } _ => {} diff --git a/rust/src/quic/logger.rs b/rust/src/quic/logger.rs index 1ba2afbb66..fc21b38372 100644 --- a/rust/src/quic/logger.rs +++ b/rust/src/quic/logger.rs @@ -124,7 +124,7 @@ fn log_quic(tx: &QuicTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> } if let Some(ref ja4) = &tx.ja4 { - js.set_string("ja4", ja4)?; + js.set_string("ja4", ja4.as_ref())?; } if !tx.extv.is_empty() { diff --git a/rust/src/quic/quic.rs b/rust/src/quic/quic.rs index 886c14e752..72872f806e 100644 --- a/rust/src/quic/quic.rs +++ b/rust/src/quic/quic.rs @@ -21,11 +21,15 @@ use super::{ frames::{Frame, QuicTlsExtension, StreamTag}, parser::{quic_pkt_num, QuicData, QuicHeader, QuicType}, }; -use crate::core::{ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_UDP}; use crate::{ applayer::{self, *}, direction::Direction, flow::Flow, + ja4::JA4, +}; +use crate::{ + core::{ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_UDP}, + ja4::JA4Impl, }; use std::collections::VecDeque; use std::ffi::CString; @@ -55,7 +59,7 @@ pub struct QuicTransaction { pub ua: Option>, pub extv: Vec, pub ja3: Option, - pub ja4: Option, + pub ja4: Option, pub client: bool, tx_data: AppLayerTxData, } @@ -63,7 +67,7 @@ pub struct QuicTransaction { impl QuicTransaction { fn new( header: QuicHeader, data: QuicData, sni: Option>, ua: Option>, - extv: Vec, ja3: Option, ja4: Option, client: bool, + extv: Vec, ja3: Option, ja4: Option, client: bool, ) -> Self { let direction = if client { Direction::ToServer @@ -164,7 +168,7 @@ impl QuicState { fn new_tx( &mut self, header: QuicHeader, data: QuicData, sni: Option>, ua: Option>, - extb: Vec, ja3: Option, ja4: Option, client: bool, + extb: Vec, ja3: Option, ja4: Option, client: bool, frag_long: bool, ) { let mut tx = QuicTransaction::new(header, data, sni, ua, extb, ja3, ja4, client); @@ -248,7 +252,7 @@ impl QuicState { let mut sni: Option> = None; let mut ua: Option> = None; let mut ja3: Option = None; - let mut ja4: Option = None; + let mut ja4: Option = None; let mut extv: Vec = Vec::new(); let mut frag_long = false; for frame in &data.frames { @@ -293,8 +297,8 @@ impl QuicState { if to_server { // our hash is complete, let's only use strings from // now on - if let Some(ref rja4) = c.ja4 { - ja4 = Some(rja4.get_hash()); + if let Some(ref rja4) = c.hs { + ja4 = JA4::try_new(rja4); } } for e in &c.extv { diff --git a/src/Makefile.am b/src/Makefile.am index 47c123798b..d5c302bd3c 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -525,7 +525,6 @@ noinst_HEADERS = \ util-ioctl.h \ util-ip.h \ util-ja3.h \ - util-ja4.h \ util-landlock.h \ util-log-redis.h \ util-logopenfile.h \ diff --git a/src/app-layer-ssl.c b/src/app-layer-ssl.c index ff8a11e3fa..027510a77f 100644 --- a/src/app-layer-ssl.c +++ b/src/app-layer-ssl.c @@ -753,9 +753,8 @@ static inline int TLSDecodeHSHelloVersion(SSLState *ssl_state, uint16_t version = (uint16_t)(*input << 8) | *(input + 1); ssl_state->curr_connp->version = version; - if (ssl_state->curr_connp->ja4 != NULL && - ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) { - SCJA4SetTLSVersion(ssl_state->curr_connp->ja4, version); + if (ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) { + SCTLSHandshakeSetTLSVersion(ssl_state->curr_connp->hs, version); } /* TLSv1.3 draft1 to draft21 use the version field as earlier TLS @@ -904,54 +903,46 @@ static inline int TLSDecodeHSHelloCipherSuites(SSLState *ssl_state, const bool enable_ja3 = SC_ATOMIC_GET(ssl_config.enable_ja3) && ssl_state->curr_connp->ja3_hash == NULL; - if (enable_ja3 || SC_ATOMIC_GET(ssl_config.enable_ja4)) { - JA3Buffer *ja3_cipher_suites = NULL; + JA3Buffer *ja3_cipher_suites = NULL; - if (enable_ja3) { - ja3_cipher_suites = Ja3BufferInit(); - if (ja3_cipher_suites == NULL) - return -1; - } + if (enable_ja3) { + ja3_cipher_suites = Ja3BufferInit(); + if (ja3_cipher_suites == NULL) + return -1; + } - uint16_t processed_len = 0; - /* coverity[tainted_data] */ - while (processed_len < cipher_suites_length) - { - if (!(HAS_SPACE(2))) { - if (enable_ja3) { - Ja3BufferFree(&ja3_cipher_suites); - } - goto invalid_length; + uint16_t processed_len = 0; + /* coverity[tainted_data] */ + while (processed_len < cipher_suites_length) { + if (!(HAS_SPACE(2))) { + if (enable_ja3) { + Ja3BufferFree(&ja3_cipher_suites); } + goto invalid_length; + } - uint16_t cipher_suite = (uint16_t)(*input << 8) | *(input + 1); - input += 2; + uint16_t cipher_suite = (uint16_t)(*input << 8) | *(input + 1); + input += 2; - if (TLSDecodeValueIsGREASE(cipher_suite) != 1) { - if (ssl_state->curr_connp->ja4 != NULL && - ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) { - SCJA4AddCipher(ssl_state->curr_connp->ja4, cipher_suite); - } - if (enable_ja3) { - int rc = Ja3BufferAddValue(&ja3_cipher_suites, cipher_suite); - if (rc != 0) { - return -1; - } + if (TLSDecodeValueIsGREASE(cipher_suite) != 1) { + if (ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) { + SCTLSHandshakeAddCipher(ssl_state->curr_connp->hs, cipher_suite); + } + if (enable_ja3) { + int rc = Ja3BufferAddValue(&ja3_cipher_suites, cipher_suite); + if (rc != 0) { + return -1; } } - processed_len += 2; } + processed_len += 2; + } - if (enable_ja3) { - int rc = Ja3BufferAppendBuffer(&ssl_state->curr_connp->ja3_str, &ja3_cipher_suites); - if (rc == -1) { - return -1; - } + if (enable_ja3) { + int rc = Ja3BufferAppendBuffer(&ssl_state->curr_connp->ja3_str, &ja3_cipher_suites); + if (rc == -1) { + return -1; } - - } else { - /* Skip cipher suites */ - input += cipher_suites_length; } return (int)(input - initial_input); @@ -1107,10 +1098,7 @@ static inline int TLSDecodeHSHelloExtensionSupportedVersions(SSLState *ssl_state uint16_t ver = (uint16_t)(input[i] << 8) | input[i + 1]; if (TLSVersionValid(ver)) { ssl_state->curr_connp->version = ver; - if (ssl_state->curr_connp->ja4 != NULL && - ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) { - SCJA4SetTLSVersion(ssl_state->curr_connp->ja4, ver); - } + SCTLSHandshakeSetTLSVersion(ssl_state->curr_connp->hs, ver); break; } i += 2; @@ -1278,15 +1266,14 @@ static inline int TLSDecodeHSHelloExtensionSigAlgorithms( if (!(HAS_SPACE(sigalgo_len))) goto invalid_length; - if (ssl_state->curr_connp->ja4 != NULL && - ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) { + if (ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) { uint16_t sigalgo_processed_len = 0; while (sigalgo_processed_len < sigalgo_len) { uint16_t sigalgo = (uint16_t)(*input << 8) | *(input + 1); input += 2; sigalgo_processed_len += 2; - SCJA4AddSigAlgo(ssl_state->curr_connp->ja4, sigalgo); + SCTLSHandshakeAddSigAlgo(ssl_state->curr_connp->hs, sigalgo); } } else { /* Skip signature algorithms */ @@ -1350,11 +1337,9 @@ static inline int TLSDecodeHSHelloExtensionALPN( break; } - /* Only record the first value for JA4 */ - if (ssl_state->curr_connp->ja4 != NULL && - ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) { + if (ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) { if (alpn_processed_len == 1) { - SCJA4SetALPN(ssl_state->curr_connp->ja4, (const char *)input, protolen); + SCTLSHandshakeSetALPN(ssl_state->curr_connp->hs, (const char *)input, protolen); } } StoreALPN(ssl_state->curr_connp, input, protolen); @@ -1550,10 +1535,9 @@ static inline int TLSDecodeHSHelloExtensions(SSLState *ssl_state, } } - if (ssl_state->curr_connp->ja4 != NULL && - ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) { + if (ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) { if (TLSDecodeValueIsGREASE(ext_type) != 1) { - SCJA4AddExtension(ssl_state->curr_connp->ja4, ext_type); + SCTLSHandshakeAddExtension(ssl_state->curr_connp->hs, ext_type); } } @@ -1605,15 +1589,6 @@ static int TLSDecodeHandshakeHello(SSLState *ssl_state, int ret; uint32_t parsed = 0; - /* Ensure that we have a JA4 state defined by now if we have JA4 enabled, - we are in a client hello and we don't have such a state yet (to avoid - leaking memory in case this function is entered more than once). */ - if (SC_ATOMIC_GET(ssl_config.enable_ja4) && - ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO && - ssl_state->curr_connp->ja4 == NULL) { - ssl_state->curr_connp->ja4 = SCJA4New(); - } - ret = TLSDecodeHSHelloVersion(ssl_state, input, input_len); if (ret < 0) goto end; @@ -2961,6 +2936,8 @@ static void *SSLStateAlloc(void *orig_state, AppProto proto_orig) ssl_state->server_connp.cert_log_flag = 0; memset(ssl_state->client_connp.random, 0, TLS_RANDOM_LEN); memset(ssl_state->server_connp.random, 0, TLS_RANDOM_LEN); + ssl_state->client_connp.hs = SCTLSHandshakeNew(); + ssl_state->server_connp.hs = SCTLSHandshakeNew(); TAILQ_INIT(&ssl_state->server_connp.certs); TAILQ_INIT(&ssl_state->server_connp.alpns); TAILQ_INIT(&ssl_state->client_connp.certs); @@ -3016,12 +2993,14 @@ static void SSLStateFree(void *p) if (ssl_state->server_connp.session_id) SCFree(ssl_state->server_connp.session_id); - if (ssl_state->client_connp.ja4) - SCJA4Free(ssl_state->client_connp.ja4); + if (ssl_state->client_connp.hs) + SCTLSHandshakeFree(ssl_state->client_connp.hs); if (ssl_state->client_connp.ja3_str) Ja3BufferFree(&ssl_state->client_connp.ja3_str); if (ssl_state->client_connp.ja3_hash) SCFree(ssl_state->client_connp.ja3_hash); + if (ssl_state->server_connp.hs) + SCTLSHandshakeFree(ssl_state->server_connp.hs); if (ssl_state->server_connp.ja3_str) Ja3BufferFree(&ssl_state->server_connp.ja3_str); if (ssl_state->server_connp.ja3_hash) diff --git a/src/app-layer-ssl.h b/src/app-layer-ssl.h index 2d136f6f01..51c43c5ce2 100644 --- a/src/app-layer-ssl.h +++ b/src/app-layer-ssl.h @@ -282,7 +282,7 @@ typedef struct SSLStateConnp_ { JA3Buffer *ja3_str; char *ja3_hash; - JA4 *ja4; + HandshakeParams *hs; /* handshake tls fragmentation buffer. Handshake messages can be fragmented over multiple * TLS records. */ diff --git a/src/detect-ja4-hash.c b/src/detect-ja4-hash.c index b1f79906bd..2e879abee6 100644 --- a/src/detect-ja4-hash.c +++ b/src/detect-ja4-hash.c @@ -35,8 +35,6 @@ #include "detect-engine-prefilter.h" #include "detect-ja4-hash.h" -#include "util-ja4.h" - #include "app-layer-ssl.h" #ifndef HAVE_JA4 @@ -146,12 +144,12 @@ static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx, if (buffer->inspect == NULL) { const SSLState *ssl_state = (SSLState *)f->alstate; - if (ssl_state->client_connp.ja4 == NULL) { + if (ssl_state->client_connp.hs == NULL) { return NULL; } uint8_t data[JA4_HEX_LEN]; - SCJA4GetHash(ssl_state->client_connp.ja4, (uint8_t(*)[JA4_HEX_LEN])data); + SCJA4GetHash(ssl_state->client_connp.hs, (uint8_t(*)[JA4_HEX_LEN])data); InspectionBufferSetup(det_ctx, list_id, buffer, data, 0); InspectionBufferCopy(buffer, data, JA4_HEX_LEN); diff --git a/src/output-json-tls.c b/src/output-json-tls.c index 72d60b19e8..f339155a2e 100644 --- a/src/output-json-tls.c +++ b/src/output-json-tls.c @@ -35,7 +35,6 @@ #include "threadvars.h" #include "util-debug.h" #include "util-ja3.h" -#include "util-ja4.h" #include "util-time.h" #define LOG_TLS_FIELD_VERSION BIT_U64(0) @@ -237,12 +236,14 @@ static void JsonTlsLogJa3(SCJsonBuilder *js, SSLState *ssl_state) static void JsonTlsLogSCJA4(SCJsonBuilder *js, SSLState *ssl_state) { - if (ssl_state->client_connp.ja4 != NULL) { +#ifdef HAVE_JA4 + if (ssl_state->client_connp.hs != NULL) { uint8_t buffer[JA4_HEX_LEN]; /* JA4 hash has 36 characters */ - SCJA4GetHash(ssl_state->client_connp.ja4, (uint8_t(*)[JA4_HEX_LEN])buffer); - SCJbSetStringFromBytes(js, "ja4", buffer, 36); + SCJA4GetHash(ssl_state->client_connp.hs, (uint8_t(*)[JA4_HEX_LEN])buffer); + SCJbSetStringFromBytes(js, "ja4", buffer, JA4_HEX_LEN); } +#endif } static void JsonTlsLogJa3SHash(SCJsonBuilder *js, SSLState *ssl_state) diff --git a/src/util-ja4.h b/src/util-ja4.h deleted file mode 100644 index 769e089652..0000000000 --- a/src/util-ja4.h +++ /dev/null @@ -1,29 +0,0 @@ -/* Copyright (C) 2024 Open Information Security Foundation - * - * You can copy, redistribute or modify this Program under the terms of - * the GNU General Public License version 2 as published by the Free - * Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * version 2 along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -/** - * \file - * - * \author Sascha Steinbiss - */ - -#ifndef SURICATA_UTIL_JA4_H -#define SURICATA_UTIL_JA4_H - -#define JA4_HEX_LEN 36 - -#endif /* SURICATA_UTIL_JA4_H */ -- 2.47.2