]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
app-layer-ssl: generate JA3 fingerprints
authorMats Klepsland <mats.klepsland@gmail.com>
Thu, 30 Nov 2017 10:04:03 +0000 (11:04 +0100)
committerVictor Julien <victor@inliniac.net>
Tue, 20 Mar 2018 15:27:22 +0000 (16:27 +0100)
Decode additional fields from the client hello packet and generate
JA3 fingerprints.

src/Makefile.am
src/app-layer-ssl.c
src/app-layer-ssl.h
src/util-error.c
src/util-error.h
src/util-ja3.c [new file with mode: 0644]
src/util-ja3.h [new file with mode: 0644]
suricata.yaml.in

index 0004126714b71b50e0ecc76fa26a91ce8b51c400..1a1ecac443c3974b0095792a48445bc2b643c57a 100644 (file)
@@ -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 \
index d4c36b272e53605d6da71c297b762cf3bb412e03..f848f61870eaec0fdc9856d60ae40e2bb85cb4d8 100644 (file)
@@ -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);
index 871d90d9764af05410328aaf107292fe42fd3a60..14c2c0a592aa8c1855cd94785b4d7beb6994d940 100644 (file)
@@ -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;
index 354c4f80c26737645a970d6dbd4e92a89bda684b..c40bef0e521837a6d880d4decf78806864b3e76f 100644 (file)
@@ -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);
     }
 
index d3fac31545cc70120921694619b4d8ee46c0ece6..4cd33fd727ea4f87b9fa786be396f9684e10d075 100644 (file)
@@ -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);
diff --git a/src/util-ja3.c b/src/util-ja3.c
new file mode 100644 (file)
index 0000000..d9b7efb
--- /dev/null
@@ -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 */
+
+}
diff --git a/src/util-ja3.h b/src/util-ja3.h
new file mode 100644 (file)
index 0000000..cae7216
--- /dev/null
@@ -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__ */
index 2f57336967e251e891b563181c008cf9da6c85a5..02333e98299b73a2cbc569293bff839484af4430 100644 (file)
@@ -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