From: Shimi Gersner <sgers...@microsoft.com> haproxy supports generating SSL certificates based on SNI using a provided CA signing certificate. Because CA certificates may be signed by multiple CAs, in some scenarios, it is neccesary for the server to attach the trust chain in addition to the generated certificate.
The following patch adds the ability to optionally serve all public certificates provided in the `ca-sign-file` PEM file. Certificate loading was ported to use `ca_sign_use_chain` structure, instead of directly reading public/private keys. --- doc/configuration.txt | 8 +++ include/haproxy/listener-t.h | 4 +- src/cfgparse-ssl.c | 15 ++++++ src/ssl_sock.c | 100 ++++++++++++++++++++--------------- 4 files changed, 82 insertions(+), 45 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 6d472134e..1d3878bc1 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -12158,6 +12158,14 @@ ca-sign-pass <passphrase> the dynamic generation of certificates is enabled. See 'generate-certificates' for details. +ca-sign-use-chain + This setting is only available when support for OpenSSL was built in. It is + the CA private key passphrase. This setting is optional and used only when + the dynamic generation of certificates is enabled. See + 'generate-certificates' for details. + Enabling this flag will attach all public certificates encoded in `ca-sign-file` + to the served certificate to the client, enabling trust. + ca-verify-file <cafile> This setting designates a PEM file from which to load CA certificates used to verify client's certificate. It designates CA certificates which must not be diff --git a/include/haproxy/listener-t.h b/include/haproxy/listener-t.h index 224e32513..38ca2839f 100644 --- a/include/haproxy/listener-t.h +++ b/include/haproxy/listener-t.h @@ -163,8 +163,8 @@ struct bind_conf { char *ca_sign_file; /* CAFile used to generate and sign server certificates */ char *ca_sign_pass; /* CAKey passphrase */ - X509 *ca_sign_cert; /* CA certificate referenced by ca_file */ - EVP_PKEY *ca_sign_pkey; /* CA private key referenced by ca_key */ + int ca_sign_use_chain; /* Optionally attached the certificate chain to the served certificate */ + struct cert_key_and_chain * ca_sign_ckch; /* CA and possible certificate chain for ca generation */ #endif struct proxy *frontend; /* the frontend all these listeners belong to, or NULL */ const struct mux_proto_list *mux_proto; /* the mux to use for all incoming connections (specified by the "proto" keyword) */ diff --git a/src/cfgparse-ssl.c b/src/cfgparse-ssl.c index 144cef882..bf8d95a61 100644 --- a/src/cfgparse-ssl.c +++ b/src/cfgparse-ssl.c @@ -538,6 +538,20 @@ static int bind_parse_ca_sign_file(char **args, int cur_arg, struct proxy *px, s return 0; } +/* parse the "ca-sign-use-chain" bind keyword */ +static int bind_parse_ca_sign_use_chain(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ +#if (!defined(SSL_NO_GENERATE_CERTIFICATES) && defined(SSL_CTRL_SET_TLSEXT_HOSTNAME) && \ + defined(SSL_CTX_set1_chain) && defined(SSL_CTX_add1_chain_cert)) + conf->ca_sign_use_chain = 1; + return 0; +#else + memprintf(err, "%sthis version of openssl cannot attach certificate chain for SSL certificate generation.\n", + err && *err ? *err : ""); + return ERR_ALERT | ERR_FATAL; +#endif +} + /* parse the "ca-sign-pass" bind keyword */ static int bind_parse_ca_sign_pass(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) { @@ -1708,6 +1722,7 @@ static struct bind_kw_list bind_kws = { "SSL", { }, { { "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 */ + { "ca-sign-use-chain", bind_parse_ca_sign_use_chain, 0 }, /* enable attaching ca chain to generated certificate */ { "ciphers", bind_parse_ciphers, 1 }, /* set SSL cipher suite */ #if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L) { "ciphersuites", bind_parse_ciphersuites, 1 }, /* set TLS 1.3 cipher suite */ diff --git a/src/ssl_sock.c b/src/ssl_sock.c index a32db1a28..7b2c6caef 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -1750,8 +1750,8 @@ static int ssl_sock_advertise_alpn_protos(SSL *s, const unsigned char **out, static SSL_CTX * ssl_sock_do_create_cert(const char *servername, struct bind_conf *bind_conf, SSL *ssl) { - X509 *cacert = bind_conf->ca_sign_cert; - EVP_PKEY *capkey = bind_conf->ca_sign_pkey; + X509 *cacert = bind_conf->ca_sign_ckch->cert; + EVP_PKEY *capkey = bind_conf->ca_sign_ckch->key; SSL_CTX *ssl_ctx = NULL; X509 *newcrt = NULL; EVP_PKEY *pkey = NULL; @@ -1864,6 +1864,18 @@ ssl_sock_do_create_cert(const char *servername, struct bind_conf *bind_conf, SSL if (!SSL_CTX_check_private_key(ssl_ctx)) goto mkcert_error; + /* Assign chain if any */ +#if defined(SSL_CTX_set1_chain) && defined(SSL_CTX_add1_chain_cert) + if (bind_conf->ca_sign_use_chain && bind_conf->ca_sign_ckch->chain) { + if (!SSL_CTX_set1_chain(ssl_ctx, bind_conf->ca_sign_ckch->chain)) { + goto mkcert_error; + } + if (!SSL_CTX_add1_chain_cert(ssl_ctx, bind_conf->ca_sign_ckch->cert)) { + goto mkcert_error; + } + } +#endif + if (newcrt) X509_free(newcrt); #ifndef OPENSSL_NO_DH @@ -1912,7 +1924,7 @@ ssl_sock_assign_generated_cert(unsigned int key, struct bind_conf *bind_conf, SS if (ssl_ctx_lru_tree) { HA_RWLOCK_WRLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); - lru = lru64_lookup(key, ssl_ctx_lru_tree, bind_conf->ca_sign_cert, 0); + lru = lru64_lookup(key, ssl_ctx_lru_tree, bind_conf->ca_sign_ckch->cert, 0); if (lru && lru->domain) { if (ssl) SSL_set_SSL_CTX(ssl, (SSL_CTX *)lru->data); @@ -1943,14 +1955,14 @@ ssl_sock_set_generated_cert(SSL_CTX *ssl_ctx, unsigned int key, struct bind_conf if (ssl_ctx_lru_tree) { HA_RWLOCK_WRLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); - lru = lru64_get(key, ssl_ctx_lru_tree, bind_conf->ca_sign_cert, 0); + lru = lru64_get(key, ssl_ctx_lru_tree, bind_conf->ca_sign_ckch->cert, 0); if (!lru) { HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); return -1; } if (lru->domain && lru->data) lru->free((SSL_CTX *)lru->data); - lru64_commit(lru, ssl_ctx, bind_conf->ca_sign_cert, 0, (void (*)(void *))SSL_CTX_free); + lru64_commit(lru, ssl_ctx, bind_conf->ca_sign_ckch->cert, 0, (void (*)(void *))SSL_CTX_free); HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); return 0; } @@ -1970,7 +1982,7 @@ ssl_sock_generated_cert_key(const void *data, size_t len) static int ssl_sock_generate_certificate(const char *servername, struct bind_conf *bind_conf, SSL *ssl) { - X509 *cacert = bind_conf->ca_sign_cert; + X509 *cacert = bind_conf->ca_sign_ckch->cert; SSL_CTX *ssl_ctx = NULL; struct lru64 *lru = NULL; unsigned int key; @@ -4823,13 +4835,12 @@ int ssl_sock_load_ca(struct bind_conf *bind_conf) { struct proxy *px = bind_conf->frontend; - FILE *fp; - X509 *cacert = NULL; - EVP_PKEY *capkey = NULL; - int err = 0; + struct cert_key_and_chain *ckch = NULL; + int ret = 0; + char *err = NULL; if (!bind_conf->generate_certs) - return err; + return ret; #if (defined SSL_CTRL_SET_TLSEXT_HOSTNAME && !defined SSL_NO_GENERATE_CERTIFICATES) if (global_ssl.ctx_cache) { @@ -4843,52 +4854,55 @@ ssl_sock_load_ca(struct bind_conf *bind_conf) ha_alert("Proxy '%s': cannot enable certificate generation, " "no CA certificate File configured at [%s:%d].\n", px->id, bind_conf->file, bind_conf->line); - goto load_error; + goto failed; } - /* read in the CA certificate */ - if (!(fp = fopen(bind_conf->ca_sign_file, "r"))) { - ha_alert("Proxy '%s': Failed to read CA certificate file '%s' at [%s:%d].\n", - px->id, bind_conf->ca_sign_file, bind_conf->file, bind_conf->line); - goto load_error; + /* Allocate cert structure */ + ckch = calloc(1, sizeof(struct cert_key_and_chain)); + if (!ckch) { + ha_alert("Proxy '%s': Failed to read CA certificate file '%s' at [%s:%d]. Chain allocation failure\n", + px->id, bind_conf->ca_sign_file, bind_conf->file, bind_conf->line); } - if (!(cacert = PEM_read_X509(fp, NULL, NULL, NULL))) { - ha_alert("Proxy '%s': Failed to read CA certificate file '%s' at [%s:%d].\n", - px->id, bind_conf->ca_sign_file, bind_conf->file, bind_conf->line); - goto read_error; + + /* Try to parse file */ + if (ssl_sock_load_files_into_ckch(bind_conf->ca_sign_file, ckch, &err)) { + ha_alert("Proxy '%s': Failed to read CA certificate file '%s' at [%s:%d]. Chain loading failed: %s\n", + px->id, bind_conf->ca_sign_file, bind_conf->file, bind_conf->line, err); + if (err) free(err); + goto failed; } - rewind(fp); - if (!(capkey = PEM_read_PrivateKey(fp, NULL, NULL, bind_conf->ca_sign_pass))) { - ha_alert("Proxy '%s': Failed to read CA private key file '%s' at [%s:%d].\n", - px->id, bind_conf->ca_sign_file, bind_conf->file, bind_conf->line); - goto read_error; + + /* Fail if missing cert or pkey */ + if ((!ckch->cert) || (!ckch->key)) { + ha_alert("Proxy '%s': Failed to read CA certificate file '%s' at [%s:%d]. Chain missing certificate or private key\n", + px->id, bind_conf->ca_sign_file, bind_conf->file, bind_conf->line); + goto failed; } - fclose (fp); - bind_conf->ca_sign_cert = cacert; - bind_conf->ca_sign_pkey = capkey; - return err; + /* Final assignment to bind */ + bind_conf->ca_sign_ckch = ckch; + return ret; + + failed: + if (ckch) { + ssl_sock_free_cert_key_and_chain_contents(ckch); + free(ckch); + } - read_error: - fclose (fp); - if (capkey) EVP_PKEY_free(capkey); - if (cacert) X509_free(cacert); - load_error: bind_conf->generate_certs = 0; - err++; - return err; + ret++; + return ret; } /* Release CA cert and private key used to generate certificated */ void ssl_sock_free_ca(struct bind_conf *bind_conf) { - if (bind_conf->ca_sign_pkey) - EVP_PKEY_free(bind_conf->ca_sign_pkey); - if (bind_conf->ca_sign_cert) - X509_free(bind_conf->ca_sign_cert); - bind_conf->ca_sign_pkey = NULL; - bind_conf->ca_sign_cert = NULL; + if (bind_conf->ca_sign_ckch) { + ssl_sock_free_cert_key_and_chain_contents(bind_conf->ca_sign_ckch); + free(bind_conf->ca_sign_ckch); + bind_conf->ca_sign_ckch = NULL; + } } /* -- 2.27.0