Index | Thread | Search

From:
Stuart Henderson <stu@spacehopper.org>
Subject:
draft-aaron-acme-profiles support for acme-client
To:
tech <tech@openbsd.org>
Date:
Sat, 11 Jan 2025 11:12:46 +0000

Download raw body.

Thread
https://letsencrypt.org/2025/01/09/acme-profiles/
https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/

letsencrypt are allowing selection of different certificate profiles via an
extension to newOrder. Initially the options are "classic" (as now) and
"tlsserver" which follows some CA/B recommendations, resulting in these
changes:

...
     Signature Algorithm: sha256WithRSAEncryption
         Issuer: C=US, O=(STAGING) Let's Encrypt, CN=(STAGING) Wannabe Watercress R11
         Validity
             Not Before: xxx
             Not After : xxx
-        Subject: CN=xxx
+        Subject:
         Subject Public Key Info:
...
X509v3 extensions:
             X509v3 Key Usage: critical
-                Digital Signature, Key Encipherment
+                Digital Signature
             X509v3 Extended Key Usage:
-                TLS Web Server Authentication, TLS Web Client Authentication
+                TLS Web Server Authentication
             X509v3 Basic Constraints: critical
                 CA:FALSE
-            X509v3 Subject Key Identifier:
-                xxx
             X509v3 Authority Key Identifier:
                 keyid:xxx

As explained in their announcement, it's currently supported by
letsencrypt's staging environment but not the production one yet.
In the future they will also be allowing an *optional* selection
of "shortlived" with the above changes and also cutting validity
to 6 days.

(This is probably a precursor to cutting validity by default, but
my gut feeling is that they'd probably drop to ~30 days but still
allow sites to select shorter validity if they want).

Here's an implementation of "profile" for acme-client done as a
setting for the domain.

Thoughts?

Do we want this yet?

Is "domain" the right place? It might make sense to allow this under
"authority" instead of, or as well as, under "domain".

Index: acme-client.conf.5
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/acme-client.conf.5,v
diff -u -p -r1.29 acme-client.conf.5
--- acme-client.conf.5	11 Jan 2021 07:23:42 -0000	1.29
+++ acme-client.conf.5	11 Jan 2025 10:50:51 -0000
@@ -187,6 +187,9 @@ A backup with name
 is created if
 .Ar file
 exists.
+.It Ic profile Ar profile
+The certificate profile to be requested.
+If this setting is absent, no profile request is made.
 .It Ic sign with Ar authority
 The certificate authority (as declared above in the
 .Sx AUTHORITIES
Index: extern.h
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/extern.h,v
diff -u -p -r1.21 extern.h
--- extern.h	21 May 2024 05:00:48 -0000	1.21
+++ extern.h	11 Jan 2025 10:50:51 -0000
@@ -212,7 +212,7 @@ int		 keyproc(int, const char *, const c
 			enum keytype);
 int		 netproc(int, int, int, int, int, int, int,
 			struct authority_c *, const char *const *,
-			size_t);
+			size_t, const char *);
 
 /*
  * Debugging functions.
@@ -263,7 +263,7 @@ char		*json_getstr(struct jsmnn *, const
 char		*json_fmt_newcert(const char *);
 char		*json_fmt_chkacc(void);
 char		*json_fmt_newacc(const char *);
-char		*json_fmt_neworder(const char *const *, size_t);
+char		*json_fmt_neworder(const char *const *, size_t, const char *);
 char		*json_fmt_protected_rsa(const char *,
 			const char *, const char *, const char *);
 char		*json_fmt_protected_ec(const char *, const char *, const char *,
Index: json.c
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/json.c,v
diff -u -p -r1.21 json.c
--- json.c	14 Sep 2020 16:00:17 -0000	1.21
+++ json.c	11 Jan 2025 10:50:51 -0000
@@ -647,7 +647,7 @@ json_fmt_newacc(const char *contact)
  * Format the "newOrder" resource request
  */
 char *
-json_fmt_neworder(const char *const *alts, size_t altsz)
+json_fmt_neworder(const char *const *alts, size_t altsz, const char *profile)
 {
 	size_t	 i;
 	int	 c;
@@ -669,7 +669,10 @@ json_fmt_neworder(const char *const *alt
 		}
 		t = p;
 	}
-	c = asprintf(&p, "%s ] }", t);
+	if (profile == NULL)
+		c = asprintf(&p, "%s ] }", t);
+	else
+		c = asprintf(&p, "%s ], \"profile\": \"%s\" }", t, profile);
 	free(t);
 	if (c == -1) {
 		warn("asprintf");
Index: main.c
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/main.c,v
diff -u -p -r1.56 main.c
--- main.c	19 Jun 2024 13:13:25 -0000	1.56
+++ main.c	11 Jan 2025 10:50:51 -0000
@@ -224,7 +224,8 @@ main(int argc, char *argv[])
 		    chng_fds[1], cert_fds[1],
 		    dns_fds[1], rvk_fds[1],
 		    revocate, authority,
-		    (const char *const *)alts, altsz);
+		    (const char *const *)alts, altsz,
+		    domain->profile);
 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
 	}
 
Index: netproc.c
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/netproc.c,v
diff -u -p -r1.37 netproc.c
--- netproc.c	10 Oct 2024 09:39:35 -0000	1.37
+++ netproc.c	11 Jan 2025 10:50:51 -0000
@@ -441,14 +441,14 @@ dochkacc(struct conn *c, const struct ca
  */
 static int
 doneworder(struct conn *c, const char *const *alts, size_t altsz,
-    struct order *order, const struct capaths *p)
+    struct order *order, const struct capaths *p, const char *profile)
 {
 	struct jsmnn	*j = NULL;
 	int		 rc = 0;
 	char		*req;
 	long		 lc;
 
-	if ((req = json_fmt_neworder(alts, altsz)) == NULL)
+	if ((req = json_fmt_neworder(alts, altsz, profile)) == NULL)
 		warnx("json_fmt_neworder");
 	else if ((lc = sreq(c, p->neworder, 1, req, &order->uri)) < 0)
 		warnx("%s: bad comm", p->neworder);
@@ -671,7 +671,7 @@ dodirs(struct conn *c, const char *addr,
 int
 netproc(int kfd, int afd, int Cfd, int cfd, int dfd, int rfd,
     int revocate, struct authority_c *authority,
-    const char *const *alts, size_t altsz)
+    const char *const *alts, size_t altsz, const char *profile)
 {
 	int		 rc = 0;
 	size_t		 i;
@@ -762,7 +762,7 @@ netproc(int kfd, int afd, int Cfd, int c
 	 * Following that, submit the request to the CA then notify the
 	 * certproc, which will in turn notify the fileproc.
 	 * XXX currently we can only sign with the account key, the RFC
-	 * also mentions signing with the privat key of the cert itself.
+	 * also mentions signing with the private key of the cert itself.
 	 */
 	if (revocate) {
 		if ((cert = readstr(rfd, COMM_CSR)) == NULL)
@@ -776,7 +776,7 @@ netproc(int kfd, int afd, int Cfd, int c
 
 	memset(&order, 0, sizeof(order));
 
-	if (!doneworder(&c, alts, altsz, &order, &paths))
+	if (!doneworder(&c, alts, altsz, &order, &paths, profile))
 		goto out;
 
 	chngs = calloc(order.authsz, sizeof(struct chng));
Index: parse.h
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/parse.h,v
diff -u -p -r1.15 parse.h
--- parse.h	14 Sep 2020 16:00:17 -0000	1.15
+++ parse.h	11 Jan 2025 10:50:51 -0000
@@ -54,6 +54,7 @@ struct domain_c {
 	char			*fullchain;
 	char			*auth;
 	char			*challengedir;
+	char			*profile;
 };
 
 struct altname_c {
Index: parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/parse.y,v
diff -u -p -r1.45 parse.y
--- parse.y	15 Dec 2022 08:06:13 -0000	1.45
+++ parse.y	11 Jan 2025 10:50:51 -0000
@@ -101,7 +101,8 @@ typedef struct {
 %}
 
 %token	AUTHORITY URL API ACCOUNT CONTACT
-%token	DOMAIN ALTERNATIVE NAME NAMES CERT FULL CHAIN KEY SIGN WITH CHALLENGEDIR
+%token	DOMAIN ALTERNATIVE NAME NAMES CERT FULL CHAIN KEY SIGN WITH
+%token	CHALLENGEDIR PROFILE
 %token	YES NO
 %token	INCLUDE
 %token	ERROR
@@ -393,6 +394,16 @@ domainoptsl	: ALTERNATIVE NAMES '{' optn
 				err(EXIT_FAILURE, "strdup");
 			domain->challengedir = s;
 		}
+		| PROFILE STRING {
+			char *s;
+			if (domain->profile != NULL) {
+				yyerror("duplicate profile");
+				YYERROR;
+			}
+			if ((s = strdup($2)) == NULL)
+				err(EXIT_FAILURE, "strdup");
+			domain->profile = s;
+		}
 		;
 
 altname_l	: altname optcommanl altname_l
@@ -470,6 +481,7 @@ lookup(char *s)
 		{"key",			KEY},
 		{"name",		NAME},
 		{"names",		NAMES},
+		{"profile",		PROFILE},
 		{"rsa",			RSA},
 		{"sign",		SIGN},
 		{"url",			URL},
@@ -1081,6 +1093,8 @@ print_config(struct acme_conf *xconf)
 		if (d->fullchain != NULL)
 			printf("\tdomain full chain certificate \"%s\"\n",
 			    d->fullchain);
+		if (d->profile != NULL)
+			printf("\tprofile \"%s\"\n", d->profile);
 		if (d->auth != NULL)
 			printf("\tsign with \"%s\"\n", d->auth);
 		if (d->challengedir != NULL)