mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-09-21 13:51:26 +02:00
MEDIUM: ssl: allow multiple fallback certificate to allow ECDSA/RSA selection
This patch changes the default certificate mechanism. Since the beginning of SSL in HAProxy, the default certificate was the first certificate of a bind line. This allowed to fallback on this certificate when no servername extension was sent by the server, or when no SAN nor CN was available in the certificate. When using a multi-certificate bundle (ecdsa+rsa), it was possible to have both certificates as the fallback one, leting openssl chose the right one. This was possible because a multi-certificate bundle was generating a unique SSL_CTX for both certificates. When the haproxy and openssl architecture evolved, we decided to use multiple SSL_CTX for a multi-cert bundle, in order to simplify the code and allow updates over the CLI. However only one default_ctx was allowed, so we lost the ability to chose between ECDSA and RSA for the default certificate. This patch allows to use a '*' filter for a certificate, which allow to lookup between multiple '*' filter, and have one in RSA and another one in ECDSA. It replaces the default_ctx mechanism in the ClientHello callback and use the standard algorithm to look for a default cert and chose between ECDSA and RSA. /!\ This patch breaks the automatic setting of the default certificate, which will be introduce in the next patch. So the first certificate of a bind line won't be used as a defaullt anymore. To use this feature, one could use crt-list with '*' filters: $ cat foo.crtlist foobar.pem.rsa * foobar.pem.ecdsa * In order to test the feature, it's easy to send a request without the servername extension and use ECDSA or RSA compatible ciphers: $ openssl s_client -connect localhost:8443 -tls1_2 -cipher ECDHE-RSA-AES256-GCM-SHA384 $ openssl s_client -connect localhost:8443 -tls1_2 -cipher ECDHE-ECDSA-AES256-GCM-SHA384
This commit is contained in:
parent
333f2cabab
commit
30592168e5
115
src/ssl_sock.c
115
src/ssl_sock.c
@ -2346,6 +2346,9 @@ static __maybe_unused struct sni_ctx *ssl_sock_chose_sni_ctx(struct bind_conf *s
|
|||||||
break;
|
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
|
/* Look for an ECDSA, RSA and DSA certificate, first in the single
|
||||||
* name and if not found in the wildcard */
|
* name and if not found in the wildcard */
|
||||||
@ -2444,7 +2447,8 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
|
|||||||
int has_rsa_sig = 0, has_ecdsa_sig = 0;
|
int has_rsa_sig = 0, has_ecdsa_sig = 0;
|
||||||
struct sni_ctx *sni_ctx;
|
struct sni_ctx *sni_ctx;
|
||||||
const char *servername;
|
const char *servername;
|
||||||
size_t servername_len;
|
size_t servername_len = 0;
|
||||||
|
int default_lookup = 0; /* did we lookup for a default yet? */
|
||||||
int allow_early = 0;
|
int allow_early = 0;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
@ -2532,14 +2536,16 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
|
|||||||
goto allow_early;
|
goto allow_early;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
/* without SNI extension, is the default_ctx (need SSL_TLSEXT_ERR_NOACK) */
|
|
||||||
if (!s->strict_sni) {
|
/* no servername field is not compatible with strict-sni */
|
||||||
HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock);
|
if (s->strict_sni)
|
||||||
ssl_sock_switchctx_set(ssl, s->default_ctx);
|
|
||||||
HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
|
|
||||||
goto allow_early;
|
|
||||||
}
|
|
||||||
goto abort;
|
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 */
|
/* extract/check clientHello information */
|
||||||
@ -2615,14 +2621,14 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sni_lookup:
|
||||||
/* we need to transform this a NULL-ended string in lowecase */
|
/* we need to transform this a NULL-ended string in lowecase */
|
||||||
for (i = 0; i < trash.size && i < servername_len; i++)
|
for (i = 0; i < trash.size && i < servername_len; i++)
|
||||||
trash.area[i] = tolower(servername[i]);
|
trash.area[i] = tolower(servername[i]);
|
||||||
trash.area[i] = 0;
|
trash.area[i] = 0;
|
||||||
servername = trash.area;
|
|
||||||
|
|
||||||
HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock);
|
HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock);
|
||||||
sni_ctx = ssl_sock_chose_sni_ctx(s, servername, has_rsa_sig, has_ecdsa_sig);
|
sni_ctx = ssl_sock_chose_sni_ctx(s, trash.area, has_rsa_sig, has_ecdsa_sig);
|
||||||
if (sni_ctx) {
|
if (sni_ctx) {
|
||||||
/* switch ctx */
|
/* switch ctx */
|
||||||
struct ssl_bind_conf *conf = sni_ctx->conf;
|
struct ssl_bind_conf *conf = sni_ctx->conf;
|
||||||
@ -2639,17 +2645,20 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
|
|||||||
|
|
||||||
HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
|
HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
|
||||||
#if (!defined SSL_NO_GENERATE_CERTIFICATES)
|
#if (!defined SSL_NO_GENERATE_CERTIFICATES)
|
||||||
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(trash.area, s, ssl)) {
|
||||||
/* switch ctx done in ssl_sock_generate_certificate */
|
/* switch ctx done in ssl_sock_generate_certificate */
|
||||||
goto allow_early;
|
goto allow_early;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (!s->strict_sni) {
|
|
||||||
/* no certificate match, is the default_ctx */
|
if (!s->strict_sni && !default_lookup) {
|
||||||
HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock);
|
/* we didn't find a SNI, and we didn't look for a default
|
||||||
ssl_sock_switchctx_set(ssl, s->default_ctx);
|
* look again to find a matching default cert */
|
||||||
HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
|
servername = "";
|
||||||
goto allow_early;
|
servername_len = 0;
|
||||||
|
default_lookup = 1;
|
||||||
|
|
||||||
|
goto sni_lookup;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We are about to raise an handshake error so the servername extension
|
/* We are about to raise an handshake error so the servername extension
|
||||||
@ -2703,6 +2712,7 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv)
|
|||||||
const char *wildp = NULL;
|
const char *wildp = NULL;
|
||||||
struct ebmb_node *node, *n;
|
struct ebmb_node *node, *n;
|
||||||
struct bind_conf *s = priv;
|
struct bind_conf *s = priv;
|
||||||
|
int default_lookup = 0; /* did we lookup for a default yet? */
|
||||||
#ifdef USE_QUIC
|
#ifdef USE_QUIC
|
||||||
const uint8_t *extension_data;
|
const uint8_t *extension_data;
|
||||||
size_t extension_len;
|
size_t extension_len;
|
||||||
@ -2742,12 +2752,15 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv)
|
|||||||
#endif
|
#endif
|
||||||
if (s->strict_sni)
|
if (s->strict_sni)
|
||||||
return SSL_TLSEXT_ERR_ALERT_FATAL;
|
return SSL_TLSEXT_ERR_ALERT_FATAL;
|
||||||
HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock);
|
|
||||||
ssl_sock_switchctx_set(ssl, s->default_ctx);
|
/* without servername extension, look for the defaults which is
|
||||||
HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
|
* defined by an empty servername string */
|
||||||
return SSL_TLSEXT_ERR_NOACK;
|
servername = "";
|
||||||
|
default_lookup = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sni_lookup:
|
||||||
|
|
||||||
for (i = 0; i < trash.size; i++) {
|
for (i = 0; i < trash.size; i++) {
|
||||||
if (!servername[i])
|
if (!servername[i])
|
||||||
break;
|
break;
|
||||||
@ -2756,6 +2769,8 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv)
|
|||||||
wildp = &trash.area[i];
|
wildp = &trash.area[i];
|
||||||
}
|
}
|
||||||
trash.area[i] = 0;
|
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);
|
HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock);
|
||||||
node = NULL;
|
node = NULL;
|
||||||
@ -2785,13 +2800,17 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv)
|
|||||||
return SSL_TLSEXT_ERR_OK;
|
return SSL_TLSEXT_ERR_OK;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (s->strict_sni) {
|
|
||||||
HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
|
HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
|
||||||
return SSL_TLSEXT_ERR_ALERT_FATAL;
|
|
||||||
|
if (!s->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;
|
||||||
}
|
}
|
||||||
ssl_sock_switchctx_set(ssl, s->default_ctx);
|
return SSL_TLSEXT_ERR_ALERT_FATAL;
|
||||||
HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
|
|
||||||
return SSL_TLSEXT_ERR_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* switch ctx */
|
/* switch ctx */
|
||||||
@ -2814,6 +2833,7 @@ static int ssl_sock_switchctx_wolfSSL_cbk(WOLFSSL* ssl, void* arg)
|
|||||||
struct bind_conf *s = arg;
|
struct bind_conf *s = arg;
|
||||||
int has_rsa_sig = 0, has_ecdsa_sig = 0;
|
int has_rsa_sig = 0, has_ecdsa_sig = 0;
|
||||||
const char *servername;
|
const char *servername;
|
||||||
|
int default_lookup = 0;
|
||||||
struct sni_ctx *sni_ctx;
|
struct sni_ctx *sni_ctx;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
@ -2825,14 +2845,13 @@ static int ssl_sock_switchctx_wolfSSL_cbk(WOLFSSL* ssl, void* arg)
|
|||||||
|
|
||||||
servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
|
servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
|
||||||
if (!servername) {
|
if (!servername) {
|
||||||
/* without SNI extension, is the default_ctx (need SSL_TLSEXT_ERR_NOACK) */
|
if (s->strict_sni)
|
||||||
if (!s->strict_sni) {
|
|
||||||
HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock);
|
|
||||||
ssl_sock_switchctx_set(ssl, s->default_ctx);
|
|
||||||
HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
|
|
||||||
goto allow_early;
|
|
||||||
}
|
|
||||||
goto abort;
|
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 */
|
/* extract sigalgs and ciphers */
|
||||||
@ -2876,6 +2895,8 @@ static int ssl_sock_switchctx_wolfSSL_cbk(WOLFSSL* ssl, void* arg)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sni_lookup:
|
||||||
|
|
||||||
/* we need to transform this into a NULL-ended string in lowecase */
|
/* we need to transform this into a NULL-ended string in lowecase */
|
||||||
for (i = 0; i < trash.size && servername[i] != '\0'; i++)
|
for (i = 0; i < trash.size && servername[i] != '\0'; i++)
|
||||||
trash.area[i] = tolower(servername[i]);
|
trash.area[i] = tolower(servername[i]);
|
||||||
@ -2897,12 +2918,13 @@ static int ssl_sock_switchctx_wolfSSL_cbk(WOLFSSL* ssl, void* arg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
|
HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
|
||||||
if (!s->strict_sni) {
|
if (!s->strict_sni && !default_lookup) {
|
||||||
/* no certificate match, is the default_ctx */
|
/* we didn't find a SNI, and we didn't look for a default
|
||||||
HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock);
|
* look again to find a matching default cert */
|
||||||
ssl_sock_switchctx_set(ssl, s->default_ctx);
|
servername = "";
|
||||||
HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
|
default_lookup = 1;
|
||||||
goto allow_early;
|
|
||||||
|
goto sni_lookup;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We are about to raise an handshake error so the servername extension
|
/* We are about to raise an handshake error so the servername extension
|
||||||
@ -3316,7 +3338,7 @@ static int ckch_inst_add_cert_sni(SSL_CTX *ctx, struct ckch_inst *ckch_inst,
|
|||||||
struct pkey_info kinfo, char *name, int order)
|
struct pkey_info kinfo, char *name, int order)
|
||||||
{
|
{
|
||||||
struct sni_ctx *sc;
|
struct sni_ctx *sc;
|
||||||
int wild = 0, neg = 0;
|
int wild = 0, neg = 0, default_crt = 0;
|
||||||
|
|
||||||
if (*name == '!') {
|
if (*name == '!') {
|
||||||
neg = 1;
|
neg = 1;
|
||||||
@ -3325,11 +3347,14 @@ static int ckch_inst_add_cert_sni(SSL_CTX *ctx, struct ckch_inst *ckch_inst,
|
|||||||
if (*name == '*') {
|
if (*name == '*') {
|
||||||
wild = 1;
|
wild = 1;
|
||||||
name++;
|
name++;
|
||||||
|
/* if this was only a '*' filter, this is a default cert */
|
||||||
|
if (!*name)
|
||||||
|
default_crt = 1;
|
||||||
}
|
}
|
||||||
/* !* filter is a nop */
|
/* !* filter is a nop */
|
||||||
if (neg && wild)
|
if (neg && wild)
|
||||||
return order;
|
return order;
|
||||||
if (*name) {
|
if (*name || default_crt) {
|
||||||
int j, len;
|
int j, len;
|
||||||
len = strlen(name);
|
len = strlen(name);
|
||||||
for (j = 0; j < len && j < trash.size; j++)
|
for (j = 0; j < len && j < trash.size; j++)
|
||||||
@ -3401,14 +3426,6 @@ void ssl_sock_load_cert_sni(struct ckch_inst *ckch_inst, struct bind_conf *bind_
|
|||||||
else
|
else
|
||||||
ebst_insert(&bind_conf->sni_ctx, &sc0->name);
|
ebst_insert(&bind_conf->sni_ctx, &sc0->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* replace the default_ctx if required with the instance's ctx. */
|
|
||||||
if (ckch_inst->is_default) {
|
|
||||||
SSL_CTX_free(bind_conf->default_ctx);
|
|
||||||
SSL_CTX_up_ref(ckch_inst->ctx);
|
|
||||||
bind_conf->default_ctx = ckch_inst->ctx;
|
|
||||||
bind_conf->default_inst = ckch_inst;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user