summaryrefslogtreecommitdiff
path: root/src/usr.sbin/ocspcheck
diff options
context:
space:
mode:
Diffstat (limited to 'src/usr.sbin/ocspcheck')
-rw-r--r--src/usr.sbin/ocspcheck/Makefile27
-rw-r--r--src/usr.sbin/ocspcheck/http.c777
-rw-r--r--src/usr.sbin/ocspcheck/http.h92
-rw-r--r--src/usr.sbin/ocspcheck/ocspcheck.8111
-rw-r--r--src/usr.sbin/ocspcheck/ocspcheck.c762
5 files changed, 0 insertions, 1769 deletions
diff --git a/src/usr.sbin/ocspcheck/Makefile b/src/usr.sbin/ocspcheck/Makefile
deleted file mode 100644
index e055c779ab..0000000000
--- a/src/usr.sbin/ocspcheck/Makefile
+++ /dev/null
@@ -1,27 +0,0 @@
1# $OpenBSD: Makefile,v 1.3 2017/05/07 04:18:47 jsg Exp $
2
3.include <bsd.own.mk>
4
5PROG= ocspcheck
6MAN= ocspcheck.8
7
8LDADD= -ltls -lssl -lcrypto
9DPADD= ${LIBTLS} ${LIBSSL} ${LIBCRYPTO}
10
11CFLAGS+= -Wall
12CFLAGS+= -Wformat
13CFLAGS+= -Wformat-security
14CFLAGS+= -Wimplicit
15CFLAGS+= -Wreturn-type
16CFLAGS+= -Wshadow
17CFLAGS+= -Wtrigraphs
18CFLAGS+= -Wuninitialized
19CFLAGS+= -Wunused
20 .if ${COMPILER_VERSION:L} == "gcc4"
21CFLAGS+= -Werror
22.endif
23CFLAGS+= -DLIBRESSL_INTERNAL
24
25SRCS= ocspcheck.c http.c
26
27.include <bsd.prog.mk>
diff --git a/src/usr.sbin/ocspcheck/http.c b/src/usr.sbin/ocspcheck/http.c
deleted file mode 100644
index 46b01f1ec4..0000000000
--- a/src/usr.sbin/ocspcheck/http.c
+++ /dev/null
@@ -1,777 +0,0 @@
1/* $Id: http.c,v 1.17 2023/04/19 12:58:16 jsg 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 <sys/types.h>
19#include <sys/socket.h>
20
21#include <arpa/inet.h>
22#include <netinet/in.h>
23
24#include <ctype.h>
25#include <err.h>
26#include <limits.h>
27#include <netdb.h>
28#include <stdio.h>
29#include <stdint.h>
30#include <stdlib.h>
31#include <string.h>
32#include <tls.h>
33#include <unistd.h>
34
35#include "http.h"
36
37/*
38 * A buffer for transferring HTTP/S data.
39 */
40struct httpxfer {
41 char *hbuf; /* header transfer buffer */
42 size_t hbufsz; /* header buffer size */
43 int headok; /* header has been parsed */
44 char *bbuf; /* body transfer buffer */
45 size_t bbufsz; /* body buffer size */
46 int bodyok; /* body has been parsed */
47 char *headbuf; /* lookaside buffer for headers */
48 struct httphead *head; /* parsed headers */
49 size_t headsz; /* number of headers */
50};
51
52/*
53 * An HTTP/S connection object.
54 */
55struct http {
56 int fd; /* connected socket */
57 short port; /* port number */
58 struct source src; /* endpoint (raw) host */
59 char *path; /* path to request */
60 char *host; /* name of endpoint host */
61 struct tls *ctx; /* if TLS */
62 writefp writer; /* write function */
63 readfp reader; /* read function */
64};
65
66struct tls_config *tlscfg;
67
68static ssize_t
69dosysread(char *buf, size_t sz, const struct http *http)
70{
71 ssize_t rc;
72
73 rc = read(http->fd, buf, sz);
74 if (rc == -1)
75 warn("%s: read", http->src.ip);
76 return rc;
77}
78
79static ssize_t
80dosyswrite(const void *buf, size_t sz, const struct http *http)
81{
82 ssize_t rc;
83
84 rc = write(http->fd, buf, sz);
85 if (rc == -1)
86 warn("%s: write", http->src.ip);
87 return rc;
88}
89
90static ssize_t
91dotlsread(char *buf, size_t sz, const struct http *http)
92{
93 ssize_t rc;
94
95 do {
96 rc = tls_read(http->ctx, buf, sz);
97 } while (rc == TLS_WANT_POLLIN || rc == TLS_WANT_POLLOUT);
98
99 if (rc == -1)
100 warnx("%s: tls_read: %s", http->src.ip,
101 tls_error(http->ctx));
102 return rc;
103}
104
105static ssize_t
106dotlswrite(const void *buf, size_t sz, const struct http *http)
107{
108 ssize_t rc;
109
110 do {
111 rc = tls_write(http->ctx, buf, sz);
112 } while (rc == TLS_WANT_POLLIN || rc == TLS_WANT_POLLOUT);
113
114 if (rc == -1)
115 warnx("%s: tls_write: %s", http->src.ip,
116 tls_error(http->ctx));
117 return rc;
118}
119
120int
121http_init(void)
122{
123 if (tlscfg != NULL)
124 return 0;
125
126 tlscfg = tls_config_new();
127 if (tlscfg == NULL) {
128 warn("tls_config_new");
129 goto err;
130 }
131
132 if (tls_config_set_ca_file(tlscfg, tls_default_ca_cert_file()) == -1) {
133 warn("tls_config_set_ca_file: %s", tls_config_error(tlscfg));
134 goto err;
135 }
136
137 return 0;
138
139 err:
140 tls_config_free(tlscfg);
141 tlscfg = NULL;
142
143 return -1;
144}
145
146static ssize_t
147http_read(char *buf, size_t sz, const struct http *http)
148{
149 ssize_t ssz, xfer;
150
151 xfer = 0;
152 do {
153 if ((ssz = http->reader(buf, sz, http)) < 0)
154 return -1;
155 if (ssz == 0)
156 break;
157 xfer += ssz;
158 sz -= ssz;
159 buf += ssz;
160 } while (ssz > 0 && sz > 0);
161
162 return xfer;
163}
164
165static int
166http_write(const char *buf, size_t sz, const struct http *http)
167{
168 ssize_t ssz, xfer;
169
170 xfer = sz;
171 while (sz > 0) {
172 if ((ssz = http->writer(buf, sz, http)) < 0)
173 return -1;
174 sz -= ssz;
175 buf += (size_t)ssz;
176 }
177 return xfer;
178}
179
180void
181http_disconnect(struct http *http)
182{
183 int rc;
184
185 if (http->ctx != NULL) {
186 /* TLS connection. */
187 do {
188 rc = tls_close(http->ctx);
189 } while (rc == TLS_WANT_POLLIN || rc == TLS_WANT_POLLOUT);
190
191 if (rc < 0)
192 warnx("%s: tls_close: %s", http->src.ip,
193 tls_error(http->ctx));
194
195 tls_free(http->ctx);
196 }
197 if (http->fd != -1) {
198 if (close(http->fd) == -1)
199 warn("%s: close", http->src.ip);
200 }
201
202 http->fd = -1;
203 http->ctx = NULL;
204}
205
206void
207http_free(struct http *http)
208{
209
210 if (http == NULL)
211 return;
212 http_disconnect(http);
213 free(http->host);
214 free(http->path);
215 free(http->src.ip);
216 free(http);
217}
218
219struct http *
220http_alloc(const struct source *addrs, size_t addrsz,
221 const char *host, short port, const char *path)
222{
223 struct sockaddr_storage ss;
224 int family, fd, c;
225 socklen_t len;
226 size_t cur, i = 0;
227 struct http *http;
228
229 /* Do this while we still have addresses to connect. */
230again:
231 if (i == addrsz)
232 return NULL;
233 cur = i++;
234
235 /* Convert to PF_INET or PF_INET6 address from string. */
236
237 memset(&ss, 0, sizeof(struct sockaddr_storage));
238
239 if (addrs[cur].family == 4) {
240 family = PF_INET;
241 ((struct sockaddr_in *)&ss)->sin_family = AF_INET;
242 ((struct sockaddr_in *)&ss)->sin_port = htons(port);
243 c = inet_pton(AF_INET, addrs[cur].ip,
244 &((struct sockaddr_in *)&ss)->sin_addr);
245 len = sizeof(struct sockaddr_in);
246 } else if (addrs[cur].family == 6) {
247 family = PF_INET6;
248 ((struct sockaddr_in6 *)&ss)->sin6_family = AF_INET6;
249 ((struct sockaddr_in6 *)&ss)->sin6_port = htons(port);
250 c = inet_pton(AF_INET6, addrs[cur].ip,
251 &((struct sockaddr_in6 *)&ss)->sin6_addr);
252 len = sizeof(struct sockaddr_in6);
253 } else {
254 warnx("%s: unknown family", addrs[cur].ip);
255 goto again;
256 }
257
258 if (c < 0) {
259 warn("%s: inet_ntop", addrs[cur].ip);
260 goto again;
261 } else if (c == 0) {
262 warnx("%s: inet_ntop", addrs[cur].ip);
263 goto again;
264 }
265
266 /* Create socket and connect. */
267
268 fd = socket(family, SOCK_STREAM, 0);
269 if (fd == -1) {
270 warn("%s: socket", addrs[cur].ip);
271 goto again;
272 } else if (connect(fd, (struct sockaddr *)&ss, len) == -1) {
273 warn("%s: connect", addrs[cur].ip);
274 close(fd);
275 goto again;
276 }
277
278 /* Allocate the communicator. */
279
280 http = calloc(1, sizeof(struct http));
281 if (http == NULL) {
282 warn("calloc");
283 close(fd);
284 return NULL;
285 }
286 http->fd = fd;
287 http->port = port;
288 http->src.family = addrs[cur].family;
289 http->src.ip = strdup(addrs[cur].ip);
290 http->host = strdup(host);
291 http->path = strdup(path);
292 if (http->src.ip == NULL || http->host == NULL || http->path == NULL) {
293 warn("strdup");
294 goto err;
295 }
296
297 /* If necessary, do our TLS setup. */
298
299 if (port != 443) {
300 http->writer = dosyswrite;
301 http->reader = dosysread;
302 return http;
303 }
304
305 http->writer = dotlswrite;
306 http->reader = dotlsread;
307
308 if ((http->ctx = tls_client()) == NULL) {
309 warn("tls_client");
310 goto err;
311 } else if (tls_configure(http->ctx, tlscfg) == -1) {
312 warnx("%s: tls_configure: %s",
313 http->src.ip, tls_error(http->ctx));
314 goto err;
315 }
316
317 if (tls_connect_socket(http->ctx, http->fd, http->host) != 0) {
318 warnx("%s: tls_connect_socket: %s, %s", http->src.ip,
319 http->host, tls_error(http->ctx));
320 goto err;
321 }
322
323 return http;
324err:
325 http_free(http);
326 return NULL;
327}
328
329struct httpxfer *
330http_open(const struct http *http, const void *p, size_t psz)
331{
332 char *req;
333 int c;
334 struct httpxfer *trans;
335
336 if (p == NULL) {
337 c = asprintf(&req,
338 "GET %s HTTP/1.0\r\n"
339 "Host: %s\r\n"
340 "\r\n",
341 http->path, http->host);
342 } else {
343 c = asprintf(&req,
344 "POST %s HTTP/1.0\r\n"
345 "Host: %s\r\n"
346 "Content-Type: application/ocsp-request\r\n"
347 "Content-Length: %zu\r\n"
348 "\r\n",
349 http->path, http->host, psz);
350 }
351 if (c == -1) {
352 warn("asprintf");
353 return NULL;
354 } else if (!http_write(req, c, http)) {
355 free(req);
356 return NULL;
357 } else if (p != NULL && !http_write(p, psz, http)) {
358 free(req);
359 return NULL;
360 }
361
362 free(req);
363
364 trans = calloc(1, sizeof(struct httpxfer));
365 if (trans == NULL)
366 warn("calloc");
367 return trans;
368}
369
370void
371http_close(struct httpxfer *x)
372{
373
374 if (x == NULL)
375 return;
376 free(x->hbuf);
377 free(x->bbuf);
378 free(x->headbuf);
379 free(x->head);
380 free(x);
381}
382
383/*
384 * Read the HTTP body from the wire.
385 * If invoked multiple times, this will return the same pointer with the
386 * same data (or NULL, if the original invocation returned NULL).
387 * Returns NULL if read or allocation errors occur.
388 * You must not free the returned pointer.
389 */
390char *
391http_body_read(const struct http *http, struct httpxfer *trans, size_t *sz)
392{
393 char buf[BUFSIZ];
394 ssize_t ssz;
395 void *pp;
396 size_t szp;
397
398 if (sz == NULL)
399 sz = &szp;
400
401 /* Have we already parsed this? */
402
403 if (trans->bodyok > 0) {
404 *sz = trans->bbufsz;
405 return trans->bbuf;
406 } else if (trans->bodyok < 0)
407 return NULL;
408
409 *sz = 0;
410 trans->bodyok = -1;
411
412 do {
413 /* If less than sizeof(buf), at EOF. */
414 if ((ssz = http_read(buf, sizeof(buf), http)) < 0)
415 return NULL;
416 else if (ssz == 0)
417 break;
418
419 pp = recallocarray(trans->bbuf,
420 trans->bbufsz, trans->bbufsz + ssz, 1);
421 if (pp == NULL) {
422 warn("recallocarray");
423 return NULL;
424 }
425 trans->bbuf = pp;
426 memcpy(trans->bbuf + trans->bbufsz, buf, ssz);
427 trans->bbufsz += ssz;
428 } while (ssz == sizeof(buf));
429
430 trans->bodyok = 1;
431 *sz = trans->bbufsz;
432 return trans->bbuf;
433}
434
435struct httphead *
436http_head_get(const char *v, struct httphead *h, size_t hsz)
437{
438 size_t i;
439
440 for (i = 0; i < hsz; i++) {
441 if (strcmp(h[i].key, v))
442 continue;
443 return &h[i];
444 }
445 return NULL;
446}
447
448/*
449 * Look through the headers and determine our HTTP code.
450 * This will return -1 on failure, otherwise the code.
451 */
452int
453http_head_status(const struct http *http, struct httphead *h, size_t sz)
454{
455 int rc;
456 unsigned int code;
457 struct httphead *st;
458
459 if ((st = http_head_get("Status", h, sz)) == NULL) {
460 warnx("%s: no status header", http->src.ip);
461 return -1;
462 }
463
464 rc = sscanf(st->val, "%*s %u %*s", &code);
465 if (rc < 0) {
466 warn("sscanf");
467 return -1;
468 } else if (rc != 1) {
469 warnx("%s: cannot convert status header", http->src.ip);
470 return -1;
471 }
472 return code;
473}
474
475/*
476 * Parse headers from the transfer.
477 * Malformed headers are skipped.
478 * A special "Status" header is added for the HTTP status line.
479 * This can only happen once http_head_read has been called with
480 * success.
481 * This can be invoked multiple times: it will only parse the headers
482 * once and after that it will just return the cache.
483 * You must not free the returned pointer.
484 * If the original header parse failed, or if memory allocation fails
485 * internally, this returns NULL.
486 */
487struct httphead *
488http_head_parse(const struct http *http, struct httpxfer *trans, size_t *sz)
489{
490 size_t hsz, szp;
491 struct httphead *h;
492 char *cp, *ep, *ccp, *buf;
493
494 if (sz == NULL)
495 sz = &szp;
496
497 /*
498 * If we've already parsed the headers, return the
499 * previously-parsed buffer now.
500 * If we have errors on the stream, return NULL now.
501 */
502
503 if (trans->head != NULL) {
504 *sz = trans->headsz;
505 return trans->head;
506 } else if (trans->headok <= 0)
507 return NULL;
508
509 if ((buf = strdup(trans->hbuf)) == NULL) {
510 warn("strdup");
511 return NULL;
512 }
513 hsz = 0;
514 cp = buf;
515
516 do {
517 if ((cp = strstr(cp, "\r\n")) != NULL)
518 cp += 2;
519 hsz++;
520 } while (cp != NULL);
521
522 /*
523 * Allocate headers, then step through the data buffer, parsing
524 * out headers as we have them.
525 * We know at this point that the buffer is NUL-terminated in
526 * the usual way.
527 */
528
529 h = calloc(hsz, sizeof(struct httphead));
530 if (h == NULL) {
531 warn("calloc");
532 free(buf);
533 return NULL;
534 }
535
536 *sz = hsz;
537 hsz = 0;
538 cp = buf;
539
540 do {
541 if ((ep = strstr(cp, "\r\n")) != NULL) {
542 *ep = '\0';
543 ep += 2;
544 }
545 if (hsz == 0) {
546 h[hsz].key = "Status";
547 h[hsz++].val = cp;
548 continue;
549 }
550
551 /* Skip bad headers. */
552 if ((ccp = strchr(cp, ':')) == NULL) {
553 warnx("%s: header without separator", http->src.ip);
554 continue;
555 }
556
557 *ccp++ = '\0';
558 while (isspace((unsigned char)*ccp))
559 ccp++;
560 h[hsz].key = cp;
561 h[hsz++].val = ccp;
562 } while ((cp = ep) != NULL);
563
564 trans->headbuf = buf;
565 trans->head = h;
566 trans->headsz = hsz;
567 return h;
568}
569
570/*
571 * Read the HTTP headers from the wire.
572 * If invoked multiple times, this will return the same pointer with the
573 * same data (or NULL, if the original invocation returned NULL).
574 * Returns NULL if read or allocation errors occur.
575 * You must not free the returned pointer.
576 */
577char *
578http_head_read(const struct http *http, struct httpxfer *trans, size_t *sz)
579{
580 char buf[BUFSIZ];
581 ssize_t ssz;
582 char *ep;
583 void *pp;
584 size_t szp;
585
586 if (sz == NULL)
587 sz = &szp;
588
589 /* Have we already parsed this? */
590
591 if (trans->headok > 0) {
592 *sz = trans->hbufsz;
593 return trans->hbuf;
594 } else if (trans->headok < 0)
595 return NULL;
596
597 *sz = 0;
598 ep = NULL;
599 trans->headok = -1;
600
601 /*
602 * Begin by reading by BUFSIZ blocks until we reach the header
603 * termination marker (two CRLFs).
604 * We might read into our body, but that's ok: we'll copy out
605 * the body parts into our body buffer afterward.
606 */
607
608 do {
609 /* If less than sizeof(buf), at EOF. */
610 if ((ssz = http_read(buf, sizeof(buf), http)) < 0)
611 return NULL;
612 else if (ssz == 0)
613 break;
614 pp = realloc(trans->hbuf, trans->hbufsz + ssz);
615 if (pp == NULL) {
616 warn("realloc");
617 return NULL;
618 }
619 trans->hbuf = pp;
620 memcpy(trans->hbuf + trans->hbufsz, buf, ssz);
621 trans->hbufsz += ssz;
622 /* Search for end of headers marker. */
623 ep = memmem(trans->hbuf, trans->hbufsz, "\r\n\r\n", 4);
624 } while (ep == NULL && ssz == sizeof(buf));
625
626 if (ep == NULL) {
627 warnx("%s: partial transfer", http->src.ip);
628 return NULL;
629 }
630 *ep = '\0';
631
632 /*
633 * The header data is invalid if it has any binary characters in
634 * it: check that now.
635 * This is important because we want to guarantee that all
636 * header keys and pairs are properly NUL-terminated.
637 */
638
639 if (strlen(trans->hbuf) != (uintptr_t)(ep - trans->hbuf)) {
640 warnx("%s: binary data in header", http->src.ip);
641 return NULL;
642 }
643
644 /*
645 * Copy remaining buffer into body buffer.
646 */
647
648 ep += 4;
649 trans->bbufsz = (trans->hbuf + trans->hbufsz) - ep;
650 trans->bbuf = malloc(trans->bbufsz);
651 if (trans->bbuf == NULL) {
652 warn("malloc");
653 return NULL;
654 }
655 memcpy(trans->bbuf, ep, trans->bbufsz);
656
657 trans->headok = 1;
658 *sz = trans->hbufsz;
659 return trans->hbuf;
660}
661
662void
663http_get_free(struct httpget *g)
664{
665
666 if (g == NULL)
667 return;
668 http_close(g->xfer);
669 http_free(g->http);
670 free(g);
671}
672
673struct httpget *
674http_get(const struct source *addrs, size_t addrsz, const char *domain,
675 short port, const char *path, const void *post, size_t postsz)
676{
677 struct http *h;
678 struct httpxfer *x;
679 struct httpget *g;
680 struct httphead *head;
681 size_t headsz, bodsz, headrsz;
682 int code;
683 char *bod, *headr;
684
685 h = http_alloc(addrs, addrsz, domain, port, path);
686 if (h == NULL)
687 return NULL;
688
689 if ((x = http_open(h, post, postsz)) == NULL) {
690 http_free(h);
691 return NULL;
692 } else if ((headr = http_head_read(h, x, &headrsz)) == NULL) {
693 http_close(x);
694 http_free(h);
695 return NULL;
696 } else if ((bod = http_body_read(h, x, &bodsz)) == NULL) {
697 http_close(x);
698 http_free(h);
699 return NULL;
700 }
701
702 http_disconnect(h);
703
704 if ((head = http_head_parse(h, x, &headsz)) == NULL) {
705 http_close(x);
706 http_free(h);
707 return NULL;
708 } else if ((code = http_head_status(h, head, headsz)) < 0) {
709 http_close(x);
710 http_free(h);
711 return NULL;
712 }
713
714 if ((g = calloc(1, sizeof(struct httpget))) == NULL) {
715 warn("calloc");
716 http_close(x);
717 http_free(h);
718 return NULL;
719 }
720
721 g->headpart = headr;
722 g->headpartsz = headrsz;
723 g->bodypart = bod;
724 g->bodypartsz = bodsz;
725 g->head = head;
726 g->headsz = headsz;
727 g->code = code;
728 g->xfer = x;
729 g->http = h;
730 return g;
731}
732
733#if 0
734int
735main(void)
736{
737 struct httpget *g;
738 struct httphead *httph;
739 size_t i, httphsz;
740 struct source addrs[2];
741 size_t addrsz;
742
743#if 0
744 addrs[0].ip = "127.0.0.1";
745 addrs[0].family = 4;
746 addrsz = 1;
747#else
748 addrs[0].ip = "2a00:1450:400a:806::2004";
749 addrs[0].family = 6;
750 addrs[1].ip = "193.135.3.123";
751 addrs[1].family = 4;
752 addrsz = 2;
753#endif
754
755 if (http_init() == -1)
756 errx(EXIT_FAILURE, "http_init");
757
758#if 0
759 g = http_get(addrs, addrsz, "localhost", 80, "/index.html");
760#else
761 g = http_get(addrs, addrsz, "www.google.ch", 80, "/index.html",
762 NULL, 0);
763#endif
764
765 if (g == NULL)
766 errx(EXIT_FAILURE, "http_get");
767
768 httph = http_head_parse(g->http, g->xfer, &httphsz);
769 warnx("code: %d", g->code);
770
771 for (i = 0; i < httphsz; i++)
772 warnx("head: [%s]=[%s]", httph[i].key, httph[i].val);
773
774 http_get_free(g);
775 return (EXIT_SUCCESS);
776}
777#endif
diff --git a/src/usr.sbin/ocspcheck/http.h b/src/usr.sbin/ocspcheck/http.h
deleted file mode 100644
index 17541b4363..0000000000
--- a/src/usr.sbin/ocspcheck/http.h
+++ /dev/null
@@ -1,92 +0,0 @@
1/* $Id: http.h,v 1.3 2017/01/25 13:52:53 inoguchi 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#ifndef HTTP_H
18#define HTTP_H
19
20struct source {
21 int family; /* 4 (PF_INET) or 6 (PF_INET6) */
22 char *ip; /* IPV4 or IPV6 address */
23};
24
25struct http;
26
27/*
28 * Write and read callbacks to allow HTTP and HTTPS.
29 * Both of these return the number of bytes read (or written) or -1 on
30 * failure.
31 * 0 bytes read means that the connection has closed.
32 */
33typedef ssize_t (*writefp)(const void *, size_t, const struct http *);
34typedef ssize_t (*readfp)(char *, size_t, const struct http *);
35
36/*
37 * HTTP/S header pair.
38 * There's also a cooked-up pair, "Status", with the status code.
39 * Both strings are NUL-terminated.
40 */
41struct httphead {
42 const char *key;
43 const char *val;
44};
45
46/*
47 * Grab all information from a transfer.
48 * DO NOT free any parts of this, and editing the parts (e.g., changing
49 * the underlying strings) will persist; so in short, don't.
50 * All of these values will be set upon http_get() success.
51 */
52struct httpget {
53 struct httpxfer *xfer; /* underlying transfer */
54 struct http *http; /* underlying connection */
55 int code; /* return code */
56 struct httphead *head; /* headers */
57 size_t headsz; /* number of headers */
58 char *headpart; /* header buffer */
59 size_t headpartsz; /* size of headpart */
60 char *bodypart; /* body buffer */
61 size_t bodypartsz; /* size of bodypart */
62};
63
64int http_init(void);
65
66/* Convenience functions. */
67struct httpget *http_get(const struct source *, size_t,
68 const char *, short, const char *,
69 const void *, size_t);
70void http_get_free(struct httpget *);
71
72/* Allocation and release. */
73struct http *http_alloc(const struct source *, size_t,
74 const char *, short, const char *);
75void http_free(struct http *);
76struct httpxfer *http_open(const struct http *, const void *, size_t);
77void http_close(struct httpxfer *);
78void http_disconnect(struct http *);
79
80/* Access. */
81char *http_head_read(const struct http *,
82 struct httpxfer *, size_t *);
83struct httphead *http_head_parse(const struct http *,
84 struct httpxfer *, size_t *);
85char *http_body_read(const struct http *,
86 struct httpxfer *, size_t *);
87int http_head_status(const struct http *,
88 struct httphead *, size_t);
89struct httphead *http_head_get(const char *,
90 struct httphead *, size_t);
91
92#endif /* HTTP_H */
diff --git a/src/usr.sbin/ocspcheck/ocspcheck.8 b/src/usr.sbin/ocspcheck/ocspcheck.8
deleted file mode 100644
index 19f55fb838..0000000000
--- a/src/usr.sbin/ocspcheck/ocspcheck.8
+++ /dev/null
@@ -1,111 +0,0 @@
1.\" $OpenBSD: ocspcheck.8,v 1.9 2017/11/29 21:15:45 jmc Exp $
2.\"
3.\" Copyright (c) 2017 Bob Beck <beck@openbsd.org>
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 AUTHOR DISCLAIMS ALL WARRANTIES
10.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.Dd $Mdocdate: November 29 2017 $
18.Dt OCSPCHECK 8
19.Os
20.Sh NAME
21.Nm ocspcheck
22.Nd check a certificate for validity against its OCSP responder
23.Sh SYNOPSIS
24.Nm
25.Op Fl Nv
26.Op Fl C Ar CAfile
27.Op Fl i Ar staplefile
28.Op Fl o Ar staplefile
29.Ar file
30.Sh DESCRIPTION
31The
32.Nm
33utility validates a PEM format certificate against the OCSP responder
34encoded in the certificate specified by the
35.Ar file
36argument.
37Normally it should be used for checking server certificates
38and maintaining saved OCSP responses to be used for OCSP stapling.
39.Pp
40The options are as follows:
41.Bl -tag -width Ds
42.It Fl C Ar CAfile
43Specify a PEM format root certificate bundle to use for the validation of
44requests.
45By default no certificates are used beyond those in the
46certificate chain provided by the
47.Ar file
48argument.
49.It Fl i Ar staplefile
50Specify an input filename from which a DER-encoded OCSP response
51will be read instead of fetching it from the OCSP server.
52A filename
53of
54.Sq -
55will read the response from standard input.
56.It Fl N
57Do not use a nonce value in the OCSP request, or validate that the
58nonce was returned in the OCSP response.
59By default a nonce is always used and validated when retrieving
60a response from an OCSP server.
61The use of this flag is a security risk as it will allow OCSP
62responses to be replayed.
63It should not be used unless the OCSP server does not support the
64use of OCSP nonces.
65.It Fl o Ar staplefile
66Specify an output filename where the DER encoded response from the
67OCSP server will be written, if the OCSP response validates.
68A filename
69of
70.Sq -
71will write the response to standard output.
72By default the response is not saved.
73.It Fl v
74Increase verbosity.
75This flag may be specified multiple times to get more verbose output.
76The default behaviour is to be silent unless something goes wrong.
77.El
78.Sh EXIT STATUS
79The
80.Nm
81utility exits 0 if the OCSP response validates for the certificate in
82.Ar file
83and all output is successfully written out.
84.Nm
85exits >0 if an error occurs or the OCSP response fails to validate.
86.Sh SEE ALSO
87.Xr nc 1 ,
88.Xr tls_config_set_ocsp_staple_file 3 ,
89.Xr tls_config_set_ocsp_staple_mem 3 ,
90.Xr httpd 8
91.Sh HISTORY
92The
93.Nm
94utility first appeared in
95.Ox 6.1 .
96.Sh AUTHORS
97.Nm
98was written by
99.An Bob Beck .
100.Sh CAVEATS
101While
102.Nm
103could possibly be used in scripts to query responders for server
104certificates seen on client connections, this is almost always a bad
105idea.
106God kills a kitten every time you make an OCSP query from the
107client side of a TLS connection.
108.Sh BUGS
109.Nm
110will create the output file if it does not exist.
111On failure a newly created output file will not be removed.
diff --git a/src/usr.sbin/ocspcheck/ocspcheck.c b/src/usr.sbin/ocspcheck/ocspcheck.c
deleted file mode 100644
index ae4b8364f6..0000000000
--- a/src/usr.sbin/ocspcheck/ocspcheck.c
+++ /dev/null
@@ -1,762 +0,0 @@
1/* $OpenBSD: ocspcheck.c,v 1.34 2024/12/04 07:58:51 tb Exp $ */
2
3/*
4 * Copyright (c) 2017,2020 Bob Beck <beck@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <arpa/inet.h>
20#include <netinet/in.h>
21#include <sys/socket.h>
22#include <sys/stat.h>
23
24#include <err.h>
25#include <fcntl.h>
26#include <limits.h>
27#include <netdb.h>
28#include <poll.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <time.h>
33#include <unistd.h>
34
35#include <openssl/err.h>
36#include <openssl/ocsp.h>
37#include <openssl/posix_time.h>
38#include <openssl/ssl.h>
39
40#include "http.h"
41
42#define MAXAGE_SEC (14*24*60*60)
43#define JITTER_SEC (60)
44#define OCSP_MAX_RESPONSE_SIZE (20480)
45
46typedef struct ocsp_request {
47 STACK_OF(X509) *fullchain;
48 OCSP_REQUEST *req;
49 char *url;
50 unsigned char *data;
51 size_t size;
52 int nonce;
53} ocsp_request;
54
55int verbose;
56#define vspew(fmt, ...) \
57 do { if (verbose >= 1) fprintf(stderr, fmt, __VA_ARGS__); } while (0)
58#define dspew(fmt, ...) \
59 do { if (verbose >= 2) fprintf(stderr, fmt, __VA_ARGS__); } while (0)
60
61#define MAX_SERVERS_DNS 8
62
63struct addr {
64 int family; /* 4 for PF_INET, 6 for PF_INET6 */
65 char ip[INET6_ADDRSTRLEN];
66};
67
68static ssize_t
69host_dns(const char *s, struct addr vec[MAX_SERVERS_DNS])
70{
71 struct addrinfo hints, *res0, *res;
72 int error;
73 ssize_t vecsz;
74 struct sockaddr *sa;
75
76 memset(&hints, 0, sizeof(hints));
77 hints.ai_family = PF_UNSPEC;
78 hints.ai_socktype = SOCK_DGRAM; /* DUMMY */
79
80 error = getaddrinfo(s, NULL, &hints, &res0);
81
82 if (error == EAI_AGAIN ||
83#ifdef EAI_NODATA
84 error == EAI_NODATA ||
85#endif
86 error == EAI_NONAME)
87 return 0;
88
89 if (error) {
90 warnx("%s: parse error: %s", s, gai_strerror(error));
91 return -1;
92 }
93
94 for (vecsz = 0, res = res0;
95 res != NULL && vecsz < MAX_SERVERS_DNS;
96 res = res->ai_next) {
97 if (res->ai_family != AF_INET &&
98 res->ai_family != AF_INET6)
99 continue;
100
101 sa = res->ai_addr;
102
103 if (res->ai_family == AF_INET) {
104 vec[vecsz].family = 4;
105 inet_ntop(AF_INET,
106 &(((struct sockaddr_in *)sa)->sin_addr),
107 vec[vecsz].ip, INET6_ADDRSTRLEN);
108 } else {
109 vec[vecsz].family = 6;
110 inet_ntop(AF_INET6,
111 &(((struct sockaddr_in6 *)sa)->sin6_addr),
112 vec[vecsz].ip, INET6_ADDRSTRLEN);
113 }
114
115 dspew("DNS returns %s for %s\n", vec[vecsz].ip, s);
116 vecsz++;
117 }
118
119 freeaddrinfo(res0);
120 return vecsz;
121}
122
123/*
124 * Extract the domain and port from a URL.
125 * The url must be formatted as schema://address[/stuff].
126 * This returns NULL on failure.
127 */
128static char *
129url2host(const char *host, short *port, char **path)
130{
131 char *url, *ep;
132
133 /* We only understand HTTP and HTTPS. */
134
135 if (strncmp(host, "https://", 8) == 0) {
136 *port = 443;
137 if ((url = strdup(host + 8)) == NULL) {
138 warn("strdup");
139 return (NULL);
140 }
141 } else if (strncmp(host, "http://", 7) == 0) {
142 *port = 80;
143 if ((url = strdup(host + 7)) == NULL) {
144 warn("strdup");
145 return (NULL);
146 }
147 } else {
148 warnx("%s: unknown schema", host);
149 return (NULL);
150 }
151
152 /* Terminate path part. */
153
154 if ((ep = strchr(url, '/')) != NULL) {
155 *path = strdup(ep);
156 *ep = '\0';
157 } else
158 *path = strdup("/");
159
160 if (*path == NULL) {
161 warn("strdup");
162 free(url);
163 return (NULL);
164 }
165
166 /* Check to see if there is a port in the url */
167 if ((ep = strchr(url, ':')) != NULL) {
168 const char *errstr;
169 short pp;
170 pp = strtonum(ep + 1, 1, SHRT_MAX, &errstr);
171 if (errstr != NULL) {
172 warnx("error parsing port from '%s': %s", url, errstr);
173 free(url);
174 free(*path);
175 return NULL;
176 }
177 *port = pp;
178 *ep = '\0';
179 }
180
181 return (url);
182}
183
184static time_t
185parse_ocsp_time(ASN1_GENERALIZEDTIME *gt)
186{
187 struct tm tm;
188 time_t rv = -1;
189
190 if (gt == NULL)
191 return -1;
192 /* RFC 6960 specifies that all times in OCSP must be GENERALIZEDTIME */
193 if (!ASN1_GENERALIZEDTIME_check(gt))
194 return -1;
195 if (!ASN1_TIME_to_tm(gt, &tm))
196 return -1;
197 if (!OPENSSL_timegm(&tm, &rv))
198 return -1;
199 return rv;
200}
201
202static X509_STORE *
203read_cacerts(const char *file, const char *dir)
204{
205 X509_STORE *store = NULL;
206 X509_LOOKUP *lookup;
207
208 if (file == NULL && dir == NULL) {
209 warnx("No CA certs to load");
210 goto end;
211 }
212 if ((store = X509_STORE_new()) == NULL) {
213 warnx("Malloc failed");
214 goto end;
215 }
216 if (file != NULL) {
217 if ((lookup = X509_STORE_add_lookup(store,
218 X509_LOOKUP_file())) == NULL) {
219 warnx("Unable to load CA cert file");
220 goto end;
221 }
222 if (!X509_LOOKUP_load_file(lookup, file, X509_FILETYPE_PEM)) {
223 warnx("Unable to load CA certs from file %s", file);
224 goto end;
225 }
226 }
227 if (dir != NULL) {
228 if ((lookup = X509_STORE_add_lookup(store,
229 X509_LOOKUP_hash_dir())) == NULL) {
230 warnx("Unable to load CA cert directory");
231 goto end;
232 }
233 if (!X509_LOOKUP_add_dir(lookup, dir, X509_FILETYPE_PEM)) {
234 warnx("Unable to load CA certs from directory %s", dir);
235 goto end;
236 }
237 }
238 return store;
239
240 end:
241 X509_STORE_free(store);
242 return NULL;
243}
244
245static STACK_OF(X509) *
246read_fullchain(const char *file, int *count)
247{
248 int i;
249 BIO *bio;
250 STACK_OF(X509_INFO) *xis = NULL;
251 X509_INFO *xi;
252 STACK_OF(X509) *rv = NULL;
253
254 *count = 0;
255
256 if ((bio = BIO_new_file(file, "r")) == NULL) {
257 warn("Unable to read a certificate from %s", file);
258 goto end;
259 }
260 if ((xis = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL)) == NULL) {
261 warnx("Unable to read PEM format from %s", file);
262 goto end;
263 }
264 if (sk_X509_INFO_num(xis) <= 0) {
265 warnx("No certificates in file %s", file);
266 goto end;
267 }
268 if ((rv = sk_X509_new_null()) == NULL) {
269 warnx("malloc failed");
270 goto end;
271 }
272
273 for (i = 0; i < sk_X509_INFO_num(xis); i++) {
274 xi = sk_X509_INFO_value(xis, i);
275 if (xi->x509 == NULL)
276 continue;
277 if (!sk_X509_push(rv, xi->x509)) {
278 warnx("unable to build x509 chain");
279 sk_X509_pop_free(rv, X509_free);
280 rv = NULL;
281 goto end;
282 }
283 xi->x509 = NULL;
284 (*count)++;
285 }
286 end:
287 BIO_free(bio);
288 sk_X509_INFO_pop_free(xis, X509_INFO_free);
289 return rv;
290}
291
292static inline X509 *
293cert_from_chain(STACK_OF(X509) *fullchain)
294{
295 return sk_X509_value(fullchain, 0);
296}
297
298static const X509 *
299issuer_from_chain(STACK_OF(X509) *fullchain)
300{
301 const X509 *cert;
302 X509_NAME *issuer_name;
303
304 cert = cert_from_chain(fullchain);
305 if ((issuer_name = X509_get_issuer_name(cert)) == NULL)
306 return NULL;
307
308 return X509_find_by_subject(fullchain, issuer_name);
309}
310
311static ocsp_request *
312ocsp_request_new_from_cert(const char *cadir, char *file, int nonce)
313{
314 X509 *cert;
315 int count = 0;
316 OCSP_CERTID *id = NULL;
317 ocsp_request *request = NULL;
318 const EVP_MD *cert_id_md = NULL;
319 const X509 *issuer;
320 STACK_OF(OPENSSL_STRING) *urls = NULL;
321
322 if ((request = calloc(1, sizeof(ocsp_request))) == NULL) {
323 warn("malloc");
324 goto err;
325 }
326
327 if ((request->req = OCSP_REQUEST_new()) == NULL)
328 goto err;
329
330 request->fullchain = read_fullchain(file, &count);
331 if (cadir == NULL) {
332 /* Drop rpath from pledge, we don't need to read anymore */
333 if (pledge("stdio inet dns", NULL) == -1)
334 err(1, "pledge");
335 }
336 if (request->fullchain == NULL) {
337 warnx("Unable to read cert chain from file %s", file);
338 goto err;
339 }
340 if (count <= 1) {
341 warnx("File %s does not contain a cert chain", file);
342 goto err;
343 }
344 if ((cert = cert_from_chain(request->fullchain)) == NULL) {
345 warnx("No certificate found in %s", file);
346 goto err;
347 }
348 if ((issuer = issuer_from_chain(request->fullchain)) == NULL) {
349 warnx("Unable to find issuer for cert in %s", file);
350 goto err;
351 }
352
353 urls = X509_get1_ocsp(cert);
354 if (urls == NULL || sk_OPENSSL_STRING_num(urls) <= 0) {
355 warnx("Certificate in %s contains no OCSP url", file);
356 goto err;
357 }
358 if ((request->url = strdup(sk_OPENSSL_STRING_value(urls, 0))) == NULL)
359 goto err;
360 X509_email_free(urls);
361 urls = NULL;
362
363 cert_id_md = EVP_sha1(); /* XXX. This sucks but OCSP is poopy */
364 if ((id = OCSP_cert_to_id(cert_id_md, cert, issuer)) == NULL) {
365 warnx("Unable to get certificate id from cert in %s", file);
366 goto err;
367 }
368 if (OCSP_request_add0_id(request->req, id) == NULL) {
369 warnx("Unable to add certificate id to request");
370 goto err;
371 }
372 id = NULL;
373
374 request->nonce = nonce;
375 if (request->nonce)
376 OCSP_request_add1_nonce(request->req, NULL, -1);
377
378 if ((request->size = i2d_OCSP_REQUEST(request->req,
379 &request->data)) <= 0) {
380 warnx("Unable to encode ocsp request");
381 goto err;
382 }
383 if (request->data == NULL) {
384 warnx("Unable to allocate memory");
385 goto err;
386 }
387 return request;
388
389 err:
390 if (request != NULL) {
391 sk_X509_pop_free(request->fullchain, X509_free);
392 free(request->url);
393 OCSP_REQUEST_free(request->req);
394 free(request->data);
395 }
396 X509_email_free(urls);
397 OCSP_CERTID_free(id);
398 free(request);
399 return NULL;
400}
401
402
403int
404validate_response(char *buf, size_t size, ocsp_request *request,
405 X509_STORE *store, char *host, char *file)
406{
407 ASN1_GENERALIZEDTIME *revtime = NULL, *thisupd = NULL, *nextupd = NULL;
408 const unsigned char **p = (const unsigned char **)&buf;
409 int status, cert_status = 0, crl_reason = 0;
410 time_t now, rev_t = -1, this_t, next_t;
411 OCSP_RESPONSE *resp = NULL;
412 OCSP_BASICRESP *bresp = NULL;
413 OCSP_CERTID *cid = NULL;
414 const X509 *cert, *issuer;
415 int ret = 0;
416
417 if ((cert = cert_from_chain(request->fullchain)) == NULL) {
418 warnx("No certificate found in %s", file);
419 goto err;
420 }
421 if ((issuer = issuer_from_chain(request->fullchain)) == NULL) {
422 warnx("Unable to find certificate issuer for cert in %s", file);
423 goto err;
424 }
425 if ((cid = OCSP_cert_to_id(NULL, cert, issuer)) == NULL) {
426 warnx("Unable to get issuer cert/CID in %s", file);
427 goto err;
428 }
429
430 if ((resp = d2i_OCSP_RESPONSE(NULL, p, size)) == NULL) {
431 warnx("OCSP response unserializable from host %s", host);
432 goto err;
433 }
434
435 if ((bresp = OCSP_response_get1_basic(resp)) == NULL) {
436 warnx("Failed to load OCSP response from %s", host);
437 goto err;
438 }
439
440 if (OCSP_basic_verify(bresp, request->fullchain, store,
441 OCSP_TRUSTOTHER) != 1) {
442 warnx("OCSP verify failed from %s", host);
443 goto err;
444 }
445 dspew("OCSP response signature validated from %s\n", host);
446
447 status = OCSP_response_status(resp);
448 if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
449 warnx("OCSP Failure: code %d (%s) from host %s",
450 status, OCSP_response_status_str(status), host);
451 goto err;
452 }
453 dspew("OCSP response status %d from host %s\n", status, host);
454
455 /* Check the nonce if we sent one */
456
457 if (request->nonce) {
458 if (OCSP_check_nonce(request->req, bresp) <= 0) {
459 warnx("No OCSP nonce, or mismatch, from host %s", host);
460 goto err;
461 }
462 }
463
464 if (OCSP_resp_find_status(bresp, cid, &cert_status, &crl_reason,
465 &revtime, &thisupd, &nextupd) != 1) {
466 warnx("OCSP verify failed: no result for cert");
467 goto err;
468 }
469
470 if (revtime && (rev_t = parse_ocsp_time(revtime)) == -1) {
471 warnx("Unable to parse revocation time in OCSP reply");
472 goto err;
473 }
474 /*
475 * Belt and suspenders, Treat it as revoked if there is either
476 * a revocation time, or status revoked.
477 */
478 if (rev_t != -1 || cert_status == V_OCSP_CERTSTATUS_REVOKED) {
479 warnx("Invalid OCSP reply: certificate is revoked");
480 if (rev_t != -1)
481 warnx("Certificate revoked at: %s", ctime(&rev_t));
482 goto err;
483 }
484 if ((this_t = parse_ocsp_time(thisupd)) == -1) {
485 warnx("unable to parse this update time in OCSP reply");
486 goto err;
487 }
488 if ((next_t = parse_ocsp_time(nextupd)) == -1) {
489 warnx("unable to parse next update time in OCSP reply");
490 goto err;
491 }
492
493 /* Don't allow this update to precede next update */
494 if (this_t >= next_t) {
495 warnx("Invalid OCSP reply: this update >= next update");
496 goto err;
497 }
498
499 now = time(NULL);
500 /*
501 * Check that this update is not more than JITTER seconds
502 * in the future.
503 */
504 if (this_t > now + JITTER_SEC) {
505 warnx("Invalid OCSP reply: this update is in the future at %s",
506 ctime(&this_t));
507 goto err;
508 }
509
510 /*
511 * Check that this update is not more than MAXSEC
512 * in the past.
513 */
514 if (this_t < now - MAXAGE_SEC) {
515 warnx("Invalid OCSP reply: this update is too old %s",
516 ctime(&this_t));
517 goto err;
518 }
519
520 /*
521 * Check that next update is still valid
522 */
523 if (next_t < now - JITTER_SEC) {
524 warnx("Invalid OCSP reply: reply has expired at %s",
525 ctime(&next_t));
526 goto err;
527 }
528
529 vspew("OCSP response validated from %s\n", host);
530 vspew(" This Update: %s", ctime(&this_t));
531 vspew(" Next Update: %s", ctime(&next_t));
532 ret = 1;
533 err:
534 OCSP_RESPONSE_free(resp);
535 OCSP_BASICRESP_free(bresp);
536 OCSP_CERTID_free(cid);
537 return ret;
538}
539
540static void
541usage(void)
542{
543 fprintf(stderr,
544 "usage: ocspcheck [-Nv] [-C CAfile] [-i staplefile] "
545 "[-o staplefile] file\n");
546 exit(1);
547}
548
549int
550main(int argc, char **argv)
551{
552 const char *cafile = NULL, *cadir = NULL;
553 char *host = NULL, *path = NULL, *certfile = NULL, *outfile = NULL,
554 *instaple = NULL, *infile = NULL;
555 struct addr addrs[MAX_SERVERS_DNS] = {{0}};
556 struct source sources[MAX_SERVERS_DNS];
557 int i, ch, staplefd = -1, infd = -1, nonce = 1;
558 ocsp_request *request = NULL;
559 size_t rescount, instaplesz = 0;
560 struct httpget *hget;
561 X509_STORE *castore;
562 ssize_t written, w;
563 short port;
564
565 while ((ch = getopt(argc, argv, "C:i:No:v")) != -1) {
566 switch (ch) {
567 case 'C':
568 cafile = optarg;
569 break;
570 case 'N':
571 nonce = 0;
572 break;
573 case 'o':
574 outfile = optarg;
575 break;
576 case 'i':
577 infile = optarg;
578 break;
579 case 'v':
580 verbose++;
581 break;
582 default:
583 usage();
584 }
585 }
586 argc -= optind;
587 argv += optind;
588
589 if (argc != 1 || (certfile = argv[0]) == NULL)
590 usage();
591
592 if (outfile != NULL) {
593 if (strcmp(outfile, "-") == 0)
594 staplefd = STDOUT_FILENO;
595 else
596 staplefd = open(outfile, O_WRONLY|O_CREAT,
597 S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH);
598 if (staplefd < 0)
599 err(1, "Unable to open output file %s", outfile);
600 }
601
602 if (infile != NULL) {
603 if (strcmp(infile, "-") == 0)
604 infd = STDIN_FILENO;
605 else
606 infd = open(infile, O_RDONLY);
607 if (infd < 0)
608 err(1, "Unable to open input file %s", infile);
609 nonce = 0; /* Can't validate a nonce on a saved reply */
610 }
611
612 if (cafile == NULL) {
613 if (access(X509_get_default_cert_file(), R_OK) == 0)
614 cafile = X509_get_default_cert_file();
615 if (access(X509_get_default_cert_dir(), F_OK) == 0)
616 cadir = X509_get_default_cert_dir();
617 }
618
619 if (cafile != NULL) {
620 if (unveil(cafile, "r") == -1)
621 err(1, "unveil %s", cafile);
622 }
623 if (cadir != NULL) {
624 if (unveil(cadir, "r") == -1)
625 err(1, "unveil %s", cadir);
626 }
627 if (unveil(certfile, "r") == -1)
628 err(1, "unveil %s", certfile);
629
630 if (pledge("stdio inet rpath dns", NULL) == -1)
631 err(1, "pledge");
632
633 /*
634 * Load our certificate and keystore, and build up an
635 * OCSP request based on the full certificate chain
636 * we have been given to check.
637 */
638 if ((castore = read_cacerts(cafile, cadir)) == NULL)
639 exit(1);
640 if ((request = ocsp_request_new_from_cert(cadir, certfile, nonce))
641 == NULL)
642 exit(1);
643
644 dspew("Built an %zu byte ocsp request\n", request->size);
645
646 if ((host = url2host(request->url, &port, &path)) == NULL)
647 errx(1, "Invalid OCSP url %s from %s", request->url,
648 certfile);
649
650 if (infd == -1) {
651 /* Get a new OCSP response from the indicated server */
652
653 vspew("Using %s to host %s, port %d, path %s\n",
654 port == 443 ? "https" : "http", host, port, path);
655
656 rescount = host_dns(host, addrs);
657 for (i = 0; i < rescount; i++) {
658 sources[i].ip = addrs[i].ip;
659 sources[i].family = addrs[i].family;
660 }
661
662 /*
663 * Do an HTTP post to send our request to the OCSP
664 * server, and hopefully get an answer back
665 */
666 hget = http_get(sources, rescount, host, port, path,
667 request->data, request->size);
668 if (hget == NULL)
669 errx(1, "http_get");
670 /*
671 * Pledge minimally before fiddling with libcrypto init
672 * routines and parsing untrusted input from someone's OCSP
673 * server.
674 */
675 if (cadir == NULL) {
676 if (pledge("stdio", NULL) == -1)
677 err(1, "pledge");
678 } else {
679 if (pledge("stdio rpath", NULL) == -1)
680 err(1, "pledge");
681 }
682
683 dspew("Server at %s returns:\n", host);
684 for (i = 0; i < hget->headsz; i++)
685 dspew(" [%s]=[%s]\n", hget->head[i].key, hget->head[i].val);
686 dspew(" [Body]=[%zu bytes]\n", hget->bodypartsz);
687 if (hget->bodypartsz <= 0)
688 errx(1, "No body in reply from %s", host);
689
690 if (hget->code != 200)
691 errx(1, "http reply code %d from %s", hget->code, host);
692
693 /*
694 * Validate the OCSP response we got back
695 */
696 OPENSSL_add_all_algorithms_noconf();
697 if (!validate_response(hget->bodypart, hget->bodypartsz,
698 request, castore, host, certfile))
699 exit(1);
700 instaple = hget->bodypart;
701 instaplesz = hget->bodypartsz;
702 } else {
703 size_t nr = 0;
704 instaplesz = 0;
705
706 /*
707 * Pledge minimally before fiddling with libcrypto init
708 */
709 if (cadir == NULL) {
710 if (pledge("stdio", NULL) == -1)
711 err(1, "pledge");
712 } else {
713 if (pledge("stdio rpath", NULL) == -1)
714 err(1, "pledge");
715 }
716
717 dspew("Using ocsp response saved in %s:\n", infile);
718
719 /* Use the existing OCSP response saved in infd */
720 instaple = calloc(OCSP_MAX_RESPONSE_SIZE, 1);
721 if (instaple) {
722 while ((nr = read(infd, instaple + instaplesz,
723 OCSP_MAX_RESPONSE_SIZE - instaplesz)) != -1 &&
724 nr != 0)
725 instaplesz += nr;
726 }
727 if (instaplesz == 0)
728 exit(1);
729 /*
730 * Validate the OCSP staple we read in.
731 */
732 OPENSSL_add_all_algorithms_noconf();
733 if (!validate_response(instaple, instaplesz,
734 request, castore, host, certfile))
735 exit(1);
736 }
737
738 /*
739 * If we have been given a place to save a staple,
740 * write out the DER format response to the staplefd
741 */
742 if (staplefd >= 0) {
743 while (ftruncate(staplefd, 0) < 0) {
744 if (errno == EINVAL)
745 break;
746 if (errno != EINTR && errno != EAGAIN)
747 err(1, "Write of OCSP response failed");
748 }
749 written = 0;
750 while (written < instaplesz) {
751 w = write(staplefd, instaple + written,
752 instaplesz - written);
753 if (w == -1) {
754 if (errno != EINTR && errno != EAGAIN)
755 err(1, "Write of OCSP response failed");
756 } else
757 written += w;
758 }
759 close(staplefd);
760 }
761 exit(0);
762}