I reviewed the lighttpd code base and this initialization of the STEK in the TLS modules is the only place that jumped out at me, the same place you identified.
I've made a quick patch (see attached) which is maybe not the best but mostly fixed the problem for me.
Would you explain further what you mean by "mostly fixed the problem for me"?
Your patch looks like one viable option. Another is to initialize stek_rotate_ts to a negative value, e.g. -28800 or -86400 depending on the TLS module. That assumes that time_t is a signed value, which is generally true, but not guaranteed. I'll probably base a solution on your patch.
diff --git a/src/mod_gnutls.c b/src/mod_gnutls.c
index 480ce739..68189575 100644
--- a/src/mod_gnutls.c
+++ b/src/mod_gnutls.c
@@ -407,7 +407,8 @@ mod_gnutls_session_ticket_key_check (server *srv, const plugin_data *p, const ti
if (stek->expire_ts < cur_ts)
mod_gnutls_session_ticket_key_free();
}
- else if (cur_ts - 86400 >= stek_rotate_ts) { /*(24 hours)*/
+ else if (cur_ts - 86400 >= stek_rotate_ts /*(24 hours)*/
+ || 0 == stek_rotate_ts) {
mod_gnutls_session_ticket_key_rotate(srv);
stek_rotate_ts = cur_ts;
}
diff --git a/src/mod_mbedtls.c b/src/mod_mbedtls.c
index 6352c733..e29f040f 100644
--- a/src/mod_mbedtls.c
+++ b/src/mod_mbedtls.c
@@ -361,7 +361,9 @@ mod_mbedtls_session_ticket_key_check (plugin_data *p, const time_t cur_ts)
mbedtls_cipher_get_key_bitlen(&key->ctx),
MBEDTLS_ENCRYPT);
if (0 != rc) { /* expire key immediately if error occurs */
- key->generation_time = cur_ts - ctx->ticket_lifetime - 1;
+ key->generation_time = cur_ts > ctx->ticket_lifetime
+ ? cur_ts - ctx->ticket_lifetime - 1
+ : 0;
ctx->active = 1 - ctx->active;
}
mbedtls_platform_zeroize(stek, sizeof(tlsext_ticket_key_t));
diff --git a/src/mod_openssl.c b/src/mod_openssl.c
index f8d5b5dc..93876dd1 100644
--- a/src/mod_openssl.c
+++ b/src/mod_openssl.c
@@ -446,7 +446,7 @@ mod_openssl_session_ticket_key_check (const plugin_data *p, const time_t cur_ts)
rotate = mod_openssl_session_ticket_key_file(p->ssl_stek_file);
tlsext_ticket_wipe_expired(cur_ts);
}
- else if (cur_ts - 28800 >= stek_rotate_ts) /*(8 hours)*/
+ else if (cur_ts - 28800 >= stek_rotate_ts || 0 == stek_rotate_ts)/*(8 hrs)*/
rotate = mod_openssl_session_ticket_key_generate(cur_ts, cur_ts+86400);
if (rotate) {
diff --git a/src/mod_wolfssl.c b/src/mod_wolfssl.c
index 2bc1906c..13e0978f 100644
--- a/src/mod_wolfssl.c
+++ b/src/mod_wolfssl.c
@@ -432,7 +432,7 @@ mod_openssl_session_ticket_key_check (const plugin_data *p, const time_t cur_ts)
rotate = mod_openssl_session_ticket_key_file(p->ssl_stek_file);
tlsext_ticket_wipe_expired(cur_ts);
}
- else if (cur_ts - 28800 >= stek_rotate_ts) /*(8 hours)*/
+ else if (cur_ts - 28800 >= stek_rotate_ts || 0 == stek_rotate_ts)/*(8 hrs)*/
rotate = mod_openssl_session_ticket_key_generate(cur_ts, cur_ts+86400);
if (rotate) {
In addition I've seen that the default openssl parameters changed from "Options" => "ServerPreference,-SessionTicket" in 1.4.55 to "Options" => "ServerPreference" in 1.4.56 (as described in the wiki [1]) which is why I didn't see the issue before upgrading.
Just a clarification: the defaults for SessionTicket
did not change in lighttpd 1.4.56. Session tickets are enabled by default in the openssl library, and the openssl library does not automatically rotate the STEK. lighttpd 1.4.56 and later add behavior to provide and to rotate the STEK by default. That's the behavior change. I think that you ran into missing initialization of the lighttpd's STEK structure due to incorrect time on the embedded device. The wiki documents a secure practice (disable session tickets with -SessionTicket
) for lighttpd 1.4.55 and earlier, if lighttpd is not periodically restarted, e.g. once a day, to force the openssl library to regenerate a new STEK.
I have a question for you: embedded devices do not tend to have a whole lot of entropy, and so it is considered good practice to save a random seed to persistent storage, and then to restore that random seed upon restart to reduce hangs waiting for initial entropy. Why is something similar not done with time? Why not ship the devices with a starting time of manufacturing time (and a random seed changed per device) for bootstrapping? Then the time might be weeks or months in the past, but would never be decades in the past (1970). Things might work better with TLS certificates, which have a better chance of being within a time window of validity. Things might also work better with cookies, HTTP Digest auth, etc.
[TLS] init STEK even if time is 1970 (fixes #3075)
(thx DamienT)
x-ref:
"TLS 1.3 with SessionTicket fail for the first 8 hours of 1970"
https://redmine.lighttpd.net/issues/3075