From 41cccd0674becb213dc2b46e260674cb3c004f49 Mon Sep 17 00:00:00 2001
From: Emmanuel Hocdet <manu@gandi.net>
Date: Mon, 16 Dec 2019 16:39:17 +0100
Subject: [PATCH] MINOR: ssl: add "ca-root-file" directive

It's only available for bind line. "ca-root-file" allows to separate
CA root certificates from "ca-file". CA names sent to client is compute
from "ca-file". Typically, "ca-file" must be defined with intermediate
certificates and "ca-root-file" with root certificates.

Fix issue #404.
---
 doc/configuration.txt    | 20 ++++++++++++++------
 include/types/listener.h |  1 +
 src/ssl_sock.c           | 38 ++++++++++++++++++++++++++++++++------
 3 files changed, 47 insertions(+), 12 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index c31c34cb3..afeb6f5d6 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -712,8 +712,9 @@ The following keywords are supported in the "global" section :
 
 ca-base <dir>
   Assigns a default directory to fetch SSL CA certificates and CRLs from when a
-  relative path is used with "ca-file" or "crl-file" directives. Absolute
-  locations specified in "ca-file" and "crl-file" prevail and ignore "ca-base".
+  relative path is used with "ca-file", "ca-root-file" or "crl-file" directives.
+  Absolute locations specified in "ca-file", "ca-root-file" and "crl-file"
+  prevail and ignore "ca-base".
 
 chroot <jail dir>
   Changes current directory to <jail dir> and performs a chroot() there before
@@ -10751,6 +10752,13 @@ ca-file <cafile>
   designates a PEM file from which to load CA certificates used to verify
   client's certificate.
 
+ca-root-file <cafile>
+  This setting is only available when support for OpenSSL was built in. It
+  designates a PEM file from which to load CA certificates used to verify
+  client's certificate. It designates CA which must not be included in ca-names
+  sent to the client. Typically, "ca-file" must be defined with intermediate
+  certificates, and "ca-root-file" with root certificates.
+
 ca-ignore-err [all|<errorID>,...]
   This setting is only available when support for OpenSSL was built in.
   Sets a comma separated list of errorIDs to ignore during verify at depth > 0.
@@ -10915,10 +10923,10 @@ crt-list <file>
 
         <crtfile> [\[<sslbindconf> ...\]] [[!]<snifilter> ...]
 
-  sslbindconf support "npn", "alpn", "verify", "ca-file", "no-ca-names",
-  crl-file", "ecdhe", "curves", "ciphers" configuration. With BoringSSL
-  and Openssl >= 1.1.1 "ssl-min-ver" and "ssl-max-ver" are also supported.
-  It override the configuration set in bind line for the certificate.
+  sslbindconf support "npn", "alpn", "verify", "ca-file", "ca-root-file",
+  "no-ca-names", "crl-file", "ecdhe", "curves", "ciphers" configuration. With
+  BoringSSL and Openssl >= 1.1.1 "ssl-min-ver" and "ssl-max-ver" are also
+  supported. It override the configuration set in bind line for the certificate.
 
   Wildcards are supported in the SNI filter. Negative filter are also supported,
   only useful in combination with a wildcard filter to exclude a particular SNI.
diff --git a/include/types/listener.h b/include/types/listener.h
index 1098b42ce..edf102a3e 100644
--- a/include/types/listener.h
+++ b/include/types/listener.h
@@ -128,6 +128,7 @@ struct ssl_bind_conf {
 	int no_ca_names:1;         /* do not send ca names to clients (ca_file related) */
 	int early_data:1;          /* early data allowed */
 	char *ca_file;             /* CAfile to use on verify */
+	char *ca_root_file;        /* CA(root)file to use on verify */
 	char *crl_file;            /* CRLfile to use on verify */
 	char *ciphers;             /* cipher suite to use if non-null */
 #if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined OPENSSL_IS_BORINGSSL && !defined LIBRESSL_VERSION_NUMBER)
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index b9cf4d5e3..0de4fce2b 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -4427,6 +4427,8 @@ void ssl_sock_free_ssl_conf(struct ssl_bind_conf *conf)
 #endif
 		free(conf->ca_file);
 		conf->ca_file = NULL;
+		free(conf->ca_root_file);
+		conf->ca_root_file = NULL;
 		free(conf->crl_file);
 		conf->crl_file = NULL;
 		free(conf->ciphers);
@@ -5046,6 +5048,7 @@ int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, struct ssl_bind_conf *ssl_
 	SSL_CTX_set_verify(ctx, verify, ssl_sock_bind_verifycbk);
 	if (verify & SSL_VERIFY_PEER) {
 		char *ca_file = (ssl_conf && ssl_conf->ca_file) ? ssl_conf->ca_file : bind_conf->ssl_conf.ca_file;
+		char *ca_root_file = (ssl_conf && ssl_conf->ca_root_file) ? ssl_conf->ca_root_file : bind_conf->ssl_conf.ca_root_file;
 		char *crl_file = (ssl_conf && ssl_conf->crl_file) ? ssl_conf->crl_file : bind_conf->ssl_conf.crl_file;
 		if (ca_file) {
 			/* set CAfile to verify */
@@ -5054,6 +5057,11 @@ int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, struct ssl_bind_conf *ssl_
 				          err && *err ? *err : "", curproxy->id, ca_file, bind_conf->arg, bind_conf->file, bind_conf->line);
 				cfgerr |= ERR_ALERT | ERR_FATAL;
 			}
+			if (ca_root_file && !ssl_set_verify_locations_file(ctx, ca_root_file)) {
+				memprintf(err, "%sProxy '%s': unable to set CA-root file '%s' for bind '%s' at [%s:%d].\n",
+				          err && *err ? *err : "", curproxy->id, ca_root_file, bind_conf->arg, bind_conf->file, bind_conf->line);
+				cfgerr |= ERR_ALERT | ERR_FATAL;
+			}
 			if (!((ssl_conf && ssl_conf->no_ca_names) || bind_conf->ssl_conf.no_ca_names)) {
 				/* set CA names for client cert request, function returns void */
 				SSL_CTX_set_client_CA_list(ctx, SSL_dup_CA_list(ssl_get_client_ca_file(ca_file)));
@@ -8491,8 +8499,8 @@ smp_fetch_ssl_c_verify(const struct arg *args, struct sample *smp, const char *k
 	return 1;
 }
 
-/* parse the "ca-file" bind keyword */
-static int ssl_bind_parse_ca_file(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, char **err)
+/* for ca-file and ca-root-file */
+static int ssl_bind_parse_ca_file_common(char **args, int cur_arg, char **ca_file_p, char **err)
 {
 	if (!*args[cur_arg + 1]) {
 		memprintf(err, "'%s' : missing CAfile path", args[cur_arg]);
@@ -8500,21 +8508,37 @@ static int ssl_bind_parse_ca_file(char **args, int cur_arg, struct proxy *px, st
 	}
 
 	if ((*args[cur_arg + 1] != '/') && global_ssl.ca_base)
-		memprintf(&conf->ca_file, "%s/%s", global_ssl.ca_base, args[cur_arg + 1]);
+		memprintf(ca_file_p, "%s/%s", global_ssl.ca_base, args[cur_arg + 1]);
 	else
-		memprintf(&conf->ca_file, "%s", args[cur_arg + 1]);
+		memprintf(ca_file_p, "%s", args[cur_arg + 1]);
 
-	if (!ssl_store_load_locations_file(conf->ca_file)) {
-		memprintf(err, "'%s' : unable to load %s", args[cur_arg], conf->ca_file);
+	if (!ssl_store_load_locations_file(*ca_file_p)) {
+		memprintf(err, "'%s' : unable to load %s", args[cur_arg], *ca_file_p);
 		return ERR_ALERT | ERR_FATAL;
 	}
 	return 0;
 }
+
+/* parse the "ca-file" bind keyword */
+static int ssl_bind_parse_ca_file(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, char **err)
+{
+	return ssl_bind_parse_ca_file_common(args, cur_arg, &conf->ca_file, err);
+}
 static int bind_parse_ca_file(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
 {
 	return ssl_bind_parse_ca_file(args, cur_arg, px, &conf->ssl_conf, err);
 }
 
+/* parse the "ca-root-file" bind keyword */
+static int ssl_bind_parse_ca_root_file(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, char **err)
+{
+	return ssl_bind_parse_ca_file_common(args, cur_arg, &conf->ca_root_file, err);
+}
+static int bind_parse_ca_root_file(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
+{
+	return ssl_bind_parse_ca_root_file(args, cur_arg, px, &conf->ssl_conf, err);
+}
+
 /* parse the "ca-sign-file" bind keyword */
 static int bind_parse_ca_sign_file(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
 {
@@ -11208,6 +11232,7 @@ static struct ssl_bind_kw ssl_bind_kws[] = {
 	{ "allow-0rtt",            ssl_bind_parse_allow_0rtt,       0 }, /* allow 0-RTT */
 	{ "alpn",                  ssl_bind_parse_alpn,             1 }, /* set ALPN supported protocols */
 	{ "ca-file",               ssl_bind_parse_ca_file,          1 }, /* set CAfile to process verify on client cert */
+	{ "ca-root-file",          ssl_bind_parse_ca_root_file,     1 }, /* set CA(root)file to process verify on client cert */
 	{ "ciphers",               ssl_bind_parse_ciphers,          1 }, /* set SSL cipher suite */
 #if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
 	{ "ciphersuites",          ssl_bind_parse_ciphersuites,     1 }, /* set TLS 1.3 cipher suite */
@@ -11229,6 +11254,7 @@ static struct bind_kw_list bind_kws = { "SSL", { }, {
 	{ "allow-0rtt",            bind_parse_allow_0rtt,         0 }, /* Allow 0RTT */
 	{ "alpn",                  bind_parse_alpn,               1 }, /* set ALPN supported protocols */
 	{ "ca-file",               bind_parse_ca_file,            1 }, /* set CAfile to process verify on client cert */
+	{ "ca-root-file",          bind_parse_ca_root_file,       1 }, /* set CA(root)file to process verify on client cert */
 	{ "ca-ignore-err",         bind_parse_ignore_err,         1 }, /* set error IDs to ignore on verify depth > 0 */
 	{ "ca-sign-file",          bind_parse_ca_sign_file,       1 }, /* set CAFile used to generate and sign server certs */
 	{ "ca-sign-pass",          bind_parse_ca_sign_pass,       1 }, /* set CAKey passphrase */
-- 
2.11.0

