mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-01-10 16:00:08 +00:00
MEDIUM: ssl: add mapping from SNI to cert file using "crt-list"
It designates a list of PEM file with an optional list of SNI filter per certificate, with the following format for each line : <crtfile>[ <snifilter>]* Wildcards are supported in the SNI filter. The certificates will be presented to clients who provide a valid TLS Server Name Indication field matching one of SNI filter. If no SNI filter is specified the CN and alt subjects are used.
This commit is contained in:
parent
47f922dd24
commit
fe61656bb2
@ -7214,6 +7214,20 @@ crt-ignore-err <errors>
|
|||||||
set to 'all', all errors are ignored. SSL handshake is not abored if an error
|
set to 'all', all errors are ignored. SSL handshake is not abored if an error
|
||||||
is ignored.
|
is ignored.
|
||||||
|
|
||||||
|
crt-list <file>
|
||||||
|
This setting is only available when support for OpenSSL was built in. It
|
||||||
|
designates a list of PEM file with an optional list of SNI filter per
|
||||||
|
certificate, with the following format for each line :
|
||||||
|
|
||||||
|
<crtfile>[ <snifilter>]*
|
||||||
|
|
||||||
|
Wildcards are supported in the SNI filter. The certificates will be presented
|
||||||
|
to clients who provide a valid TLS Server Name Indication field matching one
|
||||||
|
of SNI filter. If no SNI filter is specified the CN and alt subjects are
|
||||||
|
used. This directive may be specified 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
|
||||||
states that a connection will only be accepted once some data arrive on it,
|
states that a connection will only be accepted once some data arrive on it,
|
||||||
|
228
src/ssl_sock.c
228
src/ssl_sock.c
@ -258,10 +258,36 @@ end:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, char *name, int len, int order)
|
||||||
|
{
|
||||||
|
struct sni_ctx *sc;
|
||||||
|
int wild = 0;
|
||||||
|
int j;
|
||||||
|
|
||||||
|
if (len) {
|
||||||
|
if (*name == '*') {
|
||||||
|
wild = 1;
|
||||||
|
name++;
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
sc = malloc(sizeof(struct sni_ctx) + len + 1);
|
||||||
|
for (j = 0; j < len; j++)
|
||||||
|
sc->name.key[j] = tolower(name[j]);
|
||||||
|
sc->name.key[len] = 0;
|
||||||
|
sc->order = order++;
|
||||||
|
sc->ctx = ctx;
|
||||||
|
if (wild)
|
||||||
|
ebst_insert(&s->sni_w_ctx, &sc->name);
|
||||||
|
else
|
||||||
|
ebst_insert(&s->sni_ctx, &sc->name);
|
||||||
|
}
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
/* 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)
|
int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct bind_conf *s, char *sni_filter)
|
||||||
{
|
{
|
||||||
BIO *in;
|
BIO *in;
|
||||||
X509 *x = NULL, *ca;
|
X509 *x = NULL, *ca;
|
||||||
@ -270,7 +296,6 @@ int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct bind_co
|
|||||||
int order = 0;
|
int order = 0;
|
||||||
X509_NAME *xname;
|
X509_NAME *xname;
|
||||||
char *str;
|
char *str;
|
||||||
struct sni_ctx *sc;
|
|
||||||
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
|
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
|
||||||
STACK_OF(GENERAL_NAME) *names;
|
STACK_OF(GENERAL_NAME) *names;
|
||||||
#endif
|
#endif
|
||||||
@ -286,6 +311,18 @@ int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct bind_co
|
|||||||
if (x == NULL)
|
if (x == NULL)
|
||||||
goto end;
|
goto end;
|
||||||
|
|
||||||
|
if (sni_filter) {
|
||||||
|
while (*sni_filter) {
|
||||||
|
while (isspace(*sni_filter))
|
||||||
|
sni_filter++;
|
||||||
|
str = sni_filter;
|
||||||
|
while (!isspace(*sni_filter) && *sni_filter)
|
||||||
|
sni_filter++;
|
||||||
|
len = sni_filter - str;
|
||||||
|
order = ssl_sock_add_cert_sni(ctx, s, str, len, order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
|
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
|
||||||
names = X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL);
|
names = X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL);
|
||||||
if (names) {
|
if (names) {
|
||||||
@ -293,28 +330,8 @@ 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) {
|
||||||
if ((len = strlen(str))) {
|
len = strlen(str);
|
||||||
int j;
|
order = ssl_sock_add_cert_sni(ctx, s, str, len, order);
|
||||||
|
|
||||||
if (*str != '*') {
|
|
||||||
sc = malloc(sizeof(struct sni_ctx) + len + 1);
|
|
||||||
for (j = 0; j < len; j++)
|
|
||||||
sc->name.key[j] = tolower(str[j]);
|
|
||||||
sc->name.key[len] = 0;
|
|
||||||
sc->order = order++;
|
|
||||||
sc->ctx = ctx;
|
|
||||||
ebst_insert(&s->sni_ctx, &sc->name);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sc = malloc(sizeof(struct sni_ctx) + len);
|
|
||||||
for (j = 1; j < len; j++)
|
|
||||||
sc->name.key[j-1] = tolower(str[j]);
|
|
||||||
sc->name.key[len-1] = 0;
|
|
||||||
sc->order = order++;
|
|
||||||
sc->ctx = ctx;
|
|
||||||
ebst_insert(&s->sni_w_ctx, &sc->name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OPENSSL_free(str);
|
OPENSSL_free(str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -322,37 +339,17 @@ int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct bind_co
|
|||||||
sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
|
sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
|
||||||
}
|
}
|
||||||
#endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */
|
#endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */
|
||||||
|
|
||||||
xname = X509_get_subject_name(x);
|
xname = X509_get_subject_name(x);
|
||||||
i = -1;
|
i = -1;
|
||||||
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) {
|
||||||
if ((len = strlen(str))) {
|
len = strlen(str);
|
||||||
int j;
|
order = ssl_sock_add_cert_sni(ctx, s, str, len, order);
|
||||||
|
|
||||||
if (*str != '*') {
|
|
||||||
sc = malloc(sizeof(struct sni_ctx) + len + 1);
|
|
||||||
for (j = 0; j < len; j++)
|
|
||||||
sc->name.key[j] = tolower(str[j]);
|
|
||||||
sc->name.key[len] = 0;
|
|
||||||
sc->order = order++;
|
|
||||||
sc->ctx = ctx;
|
|
||||||
ebst_insert(&s->sni_ctx, &sc->name);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sc = malloc(sizeof(struct sni_ctx) + len);
|
|
||||||
for (j = 1; j < len; j++)
|
|
||||||
sc->name.key[j-1] = tolower(str[j]);
|
|
||||||
sc->name.key[len-1] = 0;
|
|
||||||
sc->order = order++;
|
|
||||||
sc->ctx = ctx;
|
|
||||||
ebst_insert(&s->sni_w_ctx, &sc->name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OPENSSL_free(str);
|
OPENSSL_free(str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ret = 0; /* the caller must not free the SSL_CTX argument anymore */
|
ret = 0; /* the caller must not free the SSL_CTX argument anymore */
|
||||||
if (!SSL_CTX_use_certificate(ctx, x))
|
if (!SSL_CTX_use_certificate(ctx, x))
|
||||||
@ -387,7 +384,7 @@ end:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ssl_sock_load_cert_file(const char *path, struct bind_conf *bind_conf, struct proxy *curproxy, char **err)
|
static int ssl_sock_load_cert_file(const char *path, struct bind_conf *bind_conf, struct proxy *curproxy, char *sni_filter, char **err)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
SSL_CTX *ctx;
|
SSL_CTX *ctx;
|
||||||
@ -406,7 +403,7 @@ static int ssl_sock_load_cert_file(const char *path, struct bind_conf *bind_conf
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = ssl_sock_load_cert_chain_file(ctx, path, bind_conf);
|
ret = ssl_sock_load_cert_chain_file(ctx, path, bind_conf, sni_filter);
|
||||||
if (ret <= 0) {
|
if (ret <= 0) {
|
||||||
memprintf(err, "%sunable to load SSL certificate from PEM file '%s'.\n",
|
memprintf(err, "%sunable to load SSL certificate from PEM file '%s'.\n",
|
||||||
err && *err ? *err : "", path);
|
err && *err ? *err : "", path);
|
||||||
@ -457,7 +454,7 @@ int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, struct proxy *cu
|
|||||||
int cfgerr = 0;
|
int cfgerr = 0;
|
||||||
|
|
||||||
if (!(dir = opendir(path)))
|
if (!(dir = opendir(path)))
|
||||||
return ssl_sock_load_cert_file(path, bind_conf, curproxy, err);
|
return ssl_sock_load_cert_file(path, bind_conf, curproxy, NULL, err);
|
||||||
|
|
||||||
/* strip trailing slashes, including first one */
|
/* strip trailing slashes, including first one */
|
||||||
for (end = path + strlen(path) - 1; end >= path && *end == '/'; end--)
|
for (end = path + strlen(path) - 1; end >= path && *end == '/'; end--)
|
||||||
@ -473,7 +470,7 @@ int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, struct proxy *cu
|
|||||||
}
|
}
|
||||||
if (!S_ISREG(buf.st_mode))
|
if (!S_ISREG(buf.st_mode))
|
||||||
continue;
|
continue;
|
||||||
cfgerr += ssl_sock_load_cert_file(fp, bind_conf, curproxy, err);
|
cfgerr += ssl_sock_load_cert_file(fp, bind_conf, curproxy, NULL, err);
|
||||||
}
|
}
|
||||||
closedir(dir);
|
closedir(dir);
|
||||||
return cfgerr;
|
return cfgerr;
|
||||||
@ -495,6 +492,120 @@ static int ssl_initialize_random()
|
|||||||
return random_initialized;
|
return random_initialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ssl_sock_load_cert_list_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, char **err)
|
||||||
|
{
|
||||||
|
char thisline[65536];
|
||||||
|
FILE *f;
|
||||||
|
int linenum = 0;
|
||||||
|
int cfgerr = 0;
|
||||||
|
char *sni_filter = NULL;
|
||||||
|
char *crt_file = NULL;
|
||||||
|
|
||||||
|
if ((f = fopen(file, "r")) == NULL)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
while (fgets(thisline, sizeof(thisline), f) != NULL) {
|
||||||
|
int arg;
|
||||||
|
char *end;
|
||||||
|
char *args[MAX_LINE_ARGS + 1];
|
||||||
|
char *line = thisline;
|
||||||
|
|
||||||
|
linenum++;
|
||||||
|
end = line + strlen(line);
|
||||||
|
if (end-line == sizeof(thisline)-1 && *(end-1) != '\n') {
|
||||||
|
/* Check if we reached the limit and the last char is not \n.
|
||||||
|
* Watch out for the last line without the terminating '\n'!
|
||||||
|
*/
|
||||||
|
Alert("parsing [%s:%d]: line too long, limit: %d.\n",
|
||||||
|
file, linenum, (int)sizeof(thisline)-1);
|
||||||
|
cfgerr = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* skip leading spaces */
|
||||||
|
while (isspace(*line))
|
||||||
|
line++;
|
||||||
|
|
||||||
|
arg = 0;
|
||||||
|
args[arg] = line;
|
||||||
|
|
||||||
|
while (*line && arg < MAX_LINE_ARGS) {
|
||||||
|
if (*line == '#' || *line == '\n' || *line == '\r') {
|
||||||
|
/* end of string, end of loop */
|
||||||
|
*line = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (isspace(*line)) {
|
||||||
|
/* a non-escaped space is an argument separator */
|
||||||
|
*line++ = '\0';
|
||||||
|
while (isspace(*line))
|
||||||
|
line++;
|
||||||
|
args[++arg] = line;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (*line) {
|
||||||
|
if (*line == '#' || *line == '\n' || *line == '\r') {
|
||||||
|
/* end of string, end of loop */
|
||||||
|
*line = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* empty line */
|
||||||
|
if (!**args)
|
||||||
|
continue;
|
||||||
|
if (*line) {
|
||||||
|
/* we had to stop due to too many args.
|
||||||
|
* Let's terminate the string, print the offending part then cut the
|
||||||
|
* last arg.
|
||||||
|
*/
|
||||||
|
while (*line && *line != '#' && *line != '\n' && *line != '\r')
|
||||||
|
line++;
|
||||||
|
*line = '\0';
|
||||||
|
|
||||||
|
Alert("parsing [%s:%d]: line too long, truncating at word %d, position %ld: <%s>.\n",
|
||||||
|
file, linenum, arg + 1, (long)(args[arg] - thisline + 1), args[arg]);
|
||||||
|
cfgerr = 1;
|
||||||
|
args[arg] = line;
|
||||||
|
}
|
||||||
|
/* zero out remaining args and ensure that at least one entry
|
||||||
|
* is zeroed out.
|
||||||
|
*/
|
||||||
|
while (++arg <= MAX_LINE_ARGS) {
|
||||||
|
args[arg] = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
crt_file = strdup(args[0]);
|
||||||
|
if (*args[1])
|
||||||
|
sni_filter = strdup(args[1]);
|
||||||
|
cfgerr = ssl_sock_load_cert_file(crt_file, bind_conf, curproxy, sni_filter, err);
|
||||||
|
|
||||||
|
if (sni_filter) {
|
||||||
|
free(sni_filter);
|
||||||
|
sni_filter = NULL;
|
||||||
|
}
|
||||||
|
if (crt_file) {
|
||||||
|
free(crt_file);
|
||||||
|
crt_file = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfgerr)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (sni_filter)
|
||||||
|
free(sni_filter);
|
||||||
|
if (crt_file)
|
||||||
|
free(crt_file);
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
return cfgerr;
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef SSL_OP_CIPHER_SERVER_PREFERENCE /* needs OpenSSL >= 0.9.7 */
|
#ifndef SSL_OP_CIPHER_SERVER_PREFERENCE /* needs OpenSSL >= 0.9.7 */
|
||||||
#define SSL_OP_CIPHER_SERVER_PREFERENCE 0
|
#define SSL_OP_CIPHER_SERVER_PREFERENCE 0
|
||||||
#endif
|
#endif
|
||||||
@ -2388,6 +2499,20 @@ static int bind_parse_crt(char **args, int cur_arg, struct proxy *px, struct bin
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* parse the "crt-list" bind keyword */
|
||||||
|
static int bind_parse_crt_list(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
|
||||||
|
{
|
||||||
|
if (!*args[cur_arg + 1]) {
|
||||||
|
memprintf(err, "'%s' : missing certificate location", args[cur_arg]);
|
||||||
|
return ERR_ALERT | ERR_FATAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ssl_sock_load_cert_list_file(args[cur_arg + 1], conf, px, err) > 0)
|
||||||
|
return ERR_ALERT | ERR_FATAL;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* parse the "crl-file" bind keyword */
|
/* parse the "crl-file" bind keyword */
|
||||||
static int bind_parse_crl_file(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
|
static int bind_parse_crl_file(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
|
||||||
{
|
{
|
||||||
@ -2929,6 +3054,7 @@ static struct bind_kw_list bind_kws = { "SSL", { }, {
|
|||||||
{ "crl-file", bind_parse_crl_file, 1 }, /* set certificat revocation list file use on client cert verify */
|
{ "crl-file", bind_parse_crl_file, 1 }, /* set certificat revocation list file use on client cert verify */
|
||||||
{ "crt", bind_parse_crt, 1 }, /* load SSL certificates from this location */
|
{ "crt", bind_parse_crt, 1 }, /* load SSL certificates from this location */
|
||||||
{ "crt-ignore-err", bind_parse_ignore_err, 1 }, /* set error IDs to ingore on verify depth == 0 */
|
{ "crt-ignore-err", bind_parse_ignore_err, 1 }, /* set error IDs to ingore on verify depth == 0 */
|
||||||
|
{ "crt-list", bind_parse_crt_list, 1 }, /* load a list of crt from this location */
|
||||||
{ "ecdhe", bind_parse_ecdhe, 1 }, /* defines named curve for elliptic curve Diffie-Hellman */
|
{ "ecdhe", bind_parse_ecdhe, 1 }, /* defines named curve for elliptic curve Diffie-Hellman */
|
||||||
{ "force-sslv3", bind_parse_force_sslv3, 0 }, /* force SSLv3 */
|
{ "force-sslv3", bind_parse_force_sslv3, 0 }, /* force SSLv3 */
|
||||||
{ "force-tlsv10", bind_parse_force_tlsv10, 0 }, /* force TLSv10 */
|
{ "force-tlsv10", bind_parse_force_tlsv10, 0 }, /* force TLSv10 */
|
||||||
|
Loading…
Reference in New Issue
Block a user