Hello,

On Wed, Apr 18, 2018 at 9:34 PM, Aurélien Nephtali
<aurelien.nepht...@gmail.com> wrote:
> Hello,
>
> I have some patches to support dynamically loading and unloading PEM
> certificates through the CLI. It is mainly a big refactoring of some
> part of the SSL code (thanks Thierry for your patches, we came to the
> same conclusion :) !).
>

Here is an updated version of this feature. The changes are:
    - Use a payload in the CLI to pass the certificate
    - Change the way to specify on which listener the certificate is
to be added/removed: using the "bind name".
      If the listeners are not named, the only way to update their
certificates is to do a global operation (using just the frontend name
in the command).

One thing that should be discussed is what will be the command syntax
when it will support more certificate options (OCSP, SCTL) ?
I thought about sending something like an .ini file:

[certificate]
aaaaa===

[ocsp]
bbbbb===

etc...

but one needs to prepare these files: it may not be very handy for a
one shot operation ?
Plus, without streaming we're quickly limited by the payload size with
the default value.

-- 
Aurélien Nephtali
From bfdd0e89ee79b996cd4a4b05afbde79e5919d8bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aur=C3=A9lien=20Nephtali?= <aurelien.nepht...@corp.ovh.com>
Date: Wed, 18 Apr 2018 15:34:33 +0200
Subject: [PATCH 1/2] MINOR/WIP: ssl: Refactor ssl_sock_load_cert_file() into
 ssl_sock_load_cert2()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

    - ssl_sock_get_dh_from_file() -> ssl_sock_get_dh()
    - ssl_sock_load_dh_params() takes a BIO * instead of a char *
    - ssl_sock_load_cert_chain_file() -> ssl_sock_load_cert_chain() + takes a
      BIO * instead of a char *

Signed-off-by: Aurélien Nephtali <aurelien.nepht...@corp.ovh.com>
---
 src/ssl_sock.c | 210 +++++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 136 insertions(+), 74 deletions(-)

diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 7a602ad57..eb0d43ded 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -2583,43 +2583,39 @@ static DH *ssl_get_tmp_dh(SSL *ssl, int export, int keylen)
 	return dh;
 }
 
-static DH * ssl_sock_get_dh_from_file(const char *filename)
+static DH * ssl_sock_get_dh(BIO *in)
 {
-	DH *dh = NULL;
-	BIO *in = BIO_new(BIO_s_file());
+	return PEM_read_bio_DHparams(in, NULL, NULL, NULL);
+}
 
-	if (in == NULL)
-		goto end;
+int ssl_sock_load_global_dh_param_from_file(const char *filename)
+{
+	BIO *in;
+	int ret = -1;
+
+	in = BIO_new(BIO_s_file());
+	if (!in)
+		return -1;
 
 	if (BIO_read_filename(in, filename) <= 0)
 		goto end;
 
-	dh = PEM_read_bio_DHparams(in, NULL, NULL, NULL);
+	global_dh = ssl_sock_get_dh(in);
+	if (global_dh)
+		ret = 0;
 
 end:
-        if (in)
-                BIO_free(in);
+	BIO_free(in);
 
-	return dh;
-}
-
-int ssl_sock_load_global_dh_param_from_file(const char *filename)
-{
-	global_dh = ssl_sock_get_dh_from_file(filename);
-
-	if (global_dh) {
-		return 0;
-	}
-
-	return -1;
+	return ret;
 }
 
 /* Loads Diffie-Hellman parameter from a file. Returns 1 if loaded, else -1
    if an error occured, and 0 if parameter not found. */
-int ssl_sock_load_dh_params(SSL_CTX *ctx, const char *file)
+int ssl_sock_load_dh_params(SSL_CTX *ctx, BIO *in)
 {
 	int ret = -1;
-	DH *dh = ssl_sock_get_dh_from_file(file);
+	DH *dh = ssl_sock_get_dh(in);
 
 	if (dh) {
 		ret = 1;
@@ -3192,10 +3188,9 @@ static int ssl_sock_load_multi_cert(const char *path, struct bind_conf *bind_con
 /* Loads a certificate key and CA chain from a file. Returns 0 on error, -1 if
  * an early error happens and the caller must call SSL_CTX_free() by itelf.
  */
-static int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct bind_conf *s,
-					 struct ssl_bind_conf *ssl_conf, char **sni_filter, int fcount)
+static int ssl_sock_load_cert_chain(SSL_CTX *ctx, BIO *in, struct bind_conf *s,
+				    struct ssl_bind_conf *ssl_conf, char **sni_filter, int fcount)
 {
-	BIO *in;
 	X509 *x = NULL, *ca;
 	int i, err;
 	int ret = -1;
@@ -3211,14 +3206,6 @@ static int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct
 	STACK_OF(GENERAL_NAME) *names;
 #endif
 
-	in = BIO_new(BIO_s_file());
-	if (in == NULL)
-		goto end;
-
-	if (BIO_read_filename(in, file) <= 0)
-		goto end;
-
-
 	passwd_cb = SSL_CTX_get_default_passwd_cb(ctx);
 	passwd_cb_userdata = SSL_CTX_get_default_passwd_cb_userdata(ctx);
 
@@ -3308,44 +3295,110 @@ end:
 	if (x)
 		X509_free(x);
 
-	if (in)
-		BIO_free(in);
-
 	return ret;
 }
 
-static int ssl_sock_load_cert_file(const char *path, struct bind_conf *bind_conf, struct ssl_bind_conf *ssl_conf,
-				   char **sni_filter, int fcount, char **err)
+struct ssl_input {
+    const char *cert_path;
+    struct chunk *cert_buf;
+};
+
+static int ssl_sock_load_cert2(struct ssl_input *si, struct bind_conf *bind_conf, struct ssl_bind_conf *ssl_conf,
+                               char **sni_filter, int fcount, char **err)
 {
 	int ret;
 	SSL_CTX *ctx;
+	BIO *in;
+
+	if (!si->cert_path && (!si->cert_buf || !si->cert_buf->len))
+		return 1;
 
 	ctx = SSL_CTX_new(SSLv23_server_method());
 	if (!ctx) {
-		memprintf(err, "%sunable to allocate SSL context for cert '%s'.\n",
-		          err && *err ? *err : "", path);
+		if (si->cert_path)
+			memprintf(err, "%sunable to allocate SSL context for cert '%s'.\n",
+				  err && *err ? *err : "", si->cert_path);
+		else
+			memprintf(err, "%sunable to allocate SSL context.\n",
+				  err && *err ? *err : "");
 		return 1;
 	}
 
-	if (SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM) <= 0) {
-		memprintf(err, "%sunable to load SSL private key from PEM file '%s'.\n",
-		          err && *err ? *err : "", path);
-		SSL_CTX_free(ctx);
-		return 1;
+	if (si->cert_path) {
+		if (SSL_CTX_use_PrivateKey_file(ctx, si->cert_path, SSL_FILETYPE_PEM) <= 0) {
+			memprintf(err, "%sunable to load SSL private key from PEM file '%s'.\n",
+				  err && *err ? *err : "", si->cert_path);
+			SSL_CTX_free(ctx);
+			return 1;
+		}
+
+		in = BIO_new(BIO_s_file());
+		if (!in) {
+			SSL_CTX_free(ctx);
+			return 1;
+		}
+
+		if (BIO_read_filename(in, si->cert_path) <= 0) {
+			SSL_CTX_free(ctx);
+			BIO_free(in);
+			return 1;
+		}
 	}
+	else {
+		EVP_PKEY *pkey;
+
+		in = BIO_new_mem_buf(si->cert_buf->str, si->cert_buf->len);
+		if (!in) {
+			memprintf(err, "%sunable to create a memory buffer.\n",
+				  err && *err ? *err : "");
+			SSL_CTX_free(ctx);
+			return 1;
+		}
 
-	ret = ssl_sock_load_cert_chain_file(ctx, path, bind_conf, ssl_conf, sni_filter, fcount);
+		pkey = PEM_read_bio_PrivateKey(in, NULL,
+					       SSL_CTX_get_default_passwd_cb(ctx),
+					       SSL_CTX_get_default_passwd_cb_userdata(ctx));
+		if (!pkey) {
+			memprintf(err, "%sunable to read the private key.\n",
+				  err && *err ? *err : "");
+			BIO_free(in);
+			SSL_CTX_free(ctx);
+			return 1;
+		}
+		ret = SSL_CTX_use_PrivateKey(ctx, pkey);
+		EVP_PKEY_free(pkey);
+		if (ret <= 0) {
+			memprintf(err, "%sunable to use the private key.\n",
+				  err && *err ? *err : "");
+			BIO_free(in);
+			SSL_CTX_free(ctx);
+			return 1;
+		}
+	}
+
+	BIO_reset(in);
+	ret = ssl_sock_load_cert_chain(ctx, in, bind_conf, ssl_conf, sni_filter, fcount);
 	if (ret <= 0) {
-		memprintf(err, "%sunable to load SSL certificate from PEM file '%s'.\n",
-		          err && *err ? *err : "", path);
+		if (si->cert_path)
+			memprintf(err, "%sunable to load SSL certificate from PEM file '%s'.\n",
+				  err && *err ? *err : "", si->cert_path);
+		else
+			memprintf(err, "%sunable to load SSL certificate.\n",
+				  err && *err ? *err : "");
+		BIO_free(in);
 		if (ret < 0) /* serious error, must do that ourselves */
 			SSL_CTX_free(ctx);
 		return 1;
 	}
 
 	if (SSL_CTX_check_private_key(ctx) <= 0) {
-		memprintf(err, "%sinconsistencies between private key and certificate loaded from PEM file '%s'.\n",
-		          err && *err ? *err : "", path);
+		if (si->cert_path)
+			memprintf(err, "%sinconsistencies between private key and certificate loaded from PEM file '%s'.\n",
+				  err && *err ? *err : "", si->cert_path);
+		else
+			memprintf(err, "%sinconsistencies between private key and certificate.\n",
+				  err && *err ? *err : "");
+		BIO_free(in);
 		return 1;
 	}
 
@@ -3359,38 +3412,47 @@ static int ssl_sock_load_cert_file(const char *path, struct bind_conf *bind_conf
 		SSL_CTX_set_ex_data(ctx, ssl_dh_ptr_index, NULL);
 	}
 
-	ret = ssl_sock_load_dh_params(ctx, path);
+	BIO_reset(in);
+	ret = ssl_sock_load_dh_params(ctx, in);
+	BIO_free(in);
 	if (ret < 0) {
-		if (err)
-			memprintf(err, "%sunable to load DH parameters from file '%s'.\n",
-				  *err ? *err : "", path);
+		if (err) {
+			if (si->cert_path)
+				memprintf(err, "%sunable to load DH parameters from file '%s'.\n",
+					  *err ? *err : "", si->cert_path);
+			else
+				memprintf(err, "%sunable to load DH parameters.\n",
+					  *err ? *err : "");
+		}
 		return 1;
 	}
 #endif
 
+	if (si->cert_path) {
 #if (defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP)
-	ret = ssl_sock_load_ocsp(ctx, path);
-	if (ret < 0) {
-		if (err)
-			memprintf(err, "%s '%s.ocsp' is present and activates OCSP but it is impossible to compute the OCSP certificate ID (maybe the issuer could not be found)'.\n",
-				  *err ? *err : "", path);
-		return 1;
-	}
+		ret = ssl_sock_load_ocsp(ctx, si->cert_path);
+		if (ret < 0) {
+			if (err)
+				memprintf(err, "%s '%s.ocsp' is present and activates OCSP but it is impossible to compute the OCSP certificate ID (maybe the issuer could not be found)'.\n",
+					  *err ? *err : "", si->cert_path);
+			return 1;
+		}
 #elif (defined OPENSSL_IS_BORINGSSL)
-	ssl_sock_set_ocsp_response_from_file(ctx, path);
+		ssl_sock_set_ocsp_response_from_file(ctx, si->cert_path);
 #endif
 
 #if (OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL && !defined LIBRESSL_VERSION_NUMBER)
-	if (sctl_ex_index >= 0) {
-		ret = ssl_sock_load_sctl(ctx, path);
-		if (ret < 0) {
-			if (err)
-				memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n",
-					  *err ? *err : "", path);
-			return 1;
+		if (sctl_ex_index >= 0) {
+			ret = ssl_sock_load_sctl(ctx, si->cert_path);
+			if (ret < 0) {
+				if (err)
+					memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n",
+						  *err ? *err : "", si->cert_path);
+				return 1;
+			}
 		}
-	}
 #endif
+	}
 
 #ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME
 	if (bind_conf->default_ctx) {
@@ -3424,7 +3486,7 @@ int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, char **err)
 	if (stat(path, &buf) == 0) {
 		dir = opendir(path);
 		if (!dir)
-			return ssl_sock_load_cert_file(path, bind_conf, NULL, NULL, 0, err);
+			return ssl_sock_load_cert2(&(struct ssl_input){ .cert_path = path }, bind_conf, NULL, NULL, 0, err);
 
 		/* strip trailing slashes, including first one */
 		for (end = path + strlen(path) - 1; end >= path && *end == '/'; end--)
@@ -3492,7 +3554,7 @@ int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, char **err)
 				}
 
 #endif
-				cfgerr += ssl_sock_load_cert_file(fp, bind_conf, NULL, NULL, 0, err);
+				cfgerr += ssl_sock_load_cert2(&(struct ssl_input){ .cert_path = fp }, bind_conf, NULL, NULL, 0, err);
 ignore_entry:
 				free(de);
 			}
@@ -3683,8 +3745,8 @@ int ssl_sock_load_cert_list_file(char *file, struct bind_conf *bind_conf, struct
 		}
 
 		if (stat(crt_path, &buf) == 0) {
-			cfgerr = ssl_sock_load_cert_file(crt_path, bind_conf, ssl_conf,
-							 &args[cur_arg], arg - cur_arg - 1, err);
+			cfgerr = ssl_sock_load_cert2(&(struct ssl_input){ .cert_path = crt_path }, bind_conf, ssl_conf,
+						     &args[cur_arg], arg - cur_arg - 1, err);
 		} else {
 			cfgerr = ssl_sock_load_multi_cert(crt_path, bind_conf, ssl_conf,
 							  &args[cur_arg], arg - cur_arg - 1, err);
-- 
2.11.0

From 112fc397da952dfe6211ed5762c06c416554cbee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aur=C3=A9lien=20Nephtali?= <aurelien.nepht...@corp.ovh.com>
Date: Wed, 18 Apr 2018 18:20:12 +0200
Subject: [PATCH 2/2] MINOR/WIP: ssl: Add "add ssl cert" and "del ssl cert"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Aurélien Nephtali <aurelien.nepht...@corp.ovh.com>
---
 doc/management.txt         |  20 ++++
 include/common/hathreads.h |   2 +
 include/types/listener.h   |   1 +
 src/ssl_sock.c             | 221 +++++++++++++++++++++++++++++++++++++++------
 4 files changed, 215 insertions(+), 29 deletions(-)

diff --git a/doc/management.txt b/doc/management.txt
index a2e8d8fc3..e724fdda7 100644
--- a/doc/management.txt
+++ b/doc/management.txt
@@ -1736,6 +1736,26 @@ set ssl tls-key <id> <tlskey>
   #<id> or <file> returned by "show tls-keys". <tlskey> is a base64 encoded 48
   bit TLS ticket key (ex. openssl rand -base64 48).
 
+add ssl cert <frontend[:bind_name]> <PEM certificate as payload>
+  Add a new SSL certificate to a frontend. If no bind_name is specified, the
+  certificate is added on all listeners. If per-listener certificates is to be
+  used, the listeners must be named (using the "name" option of the "bind"
+  line) in the configuration.
+
+  Example:
+    echo -e "add ssl cert fe:bind_foo <<\n$(cat cert.pem)\n" | socat /tmp/sock1 -
+
+del ssl cert <frontend[:bind_name]> <domain>
+  Delete an SSL certificate from a frontend. If no bind_name is specified, the
+  certificate is removed from all listeners. If per-listener certificates is to
+  be used, the listeners must be named (using the "name" option of the "bind"
+  line) in the configuration.
+
+  Example:
+    echo "del ssl cert fe:bind_foo example.com" | socat /tmp/sock1 -
+
+  The default certificate cannot be deleted.
+
 set table <table> key <key> [data.<data_type> <value>]*
   Create or update a stick-table entry in the table. If the key is not present,
   an entry is inserted. See stick-table in section 4.2 to find all possible
diff --git a/include/common/hathreads.h b/include/common/hathreads.h
index e27ecc63f..9af3ada18 100644
--- a/include/common/hathreads.h
+++ b/include/common/hathreads.h
@@ -297,6 +297,7 @@ enum lock_label {
 	START_LOCK,
 	TLSKEYS_REF_LOCK,
 	PENDCONN_LOCK,
+	SSL_SNI_LOCK,
 	LOCK_LABELS
 };
 struct lock_stat {
@@ -415,6 +416,7 @@ static inline const char *lock_label(enum lock_label label)
 	case START_LOCK:           return "START";
 	case TLSKEYS_REF_LOCK:     return "TLSKEYS_REF";
 	case PENDCONN_LOCK:        return "PENDCONN";
+	case SSL_SNI_LOCK:         return "SSL_SNI";
 	case LOCK_LABELS:          break; /* keep compiler happy */
 	};
 	/* only way to come here is consecutive to an internal bug */
diff --git a/include/types/listener.h b/include/types/listener.h
index c55569cd3..05e1d94d3 100644
--- a/include/types/listener.h
+++ b/include/types/listener.h
@@ -147,6 +147,7 @@ struct bind_conf {
 	int ssl_options;           /* ssl options */
 	struct eb_root sni_ctx;    /* sni_ctx tree of all known certs full-names sorted by name */
 	struct eb_root sni_w_ctx;  /* sni_ctx tree of all known certs wildcards sorted by name */
+	__decl_hathreads(HA_RWLOCK_T sni_lock); /* used to protect sni_ctx and sni_w_ctx */
 	struct tls_keys_ref *keys_ref; /* TLS ticket keys reference */
 
 	char *ca_sign_file;        /* CAFile used to generate and sign server certificates */
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index eb0d43ded..d39b31be3 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -2227,6 +2227,7 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
 	trash.str[i] = 0;
 
 	/* lookup in full qualified names */
+	HA_RWLOCK_RDLOCK(SSL_SNI_LOCK, &s->sni_lock);
 	node = ebst_lookup(&s->sni_ctx, trash.str);
 
 	/* lookup a not neg filter */
@@ -2294,8 +2295,10 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
 				if (conf->early_data)
 					allow_early = 1;
 			}
+		HA_RWLOCK_RDUNLOCK(SSL_SNI_LOCK, &s->sni_lock);
 			goto allow_early;
 	}
+	HA_RWLOCK_RDUNLOCK(SSL_SNI_LOCK, &s->sni_lock);
 #if (!defined SSL_NO_GENERATE_CERTIFICATES)
 	if (s->generate_certs && ssl_sock_generate_certificate(trash.str, s, ssl)) {
 		/* switch ctx done in ssl_sock_generate_certificate */
@@ -2363,6 +2366,7 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv)
 	trash.str[i] = 0;
 
 	/* lookup in full qualified names */
+	HA_RWLOCK_RDLOCK(SSL_SNI_LOCK, &s->sni_lock);
 	node = ebst_lookup(&s->sni_ctx, trash.str);
 
 	/* lookup a not neg filter */
@@ -2377,6 +2381,7 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv)
 		node = ebst_lookup(&s->sni_w_ctx, wildp);
 	}
 	if (!node || container_of(node, struct sni_ctx, name)->neg) {
+		HA_RWLOCK_RDUNLOCK(SSL_SNI_LOCK, &s->sni_lock);
 #if (!defined SSL_NO_GENERATE_CERTIFICATES)
 		if (s->generate_certs && ssl_sock_generate_certificate(servername, s, ssl)) {
 			/* switch ctx done in ssl_sock_generate_certificate */
@@ -2391,6 +2396,7 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv)
 
 	/* switch ctx */
 	ssl_sock_switchctx_set(ssl, container_of(node, struct sni_ctx, name)->ctx);
+	HA_RWLOCK_RDUNLOCK(SSL_SNI_LOCK, &s->sni_lock);
 	return SSL_TLSEXT_ERR_OK;
 }
 #endif /* (!) OPENSSL_IS_BORINGSSL */
@@ -2688,19 +2694,24 @@ static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, struct ssl_b
 		trash.str[j] = 0;
 
 		/* Check for duplicates. */
+		HA_RWLOCK_WRLOCK(SSL_SNI_LOCK, &s->sni_lock);
 		if (wild)
 			node = ebst_lookup(&s->sni_w_ctx, trash.str);
 		else
 			node = ebst_lookup(&s->sni_ctx, trash.str);
 		for (; node; node = ebmb_next_dup(node)) {
 			sc = ebmb_entry(node, struct sni_ctx, name);
-			if (sc->ctx == ctx && sc->conf == conf && sc->neg == neg)
+			if (sc->ctx == ctx && sc->conf == conf && sc->neg == neg) {
+				HA_RWLOCK_WRUNLOCK(SSL_SNI_LOCK, &s->sni_lock);
 				return order;
+			}
 		}
 
 		sc = malloc(sizeof(struct sni_ctx) + len + 1);
-		if (!sc)
+		if (!sc) {
+			HA_RWLOCK_WRUNLOCK(SSL_SNI_LOCK, &s->sni_lock);
 			return order;
+		}
 		memcpy(sc->name.key, trash.str, len + 1);
 		sc->ctx = ctx;
 		sc->conf = conf;
@@ -2713,6 +2724,7 @@ static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, struct ssl_b
 			ebst_insert(&s->sni_w_ctx, &sc->name);
 		else
 			ebst_insert(&s->sni_ctx, &sc->name);
+		HA_RWLOCK_WRUNLOCK(SSL_SNI_LOCK, &s->sni_lock);
 	}
 	return order;
 }
@@ -3230,6 +3242,35 @@ static int ssl_sock_load_cert_chain(SSL_CTX *ctx, BIO *in, struct bind_conf *s,
 		EVP_PKEY_free(pkey);
 	}
 
+	if (!SSL_CTX_use_certificate(ctx, x))
+		goto end;
+
+#ifdef SSL_CTX_clear_extra_chain_certs
+	SSL_CTX_clear_extra_chain_certs(ctx);
+#else
+	if (ctx->extra_certs != NULL) {
+		sk_X509_pop_free(ctx->extra_certs, X509_free);
+		ctx->extra_certs = NULL;
+	}
+#endif
+
+	while ((ca = PEM_read_bio_X509(in, NULL, passwd_cb, passwd_cb_userdata))) {
+		if (!SSL_CTX_add_extra_chain_cert(ctx, ca)) {
+			X509_free(ca);
+			goto end;
+		}
+	}
+
+	err = ERR_get_error();
+	if (!err || (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) {
+		/* we successfully reached the last cert in the file */
+		ret = 1;
+	}
+	ERR_clear_error();
+
+	if (!ret)
+		goto end;
+
 	if (fcount) {
 		while (fcount--)
 			order = ssl_sock_add_cert_sni(ctx, s, ssl_conf, kinfo, sni_filter[fcount], order);
@@ -3264,33 +3305,6 @@ static int ssl_sock_load_cert_chain(SSL_CTX *ctx, BIO *in, struct bind_conf *s,
 		}
 	}
 
-	ret = 0; /* the caller must not free the SSL_CTX argument anymore */
-	if (!SSL_CTX_use_certificate(ctx, x))
-		goto end;
-
-#ifdef SSL_CTX_clear_extra_chain_certs
-	SSL_CTX_clear_extra_chain_certs(ctx);
-#else
-	if (ctx->extra_certs != NULL) {
-		sk_X509_pop_free(ctx->extra_certs, X509_free);
-		ctx->extra_certs = NULL;
-	}
-#endif
-
-	while ((ca = PEM_read_bio_X509(in, NULL, passwd_cb, passwd_cb_userdata))) {
-		if (!SSL_CTX_add_extra_chain_cert(ctx, ca)) {
-			X509_free(ca);
-			goto end;
-		}
-	}
-
-	err = ERR_get_error();
-	if (!err || (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) {
-		/* we successfully reached the last cert in the file */
-		ret = 1;
-	}
-	ERR_clear_error();
-
 end:
 	if (x)
 		X509_free(x);
@@ -4749,6 +4763,7 @@ int ssl_sock_prepare_all_ctx(struct bind_conf *bind_conf)
 	if (bind_conf->default_ctx)
 		err += ssl_sock_prepare_ctx(bind_conf, bind_conf->default_ssl_conf, bind_conf->default_ctx);
 
+	HA_RWLOCK_RDLOCK(SSL_SNI_LOCK, &bind_conf->sni_lock);
 	node = ebmb_first(&bind_conf->sni_ctx);
 	while (node) {
 		sni = ebmb_entry(node, struct sni_ctx, name);
@@ -4768,6 +4783,7 @@ int ssl_sock_prepare_all_ctx(struct bind_conf *bind_conf)
 			err += ssl_sock_prepare_ctx(bind_conf, sni->conf, sni->ctx);
 		node = ebmb_next(node);
 	}
+	HA_RWLOCK_RDUNLOCK(SSL_SNI_LOCK, &bind_conf->sni_lock);
 	return err;
 }
 
@@ -4824,6 +4840,8 @@ int ssl_sock_prepare_bind_conf(struct bind_conf *bind_conf)
 	/* initialize CA variables if the certificates generation is enabled */
 	err += ssl_sock_load_ca(bind_conf);
 
+	HA_RWLOCK_INIT(&bind_conf->sni_lock);
+
 	return -err;
 }
 
@@ -8706,12 +8724,157 @@ static int cli_parse_set_ocspresponse(char **args, char *payload, struct appctx
 
 }
 
+static int cli_parse_add_ssl_cert(char **args, char *payload, struct appctx *appctx, void *private)
+{
+       char *p;
+       const char *bind_name = NULL;
+       struct proxy *px;
+       struct bind_conf *bind_conf;
+       struct chunk c;
+       int changes = 0;
+
+       if (!*args[3] || !payload) {
+               appctx->ctx.cli.severity = LOG_ERR;
+               appctx->ctx.cli.msg = "'add ssl cert' expects a frontend and a certificate (as a payload)\n";
+               appctx->st0 = CLI_ST_PRINT;
+               return 1;
+       }
+       p = strrchr(args[3], ':');
+       if (p) {
+	       *p = 0;
+	       bind_name = p + 1;
+       }
+       px = cli_find_frontend(appctx, args[3]);
+       if (!px) {
+               appctx->ctx.cli.severity = LOG_ERR;
+               appctx->ctx.cli.msg = "Couldn't find frontend.\n";
+               appctx->st0 = CLI_ST_PRINT;
+               return 1;
+       }
+
+       c.str = payload;
+       c.len = strlen(payload);
+
+       list_for_each_entry(bind_conf, &px->conf.bind, by_fe) {
+               char *err = NULL;
+               struct listener *l;
+
+               if (!bind_conf->is_ssl)
+                       continue;
+
+	       list_for_each_entry(l, &bind_conf->listeners, by_bind) {
+		       if (bind_name && (!l->name || strcmp(l->name, bind_name)))
+			       continue;
+
+		       if (ssl_sock_load_cert2(&(struct ssl_input){ .cert_buf = &c }, bind_conf, NULL, NULL, 0, &err)) {
+			       if (err) {
+				       appctx->ctx.cli.err = err;
+				       appctx->st0 = CLI_ST_PRINT_FREE;
+			       }
+			       else {
+				       appctx->ctx.cli.severity = LOG_ERR;
+				       appctx->ctx.cli.msg = "An error occured while loading the certificate.\n";
+				       appctx->st0 = CLI_ST_PRINT;
+			       }
+		       }
+		       changes++;
+
+		       break;
+	       }
+       }
+
+       if (!changes) {
+	       appctx->ctx.cli.severity = LOG_ERR;
+	       appctx->ctx.cli.msg = "The certificate was not added to any listener.\n";
+	       appctx->st0 = CLI_ST_PRINT;
+       }
+
+       return 1;
+}
+
+static int cli_parse_del_ssl_cert(char **args, char *payload, struct appctx *appctx, void *private)
+{
+	char *p;
+	const char *bind_name = NULL;
+	struct proxy *px;
+	struct bind_conf *bind_conf;
+	int changes = 0;
+
+	if (!*args[4]) {
+		appctx->ctx.cli.severity = LOG_ERR;
+		appctx->ctx.cli.msg = "'del ssl cert' expects a frontend and a domain.\n";
+		appctx->st0 = CLI_ST_PRINT;
+		return 1;
+	}
+	p = strrchr(args[3], ':');
+	if (p) {
+		*p = 0;
+		bind_name = p + 1;
+	}
+	px = cli_find_frontend(appctx, args[3]);
+	if (!px) {
+		appctx->ctx.cli.severity = LOG_ERR;
+		appctx->ctx.cli.msg = "Couldn't find frontend.\n";
+		appctx->st0 = CLI_ST_PRINT;
+		return 1;
+	}
+	list_for_each_entry(bind_conf, &px->conf.bind, by_fe) {
+		struct listener *l;
+		struct ebmb_node *node;
+		struct sni_ctx *sni;
+
+		if (!bind_conf->is_ssl)
+			continue;
+
+	       list_for_each_entry(l, &bind_conf->listeners, by_bind) {
+		       if (bind_name && (!l->name || strcmp(l->name, bind_name)))
+			       continue;
+
+		       HA_RWLOCK_WRLOCK(SSL_SNI_LOCK, &bind_conf->sni_lock);
+		       node = ebst_lookup(&bind_conf->sni_ctx, args[4]);
+		       if (!node) {
+			       HA_RWLOCK_WRUNLOCK(SSL_SNI_LOCK, &bind_conf->sni_lock);
+			       continue;
+		       }
+		       sni = ebmb_entry(node, struct sni_ctx, name);
+		       if (sni->ctx == bind_conf->default_ctx) {
+			       appctx->ctx.cli.severity = LOG_ERR;
+			       appctx->ctx.cli.msg = "Default certificate can't be deleted.\n";
+			       appctx->st0 = CLI_ST_PRINT;
+			       HA_RWLOCK_WRUNLOCK(SSL_SNI_LOCK, &bind_conf->sni_lock);
+			       break;
+		       }
+		       ebmb_delete(node);
+		       if (!sni->order) {
+			       SSL_CTX_free(sni->ctx);
+			       ssl_sock_free_ssl_conf(sni->conf);
+			       free(sni->conf);
+		       }
+		       free(sni);
+		       HA_RWLOCK_WRUNLOCK(SSL_SNI_LOCK, &bind_conf->sni_lock);
+		       changes++;
+
+		       break;
+	       }
+	}
+
+	if (!changes) {
+	       appctx->ctx.cli.severity = LOG_ERR;
+	       appctx->ctx.cli.msg = "The certificate was not added to any listener.\n";
+	       appctx->st0 = CLI_ST_PRINT;
+	}
+
+	return 1;
+}
+
 /* register cli keywords */
 static struct cli_kw_list cli_kws = {{ },{
 #if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
 	{ { "show", "tls-keys", NULL }, "show tls-keys [id|*]: show tls keys references or dump tls ticket keys when id specified", cli_parse_show_tlskeys, NULL },
 	{ { "set", "ssl", "tls-key", NULL }, "set ssl tls-key [id|keyfile] <tlskey>: set the next TLS key for the <id> or <keyfile> listener to <tlskey>", cli_parse_set_tlskeys, NULL },
 #endif
+	{ { "add", "ssl", "cert", NULL }, "add ssl cert <frontend[:luid]> <PEM certificate>: add an SSL certificate to a frontend", cli_parse_add_ssl_cert, NULL },
+	{ { "del", "ssl", "cert", NULL }, "del ssl cert <frontend[:luid]> <domain>: delete an SSL certificate from a frontend", cli_parse_del_ssl_cert, NULL },
 	{ { "set", "ssl", "ocsp-response", NULL }, NULL, cli_parse_set_ocspresponse, NULL },
 	{ { NULL }, NULL, NULL, NULL }
 }};
-- 
2.11.0

Reply via email to