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;
}
/*
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;
}
/*