mirror of https://github.com/OISF/suricata
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.
407 lines
12 KiB
C
407 lines
12 KiB
C
/* 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 <popof.fpn@gmail.co>
|
|
* \author Eric Leblond <eric@regit.org>
|
|
* \author Victor Julien <victor@inliniac.net>
|
|
*
|
|
* 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_ {
|
|
uint32_t tls_cnt;
|
|
|
|
uint8_t* enc_buf;
|
|
size_t enc_buf_len;
|
|
} LogTlsStoreLogThread;
|
|
|
|
static int CreateFileName(const Packet *p, SSLState *state, char *filename, size_t filename_size)
|
|
{
|
|
char path[PATH_MAX];
|
|
int file_id = SC_ATOMIC_ADD(cert_id, 1);
|
|
|
|
/* 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/%ld.%ld-%d.pem", tls_logfile_base_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, 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(&state->server_connp.certs))
|
|
SCReturn;
|
|
|
|
CreateFileName(p, state, filename, sizeof(filename));
|
|
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, &state->server_connp.certs, next) {
|
|
pemlen = Base64EncodeBufferSize(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 = Base64Encode((unsigned char*) cert->cert_data, cert->cert_len, aft->enc_buf, &pemlen);
|
|
if (ret != SC_BASE64_OK) {
|
|
SCLogWarning("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 (!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 (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 {
|
|
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 */
|
|
state->server_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 int LogTlsStoreCondition(ThreadVars *tv, const Packet *p, void *state,
|
|
void *tx, uint64_t tx_id)
|
|
{
|
|
if (p->flow == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (!(PKT_IS_TCP(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 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 = (PKT_IS_IPV4(p)) ? AF_INET : AF_INET6;
|
|
|
|
SSLState *ssl_state = (SSLState *)state;
|
|
if (unlikely(ssl_state == NULL)) {
|
|
return 0;
|
|
}
|
|
|
|
if (ssl_state->server_connp.cert_log_flag & SSL_TLS_LOG_PEM) {
|
|
LogTlsLogPem(aft, p, ssl_state, ipproto);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static TmEcode LogTlsStoreLogThreadInit(ThreadVars *t, const void *initdata, void **data)
|
|
{
|
|
LogTlsStoreLogThread *aft = SCMalloc(sizeof(LogTlsStoreLogThread));
|
|
if (unlikely(aft == NULL))
|
|
return TM_ECODE_FAILED;
|
|
memset(aft, 0, sizeof(LogTlsStoreLogThread));
|
|
|
|
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;
|
|
}
|
|
|
|
static void LogTlsStoreLogExitPrintStats(ThreadVars *tv, void *data)
|
|
{
|
|
LogTlsStoreLogThread *aft = (LogTlsStoreLogThread *)data;
|
|
if (aft == NULL) {
|
|
return;
|
|
}
|
|
|
|
SCLogInfo("(%s) certificates extracted %" PRIu32 "", tv->name, aft->tls_cnt);
|
|
}
|
|
|
|
/**
|
|
* \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(ConfNode *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;
|
|
|
|
/* FIXME we need to implement backward compatibility here */
|
|
const char *s_default_log_dir = NULL;
|
|
s_default_log_dir = ConfigGetLogDirectory();
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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, LogTlsStoreLogExitPrintStats);
|
|
|
|
SC_ATOMIC_INIT(cert_id);
|
|
SC_ATOMIC_SET(cert_id, 1);
|
|
|
|
SCLogDebug("registered");
|
|
}
|