Index | Thread | Search

From:
Lloyd <ng2d68@proton.me>
Subject:
Re: acme-client(1): add support for let's encrypt iPAddress certificates
To:
Stuart Henderson <stu@spacehopper.org>
Cc:
"tech@openbsd.org" <tech@openbsd.org>
Date:
Wed, 17 Sep 2025 20:48:50 +0000

Download raw body.

Thread
The below diff adds full support for IP address certs to acme-client(1).

BIG CHANGE -> The CN is no longer included with ACME requests by default
as was suggested by Stuart. Use the new "with cn" directive to include it.

To specify an IP address SAN, prefix it with "ip:" in acme-client.conf.
This works for both "domain name" and "alternative names {}".

Lightly tested, would appreciate attention in the following areas:

* Someone with IPv6 access to test IPv6 SANs
* revokeproc() was reworked significantly and there's probably bugs.

Feel free to make any changes.

Regards
Lloyd

Stuart Henderson wrote:
> 
> Tagging with ip: in the string and working from there seems a reasonable
> way to handle this to me. Then no need for extra config keywords other
> than relating to the profile. I don't think this would be too ugly:

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	17 Sep 2025 20:18:15 -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	17 Sep 2025 20:18:15 -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	17 Sep 2025 20:18:15 -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	17 Sep 2025 20:18:15 -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	17 Sep 2025 20:18:15 -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	17 Sep 2025 20:18:15 -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	17 Sep 2025 20:18:15 -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	17 Sep 2025 20:18:15 -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	17 Sep 2025 20:18:15 -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,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)
-			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:%X",
+					    name_buf, 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 +234,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 +338,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();