mirror of
				https://git.haproxy.org/git/haproxy.git/
				synced 2025-10-31 16:41:01 +01:00 
			
		
		
		
	Patch 23093c72 ("BUG/MINOR: ssl: suboptimal certificate selection with TLSv1.3
and dual ECDSA/RSA") introduced a problem when prioritizing the ECDSA
with TLSv1.3.
Indeed, when a client with TLSv1.3 capabilities announce a list of
ECDSA sigalgs, a list of TLSv1.3 ciphersuites compatible with ECDSA,
but only RSA ciphers for TLSv1.2, and haproxy is configured to a
ssl-max-ver TLSv1.2, then haproxy would use the ECDSA keypair, but the
client wouldn't be able to process it because TLSv1.2 was negociated.
HAProxy would be configured like that:
  ssl-default-bind-options ssl-max-ver TLSv1.2
And a client could be used this way:
  openssl s_client -connect localhost:8443 -cipher ECDHE-ECDSA-AES128-GCM-SHA256 \
          -ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
This patch fixes the issue by checking if TLSv1.3 was configured before
allowing ECDSA is an TLSv1.3 ciphersuite is in the list.
This could be backported where 23093c72 ("BUG/MINOR: ssl: suboptimal
certificate selection with TLSv1.3 and dual ECDSA/RSA") was backported.
However this is quite sensible and we should wait a bit before the
backport.
This should fix issue #2988
		
	
			
		
			
				
	
	
		
			772 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			772 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* SPDX-License-Identifier: GPL-2.0-or-later */
 | |
| 
 | |
| /* Note: do NOT include openssl/xxx.h here, do it in openssl-compat.h */
 | |
| #define _GNU_SOURCE
 | |
| #include <ctype.h>
 | |
| #include <dirent.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <unistd.h>
 | |
| 
 | |
| #include <import/ebpttree.h>
 | |
| #include <import/ebsttree.h>
 | |
| 
 | |
| #include <haproxy/openssl-compat.h>
 | |
| #include <haproxy/proto_tcp.h>
 | |
| #include <haproxy/quic_conn.h>
 | |
| #include <haproxy/quic_openssl_compat.h>
 | |
| #include <haproxy/quic_ssl.h>
 | |
| #include <haproxy/quic_tp.h>
 | |
| #include <haproxy/ssl_ckch.h>
 | |
| #include <haproxy/ssl_gencert.h>
 | |
| #include <haproxy/ssl_sock.h>
 | |
| #include <haproxy/trace.h>
 | |
| #include <haproxy/ssl_trace-t.h>
 | |
| 
 | |
| static void ssl_sock_switchctx_set(SSL *ssl, SSL_CTX *ctx)
 | |
| {
 | |
| 	SSL_set_verify(ssl, SSL_CTX_get_verify_mode(ctx), ssl_sock_bind_verifycbk);
 | |
| 	SSL_set_client_CA_list(ssl, SSL_dup_CA_list(SSL_CTX_get_client_CA_list(ctx)));
 | |
| 	SSL_set_SSL_CTX(ssl, ctx);
 | |
| #if defined(USE_QUIC) && defined(HAVE_OPENSSL_QUIC)
 | |
| 	quic_ssl_set_tls_cbs(ssl);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Return the right sni_ctx for a <bind_conf> and a chosen <servername> (must be in lowercase)
 | |
|  * RSA <have_rsa_sig> and ECDSA <have_ecdsa_sig> capabilities of the client can also be used.
 | |
|  *
 | |
|  * This function does a lookup in the bind_conf sni tree so the caller should lock its tree.
 | |
|  */
 | |
| struct sni_ctx *ssl_sock_chose_sni_ctx(struct bind_conf *s, struct connection *conn,
 | |
|                                        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;
 | |
| 	const char *wildp = NULL;
 | |
| 	int i;
 | |
| 
 | |
| 	TRACE_ENTER(SSL_EV_CONN_CHOOSE_SNI_CTX, conn, servername);
 | |
| 
 | |
| 	/* look for the first dot for wildcard search */
 | |
| 	for (i = 0; servername[i] != '\0'; i++) {
 | |
| 		if (servername[i] == '.') {
 | |
| 			wildp = &servername[i];
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	/* if the servername is empty look for the default in the wildcard list */
 | |
| 	if (!*servername)
 | |
| 		wildp = servername;
 | |
| 
 | |
| 	/* Look for an ECDSA, RSA and DSA certificate, first in the single
 | |
| 	 * name and if not found in the wildcard  */
 | |
| 	for (i = 0; i < 2; i++) {
 | |
| 		if (i == 0) 	/* lookup in full qualified names */
 | |
| 			node = ebst_lookup(&s->sni_ctx, trash.area);
 | |
| 		else if (i == 1 && wildp)  /* lookup in wildcards names */
 | |
| 			node = ebst_lookup(&s->sni_w_ctx, wildp);
 | |
| 		else
 | |
| 			break;
 | |
| 
 | |
| 		for (n = node; n; n = ebmb_next_dup(n)) {
 | |
| 
 | |
| 			/* lookup a not neg filter */
 | |
| 			if (!container_of(n, struct sni_ctx, name)->neg) {
 | |
| 				struct sni_ctx *sni, *sni_tmp;
 | |
| 				int skip = 0;
 | |
| 
 | |
| 				if (i == 1 && wildp) { /* wildcard */
 | |
| 					/* If this is a wildcard, look for an exclusion on the same crt-list line */
 | |
| 					sni = container_of(n, struct sni_ctx, name);
 | |
| 					list_for_each_entry(sni_tmp, &sni->ckch_inst->sni_ctx, by_ckch_inst) {
 | |
| 						if (sni_tmp->neg && (strcmp((const char *)sni_tmp->name.key, trash.area) == 0)) {
 | |
| 							skip = 1;
 | |
| 							break;
 | |
| 						}
 | |
| 					}
 | |
| 					if (skip)
 | |
| 						continue;
 | |
| 				}
 | |
| 
 | |
| 				switch(container_of(n, struct sni_ctx, name)->kinfo.sig) {
 | |
| 				case TLSEXT_signature_ecdsa:
 | |
| 					if (!node_ecdsa)
 | |
| 						node_ecdsa = n;
 | |
| 					break;
 | |
| 				case TLSEXT_signature_rsa:
 | |
| 					if (!node_rsa)
 | |
| 						node_rsa = n;
 | |
| 					break;
 | |
| 				default: /* TLSEXT_signature_anonymous|dsa */
 | |
| 					if (!node_anonymous)
 | |
| 						node_anonymous = n;
 | |
| 					break;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	/* Once the certificates are found, select them depending on what is
 | |
| 	 * supported in the client and by key_signature priority order: EDSA >
 | |
| 	 * RSA > DSA */
 | |
| 	if (have_ecdsa_sig && node_ecdsa) {
 | |
| 		node = node_ecdsa;
 | |
| 		TRACE_STATE("ECDSA node picked", SSL_EV_CONN_CHOOSE_SNI_CTX, conn, servername, node);
 | |
| 	} else if (have_rsa_sig && node_rsa) {
 | |
| 		node = node_rsa;
 | |
| 		TRACE_STATE("RSA node picked", SSL_EV_CONN_CHOOSE_SNI_CTX, conn, servername, node);
 | |
| 	} else if (node_anonymous) {
 | |
| 		node = node_anonymous;
 | |
| 		TRACE_STATE("Anonymous node picked", SSL_EV_CONN_CHOOSE_SNI_CTX, conn, servername, node);
 | |
| 	} else if (node_ecdsa) {
 | |
| 		node = node_ecdsa;      /* no ecdsa signature case (< TLSv1.2) */
 | |
| 		TRACE_STATE("ECDSA node picked (< TLSv1.2)", SSL_EV_CONN_CHOOSE_SNI_CTX, conn, servername, node);
 | |
| 	} else {
 | |
| 		node = node_rsa;        /* no rsa signature case (far far away) */
 | |
| 		TRACE_STATE("RSA node picked (fallback)", SSL_EV_CONN_CHOOSE_SNI_CTX, conn, servername, node);
 | |
| 	}
 | |
| 
 | |
| 	if (node) {
 | |
| 		TRACE_LEAVE(SSL_EV_CONN_CHOOSE_SNI_CTX, conn);
 | |
| 		return container_of(node, struct sni_ctx, name);
 | |
| 	}
 | |
| 
 | |
| 	TRACE_STATE("No SNI context found", SSL_EV_CONN_CHOOSE_SNI_CTX, conn);
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| #ifdef HAVE_SSL_CLIENT_HELLO_CB
 | |
| 
 | |
| int ssl_sock_switchctx_err_cbk(SSL *ssl, int *al, void *priv)
 | |
| {
 | |
| 	struct bind_conf *s = priv;
 | |
| 	(void)al; /* shut gcc stupid warning */
 | |
| 
 | |
| 	if (SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name) || (s->options & BC_O_GENERATE_CERTS))
 | |
| 		return SSL_TLSEXT_ERR_OK;
 | |
| 	return SSL_TLSEXT_ERR_NOACK;
 | |
| }
 | |
| 
 | |
| #if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
 | |
| int ssl_sock_switchctx_cbk(const struct ssl_early_callback_ctx *ctx)
 | |
| {
 | |
| 	SSL *ssl = ctx->ssl;
 | |
| #else
 | |
| int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
 | |
| {
 | |
| #endif
 | |
| 	struct connection *conn = SSL_get_ex_data(ssl, ssl_app_data_index);
 | |
| #ifdef USE_QUIC
 | |
| 	struct quic_conn *qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index);
 | |
| #endif /* USE_QUIC */
 | |
| 	struct bind_conf *s = NULL;
 | |
| 	const uint8_t *extension_data;
 | |
| 	size_t extension_len;
 | |
| 	int has_rsa_sig = 0, has_ecdsa_sig = 0;
 | |
| 	struct sni_ctx *sni_ctx;
 | |
| 	const char *servername;
 | |
| 	size_t servername_len = 0;
 | |
| 	int default_lookup = 0; /* did we lookup for a default yet? */
 | |
| 	int allow_early = 0;
 | |
| 	int i;
 | |
| 
 | |
| 	TRACE_ENTER(SSL_EV_CONN_SWITCHCTX_CB, conn);
 | |
| 
 | |
| 	if (conn)
 | |
| 		s = __objt_listener(conn->target)->bind_conf;
 | |
| #ifdef USE_QUIC
 | |
| 	else if (qc)
 | |
| 		s = __objt_listener(qc->target)->bind_conf;
 | |
| #endif /* USE_QUIC */
 | |
| 
 | |
| 	if (!s) {
 | |
| 		/* must never happen */
 | |
| 		ABORT_NOW();
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| #ifdef USE_QUIC
 | |
| 	if (qc) {
 | |
| 		/* Look for the QUIC transport parameters. */
 | |
| #if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
 | |
| 		if (!SSL_early_callback_ctx_extension_get(ctx, qc->tps_tls_ext,
 | |
| 		                                          &extension_data, &extension_len))
 | |
| #else
 | |
| 		if (!SSL_client_hello_get0_ext(ssl, qc->tps_tls_ext,
 | |
| 		                               &extension_data, &extension_len))
 | |
| #endif
 | |
| 		{
 | |
| 			/* This is not redundant. It we only return 0 without setting
 | |
| 			 * <*al>, this has as side effect to generate another TLS alert
 | |
| 			 * which would be set after calling quic_set_tls_alert().
 | |
| 			 */
 | |
| #if !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC)
 | |
| 			*al = SSL_AD_MISSING_EXTENSION;
 | |
| #endif
 | |
| 			quic_set_tls_alert(qc, SSL_AD_MISSING_EXTENSION);
 | |
| 			return 0;
 | |
| 		}
 | |
| 
 | |
| 		if (!quic_transport_params_store(qc, 0, extension_data,
 | |
| 		                                 extension_data + extension_len))
 | |
| 			goto abort;
 | |
| 
 | |
| 		qc->flags |= QUIC_FL_CONN_TX_TP_RECEIVED;
 | |
| 	}
 | |
| #endif /* USE_QUIC */
 | |
| 
 | |
| 	if (s->ssl_conf.early_data)
 | |
| 		allow_early = 1;
 | |
| #if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
 | |
| 	if (SSL_early_callback_ctx_extension_get(ctx, TLSEXT_TYPE_server_name,
 | |
| 						 &extension_data, &extension_len)) {
 | |
| #else
 | |
| 	if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &extension_data, &extension_len)) {
 | |
| #endif
 | |
| 		/*
 | |
| 		 * The server_name extension was given too much extensibility when it
 | |
| 		 * was written, so parsing the normal case is a bit complex.
 | |
| 		 */
 | |
| 		size_t len;
 | |
| 		if (extension_len <= 2) {
 | |
| 			TRACE_ERROR("Server_name parsing error (too short)", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
 | |
| 			goto abort;
 | |
| 		}
 | |
| 		/* Extract the length of the supplied list of names. */
 | |
| 		len = (*extension_data++) << 8;
 | |
| 		len |= *extension_data++;
 | |
| 		if (len + 2 != extension_len) {
 | |
| 			TRACE_ERROR("Server_name parsing error (wrong announced length)", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
 | |
| 			goto abort;
 | |
| 		}
 | |
| 		/*
 | |
| 		 * The list in practice only has a single element, so we only consider
 | |
| 		 * the first one.
 | |
| 		 */
 | |
| 		if (len == 0 || *extension_data++ != TLSEXT_NAMETYPE_host_name) {
 | |
| 			TRACE_ERROR("Server_name parsing error (wrong announced length)", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
 | |
| 			goto abort;
 | |
| 		}
 | |
| 		extension_len = len - 1;
 | |
| 		/* Now we can finally pull out the byte array with the actual hostname. */
 | |
| 		if (extension_len <= 2) {
 | |
| 			TRACE_ERROR("Server_name parsing error (byte array length)", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
 | |
| 			goto abort;
 | |
| 		}
 | |
| 		len = (*extension_data++) << 8;
 | |
| 		len |= *extension_data++;
 | |
| 		if (len == 0 || len + 2 > extension_len || len > TLSEXT_MAXLEN_host_name
 | |
| 		    || memchr(extension_data, 0, len) != NULL) {
 | |
| 			TRACE_ERROR("Server_name parsing error", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
 | |
| 			goto abort;
 | |
| 		}
 | |
| 		servername = (char *)extension_data;
 | |
| 		servername_len = len;
 | |
| 		TRACE_STATE("Server_name extension parsed", SSL_EV_CONN_SWITCHCTX_CB, conn, ssl, servername, &servername_len);
 | |
| 	} else {
 | |
| 		TRACE_STATE("No server_name extension", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
 | |
| #if (!defined SSL_NO_GENERATE_CERTIFICATES)
 | |
| 		if (s->options & BC_O_GENERATE_CERTS && ssl_sock_generate_certificate_from_conn(s, ssl)) {
 | |
| 			TRACE_STATE("No servername provided, certificate generated", SSL_EV_CONN_SWITCHCTX_CB, conn);
 | |
| 			goto allow_early;
 | |
| 		}
 | |
| #endif
 | |
| 
 | |
| 		/* no servername field is not compatible with strict-sni */
 | |
| 		if (s->ssl_options & BC_SSL_O_STRICT_SNI) {
 | |
| 			TRACE_ERROR("No server_name provided and 'strict-sni' enabled", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
 | |
| 			goto abort;
 | |
| 		}
 | |
| 
 | |
| 		/* without servername extension, look for the defaults which is
 | |
| 		 * defined by an empty servername string */
 | |
| 		servername = "";
 | |
| 		servername_len = 0;
 | |
| 		default_lookup = 1;
 | |
| 	}
 | |
| 
 | |
| 	/* extract/check clientHello information */
 | |
| #if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
 | |
| 	if (SSL_early_callback_ctx_extension_get(ctx, TLSEXT_TYPE_signature_algorithms, &extension_data, &extension_len)) {
 | |
| #else
 | |
| 	if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_signature_algorithms, &extension_data, &extension_len)) {
 | |
| #endif
 | |
| 		uint8_t sign;
 | |
| 		uint8_t hash;
 | |
| 		size_t len;
 | |
| 
 | |
| 		if (extension_len < 2) {
 | |
| 			TRACE_ERROR("Sigalg parsing error (too short)", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
 | |
| 			goto abort;
 | |
| 		}
 | |
| 		len = (*extension_data++) << 8;
 | |
| 		len |= *extension_data++;
 | |
| 		if (len + 2 != extension_len) {
 | |
| 			TRACE_ERROR("Sigalg parsing error (wrong announced length)", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
 | |
| 			goto abort;
 | |
| 		}
 | |
| 		if (len % 2 != 0) {
 | |
| 			TRACE_ERROR("Sigalg parsing error (not even)", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
 | |
| 			goto abort;
 | |
| 		}
 | |
| 		TRACE_DATA("Sigalg extension value", SSL_EV_CONN_SIGALG_EXT, conn, extension_data, &len);
 | |
| 		for (; len > 0; len -= 2) {
 | |
| 			hash = *extension_data++; /* hash */
 | |
| 			sign = *extension_data++;
 | |
| 			switch (sign) {
 | |
| 			case TLSEXT_signature_rsa:
 | |
| 				TRACE_DEVEL("Sigalg parsing: has_rsa_sig", SSL_EV_CONN_SWITCHCTX_CB, conn);
 | |
| 				has_rsa_sig = 1;
 | |
| 				break;
 | |
| 			case TLSEXT_signature_ecdsa:
 | |
| 				TRACE_DEVEL("Sigalg parsing: has_ecdsa_sig", SSL_EV_CONN_SWITCHCTX_CB, conn);
 | |
| 				has_ecdsa_sig = 1;
 | |
| 				break;
 | |
| 			case 0x04:
 | |
| 			case 0x05:
 | |
| 			case 0x06:
 | |
| 			case 0x09:
 | |
| 			case 0x0a:
 | |
| 			case 0x0b:
 | |
| 				/* match the RSA-PSS sigalgs from TLSv1.3
 | |
| 				 * https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.3
 | |
| 				 */
 | |
| 				if (hash == 0x08) {
 | |
| 					TRACE_DEVEL("Sigalg parsing: has_rsa_sig (RSA-PSS)", SSL_EV_CONN_SWITCHCTX_CB, conn);
 | |
| 					has_rsa_sig = 1;
 | |
| 				}
 | |
| 				break;
 | |
| 			default:
 | |
| 				continue;
 | |
| 			}
 | |
| 			if (has_ecdsa_sig && has_rsa_sig)
 | |
| 				break;
 | |
| 		}
 | |
| 	} else {
 | |
| 		/* without TLSEXT_TYPE_signature_algorithms extension (< TLSv1.2) */
 | |
| 		TRACE_DEVEL("Sigalg parsing: has_rsa_sig (default)", SSL_EV_CONN_SWITCHCTX_CB, conn);
 | |
| 		has_rsa_sig = 1;
 | |
| 	}
 | |
| 	if (has_ecdsa_sig) {  /* in very rare case: has ecdsa sign but not a ECDSA cipher */
 | |
| 		const SSL_CIPHER *cipher;
 | |
| 		STACK_OF(SSL_CIPHER) *ha_ciphers; /* haproxy side ciphers */
 | |
| 		uint32_t cipher_id;
 | |
| 		size_t len;
 | |
| 		const uint8_t *cipher_suites;
 | |
| 
 | |
| 		ha_ciphers = SSL_get_ciphers(ssl);
 | |
| 		has_ecdsa_sig = 0;
 | |
| 
 | |
| #if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
 | |
| 		len = ctx->cipher_suites_len;
 | |
| 		cipher_suites = ctx->cipher_suites;
 | |
| #else
 | |
| 		len = SSL_client_hello_get0_ciphers(ssl, &cipher_suites);
 | |
| #endif
 | |
| 		if (len % 2 != 0) {
 | |
| 			TRACE_ERROR("Ciphers parsing error (not even)", SSL_EV_CONN_SWITCHCTX_CB, conn);
 | |
| 			goto abort;
 | |
| 		}
 | |
| 		for (; len != 0; len -= 2, cipher_suites += 2) {
 | |
| #if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
 | |
| 			uint16_t cipher_suite = (cipher_suites[0] << 8) | cipher_suites[1];
 | |
| 			cipher = SSL_get_cipher_by_value(cipher_suite);
 | |
| #else
 | |
| 			cipher = SSL_CIPHER_find(ssl, cipher_suites);
 | |
| #endif
 | |
| 			if (!cipher)
 | |
| 				continue;
 | |
| 
 | |
| 			/* check if this cipher is available in haproxy configuration */
 | |
| #if defined(OPENSSL_IS_AWSLC)
 | |
|                         /* because AWS-LC does not provide the TLSv1.3 ciphersuites (which are NID_auth_any) in ha_ciphers,
 | |
|                          * does not check if it's available when it's an NID_auth_any
 | |
|                          */
 | |
|                         if (sk_SSL_CIPHER_find(ha_ciphers, cipher) == -1 && SSL_CIPHER_get_auth_nid(cipher) != NID_auth_any)
 | |
| 				continue;
 | |
| #else
 | |
| 
 | |
| 			if (sk_SSL_CIPHER_find(ha_ciphers, cipher) == -1)
 | |
| 				continue;
 | |
| #endif
 | |
| 
 | |
| 			cipher_id = SSL_CIPHER_get_id(cipher);
 | |
| 			/* skip the SCSV "fake" signaling ciphersuites because they are NID_auth_any (RFC 7507) */
 | |
| 			if (cipher_id == SSL3_CK_SCSV || cipher_id == SSL3_CK_FALLBACK_SCSV)
 | |
| 				continue;
 | |
| 
 | |
| 			if (SSL_CIPHER_get_auth_nid(cipher) == NID_auth_ecdsa) {
 | |
| 				has_ecdsa_sig = 1;
 | |
| 				break;
 | |
| 			}
 | |
| 			if (SSL_CIPHER_get_auth_nid(cipher) == NID_auth_any &&
 | |
| 			    s->ssl_conf.ssl_methods.max >= CONF_TLSV13) {
 | |
| 				/* Checking for TLSv1.3 ciphersuites require to check that we allow TLSv1.3, otherwise it would
 | |
| 				 * chose an ECDSA cipher because of the TLS13 ciphersuites, but the TLS12 ciphers could
 | |
| 				 * lack ECDSA capabilities.
 | |
| 				 */
 | |
| 				has_ecdsa_sig = 1;
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| sni_lookup:
 | |
| 	/* we need to transform this a NULL-ended string in lowecase */
 | |
| 	for (i = 0; i < trash.size && i < servername_len; i++)
 | |
| 		trash.area[i] = tolower((unsigned char)servername[i]);
 | |
| 	trash.area[i] = 0;
 | |
| 
 | |
| 	HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock);
 | |
| 	sni_ctx = ssl_sock_chose_sni_ctx(s, conn, trash.area, has_rsa_sig, has_ecdsa_sig);
 | |
| 	if (sni_ctx) {
 | |
| 		/* switch ctx */
 | |
| 		struct ssl_bind_conf *conf = sni_ctx->conf;
 | |
| 		ssl_sock_switchctx_set(ssl, sni_ctx->ctx);
 | |
| 		if (conf) {
 | |
| 			methodVersions[conf->ssl_methods.min].ssl_set_version(ssl, SET_MIN);
 | |
| 			methodVersions[conf->ssl_methods.max].ssl_set_version(ssl, SET_MAX);
 | |
| 			if (conf->early_data)
 | |
| 				allow_early = 1;
 | |
| 		}
 | |
| 		HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
 | |
| 		goto allow_early;
 | |
| 	}
 | |
| 
 | |
| 	HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
 | |
| #if (!defined SSL_NO_GENERATE_CERTIFICATES)
 | |
| 	if (s->options & BC_O_GENERATE_CERTS && ssl_sock_generate_certificate(trash.area, s, ssl)) {
 | |
| 		/* switch ctx done in ssl_sock_generate_certificate */
 | |
| 		TRACE_STATE("No context found, generate certificate", SSL_EV_CONN_SWITCHCTX_CB, NULL, ssl, trash.area);
 | |
| 		goto allow_early;
 | |
| 	}
 | |
| #endif
 | |
| 
 | |
| 	if (!(s->ssl_options & BC_SSL_O_STRICT_SNI) && !default_lookup) {
 | |
| 		/* we didn't find a SNI, and we didn't look for a default
 | |
| 		 * look again to find a matching default cert */
 | |
| 		servername = "";
 | |
| 		servername_len = 0;
 | |
| 		default_lookup = 1;
 | |
| 
 | |
| 		TRACE_STATE("Looking for matching default cert", SSL_EV_CONN_SWITCHCTX_CB, conn);
 | |
| 		goto sni_lookup;
 | |
| 	}
 | |
| 
 | |
| 	/* We are about to raise an handshake error so the servername extension
 | |
| 	 * callback will never be called and the SNI will never be stored in the
 | |
| 	 * SSL context. In order for the ssl_fc_sni sample fetch to still work
 | |
| 	 * in such a case, we store the SNI ourselves as an ex_data information
 | |
| 	 * in the SSL context.
 | |
| 	 */
 | |
| 	{
 | |
| 		char *client_sni = pool_alloc(ssl_sock_client_sni_pool);
 | |
| 		if (client_sni) {
 | |
| 			strncpy(client_sni, servername, TLSEXT_MAXLEN_host_name);
 | |
| 			client_sni[TLSEXT_MAXLEN_host_name] = '\0';
 | |
| 			SSL_set_ex_data(ssl, ssl_client_sni_index, client_sni);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* other cases fallback on abort, if strict-sni is set but no node was found */
 | |
| 
 | |
|  abort:
 | |
| 	/* abort handshake (was SSL_TLSEXT_ERR_ALERT_FATAL) */
 | |
| 	if (conn)
 | |
| 		conn->err_code = CO_ER_SSL_HANDSHAKE;
 | |
| 	TRACE_ERROR("No suitable SSL context found", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn, ssl, &conn->err_code);
 | |
| #if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
 | |
| 	return ssl_select_cert_error;
 | |
| #else
 | |
| 	*al = SSL_AD_UNRECOGNIZED_NAME;
 | |
| 	return 0;
 | |
| #endif
 | |
| 
 | |
| allow_early:
 | |
| #if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
 | |
| 	if (allow_early)
 | |
| 		SSL_set_early_data_enabled(ssl, 1);
 | |
| #else
 | |
| 	if (!allow_early)
 | |
| 		SSL_set_max_early_data(ssl, 0);
 | |
| #endif
 | |
| 	TRACE_LEAVE(SSL_EV_CONN_SWITCHCTX_CB, conn, ssl);
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| #else /* ! HAVE_SSL_CLIENT_HELLO_CB  */
 | |
| 
 | |
| /* Sets the SSL ctx of <ssl> to match the advertised server name. Returns a
 | |
|  * warning when no match is found, which implies the default (first) cert
 | |
|  * will keep being used.
 | |
|  */
 | |
| int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv)
 | |
| {
 | |
| 	const char *servername;
 | |
| 	const char *wildp = NULL;
 | |
| 	struct ebmb_node *node, *n;
 | |
| 	struct bind_conf *s = priv;
 | |
| 	int default_lookup = 0; /* did we lookup for a default yet? */
 | |
| #ifdef USE_QUIC
 | |
| 	const uint8_t *extension_data;
 | |
| 	size_t extension_len;
 | |
| 	struct quic_conn *qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index);
 | |
| #endif /* USE_QUIC */
 | |
| 	int i;
 | |
| 	(void)al; /* shut gcc stupid warning */
 | |
| 
 | |
| 	TRACE_ENTER(SSL_EV_CONN_SWITCHCTX_CB);
 | |
| 
 | |
| #ifdef USE_QUIC
 | |
| 	if (qc) {
 | |
| 
 | |
| 		/* Look for the QUIC transport parameters. */
 | |
| 		SSL_get_peer_quic_transport_params(ssl, &extension_data, &extension_len);
 | |
| 		if (extension_len == 0) {
 | |
| 			/* This is not redundant. It we only return 0 without setting
 | |
| 			 * <*al>, this has as side effect to generate another TLS alert
 | |
| 			 * which would be set after calling quic_set_tls_alert().
 | |
| 			 */
 | |
| 			*al = SSL_AD_MISSING_EXTENSION;
 | |
| 			quic_set_tls_alert(qc, SSL_AD_MISSING_EXTENSION);
 | |
| 			return SSL_TLSEXT_ERR_NOACK;
 | |
| 		}
 | |
| 
 | |
| 		if (!quic_transport_params_store(qc, 0, extension_data,
 | |
| 		                                 extension_data + extension_len))
 | |
| 			return SSL_TLSEXT_ERR_NOACK;
 | |
| 
 | |
| 		qc->flags |= QUIC_FL_CONN_TX_TP_RECEIVED;
 | |
| 	}
 | |
| #endif /* USE_QUIC */
 | |
| 
 | |
| 	servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
 | |
| 	if (!servername) {
 | |
| #if (!defined SSL_NO_GENERATE_CERTIFICATES)
 | |
| 		if (s->options & BC_O_GENERATE_CERTS && ssl_sock_generate_certificate_from_conn(s, ssl)) {
 | |
| 			TRACE_STATE("No servername provided, certificate generated", SSL_EV_CONN_SWITCHCTX_CB);
 | |
| 			return SSL_TLSEXT_ERR_OK;
 | |
| 		}
 | |
| #endif
 | |
| 		if (s->ssl_options & BC_SSL_O_STRICT_SNI) {
 | |
| 			TRACE_ERROR("No server_name provided and 'strict-sni' enabled", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR);
 | |
| 			return SSL_TLSEXT_ERR_ALERT_FATAL;
 | |
| 		}
 | |
| 
 | |
| 		/* without servername extension, look for the defaults which is
 | |
| 		 * defined by an empty servername string */
 | |
| 		servername = "";
 | |
| 		default_lookup = 1;
 | |
| 	}
 | |
| 
 | |
| sni_lookup:
 | |
| 
 | |
| 	for (i = 0; i < trash.size; i++) {
 | |
| 		if (!servername[i])
 | |
| 			break;
 | |
| 		trash.area[i] = tolower((unsigned char)servername[i]);
 | |
| 		if (!wildp && (trash.area[i] == '.'))
 | |
| 			wildp = &trash.area[i];
 | |
| 	}
 | |
| 	trash.area[i] = 0;
 | |
| 	if(!*trash.area) /* handle the default which in wildcard tree */
 | |
| 		wildp = trash.area;
 | |
| 
 | |
| 	HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock);
 | |
| 	node = NULL;
 | |
| 	/* lookup in full qualified names */
 | |
| 	TRACE_STATE("Lookup in fully qualified names", SSL_EV_CONN_SWITCHCTX_CB, NULL, ssl, servername);
 | |
| 	for (n = ebst_lookup(&s->sni_ctx, trash.area); n; n = ebmb_next_dup(n)) {
 | |
| 		/* lookup a not neg filter */
 | |
| 		if (!container_of(n, struct sni_ctx, name)->neg) {
 | |
| 			node = n;
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	if (!node && wildp) {
 | |
| 		/* lookup in wildcards names */
 | |
| 		TRACE_STATE("Lookup in wildcard names", SSL_EV_CONN_SWITCHCTX_CB, NULL, ssl, servername);
 | |
| 		for (n = ebst_lookup(&s->sni_w_ctx, wildp); n; n = ebmb_next_dup(n)) {
 | |
| 			/* lookup a not neg filter */
 | |
| 			if (!container_of(n, struct sni_ctx, name)->neg) {
 | |
| 				node = n;
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if (!node) {
 | |
| #if (!defined SSL_NO_GENERATE_CERTIFICATES)
 | |
| 		if (s->options & BC_O_GENERATE_CERTS && ssl_sock_generate_certificate(servername, s, ssl)) {
 | |
| 			/* switch ctx done in ssl_sock_generate_certificate */
 | |
| 			HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
 | |
| 			TRACE_STATE("No context found, generate certificate", SSL_EV_CONN_SWITCHCTX_CB, NULL, ssl, servername);
 | |
| 			return SSL_TLSEXT_ERR_OK;
 | |
| 		}
 | |
| #endif
 | |
| 		HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
 | |
| 
 | |
| 		if (!(s->ssl_options & BC_SSL_O_STRICT_SNI) && !default_lookup) {
 | |
| 			/* we didn't find a SNI, and we didn't look for a default
 | |
| 			 * look again to find a matching default cert */
 | |
| 			servername = "";
 | |
| 			default_lookup = 1;
 | |
| 
 | |
| 			TRACE_STATE("Looking for matching default cert", SSL_EV_CONN_SWITCHCTX_CB);
 | |
| 			goto sni_lookup;
 | |
| 		}
 | |
| 		TRACE_ERROR("No context found", SSL_EV_CONN_SWITCHCTX_CB, NULL, ssl, servername);
 | |
| 		return SSL_TLSEXT_ERR_ALERT_FATAL;
 | |
| 	}
 | |
| 
 | |
| 	/* switch ctx */
 | |
| 	ssl_sock_switchctx_set(ssl, container_of(node, struct sni_ctx, name)->ctx);
 | |
| 	HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
 | |
| 	TRACE_LEAVE(SSL_EV_CONN_SWITCHCTX_CB);
 | |
| 	return SSL_TLSEXT_ERR_OK;
 | |
| }
 | |
| #endif /* (!) OPENSSL_IS_BORINGSSL */
 | |
| 
 | |
| #if defined(USE_OPENSSL_WOLFSSL)
 | |
| /* This implement the equivalent of the clientHello Callback but using the cert_cb.
 | |
|  * WolfSSL is able to extract the sigalgs and ciphers of the client byt using the API
 | |
|  * provided in https://github.com/wolfSSL/wolfssl/pull/6963
 | |
|  *
 | |
|  * Not activated for now since the PR is not merged.
 | |
|  */
 | |
| int ssl_sock_switchctx_wolfSSL_cbk(WOLFSSL* ssl, void* arg)
 | |
| {
 | |
| 	struct connection *conn = SSL_get_ex_data(ssl, ssl_app_data_index);
 | |
| 	struct bind_conf *s = arg;
 | |
| 	int has_rsa_sig = 0, has_ecdsa_sig = 0;
 | |
| 	const char *servername;
 | |
| 	int default_lookup = 0;
 | |
| 	struct sni_ctx *sni_ctx;
 | |
| 	int i;
 | |
| 
 | |
| 	if (!s) {
 | |
| 		/* must never happen */
 | |
| 		ABORT_NOW();
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
 | |
| 	if (!servername) {
 | |
| 		if (s->ssl_options & BC_SSL_O_STRICT_SNI)
 | |
| 			goto abort;
 | |
| 
 | |
| 		/* without servername extension, look for the defaults which is
 | |
| 		 * defined by an empty servername string */
 | |
| 		servername = "";
 | |
| 		default_lookup = 1;
 | |
| 	}
 | |
| 
 | |
| 	/* extract sigalgs and ciphers */
 | |
| 	{
 | |
| 		const byte* suites = NULL;
 | |
| 		word16 suiteSz = 0;
 | |
| 		const byte* hashSigAlgo = NULL;
 | |
| 		word16 hashSigAlgoSz = 0;
 | |
| 		word16 idx = 0;
 | |
| 
 | |
| 		wolfSSL_get_client_suites_sigalgs(ssl, &suites, &suiteSz, &hashSigAlgo, &hashSigAlgoSz);
 | |
| 		if (suites == NULL || suiteSz == 0 || hashSigAlgo == NULL || hashSigAlgoSz == 0)
 | |
| 			return 0;
 | |
| 
 | |
| 		if (SSL_version(ssl) != TLS1_3_VERSION) {
 | |
| 
 | |
| 			/* with TLS <= 1.2, we must use the auth which is provided by the cipher, but we don't need to
 | |
| 			 * consider the auth provided by the signature algorithms */
 | |
| 
 | |
| 			for (idx = 0; idx < suiteSz; idx += 2) {
 | |
| 				WOLFSSL_CIPHERSUITE_INFO info;
 | |
| 				info = wolfSSL_get_ciphersuite_info(suites[idx], suites[idx+1]);
 | |
| 				if (info.rsaAuth)
 | |
| 					has_rsa_sig = 1;
 | |
| 				else if (info.eccAuth)
 | |
| 					has_ecdsa_sig = 1;
 | |
| 			}
 | |
| 		} else {
 | |
| 			/* with TLS >= 1.3, we must use the auth which is provided by the signature algorithms because
 | |
| 			 * the ciphers does not provide the auth */
 | |
| 
 | |
| 			for (idx = 0; idx < hashSigAlgoSz; idx += 2) {
 | |
| 				int hashAlgo;
 | |
| 				int sigAlgo;
 | |
| 
 | |
| 				wolfSSL_get_sigalg_info(hashSigAlgo[idx+0], hashSigAlgo[idx+1], &hashAlgo, &sigAlgo);
 | |
| 
 | |
| 				if (sigAlgo == RSAk || sigAlgo == RSAPSSk)
 | |
| 					has_rsa_sig = 1;
 | |
| 				else if (sigAlgo == ECDSAk)
 | |
| 					has_ecdsa_sig = 1;
 | |
| 
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| sni_lookup:
 | |
| 
 | |
| 	/* we need to transform this into a NULL-ended string in lowecase */
 | |
| 	for (i = 0; i < trash.size && servername[i] != '\0'; i++)
 | |
| 		trash.area[i] = tolower((unsigned char)servername[i]);
 | |
| 	trash.area[i] = 0;
 | |
| 	servername = trash.area;
 | |
| 
 | |
| 	HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock);
 | |
| 	sni_ctx = ssl_sock_chose_sni_ctx(s, conn, servername, has_rsa_sig, has_ecdsa_sig);
 | |
| 	if (sni_ctx) {
 | |
| 		/* switch ctx */
 | |
| 		struct ssl_bind_conf *conf = sni_ctx->conf;
 | |
| 		ssl_sock_switchctx_set(ssl, sni_ctx->ctx);
 | |
| 		if (conf) {
 | |
| 			methodVersions[conf->ssl_methods.min].ssl_set_version(ssl, SET_MIN);
 | |
| 			methodVersions[conf->ssl_methods.max].ssl_set_version(ssl, SET_MAX);
 | |
| 		}
 | |
| 		HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
 | |
| 		goto allow_early;
 | |
| 	}
 | |
| 
 | |
| 	HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
 | |
| 	if (!(s->ssl_options & BC_SSL_O_STRICT_SNI) && !default_lookup) {
 | |
| 		/* we didn't find a SNI, and we didn't look for a default
 | |
| 		 * look again to find a matching default cert */
 | |
| 		servername = "";
 | |
| 		default_lookup = 1;
 | |
| 
 | |
| 		goto sni_lookup;
 | |
| 	}
 | |
| 
 | |
| 	/* We are about to raise an handshake error so the servername extension
 | |
| 	 * callback will never be called and the SNI will never be stored in the
 | |
| 	 * SSL context. In order for the ssl_fc_sni sample fetch to still work
 | |
| 	 * in such a case, we store the SNI ourselves as an ex_data information
 | |
| 	 * in the SSL context.
 | |
| 	 */
 | |
| 	{
 | |
| 		char *client_sni = pool_alloc(ssl_sock_client_sni_pool);
 | |
| 		if (client_sni) {
 | |
| 			strncpy(client_sni, servername, TLSEXT_MAXLEN_host_name);
 | |
| 			client_sni[TLSEXT_MAXLEN_host_name] = '\0';
 | |
| 			SSL_set_ex_data(ssl, ssl_client_sni_index, client_sni);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* other cases fallback on abort, if strict-sni is set but no node was found */
 | |
| 
 | |
|  abort:
 | |
| 	/* abort handshake (was SSL_TLSEXT_ERR_ALERT_FATAL) */
 | |
| 	return 0;
 | |
| 
 | |
| allow_early:
 | |
| 	return 1;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /*
 | |
|  * Local variables:
 | |
|  *  c-indent-level: 8
 | |
|  *  c-basic-offset: 8
 | |
|  * End:
 | |
|  */
 |