On 08/21/2015 02:53 PM, Willy Tarreau wrote: > Guys, > > I'm reviving this thread here. It turns out that Emeric had some concerns > about possible impacts of the patch, maybe in part due to the openssl API > though I'm not sure I understood everything right. I found it more efficient > to involve all people who expressed opinions in this thread so that we end > up with a solution which satisfies everyone. > > Thanks, > Willy > >
Yeah, I will be easier to understand with the latest submitted patchset. I've already made some comments to Dave in private. but i need your advice. Dave, I've just noticed you loose the management of the password callback reading the BIO file. I'm not sure if there is an impact on passphrase protected files. Code initially use 'ctx->default_passwd_callback_userdata' to allow us to manage a further way to manage passphrase via configuration. I notice also you continue to load DH parameters for each files. It was not a big deal by the past because for each file correspond a uniq certificate. With your patch, i don't know what will be the behavior. In the way it is loaded on SSL_CTX, the DH parameter doesn't seem specific to the used DSA/RSA/ECDSA certificate. So which one will be used, first or latest loaded? Some users will expect to use the DH parameter defined in the same file than the used certificate, but i'm really not sure it will be the case. Finally, i don't understand what will be the behavior about the certificate chain with openssl < 1.0.2. I see you manage to load the chain differently depending the version of openssl but i ignore if the behavior will differ. Emeric
>From ab919d36c8d0665602eb5797d508f07ffd24de3b Mon Sep 17 00:00:00 2001 From: yanbzhu <[email protected]> Date: Thu, 13 Aug 2015 13:42:17 -0400 Subject: MINOR: ssl: Added keytype fields to sni_ctx Added KeyType fields to sni_ctx and added functions to get and set these fields from the sni_text. This will be used in conjuction with a later commit to support multiple key types in a single context. --- include/types/ssl_sock.h | 3 +++ src/ssl_sock.c | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 0 deletions(-) diff --git a/include/types/ssl_sock.h b/include/types/ssl_sock.h index e71ba79..dcf00fc 100644 --- a/include/types/ssl_sock.h +++ b/include/types/ssl_sock.h @@ -29,6 +29,9 @@ struct sni_ctx { SSL_CTX *ctx; /* context associated to the certificate */ int order; /* load order for the certificate */ int neg; /* reject if match */ + short has_rsa_cert; /* 1 if CTX contains an RSA certificate */ + short has_dsa_cert; /* 1 if CTX contains an DSA certificate */ + short has_ecc_cert; /* 1 if CTX contains an ECC certificate */ struct ebmb_node name; /* node holding the servername value */ }; diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 2b91eed..37898d7 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -1505,6 +1505,44 @@ end: } #endif +/* Updates a sni_ctx* to indicate that it contains a pkey/cert pair + * of the given keytype + */ +static void ssl_sock_update_sni_ctx_keytype(struct sni_ctx *sc, int cert_keytype) +{ + switch(cert_keytype){ + case EVP_PKEY_RSA: + sc->has_rsa_cert = 1; + break; + case EVP_PKEY_DSA: + sc->has_dsa_cert = 1; + break; + case EVP_PKEY_EC: + sc->has_ecc_cert = 1; + break; + } +} + +/* Checks a ebmb_node to see if the sni_ctx inside has a cert of the + * given keytype + * + * Returns: + * 1 If found + * 0 if not found + */ +static int ssl_sock_node_contains_keytype(struct ebmb_node *node, int keytype){ + switch(keytype){ + case EVP_PKEY_RSA: + return container_of(node, struct sni_ctx, name)->has_rsa_cert; + case EVP_PKEY_DSA: + return container_of(node, struct sni_ctx, name)->has_dsa_cert; + case EVP_PKEY_EC: + return container_of(node, struct sni_ctx, name)->has_ecc_cert; + } + return 0; + +} + static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, char *name, int order) { struct sni_ctx *sc; @@ -1531,6 +1569,9 @@ static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, char *name, sc->ctx = ctx; sc->order = order++; sc->neg = neg; + sc->has_rsa_cert = 0; + sc->has_dsa_cert = 0; + sc->has_ecc_cert = 0; if (wild) ebst_insert(&s->sni_w_ctx, &sc->name); else -- 1.7.1
>From 118d28066fd5856c92c91fd35da98ba4b0e6f132 Mon Sep 17 00:00:00 2001 From: yanbzhu <[email protected]> Date: Thu, 13 Aug 2015 13:53:47 -0400 Subject: MINOR: ssl: Added cert_key_and_chain struct Added cert_key_and_chain struct to ssl. This struct will store the contents of a crt path (from the config file) into memory. This will allow us to use the data stored in memory instead of reading the file multiple times. This will be used to support a later commit to load multiple pkeys/certs into a single SSL_CTX --- src/ssl_sock.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 124 insertions(+), 0 deletions(-) diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 37898d7..2c13797 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -165,6 +165,20 @@ struct certificate_ocsp { long expire; }; +/* This is used to preload the certifcate, private key + * and Cert Chain of a file passed in via the crt + * argument + * + * This way, we do not have to read the file multiple times + */ +struct cert_key_and_chain{ + X509* cert; + EVP_PKEY* key; + unsigned int num_chain_certs; + // This is an array of X509* + X509 ** chain_certs; +}; + /* * This function returns the number of seconds elapsed * since the Epoch, 1970-01-01 00:00:00 +0000 (UTC) and the @@ -1543,6 +1557,116 @@ static int ssl_sock_node_contains_keytype(struct ebmb_node *node, int keytype){ } +/* Frees the contents of a cert_key_and_chain + */ +static void ssl_sock_free_cert_key_and_chain_contents(struct cert_key_and_chain* ckc){ + int i; + if(!ckc) + return; + + // Free the certificate and set pointer to NULL + if(ckc->cert) + X509_free(ckc->cert); + ckc->cert = NULL; + + // Free the key and set pointer to NULL + if(ckc->key) + EVP_PKEY_free(ckc->key); + ckc->key = NULL; + + // Free each certificate in the chain + for(i = 0; i < ckc->num_chain_certs; i++){ + if(ckc->chain_certs[i]) + X509_free(ckc->chain_certs[i]); + } + + // Free the chain obj itself and set to NULL + if(ckc->num_chain_certs > 0){ + free(ckc->chain_certs); + ckc->num_chain_certs = 0; + ckc->chain_certs = NULL; + } + +} + + +/* Loads the contents of a crt file (path) into a cert_key_and_chain + * This allows us to carry the contents of the file without having to + * read the file multiple times. + * + * returns: + * 0 on Success + * 1 on Failure + */ +static int ssl_sock_load_crt_file_into_ckch(const char *path, struct cert_key_and_chain* ckch, char**err){ + + BIO *in; + X509 *ca = NULL; + int ret = 1; + + ssl_sock_free_cert_key_and_chain_contents(ckch); + + + in = BIO_new(BIO_s_file()); + if (in == NULL) + goto end; + + if (BIO_read_filename(in, path) <= 0) + goto end; + + // Read Certificate + ckch->cert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL); + if (ckch->cert == NULL){ + memprintf(err, "%sunable to load certificate from file '%s'.\n", + err && *err ? *err : "", path); + goto end; + } + + // Read Private Key + ckch->key = PEM_read_bio_PrivateKey(in,NULL,NULL,NULL); + if(ckch->key == NULL){ + memprintf(err, "%sunable to load private key from file '%s'.\n", + err && *err ? *err : "", path); + goto end; + } + + // Read Certificate Chain + while ((ca = PEM_read_bio_X509(in, NULL, NULL, NULL))) { + + if(ca != NULL){ + // Grow the chain certs + ckch->num_chain_certs++; + ckch->chain_certs = realloc(ckch->chain_certs,(ckch->num_chain_certs * sizeof(X509*))); + + // use - 1 here since we just incremented it above + ckch->chain_certs[ckch->num_chain_certs - 1] = ca; + } + } + + ret = ERR_get_error(); + if (ret && (ERR_GET_LIB(ret) != ERR_LIB_PEM && ERR_GET_REASON(ret) != PEM_R_NO_START_LINE)){ + memprintf(err, "%sunable to load certificate chain from file '%s'.\n", + err && *err ? *err : "", path); + goto end; + } + ERR_clear_error(); + + ret = 0; + +end: + + if (in) + BIO_free(in); + + // Something went wrong in one of the reads + if(ret != 0){ + ssl_sock_free_cert_key_and_chain_contents(ckch); + } + + return ret; +} + + static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, char *name, int order) { struct sni_ctx *sc; -- 1.7.1
>From dcde33ca63a725003393c7f3eb3e3be491edf34d Mon Sep 17 00:00:00 2001 From: yanbzhu <[email protected]> Date: Thu, 13 Aug 2015 15:02:49 -0400 Subject: MEDIUM: ssl: Changed logic for loading crt files Changed the way that crt files are loaded. Previous logic: Read the crt file, create a new ctx for the file, add that ctx to the sni trees for every CN/SAN. New Logic: Load the information of the file into a local storage structure. Create a SSL_CTX based off the contents of the structure. Iterate through the CN/SANs and add an entry into the sni trees for each CN/SAN. This may look pretty identical to the previous logic, but it's needed to support a later commit to add different key types to each SSL_CTX. For the interim time, in order to make the diff more readable, the functionality of ssl_sock_load_cert_file is replaced by ssl_sock_process_crt_file. --- src/ssl_sock.c | 308 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 files changed, 301 insertions(+), 7 deletions(-) diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 2c13797..deaf080 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -1667,7 +1667,144 @@ end: } -static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, char *name, int order) +/* Because the way we add certs to the chain differs between OpenSSL 1.0.2 vs + * earlier versions, this funciton wraps that functionality + * + * Returns + * 1 on success + * 0 on failure + * + */ +static int ssl_sock_add_cert_to_ctx_chain(SSL_CTX* ctx, X509* cert){ + +#if OPENSSL_VERSION_NUMBER >= 0x1000200fL + // SSL_CTX_add1_chain_cert was added in 1.0.2 + return SSL_CTX_add1_chain_cert(ctx,cert); +#else + int rv = SSL_CTX_add_extra_chain_cert(ctx,cert); + if(rv == 1) { + // SSL_CTX_add_extra_chain_cert does not upref cert, upref here + CRYPTO_add(&cert->references, 1, CRYPTO_LOCK_X509); + } + return rv; +#endif +} + +/* Loads the contents of a cert_key_and_chain into an SSL_CTX. This will: + * + * 1) Use the Private Key + * 2) Use the Certificate + * 3) Load the certificate chain + * 4) Validate the private key against the certificate + * + * It will also load some global informatio into the CTX: + * 5) Loads the custom DH Param (if one exists) + * + * It also loads some information from the crt path (filepath): + * 6) Loads OCSP Information (if it exists in the path) + * 7) loads sctl information + * + * Returns: + * 1 on Failure + * 0 on Success + */ +// yanbzhu: TODO: Used to be called ssl_sock_load_into_ctx +static int ssl_sock_populate_ctx(const char* path, const struct cert_key_and_chain* ckch, SSL_CTX* ctx, char **err) +{ + int ret = 0; + int i = 0; + + if (SSL_CTX_use_PrivateKey(ctx, ckch->key) <= 0) { + memprintf(err, "%sunable to load SSL private key into SSL Context '%s'.\n", + err && *err ? *err : "", path); + return 1; + } + + if (!SSL_CTX_use_certificate(ctx, ckch->cert)){ + memprintf(err, "%sunable to load SSL certificate into SSL Context '%s'.\n", + err && *err ? *err : "", path); + 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_sock_add_cert_to_ctx_chain(ctx,ckch->chain_certs[i])){ + memprintf(err, "%sunable to load chain certificate into SSL Context '%s'.\n", + err && *err ? *err : "", path); + return 1; + } + } + + if (SSL_CTX_check_private_key(ctx) <= 0) { + memprintf(err, "%sinconsistencies between private key and certificate loaded from PEM file '%s'.\n", + err && *err ? *err : "", path); + return 1; + } + + +#ifndef OPENSSL_NO_DH + /* store a NULL pointer to indicate we have not yet loaded + a custom DH param file */ + if (ssl_dh_ptr_index >= 0) { + SSL_CTX_set_ex_data(ctx, ssl_dh_ptr_index, NULL); + } + + ret = ssl_sock_load_dh_params(ctx, path); + if (ret < 0) { + if (err) + memprintf(err, "%sunable to load DH parameters from file '%s'.\n", + *err ? *err : "", path); + return 1; + } +#endif + +#if (defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) + ret = ssl_sock_load_ocsp(ctx, path); + if (ret < 0) { + if (err) + memprintf(err, "%s '%s.ocsp' is present and activates OCSP but it is impossible to compute the OCSP certificate ID (maybe the issuer could not be found)'.\n", + *err ? *err : "", path); + return 1; + } +#endif + +#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL) + if (sctl_ex_index >= 0) { + ret = ssl_sock_load_sctl(ctx, path); + if (ret < 0) { + if (err) + memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n", + *err ? *err : "", path); + return 1; + } + } +#endif + + return 0; +} + +/* Creates a new SSL_CTX and logs an error if something gets fubar'd + * */ +static SSL_CTX* ssl_sock_new_ssl_ctx(char** err){ + SSL_CTX* ctx = NULL; + ctx = SSL_CTX_new(SSLv23_server_method()); + if (!ctx) { + memprintf(err, "%sunable to allocate SSL context\n", + err && *err ? *err : ""); + return NULL; + } + + return ctx; +} + +static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, char *name, int order, int keytype) { struct sni_ctx *sc; int wild = 0, neg = 0; @@ -1696,6 +1833,9 @@ static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, char *name, sc->has_rsa_cert = 0; sc->has_dsa_cert = 0; sc->has_ecc_cert = 0; + + ssl_sock_update_sni_ctx_keytype(sc,keytype); + if (wild) ebst_insert(&s->sni_w_ctx, &sc->name); else @@ -1704,6 +1844,159 @@ static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, char *name, return order; } +/* Wraps the processing of an SSL_CTX given a SNI entry and the SSL Data + * Return Values: + * 0 = No Error + * 1 = Error in OpenSSL Calls + * + * Note that the default_ctx is passed in as a **, + * this function will allocate a new SSL_CTX if the + * SNI entry is not found in the tree and the *default_ctx is NULL. + * It will then update *default_ctx + * */ + +// YANBZHU TODO: All the eb tree code was removed, so add it back in a later commit +static int ssl_sock_process_sni_for_ctx(const char *path, const struct cert_key_and_chain* ckch, struct bind_conf *bind_conf, SSL_CTX** default_ctx, char *sni_name, char **err) +{ + int key_type = 0; + SSL_CTX* ctx = NULL; + int order = 0; + + if( *default_ctx == NULL) + { + /* No previous SNI in tree and no new ctx for this file yet */ + ctx = ssl_sock_new_ssl_ctx(err); + if (ctx == NULL) + return 1; + + if (ssl_sock_populate_ctx(path,ckch,ctx,err) != 0) { + memprintf(err, "%sUnable to load cert file into SSL Context '%s'.\n", + err && *err ? *err : "", path); + SSL_CTX_free(ctx); + return 1; + } + + /* Ctx is now valid */ + *default_ctx = ctx; + } + + key_type = EVP_PKEY_id(X509_get_pubkey(ckch->cert)); + + order = 0; + order = ssl_sock_add_cert_sni(ctx, bind_conf, sni_name, order, key_type); + + return 0; +} +/* Processes a crt file from the config. Takes the file and loads the pkey/cert/chain plus any + * additional ocsp/sctl info into a SSL Context. Then, add the SSL_CTX to the sni tree + * + */ +static int ssl_sock_process_crt_file(const char *path, struct bind_conf *bind_conf, struct proxy *curproxy, char **sni_filter, int fcount, char **err) +{ + int ret = 1; + SSL_CTX *new_ctx = NULL; + X509_NAME *xname = NULL; + char *str=NULL; + int i; + int order = 0; + struct cert_key_and_chain ckch = {0}; +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + STACK_OF(GENERAL_NAME) *names = NULL; +#endif + + // We use this instead of SSL_CTX_use_certificate_chain_file + // to avoid doing multiple disk reads for each CN/SAN for the SNI tree + // So we preload this file into memory here + ret = ssl_sock_load_crt_file_into_ckch(path,&ckch,err); + if(ret != 0) + goto end; + + /* fcount indicates that the user provided an SNI for the list of certs, just use that one instead of CN/SAN */ + if (fcount) { + int keytype = 0; + new_ctx = ssl_sock_new_ssl_ctx(err); + if(!new_ctx) + goto end; + + ret = ssl_sock_populate_ctx(path,&ckch,new_ctx,err); + + if (ret != 0) { + memprintf(err, "%sUnable to load cert file into SSL Context '%s'.\n", + err && *err ? *err : "", path); + SSL_CTX_free(new_ctx); + goto end; + } + + // Load each user specified SNI into the tree + keytype = EVP_PKEY_id(X509_get_pubkey(ckch.cert)); + while (fcount--) + order = ssl_sock_add_cert_sni(new_ctx, bind_conf, sni_filter[fcount], order, keytype); + + ret = 0; + goto end; + } + + /* Load each CN into the SNI Tree + */ + xname = X509_get_subject_name(ckch.cert); + 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(ssl_sock_process_sni_for_ctx(path,&ckch,bind_conf,&new_ctx,str,err) != 0) + goto end; + + OPENSSL_free(str); + str = NULL; + +#ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME + if (bind_conf->default_ctx) { + memprintf(err, "%sthis version of openssl cannot load multiple SSL certificates.\n", + err && *err ? *err : ""); + goto end; + } +#endif + + if (!bind_conf->default_ctx) + bind_conf->default_ctx = new_ctx; + } + } + + /* Do the above logic for each SAN */ +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + names = X509_get_ext_d2i(ckch.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) { + if(ssl_sock_process_sni_for_ctx(path,&ckch,bind_conf,&new_ctx,str,err) != 0) + goto end; + + OPENSSL_free(str); + str = NULL; + } + } + } + } +#endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */ + + ret = 0; + +end: + + if(names) + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); + + if(str) + OPENSSL_free(str); + + ssl_sock_free_cert_key_and_chain_contents(&ckch); + + return ret; +} + + /* 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. */ @@ -1733,7 +2026,8 @@ static int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct if (fcount) { while (fcount--) - order = ssl_sock_add_cert_sni(ctx, s, sni_filter[fcount], order); + order = ssl_sock_add_cert_sni(ctx, s, sni_filter[fcount], order, EVP_PKEY_RSA); + // EVP_PKEY_RSA to make the compiler happy, this function isn't used, same with 2 below } else { #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME @@ -1743,7 +2037,7 @@ static int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct 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) { - order = ssl_sock_add_cert_sni(ctx, s, str, order); + order = ssl_sock_add_cert_sni(ctx, s, str, order, EVP_PKEY_RSA); OPENSSL_free(str); } } @@ -1756,7 +2050,7 @@ static int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct 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) { - order = ssl_sock_add_cert_sni(ctx, s, str, order); + order = ssl_sock_add_cert_sni(ctx, s, str, order, EVP_PKEY_RSA); OPENSSL_free(str); } } @@ -1894,7 +2188,7 @@ int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, struct proxy *cu int cfgerr = 0; if (!(dir = opendir(path))) - return ssl_sock_load_cert_file(path, bind_conf, curproxy, NULL, 0, err); + return ssl_sock_process_crt_file(path, bind_conf, curproxy, NULL, 0, err); /* strip trailing slashes, including first one */ for (end = path + strlen(path) - 1; end >= path && *end == '/'; end--) @@ -1923,7 +2217,7 @@ int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, struct proxy *cu } if (!S_ISREG(buf.st_mode)) goto ignore_entry; - cfgerr += ssl_sock_load_cert_file(fp, bind_conf, curproxy, NULL, 0, err); + cfgerr += ssl_sock_process_crt_file(fp, bind_conf, curproxy, NULL, 0, err); ignore_entry: free(de); } @@ -2011,7 +2305,7 @@ int ssl_sock_load_cert_list_file(char *file, struct bind_conf *bind_conf, struct if (!arg) continue; - cfgerr = ssl_sock_load_cert_file(args[0], bind_conf, curproxy, &args[1], arg-1, err); + cfgerr = ssl_sock_process_crt_file(args[0], bind_conf, curproxy, &args[1], arg-1, err); if (cfgerr) { memprintf(err, "error processing line %d in file '%s' : %s", linenum, file, *err); break; -- 1.7.1
>From a4383e0c0bbed3da4b053194547128cf26467d4a Mon Sep 17 00:00:00 2001 From: yanbzhu <[email protected]> Date: Thu, 13 Aug 2015 15:21:54 -0400 Subject: MEDIUM: ssl: Load Multiple Keys into SSL_CTX If using OpenSSL >= 1.0.2, added capability to load different key types (RSA, DSA, ECDSA) into a single SSL_CTX. Look in the sni tree for an existing SSL_CTX for each CN/SAN name. If that context is found, it will check for the existence of a key of the current crt's keytype in the SSL_CTX. If one is found, it will do nothing and not load the crt into the SSL_CTX. If the keytype is not present, it will load the info of the crt into the SSL_CTX. This will allow a single SSL_CTX to serve cipher-suites of different keytypes. --- src/ssl_sock.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 files changed, 83 insertions(+), 7 deletions(-) diff --git a/src/ssl_sock.c b/src/ssl_sock.c index deaf080..ca5b3dc 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -1708,7 +1708,6 @@ static int ssl_sock_add_cert_to_ctx_chain(SSL_CTX* ctx, X509* cert){ * 1 on Failure * 0 on Success */ -// yanbzhu: TODO: Used to be called ssl_sock_load_into_ctx static int ssl_sock_populate_ctx(const char* path, const struct cert_key_and_chain* ckch, SSL_CTX* ctx, char **err) { int ret = 0; @@ -1844,7 +1843,45 @@ static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, char *name, return order; } +#if OPENSSL_VERSION_NUMBER >= 0x1000200fL +/* Searches the sni eb-tree for a node associated with the + * given SNI name + */ +static struct ebmb_node* ssl_sock_get_ctx_from_eb_tree(struct bind_conf* bind_conf, const char* name){ + + struct ebmb_node *node = NULL; + int i; + + for (i = 0; i < trash.size; i++) { + if (!name[i]) + break; + trash.str[i] = tolower(name[i]); + } + trash.str[i] = 0; + + node = ebst_lookup(&bind_conf->sni_ctx, trash.str); + if(node) + return node; + + node = ebst_lookup(&bind_conf->sni_w_ctx, trash.str); + + return node; +} +#endif /* OPENSSL_VERSION_NUMBER >= 0x1000200fL */ + /* Wraps the processing of an SSL_CTX given a SNI entry and the SSL Data + * + * If using OpenSSL >= 1.0.2, it will look in the sni tree for an existing + * SSL_CTX for the given SNI name. If that context is found, it will check + * for the existence of a key of the keytype in ckch in the SSL_CTX. If one + * is found, it will do nothing and not load the ckch into the SSL_CTX. If + * the keytype is not present, it will load the info of the ckch into the + * SSL_CTX. This will allow for multiple keys/cert/chains for each SSL_CTX and + * will allow a single SSL_CTX to serve cipher-suites of different keytypes + * + * If using OpenSSL < 1.0.2, it will create a new SSL_CTX (or reuse the default, + * see below) every time it's called. + * * Return Values: * 0 = No Error * 1 = Error in OpenSSL Calls @@ -1855,16 +1892,24 @@ static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, char *name, * It will then update *default_ctx * */ -// YANBZHU TODO: All the eb tree code was removed, so add it back in a later commit static int ssl_sock_process_sni_for_ctx(const char *path, const struct cert_key_and_chain* ckch, struct bind_conf *bind_conf, SSL_CTX** default_ctx, char *sni_name, char **err) { int key_type = 0; SSL_CTX* ctx = NULL; int order = 0; + struct ebmb_node *node = NULL; + + /* Find context from SNI EB tree for given sni_name, creating/reusing a ctx if needed + */ +#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL) + node = ssl_sock_get_ctx_from_eb_tree(bind_conf, sni_name); +# endif - if( *default_ctx == NULL) + if(node == NULL && *default_ctx == NULL) { - /* No previous SNI in tree and no new ctx for this file yet */ + /* No SNI found in tree and no new ctx for this file yet, + * create a new CTX + */ ctx = ssl_sock_new_ssl_ctx(err); if (ctx == NULL) return 1; @@ -1876,14 +1921,45 @@ static int ssl_sock_process_sni_for_ctx(const char *path, const struct cert_key_ return 1; } - /* Ctx is now valid */ + // SSL_CTX is now valid + *default_ctx = ctx; + } + else if(node == NULL) // Not found in tree, but default exists. Use it + ctx = *default_ctx; + else + ctx = container_of(node, struct sni_ctx, name)->ctx; // Use ctx found in tree key_type = EVP_PKEY_id(X509_get_pubkey(ckch->cert)); - order = 0; - order = ssl_sock_add_cert_sni(ctx, bind_conf, sni_name, order, key_type); + /* Check node to see if node has a cert of this keytype already + * If so, do not override it + */ + if(node && ssl_sock_node_contains_keytype(node,key_type)) + return 0; + + if(ctx != *default_ctx) + { + /* We pulled a ctx from the SNI tree, load cert file into it + * but no need to re-add it into the tree + */ + if (ssl_sock_populate_ctx(path, ckch,ctx,err) != 0) { + memprintf(err, "%sUnable to load cert file into SSL Context '%s'.\n", + err && *err ? *err : "", path); + return 1; + } + + /* Update node's keytypes */ + ssl_sock_update_sni_ctx_keytype(container_of(node, struct sni_ctx, name), key_type); + } + else + { + /* This is a new SNI name and it's already been populated above + * just add it to the tree */ + order = 0; + order = ssl_sock_add_cert_sni(ctx, bind_conf, sni_name, order, key_type); + } return 0; } -- 1.7.1
>From 54722d29ecff4c0cd416fca4a5a0f834c5f9632d Mon Sep 17 00:00:00 2001 From: yanbzhu <[email protected]> Date: Thu, 13 Aug 2015 15:27:32 -0400 Subject: CLEANUP: ssl: Cleaned up unused functions --- src/ssl_sock.c | 181 -------------------------------------------------------- 1 files changed, 0 insertions(+), 181 deletions(-) diff --git a/src/ssl_sock.c b/src/ssl_sock.c index ca5b3dc..05fbf2e 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -2072,187 +2072,6 @@ end: return ret; } - -/* 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. - */ -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; - X509 *x = NULL, *ca; - int i, err; - int ret = -1; - int order = 0; - X509_NAME *xname; - char *str; -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - STACK_OF(GENERAL_NAME) *names; -#endif - - in = BIO_new(BIO_s_file()); - if (in == NULL) - goto end; - - if (BIO_read_filename(in, file) <= 0) - goto end; - - x = PEM_read_bio_X509_AUX(in, NULL, ctx->default_passwd_callback, ctx->default_passwd_callback_userdata); - if (x == NULL) - goto end; - - if (fcount) { - while (fcount--) - order = ssl_sock_add_cert_sni(ctx, s, sni_filter[fcount], order, EVP_PKEY_RSA); - // EVP_PKEY_RSA to make the compiler happy, this function isn't used, same with 2 below - } - 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) { - order = ssl_sock_add_cert_sni(ctx, s, str, order, EVP_PKEY_RSA); - OPENSSL_free(str); - } - } - } - 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) { - order = ssl_sock_add_cert_sni(ctx, s, str, order, EVP_PKEY_RSA); - OPENSSL_free(str); - } - } - } - - ret = 0; /* the caller must not free the SSL_CTX argument anymore */ - if (!SSL_CTX_use_certificate(ctx, x)) - goto end; - - if (ctx->extra_certs != NULL) { - sk_X509_pop_free(ctx->extra_certs, X509_free); - ctx->extra_certs = NULL; - } - - while ((ca = PEM_read_bio_X509(in, NULL, ctx->default_passwd_callback, ctx->default_passwd_callback_userdata))) { - if (!SSL_CTX_add_extra_chain_cert(ctx, ca)) { - X509_free(ca); - goto end; - } - } - - err = ERR_get_error(); - if (!err || (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) { - /* we successfully reached the last cert in the file */ - ret = 1; - } - ERR_clear_error(); - -end: - if (x) - X509_free(x); - - if (in) - BIO_free(in); - - return ret; -} - -static int ssl_sock_load_cert_file(const char *path, struct bind_conf *bind_conf, struct proxy *curproxy, char **sni_filter, int fcount, char **err) -{ - int ret; - SSL_CTX *ctx; - - ctx = SSL_CTX_new(SSLv23_server_method()); - if (!ctx) { - memprintf(err, "%sunable to allocate SSL context for cert '%s'.\n", - err && *err ? *err : "", path); - return 1; - } - - if (SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM) <= 0) { - memprintf(err, "%sunable to load SSL private key from PEM file '%s'.\n", - err && *err ? *err : "", path); - SSL_CTX_free(ctx); - return 1; - } - - ret = ssl_sock_load_cert_chain_file(ctx, path, bind_conf, sni_filter, fcount); - if (ret <= 0) { - memprintf(err, "%sunable to load SSL certificate from PEM file '%s'.\n", - err && *err ? *err : "", path); - if (ret < 0) /* serious error, must do that ourselves */ - SSL_CTX_free(ctx); - return 1; - } - - if (SSL_CTX_check_private_key(ctx) <= 0) { - memprintf(err, "%sinconsistencies between private key and certificate loaded from PEM file '%s'.\n", - err && *err ? *err : "", path); - return 1; - } - - /* we must not free the SSL_CTX anymore below, since it's already in - * the tree, so it will be discovered and cleaned in time. - */ -#ifndef OPENSSL_NO_DH - /* store a NULL pointer to indicate we have not yet loaded - a custom DH param file */ - if (ssl_dh_ptr_index >= 0) { - SSL_CTX_set_ex_data(ctx, ssl_dh_ptr_index, NULL); - } - - ret = ssl_sock_load_dh_params(ctx, path); - if (ret < 0) { - if (err) - memprintf(err, "%sunable to load DH parameters from file '%s'.\n", - *err ? *err : "", path); - return 1; - } -#endif - -#if (defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) - ret = ssl_sock_load_ocsp(ctx, path); - if (ret < 0) { - if (err) - memprintf(err, "%s '%s.ocsp' is present and activates OCSP but it is impossible to compute the OCSP certificate ID (maybe the issuer could not be found)'.\n", - *err ? *err : "", path); - return 1; - } -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL) - if (sctl_ex_index >= 0) { - ret = ssl_sock_load_sctl(ctx, path); - if (ret < 0) { - if (err) - memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n", - *err ? *err : "", path); - return 1; - } - } -#endif - -#ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME - if (bind_conf->default_ctx) { - memprintf(err, "%sthis version of openssl cannot load multiple SSL certificates.\n", - err && *err ? *err : ""); - return 1; - } -#endif - if (!bind_conf->default_ctx) - bind_conf->default_ctx = ctx; - - return 0; -} - int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, struct proxy *curproxy, char **err) { struct dirent **de_list; -- 1.7.1

