MINOR: ssl: Add traces to the switchctx callback

This callback allows to pick the used certificate on an SSL frontend.
The certificate selection is made according to the information sent by
the client in the clientHello. The traces that were added will allow to
better understand what certificate was chosen and why. It will also warn
us if the chosen certificate was the default one.
The actual certificate parsing happens in ssl_sock_chose_sni_ctx. It's
in this function that we actually get the filename of the certificate
used.
This commit is contained in:
Remi Tricot-Le Breton 2025-04-18 17:26:56 +02:00 committed by William Lallemand
parent dbdd0630e1
commit 105c1ca139
3 changed files with 116 additions and 19 deletions

View File

@ -30,6 +30,8 @@ extern struct trace_source trace_ssl;
#define SSL_EV_CONN_HNDSHK (1ULL << 9) #define SSL_EV_CONN_HNDSHK (1ULL << 9)
#define SSL_EV_CONN_VFY_CB (1ULL << 10) #define SSL_EV_CONN_VFY_CB (1ULL << 10)
#define SSL_EV_CONN_STAPLING (1ULL << 11) #define SSL_EV_CONN_STAPLING (1ULL << 11)
#define SSL_EV_CONN_SWITCHCTX_CB (1ULL << 12)
#define SSL_EV_CONN_CHOOSE_SNI_CTX (1ULL << 13)
#define TRACE_SOURCE &trace_ssl #define TRACE_SOURCE &trace_ssl

View File

@ -20,6 +20,8 @@
#include <haproxy/ssl_ckch.h> #include <haproxy/ssl_ckch.h>
#include <haproxy/ssl_gencert.h> #include <haproxy/ssl_gencert.h>
#include <haproxy/ssl_sock.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) static void ssl_sock_switchctx_set(SSL *ssl, SSL_CTX *ctx)
{ {
@ -41,6 +43,8 @@ struct sni_ctx *ssl_sock_chose_sni_ctx(struct bind_conf *s, const char *serverna
const char *wildp = NULL; const char *wildp = NULL;
int i; int i;
TRACE_ENTER(SSL_EV_CONN_CHOOSE_SNI_CTX, NULL, servername);
/* look for the first dot for wildcard search */ /* look for the first dot for wildcard search */
for (i = 0; servername[i] != '\0'; i++) { for (i = 0; servername[i] != '\0'; i++) {
if (servername[i] == '.') { if (servername[i] == '.') {
@ -102,20 +106,29 @@ struct sni_ctx *ssl_sock_chose_sni_ctx(struct bind_conf *s, const char *serverna
/* Once the certificates are found, select them depending on what is /* Once the certificates are found, select them depending on what is
* supported in the client and by key_signature priority order: EDSA > * supported in the client and by key_signature priority order: EDSA >
* RSA > DSA */ * RSA > DSA */
if (have_ecdsa_sig && node_ecdsa) if (have_ecdsa_sig && node_ecdsa) {
node = node_ecdsa; node = node_ecdsa;
else if (have_rsa_sig && node_rsa) TRACE_STATE("ECDSA node picked", SSL_EV_CONN_CHOOSE_SNI_CTX, NULL, servername, node);
} else if (have_rsa_sig && node_rsa) {
node = node_rsa; node = node_rsa;
else if (node_anonymous) TRACE_STATE("RSA node picked", SSL_EV_CONN_CHOOSE_SNI_CTX, NULL, servername, node);
} else if (node_anonymous) {
node = node_anonymous; node = node_anonymous;
else if (node_ecdsa) TRACE_STATE("Anonymous node picked", SSL_EV_CONN_CHOOSE_SNI_CTX, NULL, servername, node);
} else if (node_ecdsa) {
node = node_ecdsa; /* no ecdsa signature case (< TLSv1.2) */ node = node_ecdsa; /* no ecdsa signature case (< TLSv1.2) */
else TRACE_STATE("ECDSA node picked (< TLSv1.2)", SSL_EV_CONN_CHOOSE_SNI_CTX, NULL, servername, node);
} else {
node = node_rsa; /* no rsa signature case (far far away) */ node = node_rsa; /* no rsa signature case (far far away) */
TRACE_STATE("RSA node picked (fallback)", SSL_EV_CONN_CHOOSE_SNI_CTX, NULL, servername, node);
}
if (node) if (node) {
TRACE_LEAVE(SSL_EV_CONN_CHOOSE_SNI_CTX);
return container_of(node, struct sni_ctx, name); return container_of(node, struct sni_ctx, name);
}
TRACE_STATE("No SNI context found", SSL_EV_CONN_CHOOSE_SNI_CTX);
return NULL; return NULL;
} }
@ -154,6 +167,8 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
int allow_early = 0; int allow_early = 0;
int i; int i;
TRACE_ENTER(SSL_EV_CONN_SWITCHCTX_CB, conn);
if (conn) if (conn)
s = __objt_listener(conn->target)->bind_conf; s = __objt_listener(conn->target)->bind_conf;
#ifdef USE_QUIC #ifdef USE_QUIC
@ -210,40 +225,55 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
* was written, so parsing the normal case is a bit complex. * was written, so parsing the normal case is a bit complex.
*/ */
size_t len; size_t len;
if (extension_len <= 2) if (extension_len <= 2) {
TRACE_ERROR("Server_name parsing error (too short)", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
goto abort; goto abort;
}
/* Extract the length of the supplied list of names. */ /* Extract the length of the supplied list of names. */
len = (*extension_data++) << 8; len = (*extension_data++) << 8;
len |= *extension_data++; len |= *extension_data++;
if (len + 2 != extension_len) 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; goto abort;
}
/* /*
* The list in practice only has a single element, so we only consider * The list in practice only has a single element, so we only consider
* the first one. * the first one.
*/ */
if (len == 0 || *extension_data++ != TLSEXT_NAMETYPE_host_name) 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; goto abort;
}
extension_len = len - 1; extension_len = len - 1;
/* Now we can finally pull out the byte array with the actual hostname. */ /* Now we can finally pull out the byte array with the actual hostname. */
if (extension_len <= 2) 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; goto abort;
}
len = (*extension_data++) << 8; len = (*extension_data++) << 8;
len |= *extension_data++; len |= *extension_data++;
if (len == 0 || len + 2 > extension_len || len > TLSEXT_MAXLEN_host_name if (len == 0 || len + 2 > extension_len || len > TLSEXT_MAXLEN_host_name
|| memchr(extension_data, 0, len) != NULL) || memchr(extension_data, 0, len) != NULL) {
TRACE_ERROR("Server_name parsing error", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
goto abort; goto abort;
}
servername = (char *)extension_data; servername = (char *)extension_data;
servername_len = len; servername_len = len;
TRACE_STATE("Server_name extension parsed", SSL_EV_CONN_SWITCHCTX_CB, conn, ssl, servername, &servername_len);
} else { } else {
TRACE_STATE("No server_name extension", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
#if (!defined SSL_NO_GENERATE_CERTIFICATES) #if (!defined SSL_NO_GENERATE_CERTIFICATES)
if (s->options & BC_O_GENERATE_CERTS && ssl_sock_generate_certificate_from_conn(s, ssl)) { 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; goto allow_early;
} }
#endif #endif
/* no servername field is not compatible with strict-sni */ /* no servername field is not compatible with strict-sni */
if (s->strict_sni) if (s->strict_sni) {
TRACE_ERROR("No server_name provided and 'strict-sni' enabled", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
goto abort; goto abort;
}
/* without servername extension, look for the defaults which is /* without servername extension, look for the defaults which is
* defined by an empty servername string */ * defined by an empty servername string */
@ -261,22 +291,31 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
uint8_t sign; uint8_t sign;
uint8_t hash; uint8_t hash;
size_t len; size_t len;
if (extension_len < 2)
if (extension_len < 2) {
TRACE_ERROR("Sigalg parsing error (too short)", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
goto abort; goto abort;
}
len = (*extension_data++) << 8; len = (*extension_data++) << 8;
len |= *extension_data++; len |= *extension_data++;
if (len + 2 != extension_len) 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; goto abort;
if (len % 2 != 0) }
if (len % 2 != 0) {
TRACE_ERROR("Sigalg parsing error (not even)", SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
goto abort; goto abort;
}
for (; len > 0; len -= 2) { for (; len > 0; len -= 2) {
hash = *extension_data++; /* hash */ hash = *extension_data++; /* hash */
sign = *extension_data++; sign = *extension_data++;
switch (sign) { switch (sign) {
case TLSEXT_signature_rsa: case TLSEXT_signature_rsa:
TRACE_DEVEL("Sigalg parsing: has_rsa_sig", SSL_EV_CONN_SWITCHCTX_CB, conn);
has_rsa_sig = 1; has_rsa_sig = 1;
break; break;
case TLSEXT_signature_ecdsa: case TLSEXT_signature_ecdsa:
TRACE_DEVEL("Sigalg parsing: has_ecdsa_sig", SSL_EV_CONN_SWITCHCTX_CB, conn);
has_ecdsa_sig = 1; has_ecdsa_sig = 1;
break; break;
case 0x04: case 0x04:
@ -288,8 +327,10 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
/* match the RSA-PSS sigalgs from TLSv1.3 /* match the RSA-PSS sigalgs from TLSv1.3
* https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.3 * https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.3
*/ */
if (hash == 0x08) if (hash == 0x08) {
TRACE_DEVEL("Sigalg parsing: has_rsa_sig (RSA-PSS)", SSL_EV_CONN_SWITCHCTX_CB, conn);
has_rsa_sig = 1; has_rsa_sig = 1;
}
break; break;
default: default:
continue; continue;
@ -299,6 +340,7 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
} }
} else { } else {
/* without TLSEXT_TYPE_signature_algorithms extension (< TLSv1.2) */ /* 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; has_rsa_sig = 1;
} }
if (has_ecdsa_sig) { /* in very rare case: has ecdsa sign but not a ECDSA cipher */ if (has_ecdsa_sig) { /* in very rare case: has ecdsa sign but not a ECDSA cipher */
@ -317,8 +359,10 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
#else #else
len = SSL_client_hello_get0_ciphers(ssl, &cipher_suites); len = SSL_client_hello_get0_ciphers(ssl, &cipher_suites);
#endif #endif
if (len % 2 != 0) if (len % 2 != 0) {
TRACE_ERROR("Ciphers parsing error (not even)", SSL_EV_CONN_SWITCHCTX_CB, conn);
goto abort; goto abort;
}
for (; len != 0; len -= 2, cipher_suites += 2) { for (; len != 0; len -= 2, cipher_suites += 2) {
#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) #if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
uint16_t cipher_suite = (cipher_suites[0] << 8) | cipher_suites[1]; uint16_t cipher_suite = (cipher_suites[0] << 8) | cipher_suites[1];
@ -381,6 +425,7 @@ sni_lookup:
#if (!defined SSL_NO_GENERATE_CERTIFICATES) #if (!defined SSL_NO_GENERATE_CERTIFICATES)
if (s->options & BC_O_GENERATE_CERTS && ssl_sock_generate_certificate(trash.area, s, ssl)) { if (s->options & BC_O_GENERATE_CERTS && ssl_sock_generate_certificate(trash.area, s, ssl)) {
/* switch ctx done in ssl_sock_generate_certificate */ /* 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; goto allow_early;
} }
#endif #endif
@ -392,6 +437,7 @@ sni_lookup:
servername_len = 0; servername_len = 0;
default_lookup = 1; default_lookup = 1;
TRACE_STATE("Looking for matching default cert", SSL_EV_CONN_SWITCHCTX_CB, conn);
goto sni_lookup; goto sni_lookup;
} }
@ -416,6 +462,7 @@ sni_lookup:
/* abort handshake (was SSL_TLSEXT_ERR_ALERT_FATAL) */ /* abort handshake (was SSL_TLSEXT_ERR_ALERT_FATAL) */
if (conn) if (conn)
conn->err_code = CO_ER_SSL_HANDSHAKE; 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) #if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
return ssl_select_cert_error; return ssl_select_cert_error;
#else #else
@ -431,6 +478,7 @@ allow_early:
if (!allow_early) if (!allow_early)
SSL_set_max_early_data(ssl, 0); SSL_set_max_early_data(ssl, 0);
#endif #endif
TRACE_LEAVE(SSL_EV_CONN_SWITCHCTX_CB, conn, ssl);
return 1; return 1;
} }
@ -455,6 +503,8 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv)
int i; int i;
(void)al; /* shut gcc stupid warning */ (void)al; /* shut gcc stupid warning */
TRACE_ENTER(SSL_EV_CONN_SWITCHCTX_CB);
#ifdef USE_QUIC #ifdef USE_QUIC
if (qc) { if (qc) {
@ -481,11 +531,15 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv)
servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (!servername) { if (!servername) {
#if (!defined SSL_NO_GENERATE_CERTIFICATES) #if (!defined SSL_NO_GENERATE_CERTIFICATES)
if (s->options & BC_O_GENERATE_CERTS && ssl_sock_generate_certificate_from_conn(s, ssl)) 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; return SSL_TLSEXT_ERR_OK;
}
#endif #endif
if (s->strict_sni) if (s->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; return SSL_TLSEXT_ERR_ALERT_FATAL;
}
/* without servername extension, look for the defaults which is /* without servername extension, look for the defaults which is
* defined by an empty servername string */ * defined by an empty servername string */
@ -509,6 +563,7 @@ sni_lookup:
HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock); HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock);
node = NULL; node = NULL;
/* lookup in full qualified names */ /* 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)) { for (n = ebst_lookup(&s->sni_ctx, trash.area); n; n = ebmb_next_dup(n)) {
/* lookup a not neg filter */ /* lookup a not neg filter */
if (!container_of(n, struct sni_ctx, name)->neg) { if (!container_of(n, struct sni_ctx, name)->neg) {
@ -518,6 +573,7 @@ sni_lookup:
} }
if (!node && wildp) { if (!node && wildp) {
/* lookup in wildcards names */ /* 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)) { for (n = ebst_lookup(&s->sni_w_ctx, wildp); n; n = ebmb_next_dup(n)) {
/* lookup a not neg filter */ /* lookup a not neg filter */
if (!container_of(n, struct sni_ctx, name)->neg) { if (!container_of(n, struct sni_ctx, name)->neg) {
@ -531,6 +587,7 @@ sni_lookup:
if (s->options & BC_O_GENERATE_CERTS && ssl_sock_generate_certificate(servername, s, ssl)) { if (s->options & BC_O_GENERATE_CERTS && ssl_sock_generate_certificate(servername, s, ssl)) {
/* switch ctx done in ssl_sock_generate_certificate */ /* switch ctx done in ssl_sock_generate_certificate */
HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock); 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; return SSL_TLSEXT_ERR_OK;
} }
#endif #endif
@ -542,14 +599,17 @@ sni_lookup:
servername = ""; servername = "";
default_lookup = 1; default_lookup = 1;
TRACE_STATE("Looking for matching default cert", SSL_EV_CONN_SWITCHCTX_CB);
goto sni_lookup; goto sni_lookup;
} }
TRACE_ERROR("No context found", SSL_EV_CONN_SWITCHCTX_CB, NULL, ssl, servername);
return SSL_TLSEXT_ERR_ALERT_FATAL; return SSL_TLSEXT_ERR_ALERT_FATAL;
} }
/* switch ctx */ /* switch ctx */
ssl_sock_switchctx_set(ssl, container_of(node, struct sni_ctx, name)->ctx); ssl_sock_switchctx_set(ssl, container_of(node, struct sni_ctx, name)->ctx);
HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock); HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
TRACE_LEAVE(SSL_EV_CONN_SWITCHCTX_CB);
return SSL_TLSEXT_ERR_OK; return SSL_TLSEXT_ERR_OK;
} }
#endif /* (!) OPENSSL_IS_BORINGSSL */ #endif /* (!) OPENSSL_IS_BORINGSSL */

View File

@ -38,6 +38,8 @@ static const struct trace_event ssl_trace_events[] = {
{ .mask = SSL_EV_CONN_HNDSHK, .name = "sslc_hndshk", .desc = "SSL handshake"}, { .mask = SSL_EV_CONN_HNDSHK, .name = "sslc_hndshk", .desc = "SSL handshake"},
{ .mask = SSL_EV_CONN_VFY_CB, .name = "sslc_vfy_cb", .desc = "SSL verify callback"}, { .mask = SSL_EV_CONN_VFY_CB, .name = "sslc_vfy_cb", .desc = "SSL verify callback"},
{ .mask = SSL_EV_CONN_STAPLING, .name = "sslc_stapling", .desc = "SSL OCSP stapling callback"}, { .mask = SSL_EV_CONN_STAPLING, .name = "sslc_stapling", .desc = "SSL OCSP stapling callback"},
{ .mask = SSL_EV_CONN_SWITCHCTX_CB, .name = "sslc_switchctx_cb", .desc = "SSL switchctx callback"},
{ .mask = SSL_EV_CONN_CHOOSE_SNI_CTX, .name = "sslc_choose_sni_ctx", .desc = "SSL choose sni context"},
{ } { }
}; };
@ -181,5 +183,38 @@ static void ssl_trace(enum trace_level level, uint64_t mask, const struct trace_
} }
} }
} }
if (mask & SSL_EV_CONN_SWITCHCTX_CB) {
if (mask & SSL_EV_CONN_ERR) {
if (a3) {
const unsigned int *err_code = a3;
chunk_appendf(&trace_buf, " err_code=%u err_str=\"%s\"", *err_code, conn_err_code_str(conn));
}
} else if (src->verbosity > SSL_VERB_SIMPLE) {
if (a3) {
const char *servername = a3;
if (a4) {
const size_t *servername_len = a4;
chunk_appendf(&trace_buf, " : servername=\"%.*s\"", (int)*servername_len, servername);
} else {
chunk_appendf(&trace_buf, " : servername=\"%s\"", servername);
}
}
}
}
if (mask & SSL_EV_CONN_CHOOSE_SNI_CTX && src->verbosity > SSL_VERB_ADVANCED) {
if (a2) {
const char *servername = a2;
chunk_appendf(&trace_buf, " : servername=\"%s\"", servername);
}
if (a3) {
const struct ebmb_node *node = a3;
struct sni_ctx *sni_ctx = container_of(node, struct sni_ctx, name);
chunk_appendf(&trace_buf, " crt=\"%s\"", sni_ctx->ckch_inst->ckch_store->path);
}
}
} }