From 3a1c12414a2e7577fd81b3a36a787c8f84b5d636 Mon Sep 17 00:00:00 2001 From: Shivani Bhardwaj Date: Tue, 26 Mar 2024 16:34:48 +0530 Subject: [PATCH] tls: store list of subject alternative names So far, the SANs were available as a part of IssuerDN via x509_parser crate but SANs were not available to the SSLState* to be directly used to setup and match against a sticky buffer. Expose it to SSLStateConnp. Feature 5234 --- rust/src/x509/mod.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++ src/app-layer-ssl.c | 26 +++++++++++++++++++++++++ src/app-layer-ssl.h | 2 ++ 3 files changed, 74 insertions(+) diff --git a/rust/src/x509/mod.rs b/rust/src/x509/mod.rs index c87928cf17..e7cdbe885c 100644 --- a/rust/src/x509/mod.rs +++ b/rust/src/x509/mod.rs @@ -23,7 +23,9 @@ use crate::common::rust_string_to_c; use nom7::Err; use std; use std::os::raw::c_char; +use std::fmt; use x509_parser::prelude::*; +use crate::x509::GeneralName; mod time; mod log; @@ -46,6 +48,19 @@ pub enum X509DecodeError { pub struct X509(X509Certificate<'static>); +pub struct SCGeneralName<'a>(&'a GeneralName<'a>); + +impl<'a> fmt::Display for SCGeneralName<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + GeneralName::DNSName(s) => write!(f, "{}", s), + GeneralName::URI(s) => write!(f, "{}", s), + GeneralName::IPAddress(s) => write!(f, "{:?}", s), + _ => write!(f, "{}", self.0) + } + } +} + /// Attempt to parse a X.509 from input, and return a pointer to the parsed object if successful. /// /// # Safety @@ -79,6 +94,37 @@ pub unsafe extern "C" fn rs_x509_get_subject(ptr: *const X509) -> *mut c_char { rust_string_to_c(subject) } +#[no_mangle] +pub unsafe extern "C" fn rs_x509_get_subjectaltname_len(ptr: *const X509) -> u16 { + if ptr.is_null() { + return 0; + } + let x509 = cast_pointer! {ptr, X509}; + let san_list = x509.0.tbs_certificate.subject_alternative_name(); + if let Ok(Some(sans)) = san_list { + // SAN length in a certificate is kept u16 following discussions at + // https://community.letsencrypt.org/t/why-sans-are-limited-to-100-domains-only + debug_validate_bug_on!(sans.value.general_names.len() == u16::MAX.into()); + return sans.value.general_names.len() as u16; + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_x509_get_subjectaltname_at(ptr: *const X509, idx: u16) -> *mut c_char { + if ptr.is_null() { + return std::ptr::null_mut(); + } + let x509 = cast_pointer! {ptr, X509}; + let san_list = x509.0.tbs_certificate.subject_alternative_name(); + if let Ok(Some(sans)) = san_list { + let general_name = &sans.value.general_names[idx as usize]; + let dns_name = SCGeneralName(general_name); + return rust_string_to_c(dns_name.to_string()); + } + return std::ptr::null_mut(); +} + #[no_mangle] pub unsafe extern "C" fn rs_x509_get_issuer(ptr: *const X509) -> *mut c_char { if ptr.is_null() { diff --git a/src/app-layer-ssl.c b/src/app-layer-ssl.c index e4a7ee0c65..139c8c3beb 100644 --- a/src/app-layer-ssl.c +++ b/src/app-layer-ssl.c @@ -292,6 +292,8 @@ static inline int SafeMemcpy(void *dst, size_t dst_offset, size_t dst_size, } \ } while (0) +static void SSLStateCertSANFree(SSLStateConnp *connp); + static void *SSLGetTx(void *state, uint64_t tx_id) { SSLState *ssl_state = (SSLState *)state; @@ -546,6 +548,15 @@ static int TlsDecodeHSCertificate(SSLState *ssl_state, SSLStateConnp *connp, } connp->cert0_issuerdn = str; + connp->cert0_sans_len = rs_x509_get_subjectaltname_len(x509); + char **sans = SCCalloc(connp->cert0_sans_len, sizeof(char *)); + if (sans == NULL) { + goto error; + } + for (uint16_t i = 0; i < connp->cert0_sans_len; i++) { + sans[i] = rs_x509_get_subjectaltname_at(x509, i); + } + connp->cert0_sans = sans; str = rs_x509_get_serial(x509); if (str == NULL) { err_code = ERR_INVALID_SERIAL; @@ -584,6 +595,8 @@ error: TlsDecodeHSCertificateErrSetEvent(ssl_state, err_code); if (x509 != NULL) rs_x509_free(x509); + + SSLStateCertSANFree(connp); return -1; invalid_cert: @@ -2832,6 +2845,16 @@ static void *SSLStateAlloc(void *orig_state, AppProto proto_orig) return (void *)ssl_state; } +static void SSLStateCertSANFree(SSLStateConnp *connp) +{ + if (connp->cert0_sans) { + for (uint16_t i = 0; i < connp->cert0_sans_len; i++) { + rs_cstring_free(connp->cert0_sans[i]); + } + SCFree(connp->cert0_sans); + } +} + /** * \internal * \brief Function to free the SSL state memory. @@ -2882,6 +2905,9 @@ static void SSLStateFree(void *p) if (ssl_state->server_connp.hs_buffer) SCFree(ssl_state->server_connp.hs_buffer); + SSLStateCertSANFree(&ssl_state->server_connp); + SSLStateCertSANFree(&ssl_state->client_connp); + AppLayerDecoderEventsFreeEvents(&ssl_state->tx_data.events); if (ssl_state->tx_data.de_state != NULL) { diff --git a/src/app-layer-ssl.h b/src/app-layer-ssl.h index fb9c83f801..90217b037c 100644 --- a/src/app-layer-ssl.h +++ b/src/app-layer-ssl.h @@ -254,6 +254,8 @@ typedef struct SSLStateConnp_ { int64_t cert0_not_after; char *cert0_fingerprint; + char **cert0_sans; + uint16_t cert0_sans_len; /* ssl server name indication extension */ char *sni; -- 2.47.2