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.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 57ef9c4..69ee1f0 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -7214,6 +7214,20 @@
set to 'all', all errors are ignored. SSL handshake is not abored if an error
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
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,
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index f26813b..90c7232 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -258,10 +258,36 @@
}
#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
* 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;
X509 *x = NULL, *ca;
@@ -270,7 +296,6 @@
int order = 0;
X509_NAME *xname;
char *str;
- struct sni_ctx *sc;
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
STACK_OF(GENERAL_NAME) *names;
#endif
@@ -286,71 +311,43 @@
if (x == NULL)
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
- names = X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL);
- if (names) {
- for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
- GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
- if (name->type == GEN_DNS) {
- if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) {
- if ((len = strlen(str))) {
- int j;
-
- 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);
- }
+ names = X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL);
+ if (names) {
+ for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
+ GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
+ if (name->type == GEN_DNS) {
+ if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) {
+ len = strlen(str);
+ order = ssl_sock_add_cert_sni(ctx, s, str, len, order);
+ OPENSSL_free(str);
}
- OPENSSL_free(str);
}
}
+ sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
}
- sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
- }
#endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */
-
- xname = X509_get_subject_name(x);
- 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);
- if (ASN1_STRING_to_UTF8((unsigned char **)&str, entry->value) >= 0) {
- if ((len = strlen(str))) {
- int j;
-
- 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);
- }
+ xname = X509_get_subject_name(x);
+ 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);
+ if (ASN1_STRING_to_UTF8((unsigned char **)&str, entry->value) >= 0) {
+ len = strlen(str);
+ order = ssl_sock_add_cert_sni(ctx, s, str, len, order);
+ OPENSSL_free(str);
}
- OPENSSL_free(str);
}
}
@@ -387,7 +384,7 @@
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;
SSL_CTX *ctx;
@@ -406,7 +403,7 @@
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) {
memprintf(err, "%sunable to load SSL certificate from PEM file '%s'.\n",
err && *err ? *err : "", path);
@@ -457,7 +454,7 @@
int cfgerr = 0;
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 */
for (end = path + strlen(path) - 1; end >= path && *end == '/'; end--)
@@ -473,7 +470,7 @@
}
if (!S_ISREG(buf.st_mode))
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);
return cfgerr;
@@ -495,6 +492,120 @@
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 */
#define SSL_OP_CIPHER_SERVER_PREFERENCE 0
#endif
@@ -2388,6 +2499,20 @@
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 */
static int bind_parse_crl_file(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
{
@@ -2929,6 +3054,7 @@
{ "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-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 */
{ "force-sslv3", bind_parse_force_sslv3, 0 }, /* force SSLv3 */
{ "force-tlsv10", bind_parse_force_tlsv10, 0 }, /* force TLSv10 */