Secure HTTP

Module: core

keywords: lighttpd, ssl

Description

lighttpd supports SSLv2 (disabled from 1.4.21 and 1.5 and onwards) and SSLv3 if it is compiled against openssl.

How to install SSL

To use SSL you must have ssl compiled into lighty. You must first have
openssl-devel installed and openssl installed as well. On Fedora or
Centos you may use yum to install this by running this command:

yum install openssl*

And type yes when it asks for a confirmation of what you would like to install.

Once installed please download the lighttpd tarball from http://www.lighttpd.net/download and extract it with

tar zxvf lighttpd-1.4.20.tar.gz

Enter into the extracted folder and compile with these configuration flags: ::

--with-openssl --with-openssl-libs=/usr/lib

Remember to change _ --with-openssl-libs= _ to the folder where your openssl libraries are installed into.

Once compiled, run make and make install. If lighty has successfully compiled SSL the command

lighttpd -v

Should display (Keep in mind that this new lighty version now has (ssl) after lightys name)

lighttpd-1.4.11 (ssl) - a light and fast webserver
Build-Date: Sep 1 2006 19:09:15

Remember if you used the RPM packages to install lighty, the init.d scripts will point to the wrong binary of lighty than the one you just compiled. The location of where you compiled lighty should be displayed near the end of make install. Once the location of the binary is found please edit the /etc/init.d/lighttpd script and change what is defined in the lighttpd="/usr/sbin/lighttpd" to your new lighty location.

Configuration

option description
ssl.engine enable/disable ssl engine
ssl.pemfile path to the PEM file for SSL support (Should contain both the private key and the certificate)
ssl.ca-file path to the CA file for support of chained certificates
ssl.dh-file path to the PEM file for Diffie-Hellman key agreement protocol (lighttpd >= 1.4.29 only)
ssl.ec-curve defines the set of elliptic-curve-cryptography domain parameters known as a "named curve" (lighttpd >= 1.4.29 only)
ssl.use-sslv2 enable/disable use of SSL version 2 (lighttpd < 1.4.21 only, newer version don't support SSLv2)
ssl.use-sslv3 enable/disable use of SSL version 3 (lighttpd >= 1.4.29 only)
ssl.cipher-list Configure the allowed SSL ciphers
ssl.honor-cipher-order enable/disable honoring the order of ciphers set in ssl.cipher-list (set by default when ssl.cipher-list is set)
ssl.disable-client-renegotiation enable/disable mitigation of client triggered re-negotiation (see CVE-2009-3555). Important: This setting can only be set globally!
ssl.verifyclient.activate enable/disable client verification
ssl.verifyclient.enforce enable/disable enforcing client verification
ssl.verifyclient.depth certificate depth for client verification
ssl.verifyclient.exportcert enable/disable client certificate export to env:SSL_CLIENT_CERT
ssl.verifyclient.username client certificate entity to export as env:REMOTE_USER (eg. SSL_CLIENT_S_DN_emailAddress, SSL_CLIENT_S_DN_UID, etc.)

Details

To enable SSL you have to provide a valid certificate and have to enable the SSL engine. To make lighttpd SSL-only, simply put the following in your main config (you probably need to set the server port to 443 as well).

ssl.engine = "enable" 
ssl.pemfile = "/path/to/server.pem"

To enable SSL in addition to normal HTTP, put the ssl.engine configuration in a socket conditional block:

$SERVER["socket"] == ":443" {
ssl.engine = "enable"
ssl.pemfile = "/etc/lighttpd/ssl/www.example.org.pem"
}

This simply enables SSL on port 443. It seems that lighttpd also automatically starts listening on port 443 this way. Like this, all of the other configuration applies to both HTTP and HTTPS. This means that the same pages will be available on both HTTP and HTTPS. If you want to serve different sites, you can change the document root inside the socket conditional:

$SERVER["socket"] == ":443" {
server.document-root = "/www/servers/www.example.org/secure/"
}

When you are using lighttpd 1.4.19 or later, you can also use the scheme conditional to distinguish between secure and normal requests. Note that you can't use the scheme conditional around ssl.engine above, since lighttpd needs to know on what port to enable SSL.

$HTTP["scheme"] == "https" {
server.document-root = "/www/servers/www.example.org/secure/"
}

If you have a .crt and a .key file, cat them together into a single PEM file (the order isn't strictly important):

$ cat host.key host.crt > host.pem

ssl.cipher-list is a list of ciphers that you want (or don't want) to use when talking SSL. For a list of ciphers and how to include/exclude them, see sections "CIPHER LIST FORMAT" and "CIPHER STRINGS" on the man-page for ciphers.

Permissions

Be careful to keep your .pem file private! Lighttpd reads all pemfiles at startup, before dropping privileges. It is therefore best to make the pem file owned by root and readable by root only:

$ chown root:root /etc/lighttpd/ssl/example.org.pem
$ chmod 400 /etc/lighttpd/ssl/example.org.pem

Chained certificates

Some certificate authorities use chained certificates. This means that your webserver certificate is not signed by the CA root certificate directly, but by an intermediate certificate (which is in turn signed by the root CA). Since browser typically only ship the root certificates and not all intermediate certificates, there is no way for a browser to verify your chained certificate if you don't supply the browser with the intermediate certificate.

Using the ssl.ca-file configuration variable, you can tell lighttpd about these intermediate certificates. You can put multiple certificates in a single file, if needed. For example:

ssl.ca-file = "/etc/lighttpd/ssl/ca.crt"

Troubleshooting

  • Please note that certificates's files are opened before the server goes chrooted (if it's have to). So these paths are system wide.
  • On server side, ssldump is your friend: ssldump -i your_network_interface_goes_here port 443
  • On client side, openssl will help you: openssl s_client -connect your_server_name_goes_here:443
  • SSL options not working or ignored? Put the options in the same block where you enabled SSL (ssl.engine = "enable").

SSL passwords

  • If you set a password on your SSL certificate, then each time lighttpd starts you will be requested to enter it manually. ie: "Enter PEM pass phrase:". To prevent this, remove the password from your private key file. At present there is no configuration option to enable storage of SSL certificate passwords in the lighttpd config file (but it wouldn't make much sense, since having the password stored on disk is not more secure than having no password at all).

Diffie-Hellman and Elliptic-Curve Diffie-Hellman parameters

Diffie-Hellman and Elliptic-Curve Diffie-Hellman key agreement protocols will be supported in lighttpd 1.4.29. By default, Diffie-Hellman and Elliptic-Curve Diffie-Hellman key agreement protocols use, respectively, the 1024-bit MODP Group with 160-bit prime order subgroup from RFC 5114 and "prime256v1" (also known as "secp256r1") elliptic curve from RFC 4492. The Elliptic-Curve Diffie-Hellman key agreement protocol is supported in OpenSSL from 0.9.8f version onwards. For maximum interoperability, OpenSSL only supports the "named curves" from RFC 4492.

Using the ssl.dh-file and ssl.ec-curve configuration variables, you can define your own set of Diffie-Hellman domain parameters. For example:

ssl.dh-file = "/etc/lighttpd/ssl/dh2048.pem" 
ssl.ec-curve = "secp384r1"

A set of Diffie-Hellman domain parameters can be generated like this (see OpenSSL documentation for help):

$ openssl dhparam -out dh2048.pem -outform PEM -2 2048

The set of supported elliptic-curves can be obtained like this (see OpenSSL documentation for help):

$ openssl ecparam -list_curves

SSL on multiple domains

A traditional problem with SSL in combination with name based virtual hosting has been that the SSL connection setup happens before the HTTP request. So at the moment lighttpd needs to send its certificate to the client, it does not know yet which domain the client will be requesting. This means it can only supply the default certificate (and use the corresponding key for encryption) and effectively, SSL can only be enabled for that default domain. There are a number of solutions to this problem, with varying levels of support.

One IP address per domain

This is the classic solution, which requires a separate IP address for each domain. Since Lighttpd knows the ip address that the client connected to before the SSL negotiation, it can simply select the right certificate to use based on the IP address. The most obvious downside to this approach is that it requires multiple addresses, which are not always available and can be expensive. This approach is currently the most reliable, since it works with all clients.

To do this in lighttpd, use the socket conditional:

$SERVER["socket"] == "10.0.0.1:443" {
ssl.pemfile = "/etc/lighttpd/ssl/www.example.org.pem"
}
$SERVER["socket"] == "10.0.0.2:443" {
ssl.pemfile = "/etc/lighttpd/ssl/mail.example.org.pem"
}

UCC / SAN Certificates

It is possible to put multiple names in a single certificate. These certificates are usually referred to as UCC (Unified Communications Certificates) certificates, or SAN (Subject Alternative Name) certificates. These certificates use the SAN property to store multiple domain names in a single certificate. This allows lighttpd to always use the same certificate, which is valid for all the domains it serves.

The main disadvantage of this approach is that you will have a single certificate with a lot of domain names in it and the certificate needs to change whenever you add a new domain name. When serving sites for different parties, this can be a problem.

According to digicert, SAN certificates are supported by nearly all browsers (except for some mobile browsers) and should be pretty safe to use.

Using a SAN certificate requires no special configuration for Lighttpd.

Wildcard certificates

Certificates support the use of wildcards, which can be useful to support multiple subdomains. For example, one can get a certificate for the *.example.org domain to support all of the subdomains of the example.org domain.

This approach of course does not scale well to multiple different domains. Again according to digicert, wildcard certificates should be supported by virtually all clients.

Server Name Indication (SNI)

An extension to the SSL / TLS protocol that has gained support recently is Server Name Indication. This addition is simple and elegant: During the SSL negotiation, the client sends the name of the host it wants to contact to the server, which can then use this information to select the correct certificate.

This is of course the best and most fitting solution to the problem. Lighttpd supports SNI since 1.4.24, if openssl 0.9.8f or newer is available.

The big downside of this approach is the limited browser support. The newest versions of the major browsers support SNI, but in particular Internet Explorer on Windows XP seems to lack support and support with mobile browsers is unclear.

To use SNI with lighttpd, simply put additional ssl.pemfile configuration directives inside host conditionals under the socket conditional. A default ssl.pemfile is still required in the socket conditional, though.

$SERVER["socket"] == ":443" {
ssl.pemfile = "/etc/lighttpd/ssl/the-default-domain.com.pem"
$HTTP["host"] == "www.example.org" {
ssl.pemfile = "/etc/lighttpd/www.example.org.pem"
}
$HTTP["host"] == "mail.example.org" {
ssl.pemfile = "/etc/lighttpd/mail.example.org.pem"
}
}

Other issues

Self-Signed Certificates

A self-signed SSL certificate can be generated like this: ::

$ openssl req -new -x509 \
-keyout server.pem -out server.pem \
-days 365 -nodes

Serve ONLY the domain you want under an SSL IP

The simple config suggested above, is perhaps too "vague" when serving multiple hosts, one per IP (the traditional and still most common way, until Windows XP dies and nearly all browsers support SNI). Consider this config:

$SERVER["socket"] == "10.0.0.1:443" {
ssl.pemfile = "/etc/lighttpd/ssl/domain1.com.pem"
server.document-root = "/www/servers/domain1.com"
}
$SERVER["socket"] == "10.0.0.2:443" {
ssl.pemfile = "/etc/lighttpd/ssl/domain2.com.pem"
server.document-root = "/www/servers/domain1.com"
}

Your DNS config might be:

domain1.com A 10.0.0.1
domain2.com A 10.0.0.2

Like this all is fine normally. However if your client has poisoned or stale DNS, or a /etc/hosts entry and makes the following request:

https://domain1.com

connecting to IP 10.0.0.2 because it has somehow resolved domain1.com to 10.0.0.2 (see above). Then lighty WILL serve the content of domain2 despite recevieving "Host:domain1.com" in the request header! You are kind of protected because your client should give an SSL domain mismatch warning here. However, as was the case in our config, domain1 and domain2 were actually subdomains and covered by the same wildcard cert => no ssl warning!

So we were getting CONTENT FROM "WRONG" DOMAIN/DOCROOT just because of stale DNS.

For a "tighter" config we would suggest:

$SERVER["socket"] == "10.0.0.1:443" {
ssl.pemfile = "/etc/lighttpd/ssl/domain1.com.pem"
$HTTP["host"] == "domain1.com" {
server.document-root = "/www/servers/domain1.com"
}
}
$SERVER["socket"] == "10.0.0.2:443" {
ssl.pemfile = "/etc/lighttpd/ssl/domain2.com.pem"
$HTTP["host"] == "domain2.com" {
server.document-root = "/www/servers/domain2.com"
}
}

This ensures that 10.0.0.2:443 will ONLY EVER serve content with a "Host: domain2.com" request header. If domain1.com is requested from 10.0.0.2:443 lighty will now respond with 404, which is better we think.

NOTE: the HTTP["host"] conditional should come after ssl engine config (ssl.enable, pemfile, cyphers...) as these are required to establish the connection, and must come before "host" is available (unless you are relying on SNI). Once the connection is up, we can make all subsequent config (docroot, log, rewrite etc) conditional upon "Host" as shown. Tested with lighttpd 1.4.32.

PCI DSS compliance

Matthew Glubb wrote:

I should clarify the reason for this work. From September 19th, all major online vendors taking card payments will be required to comply with the Payment Card Industry (PCI) Data Security Standard. Smaller vendors may self-certify but they will be more liable if fraud is committed.

Part of this standard is the disabling of SSLv2 and the removal of support for ciphers that have a key length of less than 128 bits. For this reason, I believe that the default SSL configuration for lighttpd should reflect this standard.

Since 1.4.12 you can use: ::

ssl.use-sslv2 = "disable"   -   (No longer needed as of 1.4.21 and 1.5 this is disabled by default.)
ssl.cipher-list = "..."

to disable SSLv2 and set a cipher-list. Make sure this settings are in the same block than ssl.engine = "enable", this will not work as global options.

cipher-list accepts a string containing the ciphers you would like to accept separated by whitespace. A list of strings will not work.

Matthew also provide a list of possible ciphers:

Hope it can be of use. Next time I'll submit a proper svn patch but I was in a hurry! Which might explain my somewhat short list of supported ciphers. I've since done some research and this list of supported ciphers is much more comprehensive. It supports all ciphers >= 128 bit key lengths for SSL v3.0, TLS v1.0, and AES cipher suites from RFC3268, extending TLS v1.0 (these seem to be the ones used by recent browsers, not included in the original list): ::

RC4-SHA
RC4-MD5
ADH-RC4-MD5
EDH-RSA-DES-CBC3-SHA
EDH-DSS-DES-CBC3-SHA
DES-CBC3-SHA
ADH-DES-CBC3-SHA
DES-CBC3-MD5
AES128-SHA
AES256-SHA
DH-DSS-AES128-SHA
DH-DSS-AES256-SHA
DH-RSA-AES128-SHA
DH-RSA-AES256-SHA
DHE-DSS-AES128-SHA
DHE-DSS-AES256-SHA
DHE-RSA-AES128-SHA
DHE-RSA-AES256-SHA

[FYI] bb says: the requirements permit use of SSLv2 as long as it isn't the only version available. for example, having SSLv2, SSLv3, and TLSv1 enabled is PCI compliant. SSLv2 should still be disabled, however, both because it is weak and because you can expect the PCI requirements to become more stringent in the future. here is my recommended cipher list, in order, which also happens to be PCI compliant: ::

DHE-RSA-AES256-SHA 
DHE-RSA-AES128-SHA
EDH-RSA-DES-CBC3-SHA
AES256-SHA
AES128-SHA
DES-CBC3-SHA
DES-CBC3-MD5
RC4-SHA
RC4-MD5
  • Note: you are forgetting the CAMELLIA cipher (Firefox 3 default) which going by it's wikipedia page is roughly equivalent to AES. For a good list do `openssl ciphers HIGH`

Important Update:

As of September 2013, It is no longer recommended to use RC4 ciphers, and various others such as 3DES should be disabled where possible.

See the release notes for Lighttpd 1.4.34 for further info. http://www.lighttpd.net/2014/1/20/1-4-34/

PCI DSS compliance

How to enable that the server requests a SSL client certificate?

HTTPS detection in PHP

Some PHP scripts try to detect HTTPS by checking if $_SERVER['HTTPS'] equals 'on'. To allow that, you can try this: ::

server.modules = (
"mod_setenv",
)
$SERVER["socket"] == "0.0.0.0:443" {
ssl.engine = "enable"
ssl.pemfile = "/etc/lighttpd/server.pem"
ssl.use-sslv2 = "disable"
setenv.add-environment = (
"HTTPS" => "on"
)
}

Links :