haproxy/src/ssl_clienthello.c
Frederic Lecaille 45ac235baa BUG/MEDIUM: quic: Crash after QUIC server callbacks restoration (OpenSSL 3.5)
Revert this patch which is no more useful since OpenSSL 3.5.1 to remove the
QUIC server callback restoration after SSL context switch:

    MINOR: quic: OpenSSL 3.5 internal QUIC custom extension for transport parameters reset

It was required for 3.5.0. That said, there was no CI for OpenSSL 3.5 at the date
of this commit. The CI recently revealed that the QUIC server side could crash
during QUIC reg tests just after having restored the callbacks as implemented by
the commit above.

Also revert this commit which is no more useful because it arrived with the commit
above:

	BUG/MEDIUM: quic: SSL/TCP handshake failures with OpenSSL 3.

Must be backported to 3.2.
2025-07-09 16:01:02 +02:00

771 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.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);
}
/*
* 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) && AWSLC_API_VERSION <= 32
/* 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.
* This was fixed in v1.46.0, API version changed in v1.50.0
*/
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:
*/