Hey guys, I haven’t gotten any feedback for this feature. Unless there’s severe objections, I’ll go ahead and push this to up to master.
Thanks, -Dave On 7/7/15, 3:21 PM, "Dave Zhu (yanbzhu)" <[email protected]> wrote: >Hey guys, > >Sorry for the radio silence, other projects picked up and so I was unable >to put any time into this until now. > >I¹ve gotten the feature to work against OpenSSL 1.0.2. I am able to do it >without peeking into any internal OpenSSL #defines and without using any >additional callbacks. > >However, I did have to restructure the SNI and CTX processing logic. > >Here¹s a TL;DR: > >Previously - SSL Context was created and populated with cert/pkey/chain, >then it is inserted into the SNI trees based on the CN/SANs. > >New - HAProxy will iterate through the CN/SAN¹s and determine whether or >not a new CTX is needed based on existing entries in the tree. If an >existing entry is found and does not contain the key type, HAProxy will >add the new cert/pkey into the existing context. If no existing context is >found a new context is created and populated. Only a single new context >will be created for each file, regardless of how many CN¹s and SAN¹s. > >Let me know if there¹s a better way for you guys to code review it. > >Here is the diff: > >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 3bd6fa2..aca0b03 100644 >--- a/src/ssl_sock.c >+++ b/src/ssl_sock.c >@@ -1285,7 +1285,23 @@ end: > } > #endif > >-static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, char >*name, int order) >+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; >+ } >+ >+} >+ >+static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, char >*name, int order, int ctx_cert_keytype) > { > struct sni_ctx *sc; > int wild = 0, neg = 0; >@@ -1311,6 +1327,12 @@ 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; >+ >+ ssl_sock_update_sni_ctx_keytype(sc,ctx_cert_keytype); >+ > if (wild) > ebst_insert(&s->sni_w_ctx, &sc->name); > else >@@ -1319,21 +1341,17 @@ static int ssl_sock_add_cert_sni(SSL_CTX *ctx, >struct bind_conf *s, char *name, > return order; > } > >+#if OPENSSL_VERSION_NUMBER < 0x1000200fL >+ > /* 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) >+static int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, >struct bind_conf *s) > { > BIO *in; > X509 *x = NULL, *ca; >- int i, err; >+ int 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) >@@ -1346,37 +1364,6 @@ static int ssl_sock_load_cert_chain_file(SSL_CTX >*ctx, const char *file, struct > if (x == NULL) > goto end; > >- if (fcount) { >- while (fcount--) >- order = ssl_sock_add_cert_sni(ctx, s, >sni_filter[fcount], 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) { >- order = >ssl_sock_add_cert_sni(ctx, s, str, order); >- 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); >- OPENSSL_free(str); >- } >- } >- } >- > ret = 0; /* the caller must not free the SSL_CTX argument anymore */ > if (!SSL_CTX_use_certificate(ctx, x)) > goto end; >@@ -1410,92 +1397,313 @@ end: > 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) >+#endif /* OPENSSL_VERSION_NUMBER < 0x1000200fL */ >+ >+#if OPENSSL_VERSION_NUMBER >= 0x1000200fL >+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 */ >+ >+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_cert_file_into_ctx(const char *path, struct bind_conf >*bind_conf, SSL_CTX* ctx, char **err) > { >- int ret; >- SSL_CTX *ctx; >+ int ret = 0; > >- 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); >+ 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; >- } >+#if OPENSSL_VERSION_NUMBER >= 0x1000200fL >+ ret = SSL_CTX_use_certificate_chain_file(ctx,path); >+#else >+ ret = ssl_sock_load_cert_chain_file(ctx, path, bind_conf); >+#endif > >- 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 (ret <= 0) { >+ memprintf(err, "%sunable to load SSL certificate from PEM file >'%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; >- } >+ 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; >- } >+ /* 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; >- } >+ 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; >- } >- } >+ 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; >+} >+ >+/* Just wraps up the error line*/ >+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; >+} >+ >+/* Wraps the processing of an SSL CTX given a SNI entry >+ * 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 >+ * >+ * >+ * */ >+static int ssl_sock_process_ctx_and_sni(const char *path, struct >bind_conf *bind_conf, X509* path_cert, SSL_CTX** default_ctx, char >*sni_name, char **err) >+{ >+ struct ebmb_node *node = NULL; >+ int key_type = 0; >+ SSL_CTX* ctx = NULL; >+ int order = 0; >+ >+ /* Without 1.0.2, node will always be NULL, which is fine */ >+#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(node == NULL && *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_cert_file_into_ctx(path,bind_conf,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; >+ >+ } >+ else if(node == NULL) >+ ctx = *default_ctx; >+ else >+ ctx = container_of(node, struct sni_ctx, name)->ctx; >+ >+ key_type = EVP_PKEY_id(X509_get_pubkey(path_cert)); >+ /* Check node to see if node has a cert of this keytype already */ >+ if(node && ssl_sock_node_contains_keytype(node,key_type)) >+ return 0; >+ >+ /* we pulled a ctx from the SNI tree, load cert file into it */ >+ if(ctx != *default_ctx){ >+ if (ssl_sock_cert_file_into_ctx(path,bind_conf,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, add it to the tree */ >+ { >+ /* Add to SNI */ >+ order = 0; >+ order = ssl_sock_add_cert_sni(ctx, bind_conf, sni_name, order, >key_type); >+ } >+ >+ return 0; >+} >+ >+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 = 1; >+ SSL_CTX *new_ctx = NULL; >+ FILE* file = NULL; >+ X509* cert = NULL; >+ X509_NAME *xname = NULL; >+ char *str=NULL; >+ int i; >+ int order = 0; >+ int key_type; >+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME >+ STACK_OF(GENERAL_NAME) *names = NULL; >+#endif >+ struct ebmb_node *n = NULL; >+ >+ file = fopen(path,"r"); >+ PEM_read_X509(file,&cert,NULL,NULL); >+ fclose(file); >+ >+ if(!cert){ >+ memprintf(err, "%sunable to read first certificate in '%s'.\n", >+ err && *err ? *err : "", path); >+ goto end; >+ } >+ >+ key_type = EVP_PKEY_id(X509_get_pubkey(cert)); >+ >+ /* fcount indicates that the user provided an SNI for the list of >certs, just use that one instead of CN/SAN*/ >+ if (fcount) { >+ new_ctx = ssl_sock_new_ssl_ctx(err); >+ if(!new_ctx) >+ goto end; >+ >+ ret = ssl_sock_cert_file_into_ctx(path,bind_conf,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; >+ } >+ >+ /* Keytype does not matter here */ >+ while (fcount--) >+ order = ssl_sock_add_cert_sni(new_ctx, bind_conf, >sni_filter[fcount], order, key_type); >+ >+ ret = 0; >+ goto end; >+ } >+ >+ /* If using OpenSSL 1.0.2+, find context to load cert from SNI EB >tree for each CN/SAN, creating a ctx if needed >+ * Otherwise, create a new ctx and insert it into the EB tree >+ */ >+ /* Check if CN already exists in SNI Tree */ >+ xname = X509_get_subject_name(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_ctx_and_sni(path,bind_conf,cert,&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 : ""); >- return 1; >- } >+ 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 = ctx; > >- return 0; >+ 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(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_ctx_and_sni(path,bind_conf,cert,&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(cert) >+ X509_free(cert); >+ >+ if(str) >+ OPENSSL_free(str); >+ >+ return ret; > } > > int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, struct >proxy *curproxy, char **err) >

