From 243832fd8ba57ffce47203f92a385bb4a2261c3e Mon Sep 17 00:00:00 2001
From: Emmanuel Hocdet <manu@gandi.net>
Date: Thu, 24 Oct 2019 11:32:47 +0200
Subject: [PATCH 1/3] MINOR: ssl: deduplicate ca-file

Typically server line like:
'server-template srv 1-1000 *:443 ssl ca-file ca-certificates.crt'
load ca-certificates.crt 1000 times and stay duplicated in memory.
Same case for bind line: ca-file is loaded for each certificate.
Same 'ca-file' can be load one time only and stay deduplicated in
memory.

As a corollary, this will prevent file access for ca-file when
updating a certificate via CLI.
---
 include/common/openssl-compat.h | 14 ++++++++
 src/ssl_sock.c                  | 72 +++++++++++++++++++++++++++++++++++++++--
 2 files changed, 84 insertions(+), 2 deletions(-)

diff --git a/include/common/openssl-compat.h b/include/common/openssl-compat.h
index 00395d3e7..b25ca3bd4 100644
--- a/include/common/openssl-compat.h
+++ b/include/common/openssl-compat.h
@@ -136,6 +136,20 @@ static inline STACK_OF(X509) *X509_chain_up_ref(STACK_OF(X509) *chain)
 
 #endif
 
+#ifdef OPENSSL_IS_BORINGSSL
+/*
+ * Functions missing in BoringSSL
+ */
+
+static inline X509_CRL *X509_OBJECT_get0_X509_CRL(const X509_OBJECT *a)
+{
+    if (a == NULL || a->type != X509_LU_CRL) {
+        return NULL;
+    }
+    return a->data.crl;
+}
+#endif
+
 #if (HA_OPENSSL_VERSION_NUMBER < 0x1010000fL) && (LIBRESSL_VERSION_NUMBER < 0x2070000fL)
 /*
  * Functions introduced in OpenSSL 1.1.0 and in LibreSSL 2.7.0
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index a00c29eb7..e3cac652f 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -374,6 +374,74 @@ static struct {
 	char *path;
 } ckchs_transaction;
 
+/*
+ * deduplicate cafile
+ */
+struct cafile_entry {
+	X509_STORE *ca_store;
+	struct ebmb_node node;
+	char path[0];
+};
+
+static struct eb_root cafile_tree = EB_ROOT_UNIQUE;
+
+static int ssl_store_load_locations_file(X509_STORE **store_ptr, char *path)
+{
+	struct ebmb_node *eb;
+	struct cafile_entry *ca_e;
+
+	eb = ebst_lookup(&cafile_tree, path);
+	if (eb)
+		ca_e = ebmb_entry(eb, struct cafile_entry, node);
+	else {
+		X509_STORE *store = X509_STORE_new();
+		if (X509_STORE_load_locations(store, path, NULL)) {
+			int pathlen;
+			pathlen = strlen(path);
+			ca_e = calloc(1, sizeof(*ca_e) + pathlen + 1);
+			memcpy(ca_e->path, path, pathlen + 1);
+			ca_e->ca_store = store;
+			ebst_insert(&cafile_tree, &ca_e->node);
+		} else {
+			X509_STORE_free(store);
+			return 0;
+		}
+	}
+	*store_ptr = ca_e->ca_store;
+	return 1;
+}
+
+/* mimic what X509_STORE_load_locations do with store_ctx */
+static int ssl_load_cert_crl_file(X509_STORE *store_ctx, char *path)
+{
+	X509_STORE *store;
+	if (store_ctx && ssl_store_load_locations_file(&store, path)) {
+		int i;
+		X509_OBJECT *obj;
+		STACK_OF(X509_OBJECT) *objs = X509_STORE_get0_objects(store);
+		for (i = 0; i < sk_X509_OBJECT_num(objs); i++) {
+			obj = sk_X509_OBJECT_value(objs, i);
+			switch (X509_OBJECT_get_type(obj)) {
+			case X509_LU_X509:
+				X509_STORE_add_cert(store_ctx, X509_OBJECT_get0_X509(obj));
+				break;
+			case X509_LU_CRL:
+				X509_STORE_add_crl(store_ctx, X509_OBJECT_get0_X509_CRL(obj));
+				break;
+			}
+		}
+		return 1;
+	}
+	return 0;
+}
+
+/* SSL_CTX_load_verify_locations substitute, internaly call X509_STORE_load_locations*/
+static int ssl_load_verify_locations_file(SSL_CTX *ctx, char *path)
+{
+	X509_STORE *store_ctx = SSL_CTX_get_cert_store(ctx);
+	return ssl_load_cert_crl_file(store_ctx, path);
+}
+
 /* This memory pool is used for capturing clienthello parameters. */
 struct ssl_capture {
 	unsigned long long int xxh64;
@@ -4907,7 +4975,7 @@ int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, struct ssl_bind_conf *ssl_
 		char *crl_file = (ssl_conf && ssl_conf->crl_file) ? ssl_conf->crl_file : bind_conf->ssl_conf.crl_file;
 		if (ca_file) {
 			/* load CAfile to verify */
-			if (!SSL_CTX_load_verify_locations(ctx, ca_file, NULL)) {
+			if (!ssl_load_verify_locations_file(ctx, ca_file)) {
 				if (err)
 					memprintf(err, "%sProxy '%s': unable to load CA file '%s' for bind '%s' at [%s:%d].\n",
 					          *err ? *err : "", curproxy->id, ca_file, bind_conf->arg, bind_conf->file, bind_conf->line);
@@ -5412,7 +5480,7 @@ int ssl_sock_prepare_srv_ctx(struct server *srv)
 	if (verify & SSL_VERIFY_PEER) {
 		if (srv->ssl_ctx.ca_file) {
 			/* load CAfile to verify */
-			if (!SSL_CTX_load_verify_locations(srv->ssl_ctx.ctx, srv->ssl_ctx.ca_file, NULL)) {
+			if (!ssl_load_verify_locations_file(srv->ssl_ctx.ctx, srv->ssl_ctx.ca_file)) {
 				ha_alert("Proxy '%s', server '%s' [%s:%d] unable to load CA file '%s'.\n",
 					 curproxy->id, srv->id,
 					 srv->conf.file, srv->conf.line, srv->ssl_ctx.ca_file);
-- 
2.11.0

