From: yanbzhu Date: Wed, 2 Dec 2015 18:01:29 +0000 (-0500) Subject: MEDIUM: ssl: Added support for creating SSL_CTX with multiple certs X-Git-Tag: v1.7-dev1~18 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=08ce6ab0c9fdad9cca599984b94cb58e63191116;p=thirdparty%2Fhaproxy.git MEDIUM: ssl: Added support for creating SSL_CTX with multiple certs Added ability for users to specify multiple certificates that all relate a single server. Users do this by specifying certificate "cert_name.pem" but having "cert_name.pem.rsa", "cert_name.pem.dsa" and/or "cert_name.pem.ecdsa" in the directory. HAProxy will now intelligently search for those 3 files and try combine them into as few SSL_CTX's as possible based on CN/SAN. This will allow HAProxy to support multiple ciphersuite key algorithms off a single SSL_CTX. This change integrates into the existing architecture of SNI lookup and multiple SNI's can point to the same SSL_CTX, which can support multiple key_types. --- diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 2ea7f40772..5226a49625 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -1,3 +1,4 @@ + /* * SSL/TLS transport layer over SOCK_STREAM sockets * @@ -1601,6 +1602,32 @@ struct cert_key_and_chain { X509 **chain_certs; }; +/* The order here matters for picking a default context, + * keep the most common keytype at the bottom of the list + */ +const char *SSL_SOCK_KEYTYPE_NAMES[] = { + "dsa", + "ecdsa", + "rsa" +}; +#define SSL_SOCK_NUM_KEYTYPES 3 +#define SSL_SOCK_POSSIBLE_KT_COMBOS (1<<(SSL_SOCK_NUM_KEYTYPES)) + +struct key_combo_ctx { + SSL_CTX *ctx; + int order; +}; + +/* Map used for processing multiple keypairs for a single purpose + * + * This maps CN/SNI name to certificate type + */ +struct sni_keytype { + int keytypes; /* BITMASK for keytypes */ + struct ebmb_node name; /* node holding the servername value */ +}; + + /* Frees the contents of a cert_key_and_chain */ static void ssl_sock_free_cert_key_and_chain_contents(struct cert_key_and_chain *ckch) @@ -1739,19 +1766,11 @@ static int ssl_sock_put_ckch_into_ctx(const char *path, const struct cert_key_an return 1; } - /* This only happens for OpenSSL Versions < 1.0.2 - * Otherwise ctx->extra_certs will always be NULL - */ - if (ctx->extra_certs != NULL) { - sk_X509_pop_free(ctx->extra_certs, X509_free); - ctx->extra_certs = NULL; - } - /* Load all certs in the ckch into the ctx_chain for the ssl_ctx */ for (i = 0; i < ckch->num_chain_certs; i++) { if (!SSL_CTX_add1_chain_cert(ctx, ckch->chain_certs[i])) { - memprintf(err, "%sunable to load chain certificate into SSL Context '%s'.\n", - err && *err ? *err : "", path); + memprintf(err, "%sunable to load chain certificate #%d into SSL Context '%s'. Make sure you are linking against Openssl >= 1.0.2.\n", + err && *err ? *err : "", (i+1), path); return 1; } } @@ -1765,6 +1784,247 @@ static int ssl_sock_put_ckch_into_ctx(const char *path, const struct cert_key_an return 0; } + +static void ssl_sock_populate_sni_keytypes_hplr(const char *str, struct eb_root *sni_keytypes, int key_index) +{ + struct sni_keytype *s_kt = NULL; + struct ebmb_node *node; + int i; + + for (i = 0; i < trash.size; i++) { + if (!str[i]) + break; + trash.str[i] = tolower(str[i]); + } + trash.str[i] = 0; + node = ebst_lookup(sni_keytypes, trash.str); + if (!node) { + /* CN not found in tree */ + s_kt = malloc(sizeof(struct sni_keytype) + i + 1); + /* Using memcpy here instead of strncpy. + * strncpy will cause sig_abrt errors under certain versions of gcc with -O2 + * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60792 + */ + memcpy(s_kt->name.key, trash.str, i+1); + s_kt->keytypes = 0; + ebst_insert(sni_keytypes, &s_kt->name); + } else { + /* CN found in tree */ + s_kt = container_of(node, struct sni_keytype, name); + } + + /* Mark that this CN has the keytype of key_index via keytypes mask */ + s_kt->keytypes |= 1<value) >= 0) { + /* Important line is here */ + ssl_sock_populate_sni_keytypes_hplr(str, &sni_keytypes_map, n); + + OPENSSL_free(str); + str = NULL; + } + } + + /* Do the above logic for each SAN */ +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + names = X509_get_ext_d2i(certs_and_keys[n].cert, 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) { + /* Important line is here */ + ssl_sock_populate_sni_keytypes_hplr(str, &sni_keytypes_map, n); + + OPENSSL_free(str); + str = NULL; + } + } + } + } +#endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */ + } + + /* If no files found, return error */ + if (eb_is_empty(&sni_keytypes_map)) { + memprintf(err, "%sunable to load SSL certificate file '%s' file does not exist.\n", + err && *err ? *err : "", path); + rv = 1; + goto end; + } + + /* We now have a map of CN/SAN to keytypes that are loaded in + * Iterate through the map to create the SSL_CTX's (if needed) + * and add each CTX to the SNI tree + * + * Some math here: + * There are 2^n - 1 possibile combinations, each unique + * combination is denoted by the key in the map. Each key + * has a value between 1 and 2^n - 1. Conveniently, the array + * of SSL_CTX* is sized 2^n. So, we can simply use the i'th + * entry in the array to correspond to the unique combo (key) + * associated with i. This unique key combo (i) will be associated + * with combos[i-1] + */ + + node = ebmb_first(&sni_keytypes_map); + while (node) { + SSL_CTX *cur_ctx; + + str = (char *)container_of(node, struct sni_keytype, name)->name.key; + i = container_of(node, struct sni_keytype, name)->keytypes; + cur_ctx = key_combos[i-1].ctx; + + if (cur_ctx == NULL) { + /* need to create SSL_CTX */ + cur_ctx = SSL_CTX_new(SSLv23_server_method()); + if (cur_ctx == NULL) { + memprintf(err, "%sunable to allocate SSL context.\n", + err && *err ? *err : ""); + rv = 1; + goto end; + } + + /* Load all info into SSL_CTX */ + for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) { + if (i & (1<= 0) + SSL_CTX_set_ex_data(cur_ctx, ssl_dh_ptr_index, NULL); + + rv = ssl_sock_load_dh_params(cur_ctx, NULL); + if (rv < 0) { + if (err) + memprintf(err, "%sunable to load DH parameters from file '%s'.\n", + *err ? *err : "", path); + rv = 1; + goto end; + } +#endif + + /* Update key_combos */ + key_combos[i-1].ctx = cur_ctx; + } + + /* Update SNI Tree */ + ssl_sock_add_cert_sni(cur_ctx, bind_conf, str, key_combos[i-1].order++); + node = ebmb_next(node); + } + + + /* Mark a default context if none exists, using the ctx that has the most shared keys */ + if (!bind_conf->default_ctx) { + for (i = SSL_SOCK_POSSIBLE_KT_COMBOS - 1; i >= 0; i--) { + if (key_combos[i].ctx) { + bind_conf->default_ctx = key_combos[i].ctx; + break; + } + } + } + +end: + + if (names) + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); + + for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) + ssl_sock_free_cert_key_and_chain_contents(&certs_and_keys[n]); + + node = ebmb_first(&sni_keytypes_map); + while (node) { + next = ebmb_next(node); + ebmb_delete(node); + node = next; + } + + return rv; +} +#else +/* This is a dummy, that just logs an error and returns error */ +static int ssl_sock_load_multi_cert(const char *path, struct bind_conf *bind_conf, struct proxy *curproxy, char **sni_filter, char **err) +{ + memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n", + err && *err ? *err : "", path, strerror(errno)); + return 1; +} + #endif /* #if OPENSSL_VERSION_NUMBER >= 0x1000200fL: Support for loading multiple certs into a single SSL_CTX */ /* Loads a certificate key and CA chain from a file. Returns 0 on error, -1 if @@ -1956,43 +2216,50 @@ int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, struct proxy *cu char fp[MAXPATHLEN+1]; int cfgerr = 0; - if (!(dir = opendir(path))) - return ssl_sock_load_cert_file(path, bind_conf, curproxy, NULL, 0, err); - - /* strip trailing slashes, including first one */ - for (end = path + strlen(path) - 1; end >= path && *end == '/'; end--) - *end = 0; - - n = scandir(path, &de_list, 0, alphasort); - if (n < 0) { - memprintf(err, "%sunable to scan directory '%s' : %s.\n", - err && *err ? *err : "", path, strerror(errno)); - cfgerr++; - } - else { - for (i = 0; i < n; i++) { - struct dirent *de = de_list[i]; + if (stat(path, &buf) == 0) { + dir = opendir(path); + if (!dir) + return ssl_sock_load_cert_file(path, bind_conf, curproxy, NULL, 0, err); - end = strrchr(de->d_name, '.'); - if (end && (!strcmp(end, ".issuer") || !strcmp(end, ".ocsp") || !strcmp(end, ".sctl"))) - goto ignore_entry; + /* strip trailing slashes, including first one */ + for (end = path + strlen(path) - 1; end >= path && *end == '/'; end--) + *end = 0; - snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name); - if (stat(fp, &buf) != 0) { - memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n", - err && *err ? *err : "", fp, strerror(errno)); - cfgerr++; - goto ignore_entry; + n = scandir(path, &de_list, 0, alphasort); + if (n < 0) { + memprintf(err, "%sunable to scan directory '%s' : %s.\n", + err && *err ? *err : "", path, strerror(errno)); + cfgerr++; + } + else { + for (i = 0; i < n; i++) { + struct dirent *de = de_list[i]; + + end = strrchr(de->d_name, '.'); + if (end && (!strcmp(end, ".issuer") || !strcmp(end, ".ocsp") || !strcmp(end, ".sctl"))) + goto ignore_entry; + + snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name); + if (stat(fp, &buf) != 0) { + memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n", + err && *err ? *err : "", fp, strerror(errno)); + cfgerr++; + goto ignore_entry; + } + if (!S_ISREG(buf.st_mode)) + goto ignore_entry; + cfgerr += ssl_sock_load_cert_file(fp, bind_conf, curproxy, NULL, 0, err); +ignore_entry: + free(de); } - if (!S_ISREG(buf.st_mode)) - goto ignore_entry; - cfgerr += ssl_sock_load_cert_file(fp, bind_conf, curproxy, NULL, 0, err); - ignore_entry: - free(de); + free(de_list); } - free(de_list); + closedir(dir); + return cfgerr; } - closedir(dir); + + cfgerr = ssl_sock_load_multi_cert(path, bind_conf, curproxy, NULL, err); + return cfgerr; }