From: Stuart Henderson Subject: draft-aaron-acme-profiles support for acme-client To: tech Date: Sat, 11 Jan 2025 11:12:46 +0000 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)