-
Notifications
You must be signed in to change notification settings - Fork 4
SSL deployment FAQ
Having an RSA + ECDSA configuration could be tricky to configure, it's not that difficult but one must be aware of limitations.
A standard configuration would have 2 files with a cert+key pairs in foobar.pem.ecdsa and foobar.pem.rsa. Please check that you certificates contains a CN or a DNS entry so you could select them using their SNI.
$ openssl x509 -in foobar.pem.rsa -noout -text | grep '\(Subject: CN\|DNS\)'
Subject: CN = foo.bar.com
DNS:foo.bar.com
configured this way:
haproxy.cfg:
frontend in
bind *:443 ssl crt foobar.pem.rsa crt foobar.pem.ecdsa
This configuration will allow you to match the RSA certificate when no Server Name indication was specified by the client (the first certificate declared is the fallback one). Or match either an RSA or ECDSA certificate if the right SNI was specified.
Without SNI this will result in delivering the first certificate, which is the RSA one:
$ openssl s_client -connect foo.bar.com:443 -tls1_2 </dev/null | openssl x509 -noout -text | grep "Public Key Algorithm"
Public Key Algorithm: rsaEncryption
Trying to reach haproxy without an SNI and with only an ECDSA cipher will result in an handshake failure.
$ openssl s_client -connect foo.bar.com:443 -cipher 'ECDHE-ECDSA-AES128-GCM-SHA256' -tls1_2 </dev/null | openssl x509 -noout -text | grep "Public Key Algorithm"
40D7FF43CD740000:error:0A000410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:../ssl/record/rec_layer_s3.c:1590:SSL alert number 40
Using a SNI allows to chose between the RSA and ECDSA ciphers:
$ openssl s_client -connect foo.bar.com:443 -servername foo.bar.com -cipher 'ECDHE-ECDSA-AES128-GCM-SHA256' -tls1_2 </dev/null | openssl x509 -noout -text | grep "Public Key Algorithm"
Public Key Algorithm: id-ecPublicKey
$ openssl s_client -connect foo.bar.com:443 -servername foo.bar.com -cipher 'ECDHE-RSA-AES128-GCM-SHA256' -tls1_2 </dev/null | openssl x509 -noout -text | grep "Public Key Algorithm"
Public Key Algorithm: rsaEncryption
Some tools like testssl.sh could report that only ECDSA is available on the server, which is wrong. For example:
$ testssl -E foo.bar.com:443
Testing ciphers per protocol via OpenSSL plus sockets against the server, ordered by encryption strength
Hexcode Cipher Suite Name (OpenSSL) KeyExch. Encryption Bits Cipher Suite Name (IANA/RFC)
-----------------------------------------------------------------------------------------------------------------------------
TLS 1.2
xc02c ECDHE-ECDSA-AES256-GCM-SHA384 ECDH 253 AESGCM 256 TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
xc024 ECDHE-ECDSA-AES256-SHA384 ECDH 253 AES 256 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
xc00a ECDHE-ECDSA-AES256-SHA ECDH 253 AES 256 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
xcca9 ECDHE-ECDSA-CHACHA20-POLY1305 ECDH 253 ChaCha20 256 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
xc02b ECDHE-ECDSA-AES128-GCM-SHA256 ECDH 253 AESGCM 128 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
xc023 ECDHE-ECDSA-AES128-SHA256 ECDH 253 AES 128 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
xc009 ECDHE-ECDSA-AES128-SHA ECDH 253 AES 128 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
TLS 1.3
x1302 TLS_AES_256_GCM_SHA384 ECDH 253 AESGCM 256 TLS_AES_256_GCM_SHA384
x1303 TLS_CHACHA20_POLY1305_SHA256 ECDH 253 ChaCha20 256 TLS_CHACHA20_POLY1305_SHA256
x1301 TLS_AES_128_GCM_SHA256 ECDH 253 AESGCM 128 TLS_AES_128_GCM_SHA256
However this is not the case, it only returns this result because HAProxy replies with the ECDSA certificate during the test. To actually check the ciphers you need to test them one by one, which is not done by testssl.
Testing the usable ciphers can be achieved by iterating on a openssl s_client -ciphers
command.
For example:
supported-ciphers.sh:
#!/usr/bin/env bash
SERVER=$1
SERVERNAME=${SERVER%%:*}
echo Available ciphers using ciphers list from $(openssl version).
while read -r LINE; do
cipher=( $LINE )
if [[ "${cipher[1]}" == "TLSv1.3" ]]; then
args="-ciphersuites ${cipher[0]} -tls1_3"
else
args="-cipher ${cipher[0]} -tls1_2"
fi
result=`(printf "GET / HTTP/1.1\r\n\r\n"; sleep 0.1) | openssl s_client ${args[*]} -connect $SERVER -servername $SERVERNAME 2>&1`
if [[ "$result" =~ "200 OK" ]]; then
echo "${cipher[1]} ${cipher[0]}"
fi
done < <(openssl ciphers -v 'ALL')
$ bash supported-ciphers.sh foo.bar.com:443
Available ciphers using ciphers list from OpenSSL 3.0.13 30 Jan 2024 (Library: OpenSSL 3.0.13 30 Jan 2024).
TLSv1.3 TLS_AES_256_GCM_SHA384
TLSv1.3 TLS_CHACHA20_POLY1305_SHA256
TLSv1.3 TLS_AES_128_GCM_SHA256
TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384
TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384
TLSv1.2 ECDHE-ECDSA-CHACHA20-POLY1305
TLSv1.2 ECDHE-RSA-CHACHA20-POLY1305
TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256
TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256
TLSv1.2 ECDHE-ECDSA-AES256-SHA384
TLSv1.2 ECDHE-RSA-AES256-SHA384
TLSv1.2 ECDHE-ECDSA-AES128-SHA256
TLSv1.2 ECDHE-RSA-AES128-SHA256
TLSv1 ECDHE-ECDSA-AES256-SHA
TLSv1 ECDHE-RSA-AES256-SHA
TLSv1 ECDHE-ECDSA-AES128-SHA
TLSv1 ECDHE-RSA-AES128-SHA
TLSv1.2 AES256-GCM-SHA384
TLSv1.2 AES128-GCM-SHA256
TLSv1.2 AES256-SHA256
TLSv1.2 AES128-SHA256
SSLv3 AES256-SHA
SSLv3 AES128-SHA
You can see that the list of ciphers is correct there.
TLS is difficult to configure, before debugging, you must ensure that your configuration is doing what you need and what you want to support. Please use these tools:
The "SSL handshake failure" error in your HAProxy logs is a common error that can appear when a client can't achieve the SSL handshake. Most of the time it's because of an incompatibility between the client and the HAProxy frontend configuration. Poorly written bots can do a lot of mess in your logs because of this.
HAProxy provides multiple tools to debug this issue:
Starting with HAProxy 2.5 an error-log-format can be used, this can help you debug the handshake. The %[ssl_fc_err]
fetch contains the OpenSSL error code and %[ssl_fc_err_str]
contains the associated string.
Starting with HAProxy 2.8, the "SSL hanshake failure" error is followed by the OpenSSL error string, example:
<134>May 12 17:14:04 haproxy[183151]: 127.0.0.1:49346 [12/May/2023:17:14:04.571] frt2/1: SSL handshake failure (error:0A000418:SSL routines::tlsv1 alert unknown ca)
SSL handshake failure (error:1417A0C1:SSL routines:tls_post_process_client_hello:no shared cipher)
This is the error you can have when the client does not share any ciphers with the HAProxy frontend, this could be because the client only uses old ciphers that are disabled by default with your SSL library. Depending on your SSL library configuration, distribution and build, some ciphers could be disabled. Modern distributions are disabling ciphers using SHA1 or MD5 signatures, or even RSA, DSA en DH keys shorter than 1024 bits. This behaviour could be restored using by tweaking the OpenSSL security level. With HAProxy 3.0 this can be easily done with the ssl-security-level keyword in the global section.
Note:
DHE ciphers are disabled by default since HAProxy 2.6, you can re-enable them with the tune.ssl.default-dh-param
global parameter
SSL handshake failure (error:14209102:SSL routines:tls_early_post_process_client_hello:unsupported protocol)
SSL handshake failure (error:1408F10B:SSL routines:ssl3_get_record:wrong version number)
You can get these messages when a client is trying to connect with an old version of TLS or SSL which is not supported by your setup.
Most modern distributions are disabling SSLv3, TLS 1.0 and TLS 1.1, if you want to enable them back, you need to change the OpenSSL security level in openssl.cnf or with ssl-security-level (>= haproxy 3.0)
Note:
TLS versions below TLSv1.2 are disabled by default since HAProxy 2.2, to re-enable them, you need to use the ssl-min-ver in either the bind line or on the ssl-default-bind-options
keyword of the global section.
SSL handshake failure (error:1408F09C:SSL routines:ssl3_get_record:http request)
This is Openssl getting an HTTP clear request on an HTTPS bind.
SSL handshake failure (error:142090EA:SSL routines:tls_early_post_process_client_hello:callback failed)
This error happens when the OpenSSL ClienHello callback failed, usually this is because it didn't found a certificate that matches the ServerName provided by the client.