My website for a while has used CloudFlare as its front-end. It’s a rather nice setup and means my real server gets less of a hammering, which is a good thing. A few months ago they enabled a feature called Universal SSL which I have also added to my site. Around the same time, my SSL check scripts started failing for the website, the certificate had expired apparently many many days ago. Something wasn’t right.
The problem was simply at first I’d get emails saying “The SSL certificate for enc.com.au “(CN: )” has expired!”. I use a program called ssl-cert-check that would check all (web, smtp, imap) of my certificates. It’s very easy to forget to renew and this program runs daily and does a simple check.
Running the program on the command line gave some more information, but nothing (for me) that really helped:
$ /usr/bin/ssl-cert-check -s enc.com.au -p 443 Host Status Expires Days ----------------------------------------------- ------------ ------------ ---- unable to load certificate 140364897941136:error:0906D06C:PEM routines:PEM_read_bio:no start line:pem_lib.c:701:Expecting: TRUSTED CERTIFICATE unable to load certificate 139905089558160:error:0906D06C:PEM routines:PEM_read_bio:no start line:pem_lib.c:701:Expecting: TRUSTED CERTIFICATE unable to load certificate 140017829234320:error:0906D06C:PEM routines:PEM_read_bio:no start line:pem_lib.c:701:Expecting: TRUSTED CERTIFICATE unable to load certificate 140567473276560:error:0906D06C:PEM routines:PEM_read_bio:no start line:pem_lib.c:701:Expecting: TRUSTED CERTIFICATE enc.com.au:443 Expired -2457182
So, apparently, there was something wrong with the certificate. The problem was this was CloudFlare who seem to have a good idea on how to handle certificates and all my browsers were happy.
ssl-cert-check is a shell script that uses openssl to make the connection, so the next stop was to see what openssl had to say.
$ echo "" | /usr/bin/openssl s_client -connect enc.com.au:443 CONNECTED(00000003) 140115756086928:error:14077438:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert internal error:s23_clnt.c:769: --- no peer certificate available --- No client certificate CA names sent --- SSL handshake has read 7 bytes and written 345 bytes --- New, (NONE), Cipher is (NONE) Secure Renegotiation IS NOT supported Compression: NONE Expansion: NONE No ALPN negotiated ---
No peer certificate available. That was the clue I was looking for.
Where’s my Certificate?
CloudFlare Universal SSL uses certificates that have multiple domains in the one certificate. The do this by having one canonical name which is something like sni(numbers).cloudflaressl.com and then multiple Subject Alternative Names (a bit like ServerAlias in apache configurations). This way a single server with a single certificate can serve multiple domains. The way that the client tells the server which website it is looking for is Server Name Indication (SNI). As part of the TLS handshaking the client tells the server “I want website www.enc.com.au”.
The thing is, by default, both openssl s_client and the check script do not use this feature. That was fail the SSL certificate checks were failing. The server was waiting for the client to ask what website it wanted. Modern browsers do this automatically so it just works for them.
For openssl on the command line, there is a flag -servername which does the trick nicely:
$ echo "" | /usr/bin/openssl s_client -connect enc.com.au:443 -servername enc.com.au CONNECTED(00000003) depth=2 C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO ECC Certification Authority verify error:num=20:unable to get local issuer certificate --- (lots of good SSL type messages)
That was openssl happy now. We asked the server what website we were interested in with the -servername and got the certificate.
The fix for ssl-cert-check is even simpler. Like a lot of things once you know the problem, the solution is not only easy to work out but someone has done it for you already. There is a Debian bug report on this problem with a simple fix from Francois Marier.
Just edit the check script and change the line that has:
and change it to true. Then the script is happy too:
$ ssl-cert-check -s enc.com.au -p https Host Status Expires Days ----------------------------------------------- ------------ ------------ ---- enc.com.au:https Valid Sep 30 2015 114
All working and as expected! This isn’t really a CloudFlare problem as such, it is just that’s the first place I had seen these sort of SNI certificates being used in something I administer (or more correctly something behind the something).