Project

General

Profile

Actions

Docs SSL » History » Revision 147

« Previous | Revision 147/168 (diff) | Next »
Snufkin, 2021-02-21 22:13


Secure HTTP

Module: mod_openssl mod_mbedtls mod_wolfssl mod_gnutls mod_nss

keywords: lighttpd, SSL, TLS

Description

  • lighttpd supports TLS/SSL using mod_openssl, mod_mbedtls, mod_wolfssl, mod_gnutls, or mod_nss.

Brief history

  • openssl support was historically built into the core of lighttpd 1.4.x, but is now a standalone module (mod_openssl) since lighttpd 1.4.46.
  • mod_openssl may also be built to use the openssl-compatibility layers provided by BoringSSL or LibreSSL.
  • Experimental support for mbedTLS (mod_mbedtls), wolfSSL (mod_wolfssl), GnuTLS (mod_gnutls), and NSS (mod_nss) is in development, and available for testing in lighttpd 1.4.56.
  • For TLS module alternatives to openssl, many ssl.* options are mapped from openssl to equivalents in other TLS libraries, including ssl.openssl.ssl-conf-cmd, but not completely so, and there is no plan to fully reimplement openssl SSL_CONF_cmd() (ssl.openssl.ssl-conf-cmd).

Quick Start

Stay Secure

Security software must be maintained.
  • Please regularly check that your systems are running the latest patches of TLS libraries. Checking at least once a month is recommended.
  • Do not run versions of software that have reached end-of-life (EOL) and are no longer receiving security patches!
    https://endoflife.software/applications/security-libraries/openssl
    openssl 0.9.8 EOL: 31 Dec 2015
    openssl 1.0.0 EOL: 31 Dec 2015
    openssl 1.0.1 EOL: 31 Dec 2016
    openssl 1.0.2 EOL: 31 Dec 2019
    openssl 1.1.0 EOL: 31 Aug 2018
    As of 1 Jan 2020, only openssl 1.1.1 and later continue to receive security patches from openssl.org

SSLv2, SSLv3, TLSv1, TLSv1.1 are deprecated by international standards organizations.
In lighttpd 1.4.56 and later, these deprecated protocols are disabled by default, though can still be explicitly enabled in lighttpd.conf

Configuration

basic options

option description
ssl.engine enable/disable ssl engine
ssl.pemfile path to the PEM file certificate chain (must contain both certificate chain and private key (unless ssl.privkey is set))
ssl.privkey path to the PEM file private key (required if private key is not in ssl.pemfile) (since 1.4.53)

feature selection (optional)

option description
ssl.openssl.ssl-conf-cmd specify openssl config commands (e.g. ("MinProtocol" => "TLSv1.2") restricts protocol to only TLS 1.2 or later) (since 1.4.48) (commit c09acbeb)
ssl.acme-tls-1 path to directory containing TLS-ALPN-01 ("acme-tls/1") challenges (Let's Encrypt option) (since 1.4.53)
ssl.read-ahead enable/disable use of SSL read ahead (lighttpd 1.4.45+) (default: disable) (mod_openssl only)
ssl.stapling-file path to file containing binary OCSP Response (DER format) (since 1.4.56) (see OCSP Stapling)
ssl.stek-file path to file containing binary session ticket encryption key (STEK) (global setting) (since 1.4.56) (see Session Tickets)
ssl.cipher-list configure the allowed SSL ciphers
(deprecated; prefer ssl.openssl.ssl-conf-cmd "CipherString")
ssl.honor-cipher-order enable/disable honoring server ordering preference for cipher selection (ssl.cipher-list) (default: enable)
(required for PFS unless ssl.cipher-list contains only ciphersuites which support PFS)
(allowing client preference permits clients to choose ChaCha20-Poly1305,
which might be better for mobile processors without optimized AES instruction sets)
(deprecated; prefer ssl.openssl.ssl-conf-cmd "Options" => "[+-]ServerPreference")

client certificate verification (optional)

option description
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 (e.g. SSL_CLIENT_S_DN_emailAddress, SSL_CLIENT_S_DN_UID, etc.)
ssl.ca-file path to the file for certificate authorities (CA) used for client certificate verification
ssl.ca-crl-file path to file for certificate revocation list (CRL) for client certificate (since 1.4.46)
ssl.ca-dn-file path to file for certificate authorities (CA) from which client should select client certs (if needed, a subset of ssl.ca-file) (since 1.4.46)

legacy options (prefer defaults unless you know what you are doing)

option description
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 (deprecated) enable/disable use of SSL version 2 (lighttpd < 1.4.21 only, newer version don't support SSLv2)
ssl.use-sslv3 (deprecated) enable/disable use of SSL version 3 (lighttpd >= 1.4.29 only) (disabled by default since 1.4.36)
ssl.disable-client-renegotiation (deprecated) enable/disable mitigation of client triggered re-negotiation in TLSv1.2 or earlier (see CVE-2009-3555) (default: enable mitigation)

Note: ssl.* configuration options are generally valid only in global scope or in the top level of a $SERVER["socket"] configuration condition, as they are needed when the socket connection is established, before the host is known. In cases where the client adds SNI (server name indication), some ssl.* options can be specified in $HTTP["host"] or $HTTP["scheme"] conditions, e.g. to select certificates for that specific connection. All other conditions occur after TLS negotiation has completed, so ssl.* directives nested in other configuration conditions may be ignored, including $SERVER["socket"] or $HTTP["host"] or $HTTP["scheme"] nested in other configuration conditions.

The following can be used in $HTTP["host"] or $HTTP["scheme"] conditions in addition to global scope or $SERVER["socket"] conditions. Use in other conditions may work in some versions of lighttpd, but is not guaranteed. Please test your configurations to validate that actual operation meets your expectations.

ssl.pemfile
ssl.privkey
ssl.verifyclient.activate
ssl.verifyclient.enforce
ssl.verifyclient.depth
ssl.verifyclient.exportcert
ssl.verifyclient.username
ssl.ca-file
ssl.ca-dn-file
ssl.acme-tls-1

In lighttpd 1.4.56 and later, ssl.ca-crl-file may be set alongside ssl.ca-file. (mod_openssl loads cert files at startup, and openssl requires both CAs and CRLs be loaded into the same (X509_STORE *))

On CPU-constrained systems (e.g. embedded systems), ssl.read-ahead = "disable" is recommended, and the default. If a system is not fast enough to keep up with crypto, a single connection with ssl.read-ahead = "enable" might monopolize the CPU and starve other connections, and might even delay server responses on its own connection. On systems where the CPU running TLS crypto is faster than the network wire speed, ssl.read-ahead = "enable" may slightly improve performance. In lighttpd 1.4.56 and later, ssl.read-ahead = "enable" may be set per $HTTP["host"]. Once enabled, read ahead may not be disabled on the connection, as the server may already have read additional blocks into memory.

Details

To enable SSL, you must provide a certificate and enable the SSL engine in each socket scope, i.e. ssl.engine = "enable" in each $SERVER["socket"] that should be SSL-enabled, and in the global scope if server.bind and server.port should be SSL-enabled. ssl.pemfile should contain both the certificate and the private key unless ssl.privkey is specified with the path to the private key. ssl.pemfile should additionally contain a properly sorted certificate chain for the certificate.

To make lighttpd SSL-only, simply put the following in your main config. By default, lighttpd listens to "0.0.0.0:80" unless server.bind and server.port are set to something else. To listen on port "0.0.0.0:443" and not on port 80:

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

To enable SSL/TLS on port 443 in addition to normal HTTP on port 80, put the ssl.engine configuration in a $SERVER["socket"] conditional block:

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

If you want to serve different sites from the global scope, you can change the document root inside the $SERVER["socket"] conditional:

$SERVER["socket"] == ":443" {
ssl.engine = "enable"
ssl.pemfile = "/etc/lighttpd/ssl/www.example.org.pem"
server.document-root = "/www/servers/www.example.org/secure/"
}

When you are using lighttpd 1.4.19 or later, you can also use the $HTTP["scheme"] conditional to distinguish between secure and normal requests. Note that you can't use the $HTTP["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/"
}

As a special case in lighttpd 1.4.46 and later, if ssl.pemfile and other ssl.* directives are in the global scope, then $SERVER["socket"] blocks can inherit the global config by setting ssl.engine = "enable" and no other ssl.* directives in the $SERVER["socket"] block. The following will set lighttpd to be HTTP on port 80 and HTTPS on port 443, on the default INADDR_ANY (0.0.0.0) and in6addr_any ([::]) wildcard addresses.

server.document-root = "/www/servers/www.example.org/" 
server.port = 80
$SERVER["socket"] == "[::]:80" { }
ssl.pemfile = "/etc/lighttpd/ssl/www.example.org.pem"
$SERVER["socket"] == ":443" { ssl.engine = "enable" }
$SERVER["socket"] == "[::]:443" { ssl.engine = "enable" }

If ssl.engine = "enable" directive is also in the global scope, then for some server sockets it is possible to set plain HTTP using ssl.engine = "disable". The following example demonstrates IPv4/IPv6 dual-stack server with open ports for both http and https connections.

server.document-root = "/www/servers/www.example.org/" 
ssl.pemfile = "/etc/lighttpd/ssl/www.example.org.pem"
server.port = 443
$SERVER["socket"] == "[::]:443" { ssl.engine = "enable" }
$SERVER["socket"] == ":80" { ssl.engine = "disable" }
$SERVER["socket"] == "[::]:80" { ssl.engine = "disable" }

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 openssl man-page for openssl-ciphers . For the latest list of recommended strong ciphers, see https://ssl-config.mozilla.org/#server=lighttpd&config=modern and periodically revisit the page.

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

Most 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.

For lighttpd to provide a chain of certificates, each ssl.pemfile should contain an ordered certificate chain. The order is a certificate, followed by the certificate of its issuer, and so on up to the root certificate. The final (root) certificate is optional.

Get your certificate chain right
https://medium.com/@superseb/get-your-certificate-chain-right-4b117a9c0fce

Alternative (deprecated): Historically, there is a feature/misfeature in openssl which re-uses the trusted CA certificate list (lighttpd ssl.ca-file) from the openssl (SSL_CTX *) to complete the server certificate chain if the chain has not been set for the openssl (SSL *). In other words, even though the CA list is intended for client certificate verification, the list is also used by openssl for the server certificate if a chain has not been set for the server certificate. As an alternative to the recommended method of having complete certificate chains in ssl.pemfile, you can set ssl.ca-file = "/etc/lighttpd/ssl/ca.crt" to a path with the intermediate certificates needed to complete the server certificate chain of the certificate in ssl.pemfile.

Perfect Forward Secrecy (PFS)

https://en.wikipedia.org/wiki/Forward_secrecy
"In cryptography, forward secrecy (FS), also known as perfect forward secrecy (PFS), is a feature of specific key agreement protocols that gives assurances that session keys will not be compromised even if the private key of the server is compromised.[1] Forward secrecy protects past sessions against future compromises of secret keys or passwords.[2] By generating a unique session key for every session a user initiates, the compromise of a single session key will not affect any data other than that exchanged in the specific session protected by that particular key."

lighttpd supports PFS if the lighttpd TLS module is configured to use protocols and ciphers that support PFS (enabled by default). To require PFS (and disallow downgrade) specify only ciphers which support PFS. (TLSv1.3 only uses ciphers which support PFS.)

# DEFAULT: As of Sep 2020, default in lighttpd 1.4.56 and widely supported by clients, except very old clients w/o TLSv1.2
#ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.2",  # in openssl 1.0.2: "Protocol" => "-ALL, TLSv1.2" 
#                            "Options" => "ServerPreference,
#                            "CipherString" => "HIGH")
# (lighttpd 1.4.55 and earlier: For increased security, you should disable SessionTicket or you should restart lighttpd server daily)
#ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.2",
#                            "Options" => "ServerPreference,-SessionTicket", #(lighttpd 1.4.55 and earlier)
#                            "CipherString" => "HIGH")

# RECOMMENDED: As of Sep 2020, a strong set of ciphers for PFS and widely supported by clients
#ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.2",
#                            "Options" => "-ServerPreference",
#                            "CipherString" => "EECDH+AESGCM:AES256+EECDH:CHACHA20")

# STRONGER: As of Sep 2020, a strong set of ciphers for PFS and widely supported by modern clients, without CBC ciphers reported as weak by SSLLabs
#ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.2",
#                            "Options" => "-ServerPreference",
#                            "CipherString" => "EECDH+AESGCM:AES256+EECDH:CHACHA20:!SHA1:!SHA256:!SHA384")

# STRONGEST: As of Sep 2020, for use w/ modern clients only; not compat w/ older clients
#ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.3",
#                            "Options" => "-ServerPreference")

# Expanded cipher list equivalent to ssl.cipher-list = "EECDH+AESGCM:AES256+EECDH:CHACHA20" 
# for mod_wolfssl whose modules do not support the shorter CipherString syntax
#ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.2",
#                            "Options" => "-ServerPreference",
#                            "CipherString" => "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-CCM8:ECDHE-ECDSA-AES256-CCM:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-CHACHA20-POLY1305:RSA-PSK-CHACHA20-POLY1305:DHE-PSK-CHACHA20-POLY1305:ECDHE-PSK-CHACHA20-POLY1305:PSK-CHACHA20-POLY1305")

# Expanded cipher list equivalent to ssl.cipher-list = "EECDH+AESGCM:AES256+EECDH:CHACHA20:!SHA1:!SHA256:!SHA384" 
# for mod_mbedtls, mod_wolfssl, mod_gnutls, whose modules do not support the shorter syntax
#ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.2",
#                            "Options" => "-ServerPreference",
#                            "CipherString" => "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-CCM8:ECDHE-ECDSA-AES256-CCM:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-CHACHA20-POLY1305:RSA-PSK-CHACHA20-POLY1305:DHE-PSK-CHACHA20-POLY1305:ECDHE-PSK-CHACHA20-POLY1305:PSK-CHACHA20-POLY1305")

ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.2") is strongly recommended (and "TLSv1.3" if supported by clients)
TLS protocols earlier than TLSv1.2 are deprecated, and TLSv1.3 is preferred. Major client browsers plan to drop support for TLSv1.0 and TLSv1.1 in 2020.
https://sensorstechforum.com/apple-microsoft-google-drop-support-tls1-0-tls1-1/
https://www.thesslstore.com/blog/apple-microsoft-google-disable-tls-1-0-tls-1-1/
https://scotthelme.co.uk/legacy-tls-is-on-the-way-out/

Cipher Selection

By default, lighttpd implicitly applies ssl.cipher-list = "HIGH" (since lighttpd 1.4.54) if ssl.cipher-list is not explicitly set in lighttpd.conf. This is a reasonable default on the modern internet. For a higher security, use "SUITEB192" or "SUITEB128". For compatibility with much older clients, try "MEDIUM". There are many other options (openssl ciphers) that can be used with mod_openssl, though only a small subset is supported by mod_mbedtls, mod_wolfssl, mod_gnutls, mod_nss parsing of ssl.openssl.ssl-conf-cmd or ssl.cipher-list.

Either ssl.openssl.ssl-conf-cmd = ("Options" => "-ServerPreference") or ssl.honor-cipher-order = "disable" (same effect) is recommended when all of the ciphers listed support Perfect Forward Secrecy, e.g. Mozilla ssl-configurator Modern config for lighttpd . Once the cipher list has been limited to those ciphers supporting Perfect Forward Secrecy, it is useful to permit cipher selection using client preferences since mobile devices often lack hardware acceleration for AES, and on those devices ChaCha20-Poly1305 might be up to 3x faster, given mobile users a better experience and consuming less battery life. On desktops, where hardware more often has native hardware for AES acceleration, AES-based ciphers are often the fastest. (Historically, the default in lighttpd is to use the server preference for cipher selection (ssl.honor-cipher-order = "enable" to prevent cipher downgrade attacks to weaker ciphers, such as those that do not support Perfect Forward Secrecy.)

lighttpd 1.4.56 with openssl 1.1.1 or later will use openssl option SSL_OP_PRIORITIZE_CHACHA for the benefit of mobile users, even if ssl.honor-cipher-order = "enable" and AES ciphers are listed before ChaCha20-Poly1305.

References:
https://blog.cloudflare.com/do-the-chacha-better-mobile-performance-with-cryptography/
https://security.googleblog.com/2014/04/speeding-up-and-strengthening-https.html

Session Tickets

A session ticket is an optional TLS 1.2 extension that may be used to reduce latency when a client reconnects, also known as session resumption. A session ticket allows the server to delegate storage of session state to the client, but encrypts that state with a server-side session ticket encryption key (STEK). However, Perfect Forward Secrecy (PFS) is lost if the STEK has an infinite lifetime, e.g. is never rotated. On the other hand, replacing a STEK invalidates current session tickets, requiring a full TLS handshake upon a new connection, rather than quicker session resumption, so replacement should not occur too frequently.

lighttpd 1.4.55 and earlier did not explicitly configure session tickets, but the underlying openssl library enabled session tickets by default, and never rotated the STEK. Since lighttpd 1.4.48, session tickets can be disabled in lighttpd using ssl.openssl.ssl-conf-cmd = ("Options" => "-SessionTicket")

In lighttpd 1.4.56, session tickets are still enabled by default, but are automatically rotated in all lighttpd TLS modules except in mod_nss. By default, lighttpd will rotate the STEK and each key has a lifetime of 24 hours. This is a reasonable tradeoff to allow session resumption for a defined window of time while also preserving Perfect Forward Secrecy after a STEK is rotated and the previous STEK discarded. This default is sufficient for a single lighttpd process, i.e. not using lighttpd with multiple workers. For multiple workers, use ssl.stek-file (since 1.4.56).

ssl.stek-file allows the admin control over the STEK lifetime and rotation schedule, which is useful when using multiple lighttpd workers or running lighttpd across multiple servers to serve the same site(s). If ssl.stek-file is configured in the global configuration scope, the builtin STEK rotation behavior is disabled. Instead, lighttpd will read the STEK from the file and will check every 64 seconds to see if the file needs to be re-read for a new STEK. lighttpd mod_openssl stores up to three (3) STEKs, so if the encryption key lifetime is 24 hours, then it is suggested that a new STEK be generated every 8 hours. lighttpd mod_mbedtls uses mbedtls internals, which store up to (2) STEKs, so it is suggested that a new STEK be generated every 12 hours. lighttpd mod_gnutls has only one active STEK at a time, so it is suggested that a new STEK be generated every 24 hours. Using ssl.stek-file is recommended if using lighttpd with multiple workers, as this mechanism allows the same keys to be used by each lighttpd worker, as well as across multiple servers. You are responsible for generating the STEK file via an external job. The STEK file should be stored in non-persistent storage, e.g. /dev/shm/lighttpd/stek-file (in memory) with appropriate permissions set to keep stek-file from being read by other users. Where possible, systems should also be configured without swap in order to keep the key from inadventently being swapped out to persistent storage.

The format of binary ssl.stek-file is:
  • 4-byte - format version (always 0; for use if format changes)
  • 4-byte - activation timestamp
  • 4-byte - expiration timestamp
  • 16-byte - session ticket key name
  • 32-byte - session ticket HMAC encrpytion key
  • 32-byte - session ticket AES encrpytion key

The STEK file can be created with a command such as:

cd /dev/shm/lighttpd && \
  dd if=/dev/random bs=1 count=80 status=none | \
  perl -e 'print pack("iii",0,time()+300,time()+86400),<>' \
  > STEK-file.$$ && mv STEK-file.$$ STEK-file
# (alternative: 'openssl rand 80' can be substituted for 'dd if=/dev/random bs=1 count=80 status=none' above)
The above delays activation time by 5 mins (+300 sec) to allow file to be propagated to other machines. (admin must handle this independently) If STEK generation is performed immediately prior to starting lighttpd, admin should activate keys immediately (without +300). /dev/shm/lighttpd is used in this example and must be created and permissioned by the admin. lighttpd reads the stek-file but does not create the stek-file or any part of the path.

Note that if using lighttpd before lighttpd 1.4.56 with multiple lighttpd workers, no coordinated STEK rotation occurs between processes other than by (some external job) restarting lighttpd. Restarting lighttpd generates a new key that is shared by lighttpd workers for the lifetime of the new key. If the rotation period expires and lighttpd has not been restarted, lighttpd workers will generate new independent keys, making session tickets less effective for session resumption, since clients have a lower chance for future connections to reach the same lighttpd worker. However, things will still work, and a new session will be created if session resumption fails. Before lighttpd 1.4.56, admins should plan to restart lighttpd at least once every 24 hours if session tickets are enabled and multiple lighttpd workers are configured. (A graceful restart can be accomplished by sending SIGUSR1 to the parent lighttpd process.) The difficulty, complexity, and per-environment nature of key distribution is why periodically restarting lighttpd (or nginx, or Apache) is frequently the recommended method for rotating STEKs. Proper use of ssl.stek-file with lighttpd 1.4.56 makes this easier and does not need server restarts.

If there is a connection load balancer in front of multiple independent lighttpd servers (with independent STEKs), and the load balancer is not performing SSL-termination, then it may be beneficial to configure the load balancer algorithm to attempt sticky routing, if available, so that the client has a better chance to be directed to the same server that can validate the session ticket and allow session resumption. Again, this is not necessary when using lighttpd 1.4.56 with ssl.stek-file if the STEK file is propagated to each server to that the keys are shared.

Warning: Jun 2020: GnuTLS 3.6.4 to GnuTLS 3.6.13 are vulnerable to CVE-2020-13777 If using mod_gnutls, please use GnuTLS 3.6.14 or later, or disable Session Tickets in lighttpd using ssl.openssl.ssl-conf-cmd = ("Options" => "-SessionTicket")

Warning: limitation in mod_nss: NSS does not provide a public interface to rotate the STEK. lighttpd mod_nss does not support STEK rotation. If using mod_nss, disable Session Tickets in lighttpd using ssl.openssl.ssl-conf-cmd = ("Options" => "-SessionTicket") or consider restarting the lighttpd server every 24 hours.

Session Caching

Session Caching is disabled since lighttpd 1.4.56. Session Tickets (TLSv1.2) are recommended instead of Session Caching as Session Tickets are superior, using less memory and being more easily portable across multiple lighttpd workers and multiple servers when the Session Ticket Encryption Key is shared (and rotated). To do similar with Session Caching requires much more complex infrastructure (which some other web server support), but which is no longer needed when using Session Tickets with TLSv1.2 and later.

server.feature-flags += ("ssl.session-cache" => "enable") can be used to restore default behavior prior to lighttpd-1.4.56. If set, lighttpd will not explicitly disable the TLS library session cache, instead using the TLS library defaults. Note: session cache is not yet implemented in mod_mbedtls or mod_gnutls (and is low priority since session tickets should be preferred.)

OCSP Stapling

ssl.stapling-file (since lighttpd 1.4.56) may be set alongside ssl.pemfile, in the same scope. If the file is present, the contents of the file (the OCSP Response) will be stapled along with the certificate as part of the TLS handshake.

The file must be maintained and updated periodically so that the OCSP Response does not expire. A few minutes prior to expiration, lighttpd checks about once a minute for an updated ssl.stapling-file. If the expiration can not be determined, e.g. the OCSP Response does not contain a Next Update field, then lighttpd re-reads the ssl.stapling-file once an hour.

The contents of ssl.stapling-file can be produced with appropriate variables filled in: openssl ocsp -issuer "$CHAIN_PEM" -cert "$CERT_PEM" -respout "$OCSP_RESP" -noverify -no_nonce -url "$OCSP_URI". In the lighttpd source tree, doc/scripts/cert-staple.sh provides a sample script which can be run periodically by a scheduled job.

Note: mbedTLS does not currently provide support for OCSP stapling.

HTTP/2 and TLS

RFC 7540 HTTP/2 Section 9.2. Use of TLS Features specifies requirements for using TLS with HTTP/2. By default in lighttpd 1.4.56 and later, lighttpd TLS modules disable TLS compression and TLS renegotiation in TLSv1.2 as required by RFC 7540. The RFC 7540 HTTP/2 restriction on ciphers can be achieved by specifying the "STRONGER" set of ciphers "CipherString" => "EECDH+AESGCM:AES256+EECDH:CHACHA20:!SHA1:!SHA256:!SHA384" for mod_openssl (and might differ slightly for other lighttpd TLS modules; see the Perfect Forward Secrecy (PFS) section above for alternative cipher strings). The lighttpd default (since lighttpd 1.4.54) of "CipherString" => "HIGH" is for wider compatibilty, and is not strict enough to achieve the HTTP/2 recommendations. Please use the STRONGER or STRONGEST configurations recommended above to follow the stricter guidance in RFC 7540.

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 by clients.

Server Name Indication (SNI)

Server Name Indication (SNI) is a TLS extension to the TLS handshake that allows the client to send the name of the host it wants to contact. The server can then use this information to select the correct certificate. SNI is generally well-supported by most clients, though not all mobile clients.

To use SNI with lighttpd, simply put additional ssl.pemfile configuration directives inside $HTTP["host"] conditionals under the $SERVER["socket"] conditional. A default ssl.pemfile is still required in the $SERVER["socket"] conditional.

$SERVER["socket"] == ":443" {
ssl.engine = "enable"
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"
}
}

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 works with all clients but also uses many IP addresses.

To do this in lighttpd, use the $SERVER["socket"] conditional:

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

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

Inherit ssl.* config from global config scope

To enable TLS/SSL in multiple $SERVER["socket"] contexts, but sharing ssl.* config, either duplicate the ssl.* config within each $SERVER["socket"], or put the ssl.* directives in the global context and have $SERVER["socket"] config block contain ssl.engine = "enable" without any other ssl.* directives in that $SERVER["socket"] config block.

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

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 compatibility, SAN certificates are supported by nearly all browsers (except for some mobile browsers) and should be pretty safe to use.

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 compatibility, wildcard certificates should be supported by virtually all clients.

Other issues

Let's Encrypt bootstrap

lighttpd supports Let's Encrypt bootstrap using TLS-ALPN-01 verification challenge (since lighttpd 1.4.53)

See steps in HowToSimpleSSL

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.engine = "enable"
ssl.pemfile = "/etc/lighttpd/ssl/domain1.com.pem"
server.document-root = "/www/servers/domain1.com"
}
$SERVER["socket"] == "10.0.0.2:443" {
ssl.engine = "enable"
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.document-root = "/www/servers/other-catchall  # e.g. HTTP/1.0 clients sending HTTP request headers _without_ Host:
$SERVER["socket"] == "10.0.0.1:443" {
    ssl.engine  = "enable" 
    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.engine  = "enable" 
    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 respond with 404.

Troubleshooting

  • Please note that certificate files are opened before the server performs chroot (if configured to chroot). 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 dhparam doc for help):

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

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

$ openssl ecparam -list_curves

Limitations

  • mod_nss does not support EC certificates or ssl.ca-file. NSS libraries prefers to store certificates in a certificate database and does not provide the same support for individual files, which is the typical usage with other TLS libraries. NSS is not wrong, but it is different. Patches welcome.

Links

Further TLS Security Enhancements

Reference:
- https://depthsecurity.com/blog/pins-and-staples-enhanced-ssl-security
- https://web.archive.org/web/20190604160808/https://depthsecurity.com/blog/pins-and-staples-enhanced-ssl-security

Some keywords and phrases to enter into a search engine for your own further research.

  • Certificate Transparency (CT)
    - RFC 6962 Certificate Transparency
    - requires that CAs publish open records of certificate issuance, publicly verifiable by others
    - often part of CA standard operating procedures
    - https://scotthelme.co.uk/certificate-transparency-an-introduction/
    - HTTP response header Expect-CT instructions clients to report or reject certificates missing Signed Certificate Timestamp (SCT) information
  • OCSP Stapling
    - RFC 6961 The Transport Layer Security (TLS) Multiple Certificate Status Request Extension
    - servers send (Online Certificate Status Protocol (OCSP) response to client along with the server's own certificate
    - ssl.stapling-file (since 1.4.56)
  • OCSP Must-Staple
    RFC 7633 X.509v3 Transport Layer Security (TLS) Feature Extension
    - Instructs client to reject certificate if TLS handshake does not also provide OCSP stapling status information
    - OCSP Must-Staple X.509v3 extension must be requested in the certificate signing request (CSR) sent to the certificate authority (CA)
    - HTTP response header Expect-Staple might also be leveraged to validate the results of site OCSP stapling operations and procedures before new certificates with Must-Staple are deployed.

Updated by Snufkin almost 4 years ago · 147 revisions