ECC and RSA certificates and HTTPS
To keep this practical, we will not go into theory of ECC or RSA certificates. Let’s just mention that ECC certificates can provide as much security as RSA with much lower key size, meaning much lower computation requirements on the server side. Sadly, many clients do not support ciphers based on ECC, so to maintain compatibility as well as provide good performance we need to be able to detect which type of certificate is supported by the client to be able to serve it correctly.
The above is usually achieved with analyzing the cipher suites sent by the client in the ClientHello message at the start of the SSL handshake, but we’ve opted for a much simpler approach that works very well with all modern browsers (clients).
Prerequisites
First you will need to obtain both RSA and ECC certificates for your web site. Depending on the registrar you are using, check their documentation. After you have been issued with the certificates, make sure you download the appropriate intermediate certificates and create the bundle files for HAProxy to read.
To be able to use the sample fetch required, you will need at least HAProxy 1.6-dev3 (not yet released as of writing) or you can clone latest HAProxy from the git repository. Feature was introduced in commit 5fc7d7e.
Configuration
We will use chaining in order to achieve desired functionality. You can use abstract sockets on Linux to get even more performance, but note the drawbacks that can be found in HAproxy documentation.
frontend ssl-relay mode tcp bind 0.0.0.0:443 tcp-request inspect-delay 5s # Ensure we wait for the existence or lack of the extension tcp-request content accept if { req.ssl_ec_ext 0 } # Make routing decision use_backend ssl-ecc if { req.ssl_ec_ext 1 } default_backend ssl-rsa backend ssl-ecc mode tcp server ecc unix@/var/run/haproxy_ssl_ecc.sock send-proxy-v2 backend ssl-rsa mode tcp server rsa unix@/var/run/haproxy_ssl_rsa.sock send-proxy-v2 listen all-ssl bind unix@/var/run/haproxy_ssl_ecc.sock accept-proxy ssl crt /usr/local/haproxy/ecc.www.foo.com.pem user nobody bind unix@/var/run/haproxy_ssl_rsa.sock accept-proxy ssl crt /usr/local/haproxy/www.foo.com.pem user nobody mode http server backend_1 192.168.1.1:8000 check
The whole configuration revolves around the newly implemented sample fetch: req.ssl_ec_ext. What this fetch does is that it detects the presence of Supported Elliptic Curves Extension inside the ClientHello message. This extension is defined in RFC4492 and according to it, it SHOULD be sent with every ClientHello message by the client supporting ECC. We have observed that all modern clients send it correctly.
If the extension is detected, the client is sent through a unix socket to the frontend that will serve an ECC certificate. If not, a regular RSA certificate will be served.
Benchmark
We will provide full HAProxy benchmarks in the near future, but for the sake of comparison, let us view the difference present on an E5-2680v3 CPU and OpenSSL 1.0.2.
256bit ECDSA:
sign verify sign/s verify/s
0.0000s 0.0001s 24453.3 9866.9
2048bit RSA:
sign verify sign/s verify/s
0.000682s 0.000028s 1466.4 35225.1
As you can see, looking at the sign/s we are getting over 15 times the performance with ECDSA256 compared to RSA2048.
UPDATE 2018
The above is only required if you are using HAProxy 1.6 or you are using any OpenSSL version before 1.0.2. If you are using an up-to-date version of HAProxy and OpenSSL, you can use the native ECC/RSA switching by naming your certificates with a. rsa, .dsa or .ecdsa suffix. More information can be found in the official documentation: https://www.haproxy.com/documentation/hapee/1-8r1/onepage/#5.1-crt
Hm, I’m currently trying out that feature but it doesn’t seem to work for me. Tested with 1.6.0 and 1.6.4. When forcing e.g. ECDHE-ECDSA-AES128-GCM-SHA256 as cipher I’m still directed to the RSA one and the handshake fails. When replacing “default_backend ssl-rsa” by the “ssl-ecc” one it works, so it’s not the cipher.
How are you testing this?
For ECC e.g.:
curl –ciphers ECDHE-ECDSA-AES128-GCM-SHA256 -Lvsk https://127.0.0.1:443/
if (!bleft)
goto too_short;
That’s what curl triggers at least.
Are you on IRC by chance? Might be easier to debug/discuss. I’m on Freenode in #haproxy.
So the sample is not captures unless I add something else that depends on a sample, like:
acl foo req_ssl_ver lt 3
tcp-request content reject if fud
Otherwise there will be no captured sample at all.
Can you try adding tcp-request inspect-delay?
https://cbonte.github.io/haproxy-dconv/configuration-1.6.html#4-tcp-request%20inspect-delay
I tried that already but that doesn’t help 🙁
http://cbonte.github.io/haproxy-dconv/1.7/configuration.html#ssl-default-bind-ciphers
This states the following:
There are cases where it is desirable to support multiple key types, e.g. RSA
and ECDSA in the cipher suites offered to the clients. This allows clients
that support EC certificates to be able to use EC ciphers, while
simultaneously supporting older, RSA only clients.
In order to provide this functionality, multiple PEM files, each with a
different key type, are required. To associate these PEM files into a
“cert bundle” that is recognized by haproxy, they must be named in the
following way: All PEM files that are to be bundled must have the same base
name, with a suffix indicating the key type. Currently, three suffixes are
supported: rsa, dsa and ecdsa. For example, if http://www.example.com has two PEM
files, an RSA file and an ECDSA file, they must be named: “example.pem.rsa”
and “example.pem.ecdsa”. The first part of the filename is arbitrary; only the
suffix matters. To load this bundle into haproxy, specify the base name only:
Example :
bind :8443 ssl crt example.pem
Note that the suffix is not given to haproxy; this tells haproxy to look for
a cert bundle.
Haproxy will load all PEM files in the bundle at the same time to try to
support multiple key types. PEM files are combined based on Common Name
(CN) and Subject Alternative Name (SAN) to support SNI lookups. This means
that even if you give haproxy a cert bundle, if there are no shared CN/SAN
entries in the certificates in that bundle, haproxy will not be able to
provide multi-cert support.
I tried exactly per instructions, and haproxy will not bundle them at runtime:
$ sudo haproxy -v
HA-Proxy version 1.7-dev4-41d5e3a 2016/08/14
Copyright 2000-2016 Willy Tarreau
I have example.pem.rsa and example.pem.ecdsa in the same directory and both are valid certs.
I use example.pem in the config as instructed:
Here is the error:
$ sudo haproxy -f /etc/haproxy/haproxy.cfg
[ALERT] 232/013138 (32073) : parsing [/etc/haproxy/haproxy.cfg:54] : ‘bind x.x.x.x:443’ : unable to load SSL private key from PEM file ‘/etc/ssl/private/example.pem’.
[WARNING] 232/013138 (32073) : parsing [/etc/haproxy/haproxy.cfg:59] : a ‘redirect’ rule placed after a ‘use_backend’ rule will still be processed before.
[ALERT] 232/013138 (32073) : Error(s) found in configuration file : /etc/haproxy/haproxy.cfg
[ALERT] 232/013138 (32073) : Proxy ‘https-443’: no SSL certificate specified for bind ‘x.x.x.x:443’ at [/etc/haproxy/haproxy.cfg:54] (use ‘crt’).
[ALERT] 232/013138 (32073) : Fatal errors found in configuration.
Is there a bug in this feature?
By the way… each one individually works just fine.
I have tried these instructions in version 1.8 and it does not work, only the RSA type certificate responds, the ECC certificate in no case works, does this still work in this version?