You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
suricata/src/detect-tls-subjectaltname.c

124 lines
3.8 KiB
C

/* 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 Shivani Bhardwaj <shivani@oisf.net>
*
* Implements support for tls.subjectaltname keyword.
*/
#include "suricata-common.h"
#include "threads.h"
#include "decode.h"
#include "detect.h"
#include "detect-parse.h"
#include "detect-engine.h"
#include "detect-engine-buffer.h"
#include "detect-engine-mpm.h"
#include "detect-engine-prefilter.h"
#include "detect-engine-content-inspection.h"
#include "detect-content.h"
#include "detect-tls-subjectaltname.h"
#include "detect-engine-uint.h"
#include "flow.h"
#include "flow-util.h"
#include "flow-var.h"
#include "util-debug.h"
#include "util-spm.h"
#include "util-print.h"
#include "stream-tcp.h"
#include "app-layer.h"
#include "app-layer-ssl.h"
#include "util-profiling.h"
static int DetectTlsSubjectAltNameSetup(DetectEngineCtx *, Signature *, const char *);
static int g_tls_subjectaltname_buffer_id = 0;
static bool TlsSubjectAltNameGetData(DetectEngineThreadCtx *det_ctx, const void *txv,
const uint8_t flags, uint32_t idx, const uint8_t **buf, uint32_t *buf_len)
{
const SSLState *ssl_state = (SSLState *)txv;
const SSLStateConnp *connp;
connp = &ssl_state->server_connp;
if (idx >= connp->cert0_sans_num) {
return false;
}
*buf = (const uint8_t *)connp->cert0_sans[idx].san;
if (*buf) {
*buf_len = connp->cert0_sans[idx].san_len;
} else {
// happens if the altname had a zero character in it
*buf_len = 0;
}
return true;
}
/**
* \brief Registration function for keyword: tls.subjectaltname
*/
void DetectTlsSubjectAltNameRegister(void)
{
sigmatch_table[DETECT_TLS_SUBJECTALTNAME].name = "tls.subjectaltname";
sigmatch_table[DETECT_TLS_SUBJECTALTNAME].desc =
"sticky buffer to match the TLS Subject Alternative Name buffer";
sigmatch_table[DETECT_TLS_SUBJECTALTNAME].url = "/rules/tls-keywords.html#tls-subjectaltname";
sigmatch_table[DETECT_TLS_SUBJECTALTNAME].Setup = DetectTlsSubjectAltNameSetup;
sigmatch_table[DETECT_TLS_SUBJECTALTNAME].flags |= SIGMATCH_NOOPT;
sigmatch_table[DETECT_TLS_SUBJECTALTNAME].flags |=
SIGMATCH_INFO_STICKY_BUFFER | SIGMATCH_INFO_MULTI_BUFFER;
DetectAppLayerMultiRegister("tls.subjectaltname", ALPROTO_TLS, SIG_FLAG_TOCLIENT,
tls: make the server states active phases The server states were completion milestones: server_hello landed when the server hello record was fully parsed and server_cert_done when the certificate message was done, so the states named moments at which the phase data was complete instead of the phase being processed. The flight-completion state (server_hello_done) named a moment at which no data is published and no decision can act. The states now denote the record phase being processed: server_hello is entered when the server hello message begins (its header is read once, the message may span several records) and server_cert when the certificate message begins, so the state names the message being parsed and a message that fails to decode leaves the track in the state that names it. The record that completes a hello hands the track over to the certificate phase before a single certificate byte arrives. The enter/leave is the shared handshake phase mechanism, so the client track follows: client_hello is entered at the start of the client hello as well, and the completing client hello is decided at client_cert. The flight-done state is removed and renumbers the tail (server_data, server_finished), leaving both tracks with the same five-phase shape and an absorbing finished state. Hello and certificate decode failures suppress the phase advance of their own message only: the decoders keep their pre-existing return contract (the message bytes are consumed and the error events are raised as before) and set a per-connp suppression flag that is reset at the start of the next handshake message and checked at the phase advance. A decode failure is not an app-layer parse error: the flow is neither dropped nor blinded, and the next message that decodes cleanly still advances. An undecodable certificate in a chain is skipped, not fatal: one decoder event is raised for the bad certificate and the remaining certificates are still decoded - the rest of the chain is kept and the certificate data is filled from the first fully decodable certificate. The certificate phase is suppressed for such a message (the phase data is incomplete). The incomplete-data condition is sticky per connp: it is set while the certificate message is being decoded, cleared by a fully decoded certificate message only, and consulted by the handshake-done heuristic that would otherwise establish the data phase from the certificate data - a later handshake message does not complete the data, so the tracks do not cross the cert phase on partial data. The certificate field extraction is commit-on-success: the fields are extracted into stashes and published to the connp only when the whole certificate yields every field, so a certificate that fails a late extraction leaves no partial data behind and the next certificate can fill the fields. An extraction failure raises its decoder event once per message: the message no longer aborts at the first failing certificate, and the event is not repeated for the later failing entries. The tls.cert_chain_len match is gated on the chain being final (the per-connp flag set by a fully decoded certificate message), so it does not match while the track sits in server_cert with a chain that is still arriving. TOCLIENT keyword registrations and the tc log threshold follow the renamed constants (values 1/2 unchanged; the threshold is triggered by the same writes as before, so the log fires on the same packets). The ja3s lua registration moves to server_data: lua keeps a numeric hook, so it must follow the renamed state the old hook value pointed at (server hello done was 3, server data is 3). The ja3s content is final once the ServerHello parses, which is why the C keywords (ja3s.string, ja3s.hash) stay registered at server_hello. Two string-based generic buffer lookups and the user guide follow the renamed states. Ticket: #9047.
1 month ago
TLS_STATE_SERVER_CERT, TlsSubjectAltNameGetData, 2);
DetectBufferTypeSetDescriptionByName("tls.subjectaltname", "TLS Subject Alternative Name");
DetectBufferTypeSupportsMultiInstance("tls.subjectaltname");
g_tls_subjectaltname_buffer_id = DetectBufferTypeGetByName("tls.subjectaltname");
}
/**
* \brief This function setup the tls.subjectaltname sticky buffer keyword
*
* \param de_ctx Pointer to the Detect Engine Context
* \param s Pointer to the Signature to which the keyword belongs
* \param str Should hold an empty string always
*
* \retval 0 On success
* \retval -1 On failure
*/
static int DetectTlsSubjectAltNameSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str)
{
if (SCDetectBufferSetActiveList(de_ctx, s, g_tls_subjectaltname_buffer_id) < 0)
return -1;
if (SCDetectSignatureSetAppProto(s, ALPROTO_TLS) < 0)
return -1;
return 0;
}