diff --git a/doc/userguide/output/eve/eve-json-format.rst b/doc/userguide/output/eve/eve-json-format.rst index b5a2bf4641..2890b3f556 100644 --- a/doc/userguide/output/eve/eve-json-format.rst +++ b/doc/userguide/output/eve/eve-json-format.rst @@ -1053,6 +1053,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 791186da64..37c09111a6 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -7069,6 +7069,39 @@ "type": "string" } }, + "client_handshake": { + "type": "object", + "properties": { + "version": { + "description": "TLS version in client hello", + "type": "string" + }, + "ciphers": { + "description": "TLS client cipher(s)", + "type": "array", + "minItems": 1, + "items": { + "type": "integer" + } + }, + "exts": { + "description": "TLS client extension(s)", + "type": "array", + "minItems": 1, + "items": { + "type": "integer" + } + }, + "sig_algs": { + "description": "TLS client signature algorithm(s)", + "type": "array", + "minItems": 1, + "items": { + "type": "integer" + } + } + } + }, "server_alpns": { "description": "TLS server ALPN field(s)", "type": "array", diff --git a/rust/src/handshake.rs b/rust/src/handshake.rs index 1a6edead72..df33af4dca 100644 --- a/rust/src/handshake.rs +++ b/rust/src/handshake.rs @@ -24,6 +24,7 @@ use std::os::raw::c_char; use tls_parser::{TlsCipherSuiteID, TlsExtensionType, TlsVersion}; use crate::jsonbuilder::{JsonBuilder, JsonError}; +use crate::tls_version::SCTlsVersion; #[derive(Debug, PartialEq)] pub struct HandshakeParams { @@ -124,6 +125,52 @@ impl HandshakeParams { js.close()?; Ok(()) } + + fn log_version(&self, js: &mut JsonBuilder) -> Result<(), JsonError> { + let vers = self.tls_version.map(|v| v.0).unwrap_or_default(); + let ver_str = SCTlsVersion::try_from(vers).map_err(|_| JsonError::InvalidState)?; + js.set_string("version", ver_str.as_str())?; + Ok(()) + } + + fn log_exts(&self, js: &mut JsonBuilder) -> Result<(), JsonError> { + if self.extensions.is_empty() { + return Ok(()); + } + js.open_array("exts")?; + + for v in &self.extensions { + js.append_uint(v.0.into())?; + } + js.close()?; + Ok(()) + } + + fn log_ciphers(&self, js: &mut JsonBuilder) -> Result<(), JsonError> { + if self.ciphersuites.is_empty() { + return Ok(()); + } + js.open_array("ciphers")?; + + for v in &self.ciphersuites { + js.append_uint(v.0.into())?; + } + js.close()?; + Ok(()) + } + + fn log_sig_algs(&self, js: &mut JsonBuilder) -> Result<(), JsonError> { + if self.signature_algorithms.is_empty() { + return Ok(()); + } + js.open_array("sig_algs")?; + + for v in &self.signature_algorithms { + js.append_uint(*v as u64)?; + } + js.close()?; + Ok(()) + } } // Objects used to allow C to call into this struct via the below C ABI @@ -179,6 +226,38 @@ pub unsafe extern "C" fn SCTLSHandshakeFree(hs: &mut HandshakeParams) { std::mem::drop(hs); } +#[no_mangle] +pub unsafe extern "C" fn SCTLSHandshakeLogVersion(hs: &HandshakeParams, js: *mut JsonBuilder) -> bool { + if js.is_null() { + return false; + } + return hs.log_version(js.as_mut().unwrap()).is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn SCTLSHandshakeLogCiphers(hs: &HandshakeParams, js: *mut JsonBuilder) -> bool { + if js.is_null() { + return false; + } + return hs.log_ciphers(js.as_mut().unwrap()).is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn SCTLSHandshakeLogExtensions(hs: &HandshakeParams, js: *mut JsonBuilder) -> bool { + if js.is_null() { + return false; + } + return hs.log_exts(js.as_mut().unwrap()).is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn SCTLSHandshakeLogSigAlgs(hs: &HandshakeParams, js: *mut JsonBuilder) -> bool { + if js.is_null() { + return false; + } + return hs.log_sig_algs(js.as_mut().unwrap()).is_ok() +} + #[no_mangle] pub unsafe extern "C" fn SCTLSHandshakeLogALPNs( hs: &HandshakeParams, js: *mut JsonBuilder, ptr: *const c_char diff --git a/src/output-json-tls.c b/src/output-json-tls.c index 45bb5fcb8e..3fc07e826c 100644 --- a/src/output-json-tls.c +++ b/src/output-json-tls.c @@ -37,26 +37,27 @@ #include "util-ja3.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; @@ -85,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 }; @@ -360,6 +362,27 @@ static void JsonTlsLogClientCert( } } +static void JsonTlsLogClientHandshake(SCJsonBuilder *js, SSLState *ssl_state) +{ + if (ssl_state->client_connp.hs == NULL) { + return; + } + + // Don't write an empty handshake + if (SCTLSHandshakeIsEmpty(ssl_state->client_connp.hs)) { + return; + } + + SCJbOpenObject(js, "client_handshake"); + + SCTLSHandshakeLogVersion(ssl_state->client_connp.hs, js); + SCTLSHandshakeLogCiphers(ssl_state->client_connp.hs, js); + SCTLSHandshakeLogExtensions(ssl_state->client_connp.hs, js); + SCTLSHandshakeLogSigAlgs(ssl_state->client_connp.hs, js); + + SCJbClose(js); +} + static void JsonTlsLogFields(SCJsonBuilder *js, SSLState *ssl_state, uint64_t fields) { /* tls subject */ @@ -391,8 +414,9 @@ static void JsonTlsLogFields(SCJsonBuilder *js, SSLState *ssl_state, uint64_t fi JsonTlsLogSni(js, ssl_state); /* tls version */ - if (fields & LOG_TLS_FIELD_VERSION) + if (fields & LOG_TLS_FIELD_VERSION) { JsonTlsLogVersion(js, ssl_state); + } /* tls notbefore */ if (fields & LOG_TLS_FIELD_NOTBEFORE) @@ -430,6 +454,10 @@ static void JsonTlsLogFields(SCJsonBuilder *js, SSLState *ssl_state, uint64_t fi JsonTlsLogAlpns(js, &ssl_state->server_connp, "server_alpns"); } + /* tls client handshake parameters */ + if (fields & LOG_TLS_FIELD_CLIENT_HANDSHAKE) + JsonTlsLogClientHandshake(js, ssl_state); + if (fields & LOG_TLS_FIELD_CLIENT) { const bool log_cert = (fields & LOG_TLS_FIELD_CLIENT_CERT) != 0; const bool log_chain = (fields & LOG_TLS_FIELD_CLIENT_CHAIN) != 0; diff --git a/suricata.yaml.in b/suricata.yaml.in index 972de0687e..b6506b11f0 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -288,7 +288,7 @@ outputs: #session-resumption: no # custom controls which TLS fields that are included in eve-log # WARNING: enabling custom disables extended logging. - #custom: [subject, issuer, session_resumed, serial, fingerprint, sni, version, not_before, not_after, certificate, chain, ja3, ja3s, ja4, subjectaltname, client, client_certificate, client_chain, client_alpns, server_alpns] + #custom: [subject, issuer, session_resumed, serial, fingerprint, sni, version, not_before, not_after, certificate, chain, ja3, ja3s, ja4, subjectaltname, client, client_certificate, client_chain, client_alpns, server_alpns, client_handshake] - files: force-magic: no # force logging magic on all logged files # force logging of checksums, available hash functions are md5,