xref: /openbsd-src/usr.sbin/acme-client/revokeproc.c (revision 0b60193df276dc6f43ebb60edd076225d1cc0850)
1 /*	$Id: revokeproc.c,v 1.25 2022/12/18 12:04:55 tb Exp $ */
2 /*
3  * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <assert.h>
19 #include <ctype.h>
20 #include <err.h>
21 #include <errno.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <vis.h>
27 
28 #include <openssl/pem.h>
29 #include <openssl/x509.h>
30 #include <openssl/x509v3.h>
31 #include <openssl/err.h>
32 
33 #include "extern.h"
34 
35 #define	RENEW_ALLOW (30 * 24 * 60 * 60)
36 
37 /*
38  * Convert the X509's expiration time into a time_t value.
39  */
40 static time_t
X509expires(X509 * x)41 X509expires(X509 *x)
42 {
43 	ASN1_TIME	*atim;
44 	struct tm	 t;
45 
46 	if ((atim = X509_getm_notAfter(x)) == NULL) {
47 		warnx("missing notAfter");
48 		return -1;
49 	}
50 
51 	memset(&t, 0, sizeof(t));
52 
53 	if (!ASN1_TIME_to_tm(atim, &t)) {
54 		warnx("invalid ASN1_TIME");
55 		return -1;
56 	}
57 
58 	return timegm(&t);
59 }
60 
61 int
revokeproc(int fd,const char * certfile,int force,int revocate,const char * const * alts,size_t altsz)62 revokeproc(int fd, const char *certfile, int force,
63     int revocate, const char *const *alts, size_t altsz)
64 {
65 	GENERAL_NAMES			*sans = NULL;
66 	char				*der = NULL, *dercp, *der64 = NULL;
67 	int				 rc = 0, cc, i, len;
68 	size_t				*found = NULL;
69 	FILE				*f = NULL;
70 	X509				*x = NULL;
71 	long				 lval;
72 	enum revokeop			 op, rop;
73 	time_t				 t;
74 	size_t				 j;
75 
76 	/*
77 	 * First try to open the certificate before we drop privileges
78 	 * and jail ourselves.
79 	 * We allow "f" to be NULL IFF the cert doesn't exist yet.
80 	 */
81 
82 	if ((f = fopen(certfile, "r")) == NULL && errno != ENOENT) {
83 		warn("%s", certfile);
84 		goto out;
85 	}
86 
87 	/* File-system and sandbox jailing. */
88 
89 	ERR_load_crypto_strings();
90 
91 	if (pledge("stdio", NULL) == -1) {
92 		warn("pledge");
93 		goto out;
94 	}
95 
96 	/*
97 	 * If we couldn't open the certificate, it doesn't exist so we
98 	 * haven't submitted it yet, so obviously we can mark that it
99 	 * has expired and we should renew it.
100 	 * If we're revoking, however, then that's an error!
101 	 * Ignore if the reader isn't reading in either case.
102 	 */
103 
104 	if (f == NULL && revocate) {
105 		warnx("%s: no certificate found", certfile);
106 		(void)writeop(fd, COMM_REVOKE_RESP, REVOKE_OK);
107 		goto out;
108 	} else if (f == NULL && !revocate) {
109 		if (writeop(fd, COMM_REVOKE_RESP, REVOKE_EXP) >= 0)
110 			rc = 1;
111 		goto out;
112 	}
113 
114 	if ((x = PEM_read_X509(f, NULL, NULL, NULL)) == NULL) {
115 		warnx("PEM_read_X509");
116 		goto out;
117 	}
118 
119 	/* Cache and sanity check X509v3 extensions. */
120 
121 	if (X509_check_purpose(x, -1, -1) <= 0) {
122 		warnx("%s: invalid X509v3 extensions", certfile);
123 		goto out;
124 	}
125 
126 	/* Read out the expiration date. */
127 
128 	if ((t = X509expires(x)) == -1) {
129 		warnx("X509expires");
130 		goto out;
131 	}
132 
133 	/* Extract list of SAN entries from the certificate. */
134 
135 	sans = X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL);
136 	if (sans == NULL) {
137 		warnx("%s: does not have a SAN entry", certfile);
138 		if (revocate)
139 			goto out;
140 		force = 2;
141 	}
142 
143 	/* An array of buckets: the number of entries found. */
144 
145 	if ((found = calloc(altsz, sizeof(size_t))) == NULL) {
146 		warn("calloc");
147 		goto out;
148 	}
149 
150 	/*
151 	 * Ensure the certificate's SAN entries fully cover those from the
152 	 * configuration file and that all domains are represented only once.
153 	 */
154 
155 	for (i = 0; i < sk_GENERAL_NAME_num(sans); i++) {
156 		GENERAL_NAME		*gen_name;
157 		const ASN1_IA5STRING	*name;
158 		const unsigned char	*name_buf;
159 		int			 name_len;
160 		int			 name_type;
161 
162 		gen_name = sk_GENERAL_NAME_value(sans, i);
163 		assert(gen_name != NULL);
164 
165 		name = GENERAL_NAME_get0_value(gen_name, &name_type);
166 		if (name_type != GEN_DNS)
167 			continue;
168 
169 		/* name_buf isn't a C string and could contain embedded NULs. */
170 		name_buf = ASN1_STRING_get0_data(name);
171 		name_len = ASN1_STRING_length(name);
172 
173 		for (j = 0; j < altsz; j++) {
174 			if ((size_t)name_len != strlen(alts[j]))
175 				continue;
176 			if (memcmp(name_buf, alts[j], name_len) == 0)
177 				break;
178 		}
179 		if (j == altsz) {
180 			if (revocate) {
181 				char *visbuf;
182 
183 				visbuf = calloc(4, name_len + 1);
184 				if (visbuf == NULL) {
185 					warn("%s: unexpected SAN", certfile);
186 					goto out;
187 				}
188 				strvisx(visbuf, name_buf, name_len, VIS_SAFE);
189 				warnx("%s: unexpected SAN entry: %s",
190 				    certfile, visbuf);
191 				free(visbuf);
192 				goto out;
193 			}
194 			force = 2;
195 			continue;
196 		}
197 		if (found[j]++) {
198 			if (revocate) {
199 				warnx("%s: duplicate SAN entry: %.*s",
200 				    certfile, name_len, name_buf);
201 				goto out;
202 			}
203 			force = 2;
204 		}
205 	}
206 
207 	for (j = 0; j < altsz; j++) {
208 		if (found[j])
209 			continue;
210 		if (revocate) {
211 			warnx("%s: domain not listed: %s", certfile, alts[j]);
212 			goto out;
213 		}
214 		force = 2;
215 	}
216 
217 	/*
218 	 * If we're going to revoke, write the certificate to the
219 	 * netproc in DER and base64-encoded format.
220 	 * Then exit: we have nothing left to do.
221 	 */
222 
223 	if (revocate) {
224 		dodbg("%s: revocation", certfile);
225 
226 		/*
227 		 * First, tell netproc we're online.
228 		 * If they're down, then just exit without warning.
229 		 */
230 
231 		cc = writeop(fd, COMM_REVOKE_RESP, REVOKE_EXP);
232 		if (cc == 0)
233 			rc = 1;
234 		if (cc <= 0)
235 			goto out;
236 
237 		if ((len = i2d_X509(x, NULL)) < 0) {
238 			warnx("i2d_X509");
239 			goto out;
240 		} else if ((der = dercp = malloc(len)) == NULL) {
241 			warn("malloc");
242 			goto out;
243 		} else if (len != i2d_X509(x, (u_char **)&dercp)) {
244 			warnx("i2d_X509");
245 			goto out;
246 		} else if ((der64 = base64buf_url(der, len)) == NULL) {
247 			warnx("base64buf_url");
248 			goto out;
249 		} else if (writestr(fd, COMM_CSR, der64) >= 0)
250 			rc = 1;
251 
252 		goto out;
253 	}
254 
255 	rop = time(NULL) >= (t - RENEW_ALLOW) ? REVOKE_EXP : REVOKE_OK;
256 
257 	if (rop == REVOKE_EXP)
258 		dodbg("%s: certificate renewable: %lld days left",
259 		    certfile, (long long)(t - time(NULL)) / 24 / 60 / 60);
260 	else
261 		dodbg("%s: certificate valid: %lld days left",
262 		    certfile, (long long)(t - time(NULL)) / 24 / 60 / 60);
263 
264 	if (rop == REVOKE_OK && force) {
265 		warnx("%s: %sforcing renewal", certfile,
266 		    force == 2 ? "domain list changed, " : "");
267 		rop = REVOKE_EXP;
268 	}
269 
270 	/*
271 	 * We can re-submit it given RENEW_ALLOW time before.
272 	 * If netproc is down, just exit.
273 	 */
274 
275 	if ((cc = writeop(fd, COMM_REVOKE_RESP, rop)) == 0)
276 		rc = 1;
277 	if (cc <= 0)
278 		goto out;
279 
280 	op = REVOKE__MAX;
281 	if ((lval = readop(fd, COMM_REVOKE_OP)) == 0)
282 		op = REVOKE_STOP;
283 	else if (lval == REVOKE_CHECK)
284 		op = lval;
285 
286 	if (op == REVOKE__MAX) {
287 		warnx("unknown operation from netproc");
288 		goto out;
289 	} else if (op == REVOKE_STOP) {
290 		rc = 1;
291 		goto out;
292 	}
293 
294 	rc = 1;
295 out:
296 	close(fd);
297 	if (f != NULL)
298 		fclose(f);
299 	X509_free(x);
300 	GENERAL_NAMES_free(sans);
301 	free(der);
302 	free(found);
303 	free(der64);
304 	ERR_print_errors_fp(stderr);
305 	ERR_free_strings();
306 	return rc;
307 }
308