MEDIUM: ssl: improve crt-list format to support negation

Improve the crt-list file format to allow a rule to negate a certain SNI :

        <crtfile> [[!]<snifilter> ...]

This can be useful when a domain supports a wildcard but you don't want to
deliver the wildcard cert for certain specific domains.
This commit is contained in:
Emmanuel Hocdet 2013-05-07 20:20:06 +02:00 committed by Willy Tarreau
parent 2b5702030d
commit 7c41a1b59b
3 changed files with 58 additions and 46 deletions

View File

@ -7224,17 +7224,19 @@ crt-ignore-err <errors>
crt-list <file> crt-list <file>
This setting is only available when support for OpenSSL was built in. It This setting is only available when support for OpenSSL was built in. It
designates a list of PEM file with an optional SNI filter per certificate, designates a list of PEM file with an optional list of SNI filter per
with the following format for each line : certificate, with the following format for each line :
<crtfile> [<snifilter>] <crtfile> [[!]<snifilter> ...]
Wildcards are supported in the SNI filter. The certificates will be presented Wildcards are supported in the SNI filter. Negative filter are also supported,
to clients who provide a valid TLS Server Name Indication field matching one only useful in combination with a wildcard filter to exclude a particular SNI.
of SNI filter. If no SNI filter is specified the CN and alt subjects are The certificates will be presented to clients who provide a valid TLS Server
used. This directive may be specified multiple times. See the "crt" option Name Indication field matching one of the SNI filters. If no SNI filter is
for more information. The default certificate is still needed to meet OpenSSL specified, the CN and alt subjects are used. This directive may be specified
expectations. If it is not used, the strict-sni option may be used. multiple times. See the "crt" option for more information. The default
certificate is still needed to meet OpenSSL expectations. If it is not used,
the 'strict-sni' option may be used.
defer-accept defer-accept
Is an optional keyword which is supported only on certain Linux kernels. It Is an optional keyword which is supported only on certain Linux kernels. It

View File

@ -28,6 +28,7 @@
struct sni_ctx { struct sni_ctx {
SSL_CTX *ctx; /* context associated to the certificate */ SSL_CTX *ctx; /* context associated to the certificate */
int order; /* load order for the certificate */ int order; /* load order for the certificate */
int neg; /* reject if match */
struct ebmb_node name; /* node holding the servername value */ struct ebmb_node name; /* node holding the servername value */
}; };

View File

@ -189,16 +189,15 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, struct bind_conf *s)
{ {
const char *servername; const char *servername;
const char *wildp = NULL; const char *wildp = NULL;
struct ebmb_node *node; struct ebmb_node *node, *n;
int i; int i;
(void)al; /* shut gcc stupid warning */ (void)al; /* shut gcc stupid warning */
servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (!servername) { if (!servername) {
if (s->strict_sni) return (s->strict_sni ?
return SSL_TLSEXT_ERR_ALERT_FATAL; SSL_TLSEXT_ERR_ALERT_FATAL :
else SSL_TLSEXT_ERR_ALERT_WARNING);
return SSL_TLSEXT_ERR_NOACK;
} }
for (i = 0; i < trash.size; i++) { for (i = 0; i < trash.size; i++) {
@ -212,25 +211,27 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, struct bind_conf *s)
/* lookup in full qualified names */ /* lookup in full qualified names */
node = ebst_lookup(&s->sni_ctx, trash.str); node = ebst_lookup(&s->sni_ctx, trash.str);
if (!node) {
if (!wildp) { /* lookup a not neg filter */
if (s->strict_sni) for (n = node; n; n = ebmb_next_dup(n)) {
return SSL_TLSEXT_ERR_ALERT_FATAL; if (!container_of(n, struct sni_ctx, name)->neg) {
else node = n;
return SSL_TLSEXT_ERR_ALERT_WARNING; break;
} }
/* lookup in full wildcards names */ wildp = NULL; /* never match a wildcard after matching a neg */
}
if (!node && wildp) {
/* lookup in wildcards names */
node = ebst_lookup(&s->sni_w_ctx, wildp); node = ebst_lookup(&s->sni_w_ctx, wildp);
if (!node) { }
if (s->strict_sni) if (!node || container_of(node, struct sni_ctx, name)->neg) {
return SSL_TLSEXT_ERR_ALERT_FATAL; return (s->strict_sni ?
else SSL_TLSEXT_ERR_ALERT_FATAL :
return SSL_TLSEXT_ERR_ALERT_WARNING; SSL_TLSEXT_ERR_ALERT_WARNING);
}
} }
/* switch ctx */ /* switch ctx */
SSL_set_SSL_CTX(ssl, container_of(node, struct sni_ctx, name)->ctx); SSL_set_SSL_CTX(ssl, container_of(node, struct sni_ctx, name)->ctx);
return SSL_TLSEXT_ERR_OK; return SSL_TLSEXT_ERR_OK;
} }
#endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */ #endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */
@ -309,24 +310,32 @@ end:
} }
#endif #endif
int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, char *name, int len, int order) static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, char *name, int order)
{ {
struct sni_ctx *sc; struct sni_ctx *sc;
int wild = 0; int wild = 0, neg = 0;
int j;
if (len) { if (*name == '!') {
if (*name == '*') { neg = 1;
wild = 1; name++;
name++; }
len--; if (*name == '*') {
} wild = 1;
name++;
}
/* !* filter is a nop */
if (neg && wild)
return order;
if (*name) {
int j, len;
len = strlen(name);
sc = malloc(sizeof(struct sni_ctx) + len + 1); sc = malloc(sizeof(struct sni_ctx) + len + 1);
for (j = 0; j < len; j++) for (j = 0; j < len; j++)
sc->name.key[j] = tolower(name[j]); sc->name.key[j] = tolower(name[j]);
sc->name.key[len] = 0; sc->name.key[len] = 0;
sc->order = order++;
sc->ctx = ctx; sc->ctx = ctx;
sc->order = order++;
sc->neg = neg;
if (wild) if (wild)
ebst_insert(&s->sni_w_ctx, &sc->name); ebst_insert(&s->sni_w_ctx, &sc->name);
else else
@ -338,11 +347,11 @@ int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, char *name, int len
/* Loads a certificate key and CA chain from a file. Returns 0 on error, -1 if /* Loads a certificate key and CA chain from a file. Returns 0 on error, -1 if
* an early error happens and the caller must call SSL_CTX_free() by itelf. * an early error happens and the caller must call SSL_CTX_free() by itelf.
*/ */
int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct bind_conf *s, char **sni_filter, int fcount) static int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct bind_conf *s, char **sni_filter, int fcount)
{ {
BIO *in; BIO *in;
X509 *x = NULL, *ca; X509 *x = NULL, *ca;
int i, len, err; int i, err;
int ret = -1; int ret = -1;
int order = 0; int order = 0;
X509_NAME *xname; X509_NAME *xname;
@ -364,7 +373,7 @@ int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct bind_co
if (fcount) { if (fcount) {
while (fcount--) while (fcount--)
order = ssl_sock_add_cert_sni(ctx, s, sni_filter[fcount], strlen(sni_filter[fcount]), order); order = ssl_sock_add_cert_sni(ctx, s, sni_filter[fcount], order);
} }
else { else {
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
@ -374,8 +383,7 @@ int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct bind_co
GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i); GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
if (name->type == GEN_DNS) { if (name->type == GEN_DNS) {
if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) { if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) {
len = strlen(str); order = ssl_sock_add_cert_sni(ctx, s, str, order);
order = ssl_sock_add_cert_sni(ctx, s, str, len, order);
OPENSSL_free(str); OPENSSL_free(str);
} }
} }
@ -388,8 +396,7 @@ int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct bind_co
while ((i = X509_NAME_get_index_by_NID(xname, NID_commonName, i)) != -1) { while ((i = X509_NAME_get_index_by_NID(xname, NID_commonName, i)) != -1) {
X509_NAME_ENTRY *entry = X509_NAME_get_entry(xname, i); X509_NAME_ENTRY *entry = X509_NAME_get_entry(xname, i);
if (ASN1_STRING_to_UTF8((unsigned char **)&str, entry->value) >= 0) { if (ASN1_STRING_to_UTF8((unsigned char **)&str, entry->value) >= 0) {
len = strlen(str); order = ssl_sock_add_cert_sni(ctx, s, str, order);
order = ssl_sock_add_cert_sni(ctx, s, str, len, order);
OPENSSL_free(str); OPENSSL_free(str);
} }
} }
@ -538,7 +545,7 @@ static int ssl_initialize_random()
int ssl_sock_load_cert_list_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, char **err) int ssl_sock_load_cert_list_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, char **err)
{ {
char thisline[65536]; char thisline[LINESIZE];
FILE *f; FILE *f;
int linenum = 0; int linenum = 0;
int cfgerr = 0; int cfgerr = 0;
@ -591,6 +598,8 @@ int ssl_sock_load_cert_list_file(char *file, struct bind_conf *bind_conf, struct
} }
line++; line++;
} }
if (cfgerr)
break;
/* empty line */ /* empty line */
if (!arg) if (!arg)