xref: /openbsd-src/usr.sbin/ocspcheck/ocspcheck.c (revision ddff58c9645eff8c09d585e29d683c7ea9ed0547)
1*ddff58c9Stb /* $OpenBSD: ocspcheck.c,v 1.34 2024/12/04 07:58:51 tb Exp $ */
298c0c22dSderaadt 
3471c6e53Sbeck /*
467232e7dSbeck  * Copyright (c) 2017,2020 Bob Beck <beck@openbsd.org>
5471c6e53Sbeck  *
6471c6e53Sbeck  * Permission to use, copy, modify, and distribute this software for any
7471c6e53Sbeck  * purpose with or without fee is hereby granted, provided that the above
8471c6e53Sbeck  * copyright notice and this permission notice appear in all copies.
9471c6e53Sbeck  *
10471c6e53Sbeck  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11471c6e53Sbeck  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12471c6e53Sbeck  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13471c6e53Sbeck  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14471c6e53Sbeck  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15471c6e53Sbeck  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16471c6e53Sbeck  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17471c6e53Sbeck  */
18471c6e53Sbeck 
19471c6e53Sbeck #include <arpa/inet.h>
20dc0b7803Sbeck #include <netinet/in.h>
21471c6e53Sbeck #include <sys/socket.h>
223cf47869Sbeck #include <sys/stat.h>
23471c6e53Sbeck 
24471c6e53Sbeck #include <err.h>
25471c6e53Sbeck #include <fcntl.h>
26471c6e53Sbeck #include <limits.h>
27471c6e53Sbeck #include <netdb.h>
28471c6e53Sbeck #include <poll.h>
29471c6e53Sbeck #include <stdio.h>
30471c6e53Sbeck #include <stdlib.h>
31471c6e53Sbeck #include <string.h>
32471c6e53Sbeck #include <time.h>
33471c6e53Sbeck #include <unistd.h>
34471c6e53Sbeck 
35471c6e53Sbeck #include <openssl/err.h>
36471c6e53Sbeck #include <openssl/ocsp.h>
37ac059987Sbeck #include <openssl/posix_time.h>
38471c6e53Sbeck #include <openssl/ssl.h>
39471c6e53Sbeck 
40471c6e53Sbeck #include "http.h"
41471c6e53Sbeck 
42471c6e53Sbeck #define MAXAGE_SEC (14*24*60*60)
43471c6e53Sbeck #define JITTER_SEC (60)
44a3a695edSbeck #define OCSP_MAX_RESPONSE_SIZE (20480)
45471c6e53Sbeck 
46471c6e53Sbeck typedef struct ocsp_request {
47471c6e53Sbeck 	STACK_OF(X509) *fullchain;
48471c6e53Sbeck 	OCSP_REQUEST *req;
49471c6e53Sbeck 	char *url;
50471c6e53Sbeck 	unsigned char *data;
51471c6e53Sbeck 	size_t size;
52471c6e53Sbeck 	int nonce;
53471c6e53Sbeck } ocsp_request;
54471c6e53Sbeck 
55471c6e53Sbeck int verbose;
56471c6e53Sbeck #define vspew(fmt, ...) \
57471c6e53Sbeck 	do { if (verbose >= 1) fprintf(stderr, fmt, __VA_ARGS__); } while (0)
58471c6e53Sbeck #define dspew(fmt, ...) \
59471c6e53Sbeck 	do { if (verbose >= 2) fprintf(stderr, fmt, __VA_ARGS__); } while (0)
60471c6e53Sbeck 
61471c6e53Sbeck #define MAX_SERVERS_DNS 8
62471c6e53Sbeck 
63471c6e53Sbeck struct addr {
64471c6e53Sbeck 	int	 family; /* 4 for PF_INET, 6 for PF_INET6 */
65471c6e53Sbeck 	char	 ip[INET6_ADDRSTRLEN];
66471c6e53Sbeck };
67471c6e53Sbeck 
68471c6e53Sbeck static ssize_t
69471c6e53Sbeck host_dns(const char *s, struct addr vec[MAX_SERVERS_DNS])
70471c6e53Sbeck {
71471c6e53Sbeck 	struct addrinfo		 hints, *res0, *res;
72471c6e53Sbeck 	int			 error;
73471c6e53Sbeck 	ssize_t			 vecsz;
74471c6e53Sbeck 	struct sockaddr		*sa;
75471c6e53Sbeck 
76471c6e53Sbeck 	memset(&hints, 0, sizeof(hints));
77471c6e53Sbeck 	hints.ai_family = PF_UNSPEC;
78471c6e53Sbeck 	hints.ai_socktype = SOCK_DGRAM; /* DUMMY */
79471c6e53Sbeck 
80471c6e53Sbeck 	error = getaddrinfo(s, NULL, &hints, &res0);
81471c6e53Sbeck 
82471c6e53Sbeck 	if (error == EAI_AGAIN ||
83dc0b7803Sbeck #ifdef EAI_NODATA
84471c6e53Sbeck 	    error == EAI_NODATA ||
85dc0b7803Sbeck #endif
86471c6e53Sbeck 	    error == EAI_NONAME)
8798c0c22dSderaadt 		return 0;
88471c6e53Sbeck 
89471c6e53Sbeck 	if (error) {
9098c0c22dSderaadt 		warnx("%s: parse error: %s", s, gai_strerror(error));
9198c0c22dSderaadt 		return -1;
92471c6e53Sbeck 	}
93471c6e53Sbeck 
94471c6e53Sbeck 	for (vecsz = 0, res = res0;
95d549f443Sbeck 	    res != NULL && vecsz < MAX_SERVERS_DNS;
96471c6e53Sbeck 	    res = res->ai_next) {
97471c6e53Sbeck 		if (res->ai_family != AF_INET &&
98471c6e53Sbeck 		    res->ai_family != AF_INET6)
99471c6e53Sbeck 			continue;
100471c6e53Sbeck 
101471c6e53Sbeck 		sa = res->ai_addr;
102471c6e53Sbeck 
103d549f443Sbeck 		if (res->ai_family == AF_INET) {
104471c6e53Sbeck 			vec[vecsz].family = 4;
105471c6e53Sbeck 			inet_ntop(AF_INET,
106471c6e53Sbeck 			    &(((struct sockaddr_in *)sa)->sin_addr),
107471c6e53Sbeck 				vec[vecsz].ip, INET6_ADDRSTRLEN);
108471c6e53Sbeck 		} else {
109471c6e53Sbeck 			vec[vecsz].family = 6;
110471c6e53Sbeck 			inet_ntop(AF_INET6,
111471c6e53Sbeck 			    &(((struct sockaddr_in6 *)sa)->sin6_addr),
112471c6e53Sbeck 			    vec[vecsz].ip, INET6_ADDRSTRLEN);
113471c6e53Sbeck 		}
114471c6e53Sbeck 
115471c6e53Sbeck 		dspew("DNS returns %s for %s\n", vec[vecsz].ip, s);
116471c6e53Sbeck 		vecsz++;
117471c6e53Sbeck 	}
118471c6e53Sbeck 
119471c6e53Sbeck 	freeaddrinfo(res0);
12098c0c22dSderaadt 	return vecsz;
121471c6e53Sbeck }
122471c6e53Sbeck 
123471c6e53Sbeck /*
124471c6e53Sbeck  * Extract the domain and port from a URL.
125471c6e53Sbeck  * The url must be formatted as schema://address[/stuff].
126471c6e53Sbeck  * This returns NULL on failure.
127471c6e53Sbeck  */
128471c6e53Sbeck static char *
129471c6e53Sbeck url2host(const char *host, short *port, char **path)
130471c6e53Sbeck {
131471c6e53Sbeck 	char	*url, *ep;
132471c6e53Sbeck 
133471c6e53Sbeck 	/* We only understand HTTP and HTTPS. */
134471c6e53Sbeck 
135d549f443Sbeck 	if (strncmp(host, "https://", 8) == 0) {
136471c6e53Sbeck 		*port = 443;
137d549f443Sbeck 		if ((url = strdup(host + 8)) == NULL) {
138471c6e53Sbeck 			warn("strdup");
139471c6e53Sbeck 			return (NULL);
140471c6e53Sbeck 		}
141d549f443Sbeck 	} else if (strncmp(host, "http://", 7) == 0) {
142471c6e53Sbeck 		*port = 80;
143d549f443Sbeck 		if ((url = strdup(host + 7)) == NULL) {
144471c6e53Sbeck 			warn("strdup");
145471c6e53Sbeck 			return (NULL);
146471c6e53Sbeck 		}
147471c6e53Sbeck 	} else {
148471c6e53Sbeck 		warnx("%s: unknown schema", host);
149471c6e53Sbeck 		return (NULL);
150471c6e53Sbeck 	}
151471c6e53Sbeck 
152471c6e53Sbeck 	/* Terminate path part. */
153471c6e53Sbeck 
154d549f443Sbeck 	if ((ep = strchr(url, '/')) != NULL) {
155471c6e53Sbeck 		*path = strdup(ep);
156471c6e53Sbeck 		*ep = '\0';
157471c6e53Sbeck 	} else
15867232e7dSbeck 		*path = strdup("/");
159471c6e53Sbeck 
160d549f443Sbeck 	if (*path == NULL) {
161471c6e53Sbeck 		warn("strdup");
162471c6e53Sbeck 		free(url);
163471c6e53Sbeck 		return (NULL);
164471c6e53Sbeck 	}
165471c6e53Sbeck 
16667232e7dSbeck 	/* Check to see if there is a port in the url */
16767232e7dSbeck 	if ((ep = strchr(url, ':')) != NULL) {
16867232e7dSbeck 		const char *errstr;
16967232e7dSbeck 		short pp;
17067232e7dSbeck 		pp = strtonum(ep + 1, 1, SHRT_MAX, &errstr);
17167232e7dSbeck 		if (errstr != NULL) {
17267232e7dSbeck 			warnx("error parsing port from '%s': %s", url, errstr);
17367232e7dSbeck 			free(url);
17467232e7dSbeck 			free(*path);
17567232e7dSbeck 			return NULL;
17667232e7dSbeck 		}
17767232e7dSbeck 		*port = pp;
17867232e7dSbeck 		*ep = '\0';
17967232e7dSbeck 	}
18067232e7dSbeck 
181471c6e53Sbeck 	return (url);
182471c6e53Sbeck }
183471c6e53Sbeck 
184471c6e53Sbeck static time_t
185471c6e53Sbeck parse_ocsp_time(ASN1_GENERALIZEDTIME *gt)
186471c6e53Sbeck {
187471c6e53Sbeck 	struct tm tm;
188471c6e53Sbeck 	time_t rv = -1;
189471c6e53Sbeck 
190471c6e53Sbeck 	if (gt == NULL)
191471c6e53Sbeck 		return -1;
192471c6e53Sbeck 	/* RFC 6960 specifies that all times in OCSP must be GENERALIZEDTIME */
193115610beStb 	if (!ASN1_GENERALIZEDTIME_check(gt))
194115610beStb 		return -1;
195115610beStb 	if (!ASN1_TIME_to_tm(gt, &tm))
196471c6e53Sbeck 		return -1;
197ac059987Sbeck 	if (!OPENSSL_timegm(&tm, &rv))
198471c6e53Sbeck 		return -1;
199471c6e53Sbeck 	return rv;
200471c6e53Sbeck }
201471c6e53Sbeck 
202471c6e53Sbeck static X509_STORE *
203c322fdddStb read_cacerts(const char *file, const char *dir)
204471c6e53Sbeck {
205c322fdddStb 	X509_STORE *store = NULL;
206471c6e53Sbeck 	X509_LOOKUP *lookup;
207471c6e53Sbeck 
208c322fdddStb 	if (file == NULL && dir == NULL) {
209c322fdddStb 		warnx("No CA certs to load");
210c322fdddStb 		goto end;
211c322fdddStb 	}
212471c6e53Sbeck 	if ((store = X509_STORE_new()) == NULL) {
213471c6e53Sbeck 		warnx("Malloc failed");
214471c6e53Sbeck 		goto end;
215471c6e53Sbeck 	}
216c322fdddStb 	if (file != NULL) {
217c322fdddStb 		if ((lookup = X509_STORE_add_lookup(store,
218c322fdddStb 		    X509_LOOKUP_file())) == NULL) {
219c322fdddStb 			warnx("Unable to load CA cert file");
220471c6e53Sbeck 			goto end;
221471c6e53Sbeck 		}
222471c6e53Sbeck 		if (!X509_LOOKUP_load_file(lookup, file, X509_FILETYPE_PEM)) {
223992bd1ceSbeck 			warnx("Unable to load CA certs from file %s", file);
224471c6e53Sbeck 			goto end;
225471c6e53Sbeck 		}
226c322fdddStb 	}
227c322fdddStb 	if (dir != NULL) {
228c322fdddStb 		if ((lookup = X509_STORE_add_lookup(store,
229c322fdddStb 		    X509_LOOKUP_hash_dir())) == NULL) {
230c322fdddStb 			warnx("Unable to load CA cert directory");
231471c6e53Sbeck 			goto end;
232471c6e53Sbeck 		}
233c322fdddStb 		if (!X509_LOOKUP_add_dir(lookup, dir, X509_FILETYPE_PEM)) {
234c322fdddStb 			warnx("Unable to load CA certs from directory %s", dir);
235c322fdddStb 			goto end;
236c322fdddStb 		}
237c322fdddStb 	}
238471c6e53Sbeck 	return store;
239471c6e53Sbeck 
240471c6e53Sbeck  end:
241471c6e53Sbeck 	X509_STORE_free(store);
242471c6e53Sbeck 	return NULL;
243471c6e53Sbeck }
244471c6e53Sbeck 
245471c6e53Sbeck static STACK_OF(X509) *
246471c6e53Sbeck read_fullchain(const char *file, int *count)
247471c6e53Sbeck {
248471c6e53Sbeck 	int i;
249471c6e53Sbeck 	BIO *bio;
250471c6e53Sbeck 	STACK_OF(X509_INFO) *xis = NULL;
251471c6e53Sbeck 	X509_INFO *xi;
252471c6e53Sbeck 	STACK_OF(X509) *rv = NULL;
253471c6e53Sbeck 
254471c6e53Sbeck 	*count = 0;
255471c6e53Sbeck 
256471c6e53Sbeck 	if ((bio = BIO_new_file(file, "r")) == NULL) {
257ad6a44d4Sbeck 		warn("Unable to read a certificate from %s", file);
25867232e7dSbeck 		goto end;
259471c6e53Sbeck 	}
260471c6e53Sbeck 	if ((xis = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL)) == NULL) {
261d549f443Sbeck 		warnx("Unable to read PEM format from %s", file);
26267232e7dSbeck 		goto end;
263471c6e53Sbeck 	}
264471c6e53Sbeck 	if (sk_X509_INFO_num(xis) <= 0) {
265d549f443Sbeck 		warnx("No certificates in file %s", file);
266471c6e53Sbeck 		goto end;
267471c6e53Sbeck 	}
268471c6e53Sbeck 	if ((rv = sk_X509_new_null()) == NULL) {
269d549f443Sbeck 		warnx("malloc failed");
270471c6e53Sbeck 		goto end;
271471c6e53Sbeck 	}
272471c6e53Sbeck 
273471c6e53Sbeck 	for (i = 0; i < sk_X509_INFO_num(xis); i++) {
274471c6e53Sbeck 		xi = sk_X509_INFO_value(xis, i);
275471c6e53Sbeck 		if (xi->x509 == NULL)
276471c6e53Sbeck 			continue;
277471c6e53Sbeck 		if (!sk_X509_push(rv, xi->x509)) {
278d549f443Sbeck 			warnx("unable to build x509 chain");
279471c6e53Sbeck 			sk_X509_pop_free(rv, X509_free);
280471c6e53Sbeck 			rv = NULL;
281471c6e53Sbeck 			goto end;
282471c6e53Sbeck 		}
283471c6e53Sbeck 		xi->x509 = NULL;
284471c6e53Sbeck 		(*count)++;
285471c6e53Sbeck 	}
286471c6e53Sbeck  end:
28767232e7dSbeck 	BIO_free(bio);
288471c6e53Sbeck 	sk_X509_INFO_pop_free(xis, X509_INFO_free);
289471c6e53Sbeck 	return rv;
290471c6e53Sbeck }
291471c6e53Sbeck 
292471c6e53Sbeck static inline X509 *
293471c6e53Sbeck cert_from_chain(STACK_OF(X509) *fullchain)
294471c6e53Sbeck {
295471c6e53Sbeck 	return sk_X509_value(fullchain, 0);
296471c6e53Sbeck }
297471c6e53Sbeck 
29867232e7dSbeck static const X509 *
299471c6e53Sbeck issuer_from_chain(STACK_OF(X509) *fullchain)
300471c6e53Sbeck {
30167232e7dSbeck 	const X509 *cert;
302471c6e53Sbeck 	X509_NAME *issuer_name;
303471c6e53Sbeck 
304471c6e53Sbeck 	cert = cert_from_chain(fullchain);
305471c6e53Sbeck 	if ((issuer_name = X509_get_issuer_name(cert)) == NULL)
306471c6e53Sbeck 		return NULL;
307471c6e53Sbeck 
30867232e7dSbeck 	return X509_find_by_subject(fullchain, issuer_name);
309471c6e53Sbeck }
310471c6e53Sbeck 
311471c6e53Sbeck static ocsp_request *
312c322fdddStb ocsp_request_new_from_cert(const char *cadir, char *file, int nonce)
313471c6e53Sbeck {
31467232e7dSbeck 	X509 *cert;
315471c6e53Sbeck 	int count = 0;
31667232e7dSbeck 	OCSP_CERTID *id = NULL;
31767232e7dSbeck 	ocsp_request *request = NULL;
318471c6e53Sbeck 	const EVP_MD *cert_id_md = NULL;
31967232e7dSbeck 	const X509 *issuer;
32067232e7dSbeck 	STACK_OF(OPENSSL_STRING) *urls = NULL;
321471c6e53Sbeck 
322471c6e53Sbeck 	if ((request = calloc(1, sizeof(ocsp_request))) == NULL) {
323471c6e53Sbeck 		warn("malloc");
32467232e7dSbeck 		goto err;
325471c6e53Sbeck 	}
326471c6e53Sbeck 
327471c6e53Sbeck 	if ((request->req = OCSP_REQUEST_new()) == NULL)
32867232e7dSbeck 		goto err;
329471c6e53Sbeck 
330471c6e53Sbeck 	request->fullchain = read_fullchain(file, &count);
331c322fdddStb 	if (cadir == NULL) {
332471c6e53Sbeck 		/* Drop rpath from pledge, we don't need to read anymore */
333471c6e53Sbeck 		if (pledge("stdio inet dns", NULL) == -1)
334fef88015Sbeck 			err(1, "pledge");
335c322fdddStb 	}
33667232e7dSbeck 	if (request->fullchain == NULL) {
33767232e7dSbeck 		warnx("Unable to read cert chain from file %s", file);
33867232e7dSbeck 		goto err;
33967232e7dSbeck 	}
340471c6e53Sbeck 	if (count <= 1) {
34198c0c22dSderaadt 		warnx("File %s does not contain a cert chain", file);
34267232e7dSbeck 		goto err;
343471c6e53Sbeck 	}
344471c6e53Sbeck 	if ((cert = cert_from_chain(request->fullchain)) == NULL) {
345471c6e53Sbeck 		warnx("No certificate found in %s", file);
34667232e7dSbeck 		goto err;
347471c6e53Sbeck 	}
348471c6e53Sbeck 	if ((issuer = issuer_from_chain(request->fullchain)) == NULL) {
349471c6e53Sbeck 		warnx("Unable to find issuer for cert in %s", file);
35067232e7dSbeck 		goto err;
351471c6e53Sbeck 	}
352471c6e53Sbeck 
353471c6e53Sbeck 	urls = X509_get1_ocsp(cert);
354471c6e53Sbeck 	if (urls == NULL || sk_OPENSSL_STRING_num(urls) <= 0) {
355471c6e53Sbeck 		warnx("Certificate in %s contains no OCSP url", file);
35667232e7dSbeck 		goto err;
357471c6e53Sbeck 	}
358471c6e53Sbeck 	if ((request->url = strdup(sk_OPENSSL_STRING_value(urls, 0))) == NULL)
35967232e7dSbeck 		goto err;
360471c6e53Sbeck 	X509_email_free(urls);
36167232e7dSbeck 	urls = NULL;
362471c6e53Sbeck 
363471c6e53Sbeck 	cert_id_md = EVP_sha1(); /* XXX. This sucks but OCSP is poopy */
364471c6e53Sbeck 	if ((id = OCSP_cert_to_id(cert_id_md, cert, issuer)) == NULL) {
365471c6e53Sbeck 		warnx("Unable to get certificate id from cert in %s", file);
36667232e7dSbeck 		goto err;
367471c6e53Sbeck 	}
368471c6e53Sbeck 	if (OCSP_request_add0_id(request->req, id) == NULL) {
369471c6e53Sbeck 		warnx("Unable to add certificate id to request");
37067232e7dSbeck 		goto err;
371471c6e53Sbeck 	}
37267232e7dSbeck 	id = NULL;
373471c6e53Sbeck 
374471c6e53Sbeck 	request->nonce = nonce;
375471c6e53Sbeck 	if (request->nonce)
376471c6e53Sbeck 		OCSP_request_add1_nonce(request->req, NULL, -1);
377471c6e53Sbeck 
378471c6e53Sbeck 	if ((request->size = i2d_OCSP_REQUEST(request->req,
379471c6e53Sbeck 	    &request->data)) <= 0) {
380471c6e53Sbeck 		warnx("Unable to encode ocsp request");
38167232e7dSbeck 		goto err;
382471c6e53Sbeck 	}
383471c6e53Sbeck 	if (request->data == NULL) {
3843a50f0a9Sjmc 		warnx("Unable to allocate memory");
38567232e7dSbeck 		goto err;
386471c6e53Sbeck 	}
38767232e7dSbeck 	return request;
38867232e7dSbeck 
38967232e7dSbeck  err:
39067232e7dSbeck 	if (request != NULL) {
39167232e7dSbeck 		sk_X509_pop_free(request->fullchain, X509_free);
39267232e7dSbeck 		free(request->url);
39367232e7dSbeck 		OCSP_REQUEST_free(request->req);
39467232e7dSbeck 		free(request->data);
39567232e7dSbeck 	}
39667232e7dSbeck 	X509_email_free(urls);
39767232e7dSbeck 	OCSP_CERTID_free(id);
39867232e7dSbeck 	free(request);
39967232e7dSbeck 	return NULL;
400471c6e53Sbeck }
401471c6e53Sbeck 
402471c6e53Sbeck 
403471c6e53Sbeck int
404471c6e53Sbeck validate_response(char *buf, size_t size, ocsp_request *request,
405471c6e53Sbeck     X509_STORE *store, char *host, char *file)
406471c6e53Sbeck {
407471c6e53Sbeck 	ASN1_GENERALIZEDTIME *revtime = NULL, *thisupd = NULL, *nextupd = NULL;
408471c6e53Sbeck 	const unsigned char **p = (const unsigned char **)&buf;
409471c6e53Sbeck 	int status, cert_status = 0, crl_reason = 0;
410471c6e53Sbeck 	time_t now, rev_t = -1, this_t, next_t;
41167232e7dSbeck 	OCSP_RESPONSE *resp = NULL;
41267232e7dSbeck 	OCSP_BASICRESP *bresp = NULL;
41367232e7dSbeck 	OCSP_CERTID *cid = NULL;
41467232e7dSbeck 	const X509 *cert, *issuer;
41567232e7dSbeck 	int ret = 0;
416471c6e53Sbeck 
417471c6e53Sbeck 	if ((cert = cert_from_chain(request->fullchain)) == NULL) {
418471c6e53Sbeck 		warnx("No certificate found in %s", file);
41967232e7dSbeck 		goto err;
420471c6e53Sbeck 	}
421471c6e53Sbeck 	if ((issuer = issuer_from_chain(request->fullchain)) == NULL) {
42298c0c22dSderaadt 		warnx("Unable to find certificate issuer for cert in %s", file);
42367232e7dSbeck 		goto err;
424471c6e53Sbeck 	}
425471c6e53Sbeck 	if ((cid = OCSP_cert_to_id(NULL, cert, issuer)) == NULL) {
426471c6e53Sbeck 		warnx("Unable to get issuer cert/CID in %s", file);
42767232e7dSbeck 		goto err;
428471c6e53Sbeck 	}
429471c6e53Sbeck 
430471c6e53Sbeck 	if ((resp = d2i_OCSP_RESPONSE(NULL, p, size)) == NULL) {
431471c6e53Sbeck 		warnx("OCSP response unserializable from host %s", host);
43267232e7dSbeck 		goto err;
433471c6e53Sbeck 	}
434471c6e53Sbeck 
435471c6e53Sbeck 	if ((bresp = OCSP_response_get1_basic(resp)) == NULL) {
436471c6e53Sbeck 		warnx("Failed to load OCSP response from %s", host);
43767232e7dSbeck 		goto err;
438471c6e53Sbeck 	}
439471c6e53Sbeck 
440471c6e53Sbeck 	if (OCSP_basic_verify(bresp, request->fullchain, store,
441471c6e53Sbeck 		OCSP_TRUSTOTHER) != 1) {
442471c6e53Sbeck 		warnx("OCSP verify failed from %s", host);
44367232e7dSbeck 		goto err;
444471c6e53Sbeck 	}
445471c6e53Sbeck 	dspew("OCSP response signature validated from %s\n", host);
446471c6e53Sbeck 
447471c6e53Sbeck 	status = OCSP_response_status(resp);
448471c6e53Sbeck 	if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
449471c6e53Sbeck 		warnx("OCSP Failure: code %d (%s) from host %s",
450471c6e53Sbeck 		    status, OCSP_response_status_str(status), host);
45167232e7dSbeck 		goto err;
452471c6e53Sbeck 	}
453471c6e53Sbeck 	dspew("OCSP response status %d from host %s\n", status, host);
454471c6e53Sbeck 
455471c6e53Sbeck 	/* Check the nonce if we sent one */
456471c6e53Sbeck 
457471c6e53Sbeck 	if (request->nonce) {
458471c6e53Sbeck 		if (OCSP_check_nonce(request->req, bresp) <= 0) {
459471c6e53Sbeck 			warnx("No OCSP nonce, or mismatch, from host %s", host);
46067232e7dSbeck 			goto err;
461471c6e53Sbeck 		}
462471c6e53Sbeck 	}
463471c6e53Sbeck 
464471c6e53Sbeck 	if (OCSP_resp_find_status(bresp, cid, &cert_status, &crl_reason,
465471c6e53Sbeck 	    &revtime, &thisupd, &nextupd) != 1) {
466471c6e53Sbeck 		warnx("OCSP verify failed: no result for cert");
46767232e7dSbeck 		goto err;
468471c6e53Sbeck 	}
469471c6e53Sbeck 
470471c6e53Sbeck 	if (revtime && (rev_t = parse_ocsp_time(revtime)) == -1) {
471471c6e53Sbeck 		warnx("Unable to parse revocation time in OCSP reply");
47267232e7dSbeck 		goto err;
473471c6e53Sbeck 	}
474471c6e53Sbeck 	/*
475471c6e53Sbeck 	 * Belt and suspenders, Treat it as revoked if there is either
476471c6e53Sbeck 	 * a revocation time, or status revoked.
477471c6e53Sbeck 	 */
478471c6e53Sbeck 	if (rev_t != -1 || cert_status == V_OCSP_CERTSTATUS_REVOKED) {
479471c6e53Sbeck 		warnx("Invalid OCSP reply: certificate is revoked");
480471c6e53Sbeck 		if (rev_t != -1)
481471c6e53Sbeck 			warnx("Certificate revoked at: %s", ctime(&rev_t));
48267232e7dSbeck 		goto err;
483471c6e53Sbeck 	}
484471c6e53Sbeck 	if ((this_t = parse_ocsp_time(thisupd)) == -1) {
485471c6e53Sbeck 		warnx("unable to parse this update time in OCSP reply");
48667232e7dSbeck 		goto err;
487471c6e53Sbeck 	}
488471c6e53Sbeck 	if ((next_t = parse_ocsp_time(nextupd)) == -1) {
489471c6e53Sbeck 		warnx("unable to parse next update time in OCSP reply");
49067232e7dSbeck 		goto err;
491471c6e53Sbeck 	}
492471c6e53Sbeck 
493471c6e53Sbeck 	/* Don't allow this update to precede next update */
494471c6e53Sbeck 	if (this_t >= next_t) {
495471c6e53Sbeck 		warnx("Invalid OCSP reply: this update >= next update");
49667232e7dSbeck 		goto err;
497471c6e53Sbeck 	}
498471c6e53Sbeck 
499471c6e53Sbeck 	now = time(NULL);
500471c6e53Sbeck 	/*
501471c6e53Sbeck 	 * Check that this update is not more than JITTER seconds
502471c6e53Sbeck 	 * in the future.
503471c6e53Sbeck 	 */
504471c6e53Sbeck 	if (this_t > now + JITTER_SEC) {
50567232e7dSbeck 		warnx("Invalid OCSP reply: this update is in the future at %s",
506471c6e53Sbeck 		    ctime(&this_t));
50767232e7dSbeck 		goto err;
508471c6e53Sbeck 	}
509471c6e53Sbeck 
510471c6e53Sbeck 	/*
511471c6e53Sbeck 	 * Check that this update is not more than MAXSEC
512471c6e53Sbeck 	 * in the past.
513471c6e53Sbeck 	 */
514471c6e53Sbeck 	if (this_t < now - MAXAGE_SEC) {
51567232e7dSbeck 		warnx("Invalid OCSP reply: this update is too old %s",
516471c6e53Sbeck 		    ctime(&this_t));
51767232e7dSbeck 		goto err;
518471c6e53Sbeck 	}
519471c6e53Sbeck 
520471c6e53Sbeck 	/*
521471c6e53Sbeck 	 * Check that next update is still valid
522471c6e53Sbeck 	 */
523471c6e53Sbeck 	if (next_t < now - JITTER_SEC) {
52467232e7dSbeck 		warnx("Invalid OCSP reply: reply has expired at %s",
525471c6e53Sbeck 		    ctime(&next_t));
52667232e7dSbeck 		goto err;
527471c6e53Sbeck 	}
528471c6e53Sbeck 
529471c6e53Sbeck 	vspew("OCSP response validated from %s\n", host);
530471c6e53Sbeck 	vspew("	   This Update: %s", ctime(&this_t));
531471c6e53Sbeck 	vspew("	   Next Update: %s", ctime(&next_t));
53267232e7dSbeck 	ret = 1;
53367232e7dSbeck  err:
53467232e7dSbeck 	OCSP_RESPONSE_free(resp);
53567232e7dSbeck 	OCSP_BASICRESP_free(bresp);
53667232e7dSbeck 	OCSP_CERTID_free(cid);
53767232e7dSbeck 	return ret;
538471c6e53Sbeck }
539471c6e53Sbeck 
540471c6e53Sbeck static void
541471c6e53Sbeck usage(void)
542471c6e53Sbeck {
543d22f23b4Sderaadt 	fprintf(stderr,
544e067d73eSjmc 	    "usage: ocspcheck [-Nv] [-C CAfile] [-i staplefile] "
545e067d73eSjmc 	    "[-o staplefile] file\n");
546d22f23b4Sderaadt 	exit(1);
547471c6e53Sbeck }
548471c6e53Sbeck 
549471c6e53Sbeck int
550471c6e53Sbeck main(int argc, char **argv)
551471c6e53Sbeck {
552c322fdddStb 	const char *cafile = NULL, *cadir = NULL;
55367232e7dSbeck 	char *host = NULL, *path = NULL, *certfile = NULL, *outfile = NULL,
554c322fdddStb 	    *instaple = NULL, *infile = NULL;
555ddb46478Sbeck 	struct addr addrs[MAX_SERVERS_DNS] = {{0}};
556471c6e53Sbeck 	struct source sources[MAX_SERVERS_DNS];
557a3a695edSbeck 	int i, ch, staplefd = -1, infd = -1, nonce = 1;
558471c6e53Sbeck 	ocsp_request *request = NULL;
559*ddff58c9Stb 	size_t rescount, instaplesz = 0;
560471c6e53Sbeck 	struct httpget *hget;
561471c6e53Sbeck 	X509_STORE *castore;
562471c6e53Sbeck 	ssize_t written, w;
563471c6e53Sbeck 	short port;
564471c6e53Sbeck 
565a3a695edSbeck 	while ((ch = getopt(argc, argv, "C:i:No:v")) != -1) {
566471c6e53Sbeck 		switch (ch) {
567471c6e53Sbeck 		case 'C':
568471c6e53Sbeck 			cafile = optarg;
569471c6e53Sbeck 			break;
570471c6e53Sbeck 		case 'N':
571471c6e53Sbeck 			nonce = 0;
572471c6e53Sbeck 			break;
573471c6e53Sbeck 		case 'o':
574471c6e53Sbeck 			outfile = optarg;
575471c6e53Sbeck 			break;
576a3a695edSbeck 		case 'i':
577a3a695edSbeck 			infile = optarg;
578a3a695edSbeck 			break;
579471c6e53Sbeck 		case 'v':
580471c6e53Sbeck 			verbose++;
581471c6e53Sbeck 			break;
582471c6e53Sbeck 		default:
583471c6e53Sbeck 			usage();
584471c6e53Sbeck 		}
585471c6e53Sbeck 	}
586471c6e53Sbeck 	argc -= optind;
587471c6e53Sbeck 	argv += optind;
588471c6e53Sbeck 
58967232e7dSbeck 	if (argc != 1 || (certfile = argv[0]) == NULL)
590471c6e53Sbeck 		usage();
591471c6e53Sbeck 
592471c6e53Sbeck 	if (outfile != NULL) {
593471c6e53Sbeck 		if (strcmp(outfile, "-") == 0)
594471c6e53Sbeck 			staplefd = STDOUT_FILENO;
595471c6e53Sbeck 		else
5963cf47869Sbeck 			staplefd = open(outfile, O_WRONLY|O_CREAT,
5973cf47869Sbeck 			    S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH);
598471c6e53Sbeck 		if (staplefd < 0)
59998c0c22dSderaadt 			err(1, "Unable to open output file %s", outfile);
600471c6e53Sbeck 	}
601471c6e53Sbeck 
602a3a695edSbeck 	if (infile != NULL) {
603a3a695edSbeck 		if (strcmp(infile, "-") == 0)
604a3a695edSbeck 			infd = STDIN_FILENO;
605a3a695edSbeck 		else
606a3a695edSbeck 			infd = open(infile, O_RDONLY);
607a3a695edSbeck 		if (infd < 0)
608a3a695edSbeck 			err(1, "Unable to open input file %s", infile);
609a3a695edSbeck 		nonce = 0; /* Can't validate a nonce on a saved reply */
610a3a695edSbeck 	}
611a3a695edSbeck 
612c322fdddStb 	if (cafile == NULL) {
613c322fdddStb 		if (access(X509_get_default_cert_file(), R_OK) == 0)
614c322fdddStb 			cafile = X509_get_default_cert_file();
615c322fdddStb 		if (access(X509_get_default_cert_dir(), F_OK) == 0)
616c322fdddStb 			cadir = X509_get_default_cert_dir();
617c322fdddStb 	}
618c322fdddStb 
619c322fdddStb 	if (cafile != NULL) {
620c322fdddStb 		if (unveil(cafile, "r") == -1)
621bc5a8259Sbeck 			err(1, "unveil %s", cafile);
622c322fdddStb 	}
623c322fdddStb 	if (cadir != NULL) {
624c322fdddStb 		if (unveil(cadir, "r") == -1)
625bc5a8259Sbeck 			err(1, "unveil %s", cadir);
626c322fdddStb 	}
627c322fdddStb 	if (unveil(certfile, "r") == -1)
628bc5a8259Sbeck 		err(1, "unveil %s", certfile);
629c322fdddStb 
630471c6e53Sbeck 	if (pledge("stdio inet rpath dns", NULL) == -1)
631fef88015Sbeck 		err(1, "pledge");
632471c6e53Sbeck 
633471c6e53Sbeck 	/*
634471c6e53Sbeck 	 * Load our certificate and keystore, and build up an
635d5fc9c2aSjmc 	 * OCSP request based on the full certificate chain
636471c6e53Sbeck 	 * we have been given to check.
637471c6e53Sbeck 	 */
638c322fdddStb 	if ((castore = read_cacerts(cafile, cadir)) == NULL)
639fef88015Sbeck 		exit(1);
640c322fdddStb 	if ((request = ocsp_request_new_from_cert(cadir, certfile, nonce))
641c322fdddStb 	    == NULL)
642fef88015Sbeck 		exit(1);
643471c6e53Sbeck 
644d3a917edSbeck 	dspew("Built an %zu byte ocsp request\n", request->size);
6458d2c32dbSbeck 
646471c6e53Sbeck 	if ((host = url2host(request->url, &port, &path)) == NULL)
647fef88015Sbeck 		errx(1, "Invalid OCSP url %s from %s", request->url,
648471c6e53Sbeck 		    certfile);
649a3a695edSbeck 
650a3a695edSbeck 	if (infd == -1) {
651a3a695edSbeck 		/* Get a new OCSP response from the indicated server */
652a3a695edSbeck 
653471c6e53Sbeck 		vspew("Using %s to host %s, port %d, path %s\n",
654471c6e53Sbeck 		    port == 443 ? "https" : "http", host, port, path);
655471c6e53Sbeck 
656471c6e53Sbeck 		rescount = host_dns(host, addrs);
657471c6e53Sbeck 		for (i = 0; i < rescount; i++) {
658471c6e53Sbeck 			sources[i].ip = addrs[i].ip;
659471c6e53Sbeck 			sources[i].family = addrs[i].family;
660471c6e53Sbeck 		}
661471c6e53Sbeck 
662471c6e53Sbeck 		/*
663471c6e53Sbeck 		 * Do an HTTP post to send our request to the OCSP
664471c6e53Sbeck 		 * server, and hopefully get an answer back
665471c6e53Sbeck 		 */
666471c6e53Sbeck 		hget = http_get(sources, rescount, host, port, path,
667471c6e53Sbeck 		    request->data, request->size);
668471c6e53Sbeck 		if (hget == NULL)
669fef88015Sbeck 			errx(1, "http_get");
6702a136263Sbeck 		/*
6712a136263Sbeck 		 * Pledge minimally before fiddling with libcrypto init
6722a136263Sbeck 		 * routines and parsing untrusted input from someone's OCSP
6732a136263Sbeck 		 * server.
6742a136263Sbeck 		 */
675c322fdddStb 		if (cadir == NULL) {
6762a136263Sbeck 			if (pledge("stdio", NULL) == -1)
6772a136263Sbeck 				err(1, "pledge");
678c322fdddStb 		} else {
679c322fdddStb 			if (pledge("stdio rpath", NULL) == -1)
680c322fdddStb 				err(1, "pledge");
681c322fdddStb 		}
6822a136263Sbeck 
683471c6e53Sbeck 		dspew("Server at %s returns:\n", host);
684*ddff58c9Stb 		for (i = 0; i < hget->headsz; i++)
685*ddff58c9Stb 			dspew("   [%s]=[%s]\n", hget->head[i].key, hget->head[i].val);
686d3a917edSbeck 		dspew("	  [Body]=[%zu bytes]\n", hget->bodypartsz);
687471c6e53Sbeck 		if (hget->bodypartsz <= 0)
688fef88015Sbeck 			errx(1, "No body in reply from %s", host);
689471c6e53Sbeck 
69006396e18Sbeck 		if (hget->code != 200)
69106396e18Sbeck 			errx(1, "http reply code %d from %s", hget->code, host);
69206396e18Sbeck 
693471c6e53Sbeck 		/*
694471c6e53Sbeck 		 * Validate the OCSP response we got back
695471c6e53Sbeck 		 */
696471c6e53Sbeck 		OPENSSL_add_all_algorithms_noconf();
697471c6e53Sbeck 		if (!validate_response(hget->bodypart, hget->bodypartsz,
698471c6e53Sbeck 			request, castore, host, certfile))
699fef88015Sbeck 			exit(1);
700d4d79c4cSvisa 		instaple = hget->bodypart;
701d4d79c4cSvisa 		instaplesz = hget->bodypartsz;
702a3a695edSbeck 	} else {
703a3a695edSbeck 		size_t nr = 0;
704a3a695edSbeck 		instaplesz = 0;
705a3a695edSbeck 
706a3a695edSbeck 		/*
707a3a695edSbeck 		 * Pledge minimally before fiddling with libcrypto init
708a3a695edSbeck 		 */
709c322fdddStb 		if (cadir == NULL) {
710a3a695edSbeck 			if (pledge("stdio", NULL) == -1)
711a3a695edSbeck 				err(1, "pledge");
712c322fdddStb 		} else {
713c322fdddStb 			if (pledge("stdio rpath", NULL) == -1)
714c322fdddStb 				err(1, "pledge");
715c322fdddStb 		}
716a3a695edSbeck 
717a3a695edSbeck 		dspew("Using ocsp response saved in %s:\n", infile);
718a3a695edSbeck 
719a3a695edSbeck 		/* Use the existing OCSP response saved in infd */
720a3a695edSbeck 		instaple = calloc(OCSP_MAX_RESPONSE_SIZE, 1);
721a3a695edSbeck 		if (instaple) {
722a3a695edSbeck 			while ((nr = read(infd, instaple + instaplesz,
723a3a695edSbeck 			    OCSP_MAX_RESPONSE_SIZE - instaplesz)) != -1 &&
724a3a695edSbeck 			    nr != 0)
725a3a695edSbeck 				instaplesz += nr;
726a3a695edSbeck 		}
727a3a695edSbeck 		if (instaplesz == 0)
728a3a695edSbeck 			exit(1);
729a3a695edSbeck 		/*
730a3a695edSbeck 		 * Validate the OCSP staple we read in.
731a3a695edSbeck 		 */
732a3a695edSbeck 		OPENSSL_add_all_algorithms_noconf();
733a3a695edSbeck 		if (!validate_response(instaple, instaplesz,
734a3a695edSbeck 			request, castore, host, certfile))
735a3a695edSbeck 			exit(1);
736a3a695edSbeck 	}
737471c6e53Sbeck 
738471c6e53Sbeck 	/*
739471c6e53Sbeck 	 * If we have been given a place to save a staple,
740471c6e53Sbeck 	 * write out the DER format response to the staplefd
741471c6e53Sbeck 	 */
742471c6e53Sbeck 	if (staplefd >= 0) {
7430d0375ceStb 		while (ftruncate(staplefd, 0) < 0) {
7440d0375ceStb 			if (errno == EINVAL)
7450d0375ceStb 				break;
7460109553cSbcook 			if (errno != EINTR && errno != EAGAIN)
7470109553cSbcook 				err(1, "Write of OCSP response failed");
7480d0375ceStb 		}
749471c6e53Sbeck 		written = 0;
750d4d79c4cSvisa 		while (written < instaplesz) {
751d4d79c4cSvisa 			w = write(staplefd, instaple + written,
752d4d79c4cSvisa 			    instaplesz - written);
753471c6e53Sbeck 			if (w == -1) {
754471c6e53Sbeck 				if (errno != EINTR && errno != EAGAIN)
755471c6e53Sbeck 					err(1, "Write of OCSP response failed");
756471c6e53Sbeck 			} else
757471c6e53Sbeck 				written += w;
758471c6e53Sbeck 		}
759471c6e53Sbeck 		close(staplefd);
760471c6e53Sbeck 	}
761fef88015Sbeck 	exit(0);
762471c6e53Sbeck }
763