WIP/MINOR: ssl: add sample fetches for keylog in frontend

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
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 6d47213..38fbbd4 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -704,6 +704,7 @@
    - 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 @@
   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 @@
   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 @@
   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
diff --git a/include/haproxy/ssl_sock-t.h b/include/haproxy/ssl_sock-t.h
index cc7a7aa..5db950b 100644
--- a/include/haproxy/ssl_sock-t.h
+++ b/include/haproxy/ssl_sock-t.h
@@ -226,6 +226,26 @@
 	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 @@
 	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 */
 };
 
diff --git a/include/haproxy/ssl_sock.h b/include/haproxy/ssl_sock.h
index 794ccf0..8af7edb 100644
--- a/include/haproxy/ssl_sock.h
+++ b/include/haproxy/ssl_sock.h
@@ -46,6 +46,9 @@
 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);
diff --git a/src/cfgparse-ssl.c b/src/cfgparse-ssl.c
index 144cef8..060be5a 100644
--- a/src/cfgparse-ssl.c
+++ b/src/cfgparse-ssl.c
@@ -317,6 +317,44 @@
 	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 @@
 	{ 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))
diff --git a/src/ssl_sample.c b/src/ssl_sample.c
index 4c7eccc..843554c 100644
--- a/src/ssl_sample.c
+++ b/src/ssl_sample.c
@@ -1108,6 +1108,63 @@
 	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 @@
 	{ "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
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 02967f6..69f6835 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -129,6 +129,9 @@
 	.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 @@
 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 @@
                                        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 @@
 		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;
 }
 
@@ -1679,6 +1701,30 @@
 
 	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)
@@ -4019,6 +4065,88 @@
 	SSL_CTX_sess_set_get_cb(ctx, sh_ssl_sess_get_cb);
 	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
@@ -4182,6 +4310,9 @@
 #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;
@@ -6590,6 +6721,29 @@
 {
 	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 @@
 #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);