]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: ssl: Capture more info from Client Hello
authorMarcin Deranek <marcin.deranek@booking.com>
Mon, 12 Jul 2021 12:16:55 +0000 (14:16 +0200)
committerWilly Tarreau <w@1wt.eu>
Thu, 26 Aug 2021 17:48:33 +0000 (19:48 +0200)
When we set tune.ssl.capture-cipherlist-size to a non-zero value
we are able to capture cipherlist supported by the client. To be able to
provide JA3 compatible TLS fingerprinting we need to capture more
information from Client Hello message:
- SSL Version
- SSL Extensions
- Elliptic Curves
- Elliptic Curve Point Formats
This patch allows HAProxy to capture such information and store it for
later use.

doc/configuration.txt
include/haproxy/ssl_sock-t.h
src/ssl_sample.c
src/ssl_sock.c

index 9b794de1264033ef43ba5c7bfb3b14bae91016d1..9a8d97d0e74db85fa17da3ae7ef7fd1c53852557 100644 (file)
@@ -2806,9 +2806,10 @@ tune.ssl.ssl-ctx-cache-size <number>
   1000 entries.
 
 tune.ssl.capture-cipherlist-size <number>
-  Sets the maximum size of the buffer used for capturing client-hello cipher
-  list. If the value is 0 (default value) the capture is disabled, otherwise
-  a buffer is allocated for each SSL/TLS connection.
+  Sets the maximum size of the buffer used for capturing client hello cipher
+  list, extensions list, elliptic curves list and elliptic curve point
+  formats. If the value is 0 (default value) the capture is disabled,
+  otherwise a buffer is allocated for each SSL/TLS connection.
 
 tune.vars.global-max-size <size>
 tune.vars.proc-max-size <size>
index 5acedcfc5d336008d19607e82551467b47bac622..321d7b7ed0f6964bbeeb63a8f23690834dcb3d54 100644 (file)
@@ -201,9 +201,17 @@ struct ssl_sock_msg_callback {
 
 /* This memory pool is used for capturing clienthello parameters. */
 struct ssl_capture {
-       unsigned long long int xxh64;
-       unsigned char ciphersuite_len;
-       char ciphersuite[VAR_ARRAY];
+       ullong xxh64;
+       ushort protocol_version;
+       ushort ciphersuite_len;
+       ushort extensions_len;
+       ushort ec_len;
+       uint ciphersuite_offset;
+       uint extensions_offset;
+       uint ec_offset;
+       uint ec_formats_offset;
+       uchar ec_formats_len;
+       char data[VAR_ARRAY];
 };
 
 #ifdef HAVE_SSL_KEYLOG
index f4e0c93a814bcb2137db57e21ca91261f9b6bae8..9f041ad8e925c62f87dd110e1e8a9262e1212c75 100644 (file)
@@ -1145,7 +1145,7 @@ smp_fetch_ssl_fc_cl_bin(const struct arg *args, struct sample *smp, const char *
 
        smp->flags = SMP_F_VOL_TEST | SMP_F_CONST;
        smp->data.type = SMP_T_BIN;
-       smp->data.u.str.area = capture->ciphersuite;
+       smp->data.u.str.area = capture->data + capture->ciphersuite_offset;
        smp->data.u.str.data = capture->ciphersuite_len;
        return 1;
 }
index 83003d9d05c4a78c79ba5a0dc7f04f2429a61afb..b8914a78d2a56cbf0bd79cff61f800b90bca03ff 100644 (file)
@@ -1682,9 +1682,18 @@ static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int
                                        SSL *ssl)
 {
        struct ssl_capture *capture;
-       unsigned char *msg;
-       unsigned char *end;
-       size_t rec_len;
+       uchar *msg;
+       uchar *end;
+       uchar *extensions_end;
+       uchar *ec_start = NULL;
+       uchar *ec_formats_start = NULL;
+       uchar *list_end;
+       ushort protocol_version;
+       ushort extension_id;
+       ushort ec_len = 0;
+       uchar ec_formats_len = 0;
+       int offset = 0;
+       int rec_len;
 
        /* This function is called for "from client" and "to server"
         * connections. The combination of write_p == 0 and content_type == 22
@@ -1746,11 +1755,18 @@ static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int
        if (end < msg)
                return;
 
-       /* Expect 2 bytes for protocol version (1 byte for major and 1 byte
-        * for minor, the random, composed by 4 bytes for the unix time and
-        * 28 bytes for unix payload. So we jump 1 + 1 + 4 + 28.
+       /* Expect 2 bytes for protocol version
+        * (1 byte for major and 1 byte for minor)
         */
-       msg += 1 + 1 + 4 + 28;
+       if (msg + 2 > end)
+               return;
+       protocol_version = (msg[0] << 8) + msg[1];
+       msg += 2;
+
+       /* Expect the random, composed by 4 bytes for the unix time and
+        * 28 bytes for unix payload. So we jump 4 + 28.
+        */
+       msg += 4 + 28;
        if (msg > end)
                return;
 
@@ -1772,17 +1788,116 @@ static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int
        if (msg + rec_len > end || msg + rec_len < msg)
                return;
 
-       capture = pool_alloc(pool_head_ssl_capture);
+       capture = pool_zalloc(pool_head_ssl_capture);
        if (!capture)
                return;
        /* Compute the xxh64 of the ciphersuite. */
        capture->xxh64 = XXH64(msg, rec_len, 0);
 
        /* Capture the ciphersuite. */
-       capture->ciphersuite_len = (global_ssl.capture_cipherlist < rec_len) ?
-               global_ssl.capture_cipherlist : rec_len;
-       memcpy(capture->ciphersuite, msg, capture->ciphersuite_len);
+       capture->ciphersuite_len = MIN(global_ssl.capture_cipherlist, rec_len);
+       capture->ciphersuite_offset = 0;
+       memcpy(capture->data, msg, capture->ciphersuite_len);
+       msg += rec_len;
+       offset += capture->ciphersuite_len;
+
+       /* Initialize other data */
+       capture->protocol_version = protocol_version;
 
+       /* Next, compression methods:
+        * if present, we have to jump by length + 1 for the size information
+        * if not present, we have to jump by 1 only
+        */
+       if (msg[0] > 0)
+               msg += msg[0];
+       msg += 1;
+       if (msg > end)
+               goto store_capture;
+
+       /* We reached extensions */
+       if (msg + 2 > end)
+               goto store_capture;
+       rec_len = (msg[0] << 8) + msg[1];
+       msg += 2;
+       if (msg + rec_len > end || msg + rec_len < msg)
+               goto store_capture;
+       extensions_end = msg + rec_len;
+       capture->extensions_offset = offset;
+
+       /* Parse each extension */
+       while (msg + 4 < extensions_end) {
+               /* Add 2 bytes of extension_id */
+               if (global_ssl.capture_cipherlist >= offset + 2) {
+                       capture->data[offset++] = msg[0];
+                       capture->data[offset++] = msg[1];
+                       capture->extensions_len += 2;
+               }
+               else
+                       break;
+               extension_id = (msg[0] << 8) + msg[1];
+               /* Length of the extension */
+               rec_len = (msg[2] << 8) + msg[3];
+
+               /* Expect 2 bytes extension id + 2 bytes extension size */
+               msg += 2 + 2;
+               if (msg + rec_len > extensions_end || msg + rec_len < msg)
+                       goto store_capture;
+               /* TLS Extensions
+                * https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */
+               if (extension_id == 0x000a) {
+                       /* Elliptic Curves:
+                        * https://www.rfc-editor.org/rfc/rfc8422.html
+                        * https://www.rfc-editor.org/rfc/rfc7919.html */
+                       list_end = msg + rec_len;
+                       if (msg + 2 > list_end)
+                               goto store_capture;
+                       rec_len = (msg[0] << 8) + msg[1];
+                       msg += 2;
+
+                       if (msg + rec_len > list_end || msg + rec_len < msg)
+                               goto store_capture;
+                       /* Store location/size of the list */
+                       ec_start = msg;
+                       ec_len = rec_len;
+               }
+               else if (extension_id == 0x000b) {
+                       /* Elliptic Curves Point Formats:
+                        * https://www.rfc-editor.org/rfc/rfc8422.html */
+                       list_end = msg + rec_len;
+                       if (msg + 1 > list_end)
+                               goto store_capture;
+                       rec_len = msg[0];
+                       msg += 1;
+
+                       if (msg + rec_len > list_end || msg + rec_len < msg)
+                               goto store_capture;
+                       /* Store location/size of the list */
+                       ec_formats_start = msg;
+                       ec_formats_len = rec_len;
+               }
+               msg += rec_len;
+       }
+
+       if (ec_start) {
+               rec_len = ec_len;
+               if (offset + rec_len > global_ssl.capture_cipherlist)
+                        rec_len = global_ssl.capture_cipherlist - offset;
+               memcpy(capture->data + offset, ec_start, rec_len);
+               capture->ec_offset = offset;
+               capture->ec_len = rec_len;
+               offset += rec_len;
+       }
+       if (ec_formats_start) {
+               rec_len = ec_formats_len;
+               if (offset + rec_len > global_ssl.capture_cipherlist)
+                       rec_len = global_ssl.capture_cipherlist - offset;
+               memcpy(capture->data + offset, ec_formats_start, rec_len);
+               capture->ec_formats_offset = offset;
+               capture->ec_formats_len = rec_len;
+               offset += rec_len;
+       }
+
+ store_capture:
        SSL_set_ex_data(ssl, ssl_capture_ptr_index, capture);
 }