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/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);