app-layer-ssl: generate JA3 fingerprints

Decode additional fields from the client hello packet and generate
JA3 fingerprints.
pull/3297/head
Mats Klepsland 8 years ago committed by Victor Julien
parent 3f0dea582d
commit 0c16cd0120

@ -420,6 +420,7 @@ util-host-info.c util-host-info.h \
util-hyperscan.c util-hyperscan.h \
util-ioctl.h util-ioctl.c \
util-ip.h util-ip.c \
util-ja3.h util-ja3.c \
util-logopenfile.h util-logopenfile.c \
util-logopenfile-tile.h util-logopenfile-tile.c \
util-log-redis.h util-log-redis.c \

@ -49,6 +49,7 @@
#include "util-print.h"
#include "util-pool.h"
#include "util-byte.h"
#include "util-ja3.h"
#include "flow-util.h"
#include "flow-private.h"
@ -83,8 +84,12 @@ SCEnumCharMap tls_decoder_event_table[ ] = {
/* by default we keep tracking */
#define SSL_CONFIG_DEFAULT_NOREASSEMBLE 0
/* JA3 fingerprints are disabled by default */
#define SSL_CONFIG_DEFAULT_JA3 0
typedef struct SslConfig_ {
int no_reassemble;
int enable_ja3;
} SslConfig;
SslConfig ssl_config;
@ -134,7 +139,7 @@ SslConfig ssl_config;
#define SSL_RECORD_MINIMUM_LENGTH 6
#define HAS_SPACE(n) ((uint32_t)((*input) + (n) - (initial_input)) > (uint32_t)(input_len)) ? 0 : 1
#define HAS_SPACE(n) ((uint32_t)((input) + (n) - (initial_input)) > (uint32_t)(input_len)) ? 0 : 1
static void SSLParserReset(SSLState *ssl_state)
{
@ -250,103 +255,420 @@ static void SSLSetTxDetectFlags(void *vtx, uint8_t dir, uint64_t flags)
}
}
static inline int TLSDecodeHSHelloVersion(SSLState *ssl_state, uint8_t **input,
const uint32_t input_len,
const uint8_t *initial_input)
/**
* \inline
* \brief Check if value is GREASE.
*
* http://tools.ietf.org/html/draft-davidben-tls-grease-00
*
* \param value Value to check.
*
* \retval 1 if is GREASE.
* \retval 0 if not is GREASE.
*/
static inline int TLSDecodeValueIsGREASE(const uint16_t value)
{
/* Skip version */
*input += SSLV3_CLIENT_HELLO_VERSION_LEN;
return 0;
switch (value)
{
case 0x0a0a:
case 0x1a1a:
case 0x2a2a:
case 0x3a3a:
case 0x4a4a:
case 0x5a5a:
case 0x6a6a:
case 0x7a7a:
case 0x8a8a:
case 0x9a9a:
case 0xaaaa:
case 0xbaba:
case 0xcaca:
case 0xdada:
case 0xeaea:
case 0xfafa:
return 1;
default:
return 0;
}
}
static inline int TLSDecodeHSHelloRandom(SSLState *ssl_state, uint8_t **input,
const uint32_t input_len,
const uint8_t *initial_input)
static inline int TLSDecodeHSHelloVersion(SSLState *ssl_state,
const uint8_t * const initial_input,
const uint32_t input_len)
{
/* Skip random */
*input += SSLV3_CLIENT_HELLO_RANDOM_LEN;
uint8_t *input = (uint8_t *)initial_input;
return 0;
if (!(HAS_SPACE(SSLV3_CLIENT_HELLO_VERSION_LEN))) {
SCLogDebug("TLS handshake invalid length");
SSLSetEvent(ssl_state,
TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH);
return -1;
}
if ((ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) &&
ssl_config.enable_ja3) {
uint16_t version = *input << 8 | *(input + 1);
ssl_state->ja3_str = Ja3BufferInit();
if (ssl_state->ja3_str == NULL)
return -1;
int rc = Ja3BufferAddValue(ssl_state->ja3_str, version);
if (rc != 0)
return -1;
}
input += SSLV3_CLIENT_HELLO_VERSION_LEN;
return (input - initial_input);
}
static inline int TLSDecodeHSHelloSessionID(SSLState *ssl_state,
uint8_t **input,
const uint32_t input_len,
const uint8_t *initial_input)
static inline int TLSDecodeHSHelloRandom(SSLState *ssl_state,
const uint8_t * const initial_input,
const uint32_t input_len)
{
if (!(HAS_SPACE(1))) {
uint8_t *input = (uint8_t *)initial_input;
if (!(HAS_SPACE(SSLV3_CLIENT_HELLO_RANDOM_LEN))) {
SCLogDebug("TLS handshake invalid length");
SSLSetEvent(ssl_state,
TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH);
return -1;
}
uint8_t session_id_length = **input;
*input += 1;
/* Skip random */
input += SSLV3_CLIENT_HELLO_RANDOM_LEN;
return (input - initial_input);
}
static inline int TLSDecodeHSHelloSessionID(SSLState *ssl_state,
const uint8_t * const initial_input,
const uint32_t input_len)
{
uint8_t *input = (uint8_t *)initial_input;
if (!(HAS_SPACE(1)))
goto invalid_length;
uint8_t session_id_length = *input;
input += 1;
if (session_id_length != 0) {
ssl_state->flags |= SSL_AL_FLAG_SSL_CLIENT_SESSION_ID;
}
*input += session_id_length;
if (!(HAS_SPACE(session_id_length)))
goto invalid_length;
return 0;
input += session_id_length;
return (input - initial_input);
invalid_length:
SCLogDebug("TLS handshake invalid length");
SSLSetEvent(ssl_state,
TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH);
return -1;
}
static inline int TLSDecodeHSHelloCipherSuites(SSLState *ssl_state,
uint8_t **input,
const uint32_t input_len,
const uint8_t *initial_input)
const uint8_t * const initial_input,
const uint32_t input_len)
{
if (!(HAS_SPACE(2))) {
SCLogDebug("TLS handshake invalid length");
SSLSetEvent(ssl_state,
TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH);
return -1;
}
uint8_t *input = (uint8_t *)initial_input;
uint16_t cipher_suites_length = **input << 8 | *(*input + 1);
*input += 2;
if (!(HAS_SPACE(2)))
goto invalid_length;
/* Skip cipher suites */
*input += cipher_suites_length;
uint16_t cipher_suites_length = *input << 8 | *(input + 1);
input += 2;
return 0;
if (!(HAS_SPACE(cipher_suites_length)))
goto invalid_length;
if ((ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) &&
ssl_config.enable_ja3) {
int rc;
JA3Buffer *ja3_cipher_suites = Ja3BufferInit();
if (ja3_cipher_suites == NULL)
return -1;
uint16_t processed_len = 0;
while (processed_len < cipher_suites_length)
{
if (!(HAS_SPACE(2))) {
Ja3BufferFree(ja3_cipher_suites);
goto invalid_length;
}
uint16_t cipher_suite = *input << 8 | *(input + 1);
input += 2;
if (TLSDecodeValueIsGREASE(cipher_suite) != 1) {
rc = Ja3BufferAddValue(ja3_cipher_suites, cipher_suite);
if (rc != 0) {
Ja3BufferFree(ja3_cipher_suites);
return -1;
}
}
processed_len += 2;
}
rc = Ja3BufferAppendBuffer(ssl_state->ja3_str, ja3_cipher_suites);
if (rc == -1) {
return -1;
}
} else {
/* Skip cipher suites */
input += cipher_suites_length;
}
return (input - initial_input);
invalid_length:
SCLogDebug("TLS handshake invalid length");
SSLSetEvent(ssl_state,
TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH);
return -1;
}
static inline int TLSDecodeHSHelloCompressionMethods(SSLState *ssl_state,
uint8_t **input,
const uint32_t input_len,
const uint8_t *initial_input)
const uint8_t * const initial_input,
const uint32_t input_len)
{
if (!(HAS_SPACE(1))) {
SCLogDebug("TLS handshake invalid length");
uint8_t *input = (uint8_t *)initial_input;
if (!(HAS_SPACE(1)))
goto invalid_length;
/* Skip compression methods */
uint8_t compression_methods_length = *input;
input += 1;
if (!(HAS_SPACE(compression_methods_length)))
goto invalid_length;
input += compression_methods_length;
return (input - initial_input);
invalid_length:
SCLogDebug("TLS handshake invalid_length");
SSLSetEvent(ssl_state,
TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH);
return -1;
}
static inline int TLSDecodeHSHelloExtensionSni(SSLState *ssl_state,
const uint8_t * const initial_input,
const uint32_t input_len)
{
uint8_t *input = (uint8_t *)initial_input;
if (!(HAS_SPACE(2)))
goto invalid_length;
/* Skip sni_list_length */
input += 2;
if (!(HAS_SPACE(1)))
goto invalid_length;
uint8_t sni_type = *input;
input += 1;
/* Currently the only type allowed is host_name
(RFC6066 section 3). */
if (sni_type != SSL_SNI_TYPE_HOST_NAME) {
SCLogDebug("Unknown SNI type");
SSLSetEvent(ssl_state,
TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH);
TLS_DECODER_EVENT_INVALID_SNI_TYPE);
return -1;
}
/* Skip compression methods */
uint8_t compression_methods_length = **input;
*input += 1;
if (!(HAS_SPACE(2)))
goto invalid_length;
*input += compression_methods_length;
uint16_t sni_len = *input << 8 | *(input + 1);
input += 2;
return 0;
if (!(HAS_SPACE(sni_len)))
goto invalid_length;
/* There must not be more than one extension of the same
type (RFC5246 section 7.4.1.4). */
if (ssl_state->curr_connp->sni) {
SCLogDebug("Multiple SNI extensions");
SSLSetEvent(ssl_state,
TLS_DECODER_EVENT_MULTIPLE_SNI_EXTENSIONS);
input += sni_len;
return (input - initial_input);
}
/* host_name contains the fully qualified domain name,
and should therefore be limited by the maximum domain
name length. */
if (sni_len > 255) {
SCLogDebug("SNI length >255");
SSLSetEvent(ssl_state,
TLS_DECODER_EVENT_INVALID_SNI_LENGTH);
return -1;
}
size_t sni_strlen = sni_len + 1;
ssl_state->curr_connp->sni = SCMalloc(sni_strlen);
if (unlikely(ssl_state->curr_connp->sni == NULL))
return -1;
memcpy(ssl_state->curr_connp->sni, input, sni_strlen - 1);
ssl_state->curr_connp->sni[sni_strlen-1] = 0;
input += sni_len;
return (input - initial_input);
invalid_length:
SCLogDebug("TLS handshake invalid length");
SSLSetEvent(ssl_state,
TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH);
return -1;
}
static inline int TLSDecodeHSHelloExtensionEllipticCurves(SSLState *ssl_state,
const uint8_t * const initial_input,
const uint32_t input_len,
JA3Buffer *ja3_elliptic_curves)
{
uint8_t *input = (uint8_t *)initial_input;
if (!(HAS_SPACE(2)))
goto invalid_length;
uint16_t elliptic_curves_len = *input << 8 | *(input + 1);
input += 2;
if (!(HAS_SPACE(elliptic_curves_len)))
goto invalid_length;
if ((ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) &&
ssl_config.enable_ja3) {
uint16_t ec_processed_len = 0;
while (ec_processed_len < elliptic_curves_len)
{
uint16_t elliptic_curve = *input << 8 | *(input + 1);
input += 2;
if (TLSDecodeValueIsGREASE(elliptic_curve) != 1) {
int rc = Ja3BufferAddValue(ja3_elliptic_curves,
elliptic_curve);
if (rc != 0)
return -1;
}
ec_processed_len += 2;
}
} else {
/* Skip elliptic curves */
input += elliptic_curves_len;
}
return (input - initial_input);
invalid_length:
SCLogDebug("TLS handshake invalid length");
SSLSetEvent(ssl_state,
TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH);
return -1;
}
static inline int TLSDecodeHSHelloExtensionEllipticCurvePF(SSLState *ssl_state,
const uint8_t * const initial_input,
const uint32_t input_len,
JA3Buffer *ja3_elliptic_curves_pf)
{
uint8_t *input = (uint8_t *)initial_input;
if (!(HAS_SPACE(1)))
goto invalid_length;
uint8_t ec_pf_len = *input;
input += 1;
if (!(HAS_SPACE(ec_pf_len)))
goto invalid_length;
if ((ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) &&
ssl_config.enable_ja3) {
uint8_t ec_pf_processed_len = 0;
while (ec_pf_processed_len < ec_pf_len)
{
uint8_t elliptic_curve_pf = *input;
input += 1;
if (TLSDecodeValueIsGREASE(elliptic_curve_pf) != 1) {
int rc = Ja3BufferAddValue(ja3_elliptic_curves_pf,
elliptic_curve_pf);
if (rc != 0)
return -1;
}
ec_pf_processed_len += 1;
}
} else {
/* Skip elliptic curve point formats */
input += ec_pf_len;
}
return (input - initial_input);
invalid_length:
SCLogDebug("TLS handshake invalid length");
SSLSetEvent(ssl_state,
TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH);
return -1;
}
static inline int TLSDecodeHSHelloExtensions(SSLState *ssl_state,
uint8_t **input,
const uint32_t input_len,
const uint8_t *initial_input)
const uint8_t * const initial_input,
const uint32_t input_len)
{
uint8_t *input = (uint8_t *)initial_input;
int ret;
int rc;
uint32_t parsed = 0;
JA3Buffer *ja3_extensions = NULL;
JA3Buffer *ja3_elliptic_curves = NULL;
JA3Buffer *ja3_elliptic_curves_pf = NULL;
if ((ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) &&
ssl_config.enable_ja3) {
ja3_extensions = Ja3BufferInit();
ja3_elliptic_curves = Ja3BufferInit();
ja3_elliptic_curves_pf = Ja3BufferInit();
if (ja3_extensions == NULL || ja3_elliptic_curves == NULL ||
ja3_elliptic_curves_pf == NULL)
return -1;
}
/* Extensions are optional (RFC5246 section 7.4.1.2) */
if (!(HAS_SPACE(2)))
goto end;
uint16_t extensions_len = **input << 8 | *(*input + 1);
*input += 2;
uint16_t extensions_len = *input << 8 | *(input + 1);
input += 2;
if (!(HAS_SPACE(extensions_len)))
goto invalid_length;
@ -357,133 +679,167 @@ static inline int TLSDecodeHSHelloExtensions(SSLState *ssl_state,
if (!(HAS_SPACE(2)))
goto invalid_length;
uint16_t ext_type = **input << 8 | *(*input + 1);
*input += 2;
uint16_t ext_type = *input << 8 | *(input + 1);
input += 2;
if (!(HAS_SPACE(2)))
goto invalid_length;
uint16_t ext_len = **input << 8 | *(*input + 1);
*input += 2;
uint16_t ext_len = *input << 8 | *(input + 1);
input += 2;
if (!(HAS_SPACE(ext_len)))
goto invalid_length;
parsed = input - initial_input;
switch (ext_type) {
case SSL_EXTENSION_SNI:
{
/* There must not be more than one extension of the same
type (RFC5246 section 7.4.1.4). */
if (ssl_state->curr_connp->sni) {
SCLogDebug("Multiple SNI extensions");
SSLSetEvent(ssl_state,
TLS_DECODER_EVENT_MULTIPLE_SNI_EXTENSIONS);
return -1;
}
/* Skip sni_list_length */
*input += 2;
if (!(HAS_SPACE(1)))
goto invalid_length;
uint8_t sni_type = **input;
*input += 1;
/* Currently the only type allowed is host_name
(RFC6066 section 3). */
if (sni_type != SSL_SNI_TYPE_HOST_NAME) {
SCLogDebug("Unknown SNI type");
SSLSetEvent(ssl_state,
TLS_DECODER_EVENT_INVALID_SNI_TYPE);
return -1;
}
ret = TLSDecodeHSHelloExtensionSni(ssl_state, input,
input_len - parsed);
if (ret < 0)
goto end;
if (!(HAS_SPACE(2)))
goto invalid_length;
input += ret;
uint16_t sni_len = **input << 8 | *(*input + 1);
*input += 2;
break;
}
if (!(HAS_SPACE(sni_len)))
goto invalid_length;
case SSL_EXTENSION_ELLIPTIC_CURVES:
{
ret = TLSDecodeHSHelloExtensionEllipticCurves(ssl_state, input,
input_len - parsed,
ja3_elliptic_curves);
if (ret < 0)
goto end;
/* host_name contains the fully qualified domain name,
and should therefore be limited by the maximum domain
name length. */
if (sni_len > 255) {
SCLogDebug("SNI length >255");
SSLSetEvent(ssl_state,
TLS_DECODER_EVENT_INVALID_SNI_LENGTH);
return -1;
}
input += ret;
size_t sni_strlen = sni_len + 1;
ssl_state->curr_connp->sni = SCMalloc(sni_strlen);
break;
}
if (unlikely(ssl_state->curr_connp->sni == NULL))
return -1;
case SSL_EXTENSION_EC_POINT_FORMATS:
{
ret = TLSDecodeHSHelloExtensionEllipticCurvePF(ssl_state, input,
input_len - parsed,
ja3_elliptic_curves_pf);
if (ret < 0)
goto end;
memcpy(ssl_state->curr_connp->sni, *input, sni_strlen - 1);
ssl_state->curr_connp->sni[sni_strlen-1] = 0;
input += ret;
*input += sni_len;
break;
}
default:
{
*input += ext_len;
input += ext_len;
break;
}
}
if ((ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) &&
ssl_config.enable_ja3) {
if (TLSDecodeValueIsGREASE(ext_type) != 1) {
rc = Ja3BufferAddValue(ja3_extensions, ext_type);
if (rc != 0)
goto error;
}
}
processed_len += ext_len + 4;
}
end:
return 0;
if ((ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) &&
ssl_config.enable_ja3) {
rc = Ja3BufferAppendBuffer(ssl_state->ja3_str, ja3_extensions);
if (rc == -1)
goto error;
rc = Ja3BufferAppendBuffer(ssl_state->ja3_str, ja3_elliptic_curves);
if (rc == -1)
goto error;
rc = Ja3BufferAppendBuffer(ssl_state->ja3_str,
ja3_elliptic_curves_pf);
if (rc == -1)
goto error;
}
return (input - initial_input);
invalid_length:
SCLogDebug("TLS handshake invalid length");
SSLSetEvent(ssl_state,
TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH);
error:
if (ja3_extensions != NULL)
Ja3BufferFree(ja3_extensions);
if (ja3_elliptic_curves != NULL)
Ja3BufferFree(ja3_elliptic_curves);
if (ja3_elliptic_curves_pf != NULL)
Ja3BufferFree(ja3_elliptic_curves_pf);
return -1;
}
static int TLSDecodeHandshakeHello(SSLState *ssl_state, uint8_t *input,
uint32_t input_len)
static int TLSDecodeHandshakeHello(SSLState *ssl_state,
const uint8_t * const input,
const uint32_t input_len)
{
int rc;
uint8_t *initial_input = input;
int ret;
uint32_t parsed = 0;
/* Only parse the message if it is complete */
if (input_len < ssl_state->curr_connp->message_length || input_len < 40)
goto end;
rc = TLSDecodeHSHelloVersion(ssl_state, &input, input_len, initial_input);
if (rc != 0)
ret = TLSDecodeHSHelloVersion(ssl_state, input, input_len);
if (ret < 0)
goto end;
rc = TLSDecodeHSHelloRandom(ssl_state, &input, input_len, initial_input);
if (rc != 0)
parsed += ret;
ret = TLSDecodeHSHelloRandom(ssl_state, input + parsed, input_len - parsed);
if (ret < 0)
goto end;
rc = TLSDecodeHSHelloSessionID(ssl_state, &input, input_len, initial_input);
if (rc != 0)
parsed += ret;
ret = TLSDecodeHSHelloSessionID(ssl_state, input + parsed,
input_len - parsed);
if (ret < 0)
goto end;
rc = TLSDecodeHSHelloCipherSuites(ssl_state, &input, input_len,
initial_input);
if (rc != 0)
parsed += ret;
ret = TLSDecodeHSHelloCipherSuites(ssl_state, input + parsed,
input_len - parsed);
if (ret < 0)
goto end;
rc = TLSDecodeHSHelloCompressionMethods(ssl_state, &input, input_len,
initial_input);
if (rc != 0)
parsed += ret;
ret = TLSDecodeHSHelloCompressionMethods(ssl_state, input + parsed,
input_len - parsed);
if (ret < 0)
goto end;
rc = TLSDecodeHSHelloExtensions(ssl_state, &input, input_len,
initial_input);
if (rc != 0)
parsed += ret;
ret = TLSDecodeHSHelloExtensions(ssl_state, input + parsed,
input_len - parsed);
if (ret < 0)
goto end;
if ((ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) &&
ssl_config.enable_ja3) {
ssl_state->ja3_hash = Ja3GenerateHash(ssl_state->ja3_str);
}
end:
return 0;
}
@ -1686,6 +2042,11 @@ static void SSLStateFree(void *p)
if (ssl_state->server_connp.sni)
SCFree(ssl_state->server_connp.sni);
if (ssl_state->ja3_str)
Ja3BufferFree(ssl_state->ja3_str);
if (ssl_state->ja3_hash)
SCFree(ssl_state->ja3_hash);
AppLayerDecoderEventsFreeEvents(&ssl_state->decoder_events);
if (ssl_state->de_state != NULL) {
@ -1960,6 +2321,25 @@ void RegisterSSLParsers(void)
if (ConfGetBool("app-layer.protocols.tls.no-reassemble", &ssl_config.no_reassemble) != 1)
ssl_config.no_reassemble = SSL_CONFIG_DEFAULT_NOREASSEMBLE;
}
/* Check if we should generate JA3 fingerprints */
if (ConfGetBool("app-layer.protocols.tls.ja3-fingerprints",
&ssl_config.enable_ja3) != 1) {
ssl_config.enable_ja3 = SSL_CONFIG_DEFAULT_JA3;
}
#ifndef HAVE_NSS
if (ssl_config.enable_ja3) {
SCLogWarning(SC_WARN_NO_JA3_SUPPORT,
"no MD5 calculation support built in, disabling JA3");
ssl_config.enable_ja3 = 0;
}
#else
if (RunmodeIsUnittests()) {
ssl_config.enable_ja3 = 1;
}
#endif
} else {
SCLogInfo("Parsed disabled for %s protocol. Protocol detection"
"still on.", proto_name);

@ -29,6 +29,7 @@
#include "app-layer-protos.h"
#include "app-layer-parser.h"
#include "decode-events.h"
#include "util-ja3.h"
#include "queue.h"
enum {
@ -107,6 +108,8 @@ enum {
/* extensions */
#define SSL_EXTENSION_SNI 0x0000
#define SSL_EXTENSION_ELLIPTIC_CURVES 0x000a
#define SSL_EXTENSION_EC_POINT_FORMATS 0x000b
/* SNI types */
#define SSL_SNI_TYPE_HOST_NAME 0
@ -202,6 +205,9 @@ typedef struct SSLState_ {
uint32_t current_flags;
JA3Buffer *ja3_str;
char *ja3_hash;
SSLStateConnp *curr_connp;
SSLStateConnp client_connp;

@ -348,7 +348,7 @@ const char * SCErrorToString(SCError err)
CASE_CODE (SC_ERR_CREATE_DIRECTORY);
CASE_CODE (SC_WARN_FLOWBIT);
CASE_CODE (SC_ERR_SMB_CONFIG);
CASE_CODE (SC_WARN_NO_JA3_SUPPORT);
CASE_CODE (SC_ERR_MAX);
}

@ -338,8 +338,8 @@ typedef enum {
SC_ERR_CREATE_DIRECTORY,
SC_WARN_FLOWBIT,
SC_ERR_SMB_CONFIG,
SC_ERR_MAX,
SC_WARN_NO_JA3_SUPPORT,
SC_ERR_MAX
} SCError;
const char *SCErrorToString(SCError);

@ -0,0 +1,249 @@
/* Copyright (C) 2007-2017 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 Mats Klepsland <mats.klepsland@gmail.com>
*
* Functions used to generate JA3 fingerprint.
*/
#include "suricata-common.h"
#include "util-validate.h"
#include "util-ja3.h"
#define MD5_STRING_LENGTH 33
/**
* \brief Allocate new buffer.
*
* \return pointer to buffer on success.
* \return NULL on failure.
*/
JA3Buffer *Ja3BufferInit(void)
{
JA3Buffer *buffer = SCCalloc(1, sizeof(JA3Buffer));
if (buffer == NULL) {
return NULL;
}
return buffer;
}
/**
* \brief Free allocated buffer.
*
* \param buffer The buffer to free.
*/
void Ja3BufferFree(JA3Buffer *buffer)
{
DEBUG_VALIDATE_BUG_ON(buffer == NULL);
if (buffer->data != NULL) {
SCFree(buffer->data);
}
SCFree(buffer);
}
/**
* \internal
* \brief Resize buffer if it is full.
*
* \param buffer The buffer.
* \param len The length of the data that should fit into the buffer.
*
* \retval 0 on success.
* \retval -1 on failure.
*/
static int Ja3BufferResizeIfFull(JA3Buffer *buffer, uint32_t len)
{
DEBUG_VALIDATE_BUG_ON(buffer == NULL);
if (len == 0) {
return 0;
}
while (buffer->used + len + 2 > buffer->size)
{
buffer->size *= 2;
char *tmp = SCRealloc(buffer->data, buffer->size * sizeof(char));
if (tmp == NULL) {
SCLogError(SC_ERR_MEM_ALLOC, "Error resizing JA3 buffer");
return -1;
}
buffer->data = tmp;
}
return 0;
}
/**
* \brief Append buffer to buffer.
*
* Append the second buffer to the first and then free it.
*
* \param buffer1 The first buffer.
* \param buffer2 The second buffer.
*
* \retval 0 on success.
* \retval -1 on failure.
*/
int Ja3BufferAppendBuffer(JA3Buffer *buffer1, JA3Buffer *buffer2)
{
if (buffer1 == NULL || buffer2 == NULL) {
SCLogError(SC_ERR_INVALID_ARGUMENT, "Buffers should not be NULL");
return -1;
}
/* If buffer1 contains no data, then we just copy the second buffer
instead of appending its data. */
if (buffer1->data == NULL) {
buffer1->data = buffer2->data;
buffer1->used = buffer2->used;
buffer1->size = buffer2->size;
SCFree(buffer2);
return 0;
}
int rc = Ja3BufferResizeIfFull(buffer1, buffer2->used);
if (rc != 0) {
Ja3BufferFree(buffer1);
Ja3BufferFree(buffer2);
return -1;
}
if (buffer2->used == 0) {
buffer1->used += snprintf(buffer1->data + buffer1->used, buffer1->size -
buffer1->used, ",");
} else {
buffer1->used += snprintf(buffer1->data + buffer1->used, buffer1->size -
buffer1->used, ",%s", buffer2->data);
}
Ja3BufferFree(buffer2);
return 0;
}
/**
* \internal
* \brief Return number of digits in number.
*
* \param num The number.
*
* \return digits Number of digits.
*/
static uint32_t NumberOfDigits(uint32_t num)
{
if (num < 10) {
return 1;
}
return 1 + NumberOfDigits(num / 10);
}
/**
* \brief Add value to buffer.
*
* \param buffer The buffer.
* \param value The value.
*
* \retval 0 on success.
* \retval -1 on failure.
*/
int Ja3BufferAddValue(JA3Buffer *buffer, uint32_t value)
{
if (buffer == NULL) {
SCLogError(SC_ERR_INVALID_ARGUMENT, "Buffer should not be NULL");
return -1;
}
if (buffer->data == NULL) {
buffer->data = SCMalloc(JA3_BUFFER_INITIAL_SIZE * sizeof(char));
if (buffer->data == NULL) {
SCLogError(SC_ERR_MEM_ALLOC,
"Error allocating memory for JA3 data");
Ja3BufferFree(buffer);
return -1;
}
buffer->size = JA3_BUFFER_INITIAL_SIZE;
}
uint32_t value_len = NumberOfDigits(value);
int rc = Ja3BufferResizeIfFull(buffer, value_len);
if (rc != 0) {
Ja3BufferFree(buffer);
return -1;
}
if (buffer->used == 0) {
buffer->used += snprintf(buffer->data, buffer->size, "%d", value);
}
else {
buffer->used += snprintf(buffer->data + buffer->used, buffer->size -
buffer->used, "-%d", value);
}
return 0;
}
/**
* \brief Generate Ja3 hash string.
*
* \param buffer The Ja3 buffer.
*
* \retval pointer to hash string on success.
* \retval NULL on failure.
*/
char *Ja3GenerateHash(JA3Buffer *buffer)
{
#ifdef HAVE_NSS
if (buffer == NULL) {
SCLogError(SC_ERR_INVALID_ARGUMENT, "Buffer should not be NULL");
return NULL;
}
if (buffer->data == NULL) {
SCLogError(SC_ERR_INVALID_VALUE, "Buffer data should not be NULL");
return NULL;
}
char *ja3_hash = SCMalloc(MD5_STRING_LENGTH * sizeof(char));
if (ja3_hash == NULL) {
SCLogError(SC_ERR_MEM_ALLOC,
"Error allocating memory for JA3 hash");
return NULL;
}
unsigned char md5[MD5_LENGTH];
HASH_HashBuf(HASH_AlgMD5, md5, (unsigned char *)buffer->data, buffer->used);
int i, x;
for (i = 0, x = 0; x < MD5_LENGTH; x++) {
i += snprintf(ja3_hash + i, MD5_STRING_LENGTH - i, "%02x", md5[x]);
}
return ja3_hash;
#else
return NULL;
#endif /* HAVE_NSS */
}

@ -0,0 +1,41 @@
/* Copyright (C) 2007-2017 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 Mats Klepsland <mats.klepsland@gmail.com>
*/
#ifndef __UTIL_JA3_H__
#define __UTIL_JA3_H__
#define JA3_BUFFER_INITIAL_SIZE 128
typedef struct JA3Buffer_ {
char *data;
size_t size;
size_t used;
} JA3Buffer;
JA3Buffer *Ja3BufferInit(void);
void Ja3BufferFree(JA3Buffer *);
int Ja3BufferAppendBuffer(JA3Buffer *, JA3Buffer *);
int Ja3BufferAddValue(JA3Buffer *, uint32_t);
char *Ja3GenerateHash(JA3Buffer *);
#endif /* __UTIL_JA3_H__ */

@ -779,6 +779,9 @@ app-layer:
detection-ports:
dp: 443
# Generate JA3 fingerprint from client hello
ja3-fingerprints: no
# Completely stop processing TLS/SSL session after the handshake
# completed. If bypass is enabled this will also trigger flow
# bypass. If disabled (the default), TLS/SSL session is still

Loading…
Cancel
Save