#include "util-print.h"
#include "util-pool.h"
#include "util-byte.h"
+#include "util-ja3.h"
#include "flow-util.h"
#include "flow-private.h"
/* 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;
#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)
{
}
}
-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;
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;
}
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) {
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);
--- /dev/null
+/* 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 */
+
+}