diff --git a/Makefile b/Makefile index a66c9b69c..3fab62d69 100644 --- a/Makefile +++ b/Makefile @@ -599,7 +599,7 @@ ifneq ($(USE_OPENSSL),) SSL_LDFLAGS := $(if $(SSL_LIB),-L$(SSL_LIB)) -lssl -lcrypto endif USE_SSL := $(if $(USE_SSL),$(USE_SSL),implicit) - OPTIONS_OBJS += src/ssl_sock.o src/ssl_ckch.o src/ssl_sample.o src/ssl_crtlist.o src/cfgparse-ssl.o src/ssl_utils.o src/jwt.o src/ssl_ocsp.o + OPTIONS_OBJS += src/ssl_sock.o src/ssl_ckch.o src/ssl_sample.o src/ssl_crtlist.o src/cfgparse-ssl.o src/ssl_utils.o src/jwt.o src/ssl_ocsp.o src/ssl_gencert.o endif ifneq ($(USE_ENGINE),) diff --git a/include/haproxy/ssl_gencert.h b/include/haproxy/ssl_gencert.h new file mode 100644 index 000000000..4f9510e01 --- /dev/null +++ b/include/haproxy/ssl_gencert.h @@ -0,0 +1,32 @@ +/* + * include/haproxy/ssl_gencert.h + * This file contains definition for ssl 'generate-certificates' option. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, version 2.1 + * exclusively. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _HAPROXY_SSL_GENCERT_H +#define _HAPROXY_SSL_GENCERT_H +#ifdef USE_OPENSSL + +int ssl_sock_generate_certificate(const char *servername, struct bind_conf *bind_conf, SSL *ssl); +int ssl_sock_generate_certificate_from_conn(struct bind_conf *bind_conf, SSL *ssl); +SSL_CTX *ssl_sock_assign_generated_cert(unsigned int key, struct bind_conf *bind_conf, SSL *ssl); +SSL_CTX *ssl_sock_get_generated_cert(unsigned int key, struct bind_conf *bind_conf); +int ssl_sock_set_generated_cert(SSL_CTX *ctx, unsigned int key, struct bind_conf *bind_conf); +unsigned int ssl_sock_generated_cert_key(const void *data, size_t len); + +#endif /* USE_OPENSSL */ +#endif /* _HAPROXY_SSL_GENCERT_H */ diff --git a/include/haproxy/ssl_sock.h b/include/haproxy/ssl_sock.h index 02d5b023f..0befb570c 100644 --- a/include/haproxy/ssl_sock.h +++ b/include/haproxy/ssl_sock.h @@ -114,11 +114,9 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv); #endif int increment_sslconn(); -SSL_CTX *ssl_sock_assign_generated_cert(unsigned int key, struct bind_conf *bind_conf, SSL *ssl); -SSL_CTX *ssl_sock_get_generated_cert(unsigned int key, struct bind_conf *bind_conf); -int ssl_sock_set_generated_cert(SSL_CTX *ctx, unsigned int key, struct bind_conf *bind_conf); -unsigned int ssl_sock_generated_cert_key(const void *data, size_t len); void ssl_sock_load_cert_sni(struct ckch_inst *ckch_inst, struct bind_conf *bind_conf); +struct sni_ctx *ssl_sock_chose_sni_ctx(struct bind_conf *s, const char *servername, + int have_rsa_sig, int have_ecdsa_sig); #ifdef SSL_MODE_ASYNC void ssl_async_fd_handler(int fd); void ssl_async_fd_free(int fd); @@ -139,6 +137,12 @@ int ssl_get_ocspresponse_detail(unsigned char *ocsp_certid, struct buffer *out); int ssl_ocsp_response_print(struct buffer *ocsp_response, struct buffer *out); #endif +#if (HA_OPENSSL_VERSION_NUMBER < 0x3000000fL) +DH *ssl_get_tmp_dh_cbk(SSL *ssl, int export, int keylen); +#else +void ssl_sock_set_tmp_dh_from_pkey(SSL_CTX *ctx, EVP_PKEY *pkey); +#endif + /* ssl shctx macro */ #define sh_ssl_sess_tree_delete(s) ebmb_delete(&(s)->key); diff --git a/src/ssl_gencert.c b/src/ssl_gencert.c new file mode 100644 index 000000000..95195d133 --- /dev/null +++ b/src/ssl_gencert.c @@ -0,0 +1,466 @@ +/* + * SSL 'generate-certificate' option logic. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#define _GNU_SOURCE +#include + +#include +#include +#include +#include +#include + +#if (defined SSL_CTRL_SET_TLSEXT_HOSTNAME && !defined SSL_NO_GENERATE_CERTIFICATES) +/* X509V3 Extensions that will be added on generated certificates */ +#define X509V3_EXT_SIZE 5 +static char *x509v3_ext_names[X509V3_EXT_SIZE] = { + "basicConstraints", + "nsComment", + "subjectKeyIdentifier", + "authorityKeyIdentifier", + "keyUsage", +}; +static char *x509v3_ext_values[X509V3_EXT_SIZE] = { + "CA:FALSE", + "\"OpenSSL Generated Certificate\"", + "hash", + "keyid,issuer:always", + "nonRepudiation,digitalSignature,keyEncipherment" +}; +/* LRU cache to store generated certificate */ +static struct lru64_head *ssl_ctx_lru_tree = NULL; +static unsigned int ssl_ctx_lru_seed = 0; +static unsigned int ssl_ctx_serial; +__decl_rwlock(ssl_ctx_lru_rwlock); + +#endif // SSL_CTRL_SET_TLSEXT_HOSTNAME + +#ifndef SSL_NO_GENERATE_CERTIFICATES + +/* Configure a DNS SAN extension on a certificate. */ +int ssl_sock_add_san_ext(X509V3_CTX* ctx, X509* cert, const char *servername) { + int failure = 0; + X509_EXTENSION *san_ext = NULL; + CONF *conf = NULL; + struct buffer *san_name = get_trash_chunk(); + + conf = NCONF_new(NULL); + if (!conf) { + failure = 1; + goto cleanup; + } + + /* Build an extension based on the DNS entry above */ + chunk_appendf(san_name, "DNS:%s", servername); + san_ext = X509V3_EXT_nconf_nid(conf, ctx, NID_subject_alt_name, san_name->area); + if (!san_ext) { + failure = 1; + goto cleanup; + } + + /* Add the extension */ + if (!X509_add_ext(cert, san_ext, -1 /* Add to end */)) { + failure = 1; + goto cleanup; + } + + /* Success */ + failure = 0; + +cleanup: + if (NULL != san_ext) X509_EXTENSION_free(san_ext); + if (NULL != conf) NCONF_free(conf); + + return failure; +} + +/* Create a X509 certificate with the specified servername and serial. This + * function returns a SSL_CTX object or NULL if an error occurs. */ +static SSL_CTX *ssl_sock_do_create_cert(const char *servername, struct bind_conf *bind_conf, SSL *ssl) +{ + 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; + SSL *tmp_ssl = NULL; + CONF *ctmp = NULL; + X509_NAME *name; + const EVP_MD *digest; + X509V3_CTX ctx; + unsigned int i; + int key_type; + struct sni_ctx *sni_ctx; + + sni_ctx = ssl_sock_chose_sni_ctx(bind_conf, "", 1, 1); + if (!sni_ctx) + goto mkcert_error; + + /* Get the private key of the default certificate and use it */ +#ifdef HAVE_SSL_CTX_get0_privatekey + pkey = SSL_CTX_get0_privatekey(sni_ctx->ctx); +#else + tmp_ssl = SSL_new(sni_ctx->ctx); + if (tmp_ssl) + pkey = SSL_get_privatekey(tmp_ssl); +#endif + if (!pkey) + goto mkcert_error; + + /* Create the certificate */ + if (!(newcrt = X509_new())) + goto mkcert_error; + + /* Set version number for the certificate (X509v3) and the serial + * number */ + if (X509_set_version(newcrt, 2L) != 1) + goto mkcert_error; + ASN1_INTEGER_set(X509_get_serialNumber(newcrt), _HA_ATOMIC_ADD_FETCH(&ssl_ctx_serial, 1)); + + /* Set duration for the certificate */ + if (!X509_gmtime_adj(X509_getm_notBefore(newcrt), (long)-60*60*24) || + !X509_gmtime_adj(X509_getm_notAfter(newcrt),(long)60*60*24*365)) + goto mkcert_error; + + /* set public key in the certificate */ + if (X509_set_pubkey(newcrt, pkey) != 1) + goto mkcert_error; + + /* Set issuer name from the CA */ + if (!(name = X509_get_subject_name(cacert))) + goto mkcert_error; + if (X509_set_issuer_name(newcrt, name) != 1) + goto mkcert_error; + + /* Set the subject name using the same, but the CN */ + name = X509_NAME_dup(name); + if (X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, + (const unsigned char *)servername, + -1, -1, 0) != 1) { + X509_NAME_free(name); + goto mkcert_error; + } + if (X509_set_subject_name(newcrt, name) != 1) { + X509_NAME_free(name); + goto mkcert_error; + } + X509_NAME_free(name); + + /* Add x509v3 extensions as specified */ + ctmp = NCONF_new(NULL); + X509V3_set_ctx(&ctx, cacert, newcrt, NULL, NULL, 0); + for (i = 0; i < X509V3_EXT_SIZE; i++) { + X509_EXTENSION *ext; + + if (!(ext = X509V3_EXT_nconf(ctmp, &ctx, x509v3_ext_names[i], x509v3_ext_values[i]))) + goto mkcert_error; + if (!X509_add_ext(newcrt, ext, -1)) { + X509_EXTENSION_free(ext); + goto mkcert_error; + } + X509_EXTENSION_free(ext); + } + + /* Add SAN extension */ + if (ssl_sock_add_san_ext(&ctx, newcrt, servername)) { + goto mkcert_error; + } + + /* Sign the certificate with the CA private key */ + + key_type = EVP_PKEY_base_id(capkey); + + if (key_type == EVP_PKEY_DSA) + digest = EVP_sha1(); + else if (key_type == EVP_PKEY_RSA) + digest = EVP_sha256(); + else if (key_type == EVP_PKEY_EC) + digest = EVP_sha256(); + else { +#ifdef ASN1_PKEY_CTRL_DEFAULT_MD_NID + int nid; + + if (EVP_PKEY_get_default_digest_nid(capkey, &nid) <= 0) + goto mkcert_error; + if (!(digest = EVP_get_digestbynid(nid))) + goto mkcert_error; +#else + goto mkcert_error; +#endif + } + + if (!(X509_sign(newcrt, capkey, digest))) + goto mkcert_error; + + /* Create and set the new SSL_CTX */ + if (!(ssl_ctx = SSL_CTX_new(SSLv23_server_method()))) + goto mkcert_error; + if (!SSL_CTX_use_PrivateKey(ssl_ctx, pkey)) + goto mkcert_error; + if (!SSL_CTX_use_certificate(ssl_ctx, newcrt)) + goto mkcert_error; + if (!SSL_CTX_check_private_key(ssl_ctx)) + goto mkcert_error; + + /* Build chaining the CA cert and the rest of the chain, keep these order */ +#if defined(SSL_CTX_add1_chain_cert) + if (!SSL_CTX_add1_chain_cert(ssl_ctx, bind_conf->ca_sign_ckch->cert)) { + goto mkcert_error; + } + + if (bind_conf->ca_sign_ckch->chain) { + for (i = 0; i < sk_X509_num(bind_conf->ca_sign_ckch->chain); i++) { + X509 *chain_cert = sk_X509_value(bind_conf->ca_sign_ckch->chain, i); + if (!SSL_CTX_add1_chain_cert(ssl_ctx, chain_cert)) { + goto mkcert_error; + } + } + } +#endif + + if (newcrt) X509_free(newcrt); + +#ifndef OPENSSL_NO_DH +#if (HA_OPENSSL_VERSION_NUMBER < 0x3000000fL) + SSL_CTX_set_tmp_dh_callback(ssl_ctx, ssl_get_tmp_dh_cbk); +#else + ssl_sock_set_tmp_dh_from_pkey(ssl_ctx, pkey); +#endif +#endif + +#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L) +#if defined(SSL_CTX_set1_curves_list) + { + const char *ecdhe = (bind_conf->ssl_conf.ecdhe ? bind_conf->ssl_conf.ecdhe : ECDHE_DEFAULT_CURVE); + if (!SSL_CTX_set1_curves_list(ssl_ctx, ecdhe)) + goto end; + } +#endif +#else +#if defined(SSL_CTX_set_tmp_ecdh) && !defined(OPENSSL_NO_ECDH) + { + const char *ecdhe = (bind_conf->ssl_conf.ecdhe ? bind_conf->ssl_conf.ecdhe : ECDHE_DEFAULT_CURVE); + EC_KEY *ecc; + int nid; + + if ((nid = OBJ_sn2nid(ecdhe)) == NID_undef) + goto end; + if (!(ecc = EC_KEY_new_by_curve_name(nid))) + goto end; + SSL_CTX_set_tmp_ecdh(ssl_ctx, ecc); + EC_KEY_free(ecc); + } +#endif /* defined(SSL_CTX_set_tmp_ecdh) && !defined(OPENSSL_NO_ECDH) */ +#endif /* HA_OPENSSL_VERSION_NUMBER >= 0x10101000L */ + end: + return ssl_ctx; + + mkcert_error: + if (ctmp) NCONF_free(ctmp); + if (tmp_ssl) SSL_free(tmp_ssl); + if (ssl_ctx) SSL_CTX_free(ssl_ctx); + if (newcrt) X509_free(newcrt); + return NULL; +} + + +/* Do a lookup for a certificate in the LRU cache used to store generated + * certificates and immediately assign it to the SSL session if not null. */ +SSL_CTX *ssl_sock_assign_generated_cert(unsigned int key, struct bind_conf *bind_conf, SSL *ssl) +{ + struct lru64 *lru = NULL; + + 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_ckch->cert, 0); + if (lru && lru->domain) { + if (ssl) + SSL_set_SSL_CTX(ssl, (SSL_CTX *)lru->data); + HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); + return (SSL_CTX *)lru->data; + } + HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); + } + return NULL; +} + +/* Same as but without SSL session. This + * function is not thread-safe, it should only be used to check if a certificate + * exists in the lru cache (with no warranty it will not be removed by another + * thread). It is kept for backward compatibility. */ +SSL_CTX * +ssl_sock_get_generated_cert(unsigned int key, struct bind_conf *bind_conf) +{ + return ssl_sock_assign_generated_cert(key, bind_conf, NULL); +} + +/* Set a certificate int the LRU cache used to store generated + * certificate. Return 0 on success, otherwise -1 */ +int ssl_sock_set_generated_cert(SSL_CTX *ssl_ctx, unsigned int key, struct bind_conf *bind_conf) +{ + struct lru64 *lru = NULL; + + 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_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_ckch->cert, 0, (void (*)(void *))SSL_CTX_free); + HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); + return 0; + } + return -1; +} + +/* Compute the key of the certificate. */ +unsigned int +ssl_sock_generated_cert_key(const void *data, size_t len) +{ + return XXH32(data, len, ssl_ctx_lru_seed); +} + +/* Generate a cert and immediately assign it to the SSL session so that the cert's + * refcount is maintained regardless of the cert's presence in the LRU cache. + */ +int ssl_sock_generate_certificate(const char *servername, struct bind_conf *bind_conf, SSL *ssl) +{ + X509 *cacert = bind_conf->ca_sign_ckch->cert; + SSL_CTX *ssl_ctx = NULL; + struct lru64 *lru = NULL; + unsigned int key; + + key = ssl_sock_generated_cert_key(servername, strlen(servername)); + if (ssl_ctx_lru_tree) { + HA_RWLOCK_WRLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); + lru = lru64_get(key, ssl_ctx_lru_tree, cacert, 0); + if (lru && lru->domain) + ssl_ctx = (SSL_CTX *)lru->data; + if (!ssl_ctx && lru) { + ssl_ctx = ssl_sock_do_create_cert(servername, bind_conf, ssl); + lru64_commit(lru, ssl_ctx, cacert, 0, (void (*)(void *))SSL_CTX_free); + } + SSL_set_SSL_CTX(ssl, ssl_ctx); + HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); + return 1; + } + else { + ssl_ctx = ssl_sock_do_create_cert(servername, bind_conf, ssl); + SSL_set_SSL_CTX(ssl, ssl_ctx); + /* No LRU cache, this CTX will be released as soon as the session dies */ + SSL_CTX_free(ssl_ctx); + return 1; + } + return 0; +} +int ssl_sock_generate_certificate_from_conn(struct bind_conf *bind_conf, SSL *ssl) +{ + unsigned int key; + struct connection *conn = SSL_get_ex_data(ssl, ssl_app_data_index); + + if (conn_get_dst(conn)) { + key = ssl_sock_generated_cert_key(conn->dst, get_addr_len(conn->dst)); + if (ssl_sock_assign_generated_cert(key, bind_conf, ssl)) + return 1; + } + return 0; +} + +/* Load CA cert file and private key used to generate certificates */ +int +ssl_sock_load_ca(struct bind_conf *bind_conf) +{ + struct proxy *px = bind_conf->frontend; + struct ckch_data *data = NULL; + int ret = 0; + char *err = NULL; + + if (!(bind_conf->options & BC_O_GENERATE_CERTS)) + return ret; + +#if (defined SSL_CTRL_SET_TLSEXT_HOSTNAME && !defined SSL_NO_GENERATE_CERTIFICATES) + if (global_ssl.ctx_cache) { + ssl_ctx_lru_tree = lru64_new(global_ssl.ctx_cache); + } + ssl_ctx_lru_seed = (unsigned int)time(NULL); + ssl_ctx_serial = now_ms; +#endif + + if (!bind_conf->ca_sign_file) { + 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 failed; + } + + /* Allocate cert structure */ + data = calloc(1, sizeof(*data)); + if (!data) { + 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); + goto failed; + } + + /* Try to parse file */ + if (ssl_sock_load_files_into_ckch(bind_conf->ca_sign_file, data, &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); + free(err); + goto failed; + } + + /* Fail if missing cert or pkey */ + if ((!data->cert) || (!data->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; + } + + /* Final assignment to bind */ + bind_conf->ca_sign_ckch = data; + return ret; + + failed: + if (data) { + ssl_sock_free_cert_key_and_chain_contents(data); + free(data); + } + + bind_conf->options &= ~BC_O_GENERATE_CERTS; + 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_ckch) { + ssl_sock_free_cert_key_and_chain_contents(bind_conf->ca_sign_ckch); + ha_free(&bind_conf->ca_sign_ckch); + } +} + +#endif /* !defined SSL_NO_GENERATE_CERTIFICATES */ + + +static void __ssl_gencert_deinit(void) +{ +#if (defined SSL_CTRL_SET_TLSEXT_HOSTNAME && !defined SSL_NO_GENERATE_CERTIFICATES) + if (ssl_ctx_lru_tree) { + lru64_destroy(ssl_ctx_lru_tree); + HA_RWLOCK_DESTROY(&ssl_ctx_lru_rwlock); + } +#endif +} +REGISTER_POST_DEINIT(__ssl_gencert_deinit); + diff --git a/src/ssl_sock.c b/src/ssl_sock.c index f0e76ba7e..0c5fe96f2 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -72,6 +72,7 @@ #include #include #include +#include #include #include #include @@ -504,38 +505,8 @@ static HASSL_DH *global_dh = NULL; static HASSL_DH *local_dh_1024 = NULL; static HASSL_DH *local_dh_2048 = NULL; static HASSL_DH *local_dh_4096 = NULL; -#if (HA_OPENSSL_VERSION_NUMBER < 0x3000000fL) -static DH *ssl_get_tmp_dh_cbk(SSL *ssl, int export, int keylen); -#else -static void ssl_sock_set_tmp_dh_from_pkey(SSL_CTX *ctx, EVP_PKEY *pkey); -#endif #endif /* OPENSSL_NO_DH */ -#if (defined SSL_CTRL_SET_TLSEXT_HOSTNAME && !defined SSL_NO_GENERATE_CERTIFICATES) -/* X509V3 Extensions that will be added on generated certificates */ -#define X509V3_EXT_SIZE 5 -static char *x509v3_ext_names[X509V3_EXT_SIZE] = { - "basicConstraints", - "nsComment", - "subjectKeyIdentifier", - "authorityKeyIdentifier", - "keyUsage", -}; -static char *x509v3_ext_values[X509V3_EXT_SIZE] = { - "CA:FALSE", - "\"OpenSSL Generated Certificate\"", - "hash", - "keyid,issuer:always", - "nonRepudiation,digitalSignature,keyEncipherment" -}; -/* LRU cache to store generated certificate */ -static struct lru64_head *ssl_ctx_lru_tree = NULL; -static unsigned int ssl_ctx_lru_seed = 0; -static unsigned int ssl_ctx_serial; -__decl_rwlock(ssl_ctx_lru_rwlock); - -#endif // SSL_CTRL_SET_TLSEXT_HOSTNAME - /* The order here matters for picking a default context, * keep the most common keytype at the bottom of the list */ @@ -1891,350 +1862,6 @@ static int ssl_sock_advertise_alpn_protos(SSL *s, const unsigned char **out, } #endif -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME -#ifndef SSL_NO_GENERATE_CERTIFICATES - -/* Configure a DNS SAN extension on a certificate. */ -int ssl_sock_add_san_ext(X509V3_CTX* ctx, X509* cert, const char *servername) { - int failure = 0; - X509_EXTENSION *san_ext = NULL; - CONF *conf = NULL; - struct buffer *san_name = get_trash_chunk(); - - conf = NCONF_new(NULL); - if (!conf) { - failure = 1; - goto cleanup; - } - - /* Build an extension based on the DNS entry above */ - chunk_appendf(san_name, "DNS:%s", servername); - san_ext = X509V3_EXT_nconf_nid(conf, ctx, NID_subject_alt_name, san_name->area); - if (!san_ext) { - failure = 1; - goto cleanup; - } - - /* Add the extension */ - if (!X509_add_ext(cert, san_ext, -1 /* Add to end */)) { - failure = 1; - goto cleanup; - } - - /* Success */ - failure = 0; - -cleanup: - if (NULL != san_ext) X509_EXTENSION_free(san_ext); - if (NULL != conf) NCONF_free(conf); - - return failure; -} - -static __maybe_unused struct sni_ctx *ssl_sock_chose_sni_ctx(struct bind_conf *s, const char *servername, - int have_rsa_sig, int have_ecdsa_sig); - -/* Create a X509 certificate with the specified servername and serial. This - * function returns a SSL_CTX object or NULL if an error occurs. */ -static SSL_CTX * -ssl_sock_do_create_cert(const char *servername, struct bind_conf *bind_conf, SSL *ssl) -{ - 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; - SSL *tmp_ssl = NULL; - CONF *ctmp = NULL; - X509_NAME *name; - const EVP_MD *digest; - X509V3_CTX ctx; - unsigned int i; - int key_type; - struct sni_ctx *sni_ctx; - - sni_ctx = ssl_sock_chose_sni_ctx(bind_conf, "", 1, 1); - if (!sni_ctx) - goto mkcert_error; - - /* Get the private key of the default certificate and use it */ -#ifdef HAVE_SSL_CTX_get0_privatekey - pkey = SSL_CTX_get0_privatekey(sni_ctx->ctx); -#else - tmp_ssl = SSL_new(sni_ctx->ctx); - if (tmp_ssl) - pkey = SSL_get_privatekey(tmp_ssl); -#endif - if (!pkey) - goto mkcert_error; - - /* Create the certificate */ - if (!(newcrt = X509_new())) - goto mkcert_error; - - /* Set version number for the certificate (X509v3) and the serial - * number */ - if (X509_set_version(newcrt, 2L) != 1) - goto mkcert_error; - ASN1_INTEGER_set(X509_get_serialNumber(newcrt), _HA_ATOMIC_ADD_FETCH(&ssl_ctx_serial, 1)); - - /* Set duration for the certificate */ - if (!X509_gmtime_adj(X509_getm_notBefore(newcrt), (long)-60*60*24) || - !X509_gmtime_adj(X509_getm_notAfter(newcrt),(long)60*60*24*365)) - goto mkcert_error; - - /* set public key in the certificate */ - if (X509_set_pubkey(newcrt, pkey) != 1) - goto mkcert_error; - - /* Set issuer name from the CA */ - if (!(name = X509_get_subject_name(cacert))) - goto mkcert_error; - if (X509_set_issuer_name(newcrt, name) != 1) - goto mkcert_error; - - /* Set the subject name using the same, but the CN */ - name = X509_NAME_dup(name); - if (X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, - (const unsigned char *)servername, - -1, -1, 0) != 1) { - X509_NAME_free(name); - goto mkcert_error; - } - if (X509_set_subject_name(newcrt, name) != 1) { - X509_NAME_free(name); - goto mkcert_error; - } - X509_NAME_free(name); - - /* Add x509v3 extensions as specified */ - ctmp = NCONF_new(NULL); - X509V3_set_ctx(&ctx, cacert, newcrt, NULL, NULL, 0); - for (i = 0; i < X509V3_EXT_SIZE; i++) { - X509_EXTENSION *ext; - - if (!(ext = X509V3_EXT_nconf(ctmp, &ctx, x509v3_ext_names[i], x509v3_ext_values[i]))) - goto mkcert_error; - if (!X509_add_ext(newcrt, ext, -1)) { - X509_EXTENSION_free(ext); - goto mkcert_error; - } - X509_EXTENSION_free(ext); - } - - /* Add SAN extension */ - if (ssl_sock_add_san_ext(&ctx, newcrt, servername)) { - goto mkcert_error; - } - - /* Sign the certificate with the CA private key */ - - key_type = EVP_PKEY_base_id(capkey); - - if (key_type == EVP_PKEY_DSA) - digest = EVP_sha1(); - else if (key_type == EVP_PKEY_RSA) - digest = EVP_sha256(); - else if (key_type == EVP_PKEY_EC) - digest = EVP_sha256(); - else { -#ifdef ASN1_PKEY_CTRL_DEFAULT_MD_NID - int nid; - - if (EVP_PKEY_get_default_digest_nid(capkey, &nid) <= 0) - goto mkcert_error; - if (!(digest = EVP_get_digestbynid(nid))) - goto mkcert_error; -#else - goto mkcert_error; -#endif - } - - if (!(X509_sign(newcrt, capkey, digest))) - goto mkcert_error; - - /* Create and set the new SSL_CTX */ - if (!(ssl_ctx = SSL_CTX_new(SSLv23_server_method()))) - goto mkcert_error; - if (!SSL_CTX_use_PrivateKey(ssl_ctx, pkey)) - goto mkcert_error; - if (!SSL_CTX_use_certificate(ssl_ctx, newcrt)) - goto mkcert_error; - if (!SSL_CTX_check_private_key(ssl_ctx)) - goto mkcert_error; - - /* Build chaining the CA cert and the rest of the chain, keep these order */ -#if defined(SSL_CTX_add1_chain_cert) - if (!SSL_CTX_add1_chain_cert(ssl_ctx, bind_conf->ca_sign_ckch->cert)) { - goto mkcert_error; - } - - if (bind_conf->ca_sign_ckch->chain) { - for (i = 0; i < sk_X509_num(bind_conf->ca_sign_ckch->chain); i++) { - X509 *chain_cert = sk_X509_value(bind_conf->ca_sign_ckch->chain, i); - if (!SSL_CTX_add1_chain_cert(ssl_ctx, chain_cert)) { - goto mkcert_error; - } - } - } -#endif - - if (newcrt) X509_free(newcrt); - -#ifndef OPENSSL_NO_DH -#if (HA_OPENSSL_VERSION_NUMBER < 0x3000000fL) - SSL_CTX_set_tmp_dh_callback(ssl_ctx, ssl_get_tmp_dh_cbk); -#else - ssl_sock_set_tmp_dh_from_pkey(ssl_ctx, pkey); -#endif -#endif - -#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L) -#if defined(SSL_CTX_set1_curves_list) - { - const char *ecdhe = (bind_conf->ssl_conf.ecdhe ? bind_conf->ssl_conf.ecdhe : ECDHE_DEFAULT_CURVE); - if (!SSL_CTX_set1_curves_list(ssl_ctx, ecdhe)) - goto end; - } -#endif -#else -#if defined(SSL_CTX_set_tmp_ecdh) && !defined(OPENSSL_NO_ECDH) - { - const char *ecdhe = (bind_conf->ssl_conf.ecdhe ? bind_conf->ssl_conf.ecdhe : ECDHE_DEFAULT_CURVE); - EC_KEY *ecc; - int nid; - - if ((nid = OBJ_sn2nid(ecdhe)) == NID_undef) - goto end; - if (!(ecc = EC_KEY_new_by_curve_name(nid))) - goto end; - SSL_CTX_set_tmp_ecdh(ssl_ctx, ecc); - EC_KEY_free(ecc); - } -#endif /* defined(SSL_CTX_set_tmp_ecdh) && !defined(OPENSSL_NO_ECDH) */ -#endif /* HA_OPENSSL_VERSION_NUMBER >= 0x10101000L */ - end: - return ssl_ctx; - - mkcert_error: - if (ctmp) NCONF_free(ctmp); - if (tmp_ssl) SSL_free(tmp_ssl); - if (ssl_ctx) SSL_CTX_free(ssl_ctx); - if (newcrt) X509_free(newcrt); - return NULL; -} - - -/* Do a lookup for a certificate in the LRU cache used to store generated - * certificates and immediately assign it to the SSL session if not null. */ -SSL_CTX * -ssl_sock_assign_generated_cert(unsigned int key, struct bind_conf *bind_conf, SSL *ssl) -{ - struct lru64 *lru = NULL; - - 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_ckch->cert, 0); - if (lru && lru->domain) { - if (ssl) - SSL_set_SSL_CTX(ssl, (SSL_CTX *)lru->data); - HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); - return (SSL_CTX *)lru->data; - } - HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); - } - return NULL; -} - -/* Same as but without SSL session. This - * function is not thread-safe, it should only be used to check if a certificate - * exists in the lru cache (with no warranty it will not be removed by another - * thread). It is kept for backward compatibility. */ -SSL_CTX * -ssl_sock_get_generated_cert(unsigned int key, struct bind_conf *bind_conf) -{ - return ssl_sock_assign_generated_cert(key, bind_conf, NULL); -} - -/* Set a certificate int the LRU cache used to store generated - * certificate. Return 0 on success, otherwise -1 */ -int -ssl_sock_set_generated_cert(SSL_CTX *ssl_ctx, unsigned int key, struct bind_conf *bind_conf) -{ - struct lru64 *lru = NULL; - - 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_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_ckch->cert, 0, (void (*)(void *))SSL_CTX_free); - HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); - return 0; - } - return -1; -} - -/* Compute the key of the certificate. */ -unsigned int -ssl_sock_generated_cert_key(const void *data, size_t len) -{ - return XXH32(data, len, ssl_ctx_lru_seed); -} - -/* Generate a cert and immediately assign it to the SSL session so that the cert's - * refcount is maintained regardless of the cert's presence in the LRU cache. - */ -static int -ssl_sock_generate_certificate(const char *servername, struct bind_conf *bind_conf, SSL *ssl) -{ - X509 *cacert = bind_conf->ca_sign_ckch->cert; - SSL_CTX *ssl_ctx = NULL; - struct lru64 *lru = NULL; - unsigned int key; - - key = ssl_sock_generated_cert_key(servername, strlen(servername)); - if (ssl_ctx_lru_tree) { - HA_RWLOCK_WRLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); - lru = lru64_get(key, ssl_ctx_lru_tree, cacert, 0); - if (lru && lru->domain) - ssl_ctx = (SSL_CTX *)lru->data; - if (!ssl_ctx && lru) { - ssl_ctx = ssl_sock_do_create_cert(servername, bind_conf, ssl); - lru64_commit(lru, ssl_ctx, cacert, 0, (void (*)(void *))SSL_CTX_free); - } - SSL_set_SSL_CTX(ssl, ssl_ctx); - HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); - return 1; - } - else { - ssl_ctx = ssl_sock_do_create_cert(servername, bind_conf, ssl); - SSL_set_SSL_CTX(ssl, ssl_ctx); - /* No LRU cache, this CTX will be released as soon as the session dies */ - SSL_CTX_free(ssl_ctx); - return 1; - } - return 0; -} -static int -ssl_sock_generate_certificate_from_conn(struct bind_conf *bind_conf, SSL *ssl) -{ - unsigned int key; - struct connection *conn = SSL_get_ex_data(ssl, ssl_app_data_index); - - if (conn_get_dst(conn)) { - key = ssl_sock_generated_cert_key(conn->dst, get_addr_len(conn->dst)); - if (ssl_sock_assign_generated_cert(key, bind_conf, ssl)) - return 1; - } - return 0; -} -#endif /* !defined SSL_NO_GENERATE_CERTIFICATES */ - #if (HA_OPENSSL_VERSION_NUMBER < 0x1010000fL) static void ctx_set_SSLv3_func(SSL_CTX *ctx, set_context_func c) @@ -2340,7 +1967,7 @@ static void ssl_sock_switchctx_set(SSL *ssl, SSL_CTX *ctx) * * This function does a lookup in the bind_conf sni tree so the caller should lock its tree. */ -static __maybe_unused struct sni_ctx *ssl_sock_chose_sni_ctx(struct bind_conf *s, const char *servername, +struct sni_ctx *ssl_sock_chose_sni_ctx(struct bind_conf *s, const char *servername, int have_rsa_sig, int have_ecdsa_sig) { struct ebmb_node *node, *n, *node_ecdsa = NULL, *node_rsa = NULL, *node_anonymous = NULL; @@ -2827,7 +2454,6 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv) return SSL_TLSEXT_ERR_OK; } #endif /* (!) OPENSSL_IS_BORINGSSL */ -#endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */ #if defined(USE_OPENSSL_WOLFSSL) /* This implement the equivalent of the clientHello Callback but using the cert_cb. @@ -3235,7 +2861,7 @@ static HASSL_DH *ssl_get_tmp_dh(EVP_PKEY *pkey) #if (HA_OPENSSL_VERSION_NUMBER < 0x3000000fL) /* Returns Diffie-Hellman parameters matching the private key length but not exceeding global_ssl.default_dh_param */ -static HASSL_DH *ssl_get_tmp_dh_cbk(SSL *ssl, int export, int keylen) +HASSL_DH *ssl_get_tmp_dh_cbk(SSL *ssl, int export, int keylen) { EVP_PKEY *pkey = SSL_get_privatekey(ssl); @@ -3261,7 +2887,7 @@ static int ssl_sock_set_tmp_dh(SSL_CTX *ctx, HASSL_DH *dh) } #if (HA_OPENSSL_VERSION_NUMBER >= 0x3000000fL) -static void ssl_sock_set_tmp_dh_from_pkey(SSL_CTX *ctx, EVP_PKEY *pkey) +void ssl_sock_set_tmp_dh_from_pkey(SSL_CTX *ctx, EVP_PKEY *pkey) { HASSL_DH *dh = NULL; if (pkey && (dh = ssl_get_tmp_dh(pkey))) { @@ -5760,81 +5386,6 @@ void ssl_sock_destroy_bind_conf(struct bind_conf *bind_conf) bind_conf->ca_sign_file = NULL; } -/* Load CA cert file and private key used to generate certificates */ -int -ssl_sock_load_ca(struct bind_conf *bind_conf) -{ - struct proxy *px = bind_conf->frontend; - struct ckch_data *data = NULL; - int ret = 0; - char *err = NULL; - - if (!(bind_conf->options & BC_O_GENERATE_CERTS)) - return ret; - -#if (defined SSL_CTRL_SET_TLSEXT_HOSTNAME && !defined SSL_NO_GENERATE_CERTIFICATES) - if (global_ssl.ctx_cache) { - ssl_ctx_lru_tree = lru64_new(global_ssl.ctx_cache); - } - ssl_ctx_lru_seed = (unsigned int)time(NULL); - ssl_ctx_serial = now_ms; -#endif - - if (!bind_conf->ca_sign_file) { - 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 failed; - } - - /* Allocate cert structure */ - data = calloc(1, sizeof(*data)); - if (!data) { - 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); - goto failed; - } - - /* Try to parse file */ - if (ssl_sock_load_files_into_ckch(bind_conf->ca_sign_file, data, &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); - free(err); - goto failed; - } - - /* Fail if missing cert or pkey */ - if ((!data->cert) || (!data->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; - } - - /* Final assignment to bind */ - bind_conf->ca_sign_ckch = data; - return ret; - - failed: - if (data) { - ssl_sock_free_cert_key_and_chain_contents(data); - free(data); - } - - bind_conf->options &= ~BC_O_GENERATE_CERTS; - 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_ckch) { - ssl_sock_free_cert_key_and_chain_contents(bind_conf->ca_sign_ckch); - ha_free(&bind_conf->ca_sign_ckch); - } -} - /* * Try to allocate the BIO and SSL session objects of connection with and * as addresses, as BIO method and as SSL context inherited settings. @@ -8082,12 +7633,6 @@ void ssl_free_dh(void) { static void __ssl_sock_deinit(void) { -#if (defined SSL_CTRL_SET_TLSEXT_HOSTNAME && !defined SSL_NO_GENERATE_CERTIFICATES) - if (ssl_ctx_lru_tree) { - lru64_destroy(ssl_ctx_lru_tree); - HA_RWLOCK_DESTROY(&ssl_ctx_lru_rwlock); - } -#endif #if (HA_OPENSSL_VERSION_NUMBER < 0x10100000L) ERR_remove_state(0);