From: Lloyd Subject: Re: acme-client(1): add support for let's encrypt iPAddress certificates To: Lloyd Cc: Stuart Henderson , "tech@openbsd.org" Date: Thu, 18 Sep 2025 00:50:42 +0000 Fixed a bug with IPv6 parsing on revocation. As is, this should work with fully padded IPv6 addresses in the config; e.g. domain name ip:fe80:0000:0000:0000:0000:0000:0000:0001 The correct way to store this in memory should probably be in network byte order so users can specify compact v6 addresses and we don't have to worry about comparing strings. Either way, the current method of cramming all the SAN hostnames into alts[] is really ugly and needs to go. This gets you about 90% of the way there. Regards Lloyd Lloyd wrote: > The below diff adds full support for IP address certs to acme-client(1). > Index: acme-client.conf.5 =================================================================== RCS file: /cvs/src/usr.sbin/acme-client/acme-client.conf.5,v retrieving revision 1.29 diff -u -p -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 18 Sep 2025 00:28:40 -0000 @@ -110,7 +110,10 @@ The certificates to be obtained through .It Ic domain Ar handle Brq ... Each domain section begins with the .Ic domain -keyword followed by an identifier for this domain block. +keyword followed by an identifier for this domain block. For certificates with +primary SAN type iPAddress, an additional directive of type +.Cm domain name ip: +must follow. .El .Pp It is followed by a block of options enclosed in curly brackets: @@ -118,19 +121,23 @@ It is followed by a block of options enc .It Ic domain name Ar name The .Ar name -to be used as the common name component of the subject of the -X.509 certificate. -This is optional. +to be used as the primary Subject Alternative Name (or common name if included) +in the X.509 certificate. This is optional for SANs of type DNS. If not specified, the .Ar handle -of the domain block will be used as common name. +of the domain block will be used as the primary SAN (or common name). To +specify a SAN of type iPAddress as the +.Ar name , +prefix the address with +.Cm ip: . +To use a primary SAN of type iPAddress, this field is mandatory. .It Ic alternative names Brq ... A list of alternative names, comma or space separated, for which the certificate will be valid. -The common name is included automatically if this option is present, -but there is no automatic conversion/inclusion between "www." and -plain domain name forms. +The common name is no longer included by default. To specify a SAN of type +iPAddress as an alternative name, prefix the address with +.Cm ip: . .It Ic domain key Ar file Op Ar keytype The private key file for which the certificate will be obtained. .Ar keytype @@ -187,6 +194,13 @@ A backup with name is created if .Ar file exists. +.It Ic with cn +Include the X.509 Common Name attribute in the certificate +request. Use this if your CA's Certificate Policy requires it. +By default, the CN attribute is suppressed. +.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 retrieving revision 1.21 diff -u -p -u -p -r1.21 extern.h --- extern.h 21 May 2024 05:00:48 -0000 1.21 +++ extern.h 18 Sep 2025 00:28:40 -0000 @@ -204,15 +204,12 @@ int acctproc(int, const char *, enum k int certproc(int, int); int chngproc(int, const char *); int dnsproc(int); -int revokeproc(int, const char *, int, int, const char *const *, - size_t); +int revokeproc(int, const char *, int, int, struct domain_c *); int fileproc(int, const char *, const char *, const char *, const char *); -int keyproc(int, const char *, const char **, size_t, - enum keytype); +int keyproc(int, struct domain_c *); int netproc(int, int, int, int, int, int, int, - struct authority_c *, const char *const *, - size_t); + struct authority_c *, struct domain_c *); /* * Debugging functions. @@ -263,7 +260,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(struct domain_c *); 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 retrieving revision 1.21 diff -u -p -u -p -r1.21 json.c --- json.c 14 Sep 2020 16:00:17 -0000 1.21 +++ json.c 18 Sep 2025 00:28:40 -0000 @@ -647,20 +647,31 @@ 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(struct domain_c *domain) { - size_t i; - int c; - char *p, *t; + int c; + char *p, *t; + struct altname_c *ac, *first; if ((p = strdup("{ \"identifiers\": [")) == NULL) goto err; - t = p; - for (i = 0; i < altsz; i++) { - c = asprintf(&p, - "%s { \"type\": \"dns\", \"value\": \"%s\" }%s", - t, alts[i], i + 1 == altsz ? "" : ","); + first = TAILQ_FIRST(&domain->altname_list); + TAILQ_FOREACH(ac, &domain->altname_list, entry) { + switch (ac->idtype) { + case ID_DNS: + c = asprintf(&p, + "%s%s { \"type\": \"dns\", " + "\"value\": \"%s\" }", + t, ac == first ? "" : ",", ac->domain); + break; + case ID_IP: + c = asprintf(&p, + "%s%s { \"type\": \"ip\", " + "\"value\": \"%s\" }", + t, ac == first ? "" : ",", ac->domain); + break; + } free(t); if (c == -1) { warn("asprintf"); @@ -669,7 +680,11 @@ json_fmt_neworder(const char *const *alt } t = p; } - c = asprintf(&p, "%s ] }", t); + if (domain->profile == NULL) + c = asprintf(&p, "%s ] }", t); + else + c = asprintf(&p, "%s ], \"profile\": \"%s\" }", t, + domain->profile); free(t); if (c == -1) { warn("asprintf"); Index: keyproc.c =================================================================== RCS file: /cvs/src/usr.sbin/acme-client/keyproc.c,v retrieving revision 1.18 diff -u -p -u -p -r1.18 keyproc.c --- keyproc.c 28 Aug 2022 18:30:29 -0000 1.18 +++ keyproc.c 18 Sep 2025 00:28:40 -0000 @@ -74,13 +74,12 @@ add_ext(STACK_OF(X509_EXTENSION) *sk, in * jail and, on success, ship it to "netsock" as an X509 request. */ int -keyproc(int netsock, const char *keyfile, const char **alts, size_t altsz, - enum keytype keytype) +keyproc(int netsock, struct domain_c *domain) { char *der64 = NULL, *der = NULL, *dercp; char *sans = NULL, *san = NULL; FILE *f; - size_t i, sansz; + size_t sansz; void *pp; EVP_PKEY *pkey = NULL; X509_REQ *x = NULL; @@ -88,6 +87,8 @@ keyproc(int netsock, const char *keyfile int len, rc = 0, cc, nid, newkey = 0; mode_t prev; STACK_OF(X509_EXTENSION) *exts = NULL; + struct altname_c *ac, *first; + const char *keyfile = domain->key; /* * First, open our private key file read-only or write-only if @@ -117,7 +118,7 @@ keyproc(int netsock, const char *keyfile } if (newkey) { - switch (keytype) { + switch (domain->keytype) { case KT_ECDSA: if ((pkey = ec_key_create(f, keyfile)) == NULL) goto out; @@ -155,20 +156,21 @@ keyproc(int netsock, const char *keyfile goto out; } - /* Now specify the common name that we'll request. */ - - if ((name = X509_NAME_new()) == NULL) { - warnx("X509_NAME_new"); - goto out; - } else if (!X509_NAME_add_entry_by_txt(name, "CN", - MBSTRING_ASC, (u_char *)alts[0], -1, -1, 0)) { - warnx("X509_NAME_add_entry_by_txt: CN=%s", alts[0]); - goto out; - } else if (!X509_REQ_set_subject_name(x, name)) { - warnx("X509_req_set_issuer_name"); - goto out; + if (domain->with_cn) { + /* Now specify the common name that we'll request. */ + if ((name = X509_NAME_new()) == NULL) { + warnx("X509_NAME_new"); + goto out; + } else if (!X509_NAME_add_entry_by_txt(name, "CN", + MBSTRING_ASC, (u_char *)domain->domain, -1, -1, 0)) { + warnx("X509_NAME_add_entry_by_txt: CN=%s", + domain->domain); + goto out; + } else if (!X509_REQ_set_subject_name(x, name)) { + warnx("X509_req_set_issuer_name"); + goto out; + } } - /* * Now add the SAN extensions. * This was lifted more or less directly from demos/x509/mkreq.c @@ -195,9 +197,18 @@ keyproc(int netsock, const char *keyfile * domains: NOT an entry per domain! */ - for (i = 0; i < altsz; i++) { - cc = asprintf(&san, "%sDNS:%s", - i ? "," : "", alts[i]); + first = TAILQ_FIRST(&domain->altname_list); + TAILQ_FOREACH(ac, &domain->altname_list, entry) { + switch (ac->idtype) { + case ID_DNS: + cc = asprintf(&san, "%sDNS:%s", + ac == first ? "" : ",", ac->domain); + break; + case ID_IP: + cc = asprintf(&san, "%sIP:%s", + ac == first ? "" : ",", ac->domain); + break; + } if (cc == -1) { warn("asprintf"); goto out; Index: main.c =================================================================== RCS file: /cvs/src/usr.sbin/acme-client/main.c,v retrieving revision 1.56 diff -u -p -u -p -r1.56 main.c --- main.c 19 Jun 2024 13:13:25 -0000 1.56 +++ main.c 18 Sep 2025 00:28:40 -0000 @@ -40,7 +40,6 @@ enum comp proccomp; int main(int argc, char *argv[]) { - const char **alts = NULL; char *certdir = NULL; char *chngdir = NULL, *auth = NULL; char *conffile = CONF_FILE; @@ -51,7 +50,7 @@ main(int argc, char *argv[]) int c, rc, revocate = 0; int popts = 0; pid_t pids[COMP__MAX]; - size_t i, altsz, ne; + size_t ne; struct acme_conf *conf = NULL; struct authority_c *authority = NULL; @@ -174,15 +173,12 @@ main(int argc, char *argv[]) return EXIT_SUCCESS; /* Set the zeroth altname as our domain. */ - altsz = domain->altname_count + 1; - alts = calloc(altsz, sizeof(char *)); - if (alts == NULL) - err(EXIT_FAILURE, "calloc"); - alts[0] = domain->domain; - i = 1; - /* XXX get rid of alts[] later */ - TAILQ_FOREACH(ac, &domain->altname_list, entry) - alts[i++] = ac->domain; + + ac = calloc(1, sizeof(struct altname_c)); + ac->domain = domain->domain; + ac->idtype = domain->idtype; + TAILQ_INSERT_HEAD(&domain->altname_list, ac, entry); + domain->altname_count++; /* * Open channels between our components. @@ -223,8 +219,7 @@ main(int argc, char *argv[]) c = netproc(key_fds[1], acct_fds[1], chng_fds[1], cert_fds[1], dns_fds[1], rvk_fds[1], - revocate, authority, - (const char *const *)alts, altsz); + revocate, authority, domain); exit(c ? EXIT_SUCCESS : EXIT_FAILURE); } @@ -249,9 +244,7 @@ main(int argc, char *argv[]) close(chng_fds[0]); close(file_fds[0]); close(file_fds[1]); - c = keyproc(key_fds[0], domain->key, - (const char **)alts, altsz, - domain->keytype); + c = keyproc(key_fds[0], domain); exit(c ? EXIT_SUCCESS : EXIT_FAILURE); } @@ -354,8 +347,7 @@ main(int argc, char *argv[]) if (pids[COMP_REVOKE] == 0) { proccomp = COMP_REVOKE; c = revokeproc(rvk_fds[0], domain->cert != NULL ? domain->cert : - domain->fullchain, force, revocate, - (const char *const *)alts, altsz); + domain->fullchain, force, revocate, domain); exit(c ? EXIT_SUCCESS : EXIT_FAILURE); } Index: netproc.c =================================================================== RCS file: /cvs/src/usr.sbin/acme-client/netproc.c,v retrieving revision 1.37.4.1 diff -u -p -u -p -r1.37.4.1 netproc.c --- netproc.c 10 Jun 2025 18:21:49 -0000 1.37.4.1 +++ netproc.c 18 Sep 2025 00:28:40 -0000 @@ -440,15 +440,15 @@ dochkacc(struct conn *c, const struct ca * Submit a new order for a certificate. */ static int -doneworder(struct conn *c, const char *const *alts, size_t altsz, - struct order *order, const struct capaths *p) +doneworder(struct conn *c, struct domain_c *domain, struct order *order, + const struct capaths *p) { struct jsmnn *j = NULL; int rc = 0; char *req; long lc; - if ((req = json_fmt_neworder(alts, altsz)) == NULL) + if ((req = json_fmt_neworder(domain)) == 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) + struct domain_c *domain) { int rc = 0, retries = 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, domain, &order, &paths)) goto out; chngs = calloc(order.authsz, sizeof(struct chng)); Index: parse.h =================================================================== RCS file: /cvs/src/usr.sbin/acme-client/parse.h,v retrieving revision 1.15 diff -u -p -u -p -r1.15 parse.h --- parse.h 14 Sep 2020 16:00:17 -0000 1.15 +++ parse.h 18 Sep 2025 00:28:40 -0000 @@ -32,6 +32,11 @@ enum keytype { KT_ECDSA }; +enum identifiertype { + ID_DNS = 0, /* RFC 8555 */ + ID_IP /* RFC 8738 */ +}; + struct authority_c { TAILQ_ENTRY(authority_c) entry; char *name; @@ -54,11 +59,15 @@ struct domain_c { char *fullchain; char *auth; char *challengedir; + char *profile; + enum identifiertype idtype; + int with_cn; }; struct altname_c { TAILQ_ENTRY(altname_c) entry; char *domain; + enum identifiertype idtype; }; struct keyfile { @@ -85,5 +94,6 @@ struct authority_c *authority_find0(stru struct domain_c *domain_find_handle(struct acme_conf *, char *); int domain_valid(const char *); +int altname_valid(const char *); #endif /* PARSE_H */ Index: parse.y =================================================================== RCS file: /cvs/src/usr.sbin/acme-client/parse.y,v retrieving revision 1.45 diff -u -p -u -p -r1.45 parse.y --- parse.y 15 Dec 2022 08:06:13 -0000 1.45 +++ parse.y 18 Sep 2025 00:28:41 -0000 @@ -101,8 +101,8 @@ typedef struct { %} %token AUTHORITY URL API ACCOUNT CONTACT -%token DOMAIN ALTERNATIVE NAME NAMES CERT FULL CHAIN KEY SIGN WITH CHALLENGEDIR -%token YES NO +%token DOMAIN ALTERNATIVE NAME NAMES CERT FULL CHAIN KEY SIGN WITH +%token CHALLENGEDIR PROFILE CN %token INCLUDE %token ERROR %token RSA ECDSA @@ -219,7 +219,7 @@ authorityoptsl : API URL STRING { err(EXIT_FAILURE, "strdup"); auth->api = s; } - | ACCOUNT KEY STRING keytype{ + | ACCOUNT KEY STRING keytype { char *s; if (auth->account != NULL) { yyerror("duplicate account"); @@ -294,8 +294,21 @@ domainoptsl : ALTERNATIVE NAMES '{' optn yyerror("duplicate domain name"); YYERROR; } - if ((s = strdup($3)) == NULL) - err(EXIT_FAILURE, "strdup"); + if (!altname_valid($3)) { + yyerror("bad domain name syntax"); + YYERROR; + } + if ((strncmp($3, "ip:", 3) == 0) && + (strlen($3) > 3)) { + domain->idtype = ID_IP; + if ((s = strdup(($3) + 3)) == NULL) + err(EXIT_FAILURE, "strdup"); + } + else { + domain->idtype = ID_DNS; + if ((s = strdup($3)) == NULL) + err(EXIT_FAILURE, "strdup"); + } domain->domain = s; } | DOMAIN KEY STRING keytype { @@ -393,6 +406,19 @@ 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; + } + | WITH CN { + domain->with_cn = 1; + } ; altname_l : altname optcommanl altname_l @@ -402,15 +428,26 @@ altname_l : altname optcommanl altname_l altname : STRING { char *s; struct altname_c *ac; - if (!domain_valid($1)) { - yyerror("bad domain syntax"); + if (!altname_valid($1)) { + yyerror("bad altname syntax"); YYERROR; } if ((ac = calloc(1, sizeof(struct altname_c))) == NULL) - err(EXIT_FAILURE, "calloc"); - if ((s = strdup($1)) == NULL) { - free(ac); - err(EXIT_FAILURE, "strdup"); + err(EXIT_FAILURE, "calloc"); + if ((strncmp($1, "ip:", 3) == 0) && + (strlen($1) > 3)) { + ac->idtype = ID_IP; + if ((s = strdup(($1) + 3)) == NULL) { + free(ac); + err(EXIT_FAILURE, "strdup"); + } + } + else { + ac->idtype = ID_DNS; + if ((s = strdup($1)) == NULL) { + free(ac); + err(EXIT_FAILURE, "strdup"); + } } ac->domain = s; TAILQ_INSERT_TAIL(&domain->altname_list, ac, entry); @@ -462,6 +499,7 @@ lookup(char *s) {"certificate", CERT}, {"chain", CHAIN}, {"challengedir", CHALLENGEDIR}, + {"cn", CN}, {"contact", CONTACT}, {"domain", DOMAIN}, {"ecdsa", ECDSA}, @@ -470,6 +508,7 @@ lookup(char *s) {"key", KEY}, {"name", NAME}, {"names", NAMES}, + {"profile", PROFILE}, {"rsa", RSA}, {"sign", SIGN}, {"url", URL}, @@ -1060,12 +1099,15 @@ print_config(struct acme_conf *xconf) f = 0; printf("domain %s {\n", d->handle); if (d->domain != NULL) - printf("\tdomain name \"%s\"\n", d->domain); + printf("\tdomain name \"%s%s\"\n", + (d->idtype == ID_IP) ? "ip:" : "", d->domain); TAILQ_FOREACH(ac, &d->altname_list, entry) { if (!f) printf("\talternative names {"); if (ac->domain != NULL) { - printf("%s%s", f ? ", " : " ", ac->domain); + printf("%s%s%s", f ? ", " : " ", + (d->idtype == ID_IP) ? "ip:" : "", + ac->domain); f = 1; } } @@ -1081,6 +1123,10 @@ 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->with_cn != 0) + printf("\twith cn\n"); if (d->auth != NULL) printf("\tsign with \"%s\"\n", d->auth); if (d->challengedir != NULL) @@ -1102,6 +1148,17 @@ domain_valid(const char *cp) for ( ; *cp != '\0'; cp++) if (!(*cp == '.' || *cp == '-' || *cp == '_' || isalnum((unsigned char)*cp))) + return 0; + return 1; +} + +int +altname_valid(const char *cp) +{ + + for ( ; *cp != '\0'; cp++) + if (!(*cp == '.' || *cp == '-' || + *cp == ':' || isalnum((unsigned char)*cp))) return 0; return 1; } Index: revokeproc.c =================================================================== RCS file: /cvs/src/usr.sbin/acme-client/revokeproc.c,v retrieving revision 1.25 diff -u -p -u -p -r1.25 revokeproc.c --- revokeproc.c 18 Dec 2022 12:04:55 -0000 1.25 +++ revokeproc.c 18 Sep 2025 00:28:41 -0000 @@ -60,18 +60,18 @@ X509expires(X509 *x) int revokeproc(int fd, const char *certfile, int force, - int revocate, const char *const *alts, size_t altsz) + int revocate, struct domain_c *domain) { GENERAL_NAMES *sans = NULL; char *der = NULL, *dercp, *der64 = NULL; - int rc = 0, cc, i, len; - size_t *found = NULL; + int rc = 0, cc, sanidx, len; + int *found_altnames = NULL; FILE *f = NULL; X509 *x = NULL; long lval; enum revokeop op, rop; time_t t; - size_t j; + int j, k; /* * First try to open the certificate before we drop privileges @@ -142,7 +142,8 @@ revokeproc(int fd, const char *certfile, /* An array of buckets: the number of entries found. */ - if ((found = calloc(altsz, sizeof(size_t))) == NULL) { + if ((found_altnames = (int *)calloc(domain->altname_count, + sizeof(int))) == NULL) { warn("calloc"); goto out; } @@ -152,49 +153,79 @@ revokeproc(int fd, const char *certfile, * configuration file and that all domains are represented only once. */ - for (i = 0; i < sk_GENERAL_NAME_num(sans); i++) { + for (sanidx = 0; sanidx < sk_GENERAL_NAME_num(sans); sanidx++) { GENERAL_NAME *gen_name; - const ASN1_IA5STRING *name; - const unsigned char *name_buf; + unsigned char *name_buf; + unsigned char *p; int name_len; - int name_type; + struct altname_c *ac; + int i; + - gen_name = sk_GENERAL_NAME_value(sans, i); + gen_name = sk_GENERAL_NAME_value(sans, sanidx); assert(gen_name != NULL); - - name = GENERAL_NAME_get0_value(gen_name, &name_type); - if (name_type != GEN_DNS) - continue; - - /* name_buf isn't a C string and could contain embedded NULs. */ - name_buf = ASN1_STRING_get0_data(name); - name_len = ASN1_STRING_length(name); - - for (j = 0; j < altsz; j++) { - if ((size_t)name_len != strlen(alts[j])) + + if (gen_name->type == GEN_IPADD) { + p = gen_name->d.iPAddress->data; + name_len = gen_name->d.iPAddress->length; + + if (name_len == 4) + asprintf((char **)&name_buf, "%d.%d.%d.%d", + p[0], p[1], p[2], p[3]); + else if (name_len == 16) { + name_buf = strdup(""); + for (i = 0; i < 8; i++) { + asprintf((char **)&name_buf, "%s%s%04x", + name_buf, i == 0 ? "" : ":", + p[0] << 8 | p[1]); + p += 2; + } + } + else continue; - if (memcmp(name_buf, alts[j], name_len) == 0) + } + else if (gen_name->type == GEN_DNS) { + name_len = gen_name->d.dNSName->length; + asprintf((char **)&name_buf, "%.*s", + name_len, gen_name->d.dNSName->data); + } + else + continue; + + /* now we have a real C string */ + name_len = strlen(name_buf); + + j = 0; + TAILQ_FOREACH(ac, &domain->altname_list, entry) { + if (memcmp(name_buf, ac->domain, name_len) == 0) { + found_altnames[j]++; break; + } + /* increment if didn't match */ + j++; } - if (j == altsz) { + if (j >= domain->altname_count) { + /* we haven't matched any */ if (revocate) { char *visbuf; visbuf = calloc(4, name_len + 1); if (visbuf == NULL) { - warn("%s: unexpected SAN", certfile); + warn("%s: unexpected SAN in " + "certificate", certfile); goto out; } strvisx(visbuf, name_buf, name_len, VIS_SAFE); - warnx("%s: unexpected SAN entry: %s", - certfile, visbuf); + warnx("%s: unexpected SAN entry in " + "certificate: %s", certfile, visbuf); free(visbuf); goto out; } force = 2; continue; } - if (found[j]++) { + /* should not reach here if j is out of bounds */ + if (found_altnames[j] > 1) { if (revocate) { warnx("%s: duplicate SAN entry: %.*s", certfile, name_len, name_buf); @@ -204,11 +235,20 @@ revokeproc(int fd, const char *certfile, } } - for (j = 0; j < altsz; j++) { - if (found[j]) + for (j = 0; j < domain->altname_count; j++) { + struct altname_c *ac; + + if (found_altnames[j]) continue; if (revocate) { - warnx("%s: domain not listed: %s", certfile, alts[j]); + k = 0; + TAILQ_FOREACH(ac, &domain->altname_list, entry) { + if (j == k) + break; + k++; + } + warnx("%s: domain not listed: %s", certfile, + ac->domain); goto out; } force = 2; @@ -299,7 +339,7 @@ out: X509_free(x); GENERAL_NAMES_free(sans); free(der); - free(found); + free(found_altnames); free(der64); ERR_print_errors_fp(stderr); ERR_free_strings();