Index | Thread | Search

From:
Stuart Henderson <stu@spacehopper.org>
Subject:
Re: acme-client(1): add support for let's encrypt iPAddress certificates
To:
Lloyd <ng2d68@proton.me>, Peter Hessler <phessler@theapt.org>, "tech@openbsd.org" <tech@openbsd.org>
Date:
Wed, 17 Dec 2025 12:40:08 +0000

Download raw body.

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

this is now live in prod on letsencrypt, but beware if testing, there
is a bug.

if you list IPv6 addresses, it hits "domain list changed, forcing
renewal" on every renewal.

this is because, when setting up found_altnames, it's doing a memcmp()
between the expanded v6 address and the compressed one, i.e.

memcmp("xxxx:xxxx:0001:0101:0000:0000:0000:0002", "xxxx:xxxx:1:101::2", 39

updated diff below uses inet_ntop, rather than hand-rolled functions,
to generate strings from the addresses in an existing cert.
I also dropped the "with cn" option and setting Subject in the CSR.

(I think v6 addresses probably ought to be normalised to the inet_ntop
form when read from the config file too - I haven't done that in this
diff. Looks like letsencrypt won't issue a cert if you try to use the
expanded form anyway).

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	17 Dec 2025 12:39:56 -0000
@@ -115,6 +115,12 @@ The certificates to be obtained through 
 Each domain section begins with the
 .Ic domain
 keyword followed by an identifier for this domain block.
+If requesting a certificate with only an IP address,
+or if requesting two certificates of different types (EC and RSA)
+for the same name,
+an additional
+.Cm domain name
+directive must follow.
 .El
 .Pp
 It is followed by a block of options enclosed in curly brackets:
@@ -122,19 +128,20 @@ 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
+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.
+To use a SAN of type iPAddress, specify an IP address prefixed with
+.Cm ip: .
 .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.
+To use a SAN of type iPAddress, specify an IP address prefixed 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
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	17 Dec 2025 12:39:56 -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	17 Dec 2025 12:39:56 -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	17 Dec 2025 12:39:56 -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,6 @@ 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;
-	}
-
 	/*
 	 * Now add the SAN extensions.
 	 * This was lifted more or less directly from demos/x509/mkreq.c
@@ -195,9 +182,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	17 Dec 2025 12:39:56 -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	17 Dec 2025 12:39:56 -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	17 Dec 2025 12:39:56 -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;
@@ -47,6 +52,7 @@ struct domain_c {
 	TAILQ_HEAD(, altname_c)	 altname_list;
 	int			 altname_count;
 	enum keytype		 keytype;
+	enum identifiertype	 idtype;
 	char			*handle;
 	char			*domain;
 	char			*key;
@@ -60,7 +66,8 @@ struct domain_c {
 
 struct altname_c {
 	TAILQ_ENTRY(altname_c)	 entry;
-	char		       	*domain;
+	char			*domain;
+	enum identifiertype	 idtype;
 };
 
 struct keyfile {
@@ -87,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
diff -u -p -r1.47 parse.y
--- parse.y	16 Sep 2025 15:06:02 -0000	1.47
+++ parse.y	17 Dec 2025 12:39:56 -0000
@@ -103,7 +103,6 @@ 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	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,16 @@ 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;
+		}
 		;
 
 altname_l	: altname optcommanl altname_l
@@ -417,15 +439,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);
@@ -1079,12 +1112,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;
 			}
 		}
@@ -1123,6 +1159,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	17 Dec 2025 12:39:56 -0000
@@ -15,6 +15,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <sys/socket.h>
+#include <arpa/inet.h>
 #include <assert.h>
 #include <ctype.h>
 #include <err.h>
@@ -74,19 +76,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 +163,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 +174,80 @@ 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;
 		int			 name_len;
-		int			 name_type;
+		struct altname_c	*ac;
 
-		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) {
+			char	ip[INET6_ADDRSTRLEN];
+			name_len = gen_name->d.iPAddress->length;
+			if (name_len == 4)
+				asprintf((char **)&name_buf, "%s",
+				    inet_ntop(AF_INET,
+					gen_name->d.iPAddress->data,
+					ip, INET6_ADDRSTRLEN));
+			else if (name_len == 16)
+				asprintf((char **)&name_buf, "%s",
+				    inet_ntop(AF_INET6,
+					gen_name->d.iPAddress->data,
+					ip, INET6_ADDRSTRLEN));
+			else {
+				warnx("invalid name_len %d", name_len);
+				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) {
+			int xx;
+			xx = memcmp(name_buf, ac->domain, name_len);
+			warnx("<< memcmp(%s, %s, %d) = %d >>", name_buf, ac->domain, name_len, xx);
+			if (xx == 0) {
+				found_altnames[j]++;
 				break;
+			}
+			/* increment if didn't match */
+			j++;
 		}
-		if (j == altsz) {
+		if (j >= domain->altname_count) {
+			warnx("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) {
+			warnx("<< dup san entry %.*s >>", name_len, name_buf);
 			if (revocate) {
 				warnx("%s: duplicate SAN entry: %.*s",
 				    certfile, name_len, name_buf);
@@ -224,13 +257,23 @@ 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;
 		}
+		warnx("<< altname not found, force=2 >>");
 		force = 2;
 	}
 
@@ -340,7 +383,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();