From: Florian Obser Subject: acme-client(8): Adapt renewal calculation for shortlived certificates. To: tech Date: Tue, 16 Sep 2025 17:31:23 +0200 If the lifetime is more than 10 days, renew if less than 1/3 of the lifetime is left. Otherwise renew after 1/2 of the remaining lifetime. Since we suggest to run the cronjob daily, this is capped at 3 days remaining lifetime to have the opportunity to run the cronjob at least twice. I would like to commit this as soon as the tree unlocks again after 7.8. OK? diff --git revokeproc.c revokeproc.c index 0f1bf32678b..6790211e40b 100644 --- revokeproc.c +++ revokeproc.c @@ -32,13 +32,11 @@ #include "extern.h" -#define RENEW_ALLOW (30 * 24 * 60 * 60) - /* - * Convert the X509's expiration time into a time_t value. + * Convert the X509's notAfter time into a time_t value. */ static time_t -X509expires(X509 *x) +X509notafter(X509 *x) { ASN1_TIME *atim; struct tm t; @@ -58,6 +56,30 @@ X509expires(X509 *x) return timegm(&t); } +/* + * Convert the X509's notBefore time into a time_t value. + */ +static time_t +X509notbefore(X509 *x) +{ + ASN1_TIME *atim; + struct tm t; + + if ((atim = X509_getm_notBefore(x)) == NULL) { + warnx("missing notBefore"); + return -1; + } + + memset(&t, 0, sizeof(t)); + + if (!ASN1_TIME_to_tm(atim, &t)) { + warnx("invalid ASN1_TIME"); + return -1; + } + + return timegm(&t); +} + int revokeproc(int fd, const char *certfile, int force, int revocate, const char *const *alts, size_t altsz) @@ -70,7 +92,8 @@ revokeproc(int fd, const char *certfile, int force, X509 *x = NULL; long lval; enum revokeop op, rop; - time_t t; + time_t notafter, notbefore, cert_validity; + time_t remaining_validity, renew_allow; size_t j; /* @@ -125,8 +148,13 @@ revokeproc(int fd, const char *certfile, int force, /* Read out the expiration date. */ - if ((t = X509expires(x)) == -1) { - warnx("X509expires"); + if ((notafter = X509notafter(x)) == -1) { + warnx("X509notafter"); + goto out; + } + + if ((notbefore = X509notbefore(x)) == -1) { + warnx("X509notbefore"); goto out; } @@ -252,14 +280,35 @@ revokeproc(int fd, const char *certfile, int force, goto out; } - rop = time(NULL) >= (t - RENEW_ALLOW) ? REVOKE_EXP : REVOKE_OK; + cert_validity = notafter - notbefore; + + if (cert_validity < 0) { + warnx("Invalid cert, expire time before inception time"); + rc = -1; + goto out; + } + if (cert_validity > 10 * 24 * 60 * 60) + renew_allow = cert_validity / 3; + else + renew_allow = cert_validity / 2; + + /* We suggest to run renewals daily. Make sure we have 2 chances. */ + if (renew_allow < 3 * 24 * 60 *60) + renew_allow = 3 * 24 * 60 *60; + + remaining_validity = notafter - time(NULL); + + if (remaining_validity < renew_allow) + rop = REVOKE_EXP; + else + rop = REVOKE_OK; if (rop == REVOKE_EXP) dodbg("%s: certificate renewable: %lld days left", - certfile, (long long)(t - time(NULL)) / 24 / 60 / 60); + certfile, (long long)(remaining_validity / 24 / 60 / 60)); else dodbg("%s: certificate valid: %lld days left", - certfile, (long long)(t - time(NULL)) / 24 / 60 / 60); + certfile, (long long)(remaining_validity / 24 / 60 / 60)); if (rop == REVOKE_OK && force) { warnx("%s: %sforcing renewal", certfile, -- In my defence, I have been left unsupervised.