Index | Thread | Search

From:
Florian Obser <florian@openbsd.org>
Subject:
acme-client(8): Adapt renewal calculation for shortlived certificates.
To:
tech <tech@openbsd.org>
Date:
Tue, 16 Sep 2025 17:31:23 +0200

Download raw body.

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