SSL pandemonium with lighttpd

3rd April 2013 | Comments

I've had a bit confusing errors with my lighttpd installation when using SSL certificates. Everything worked fine on most of the desktop browsers and iOS had no problem, too, but Android threw an error as well as some online SSL check tool.

I'm using free certificates from StartSSL, but these are supported on a lot of platforms, just really old or odd ones might be problematic. Even Android supports them since version 2.2. Also Server Name Indication (SNI), which is used to run multiple domains with different certificates on a single IP, is already supported in current Android versions. Additionally, other sites signed by StartSSL were just fine.

Debugging

If you have any issues with SSL, just start debugging using the openssl command:

openssl s_client -connect example.com:443

This will show you a lot of information about your SSL installation, including which certificates are transmitted and how the chain looks like. You could even check it against a specific set of root certificates with the -CApath switch.

The chain looked like this:

Certificate chain
 0 s:/C=DE/CN=www.example.com/emailAddress=mail@example.com
   i:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Class 1 Primary Intermediate Server CA
 1 s:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Class 1 Primary Intermediate Server CA
   i:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority

Everything is fine, so I suspected some compatibility issues between the used lighttpd version and some browsers, maybe using old versions of OpenSSL.

Debugging SNI

Just recently I stumbled upon extra checking the SNI usage. Turns out OpenSSL supports also that by using the -servername switch:

openssl s_client -connect example.com:443 -servername example.com

Now I finally got near the real issue, since the result is a non-existing chain. Only the server certificate itself got transferred, so I get a couple of errors:

verify error:num=20:unable to get local issuer certificate
...
verify error:num=27:certificate not trusted
...
verify error:num=21:unable to verify the first certificate

Modern browsers don't just store authorized root certificates of certificate authorities (CAs), but also often include intermediate certificates used by them. So they were still able to verify my certificate without problems. But for some clients like Android, this is rather problematic.

But what's wrong with my server?

Lighttpd configuration

The SSL part of the lighttpd configuration files was looking similar to this:

$SERVER["socket"] == "0.0.0.0:443" {
        ssl.engine  = "enable"
        ssl.ca-file = "/etc/lighttpd/ssl/example.com/ca-certs.crt"
        ssl.pemfile = "/etc/lighttpd/ssl/example.com/ssl.crt"
        ssl.cipher-list = var.ssl-cl
}

$HTTP["host"] =~ "^(www\.)?example\.com$" {
        ssl.pemfile = "/etc/lighttpd/ssl/example/ssl.crt"
}

$HTTP["host"] =~ "^(www\.)?example\.net$" {
        ssl.pemfile = "/etc/lighttpd/ssl/example/ssl.crt"
}

Note that I'm using example.com and example.net as my two domains. If no hostname is specified, e.g. the client doesn't support SNI, then the certificate specified in the socket block is used. Otherwise ssl.pemfile is overwritten with the adequate path. But after further tests I found out that lighttpd is really a bit quirky in case of SSL settings.

Finally, the solution

The problem is, that it's really enormously important to specify all used SSL settings directly in a row, which means that you have to repeatedly specify all parameters for each block. Otherwise lighttpd tends to discard some options.

Here my complete current configuration pattern including IPv6 support:

# /usr/share/doc/lighttpd-doc/ssl.txt

var.ssl-cl = "AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH"

$SERVER["socket"] == "0.0.0.0:443" {
        ssl.engine  = "enable"
        ssl.ca-file = "/etc/lighttpd/ssl/example.com/ca-certs.crt"
        ssl.pemfile = "/etc/lighttpd/ssl/example.com/ssl.crt"
        ssl.cipher-list = var.ssl-cl
}

$SERVER["socket"] == "[::]:443" {
        ssl.engine  = "enable"
        ssl.ca-file = "/etc/lighttpd/ssl/example.com/ca-certs.crt"
        ssl.pemfile = "/etc/lighttpd/ssl/example.com/ssl.crt"
        ssl.cipher-list = var.ssl-cl
}

$HTTP["host"] =~ "^(www\.)?example\.com$" {
        ssl.ca-file = "/etc/lighttpd/ssl/example.com/ca-certs.crt"
        ssl.pemfile = "/etc/lighttpd/ssl/example.com/ssl.crt"
        ssl.cipher-list = var.ssl-cl
}

$HTTP["host"] =~ "^(www\.)?example\.net$" {
        ssl.ca-file = "/etc/lighttpd/ssl/example.net/ca-certs.crt"
        ssl.pemfile = "/etc/lighttpd/ssl/example.net/ssl.crt"
        ssl.cipher-list = var.ssl-cl
}

Just working fine now. So if you have any problems with your SSL installation, make sure you also test SNI. Most tutorials and check tools completely ignored that.

blog comments powered by Disqus