From: Stuart Henderson Subject: Re: acme-client(1): add support for let's encrypt iPAddress certificates To: Lloyd Cc: Peter Hessler , "tech@openbsd.org" Date: Sun, 14 Dec 2025 14:04:09 +0000 On 2025/12/13 21:20, Lloyd wrote: > Peter Hessler wrote: > > > I was looking at this topic and noticed thet LetsEncrypt is planning on > > releasing this to production soon, so IMHO this is a good time to clean > > up the diffs and get it in. > > > > Can you update the patches and re-send? > > With holidays around the corner, I don't have time to update this right > now. The diff was built against an older branch and will not merge > cleanly against -current because different profile code was merged in > the meantime. So this will take some time to re-familiarize myself. That > said, I've been running this against preprod for a while with no issues. > > This feature is expected to be enabled in LE prod real soon now. > > Regards > Lloyd > Diff below merges this to -current. Works for me with a shortlived IP address cert on letsencrypt staging, with a standard cert on letsencrypt prod, and src/regress/usr.sbin/acme-client (using pebble) is still happy. I have fixed "new sentence new line" in the manpage changes but not made any substantial changes including any of the things I suggested; comments on those bits: : > Config files and the manual will be simpler if the parser is changed to : > allow using ip:XXX directly here, i.e. "domain ip:192.0.2.0", without : > needing to specify "domain name". Then we could also bring in the ip: : > text : > here, : : This was intentional. The handle should be a static identifier. I felt : that whether or not "ip:" gets stripped from the handle becomes : ambiguous. Which is the real handle - with or without "ip:"? Which do : you specify on the command line? The handle gets screened as a valid I think "with ip:". I suppose the config for a cert where you have both IP and domain names in the same cert won't look too bad, domain example.org { alternative names { ip:192.0.2.0 ip:2001:db8:0:1::1234 } domain key "/etc/ssl/private/server.key" domain chain certificate "/etc/ssl/server.crt" sign with letsencrypt-staging profile shortlived } but as things stand it's pretty ugly for an ip-only cert domain ip_only_cert { domain name ip:192.0.2.0 alternative names { ip:2001:db8:0:1::1234 } domain key "/etc/ssl/private/iponly.key" domain chain certificate "/etc/ssl/iponly.pem" sign with letsencrypt-staging profile tlsserver } : domain name so cannot contain colons - but that needs to be supported : for IPv6 but still prevent you from entering something invalid like : www:openbsd:org. I thought it would make the parser unnecessarily : complex. : > I don't think any current CAs supported by acme-client are requiring : > this? I'm thinking the only thing that might require CN is software : > using the cert which might (though shouldn't) get confused if it's not : > there. : : I left this as an on/off knob for legacy support. Maybe someone out : there prefers CN's in their certs and they are still supported for the : default long-lived profiles. AFAIK the CA will generate CN themselves rather than copying from the CSR. Certainly letsencrypt does (with the default profile). Since the CSR is not stored, I think the only reason for having a knob would be if a CA fails to issue a cert if there's no CN (letsencrypt staging, prod, and pebble work) so I think I'd prefer to drop the knob unless/until it's proven that we need it. Simplifies the docs too. Index: acme-client.conf.5 =================================================================== RCS file: /cvs/src/usr.sbin/acme-client/acme-client.conf.5,v diff -u -p -r1.32 acme-client.conf.5 --- acme-client.conf.5 16 Sep 2025 15:06:02 -0000 1.32 +++ acme-client.conf.5 14 Dec 2025 13:37:50 -0000 @@ -115,6 +115,10 @@ The certificates to be obtained through Each domain section begins with the .Ic domain 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: @@ -122,19 +126,25 @@ 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 @@ -194,6 +204,11 @@ exists. .It Ic profile Ar profile The certificate profile to be requested. If this setting is absent, no profile request is made. +.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 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.22 extern.h --- extern.h 16 Sep 2025 15:06:02 -0000 1.22 +++ extern.h 14 Dec 2025 13:37:50 -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, const char *); + 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, const char *); +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 diff -u -p -r1.22 json.c --- json.c 16 Sep 2025 15:06:02 -0000 1.22 +++ json.c 14 Dec 2025 13:37:50 -0000 @@ -647,20 +647,32 @@ json_fmt_newacc(const char *contact) * Format the "newOrder" resource request */ char * -json_fmt_neworder(const char *const *alts, size_t altsz, const char *profile) +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,10 +681,11 @@ json_fmt_neworder(const char *const *alt } t = p; } - if (profile == NULL) + if (domain->profile == NULL) c = asprintf(&p, "%s ] }", t); else - c = asprintf(&p, "%s ], \"profile\": \"%s\" }", t, profile); + 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 diff -u -p -r1.18 keyproc.c --- keyproc.c 28 Aug 2022 18:30:29 -0000 1.18 +++ keyproc.c 14 Dec 2025 13:37:50 -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 diff -u -p -r1.57 main.c --- main.c 16 Sep 2025 15:06:02 -0000 1.57 +++ main.c 14 Dec 2025 13:37:50 -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; @@ -112,7 +111,7 @@ main(int argc, char *argv[]) if ((tmpsd = dirname(tmps)) == NULL) err(EXIT_FAILURE, "dirname"); if ((certdir = strdup(tmpsd)) == NULL) - err(EXIT_FAILURE, "strdup"); + err(EXIT_FAILURE, "strdup"); free(tmps); tmps = tmpsd = 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,9 +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, - domain->profile); + revocate, authority, domain); exit(c ? EXIT_SUCCESS : EXIT_FAILURE); } @@ -250,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); } @@ -355,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 diff -u -p -r1.45 netproc.c --- netproc.c 16 Sep 2025 15:06:02 -0000 1.45 +++ netproc.c 14 Dec 2025 13:37:50 -0000 @@ -492,15 +492,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, const char *profile) +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, profile)) == 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); @@ -723,7 +723,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 *profile) + struct domain_c *domain) { int rc = 0, retries = 0; size_t i; @@ -828,7 +828,7 @@ netproc(int kfd, int afd, int Cfd, int c memset(&order, 0, sizeof(order)); - if (!doneworder(&c, alts, altsz, &order, &paths, profile)) + 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 diff -u -p -r1.17 parse.h --- parse.h 16 Sep 2025 15:06:02 -0000 1.17 +++ parse.h 14 Dec 2025 13:37:50 -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; @@ -46,7 +51,9 @@ struct domain_c { TAILQ_ENTRY(domain_c) entry; TAILQ_HEAD(, altname_c) altname_list; int altname_count; + int with_cn; enum keytype keytype; + enum identifiertype idtype; char *handle; char *domain; char *key; @@ -60,7 +67,8 @@ struct domain_c { struct altname_c { TAILQ_ENTRY(altname_c) entry; - char *domain; + char *domain; + enum identifiertype idtype; }; struct keyfile { @@ -87,5 +95,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 diff -u -p -r1.47 parse.y --- parse.y 16 Sep 2025 15:06:02 -0000 1.47 +++ parse.y 14 Dec 2025 13:37:50 -0000 @@ -102,8 +102,7 @@ typedef struct { %token AUTHORITY URL API ACCOUNT CONTACT %token DOMAIN ALTERNATIVE NAME NAMES CERT FULL CHAIN KEY SIGN WITH -%token CHALLENGEDIR PROFILE -%token YES NO +%token CHALLENGEDIR PROFILE CN %token INCLUDE %token ERROR %token RSA ECDSA @@ -221,7 +220,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"); @@ -299,8 +298,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 { @@ -408,6 +420,19 @@ domainoptsl : ALTERNATIVE NAMES '{' optn err(EXIT_FAILURE, "strdup"); domain->profile = 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 @@ -417,15 +442,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"); + 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); @@ -477,6 +513,7 @@ lookup(char *s) {"certificate", CERT}, {"chain", CHAIN}, {"challengedir", CHALLENGEDIR}, + {"cn", CN}, {"contact", CONTACT}, {"domain", DOMAIN}, {"ecdsa", ECDSA}, @@ -1079,12 +1116,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; } } @@ -1102,6 +1142,8 @@ print_config(struct acme_conf *xconf) 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) @@ -1123,6 +1165,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 diff -u -p -r1.26 revokeproc.c --- revokeproc.c 18 Sep 2025 13:22:36 -0000 1.26 +++ revokeproc.c 14 Dec 2025 13:37:50 -0000 @@ -74,19 +74,18 @@ X509notbefore(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, j, k; + int *found_altnames = NULL; FILE *f = NULL; X509 *x = NULL; long lval; enum revokeop op, rop; time_t notafter, notbefore, cert_validity; time_t remaining_validity, renew_allow; - size_t j; /* * First try to open the certificate before we drop privileges @@ -162,7 +161,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; } @@ -172,49 +172,78 @@ 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) + 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; + } + 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; - /* 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); + /* now we have a real C string */ + name_len = strlen(name_buf); - for (j = 0; j < altsz; j++) { - if ((size_t)name_len != strlen(alts[j])) - continue; - if (memcmp(name_buf, alts[j], name_len) == 0) + 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); @@ -224,11 +253,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; @@ -340,7 +378,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();