]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
WIP/MINOR: ssl: add sample fetches for keylog in frontend
authorWilliam Lallemand <wlallemand@haproxy.com>
Mon, 6 Jul 2020 09:41:30 +0000 (11:41 +0200)
committerWilliam Lallemand <wlallemand@haproxy.org>
Mon, 6 Jul 2020 17:08:03 +0000 (19:08 +0200)
OpenSSL 1.1.1 provides a callback registering function
SSL_CTX_set_keylog_callback, which allows one to receive a string
containing the keys to deciphers TLSv1.3.

Unfortunately it is not possible to store this data in binary form and
we can only get this information using the callback. Which means that we
need to store it until the connection is closed.

This patches add 2 pools, the first one, pool_head_ssl_keylog is used to
store a struct ssl_keylog which will be inserted as a ex_data in a SSL *.
The second one is pool_head_ssl_keylog_str which will be used to store
the hexadecimal strings.

To enable the capture of the keys, you need to set "tune.ssl.keylog on"
in your configuration.

The following fetches were implemented:

ssl_fc_client_early_traffic_secret,
ssl_fc_client_handshake_traffic_secret,
ssl_fc_server_handshake_traffic_secret,
ssl_fc_client_traffic_secret_0,
ssl_fc_server_traffic_secret_0,
ssl_fc_exporter_secret,
ssl_fc_early_exporter_secret

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

index 6d472134eb0fbf18f83be01f8a6a1e36d31e9cbf..38fbbd48d56bf86bd551518efa19022541b3ec6f 100644 (file)
@@ -704,6 +704,7 @@ The following keywords are supported in the "global" section :
    - tune.sndbuf.client
    - tune.sndbuf.server
    - tune.ssl.cachesize
+   - tune.ssl.keylog
    - tune.ssl.lifetime
    - tune.ssl.force-private-cache
    - tune.ssl.maxrecord
@@ -2157,6 +2158,44 @@ tune.ssl.force-private-cache
   this case, adding a first layer of hash-based load balancing before the SSL
   layer might limit the impact of the lack of session sharing.
 
+tune.ssl.keylog { on | off }
+  This option activates the logging of the TLS keys. It should be used with
+  care as it will consume more memory per SSL session and could decrease
+  performances. This is disabled by default.
+
+  These sample fetches should be used to generate the SSLKEYLOGFILE that is
+  required to decipher traffic with wireshark.
+
+  https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
+
+  The SSLKEYLOG is a series of lines which are formatted this way:
+
+    <Label> <space> <ClientRandom> <space> <Secret>
+
+  The ClientRandom is provided by the %[ssl_fc_client_random,hex] sample
+  fetch, the secret and the Label could be find in the array below. You need
+  to generate a SSLKEYLOGFILE with all the labels in this array.
+
+  The following sample fetches are hexadecimal strings and does not need to be
+  converted.
+
+  SSLKEYLOGFILE Label             |  Sample fetches for the Secrets
+  --------------------------------|-----------------------------------------
+  CLIENT_EARLY_TRAFFIC_SECRET     |  %[ssl_fc_client_early_traffic_secret]
+  CLIENT_HANDSHAKE_TRAFFIC_SECRET |  %[ssl_fc_client_handshake_traffic_secret]
+  SERVER_HANDSHAKE_TRAFFIC_SECRET |  %[ssl_fc_server_handshake_traffic_secret]
+  CLIENT_TRAFFIC_SECRET_0         |  %[ssl_fc_client_traffic_secret_0]
+  SERVER_TRAFFIC_SECRET_0         |  %[ssl_fc_server_traffic_secret_0]
+  EARLY_EXPORTER_SECRET           |  %[ssl_fc_exporter_secret]
+  EXPORTER_SECRET                 |  %[ssl_fc_early_exporter_secret]
+
+  This is only available with OpenSSL 1.1.1, and useful with TLS1.3 session.
+
+  If you want to generate the content of a SSLKEYLOGFILE with TLS < 1.3, you
+  only need this line:
+
+  "CLIENT_RANDOM %[ssl_fc_client_random,hex] %[ssl_fc_session_key,hex]"
+
 tune.ssl.lifetime <timeout>
   Sets how long a cached SSL session may remain valid. This time is expressed
   in seconds and defaults to 300 (5 min). It is important to understand that it
@@ -17020,6 +17059,51 @@ ssl_fc_client_random : binary
   was made over an SSL/TLS transport layer. It is useful to to decrypt traffic
   sent using ephemeral ciphers. This requires OpenSSL >= 1.1.0, or BoringSSL.
 
+ssl_fc_client_early_traffic_secret : string
+  Return the CLIENT_EARLY_TRAFFIC_SECRET as an hexadecimal string for the
+  front connection when the incoming connection was made over a TLS 1.3
+  transport layer.
+  Require OpenSSL >= 1.1.1. This is one of the keys dumped by the OpenSSL
+  keylog callback to generate the SSLKEYLOGFILE. The SSL Key logging must be
+  activated with "tune.ssl.keylog on" in the global section. See also
+  "tune.ssl.keylog"
+
+ssl_fc_client_handshake_traffic_secret : string
+  Return the CLIENT_HANDSHAKE_TRAFFIC_SECRET as an hexadecimal string for the
+  front connection when the incoming connection was made over a TLS 1.3
+  transport layer.
+  Require OpenSSL >= 1.1.1. This is one of the keys dumped by the OpenSSL
+  keylog callback to generate the SSLKEYLOGFILE. The SSL Key logging must be
+  activated with "tune.ssl.keylog on" in the global section. See also
+  "tune.ssl.keylog"
+
+ssl_fc_client_traffic_secret_0 : string
+  Return the CLIENT_TRAFFIC_SECRET_0 as an hexadecimal string for the
+  front connection when the incoming connection was made over a TLS 1.3
+  transport layer.
+  Require OpenSSL >= 1.1.1. This is one of the keys dumped by the OpenSSL
+  keylog callback to generate the SSLKEYLOGFILE. The SSL Key logging must be
+  activated with "tune.ssl.keylog on" in the global section. See also
+  "tune.ssl.keylog"
+
+ssl_fc_exporter_secret : string
+  Return the EXPORTER_SECRET as an hexadecimal string for the
+  front connection when the incoming connection was made over a TLS 1.3
+  transport layer.
+  Require OpenSSL >= 1.1.1. This is one of the keys dumped by the OpenSSL
+  keylog callback to generate the SSLKEYLOGFILE. The SSL Key logging must be
+  activated with "tune.ssl.keylog on" in the global section. See also
+  "tune.ssl.keylog"
+
+ssl_fc_early_exporter_secret : string
+  Return the EARLY_EXPORTER_SECRET as an hexadecimal string for the
+  front connection when the incoming connection was made over an TLS 1.3
+  transport layer.
+  Require OpenSSL >= 1.1.1. This is one of the keys dumped by the OpenSSL
+  keylog callback to generate the SSLKEYLOGFILE. The SSL Key logging must be
+  activated with "tune.ssl.keylog on" in the global section. See also
+  "tune.ssl.keylog"
+
 ssl_fc_has_crt : boolean
   Returns true if a client certificate is present in an incoming connection over
   SSL/TLS transport layer. Useful if 'verify' statement is set to 'optional'.
@@ -17064,6 +17148,24 @@ ssl_fc_unique_id : binary
   returns the TLS unique ID as defined in RFC5929 section 3. The unique id
   can be encoded to base64 using the converter: "ssl_bc_unique_id,base64".
 
+ssl_fc_server_handshake_traffic_secret : string
+  Return the SERVER_HANDSHAKE_TRAFFIC_SECRET as an hexadecimal string for the
+  front connection when the incoming connection was made over a TLS 1.3
+  transport layer.
+  Require OpenSSL >= 1.1.1. This is one of the keys dumped by the OpenSSL
+  keylog callback to generate the SSLKEYLOGFILE. The SSL Key logging must be
+  activated with "tune.ssl.keylog on" in the global section. See also
+  "tune.ssl.keylog"
+
+ssl_fc_server_traffic_secret_0 : string
+  Return the SERVER_TRAFFIC_SECRET_0 as an hexadecimal string for the
+  front connection when the incoming connection was made over an TLS 1.3
+  transport layer.
+  Require OpenSSL >= 1.1.1. This is one of the keys dumped by the OpenSSL
+  keylog callback to generate the SSLKEYLOGFILE. The SSL Key logging must be
+  activated with "tune.ssl.keylog on" in the global section. See also
+  "tune.ssl.keylog"
+
 ssl_fc_server_random : binary
   Returns the server random of the front connection when the incoming connection
   was made over an SSL/TLS transport layer. It is useful to to decrypt traffic
index cc7a7aad6762457efc1f45795cf5bab50368ee27..5db950b368f432eecea5b0c3ccc33d6c24c4cb0b 100644 (file)
@@ -226,6 +226,26 @@ struct ssl_capture {
        char ciphersuite[0];
 };
 
+#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
+#define SSL_KEYLOG_MAX_SECRET_SIZE 129
+
+struct ssl_keylog {
+       /*
+        * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
+        */
+       char *client_random;
+
+       /* TLS 1.3 */
+       char *client_early_traffic_secret;
+       char *client_handshake_traffic_secret;
+       char *server_handshake_traffic_secret;
+       char *client_traffic_secret_0;
+       char *server_traffic_secret_0;
+       char *exporter_secret;
+       char *early_exporter_secret;
+};
+#endif
+
 struct ssl_sock_ctx {
        struct connection *conn;
        SSL *ssl;
@@ -268,6 +288,7 @@ struct global_ssl {
        unsigned int default_dh_param; /* SSL maximum DH parameter size */
        int ctx_cache; /* max number of entries in the ssl_ctx cache. */
        int capture_cipherlist; /* Size of the cipherlist buffer. */
+       int keylog; /* activate keylog  */
        int extra_files; /* which files not defined in the configuration file are we looking for */
 };
 
index 794ccf0d11a7caca536bcabf9628dde1a20c29c2..8af7edbf01b5a39e47eceb91e0f05ec3d17d6b27 100644 (file)
@@ -46,6 +46,9 @@ extern unsigned int openssl_engines_initialized;
 extern int nb_engines;
 extern struct xprt_ops ssl_sock;
 extern int ssl_capture_ptr_index;
+extern int ssl_keylog_index;
+extern struct pool_head *pool_head_ssl_keylog;
+extern struct pool_head *pool_head_ssl_keylog_str;
 
 int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, struct ssl_bind_conf *, SSL_CTX *ctx, char **err);
 int ssl_sock_prepare_all_ctx(struct bind_conf *bind_conf);
index 144cef882c383e40ac317c7e26c0d9e3201965e5..060be5ab42bae012a17c63dc184f97cdfb53f749 100644 (file)
@@ -317,6 +317,44 @@ static int ssl_parse_global_capture_cipherlist(char **args, int section_type, st
        return 0;
 }
 
+/* init the SSLKEYLOGFILE pool */
+#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
+static int ssl_parse_global_keylog(char **args, int section_type, struct proxy *curpx,
+                                       struct proxy *defpx, const char *file, int line,
+                                       char **err)
+{
+
+       if (too_many_args(1, args, err, NULL))
+               return -1;
+
+       if (strcmp(args[1], "on") == 0)
+               global_ssl.keylog = 1;
+       else if (strcmp(args[1], "off") == 0)
+               global_ssl.keylog = 0;
+       else {
+               memprintf(err, "'%s' expects either 'on' or 'off' but got '%s'.", args[0], args[1]);
+               return -1;
+       }
+
+       if (pool_head_ssl_keylog) /* already configured */
+               return 0;
+
+       pool_head_ssl_keylog = create_pool("ssl-keylogfile", sizeof(struct ssl_keylog), MEM_F_SHARED);
+       if (!pool_head_ssl_keylog) {
+               memprintf(err, "Out of memory error.");
+               return -1;
+       }
+
+       pool_head_ssl_keylog_str = create_pool("ssl-keylogfile-str", sizeof(char) * SSL_KEYLOG_MAX_SECRET_SIZE, MEM_F_SHARED);
+       if (!pool_head_ssl_keylog_str) {
+               memprintf(err, "Out of memory error.");
+               return -1;
+       }
+
+       return 0;
+}
+#endif
+
 /* parse "ssl.force-private-cache".
  * Returns <0 on alert, >0 on warning, 0 on success.
  */
@@ -1820,6 +1858,9 @@ static struct cfg_kw_list cfg_kws = {ILH, {
        { CFG_GLOBAL, "tune.ssl.maxrecord", ssl_parse_global_int },
        { CFG_GLOBAL, "tune.ssl.ssl-ctx-cache-size", ssl_parse_global_int },
        { CFG_GLOBAL, "tune.ssl.capture-cipherlist-size", ssl_parse_global_capture_cipherlist },
+#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
+       { CFG_GLOBAL, "tune.ssl.keylog", ssl_parse_global_keylog },
+#endif
        { CFG_GLOBAL, "ssl-default-bind-ciphers", ssl_parse_global_ciphers },
        { CFG_GLOBAL, "ssl-default-server-ciphers", ssl_parse_global_ciphers },
 #if ((HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL) || defined(LIBRESSL_VERSION_NUMBER))
index 4c7ecccdfd08bcf30ff491da35cec77b364a0356..843554ceb7d9513be8f4e1ce6dbcf5df3a2b5d36 100644 (file)
@@ -1108,6 +1108,63 @@ smp_fetch_ssl_fc_cl_xxh64(const struct arg *args, struct sample *smp, const char
        return 1;
 }
 
+/* Dump the SSL keylog, it only works with "tune.ssl.keylog 1" */
+#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
+static int smp_fetch_ssl_x_keylog(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+       struct connection *conn;
+       struct ssl_keylog *keylog;
+       SSL *ssl;
+       char *src = NULL;
+       const char *sfx;
+
+       conn = (kw[4] != 'b') ? objt_conn(smp->sess->origin) :
+              smp->strm ? cs_conn(objt_cs(smp->strm->si[1].end)) : NULL;
+
+       if (conn->flags & CO_FL_WAIT_XPRT) {
+               smp->flags |= SMP_F_MAY_CHANGE;
+               return 0;
+       }
+
+       ssl = ssl_sock_get_ssl_object(conn);
+       if (!ssl)
+               return 0;
+
+       keylog = SSL_get_ex_data(ssl, ssl_keylog_index);
+       if (!keylog)
+               return 0;
+
+       sfx = kw + strlen("ssl_xx_");
+
+       if (strcmp(sfx, "client_early_traffic_secret") == 0) {
+               src = keylog->client_early_traffic_secret;
+       } else if (strcmp(sfx, "client_handshake_traffic_secret") == 0) {
+               src = keylog->client_handshake_traffic_secret;
+       } else if (strcmp(sfx, "server_handshake_traffic_secret") == 0) {
+               src = keylog->server_handshake_traffic_secret;
+       } else if (strcmp(sfx, "client_traffic_secret_0") == 0) {
+               src = keylog->client_traffic_secret_0;
+       } else if (strcmp(sfx, "server_traffic_secret_0") == 0) {
+               src = keylog->server_traffic_secret_0;
+       } else if (strcmp(sfx, "exporter_secret") == 0) {
+               src = keylog->exporter_secret;
+       } else if (strcmp(sfx, "early_exporter_secret") == 0) {
+               src = keylog->early_exporter_secret;
+       }
+
+       if (!src || !*src)
+               return 0;
+
+       smp->data.u.str.area = src;
+       smp->data.type = SMP_T_STR;
+       smp->flags |= SMP_F_CONST;
+       smp->data.u.str.data = strlen(smp->data.u.str.area);
+       return 1;
+/*     log-format "CLIENT_RANDOM %[ssl_fc_client_random,hex] %[ssl_fc_session_key,hex]" */
+
+}
+#endif
+
 static int
 smp_fetch_ssl_fc_cl_str(const struct arg *args, struct sample *smp, const char *kw, void *private)
 {
@@ -1379,6 +1436,17 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
        { "ssl_fc_server_random",   smp_fetch_ssl_fc_random,      0,                   NULL,    SMP_T_BIN,  SMP_USE_L5CLI },
        { "ssl_fc_session_key",     smp_fetch_ssl_fc_session_key, 0,                   NULL,    SMP_T_BIN,  SMP_USE_L5CLI },
 #endif
+
+#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
+       { "ssl_fc_client_early_traffic_secret",     smp_fetch_ssl_x_keylog,       0,   NULL,    SMP_T_STR,  SMP_USE_L5CLI },
+       { "ssl_fc_client_handshake_traffic_secret", smp_fetch_ssl_x_keylog,       0,   NULL,    SMP_T_STR,  SMP_USE_L5CLI },
+       { "ssl_fc_server_handshake_traffic_secret", smp_fetch_ssl_x_keylog,       0,   NULL,    SMP_T_STR,  SMP_USE_L5CLI },
+       { "ssl_fc_client_traffic_secret_0",         smp_fetch_ssl_x_keylog,       0,   NULL,    SMP_T_STR,  SMP_USE_L5CLI },
+       { "ssl_fc_server_traffic_secret_0",         smp_fetch_ssl_x_keylog,       0,   NULL,    SMP_T_STR,  SMP_USE_L5CLI },
+       { "ssl_fc_exporter_secret",                 smp_fetch_ssl_x_keylog,       0,   NULL,    SMP_T_STR,  SMP_USE_L5CLI },
+       { "ssl_fc_early_exporter_secret",           smp_fetch_ssl_x_keylog,       0,   NULL,    SMP_T_STR,  SMP_USE_L5CLI },
+#endif
+
 #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
        { "ssl_fc_sni",             smp_fetch_ssl_fc_sni,         0,                   NULL,    SMP_T_STR,  SMP_USE_L5CLI },
 #endif
index 02967f65d42035038adc6372464d6972a33261cb..69f6835c4973eeb97cc9a3b181bd77d0f6604459 100644 (file)
@@ -129,6 +129,9 @@ struct global_ssl global_ssl = {
        .ctx_cache = DEFAULT_SSL_CTX_CACHE,
        .capture_cipherlist = 0,
        .extra_files = SSL_GF_ALL,
+#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
+       .keylog = 0
+#endif
 };
 
 static BIO_METHOD *ha_meth;
@@ -433,6 +436,12 @@ struct pool_head *pool_head_ssl_capture = NULL;
 int ssl_capture_ptr_index = -1;
 static int ssl_app_data_index = -1;
 
+#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
+int ssl_keylog_index = -1;
+struct pool_head *pool_head_ssl_keylog = NULL;
+struct pool_head *pool_head_ssl_keylog_str = NULL;
+#endif
+
 #if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
 struct list tlskeys_reference = LIST_HEAD_INIT(tlskeys_reference);
 #endif
@@ -505,6 +514,12 @@ static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int
                                        int content_type, const void *buf, size_t len,
                                        SSL *ssl);
 
+#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
+static void ssl_init_keylog(struct connection *conn, int write_p, int version,
+                            int content_type, const void *buf, size_t len,
+                            SSL *ssl);
+#endif
+
 /* List head of all registered SSL/TLS protocol message callbacks. */
 struct list ssl_sock_msg_callbacks = LIST_HEAD_INIT(ssl_sock_msg_callbacks);
 
@@ -544,6 +559,13 @@ static int ssl_sock_register_msg_callbacks(void)
                if (!ssl_sock_register_msg_callback(ssl_sock_parse_clienthello))
                        return ERR_ABORT;
        }
+#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
+       if (global_ssl.keylog > 0) {
+               if (!ssl_sock_register_msg_callback(ssl_init_keylog))
+                       return ERR_ABORT;
+       }
+#endif
+
        return 0;
 }
 
@@ -1680,6 +1702,30 @@ static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int
        SSL_set_ex_data(ssl, ssl_capture_ptr_index, capture);
 }
 
+
+#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
+static void ssl_init_keylog(struct connection *conn, int write_p, int version,
+                            int content_type, const void *buf, size_t len,
+                            SSL *ssl)
+{
+       struct ssl_keylog *keylog;
+
+       if (SSL_get_ex_data(ssl, ssl_keylog_index))
+               return;
+
+       keylog = pool_alloc(pool_head_ssl_keylog);
+       if (!keylog)
+               return;
+
+       memset(keylog, 0, sizeof(*keylog));
+
+       if (!SSL_set_ex_data(ssl, ssl_keylog_index, keylog)) {
+               pool_free(pool_head_ssl_keylog, keylog);
+               return;
+       }
+}
+#endif
+
 /* Callback is called for ssl protocol analyse */
 void ssl_sock_msgcbk(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *arg)
 {
@@ -4020,6 +4066,88 @@ void ssl_set_shctx(SSL_CTX *ctx)
        SSL_CTX_sess_set_remove_cb(ctx, sh_ssl_sess_remove_cb);
 }
 
+/*
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
+ *
+ * The format is:
+ * * <Label> <space> <ClientRandom> <space> <Secret>
+ * We only need to copy the secret as there is a sample fetch for the ClientRandom
+ */
+
+#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
+void SSL_CTX_keylog(const SSL *ssl, const char *line)
+{
+       struct ssl_keylog *keylog;
+       char *lastarg = NULL;
+       char *dst = NULL;
+
+       keylog = SSL_get_ex_data(ssl, ssl_keylog_index);
+       if (!keylog)
+               return;
+
+       lastarg = strrchr(line, ' ');
+       if (lastarg == NULL || ++lastarg == NULL)
+               return;
+
+       dst = pool_alloc(pool_head_ssl_keylog_str);
+       if (!dst)
+               return;
+
+       strncpy(dst, lastarg, SSL_KEYLOG_MAX_SECRET_SIZE-1);
+       dst[SSL_KEYLOG_MAX_SECRET_SIZE-1] = '\0';
+
+       if (strncmp(line, "CLIENT_RANDOM ", strlen("CLIENT RANDOM ")) == 0) {
+               if (keylog->client_random)
+                       goto error;
+               keylog->client_random = dst;
+
+       } else if (strncmp(line, "CLIENT_EARLY_TRAFFIC_SECRET ", strlen("CLIENT_EARLY_TRAFFIC_SECRET ")) == 0) {
+               if (keylog->client_early_traffic_secret)
+                       goto error;
+               keylog->client_early_traffic_secret = dst;
+
+       } else if (strncmp(line, "CLIENT_HANDSHAKE_TRAFFIC_SECRET ", strlen("CLIENT_HANDSHAKE_TRAFFIC_SECRET ")) == 0) {
+               if(keylog->client_handshake_traffic_secret)
+                       goto error;
+               keylog->client_handshake_traffic_secret = dst;
+
+       } else if (strncmp(line, "SERVER_HANDSHAKE_TRAFFIC_SECRET ", strlen("SERVER_HANDSHAKE_TRAFFIC_SECRET ")) == 0) {
+               if (keylog->server_handshake_traffic_secret)
+                       goto error;
+               keylog->server_handshake_traffic_secret = dst;
+
+       } else if (strncmp(line, "CLIENT_TRAFFIC_SECRET_0 ", strlen("CLIENT_TRAFFIC_SECRET_0 ")) == 0) {
+               if (keylog->client_traffic_secret_0)
+                       goto error;
+               keylog->client_traffic_secret_0 = dst;
+
+       } else if (strncmp(line, "SERVER_TRAFFIC_SECRET_0 ", strlen("SERVER_TRAFFIC_SECRET_0 ")) == 0) {
+               if (keylog->server_traffic_secret_0)
+                       goto error;
+               keylog->server_traffic_secret_0 = dst;
+
+       } else if (strncmp(line, "EARLY_EXPORTER_SECRET ", strlen("EARLY_EXPORTER_SECRET ")) == 0) {
+               if (keylog->early_exporter_secret)
+                       goto error;
+               keylog->early_exporter_secret = dst;
+
+       } else if (strncmp(line, "EXPORTER_SECRET ", strlen("EXPORTER_SECRET ")) == 0) {
+               if (keylog->exporter_secret)
+                       goto error;
+               keylog->exporter_secret = dst;
+       } else {
+               goto error;
+       }
+
+       return;
+
+error:
+       pool_free(pool_head_ssl_keylog_str, dst);
+
+       return;
+}
+#endif
+
 /*
  * This function applies the SSL configuration on a SSL_CTX
  * It returns an error code and fills the <err> buffer
@@ -4182,6 +4310,9 @@ int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, struct ssl_bind_conf *ssl_
 #if HA_OPENSSL_VERSION_NUMBER >= 0x00907000L
        SSL_CTX_set_msg_callback(ctx, ssl_sock_msgcbk);
 #endif
+#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
+       SSL_CTX_set_keylog_callback(ctx, SSL_CTX_keylog);
+#endif
 
 #if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
        ssl_conf_cur = NULL;
@@ -6591,6 +6722,29 @@ static void ssl_sock_capture_free_func(void *parent, void *ptr, CRYPTO_EX_DATA *
        pool_free(pool_head_ssl_capture, ptr);
 }
 
+#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
+static void ssl_sock_keylog_free_func(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx, long argl, void *argp)
+{
+       struct ssl_keylog *keylog;
+
+       if (!ptr)
+               return;
+
+       keylog = ptr;
+
+       pool_free(pool_head_ssl_keylog_str, keylog->client_random);
+       pool_free(pool_head_ssl_keylog_str, keylog->client_early_traffic_secret);
+       pool_free(pool_head_ssl_keylog_str, keylog->client_handshake_traffic_secret);
+       pool_free(pool_head_ssl_keylog_str, keylog->server_handshake_traffic_secret);
+       pool_free(pool_head_ssl_keylog_str, keylog->client_traffic_secret_0);
+       pool_free(pool_head_ssl_keylog_str, keylog->server_traffic_secret_0);
+       pool_free(pool_head_ssl_keylog_str, keylog->exporter_secret);
+       pool_free(pool_head_ssl_keylog_str, keylog->early_exporter_secret);
+
+       pool_free(pool_head_ssl_keylog, ptr);
+}
+#endif
+
 __attribute__((constructor))
 static void __ssl_sock_init(void)
 {
@@ -6630,6 +6784,9 @@ static void __ssl_sock_init(void)
 #endif
        ssl_app_data_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
        ssl_capture_ptr_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, ssl_sock_capture_free_func);
+#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
+       ssl_keylog_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, ssl_sock_keylog_free_func);
+#endif
 #ifndef OPENSSL_NO_ENGINE
        ENGINE_load_builtin_engines();
        hap_register_post_check(ssl_check_async_engine_count);