/* Copyright (C) 2014 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 Roliers Jean-Paul * \author Eric Leblond * \author Victor Julien * * Implements TLS store portion of the engine. * */ #include "suricata-common.h" #include "log-tlsstore.h" #include "decode.h" #include "app-layer-parser.h" #include "app-layer-ssl.h" #include "output.h" #include "log-tlslog.h" #include "util-conf.h" #include "util-path.h" #include "util-time.h" #define MODULE_NAME "LogTlsStoreLog" static char tls_logfile_base_dir[PATH_MAX] = "/tmp"; SC_ATOMIC_DECLARE(unsigned int, cert_id); static char logging_dir_not_writable; #define LOGGING_WRITE_ISSUE_LIMIT 6 typedef struct LogTlsStoreLogThread_ { uint8_t* enc_buf; size_t enc_buf_len; } LogTlsStoreLogThread; static int CreateFileName( const Packet *p, SSLState *state, char *filename, size_t filename_size, const bool client) { char path[PATH_MAX]; int file_id = SC_ATOMIC_ADD(cert_id, 1); const char *dir = client ? "client-" : ""; /* 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 */ if (snprintf(path, sizeof(path), "%s/%s%ld.%ld-%d.pem", tls_logfile_base_dir, dir, (long int)SCTIME_SECS(p->ts), (long int)SCTIME_USECS(p->ts), file_id) == sizeof(path)) return 0; strlcpy(filename, path, filename_size); return 1; } static void LogTlsLogPem(LogTlsStoreLogThread *aft, const Packet *p, SSLState *state, SSLStateConnp *connp, 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; uint8_t *ptmp; SSLCertsChain *cert; if (TAILQ_EMPTY(&connp->certs)) { SCReturn; } const bool client = connp == &state->client_connp; CreateFileName(p, state, filename, sizeof(filename), client); if (strlen(filename) == 0) { SCLogWarning("Can't create PEM filename"); SCReturn; } fp = fopen(filename, "w"); if (fp == NULL) { if (logging_dir_not_writable < LOGGING_WRITE_ISSUE_LIMIT) { SCLogWarning( "Can't create PEM file '%s' in '%s' directory", filename, tls_logfile_base_dir); logging_dir_not_writable++; } SCReturn; } TAILQ_FOREACH (cert, &connp->certs, next) { pemlen = SCBase64EncodeBufferSize(cert->cert_len); if (pemlen > aft->enc_buf_len) { ptmp = (uint8_t*) SCRealloc(aft->enc_buf, sizeof(uint8_t) * pemlen); if (ptmp == NULL) { SCFree(aft->enc_buf); aft->enc_buf = NULL; aft->enc_buf_len = 0; SCLogWarning("Can't allocate data for base64 encoding"); goto end_fp; } aft->enc_buf = ptmp; aft->enc_buf_len = pemlen; } memset(aft->enc_buf, 0, aft->enc_buf_len); ret = SCBase64Encode( (unsigned char *)cert->cert_data, cert->cert_len, aft->enc_buf, &pemlen); if (ret != SC_BASE64_OK) { SCLogWarning("Invalid return of SCBase64Encode 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 (!TLSGetIPInformations(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 (PacketIsTCP(p) || PacketIsUDP(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", connp->cert0_subject, connp->cert0_issuerdn, connp->cert0_fingerprint) < 0) goto end_fwrite_fpmeta; fclose(fpmeta); } else { if (logging_dir_not_writable < LOGGING_WRITE_ISSUE_LIMIT) { SCLogWarning("Can't create meta file '%s' in '%s' directory", filename, tls_logfile_base_dir); logging_dir_not_writable++; } SCReturn; } /* Reset the store flag */ connp->cert_log_flag &= ~SSL_TLS_LOG_PEM; SCReturn; end_fwrite_fp: fclose(fp); if (logging_dir_not_writable < LOGGING_WRITE_ISSUE_LIMIT) { SCLogWarning("Unable to write certificate"); logging_dir_not_writable++; } end_fwrite_fpmeta: if (fpmeta) { fclose(fpmeta); if (logging_dir_not_writable < LOGGING_WRITE_ISSUE_LIMIT) { SCLogWarning("Unable to write certificate metafile"); logging_dir_not_writable++; } } SCReturn; end_fp: fclose(fp); SCReturn; } /** \internal * \brief Condition function for TLS logger * \retval bool true or false -- log now? */ static bool LogTlsStoreCondition( ThreadVars *tv, const Packet *p, void *state, void *tx, uint64_t tx_id) { if (p->flow == NULL) { return false; } if (!(PacketIsTCP(p))) { return false; } SSLState *ssl_state = (SSLState *)state; if (ssl_state == NULL) { SCLogDebug("no tls state, so no request logging"); goto dontlog; } if ((ssl_state->server_connp.cert_log_flag & SSL_TLS_LOG_PEM) == 0) { goto dontlog; } if (ssl_state->server_connp.cert0_issuerdn == NULL || ssl_state->server_connp.cert0_subject == NULL) { goto dontlog; } return true; dontlog: return false; } static bool LogTlsStoreConditionClient( ThreadVars *tv, const Packet *p, void *state, void *tx, uint64_t tx_id) { if (p->flow == NULL) { return false; } if (!(PacketIsTCP(p))) { return false; } SSLState *ssl_state = (SSLState *)state; if (ssl_state == NULL) { SCLogDebug("no tls state, so no request logging"); goto dontlog; } if ((ssl_state->client_connp.cert_log_flag & SSL_TLS_LOG_PEM) == 0) { goto dontlog; } if ((ssl_state->client_connp.cert0_issuerdn == NULL || ssl_state->client_connp.cert0_subject == NULL)) { goto dontlog; } return true; dontlog: return false; } static int LogTlsStoreLoggerClient(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f, void *state, void *tx, uint64_t tx_id) { LogTlsStoreLogThread *aft = (LogTlsStoreLogThread *)thread_data; int ipproto = (PacketIsIPv4(p)) ? AF_INET : AF_INET6; SSLState *ssl_state = (SSLState *)state; if (unlikely(ssl_state == NULL)) { return 0; } /* client cert */ SSLStateConnp *connp = &ssl_state->client_connp; if (connp->cert_log_flag & SSL_TLS_LOG_PEM) { LogTlsLogPem(aft, p, ssl_state, connp, ipproto); } return 0; } static int LogTlsStoreLogger(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f, void *state, void *tx, uint64_t tx_id) { LogTlsStoreLogThread *aft = (LogTlsStoreLogThread *)thread_data; int ipproto = (PacketIsIPv4(p)) ? AF_INET : AF_INET6; SSLState *ssl_state = (SSLState *)state; if (unlikely(ssl_state == NULL)) { return 0; } /* server cert */ SSLStateConnp *connp = &ssl_state->server_connp; if (connp->cert_log_flag & SSL_TLS_LOG_PEM) { LogTlsLogPem(aft, p, ssl_state, connp, ipproto); } return 0; } static TmEcode LogTlsStoreLogThreadInit(ThreadVars *t, const void *initdata, void **data) { LogTlsStoreLogThread *aft = SCCalloc(1, sizeof(LogTlsStoreLogThread)); if (unlikely(aft == NULL)) return TM_ECODE_FAILED; if (initdata == NULL) { SCLogDebug("Error getting context for LogTLSStore. \"initdata\" argument NULL"); SCFree(aft); return TM_ECODE_FAILED; } struct stat stat_buf; /* coverity[toctou] */ if (stat(tls_logfile_base_dir, &stat_buf) != 0) { int ret; /* coverity[toctou] */ ret = SCMkDir(tls_logfile_base_dir, S_IRWXU|S_IXGRP|S_IRGRP); if (ret != 0) { int err = errno; if (err != EEXIST) { SCLogError("Cannot create certs drop directory %s: %s", tls_logfile_base_dir, strerror(err)); exit(EXIT_FAILURE); } } else { SCLogInfo("Created certs drop directory %s", tls_logfile_base_dir); } } *data = (void *)aft; return TM_ECODE_OK; } static TmEcode LogTlsStoreLogThreadDeinit(ThreadVars *t, void *data) { LogTlsStoreLogThread *aft = (LogTlsStoreLogThread *)data; if (aft == NULL) { return TM_ECODE_OK; } if (aft->enc_buf != NULL) SCFree(aft->enc_buf); /* clear memory */ memset(aft, 0, sizeof(LogTlsStoreLogThread)); SCFree(aft); return TM_ECODE_OK; } /** * \internal * * \brief deinit the log ctx and write out the waldo * * \param output_ctx output context to deinit */ static void LogTlsStoreLogDeInitCtx(OutputCtx *output_ctx) { SCFree(output_ctx); } /** \brief Create a new http log LogFilestoreCtx. * \param conf Pointer to ConfNode containing this loggers configuration. * \return NULL if failure, LogFilestoreCtx* to the file_ctx if succesful * */ static OutputInitResult LogTlsStoreLogInitCtx(SCConfNode *conf) { OutputInitResult result = { NULL, false }; OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx)); if (unlikely(output_ctx == NULL)) return result; output_ctx->data = NULL; output_ctx->DeInit = LogTlsStoreLogDeInitCtx; const char *s_default_log_dir = SCConfigGetLogDirectory(); const char *s_base_dir = SCConfNodeLookupChildValue(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); } } SCLogInfo("storing certs in %s", tls_logfile_base_dir); /* enable the logger for the app layer */ AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_TLS); result.ctx = output_ctx; result.ok = true; SCReturnCT(result, "OutputInitResult"); } void LogTlsStoreRegister (void) { OutputRegisterTxModuleWithCondition(LOGGER_TLS_STORE, MODULE_NAME, "tls-store", LogTlsStoreLogInitCtx, ALPROTO_TLS, LogTlsStoreLogger, LogTlsStoreCondition, LogTlsStoreLogThreadInit, LogTlsStoreLogThreadDeinit); OutputRegisterTxModuleWithCondition(LOGGER_TLS_STORE_CLIENT, MODULE_NAME, "tls-store", LogTlsStoreLogInitCtx, ALPROTO_TLS, LogTlsStoreLoggerClient, LogTlsStoreConditionClient, LogTlsStoreLogThreadInit, LogTlsStoreLogThreadDeinit); SC_ATOMIC_INIT(cert_id); SC_ATOMIC_SET(cert_id, 1); SCLogDebug("registered"); }