Hello, Great ! I was actually speaking about it this morning at work ! Would love to see this feature integrated (can it be backported to 1.5 too ?)
2015-06-23 20:07 GMT+02:00 Dave Zhu (yanbzhu) <[email protected]>: > Hello all, > > I have a proposed enhancement that I have coded up and would like your > comments. > > The idea behind this is that when HAProxy is used to terminate SSL, and > is configured with multiple certificates/keys with different key types > (RSA, ECDSA, DSA), it only serves up the first cert/key loaded in the > config (unless SNI is used). This means that if a client were to prefer > ECDSA over RSA, even if HAProxy has an ECDSA certificate, it will use the > RSA certificate. My proposed enhancement is that HAProxy switch the CTX > that’s used, based on the clients’ choice of cipher-suites as well as the > locally available certificates/keys. > > Currently, I’ve coded it so that this only happens when the client does > not specify an SNI, but I’m looking for guidance on what you would consider > to be the best solution. This approach can certainly be taken to be > compatible with SNI. > > Is this something that you would be interested in folding into the > codebase? > > Here’s my diff: > diff --git a/include/types/listener.h b/include/types/listener.h > index 895cd00..4a544d1 100644 > --- a/include/types/listener.h > +++ b/include/types/listener.h > @@ -133,6 +133,9 @@ struct bind_conf { > struct eb_root sni_ctx; /* sni_ctx tree of all known certs > full-names sorted by name */ > struct eb_root sni_w_ctx; /* sni_ctx tree of all known certs wildcards > sorted by name */ > struct tls_keys_ref *keys_ref; /* TLS ticket keys reference */ > + SSL_CTX *default_rsa_ctx; /* SSL Context of first RSA Certificate*/ > + SSL_CTX *default_dsa_ctx; /* SSL Context of first DSA Certificate*/ > + SSL_CTX *default_ecdsa_ctx;/* SSL Context of first ECDSA Certificate*/ > #endif > int is_ssl; /* SSL is required for these listeners */ > unsigned long bind_proc; /* bitmask of processes allowed to use these > listeners */ > diff --git a/src/ssl_sock.c b/src/ssl_sock.c > index 3bd6fa2..0343c69 100644 > --- a/src/ssl_sock.c > +++ b/src/ssl_sock.c > @@ -978,6 +978,61 @@ static int ssl_sock_advertise_alpn_protos(SSL *s, > const unsigned char **out, > } > #endif > > + > + > +/* This CB is techinically for using pre-shared secret for this > + * handshake for a non-resumption case > + * > + * However, it is currnetly the only place where we can access the peer's > + * cipher list before a cipher is picked > + * > + * Therefore, we use this CB in order to swap the SSL_CTX based on the > + * client's preferred ciphersuite if we have multiple key types in the > config > + */ > +#ifdef USE_OPENSSL > +int ssl_sock_switchctx_by_peer_cipher_cbk(SSL *ssl, void *secret, int > *secret_len, > + STACK_OF(SSL_CIPHER) *peer_ciphers, SSL_CIPHER **cipher, void* arg) > +{ > + int i; > + SSL_CTX* new_ctx = NULL; > + SSL_CIPHER *cur_cipher; > + struct bind_conf* s = (struct bind_conf*) arg; > + > + for (i=0; i<sk_SSL_CIPHER_num(peer_ciphers); i++) > + { > + new_ctx = NULL; > + cur_cipher = sk_SSL_CIPHER_value(peer_ciphers,i); > + > + /* This information is located in ssl_locl.h, > + * but that file is not visibile outside the package > + * #define SSL_aRSA 0x00000001L - RSA auth > + #define SSL_aDSS 0x00000002L - DSS auth > + #define SSL_aECDSA 0x00000040L - ECDSA auth > + */ > + > + if(cur_cipher->algorithm_auth == 0x40L && s->default_ecdsa_ctx){ > + new_ctx = s->default_ecdsa_ctx; > + } else if (cur_cipher->algorithm_auth == 0x02L && s->default_dsa_ctx){ > + new_ctx = s->default_dsa_ctx; > + } else if(cur_cipher->algorithm_auth == 0x01L && s->default_rsa_ctx){ > + new_ctx = s->default_rsa_ctx; > + } > + > + if(new_ctx){ > + /* Found a new CTX, switch it now*/ > + SSL_set_SSL_CTX(ssl,new_ctx); > + break; > + } > + } > + > + /* 0 means that a session secret was not found, > + * which is always the case for us > + */ > + return 0; > +} > +#endif > + > + > #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME > /* Sets the SSL ctx of <ssl> to match the advertised server name. Returns > a > * warning when no match is found, which implies the default (first) cert > @@ -993,6 +1048,7 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, > struct bind_conf *s) > > servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); > if (!servername) { > + > > SSL_set_session_secret_cb(ssl,ssl_sock_switchctx_by_peer_cipher_cbk,(void*)s); > return (s->strict_sni ? > SSL_TLSEXT_ERR_ALERT_FATAL : > SSL_TLSEXT_ERR_NOACK); > @@ -1033,6 +1089,8 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, > struct bind_conf *s) > } > #endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */ > > + > + > #ifndef OPENSSL_NO_DH > > static DH * ssl_get_dh_1024(void) > @@ -1414,6 +1472,9 @@ static int ssl_sock_load_cert_file(const char *path, > struct bind_conf *bind_conf > { > int ret; > SSL_CTX *ctx; > + EVP_PKEY *pkey=NULL; > + FILE* file = NULL; > + int key_type = 0; > > ctx = SSL_CTX_new(SSLv23_server_method()); > if (!ctx) { > @@ -1422,13 +1483,35 @@ static int ssl_sock_load_cert_file(const char > *path, struct bind_conf *bind_conf > 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; > + file = fopen(path,"r"); > + PEM_read_PrivateKey(file,&pkey,NULL,NULL); > + if(file){ > + fclose(file); > + } > + > + if (pkey == NULL) { > + memprintf(err, "%sunable to load SSL private key from PEM file > '%s'.\n", > + err && *err ? *err : "", path); > + SSL_CTX_free(ctx); > + return 1; > + } > + > + key_type=EVP_PKEY_id(pkey); > + ret=SSL_CTX_use_PrivateKey(ctx,pkey); > + > + if (ret <= 0) { > + memprintf(err, > + "%sunable to set SSL_CTX private key from PEM file '%s'.\n", > + err && *err ? *err : "", path); > + EVP_PKEY_free(pkey); > + SSL_CTX_free(ctx); > + return 1; > } > > + /* We no longer need the key anymore */ > + EVP_PKEY_free(pkey); > + > + > 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", > @@ -1493,7 +1576,22 @@ static int ssl_sock_load_cert_file(const char > *path, struct bind_conf *bind_conf > } > #endif > if (!bind_conf->default_ctx) > - bind_conf->default_ctx = ctx; > + bind_conf->default_ctx = ctx; > + > + switch(key_type){ > + case EVP_PKEY_RSA: > + if(!bind_conf->default_rsa_ctx) > + bind_conf->default_rsa_ctx = ctx; > + break; > + case EVP_PKEY_DSA: > + if(!bind_conf->default_dsa_ctx) > + bind_conf->default_dsa_ctx = ctx; > + break; > + case EVP_PKEY_EC: > + if(!bind_conf->default_ecdsa_ctx) > + bind_conf->default_ecdsa_ctx = ctx; > + break; > + } > > return 0; > } > @@ -1870,6 +1968,8 @@ int ssl_sock_prepare_ctx(struct bind_conf > *bind_conf, SSL_CTX *ctx, struct proxy > SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_cbk); > SSL_CTX_set_tlsext_servername_arg(ctx, bind_conf); > #endif > + > + > #if defined(SSL_CTX_set_tmp_ecdh) && !defined(OPENSSL_NO_ECDH) > { > int i; > @@ -2243,6 +2343,9 @@ void ssl_sock_free_all_ctx(struct bind_conf > *bind_conf) > } > > bind_conf->default_ctx = NULL; > + bind_conf->default_rsa_ctx = NULL; > + bind_conf->default_dsa_ctx = NULL; > + bind_conf->default_ecdsa_ctx = NULL; > } > > /* >

