From: Jean-Paul Roliers Date: Sat, 4 Feb 2012 16:37:41 +0000 (+0100) Subject: tls: adding store option for TLS X-Git-Tag: suricata-1.4beta1~70 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=c4df7a45aeef26bb4fbf9fc48c2607b38c633e06;p=thirdparty%2Fsuricata.git tls: adding store option for TLS This patch adds a TLS store option to save certificate in PEM format. Each time the store action is met, a file and a metafile are created. Reworked-by: Eric Leblond --- diff --git a/src/app-layer-ssl.c b/src/app-layer-ssl.c index c092a5f98b..6c71a44c19 100644 --- a/src/app-layer-ssl.c +++ b/src/app-layer-ssl.c @@ -860,6 +860,8 @@ void *SSLStateAlloc(void) if (ssl_state == NULL) return NULL; memset(ssl_state, 0, sizeof(SSLState)); + ((SSLState*)ssl_state)->client_connp.cert_log_flag = 0; + ((SSLState*)ssl_state)->server_connp.cert_log_flag = 0; return ssl_state; } diff --git a/src/app-layer-ssl.h b/src/app-layer-ssl.h index 85da873c1f..1d6f1f81bb 100644 --- a/src/app-layer-ssl.h +++ b/src/app-layer-ssl.h @@ -64,6 +64,9 @@ enum { #define SSL_AL_FLAG_STATE_SERVER_KEYX 0x1000 #define SSL_AL_FLAG_STATE_UNKNOWN 0x2000 +#define SSL_TLS_LOG_PEM (1 << 0) + + /* SSL versions. We'll use a unified format for all, with the top byte * holding the major version and the lower byte the minor version */ @@ -102,6 +105,11 @@ typedef struct SSLStateConnp_ { char *cert0_issuerdn; char *cert0_fingerprint; + uint8_t *cert_input; + uint32_t cert_input_len; + + uint32_t cert_log_flag; + /* buffer for the tls record. * We use a malloced buffer, if the record is fragmented */ uint8_t *trec; diff --git a/src/app-layer-tls-handshake.c b/src/app-layer-tls-handshake.c index ce0349cafd..8c086cbeec 100644 --- a/src/app-layer-tls-handshake.c +++ b/src/app-layer-tls-handshake.c @@ -124,8 +124,8 @@ int DecodeTLSHandshakeServerCertificate(SSLState *ssl_state, uint8_t *input, uin } else { //SCLogInfo("TLS Cert %d: %s\n", i, buffer); if (i==0) { - ssl_state->curr_connp->cert0_subject = SCStrdup(buffer); - if (ssl_state->curr_connp->cert0_subject == NULL) { + ssl_state->server_connp.cert0_subject = SCStrdup(buffer); + if (ssl_state->server_connp.cert0_subject == NULL) { DerFree(cert); return -1; } @@ -137,8 +137,8 @@ int DecodeTLSHandshakeServerCertificate(SSLState *ssl_state, uint8_t *input, uin } else { //SCLogInfo("TLS IssuerDN %d: %s\n", i, buffer); if (i==0) { - ssl_state->curr_connp->cert0_issuerdn = SCStrdup(buffer); - if (ssl_state->curr_connp->cert0_issuerdn == NULL) { + ssl_state->server_connp.cert0_issuerdn = SCStrdup(buffer); + if (ssl_state->server_connp.cert0_issuerdn == NULL) { DerFree(cert); return -1; } @@ -169,6 +169,9 @@ int DecodeTLSHandshakeServerCertificate(SSLState *ssl_state, uint8_t *input, uin SCLogWarning(SC_ERR_MEM_ALLOC, "Can not allocate fingerprint string"); } } + + ssl_state->server_connp.cert_input = input; + ssl_state->server_connp.cert_input_len = cur_cert_length; } } diff --git a/src/detect-tls.c b/src/detect-tls.c index ba88083f7b..2075494bd6 100644 --- a/src/detect-tls.c +++ b/src/detect-tls.c @@ -85,6 +85,9 @@ static void DetectTlsIssuerDNFree(void *); static int DetectTlsFingerprintMatch (ThreadVars *, DetectEngineThreadCtx *, Flow *, uint8_t, void *, Signature *, SigMatch *); static int DetectTlsFingerprintSetup (DetectEngineCtx *, Signature *, char *); static void DetectTlsFingerprintFree(void *); +static int DetectTlsStoreSetup (DetectEngineCtx *, Signature *, char *); +static int DetectTlsStoreMatch (ThreadVars *, DetectEngineThreadCtx *, Flow *, uint8_t, void *, Signature *, SigMatch *); + /** * \brief Registration function for keyword: tls.version */ @@ -113,6 +116,15 @@ void DetectTlsRegister (void) { sigmatch_table[DETECT_AL_TLS_FINGERPRINT].Free = DetectTlsFingerprintFree; sigmatch_table[DETECT_AL_TLS_FINGERPRINT].RegisterTests = NULL; + sigmatch_table[DETECT_AL_TLS_STORE].name = "tls.store"; + sigmatch_table[DETECT_AL_TLS_STORE].Match = NULL; + sigmatch_table[DETECT_AL_TLS_STORE].AppLayerMatch = DetectTlsStoreMatch; + sigmatch_table[DETECT_AL_TLS_STORE].alproto = ALPROTO_TLS; + sigmatch_table[DETECT_AL_TLS_STORE].Setup = DetectTlsStoreSetup; + sigmatch_table[DETECT_AL_TLS_STORE].Free = NULL; + sigmatch_table[DETECT_AL_TLS_STORE].RegisterTests = NULL; + sigmatch_table[DETECT_AL_TLS_STORE].flags |= SIGMATCH_NOOPT; + const char *eb; int eo; int opts = 0; @@ -745,6 +757,64 @@ static void DetectTlsFingerprintFree(void *ptr) { SCFree(id_d); } +/** + * \brief this function is used to add the parsed "store" option + * \brief into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param idstr pointer to the user provided "store" option + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectTlsStoreSetup (DetectEngineCtx *de_ctx, Signature *s, char *str) +{ + SigMatch *sm = NULL; + + s->flags |= SIG_FLAG_TLSSTORE; + + sm = SigMatchAlloc(); + if (sm == NULL) + goto error; + + sm->type = DETECT_AL_TLS_STORE; + SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_AMATCH); + + if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_TLS) { + SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords."); + goto error; + } + + s->alproto = ALPROTO_TLS; + return 0; + +error: + if (sm != NULL) SCFree(sm); + return -1; + +} + +static int DetectTlsStoreMatch (ThreadVars *t, DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, void *state, Signature *s, SigMatch *m) +{ + SCEnter(); + + SSLState *ssl_state = (SSLState *)state; + if (ssl_state == NULL) { + SCLogDebug("no tls state, no match"); + SCReturnInt(1); + } + + FLOWLOCK_WRLOCK(f); + if (s->flags & SIG_FLAG_TLSSTORE) { + ssl_state->server_connp.cert_log_flag |= SSL_TLS_LOG_PEM; + } + + FLOWLOCK_UNLOCK(f); + SCReturnInt(1); +} + + /** * \brief this function registers unit tests for DetectTlsIssuerDN */ diff --git a/src/detect.h b/src/detect.h index 86ca8d3ff0..be9607e459 100644 --- a/src/detect.h +++ b/src/detect.h @@ -261,6 +261,8 @@ typedef struct DetectPort_ { #define SIG_FLAG_TOSERVER (1<<19) #define SIG_FLAG_TOCLIENT (1<<20) +#define SIG_FLAG_TLSSTORE (1<<21) + /* signature init flags */ #define SIG_FLAG_INIT_DEONLY 1 /**< decode event only signature */ #define SIG_FLAG_INIT_PACKET (1<<1) /**< signature has matches against a packet (as opposed to app layer) */ @@ -996,6 +998,7 @@ enum { DETECT_AL_TLS_SUBJECT, DETECT_AL_TLS_ISSUERDN, DETECT_AL_TLS_FINGERPRINT, + DETECT_AL_TLS_STORE, DETECT_AL_HTTP_COOKIE, DETECT_AL_HTTP_METHOD, diff --git a/src/log-tlslog.c b/src/log-tlslog.c index bb1fae1b6a..b7514c17b5 100644 --- a/src/log-tlslog.c +++ b/src/log-tlslog.c @@ -47,12 +47,17 @@ #include "util-buffer.h" #include "util-logopenfile.h" +#include "util-crypt.h" #define DEFAULT_LOG_FILENAME "tls.log" +static char tls_logfile_base_dir[PATH_MAX] = "/tmp"; +SC_ATOMIC_DECLARE(unsigned int, cert_id); + #define MODULE_NAME "LogTlsLog" #define OUTPUT_BUFFER_SIZE 65535 +#define CERT_ENC_BUFFER_SIZE 2048 #define LOG_TLS_DEFAULT 0 #define LOG_TLS_EXTENDED 1 @@ -79,6 +84,8 @@ void TmModuleLogTlsLogRegister(void) /* enable the logger for the app layer */ AppLayerRegisterLogger(ALPROTO_TLS); + + SC_ATOMIC_INIT(cert_id); } void TmModuleLogTlsLogIPv4Register(void) @@ -109,10 +116,13 @@ typedef struct LogTlsFileCtx_ { typedef struct LogTlsLogThread_ { LogTlsFileCtx *tlslog_ctx; + /** LogTlsFileCtx has the pointer to the file and a mutex to allow multithreading */ uint32_t tls_cnt; MemBuffer *buffer; + uint8_t* enc_buf; + size_t enc_buf_len; } LogTlsLogThread; static void CreateTimeString(const struct timeval *ts, char *str, size_t size) @@ -121,7 +131,9 @@ static void CreateTimeString(const struct timeval *ts, char *str, size_t size) struct tm local_tm; struct tm *t = (struct tm *) localtime_r(&time, &local_tm); - snprintf(str, size, "%02d/%02d/%02d-%02d:%02d:%02d.%06u", t->tm_mon + 1, t->tm_mday, t->tm_year + 1900, t->tm_hour, t->tm_min, t->tm_sec, (uint32_t) ts->tv_usec); + snprintf(str, size, "%02d/%02d/%02d-%02d:%02d:%02d.%06u", + t->tm_mon + 1, t->tm_mday, t->tm_year + 1900, t->tm_hour, + t->tm_min, t->tm_sec, (uint32_t) ts->tv_usec); } static void LogTlsLogExtended(LogTlsLogThread *aft, SSLState * state) @@ -131,6 +143,194 @@ static void LogTlsLogExtended(LogTlsLogThread *aft, SSLState * state) } } +static int GetIPInformations(Packet *p, char* srcip, size_t srcip_len, + Port* sp, char* dstip, size_t dstip_len, + Port* dp, int ipproto) +{ + if ((PKT_IS_TOSERVER(p))) { + switch (ipproto) { + case AF_INET: + PrintInet(AF_INET, (const void *) GET_IPV4_SRC_ADDR_PTR(p), srcip, srcip_len); + PrintInet(AF_INET, (const void *) GET_IPV4_DST_ADDR_PTR(p), dstip, dstip_len); + break; + case AF_INET6: + PrintInet(AF_INET6, (const void *) GET_IPV6_SRC_ADDR(p), srcip, srcip_len); + PrintInet(AF_INET6, (const void *) GET_IPV6_DST_ADDR(p), dstip, dstip_len); + break; + default: + return 0; + } + *sp = p->sp; + *dp = p->dp; + } else { + switch (ipproto) { + case AF_INET: + PrintInet(AF_INET, (const void *) GET_IPV4_DST_ADDR_PTR(p), srcip, srcip_len); + PrintInet(AF_INET, (const void *) GET_IPV4_SRC_ADDR_PTR(p), dstip, dstip_len); + break; + case AF_INET6: + PrintInet(AF_INET6, (const void *) GET_IPV6_DST_ADDR(p), srcip, srcip_len); + PrintInet(AF_INET6, (const void *) GET_IPV6_SRC_ADDR(p), dstip, dstip_len); + break; + default: + return 0; + } + *sp = p->dp; + *dp = p->sp; + } + return 1; +} + +static int CreateFileName(LogTlsFileCtx *log, Packet *p, SSLState *state, char *filename) +{ +#define FILELEN 64 //filename len + extention + ending path / + some space + + int filenamelen = FILELEN + strlen(tls_logfile_base_dir); + int file_id = SC_ATOMIC_ADD(cert_id, 1); + + if (filenamelen + 1 > PATH_MAX) { + return 0; + } + + /* Use format : packet time + incremental ID + * When running on same pcap it will overwrite + * On a live device, we will not be able to overwrite */ + snprintf(filename, filenamelen, "%s/%ld.%ld-%d.pem", + tls_logfile_base_dir, + p->ts.tv_sec, + p->ts.tv_usec, + file_id); + return 1; +} + + +static void LogTlsLogPem(LogTlsLogThread *aft, Packet *p, SSLState *state, LogTlsFileCtx *log, int ipproto) +{ +#define PEMHEADER "-----BEGIN CERTIFICATE-----\n" +#define PEMFOOTER "-----END CERTIFICATE-----\n" + //Logging pem certificate + char filename[PATH_MAX] = ""; + FILE* fp = NULL; + FILE* fpmeta = NULL; + unsigned long pemlen; + unsigned char* pembase64ptr = NULL; + int ret; + + if ((state->server_connp.cert_input == NULL) || (state->server_connp.cert_input_len == 0)) + SCReturn; + + CreateFileName(log, p, state, filename); + if (strlen(filename) == 0) { + SCLogWarning(SC_ERR_FOPEN, "Can't create PEM filename"); + SCReturn; + } + + fp = fopen(filename, "w"); + if (fp == NULL) { + SCLogWarning(SC_ERR_FOPEN, "Can't create PEM file: %s", filename); + SCReturn; + } + + pemlen = (4 * (state->server_connp.cert_input_len + 2) / 3) +1; + if (pemlen > aft->enc_buf_len) { + aft->enc_buf = (uint8_t*) SCRealloc(aft->enc_buf, sizeof(uint8_t) * pemlen); + if (aft->enc_buf == NULL) { + SCLogWarning(SC_ERR_MEM_ALLOC, "Can't allocate data for base64 encoding"); + goto end_fp; + } + aft->enc_buf_len = pemlen; + } + + memset(aft->enc_buf, 0, aft->enc_buf_len); + + ret = Base64Encode((unsigned char*) state->server_connp.cert_input, state->server_connp.cert_input_len, aft->enc_buf, &pemlen); + if (ret != SC_BASE64_OK) { + SCLogWarning(SC_ERR_INVALID_ARGUMENTS, "Invalid return of Base64Encode function"); + goto end_fwrite_fp; + } + + if (fprintf(fp, PEMHEADER) < 0) + goto end_fwrite_fp; + + pembase64ptr = aft->enc_buf; + while (pemlen > 0) { + size_t loffset = pemlen >= 64 ? 64 : pemlen; + if (fwrite(pembase64ptr, 1, loffset, fp) != loffset) + goto end_fwrite_fp; + if (fwrite("\n", 1, 1, fp) != 1) + goto end_fwrite_fp; + pembase64ptr += 64; + if (pemlen < 64) + break; + pemlen -= 64; + } + + if (fprintf(fp, PEMFOOTER) < 0) + goto end_fwrite_fp; + fclose(fp); + + //Logging certificate informations + memcpy(filename + (strlen(filename) - 3), "meta", 4); + fpmeta = fopen(filename, "w"); + if (fpmeta != NULL) { + #define PRINT_BUF_LEN 46 + char srcip[PRINT_BUF_LEN], dstip[PRINT_BUF_LEN]; + char timebuf[64]; + Port sp, dp; + CreateTimeString(&p->ts, timebuf, sizeof(timebuf)); + if (!GetIPInformations(p, srcip, PRINT_BUF_LEN, &sp, dstip, PRINT_BUF_LEN, &dp, ipproto)) + goto end_fwrite_fpmeta; + if (fprintf(fpmeta, "TIME: %s\n", timebuf) < 0) + goto end_fwrite_fpmeta; + if (p->pcap_cnt > 0) { + if (fprintf(fpmeta, "PCAP PKT NUM: %"PRIu64"\n", p->pcap_cnt) < 0) + goto end_fwrite_fpmeta; + } + if (fprintf(fpmeta, "SRC IP: %s\n", srcip) < 0) + goto end_fwrite_fpmeta; + if (fprintf(fpmeta, "DST IP: %s\n", dstip) < 0) + goto end_fwrite_fpmeta; + if (fprintf(fpmeta, "PROTO: %" PRIu32 "\n", p->proto) < 0) + goto end_fwrite_fpmeta; + if (PKT_IS_TCP(p) || PKT_IS_UDP(p)) { + if (fprintf(fpmeta, "SRC PORT: %" PRIu16 "\n", sp) < 0) + goto end_fwrite_fpmeta; + if (fprintf(fpmeta, "DST PORT: %" PRIu16 "\n", dp) < 0) + goto end_fwrite_fpmeta; + } + + if (fprintf(fpmeta, "TLS SUBJECT: %s\n" + "TLS ISSUERDN: %s\n" + "TLS FINGERPRINT: %s\n", + state->server_connp.cert0_subject, + state->server_connp.cert0_issuerdn, + state->server_connp.cert0_fingerprint) < 0) + goto end_fwrite_fpmeta; + + fclose(fpmeta); + } else { + SCLogWarning(SC_ERR_FOPEN, "Can't open meta file: %s", + filename); + SCReturn; + } + + /* Reset the store flag */ + state->server_connp.cert_log_flag &= ~SSL_TLS_LOG_PEM; + SCReturn; + +end_fwrite_fp: + fclose(fp); + SCLogWarning(SC_ERR_FWRITE, "Unable to write certificate"); +end_fwrite_fpmeta: + if (fpmeta) { + fclose(fpmeta); + SCLogWarning(SC_ERR_FWRITE, "Unable to write certificate metafile"); + } +end_fp: + fclose(fp); +} + + static TmEcode LogTlsLogIPWrapper(ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, PacketQueue *postpq, int ipproto) { @@ -151,12 +351,6 @@ static TmEcode LogTlsLogIPWrapper(ThreadVars *tv, Packet *p, void *data, PacketQ if (proto != ALPROTO_TLS) goto end; - int r = AppLayerTransactionGetLoggedId(p->flow); - - if (r != 0) { - goto end; - } - SSLState *ssl_state = (SSLState *) AppLayerGetProtoStateFromPacket(p); if (ssl_state == NULL) { SCLogDebug("no tls state, so no request logging"); @@ -166,39 +360,23 @@ static TmEcode LogTlsLogIPWrapper(ThreadVars *tv, Packet *p, void *data, PacketQ if (ssl_state->server_connp.cert0_issuerdn == NULL || ssl_state->server_connp.cert0_subject == NULL) goto end; + if (ssl_state->server_connp.cert_log_flag & SSL_TLS_LOG_PEM) { + LogTlsLogPem(aft, p, ssl_state, hlog, ipproto); + } + + int r = AppLayerTransactionGetLoggedId(p->flow); + + if (r != 0) { + goto end; + } + CreateTimeString(&p->ts, timebuf, sizeof(timebuf)); - char srcip[46], dstip[46]; + #define PRINT_BUF_LEN 46 + char srcip[PRINT_BUF_LEN], dstip[PRINT_BUF_LEN]; Port sp, dp; - if ((PKT_IS_TOSERVER(p))) { - switch (ipproto) { - case AF_INET: - PrintInet(AF_INET, (const void *) GET_IPV4_SRC_ADDR_PTR(p), srcip, sizeof(srcip)); - PrintInet(AF_INET, (const void *) GET_IPV4_DST_ADDR_PTR(p), dstip, sizeof(dstip)); - break; - case AF_INET6: - PrintInet(AF_INET6, (const void *) GET_IPV6_SRC_ADDR(p), srcip, sizeof(srcip)); - PrintInet(AF_INET6, (const void *) GET_IPV6_DST_ADDR(p), dstip, sizeof(dstip)); - break; - default: - goto end; - } - sp = p->sp; - dp = p->dp; - } else { - switch (ipproto) { - case AF_INET: - PrintInet(AF_INET, (const void *) GET_IPV4_DST_ADDR_PTR(p), srcip, sizeof(srcip)); - PrintInet(AF_INET, (const void *) GET_IPV4_SRC_ADDR_PTR(p), dstip, sizeof(dstip)); - break; - case AF_INET6: - PrintInet(AF_INET6, (const void *) GET_IPV6_DST_ADDR(p), srcip, sizeof(srcip)); - PrintInet(AF_INET6, (const void *) GET_IPV6_SRC_ADDR(p), dstip, sizeof(dstip)); - break; - default: - goto end; - } - sp = p->dp; - dp = p->sp; + if (!GetIPInformations(p, srcip, PRINT_BUF_LEN, + &sp, dstip, PRINT_BUF_LEN, &dp, ipproto)) { + goto end; } /* reset */ @@ -210,6 +388,7 @@ static TmEcode LogTlsLogIPWrapper(ThreadVars *tv, Packet *p, void *data, PacketQ ssl_state->server_connp.cert0_subject, ssl_state->server_connp.cert0_issuerdn); AppLayerTransactionUpdateLoggedId(p->flow); + if (hlog->flags & LOG_TLS_EXTENDED) { LogTlsLogExtended(aft, ssl_state); } else { @@ -280,6 +459,14 @@ TmEcode LogTlsLogThreadInit(ThreadVars *t, void *initdata, void **data) return TM_ECODE_FAILED; } + aft->enc_buf = SCMalloc(CERT_ENC_BUFFER_SIZE); + if (aft->enc_buf == NULL) { + SCFree(aft); + return TM_ECODE_FAILED; + } + aft->enc_buf_len = CERT_ENC_BUFFER_SIZE; + memset(aft->enc_buf, 0, aft->enc_buf_len); + /* Use the Ouptut Context (file pointer and mutex) */ aft->tlslog_ctx = ((OutputCtx *) initdata)->data; @@ -326,6 +513,26 @@ OutputCtx *LogTlsLogInitCtx(ConfNode *conf) return NULL; } + char *s_default_log_dir = NULL; + if (ConfGet("default-log-dir", &s_default_log_dir) != 1) + s_default_log_dir = DEFAULT_LOG_DIR; + + const char *s_base_dir = NULL; + s_base_dir = ConfNodeLookupChildValue(conf, "certs-log-dir"); + if (s_base_dir == NULL || strlen(s_base_dir) == 0) { + strlcpy(tls_logfile_base_dir, + s_default_log_dir, sizeof(tls_logfile_base_dir)); + } else { + if (PathIsAbsolute(s_base_dir)) { + strlcpy(tls_logfile_base_dir, + s_base_dir, sizeof(tls_logfile_base_dir)); + } else { + snprintf(tls_logfile_base_dir, sizeof(tls_logfile_base_dir), + "%s/%s", s_default_log_dir, s_base_dir); + } + } + + /* FIXME config variable here */ if (SCConfLogOpenGeneric(conf, file_ctx, DEFAULT_LOG_FILENAME) < 0) { LogFileFreeCtx(file_ctx); return NULL; @@ -345,7 +552,6 @@ OutputCtx *LogTlsLogInitCtx(ConfNode *conf) } } - OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx)); if (output_ctx == NULL) return NULL; diff --git a/suricata.yaml.in b/suricata.yaml.in index e6ce8bacc9..c0526405ca 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -80,6 +80,7 @@ outputs: enabled: no # Log TLS connections. filename: tls.log # File to store TLS logs. #extended: yes # Log extended information like fingerprint + certs-log-dir: certs # directory to store the certificates files # a line based log to used with pcap file study. # this module is dedicated to offline pcap parsing (empty output