Hello Emeric, On Tue, May 22, 2018 at 05:37:58PM +0200, Emeric Brun wrote: > Hi Auréline > > I see that you're using the domain to known the certificate to delete. > > If you take a look to crt-list, you will see that the identifier of the > certificate > is customizable and is not necessarily the domain. > > I think to perform the adds/delete operation we should use the same > identifiers than the crt-list option >
Right, I forgot about these. It's now possible to use one of the identifiers (if any) of a certificate from a crt-list in the deletion. I added a refcount in the struct sni_ctx, added wildcard support that I forgot and corrected a typo in an error message. It's still RFC patches. -- Aurélien.
>From 9612ff9dcfda0611e5287ea0e9d9959d972e0061 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 6e773ddc07ba720c0999cc20b67b2a0745ba2614 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 | 4 + include/types/listener.h | 1 + include/types/ssl_sock.h | 6 + src/ssl_sock.c | 266 +++++++++++++++++++++++++++++++++++++++------ 5 files changed, 263 insertions(+), 34 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..b217243a0 100644 --- a/include/common/hathreads.h +++ b/include/common/hathreads.h @@ -297,6 +297,8 @@ enum lock_label { START_LOCK, TLSKEYS_REF_LOCK, PENDCONN_LOCK, + SSL_SNI_LOCK, + SSL_SNI_CTX_LOCK, LOCK_LABELS }; struct lock_stat { @@ -415,6 +417,8 @@ 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 SSL_SNI_CTX_LOCK: return "SSL_SNI_CTX_LOCK"; 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/include/types/ssl_sock.h b/include/types/ssl_sock.h index 8a5b3eeb8..63f1b0d64 100644 --- a/include/types/ssl_sock.h +++ b/include/types/ssl_sock.h @@ -32,12 +32,18 @@ struct pkey_info { uint16_t bits; /* key size in bits */ }; +struct sni_ctx_lock { + unsigned int ref_count; + __decl_hathreads(HA_SPINLOCK_T lock); /* spin lock to protect the refcount */ +}; + struct sni_ctx { SSL_CTX *ctx; /* context associated to the certificate */ int order; /* load order for the certificate */ uint8_t neg; /* reject if match */ struct pkey_info kinfo; /* pkey info */ struct ssl_bind_conf *conf; /* ssl "bind" conf for the certificate */ + struct sni_ctx_lock *ctx_lock; struct ebmb_node name; /* node holding the servername value */ }; diff --git a/src/ssl_sock.c b/src/ssl_sock.c index eb0d43ded..a89f651c2 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 */ @@ -2661,7 +2667,7 @@ end: #endif static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, struct ssl_bind_conf *conf, - struct pkey_info kinfo, char *name, int order) + struct pkey_info kinfo, char *name, int order, struct sni_ctx **parent) { struct sni_ctx *sc; int wild = 0, neg = 0; @@ -2688,31 +2694,54 @@ 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; sc->kinfo = kinfo; sc->order = order++; sc->neg = neg; + if (*parent) { + sc->ctx_lock = (*parent)->ctx_lock; + HA_SPIN_LOCK(SSL_SNI_CTX_LOCK, &(*parent)->ctx_lock->lock); + (*parent)->ctx_lock->ref_count++; + HA_SPIN_UNLOCK(SSL_SNI_CTX_LOCK, &(*parent)->ctx_lock->lock); + } + else { + sc->ctx_lock = malloc(sizeof(*sc->ctx_lock)); + if (!sc->ctx_lock) { + HA_RWLOCK_WRUNLOCK(SSL_SNI_LOCK, &s->sni_lock); + free(sc); + return order; + } + sc->ctx_lock->ref_count = 1; + HA_SPIN_INIT(&sc->ctx_lock->lock); + *parent = sc; + } if (kinfo.sig != TLSEXT_signature_anonymous) SSL_CTX_set_ex_data(ctx, ssl_pkey_info_index, &sc->kinfo); if (wild) 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; } @@ -2741,6 +2770,7 @@ struct cert_key_and_chain { struct key_combo_ctx { SSL_CTX *ctx; int order; + struct sni_ctx *parent; }; /* Map used for processing multiple keypairs for a single purpose @@ -3140,7 +3170,8 @@ static int ssl_sock_load_multi_cert(const char *path, struct bind_conf *bind_con /* Update SNI Tree */ key_combos[i-1].order = ssl_sock_add_cert_sni(cur_ctx, bind_conf, ssl_conf, - kinfo, str, key_combos[i-1].order); + kinfo, str, key_combos[i-1].order, + &key_combos[i-1].parent); node = ebmb_next(node); } @@ -3201,6 +3232,7 @@ static int ssl_sock_load_cert_chain(SSL_CTX *ctx, BIO *in, struct bind_conf *s, void *passwd_cb_userdata; EVP_PKEY *pkey; struct pkey_info kinfo = { .sig = TLSEXT_signature_anonymous, .bits = 0 }; + struct sni_ctx *parent = NULL; #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME STACK_OF(GENERAL_NAME) *names; @@ -3230,9 +3262,38 @@ 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); + order = ssl_sock_add_cert_sni(ctx, s, ssl_conf, kinfo, sni_filter[fcount], order, &parent); } else { #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME @@ -3242,7 +3303,7 @@ static int ssl_sock_load_cert_chain(SSL_CTX *ctx, BIO *in, struct bind_conf *s, GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i); if (name->type == GEN_DNS) { if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) { - order = ssl_sock_add_cert_sni(ctx, s, ssl_conf, kinfo, str, order); + order = ssl_sock_add_cert_sni(ctx, s, ssl_conf, kinfo, str, order, &parent); OPENSSL_free(str); } } @@ -3258,39 +3319,12 @@ static int ssl_sock_load_cert_chain(SSL_CTX *ctx, BIO *in, struct bind_conf *s, value = X509_NAME_ENTRY_get_data(entry); if (ASN1_STRING_to_UTF8((unsigned char **)&str, value) >= 0) { - order = ssl_sock_add_cert_sni(ctx, s, ssl_conf, kinfo, str, order); + order = ssl_sock_add_cert_sni(ctx, s, ssl_conf, kinfo, str, order, &parent); OPENSSL_free(str); } } } - 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 +4783,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 +4803,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 +4860,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 +8744,172 @@ 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 eb_root *root; + struct listener *l; + struct ebmb_node *node; + struct sni_ctx *sni; + + if (!bind_conf->is_ssl) + continue; + + if (*args[4] == '*') { + root = &bind_conf->sni_w_ctx; + args[4]++; + } + else + root = &bind_conf->sni_ctx; + + 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(root, 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); + goto end; + } + HA_SPIN_LOCK(SSL_SNI_CTX_LOCK, &sni->ctx_lock->lock); + sni->ctx_lock->ref_count--; + if (!sni->ctx_lock->ref_count) { + SSL_CTX_free(sni->ctx); + ssl_sock_free_ssl_conf(sni->conf); + free(sni->conf); + free(sni->ctx_lock); + } + else + HA_SPIN_UNLOCK(SSL_SNI_CTX_LOCK, &sni->ctx_lock->lock); + ebmb_delete(node); + 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 removed from any listener.\n"; + appctx->st0 = CLI_ST_PRINT; + } + +end: + + 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