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)
>

Reply via email to