From 755de40d9f6a2f77594ed92f06044b58f0e16dfe Mon Sep 17 00:00:00 2001 From: Richard McConnell Date: Fri, 31 Jan 2025 11:30:01 +0000 Subject: [PATCH 1/3] Output/TLS: Allow logging of cl-handshake params Add new custom log fields: "client_handshake" which logs the following: 1. TLS version used during handshake 2. TLS extensions, excluding GREASE, SNI and ALPN 3. All cipher suites, excluding GREASE 4. All signature algorithms, excluding GREASE The use-case is for logging TLS handshake parameters in order to survey them, and so that JA4 hashe can be computed offline (in the case that they're not already computed for the purposes of rule matching). --- doc/userguide/output/eve/eve-json-format.rst | 3 + etc/schema.json | 29 +++++++ rust/src/ja4.rs | 65 +++++++++----- src/Makefile.am | 1 - src/detect-ja4-hash.c | 2 - src/output-json-tls.c | 91 ++++++++++++++------ src/util-ja4.h | 29 ------- 7 files changed, 144 insertions(+), 76 deletions(-) delete mode 100644 src/util-ja4.h diff --git a/doc/userguide/output/eve/eve-json-format.rst b/doc/userguide/output/eve/eve-json-format.rst index 952945dffc98..dcf6cd748849 100644 --- a/doc/userguide/output/eve/eve-json-format.rst +++ b/doc/userguide/output/eve/eve-json-format.rst @@ -1047,6 +1047,9 @@ In addition to this, custom logging also allows the following fields: * "certificate": The TLS certificate base64 encoded * "chain": The entire TLS certificate chain base64 encoded +* "client_handshake": structure containing "version", "ciphers" ([u16]), "exts" ([u16]), "sig_algs" ([u16]), + for client hello supported cipher suites, extensions, and signature algorithms, + respectively, in the order that they're mentioned (ie. unsorted) Examples ~~~~~~~~ diff --git a/etc/schema.json b/etc/schema.json index 3a877aabb96e..207fd792c6fb 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -6729,6 +6729,35 @@ "type": "string" } }, + "client_handshake": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "ciphers": { + "description": "TLS client cipher(s)", + "type": "array", + "items": { + "type": "integer" + } + }, + "exts": { + "description": "TLS client extension(s)", + "type": "array", + "items": { + "type": "integer" + } + }, + "sig_algs": { + "description": "TLS client signature algorithm(s)", + "type": "array", + "items": { + "type": "integer" + } + } + } + }, "server_alpns": { "description": "TLS server ALPN field(s)", "type": "array", diff --git a/rust/src/ja4.rs b/rust/src/ja4.rs index 4660f2330227..8e80739dccb0 100644 --- a/rust/src/ja4.rs +++ b/rust/src/ja4.rs @@ -30,6 +30,8 @@ use tls_parser::{TlsCipherSuiteID, TlsExtensionType, TlsVersion}; #[cfg(feature = "ja4")] use crate::jsonbuilder::HEX; +pub const JA4_HEX_LEN: usize = 36; + #[derive(Debug, PartialEq)] pub struct JA4 { tls_version: Option, @@ -39,10 +41,6 @@ pub struct JA4 { 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, } impl Default for JA4 { @@ -65,7 +63,6 @@ impl JA4 { domain: false, alpn: ['0', '0'], quic: false, - nof_exts: 0, } } pub fn set_quic(&mut self) {} @@ -113,7 +110,6 @@ impl JA4 { domain: false, alpn: ['0', '0'], quic: false, - nof_exts: 0, } } @@ -170,14 +166,10 @@ impl JA4 { if JA4::is_grease(u16::from(ext)) { return; } - if ext != TlsExtensionType::ApplicationLayerProtocolNegotiation - && ext != TlsExtensionType::ServerName - { - self.extensions.push(ext); - } else if ext == TlsExtensionType::ServerName { + if ext == TlsExtensionType::ServerName { self.domain = true; } - self.nof_exts += 1; + self.extensions.push(ext); } pub fn add_signature_algorithm(&mut self, sigalgo: u16) { @@ -188,6 +180,17 @@ impl JA4 { } pub fn get_hash(&self) -> String { + // 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 = self + .extensions + .iter() + .filter(|&ext| { + *ext != TlsExtensionType::ApplicationLayerProtocolNegotiation + && *ext != TlsExtensionType::ServerName + }) + .collect::>(); + // Calculate JA4_a let ja4_a = format!( "{proto}{version}{sni}{nof_c:02}{nof_e:02}{al1}{al2}", @@ -195,7 +198,7 @@ impl JA4 { 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), + nof_e = min(99, self.extensions.len()), al1 = self.alpn[0], al2 = self.alpn[1] ); @@ -214,11 +217,10 @@ 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 @@ -269,9 +271,32 @@ pub unsafe extern "C" fn SCJA4SetALPN(j: &mut JA4, proto: *const c_char, len: u1 } #[no_mangle] -pub unsafe extern "C" fn SCJA4GetHash(j: &mut JA4, out: &mut [u8; 36]) { +pub unsafe extern "C" fn SCJA4GetVersion(j: &JA4) -> u16 { + u16::from(j.tls_version.unwrap_or(TlsVersion(0))) +} + +#[no_mangle] +pub unsafe extern "C" fn SCJA4GetHash(j: &mut JA4, out: &mut [u8; JA4_HEX_LEN]) { let hash = j.get_hash(); - out[0..36].copy_from_slice(hash.as_bytes()); + out[0..JA4_HEX_LEN].copy_from_slice(hash.as_bytes()); +} + +#[no_mangle] +pub unsafe extern "C" fn SCJA4GetCiphers(j: &mut JA4, out: *mut usize) -> *const u16 { + *out = j.ciphersuites.len(); + j.ciphersuites.as_ptr() as *const u16 +} + +#[no_mangle] +pub unsafe extern "C" fn SCJA4GetExtensions(j: &mut JA4, out: *mut usize) -> *const u16 { + *out = j.extensions.len(); + j.extensions.as_ptr() as *const u16 +} + +#[no_mangle] +pub unsafe extern "C" fn SCJA4GetSigAlgs(j: &mut JA4, out: *mut usize) -> *const u16 { + *out = j.signature_algorithms.len(); + j.signature_algorithms.as_ptr() } #[no_mangle] diff --git a/src/Makefile.am b/src/Makefile.am index c3b1e9d237f9..54a872ee3dd6 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -507,7 +507,6 @@ noinst_HEADERS = \ util-ioctl.h \ util-ip.h \ util-ja3.h \ - util-ja4.h \ util-landlock.h \ util-logopenfile.h \ util-log-redis.h \ diff --git a/src/detect-ja4-hash.c b/src/detect-ja4-hash.c index 78a9367fdaa5..1cd262782251 100644 --- a/src/detect-ja4-hash.c +++ b/src/detect-ja4-hash.c @@ -34,8 +34,6 @@ #include "detect-engine-prefilter.h" #include "detect-ja4-hash.h" -#include "util-ja4.h" - #include "app-layer-ssl.h" #ifndef HAVE_JA4 diff --git a/src/output-json-tls.c b/src/output-json-tls.c index c4ba0e249e62..1e0299acfcf8 100644 --- a/src/output-json-tls.c +++ b/src/output-json-tls.c @@ -35,29 +35,29 @@ #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) -#define LOG_TLS_FIELD_SUBJECT BIT_U64(1) -#define LOG_TLS_FIELD_ISSUER BIT_U64(2) -#define LOG_TLS_FIELD_SERIAL BIT_U64(3) -#define LOG_TLS_FIELD_FINGERPRINT BIT_U64(4) -#define LOG_TLS_FIELD_NOTBEFORE BIT_U64(5) -#define LOG_TLS_FIELD_NOTAFTER BIT_U64(6) -#define LOG_TLS_FIELD_SNI BIT_U64(7) -#define LOG_TLS_FIELD_CERTIFICATE BIT_U64(8) -#define LOG_TLS_FIELD_CHAIN BIT_U64(9) -#define LOG_TLS_FIELD_SESSION_RESUMED BIT_U64(10) -#define LOG_TLS_FIELD_JA3 BIT_U64(11) -#define LOG_TLS_FIELD_JA3S BIT_U64(12) -#define LOG_TLS_FIELD_CLIENT BIT_U64(13) /**< client fields (issuer, subject, etc) */ -#define LOG_TLS_FIELD_CLIENT_CERT BIT_U64(14) -#define LOG_TLS_FIELD_CLIENT_CHAIN BIT_U64(15) -#define LOG_TLS_FIELD_JA4 BIT_U64(16) -#define LOG_TLS_FIELD_SUBJECTALTNAME BIT_U64(17) -#define LOG_TLS_FIELD_CLIENT_ALPNS BIT_U64(18) -#define LOG_TLS_FIELD_SERVER_ALPNS BIT_U64(19) +#define LOG_TLS_FIELD_VERSION BIT_U64(0) +#define LOG_TLS_FIELD_SUBJECT BIT_U64(1) +#define LOG_TLS_FIELD_ISSUER BIT_U64(2) +#define LOG_TLS_FIELD_SERIAL BIT_U64(3) +#define LOG_TLS_FIELD_FINGERPRINT BIT_U64(4) +#define LOG_TLS_FIELD_NOTBEFORE BIT_U64(5) +#define LOG_TLS_FIELD_NOTAFTER BIT_U64(6) +#define LOG_TLS_FIELD_SNI BIT_U64(7) +#define LOG_TLS_FIELD_CERTIFICATE BIT_U64(8) +#define LOG_TLS_FIELD_CHAIN BIT_U64(9) +#define LOG_TLS_FIELD_SESSION_RESUMED BIT_U64(10) +#define LOG_TLS_FIELD_JA3 BIT_U64(11) +#define LOG_TLS_FIELD_JA3S BIT_U64(12) +#define LOG_TLS_FIELD_CLIENT BIT_U64(13) /**< client fields (issuer, subject, etc) */ +#define LOG_TLS_FIELD_CLIENT_CERT BIT_U64(14) +#define LOG_TLS_FIELD_CLIENT_CHAIN BIT_U64(15) +#define LOG_TLS_FIELD_JA4 BIT_U64(16) +#define LOG_TLS_FIELD_SUBJECTALTNAME BIT_U64(17) +#define LOG_TLS_FIELD_CLIENT_ALPNS BIT_U64(18) +#define LOG_TLS_FIELD_SERVER_ALPNS BIT_U64(19) +#define LOG_TLS_FIELD_CLIENT_HANDSHAKE BIT_U64(20) typedef struct { const char *name; @@ -86,6 +86,7 @@ TlsFields tls_fields[] = { { "subjectaltname", LOG_TLS_FIELD_SUBJECTALTNAME }, { "client_alpns", LOG_TLS_FIELD_CLIENT_ALPNS }, { "server_alpns", LOG_TLS_FIELD_SERVER_ALPNS }, + { "client_handshake", LOG_TLS_FIELD_CLIENT_HANDSHAKE }, { NULL, -1 }, // clang-format on }; @@ -190,10 +191,10 @@ static void JsonTlsLogSerial(JsonBuilder *js, SSLState *ssl_state) } } -static void JsonTlsLogVersion(JsonBuilder *js, SSLState *ssl_state) +static void JsonTlsLogVersion(JsonBuilder *js, const uint16_t version) { char ssl_version[SSL_VERSION_MAX_STRLEN]; - SSLVersionToString(ssl_state->server_connp.version, ssl_version); + SSLVersionToString(version, ssl_version); jb_set_string(js, "version", ssl_version); } @@ -374,6 +375,44 @@ static void JsonTlsLogClientCert( } } +static void JsonTlsLogClientHandshake(JsonBuilder *js, SSLState *ssl_state) +{ + const uint16_t *val; + uintptr_t i, nr; + + if (ssl_state->client_connp.ja4 == NULL) { + return; + } + + jb_open_object(js, "client_handshake"); + + const uint16_t vers = SCJA4GetVersion(ssl_state->client_connp.ja4); + JsonTlsLogVersion(js, vers); + + val = SCJA4GetCiphers(ssl_state->client_connp.ja4, &nr); + jb_open_array(js, "ciphers"); + for (i = 0; i < nr; i++) { + jb_append_uint(js, val[i]); + } + jb_close(js); + + val = SCJA4GetExtensions(ssl_state->client_connp.ja4, &nr); + jb_open_array(js, "exts"); + for (i = 0; i < nr; i++) { + jb_append_uint(js, val[i]); + } + jb_close(js); + + val = SCJA4GetSigAlgs(ssl_state->client_connp.ja4, &nr); + jb_open_array(js, "sig_algs"); + for (i = 0; i < nr; i++) { + jb_append_uint(js, val[i]); + } + jb_close(js); + + jb_close(js); +} + static void JsonTlsLogFields(JsonBuilder *js, SSLState *ssl_state, uint64_t fields) { /* tls subject */ @@ -406,7 +445,7 @@ static void JsonTlsLogFields(JsonBuilder *js, SSLState *ssl_state, uint64_t fiel /* tls version */ if (fields & LOG_TLS_FIELD_VERSION) - JsonTlsLogVersion(js, ssl_state); + JsonTlsLogVersion(js, ssl_state->server_connp.version); /* tls notbefore */ if (fields & LOG_TLS_FIELD_NOTBEFORE) @@ -453,6 +492,10 @@ static void JsonTlsLogFields(JsonBuilder *js, SSLState *ssl_state, uint64_t fiel jb_close(js); } } + + /* tls client handshake parameters */ + if (fields & LOG_TLS_FIELD_CLIENT_HANDSHAKE) + JsonTlsLogClientHandshake(js, ssl_state); } bool JsonTlsLogJSONExtended(void *vtx, JsonBuilder *tjs) diff --git a/src/util-ja4.h b/src/util-ja4.h deleted file mode 100644 index 769e089652d8..000000000000 --- 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 */ From f3fca1e6978d9f4678c6238a9e6bcbc19df0973b Mon Sep 17 00:00:00 2001 From: Richard McConnell Date: Fri, 31 Jan 2025 12:15:19 +0000 Subject: [PATCH 2/3] Output/TLS: Allow logging of sv-handshake params "server_handshake" which logs the following: 1. TLS version used during handshake 2. The chosen cipher suite, excluding GREASE 3. TLS extensions, excluding GREASE --- doc/userguide/output/eve/eve-json-format.rst | 2 ++ etc/schema.json | 19 +++++++++++ rust/src/ja4.rs | 5 +++ src/app-layer-ssl.c | 23 +++++++++----- src/output-json-tls.c | 33 ++++++++++++++++++++ 5 files changed, 74 insertions(+), 8 deletions(-) diff --git a/doc/userguide/output/eve/eve-json-format.rst b/doc/userguide/output/eve/eve-json-format.rst index dcf6cd748849..f4c3f5c00850 100644 --- a/doc/userguide/output/eve/eve-json-format.rst +++ b/doc/userguide/output/eve/eve-json-format.rst @@ -1050,6 +1050,8 @@ In addition to this, custom logging also allows the following fields: * "client_handshake": structure containing "version", "ciphers" ([u16]), "exts" ([u16]), "sig_algs" ([u16]), for client hello supported cipher suites, extensions, and signature algorithms, respectively, in the order that they're mentioned (ie. unsorted) +* "server_handshake": structure containing "version", "chosen cipher", "exts" ([u16]), for server hello + in the order that they're mentioned (ie. unsorted) Examples ~~~~~~~~ diff --git a/etc/schema.json b/etc/schema.json index 207fd792c6fb..77c15bd521c0 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -6758,6 +6758,25 @@ } } }, + "server_handshake": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "cipher": { + "description": "TLS server's chosen cipher", + "type": "integer" + }, + "exts": { + "description": "TLS server extension(s)", + "type": "array", + "items": { + "type": "integer" + } + } + } + }, "server_alpns": { "description": "TLS server ALPN field(s)", "type": "array", diff --git a/rust/src/ja4.rs b/rust/src/ja4.rs index 8e80739dccb0..966e646c17dc 100644 --- a/rust/src/ja4.rs +++ b/rust/src/ja4.rs @@ -287,6 +287,11 @@ pub unsafe extern "C" fn SCJA4GetCiphers(j: &mut JA4, out: *mut usize) -> *const j.ciphersuites.as_ptr() as *const u16 } +#[no_mangle] +pub unsafe extern "C" fn SCJA4GetFirstCipher(j: &mut JA4) -> u16 { + j.ciphersuites.first().map(|&v| *v).unwrap_or(0) +} + #[no_mangle] pub unsafe extern "C" fn SCJA4GetExtensions(j: &mut JA4, out: *mut usize) -> *const u16 { *out = j.extensions.len(); diff --git a/src/app-layer-ssl.c b/src/app-layer-ssl.c index 8da3617acffc..72b2d2d63d36 100644 --- a/src/app-layer-ssl.c +++ b/src/app-layer-ssl.c @@ -695,7 +695,8 @@ static inline int TLSDecodeHSHelloVersion(SSLState *ssl_state, ssl_state->curr_connp->version = version; if (ssl_state->curr_connp->ja4 != NULL && - ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) { + ssl_state->current_flags & + (SSL_AL_FLAG_STATE_CLIENT_HELLO | SSL_AL_FLAG_STATE_SERVER_HELLO)) { SCJA4SetTLSVersion(ssl_state->curr_connp->ja4, version); } @@ -870,7 +871,8 @@ static inline int TLSDecodeHSHelloCipherSuites(SSLState *ssl_state, if (TLSDecodeValueIsGREASE(cipher_suite) != 1) { if (ssl_state->curr_connp->ja4 != NULL && - ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) { + ssl_state->current_flags & + (SSL_AL_FLAG_STATE_CLIENT_HELLO | SSL_AL_FLAG_STATE_SERVER_HELLO)) { SCJA4AddCipher(ssl_state->curr_connp->ja4, cipher_suite); } if (enable_ja3) { @@ -1293,7 +1295,8 @@ static inline int TLSDecodeHSHelloExtensionALPN( /* Only record the first value for JA4 */ if (ssl_state->curr_connp->ja4 != NULL && - ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) { + ssl_state->current_flags & + (SSL_AL_FLAG_STATE_CLIENT_HELLO | SSL_AL_FLAG_STATE_SERVER_HELLO)) { if (alpn_processed_len == 1) { SCJA4SetALPN(ssl_state->curr_connp->ja4, (const char *)input, protolen); } @@ -1492,7 +1495,8 @@ static inline int TLSDecodeHSHelloExtensions(SSLState *ssl_state, } if (ssl_state->curr_connp->ja4 != NULL && - ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) { + ssl_state->current_flags & + (SSL_AL_FLAG_STATE_CLIENT_HELLO | SSL_AL_FLAG_STATE_SERVER_HELLO)) { if (TLSDecodeValueIsGREASE(ext_type) != 1) { SCJA4AddExtension(ssl_state->curr_connp->ja4, ext_type); } @@ -1546,11 +1550,12 @@ 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). */ + /* Ensure that we have a JA4 state defined by now, we are in a client/server 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->current_flags & + (SSL_AL_FLAG_STATE_CLIENT_HELLO | SSL_AL_FLAG_STATE_SERVER_HELLO) && ssl_state->curr_connp->ja4 == NULL) { ssl_state->curr_connp->ja4 = SCJA4New(); } @@ -2894,6 +2899,8 @@ static void SSLStateFree(void *p) if (ssl_state->client_connp.ja4) SCJA4Free(ssl_state->client_connp.ja4); + if (ssl_state->server_connp.ja4) + SCJA4Free(ssl_state->server_connp.ja4); if (ssl_state->client_connp.ja3_str) Ja3BufferFree(&ssl_state->client_connp.ja3_str); if (ssl_state->client_connp.ja3_hash) diff --git a/src/output-json-tls.c b/src/output-json-tls.c index 1e0299acfcf8..759e00fafb4c 100644 --- a/src/output-json-tls.c +++ b/src/output-json-tls.c @@ -58,6 +58,7 @@ #define LOG_TLS_FIELD_CLIENT_ALPNS BIT_U64(18) #define LOG_TLS_FIELD_SERVER_ALPNS BIT_U64(19) #define LOG_TLS_FIELD_CLIENT_HANDSHAKE BIT_U64(20) +#define LOG_TLS_FIELD_SERVER_HANDSHAKE BIT_U64(21) typedef struct { const char *name; @@ -87,6 +88,7 @@ TlsFields tls_fields[] = { { "client_alpns", LOG_TLS_FIELD_CLIENT_ALPNS }, { "server_alpns", LOG_TLS_FIELD_SERVER_ALPNS }, { "client_handshake", LOG_TLS_FIELD_CLIENT_HANDSHAKE }, + { "server_handshake", LOG_TLS_FIELD_SERVER_HANDSHAKE }, { NULL, -1 }, // clang-format on }; @@ -413,6 +415,33 @@ static void JsonTlsLogClientHandshake(JsonBuilder *js, SSLState *ssl_state) jb_close(js); } +static void JsonTlsLogServerHandshake(JsonBuilder *js, SSLState *ssl_state) +{ + const uint16_t *val; + uintptr_t i, nr; + + if (ssl_state->server_connp.ja4 == NULL) { + return; + } + + jb_open_object(js, "server_handshake"); + + const uint16_t vers = SCJA4GetVersion(ssl_state->server_connp.ja4); + JsonTlsLogVersion(js, vers); + + const uint16_t choosen_cipher = SCJA4GetFirstCipher(ssl_state->server_connp.ja4); + jb_set_uint(js, "cipher", choosen_cipher); + + val = SCJA4GetExtensions(ssl_state->server_connp.ja4, &nr); + jb_open_array(js, "exts"); + for (i = 0; i < nr; i++) { + jb_append_uint(js, val[i]); + } + jb_close(js); + + jb_close(js); +} + static void JsonTlsLogFields(JsonBuilder *js, SSLState *ssl_state, uint64_t fields) { /* tls subject */ @@ -496,6 +525,10 @@ static void JsonTlsLogFields(JsonBuilder *js, SSLState *ssl_state, uint64_t fiel /* tls client handshake parameters */ if (fields & LOG_TLS_FIELD_CLIENT_HANDSHAKE) JsonTlsLogClientHandshake(js, ssl_state); + + /* tls server handshake parameters */ + if (fields & LOG_TLS_FIELD_SERVER_HANDSHAKE) + JsonTlsLogServerHandshake(js, ssl_state); } bool JsonTlsLogJSONExtended(void *vtx, JsonBuilder *tjs) From f0f22e6d40419321d595c99e011efcb6427f2821 Mon Sep 17 00:00:00 2001 From: Richard McConnell Date: Fri, 31 Jan 2025 16:30:39 +0000 Subject: [PATCH 3/3] Enable JA4 tracking without fingerprint enabled The JA4 object can now 'track' the data from each TLS conversation without the user having to explicitly enabling ja4-fingerprint. This is to allow for other fields to be output without the JA4, for example client_handshake. --- doc/userguide/output/eve/eve-json-format.rst | 2 +- src/app-layer-ssl.c | 5 ++--- src/output-json-tls.c | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/userguide/output/eve/eve-json-format.rst b/doc/userguide/output/eve/eve-json-format.rst index f4c3f5c00850..89b9ed97833e 100644 --- a/doc/userguide/output/eve/eve-json-format.rst +++ b/doc/userguide/output/eve/eve-json-format.rst @@ -1041,7 +1041,7 @@ If extended logging is enabled the following fields are also included: * "client_alpns": array of strings with ALPN values * "server_alpns": array of strings with ALPN values -JA3 and JA4 must be enabled in the Suricata config file (set 'app-layer.protocols.tls.ja3-fingerprints'/'app-layer.protocols.tls.ja4-fingerprints' to 'yes'). +JA3 and JA4 fingerprints can be enabled in the Suricata config file (set 'app-layer.protocols.tls.ja3-fingerprints'/'app-layer.protocols.tls.ja4-fingerprints' to 'yes'). In addition to this, custom logging also allows the following fields: diff --git a/src/app-layer-ssl.c b/src/app-layer-ssl.c index 72b2d2d63d36..3ff2c2e1dcd3 100644 --- a/src/app-layer-ssl.c +++ b/src/app-layer-ssl.c @@ -846,7 +846,7 @@ 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)) { + if (enable_ja3 || ssl_state->curr_connp->ja4 != NULL) { JA3Buffer *ja3_cipher_suites = NULL; if (enable_ja3) { @@ -1553,8 +1553,7 @@ static int TLSDecodeHandshakeHello(SSLState *ssl_state, /* Ensure that we have a JA4 state defined by now, we are in a client/server 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 & + if (ssl_state->current_flags & (SSL_AL_FLAG_STATE_CLIENT_HELLO | SSL_AL_FLAG_STATE_SERVER_HELLO) && ssl_state->curr_connp->ja4 == NULL) { ssl_state->curr_connp->ja4 = SCJA4New(); diff --git a/src/output-json-tls.c b/src/output-json-tls.c index 759e00fafb4c..9b382a55ec0d 100644 --- a/src/output-json-tls.c +++ b/src/output-json-tls.c @@ -247,7 +247,7 @@ static void JsonTlsLogJa3(JsonBuilder *js, SSLState *ssl_state) static void JsonTlsLogSCJA4(JsonBuilder *js, SSLState *ssl_state) { - if (ssl_state->client_connp.ja4 != NULL) { + if (SSLJA4IsEnabled() && ssl_state->client_connp.ja4 != NULL) { uint8_t buffer[JA4_HEX_LEN]; /* JA4 hash has 36 characters */ SCJA4GetHash(ssl_state->client_connp.ja4, (uint8_t(*)[JA4_HEX_LEN])buffer);