Docs SSL » History » Revision 146
Revision 145 (gstrauss, 2021-01-05 19:59) → Revision 146/168 (gstrauss, 2021-02-01 11:52)
h1. Secure HTTP
bq. Module: mod_openssl mod_mbedtls mod_wolfssl mod_gnutls mod_nss
bq. keywords: lighttpd, SSL, TLS
{{>toc}}
h2. Description
* lighttpd supports TLS/SSL using @mod_openssl@, @mod_mbedtls@, @mod_wolfssl@, @mod_gnutls@, or @mod_nss@.
h2. 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@).
h2. Quick Start
* While there are many configuration options, a minimal configuration requires simply:
<pre>
server.modules += ("mod_openssl")
$SERVER["socket"] == ":443" {
ssl.engine = "enable"
ssl.pemfile = "/path/to/cert-privkey-and-cert-chain.pem"
}
</pre>
* [[HowToSimpleSSL]] for other simple sample configs and Let's Encrypt support
* [[HowToRedirectHttpToHttps|Redirect HTTP requests to HTTPS]]
* [[Docs_ModSetEnv#HTTP-Strict-Transport-Security-HSTS|HTTP Strict Transport Security]] (HSTS) using [[Docs_ModSetEnv|mod_setenv]]
h2. 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
h2. Configuration
basic options
table{margin-left: 2em}.
|_.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)
table{margin-left: 2em}.
|_.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 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 [[Docs_SSL#OCSP Stapling|OCSP Stapling]])|
| ssl.stek-file | path to file containing binary session ticket encryption key (STEK) (global setting) (since 1.4.56) (see [[Docs_SSL#Session-Tickets|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)
table{margin-left: 2em}.
|_.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)
table{margin-left: 2em}.
|_.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.
<pre>
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
</pre>
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.
h3. Details
To enable SSL, you must provide a certificate and enable the SSL engine in each socket scope, i.e. <code>ssl.engine = "enable"</code> in each @$SERVER["socket"]@ that should be SSL-enabled, and in the global scope if <code>server.bind</code> and <code>server.port</code> 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 <code>ssl.pemfile</code> and other ssl.* directives are in the global scope, then <code>$SERVER["socket"]</code> blocks can inherit the global config by setting <code>ssl.engine = "enable"</code> and _no other ssl.* directives_ in the <code>$SERVER["socket"]</code> 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 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":https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html . For the latest list of recommended strong ciphers, see https://ssl-config.mozilla.org/#server=lighttpd&config=modern and periodically revisit the page.
h3. 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
h3. 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@.
h3. 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.)
<pre>
# 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")
</pre>
<pre>
# 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")
</pre>
@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/
h3. 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":https://www.openssl.org/docs/manmaster/man1/ciphers.html) 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":https://ssl-config.mozilla.org/#server=lighttpd&config=modern . 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
h3. 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:
<pre>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)
</pre>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":https://gitlab.com/gnutls/gnutls/-/issues/1011 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.
h3. 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.)
h3. 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.
h3. HTTP/2 and TLS
"RFC 7540 HTTP/2":https://httpwg.org/specs/rfc7540.html Section "9.2. Use of TLS Features":https://httpwg.org/specs/rfc7540.html#TLSUsage 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.
h2. 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.
h3. Server Name Indication (SNI)
"Server Name Indication":http://en.wikipedia.org/wiki/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"
}
}
h3. 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:
<pre>
$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"
}
</pre>
h3. 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 <code>ssl.engine = "enable"</code> without any other <code>ssl.*</code> directives in that @$SERVER["socket"]@ config block.
<pre>
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"
}
</pre>
h3. 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":http://www.digicert.com/subject-alternative-name-compatibility.htm, SAN certificates are supported by nearly all browsers (except for some mobile browsers) and should be pretty safe to use.
h3. 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":http://www.digicert.com/ssl-support/wildcard-compatibility.htm, wildcard certificates should be supported by virtually all clients.
h2. Other issues
h3. 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]]
h3. 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
h3. 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:<pre>
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"
}
}
</pre>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.
h3. 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").
h3. 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).
h3. 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":http://tools.ietf.org/rfc/rfc5114.txt and "prime256v1" (also known as "secp256r1") elliptic curve from "RFC 4492":http://tools.ietf.org/rfc/rfc4492.txt. 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":http://tools.ietf.org/rfc/rfc4492.txt.
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":https://www.openssl.org/docs/man1.1.1/man1/dhparam.html 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":https://www.openssl.org/docs/man1.1.1/man1/ecparam.html doc for help):
$ openssl ecparam -list_curves
h3. 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.
h2. Links
* [[HowToSimpleSSL]]
* [[HowToRedirectHttpToHttps|Redirect HTTP requests to HTTPS]]
* [[lighttpd:IPv6-Config|IPv6 Config]]
* Qualsys SSL Labs "SSL/TLS Deployment Best Practices":https://www.ssllabs.com/projects/best-practices/index.html
h2. 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.
* DNS Certification Authority Authorization (CAA)
- "RFC 6844":https://tools.ietf.org/html/rfc6844 DNS Certification Authority Authorization (CAA) Resource Record
- Precaution against other CAs (not your chosen CA) being tricked into issuing certificate for your domain
- https://sslmate.com/caa/
- https://scotthelme.co.uk/certificate-authority-authorization/
* Certificate Transparency (CT)
- "RFC 6962":https://tools.ietf.org/html/rfc6962 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
* HTTP Strict Transport Security (HSTS)
- "RFC 6797":https://tools.ietf.org/html/rfc6797 HTTP Strict Transport Security (HSTS)
- HTTP response header to share site HTTPS policy with client
- [[Docs_ModSetEnv#HTTP-Strict-Transport-Security-HSTS|HTTP Strict Transport Security]] using [[Docs_ModSetEnv|mod_setenv]]
* OCSP Stapling
- "RFC 6961":https://tools.ietf.org/html/rfc6961 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":https://tools.ietf.org/html/rfc7633 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.
* HTTP Public Key Pinning (HPKP)
- "RFC 7469":https://tools.ietf.org/html/rfc7469 Public Key Pinning Extension for HTTP
- no longer recommended due to operational burdens and dangers
- https://scotthelme.co.uk/hpkp-is-no-more/