Index | Thread | Search

From:
Florian Obser <florian@openbsd.org>
Subject:
Re: [stu@spacehopper.org: Re: acme-client(1): add support for let's encrypt iPAddress certificates]
To:
tech@openbsd.org
Date:
Sat, 21 Feb 2026 19:02:36 +0100

Download raw body.

Thread
On 2026-02-21 14:40 +01, Florian Obser <florian@openbsd.org> wrote:
> On 2026-02-20 14:20 UTC, Stuart Henderson <stu@spacehopper.org> wrote:
>> just updated the machine where I'm running acme-client, which reminded
>> me that I still have this diff.. any interest?
>
> I'm reviewing the diff and trying it out.
>

This is on top of the CN removal diff.

I tweaked a bunch of things:

+ keyproc.c & json.c: KNF;  made "first" less awkward
+ revokeproc.c: asprintf & inet_ntop can fail, add vis, prevent asprintf
  leak
+ main.c: calloc error check
+ parse.y: getaddrinfo(3) with AI_NUMERICHOST is the canonical way to
  figure out if a thing is an ip address; fixed duplicate "DOMAIN
  PROFILE" (bad merge)

With the parse.y change we no longer need to prefix IP addresses with
IP:, which I found to be a nuisance.

I've also added a regress test.

This one's OK florian

diff --git regress/usr.sbin/acme-client/Makefile regress/usr.sbin/acme-client/Makefile
index caa05c3f804..aedbdf9c376 100644
--- regress/usr.sbin/acme-client/Makefile
+++ regress/usr.sbin/acme-client/Makefile
@@ -40,6 +40,12 @@ etc/acme-client-profile.conf: acme-client-profile.conf
 		${.CURDIR}/acme-client-profile.conf \
 		> etc/acme-client-profile.conf
 
+etc/acme-client-ipaddress.conf: acme-client-ipaddress.conf
+	mkdir -p etc
+	sed 's,$${.OBJDIR},${.OBJDIR},'\
+		${.CURDIR}/acme-client-ipaddress.conf \
+		> etc/acme-client-ipaddress.conf
+
 etc/httpd.conf: httpd.conf
 	mkdir -p etc
 	sed 's,$${.OBJDIR},${.OBJDIR},'\
@@ -85,6 +91,24 @@ run-regress-acme-profile: etc/acme-client-profile.conf
 	    -f ${.OBJDIR}/etc/acme-client-profile.conf \
 	    -r -v localhost
 
+REGRESS_TARGETS +=	run-regress-acme-ipaddress
+run-regress-acme-ipaddress: etc/acme-client-ipaddress.conf
+	${SUDO} /usr/sbin/acme-client \
+	    -f ${.OBJDIR}/etc/acme-client-ipaddress.conf \
+	    -v localhost
+	${SUDO} /usr/sbin/acme-client \
+	    -f ${.OBJDIR}/etc/acme-client-ipaddress.conf \
+	    -r -v localhost
+
+REGRESS_TARGETS +=	run-regress-acme-ipaddress2
+run-regress-acme-ipaddress2: etc/acme-client-ipaddress.conf
+	${SUDO} /usr/sbin/acme-client \
+	    -f ${.OBJDIR}/etc/acme-client-ipaddress.conf \
+	    -v ::1
+	${SUDO} /usr/sbin/acme-client \
+	    -f ${.OBJDIR}/etc/acme-client-ipaddress.conf \
+	    -r -v ::1
+
 REGRESS_TARGETS +=	run-regress-cleanup
 run-regress-cleanup:
 	${.MAKE} -C ${.CURDIR} httpd-stop
diff --git regress/usr.sbin/acme-client/acme-client-ipaddress.conf regress/usr.sbin/acme-client/acme-client-ipaddress.conf
new file mode 100644
index 00000000000..15cea54a7a4
--- /dev/null
+++ regress/usr.sbin/acme-client/acme-client-ipaddress.conf
@@ -0,0 +1,22 @@
+authority pebble {
+	account key "${.OBJDIR}/etc/acme/privkey.pem"
+	api url https://127.0.0.1:14000/dir
+	insecure
+}
+domain localhost {
+	domain key "${.OBJDIR}/etc/ssl/acme/private/privkey.pem"
+	domain certificate "${.OBJDIR}/etc/ssl/acme/cert.pem"
+	domain name 127.0.0.1
+	alternative names {::1, "localhost"}
+	profile "shortlived"
+	sign with "pebble"
+	challengedir "${.OBJDIR}/www/acme"
+}
+
+domain ::1 {
+	domain key "${.OBJDIR}/etc/ssl/acme/private/privkey2.pem"
+	domain certificate "${.OBJDIR}/etc/ssl/acme/cert2.pem"
+	profile "shortlived"
+	sign with "pebble"
+	challengedir "${.OBJDIR}/www/acme"
+}

diff --git usr.sbin/acme-client/extern.h usr.sbin/acme-client/extern.h
index 647ff0d332f..01b03495c6a 100644
--- usr.sbin/acme-client/extern.h
+++ usr.sbin/acme-client/extern.h
@@ -204,15 +204,12 @@ int		 acctproc(int, const char *, enum keytype);
 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 *);
 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 *,
diff --git usr.sbin/acme-client/json.c usr.sbin/acme-client/json.c
index 9b2a46b05fe..aaf8c55e467 100644
--- usr.sbin/acme-client/json.c
+++ usr.sbin/acme-client/json.c
@@ -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, const char *profile)
+json_fmt_neworder(struct domain_c *domain)
 {
-	size_t	 i;
-	int	 c;
-	char	*p, *t;
+	int			 c, first;
+	char			*p, *t;
+	struct altname_c	*ac;
 
 	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 = 1;
+	TAILQ_FOREACH(ac, &domain->altname_list, entry) {
+		switch (ac->idtype) {
+		case ID_DNS:
+			c = asprintf(&p, "%s%s { \"type\": \"dns\", "
+			    "\"value\": \"%s\" }", t, first ? "" : ",",
+			    ac->domain);
+			break;
+		case ID_IP:
+			c = asprintf(&p, "%s%s { \"type\": \"ip\", "
+			    "\"value\": \"%s\" }", t, first ? "" : ",",
+			    ac->domain);
+			break;
+		}
+		first = 0;
 		free(t);
 		if (c == -1) {
 			warn("asprintf");
@@ -669,10 +680,11 @@ json_fmt_neworder(const char *const *alts, size_t altsz, const char *profile)
 		}
 		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");
diff --git usr.sbin/acme-client/keyproc.c usr.sbin/acme-client/keyproc.c
index 977bb3224ec..f640b03a1b6 100644
--- usr.sbin/acme-client/keyproc.c
+++ usr.sbin/acme-client/keyproc.c
@@ -74,19 +74,20 @@ add_ext(STACK_OF(X509_EXTENSION) *sk, int nid, const char *value)
  * 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;
-	int		 len, rc = 0, cc, nid, newkey = 0;
+	int		 len, rc = 0, cc, nid, newkey = 0, first;
 	mode_t		 prev;
 	STACK_OF(X509_EXTENSION) *exts = NULL;
+	struct altname_c	 *ac;
+	const char	*keyfile = domain->key;
 
 	/*
 	 * First, open our private key file read-only or write-only if
@@ -116,7 +117,7 @@ keyproc(int netsock, const char *keyfile, const char **alts, size_t altsz,
 	}
 
 	if (newkey) {
-		switch (keytype) {
+		switch (domain->keytype) {
 		case KT_ECDSA:
 			if ((pkey = ec_key_create(f, keyfile)) == NULL)
 				goto out;
@@ -180,9 +181,19 @@ keyproc(int netsock, const char *keyfile, const char **alts, size_t altsz,
 	 * domains: NOT an entry per domain!
 	 */
 
-	for (i = 0; i < altsz; i++) {
-		cc = asprintf(&san, "%sDNS:%s",
-		    i ? "," : "", alts[i]);
+	first = 1;
+	TAILQ_FOREACH(ac, &domain->altname_list, entry) {
+		switch (ac->idtype) {
+		case ID_DNS:
+			cc = asprintf(&san, "%sDNS:%s", first ? "" : ",",
+			    ac->domain);
+			break;
+		case ID_IP:
+			cc = asprintf(&san, "%sIP:%s", first ? "" : ",",
+			    ac->domain);
+			break;
+		}
+		first = 0;
 		if (cc == -1) {
 			warn("asprintf");
 			goto out;
diff --git usr.sbin/acme-client/main.c usr.sbin/acme-client/main.c
index 2478adb2103..ae92e475fda 100644
--- usr.sbin/acme-client/main.c
+++ usr.sbin/acme-client/main.c
@@ -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,15 @@ 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)
+
+	ac = calloc(1, sizeof(struct altname_c));
+	if (ac == 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->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 +222,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 +247,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 +350,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);
 	}
 
diff --git usr.sbin/acme-client/netproc.c usr.sbin/acme-client/netproc.c
index 02fc0af6cb8..69e355838ce 100644
--- usr.sbin/acme-client/netproc.c
+++ usr.sbin/acme-client/netproc.c
@@ -492,15 +492,15 @@ dochkacc(struct conn *c, const struct capaths *p, const char *contact)
  * 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, struct capaths *paths)
 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 cfd, int dfd, int rfd,
 
 	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));
diff --git usr.sbin/acme-client/parse.h usr.sbin/acme-client/parse.h
index b30b7e9d43a..120c727b9f4 100644
--- usr.sbin/acme-client/parse.h
+++ usr.sbin/acme-client/parse.h
@@ -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(struct acme_conf *);
 struct domain_c		*domain_find_handle(struct acme_conf *, char *);
 
 int			 domain_valid(const char *);
+const char		*ip_valid(const char *);
 
 #endif /* PARSE_H */
diff --git usr.sbin/acme-client/parse.y usr.sbin/acme-client/parse.y
index 561879f7931..8bed7ee00bf 100644
--- usr.sbin/acme-client/parse.y
+++ usr.sbin/acme-client/parse.y
@@ -25,12 +25,15 @@
 
 %{
 #include <sys/types.h>
+#include <sys/socket.h>
 #include <sys/queue.h>
 #include <sys/stat.h>
+#include <arpa/inet.h>
 #include <ctype.h>
 #include <err.h>
 #include <errno.h>
 #include <limits.h>
+#include <netdb.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -103,7 +106,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 +223,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");
@@ -251,7 +253,7 @@ domain		: DOMAIN STRING {
 			char *s;
 			if ((s = strdup($2)) == NULL)
 				err(EXIT_FAILURE, "strdup");
-			if (!domain_valid(s)) {
+			if (!ip_valid(s) && !domain_valid(s)) {
 				yyerror("%s: bad domain syntax", s);
 				free(s);
 				YYERROR;
@@ -262,10 +264,21 @@ domain		: DOMAIN STRING {
 				YYERROR;
 			}
 		} '{' optnl domainopts_l '}' {
+			char		*s;
+			const char	*ip;
+
 			if (domain->domain == NULL) {
-				if ((domain->domain = strdup(domain->handle))
-				    == NULL)
-					err(EXIT_FAILURE, "strdup");
+				if ((ip = ip_valid(domain->handle)) != NULL) {
+					domain->idtype = ID_IP;
+					if ((s = strdup(ip)) == NULL)
+						err(EXIT_FAILURE, "strdup");
+				} else {
+					if ((s = strdup(domain->handle)) ==
+					    NULL)
+						err(EXIT_FAILURE, "strdup");
+					domain->idtype = ID_DNS;
+				}
+				domain->domain = s;
 			}
 			/* enforce minimum config here */
 			if (domain->key == NULL) {
@@ -294,13 +307,27 @@ domainopts_l	: domainopts_l domainoptsl nl
 
 domainoptsl	: ALTERNATIVE NAMES '{' optnl altname_l '}'
 		| DOMAIN NAME STRING {
-			char *s;
+			char		*s;
+			const char	*ip;
+
 			if (domain->domain != NULL) {
 				yyerror("duplicate domain name");
 				YYERROR;
 			}
-			if ((s = strdup($3)) == NULL)
-				err(EXIT_FAILURE, "strdup");
+
+			if ((ip = ip_valid($3)) != NULL) {
+				domain->idtype = ID_IP;
+				if ((s = strdup(ip)) == NULL)
+					err(EXIT_FAILURE, "strdup");
+			} else {
+				if (!domain_valid($3)) {
+					yyerror("bad domain name syntax");
+					YYERROR;
+				}
+				domain->idtype = ID_DNS;
+				if ((s = strdup($3)) == NULL)
+					err(EXIT_FAILURE, "strdup");
+			}
 			domain->domain = s;
 		}
 		| DOMAIN KEY STRING keytype {
@@ -416,17 +443,26 @@ altname_l	: altname optcommanl altname_l
 
 altname		: STRING {
 			char			*s;
+			const char		*ip;
 			struct altname_c	*ac;
-			if (!domain_valid($1)) {
-				yyerror("bad domain 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 ((ip = ip_valid($1)) != NULL) {
+				ac->idtype = ID_IP;
+				if ((s = strdup(ip)) == NULL)
+					err(EXIT_FAILURE, "strdup");
+			} else {
+				if (!domain_valid($1)) {
+					yyerror("bad domain name syntax");
+					YYERROR;
+				}
+				ac->idtype = ID_DNS;
+				if ((s = strdup($1)) == NULL)
+					err(EXIT_FAILURE, "strdup");
 			}
+
 			ac->domain = s;
 			TAILQ_INSERT_TAIL(&domain->altname_list, ac, entry);
 			domain->altname_count++;
@@ -1127,6 +1163,31 @@ domain_valid(const char *cp)
 	return 1;
 }
 
+const char *
+ip_valid(const char *ip)
+{
+	static char	 ip_buf[NI_MAXHOST];
+	struct addrinfo	 hints, *res;
+	int		 error;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_flags = AI_NUMERICHOST;
+
+	error = getaddrinfo(ip, NULL, &hints, &res);
+	if (error)
+		return NULL;
+
+	error = getnameinfo(res->ai_addr, res->ai_addrlen, ip_buf,
+	    sizeof(ip_buf), NULL, 0, NI_NUMERICHOST);
+
+	if (error)
+		return NULL;
+
+	return ip_buf;
+}
+
+
 int
 conf_check_file(char *s)
 {
diff --git usr.sbin/acme-client/revokeproc.c usr.sbin/acme-client/revokeproc.c
index 65170078226..84a923baa3e 100644
--- usr.sbin/acme-client/revokeproc.c
+++ usr.sbin/acme-client/revokeproc.c
@@ -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, int force,
 
 	/* 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,63 +174,120 @@ revokeproc(int fd, const char *certfile, int force,
 	 * 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;
+		char			*name_buf = NULL;
 		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_buf[INET6_ADDRSTRLEN];
+			const char	*ip;
+
+			name_len = gen_name->d.iPAddress->length;
+			switch (name_len) {
+			case 4:
+				ip = inet_ntop(AF_INET,
+				    gen_name->d.iPAddress->data,
+				    ip_buf, INET6_ADDRSTRLEN);
+				break;
+			case 16:
+				ip = inet_ntop(AF_INET6,
+				    gen_name->d.iPAddress->data,
+				    ip_buf, INET6_ADDRSTRLEN);
+				break;
+			default:
+				ip = NULL;
+				break;
+			}
+			if (ip == NULL) {
+				warnx("invalid IP address");
+				continue;
+			}
+			name_len = asprintf(&name_buf, "%s", ip);
+		} else if (gen_name->type == GEN_DNS) {
+			name_len = gen_name->d.dNSName->length;
+			name_len = asprintf(&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);
+		if (name_len == -1) {
+			warn("asprintf");
+			continue;
+		}
 
-		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 (strcmp(name_buf, ac->domain) == 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);
+					free(name_buf);
 					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);
+				free(name_buf);
 				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);
+				char *visbuf;
+				visbuf = calloc(4, name_len + 1);
+				if (visbuf == NULL) {
+					warn("%s: duplicate SAN in "
+					    "certificate", certfile);
+					free(name_buf);
+					goto out;
+				}
+				warnx("%s: duplicate SAN entry in "
+				    "certificate: %s", certfile, visbuf);
+				free(name_buf);
+				free(visbuf);
 				goto out;
 			}
 			force = 2;
 		}
+
+		free(name_buf);
 	}
 
-	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 +399,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();



-- 
In my defence, I have been left unsupervised.