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