check in v3.8.2 source
This commit is contained in:
183
apps/ocspcheck/compat/memmem.c
Normal file
183
apps/ocspcheck/compat/memmem.c
Normal file
@@ -0,0 +1,183 @@
|
||||
/* $OpenBSD: memmem.c,v 1.5 2020/04/16 12:39:28 claudio Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2005-2020 Rich Felker, et al.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static char *
|
||||
twobyte_memmem(const unsigned char *h, size_t k, const unsigned char *n)
|
||||
{
|
||||
uint16_t nw = n[0]<<8 | n[1], hw = h[0]<<8 | h[1];
|
||||
for (h+=2, k-=2; k; k--, hw = hw<<8 | *h++)
|
||||
if (hw == nw) return (char *)h-2;
|
||||
return hw == nw ? (char *)h-2 : 0;
|
||||
}
|
||||
|
||||
static char *
|
||||
threebyte_memmem(const unsigned char *h, size_t k, const unsigned char *n)
|
||||
{
|
||||
uint32_t nw = n[0]<<24 | n[1]<<16 | n[2]<<8;
|
||||
uint32_t hw = h[0]<<24 | h[1]<<16 | h[2]<<8;
|
||||
for (h+=3, k-=3; k; k--, hw = (hw|*h++)<<8)
|
||||
if (hw == nw) return (char *)h-3;
|
||||
return hw == nw ? (char *)h-3 : 0;
|
||||
}
|
||||
|
||||
static char *
|
||||
fourbyte_memmem(const unsigned char *h, size_t k, const unsigned char *n)
|
||||
{
|
||||
uint32_t nw = n[0]<<24 | n[1]<<16 | n[2]<<8 | n[3];
|
||||
uint32_t hw = h[0]<<24 | h[1]<<16 | h[2]<<8 | h[3];
|
||||
for (h+=4, k-=4; k; k--, hw = hw<<8 | *h++)
|
||||
if (hw == nw) return (char *)h-4;
|
||||
return hw == nw ? (char *)h-4 : 0;
|
||||
}
|
||||
|
||||
#define MAX(a,b) ((a)>(b)?(a):(b))
|
||||
#define MIN(a,b) ((a)<(b)?(a):(b))
|
||||
|
||||
#define BITOP(a,b,op) \
|
||||
((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a))))
|
||||
|
||||
/*
|
||||
* Maxime Crochemore and Dominique Perrin, Two-way string-matching,
|
||||
* Journal of the ACM, 38(3):651-675, July 1991.
|
||||
*/
|
||||
static char *
|
||||
twoway_memmem(const unsigned char *h, const unsigned char *z,
|
||||
const unsigned char *n, size_t l)
|
||||
{
|
||||
size_t i, ip, jp, k, p, ms, p0, mem, mem0;
|
||||
size_t byteset[32 / sizeof(size_t)] = { 0 };
|
||||
size_t shift[256];
|
||||
|
||||
/* Computing length of needle and fill shift table */
|
||||
for (i=0; i<l; i++)
|
||||
BITOP(byteset, n[i], |=), shift[n[i]] = i+1;
|
||||
|
||||
/* Compute maximal suffix */
|
||||
ip = -1; jp = 0; k = p = 1;
|
||||
while (jp+k<l) {
|
||||
if (n[ip+k] == n[jp+k]) {
|
||||
if (k == p) {
|
||||
jp += p;
|
||||
k = 1;
|
||||
} else k++;
|
||||
} else if (n[ip+k] > n[jp+k]) {
|
||||
jp += k;
|
||||
k = 1;
|
||||
p = jp - ip;
|
||||
} else {
|
||||
ip = jp++;
|
||||
k = p = 1;
|
||||
}
|
||||
}
|
||||
ms = ip;
|
||||
p0 = p;
|
||||
|
||||
/* And with the opposite comparison */
|
||||
ip = -1; jp = 0; k = p = 1;
|
||||
while (jp+k<l) {
|
||||
if (n[ip+k] == n[jp+k]) {
|
||||
if (k == p) {
|
||||
jp += p;
|
||||
k = 1;
|
||||
} else k++;
|
||||
} else if (n[ip+k] < n[jp+k]) {
|
||||
jp += k;
|
||||
k = 1;
|
||||
p = jp - ip;
|
||||
} else {
|
||||
ip = jp++;
|
||||
k = p = 1;
|
||||
}
|
||||
}
|
||||
if (ip+1 > ms+1) ms = ip;
|
||||
else p = p0;
|
||||
|
||||
/* Periodic needle? */
|
||||
if (memcmp(n, n+p, ms+1)) {
|
||||
mem0 = 0;
|
||||
p = MAX(ms, l-ms-1) + 1;
|
||||
} else mem0 = l-p;
|
||||
mem = 0;
|
||||
|
||||
/* Search loop */
|
||||
for (;;) {
|
||||
/* If remainder of haystack is shorter than needle, done */
|
||||
if (z-h < l) return 0;
|
||||
|
||||
/* Check last byte first; advance by shift on mismatch */
|
||||
if (BITOP(byteset, h[l-1], &)) {
|
||||
k = l-shift[h[l-1]];
|
||||
if (k) {
|
||||
if (k < mem) k = mem;
|
||||
h += k;
|
||||
mem = 0;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
h += l;
|
||||
mem = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Compare right half */
|
||||
for (k=MAX(ms+1,mem); k<l && n[k] == h[k]; k++);
|
||||
if (k < l) {
|
||||
h += k-ms;
|
||||
mem = 0;
|
||||
continue;
|
||||
}
|
||||
/* Compare left half */
|
||||
for (k=ms+1; k>mem && n[k-1] == h[k-1]; k--);
|
||||
if (k <= mem) return (char *)h;
|
||||
h += p;
|
||||
mem = mem0;
|
||||
}
|
||||
}
|
||||
|
||||
void *
|
||||
memmem(const void *h0, size_t k, const void *n0, size_t l)
|
||||
{
|
||||
const unsigned char *h = h0, *n = n0;
|
||||
|
||||
/* Return immediately on empty needle */
|
||||
if (!l) return (void *)h;
|
||||
|
||||
/* Return immediately when needle is longer than haystack */
|
||||
if (k<l) return 0;
|
||||
|
||||
/* Use faster algorithms for short needles */
|
||||
h = memchr(h0, *n, k);
|
||||
if (!h || l==1) return (void *)h;
|
||||
k -= h - (const unsigned char *)h0;
|
||||
if (k<l) return 0;
|
||||
if (l==2) return twobyte_memmem(h, k, n);
|
||||
if (l==3) return threebyte_memmem(h, k, n);
|
||||
if (l==4) return fourbyte_memmem(h, k, n);
|
||||
|
||||
return twoway_memmem(h, h+k, n, l);
|
||||
}
|
777
apps/ocspcheck/http.c
Normal file
777
apps/ocspcheck/http.c
Normal file
@@ -0,0 +1,777 @@
|
||||
/* $Id: http.c,v 1.17 2023/04/19 12:58:16 jsg Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <err.h>
|
||||
#include <limits.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <tls.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "http.h"
|
||||
|
||||
/*
|
||||
* A buffer for transferring HTTP/S data.
|
||||
*/
|
||||
struct httpxfer {
|
||||
char *hbuf; /* header transfer buffer */
|
||||
size_t hbufsz; /* header buffer size */
|
||||
int headok; /* header has been parsed */
|
||||
char *bbuf; /* body transfer buffer */
|
||||
size_t bbufsz; /* body buffer size */
|
||||
int bodyok; /* body has been parsed */
|
||||
char *headbuf; /* lookaside buffer for headers */
|
||||
struct httphead *head; /* parsed headers */
|
||||
size_t headsz; /* number of headers */
|
||||
};
|
||||
|
||||
/*
|
||||
* An HTTP/S connection object.
|
||||
*/
|
||||
struct http {
|
||||
int fd; /* connected socket */
|
||||
short port; /* port number */
|
||||
struct source src; /* endpoint (raw) host */
|
||||
char *path; /* path to request */
|
||||
char *host; /* name of endpoint host */
|
||||
struct tls *ctx; /* if TLS */
|
||||
writefp writer; /* write function */
|
||||
readfp reader; /* read function */
|
||||
};
|
||||
|
||||
struct tls_config *tlscfg;
|
||||
|
||||
static ssize_t
|
||||
dosysread(char *buf, size_t sz, const struct http *http)
|
||||
{
|
||||
ssize_t rc;
|
||||
|
||||
rc = read(http->fd, buf, sz);
|
||||
if (rc == -1)
|
||||
warn("%s: read", http->src.ip);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
dosyswrite(const void *buf, size_t sz, const struct http *http)
|
||||
{
|
||||
ssize_t rc;
|
||||
|
||||
rc = write(http->fd, buf, sz);
|
||||
if (rc == -1)
|
||||
warn("%s: write", http->src.ip);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
dotlsread(char *buf, size_t sz, const struct http *http)
|
||||
{
|
||||
ssize_t rc;
|
||||
|
||||
do {
|
||||
rc = tls_read(http->ctx, buf, sz);
|
||||
} while (rc == TLS_WANT_POLLIN || rc == TLS_WANT_POLLOUT);
|
||||
|
||||
if (rc == -1)
|
||||
warnx("%s: tls_read: %s", http->src.ip,
|
||||
tls_error(http->ctx));
|
||||
return rc;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
dotlswrite(const void *buf, size_t sz, const struct http *http)
|
||||
{
|
||||
ssize_t rc;
|
||||
|
||||
do {
|
||||
rc = tls_write(http->ctx, buf, sz);
|
||||
} while (rc == TLS_WANT_POLLIN || rc == TLS_WANT_POLLOUT);
|
||||
|
||||
if (rc == -1)
|
||||
warnx("%s: tls_write: %s", http->src.ip,
|
||||
tls_error(http->ctx));
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
http_init(void)
|
||||
{
|
||||
if (tlscfg != NULL)
|
||||
return 0;
|
||||
|
||||
tlscfg = tls_config_new();
|
||||
if (tlscfg == NULL) {
|
||||
warn("tls_config_new");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (tls_config_set_ca_file(tlscfg, tls_default_ca_cert_file()) == -1) {
|
||||
warn("tls_config_set_ca_file: %s", tls_config_error(tlscfg));
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
tls_config_free(tlscfg);
|
||||
tlscfg = NULL;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
http_read(char *buf, size_t sz, const struct http *http)
|
||||
{
|
||||
ssize_t ssz, xfer;
|
||||
|
||||
xfer = 0;
|
||||
do {
|
||||
if ((ssz = http->reader(buf, sz, http)) < 0)
|
||||
return -1;
|
||||
if (ssz == 0)
|
||||
break;
|
||||
xfer += ssz;
|
||||
sz -= ssz;
|
||||
buf += ssz;
|
||||
} while (ssz > 0 && sz > 0);
|
||||
|
||||
return xfer;
|
||||
}
|
||||
|
||||
static int
|
||||
http_write(const char *buf, size_t sz, const struct http *http)
|
||||
{
|
||||
ssize_t ssz, xfer;
|
||||
|
||||
xfer = sz;
|
||||
while (sz > 0) {
|
||||
if ((ssz = http->writer(buf, sz, http)) < 0)
|
||||
return -1;
|
||||
sz -= ssz;
|
||||
buf += (size_t)ssz;
|
||||
}
|
||||
return xfer;
|
||||
}
|
||||
|
||||
void
|
||||
http_disconnect(struct http *http)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (http->ctx != NULL) {
|
||||
/* TLS connection. */
|
||||
do {
|
||||
rc = tls_close(http->ctx);
|
||||
} while (rc == TLS_WANT_POLLIN || rc == TLS_WANT_POLLOUT);
|
||||
|
||||
if (rc < 0)
|
||||
warnx("%s: tls_close: %s", http->src.ip,
|
||||
tls_error(http->ctx));
|
||||
|
||||
tls_free(http->ctx);
|
||||
}
|
||||
if (http->fd != -1) {
|
||||
if (close(http->fd) == -1)
|
||||
warn("%s: close", http->src.ip);
|
||||
}
|
||||
|
||||
http->fd = -1;
|
||||
http->ctx = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
http_free(struct http *http)
|
||||
{
|
||||
|
||||
if (http == NULL)
|
||||
return;
|
||||
http_disconnect(http);
|
||||
free(http->host);
|
||||
free(http->path);
|
||||
free(http->src.ip);
|
||||
free(http);
|
||||
}
|
||||
|
||||
struct http *
|
||||
http_alloc(const struct source *addrs, size_t addrsz,
|
||||
const char *host, short port, const char *path)
|
||||
{
|
||||
struct sockaddr_storage ss;
|
||||
int family, fd, c;
|
||||
socklen_t len;
|
||||
size_t cur, i = 0;
|
||||
struct http *http;
|
||||
|
||||
/* Do this while we still have addresses to connect. */
|
||||
again:
|
||||
if (i == addrsz)
|
||||
return NULL;
|
||||
cur = i++;
|
||||
|
||||
/* Convert to PF_INET or PF_INET6 address from string. */
|
||||
|
||||
memset(&ss, 0, sizeof(struct sockaddr_storage));
|
||||
|
||||
if (addrs[cur].family == 4) {
|
||||
family = PF_INET;
|
||||
((struct sockaddr_in *)&ss)->sin_family = AF_INET;
|
||||
((struct sockaddr_in *)&ss)->sin_port = htons(port);
|
||||
c = inet_pton(AF_INET, addrs[cur].ip,
|
||||
&((struct sockaddr_in *)&ss)->sin_addr);
|
||||
len = sizeof(struct sockaddr_in);
|
||||
} else if (addrs[cur].family == 6) {
|
||||
family = PF_INET6;
|
||||
((struct sockaddr_in6 *)&ss)->sin6_family = AF_INET6;
|
||||
((struct sockaddr_in6 *)&ss)->sin6_port = htons(port);
|
||||
c = inet_pton(AF_INET6, addrs[cur].ip,
|
||||
&((struct sockaddr_in6 *)&ss)->sin6_addr);
|
||||
len = sizeof(struct sockaddr_in6);
|
||||
} else {
|
||||
warnx("%s: unknown family", addrs[cur].ip);
|
||||
goto again;
|
||||
}
|
||||
|
||||
if (c < 0) {
|
||||
warn("%s: inet_ntop", addrs[cur].ip);
|
||||
goto again;
|
||||
} else if (c == 0) {
|
||||
warnx("%s: inet_ntop", addrs[cur].ip);
|
||||
goto again;
|
||||
}
|
||||
|
||||
/* Create socket and connect. */
|
||||
|
||||
fd = socket(family, SOCK_STREAM, 0);
|
||||
if (fd == -1) {
|
||||
warn("%s: socket", addrs[cur].ip);
|
||||
goto again;
|
||||
} else if (connect(fd, (struct sockaddr *)&ss, len) == -1) {
|
||||
warn("%s: connect", addrs[cur].ip);
|
||||
close(fd);
|
||||
goto again;
|
||||
}
|
||||
|
||||
/* Allocate the communicator. */
|
||||
|
||||
http = calloc(1, sizeof(struct http));
|
||||
if (http == NULL) {
|
||||
warn("calloc");
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
http->fd = fd;
|
||||
http->port = port;
|
||||
http->src.family = addrs[cur].family;
|
||||
http->src.ip = strdup(addrs[cur].ip);
|
||||
http->host = strdup(host);
|
||||
http->path = strdup(path);
|
||||
if (http->src.ip == NULL || http->host == NULL || http->path == NULL) {
|
||||
warn("strdup");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* If necessary, do our TLS setup. */
|
||||
|
||||
if (port != 443) {
|
||||
http->writer = dosyswrite;
|
||||
http->reader = dosysread;
|
||||
return http;
|
||||
}
|
||||
|
||||
http->writer = dotlswrite;
|
||||
http->reader = dotlsread;
|
||||
|
||||
if ((http->ctx = tls_client()) == NULL) {
|
||||
warn("tls_client");
|
||||
goto err;
|
||||
} else if (tls_configure(http->ctx, tlscfg) == -1) {
|
||||
warnx("%s: tls_configure: %s",
|
||||
http->src.ip, tls_error(http->ctx));
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (tls_connect_socket(http->ctx, http->fd, http->host) != 0) {
|
||||
warnx("%s: tls_connect_socket: %s, %s", http->src.ip,
|
||||
http->host, tls_error(http->ctx));
|
||||
goto err;
|
||||
}
|
||||
|
||||
return http;
|
||||
err:
|
||||
http_free(http);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct httpxfer *
|
||||
http_open(const struct http *http, const void *p, size_t psz)
|
||||
{
|
||||
char *req;
|
||||
int c;
|
||||
struct httpxfer *trans;
|
||||
|
||||
if (p == NULL) {
|
||||
c = asprintf(&req,
|
||||
"GET %s HTTP/1.0\r\n"
|
||||
"Host: %s\r\n"
|
||||
"\r\n",
|
||||
http->path, http->host);
|
||||
} else {
|
||||
c = asprintf(&req,
|
||||
"POST %s HTTP/1.0\r\n"
|
||||
"Host: %s\r\n"
|
||||
"Content-Type: application/ocsp-request\r\n"
|
||||
"Content-Length: %zu\r\n"
|
||||
"\r\n",
|
||||
http->path, http->host, psz);
|
||||
}
|
||||
if (c == -1) {
|
||||
warn("asprintf");
|
||||
return NULL;
|
||||
} else if (!http_write(req, c, http)) {
|
||||
free(req);
|
||||
return NULL;
|
||||
} else if (p != NULL && !http_write(p, psz, http)) {
|
||||
free(req);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
free(req);
|
||||
|
||||
trans = calloc(1, sizeof(struct httpxfer));
|
||||
if (trans == NULL)
|
||||
warn("calloc");
|
||||
return trans;
|
||||
}
|
||||
|
||||
void
|
||||
http_close(struct httpxfer *x)
|
||||
{
|
||||
|
||||
if (x == NULL)
|
||||
return;
|
||||
free(x->hbuf);
|
||||
free(x->bbuf);
|
||||
free(x->headbuf);
|
||||
free(x->head);
|
||||
free(x);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the HTTP body from the wire.
|
||||
* If invoked multiple times, this will return the same pointer with the
|
||||
* same data (or NULL, if the original invocation returned NULL).
|
||||
* Returns NULL if read or allocation errors occur.
|
||||
* You must not free the returned pointer.
|
||||
*/
|
||||
char *
|
||||
http_body_read(const struct http *http, struct httpxfer *trans, size_t *sz)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
ssize_t ssz;
|
||||
void *pp;
|
||||
size_t szp;
|
||||
|
||||
if (sz == NULL)
|
||||
sz = &szp;
|
||||
|
||||
/* Have we already parsed this? */
|
||||
|
||||
if (trans->bodyok > 0) {
|
||||
*sz = trans->bbufsz;
|
||||
return trans->bbuf;
|
||||
} else if (trans->bodyok < 0)
|
||||
return NULL;
|
||||
|
||||
*sz = 0;
|
||||
trans->bodyok = -1;
|
||||
|
||||
do {
|
||||
/* If less than sizeof(buf), at EOF. */
|
||||
if ((ssz = http_read(buf, sizeof(buf), http)) < 0)
|
||||
return NULL;
|
||||
else if (ssz == 0)
|
||||
break;
|
||||
|
||||
pp = recallocarray(trans->bbuf,
|
||||
trans->bbufsz, trans->bbufsz + ssz, 1);
|
||||
if (pp == NULL) {
|
||||
warn("recallocarray");
|
||||
return NULL;
|
||||
}
|
||||
trans->bbuf = pp;
|
||||
memcpy(trans->bbuf + trans->bbufsz, buf, ssz);
|
||||
trans->bbufsz += ssz;
|
||||
} while (ssz == sizeof(buf));
|
||||
|
||||
trans->bodyok = 1;
|
||||
*sz = trans->bbufsz;
|
||||
return trans->bbuf;
|
||||
}
|
||||
|
||||
struct httphead *
|
||||
http_head_get(const char *v, struct httphead *h, size_t hsz)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < hsz; i++) {
|
||||
if (strcmp(h[i].key, v))
|
||||
continue;
|
||||
return &h[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Look through the headers and determine our HTTP code.
|
||||
* This will return -1 on failure, otherwise the code.
|
||||
*/
|
||||
int
|
||||
http_head_status(const struct http *http, struct httphead *h, size_t sz)
|
||||
{
|
||||
int rc;
|
||||
unsigned int code;
|
||||
struct httphead *st;
|
||||
|
||||
if ((st = http_head_get("Status", h, sz)) == NULL) {
|
||||
warnx("%s: no status header", http->src.ip);
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = sscanf(st->val, "%*s %u %*s", &code);
|
||||
if (rc < 0) {
|
||||
warn("sscanf");
|
||||
return -1;
|
||||
} else if (rc != 1) {
|
||||
warnx("%s: cannot convert status header", http->src.ip);
|
||||
return -1;
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse headers from the transfer.
|
||||
* Malformed headers are skipped.
|
||||
* A special "Status" header is added for the HTTP status line.
|
||||
* This can only happen once http_head_read has been called with
|
||||
* success.
|
||||
* This can be invoked multiple times: it will only parse the headers
|
||||
* once and after that it will just return the cache.
|
||||
* You must not free the returned pointer.
|
||||
* If the original header parse failed, or if memory allocation fails
|
||||
* internally, this returns NULL.
|
||||
*/
|
||||
struct httphead *
|
||||
http_head_parse(const struct http *http, struct httpxfer *trans, size_t *sz)
|
||||
{
|
||||
size_t hsz, szp;
|
||||
struct httphead *h;
|
||||
char *cp, *ep, *ccp, *buf;
|
||||
|
||||
if (sz == NULL)
|
||||
sz = &szp;
|
||||
|
||||
/*
|
||||
* If we've already parsed the headers, return the
|
||||
* previously-parsed buffer now.
|
||||
* If we have errors on the stream, return NULL now.
|
||||
*/
|
||||
|
||||
if (trans->head != NULL) {
|
||||
*sz = trans->headsz;
|
||||
return trans->head;
|
||||
} else if (trans->headok <= 0)
|
||||
return NULL;
|
||||
|
||||
if ((buf = strdup(trans->hbuf)) == NULL) {
|
||||
warn("strdup");
|
||||
return NULL;
|
||||
}
|
||||
hsz = 0;
|
||||
cp = buf;
|
||||
|
||||
do {
|
||||
if ((cp = strstr(cp, "\r\n")) != NULL)
|
||||
cp += 2;
|
||||
hsz++;
|
||||
} while (cp != NULL);
|
||||
|
||||
/*
|
||||
* Allocate headers, then step through the data buffer, parsing
|
||||
* out headers as we have them.
|
||||
* We know at this point that the buffer is NUL-terminated in
|
||||
* the usual way.
|
||||
*/
|
||||
|
||||
h = calloc(hsz, sizeof(struct httphead));
|
||||
if (h == NULL) {
|
||||
warn("calloc");
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*sz = hsz;
|
||||
hsz = 0;
|
||||
cp = buf;
|
||||
|
||||
do {
|
||||
if ((ep = strstr(cp, "\r\n")) != NULL) {
|
||||
*ep = '\0';
|
||||
ep += 2;
|
||||
}
|
||||
if (hsz == 0) {
|
||||
h[hsz].key = "Status";
|
||||
h[hsz++].val = cp;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Skip bad headers. */
|
||||
if ((ccp = strchr(cp, ':')) == NULL) {
|
||||
warnx("%s: header without separator", http->src.ip);
|
||||
continue;
|
||||
}
|
||||
|
||||
*ccp++ = '\0';
|
||||
while (isspace((unsigned char)*ccp))
|
||||
ccp++;
|
||||
h[hsz].key = cp;
|
||||
h[hsz++].val = ccp;
|
||||
} while ((cp = ep) != NULL);
|
||||
|
||||
trans->headbuf = buf;
|
||||
trans->head = h;
|
||||
trans->headsz = hsz;
|
||||
return h;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the HTTP headers from the wire.
|
||||
* If invoked multiple times, this will return the same pointer with the
|
||||
* same data (or NULL, if the original invocation returned NULL).
|
||||
* Returns NULL if read or allocation errors occur.
|
||||
* You must not free the returned pointer.
|
||||
*/
|
||||
char *
|
||||
http_head_read(const struct http *http, struct httpxfer *trans, size_t *sz)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
ssize_t ssz;
|
||||
char *ep;
|
||||
void *pp;
|
||||
size_t szp;
|
||||
|
||||
if (sz == NULL)
|
||||
sz = &szp;
|
||||
|
||||
/* Have we already parsed this? */
|
||||
|
||||
if (trans->headok > 0) {
|
||||
*sz = trans->hbufsz;
|
||||
return trans->hbuf;
|
||||
} else if (trans->headok < 0)
|
||||
return NULL;
|
||||
|
||||
*sz = 0;
|
||||
ep = NULL;
|
||||
trans->headok = -1;
|
||||
|
||||
/*
|
||||
* Begin by reading by BUFSIZ blocks until we reach the header
|
||||
* termination marker (two CRLFs).
|
||||
* We might read into our body, but that's ok: we'll copy out
|
||||
* the body parts into our body buffer afterward.
|
||||
*/
|
||||
|
||||
do {
|
||||
/* If less than sizeof(buf), at EOF. */
|
||||
if ((ssz = http_read(buf, sizeof(buf), http)) < 0)
|
||||
return NULL;
|
||||
else if (ssz == 0)
|
||||
break;
|
||||
pp = realloc(trans->hbuf, trans->hbufsz + ssz);
|
||||
if (pp == NULL) {
|
||||
warn("realloc");
|
||||
return NULL;
|
||||
}
|
||||
trans->hbuf = pp;
|
||||
memcpy(trans->hbuf + trans->hbufsz, buf, ssz);
|
||||
trans->hbufsz += ssz;
|
||||
/* Search for end of headers marker. */
|
||||
ep = memmem(trans->hbuf, trans->hbufsz, "\r\n\r\n", 4);
|
||||
} while (ep == NULL && ssz == sizeof(buf));
|
||||
|
||||
if (ep == NULL) {
|
||||
warnx("%s: partial transfer", http->src.ip);
|
||||
return NULL;
|
||||
}
|
||||
*ep = '\0';
|
||||
|
||||
/*
|
||||
* The header data is invalid if it has any binary characters in
|
||||
* it: check that now.
|
||||
* This is important because we want to guarantee that all
|
||||
* header keys and pairs are properly NUL-terminated.
|
||||
*/
|
||||
|
||||
if (strlen(trans->hbuf) != (uintptr_t)(ep - trans->hbuf)) {
|
||||
warnx("%s: binary data in header", http->src.ip);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy remaining buffer into body buffer.
|
||||
*/
|
||||
|
||||
ep += 4;
|
||||
trans->bbufsz = (trans->hbuf + trans->hbufsz) - ep;
|
||||
trans->bbuf = malloc(trans->bbufsz);
|
||||
if (trans->bbuf == NULL) {
|
||||
warn("malloc");
|
||||
return NULL;
|
||||
}
|
||||
memcpy(trans->bbuf, ep, trans->bbufsz);
|
||||
|
||||
trans->headok = 1;
|
||||
*sz = trans->hbufsz;
|
||||
return trans->hbuf;
|
||||
}
|
||||
|
||||
void
|
||||
http_get_free(struct httpget *g)
|
||||
{
|
||||
|
||||
if (g == NULL)
|
||||
return;
|
||||
http_close(g->xfer);
|
||||
http_free(g->http);
|
||||
free(g);
|
||||
}
|
||||
|
||||
struct httpget *
|
||||
http_get(const struct source *addrs, size_t addrsz, const char *domain,
|
||||
short port, const char *path, const void *post, size_t postsz)
|
||||
{
|
||||
struct http *h;
|
||||
struct httpxfer *x;
|
||||
struct httpget *g;
|
||||
struct httphead *head;
|
||||
size_t headsz, bodsz, headrsz;
|
||||
int code;
|
||||
char *bod, *headr;
|
||||
|
||||
h = http_alloc(addrs, addrsz, domain, port, path);
|
||||
if (h == NULL)
|
||||
return NULL;
|
||||
|
||||
if ((x = http_open(h, post, postsz)) == NULL) {
|
||||
http_free(h);
|
||||
return NULL;
|
||||
} else if ((headr = http_head_read(h, x, &headrsz)) == NULL) {
|
||||
http_close(x);
|
||||
http_free(h);
|
||||
return NULL;
|
||||
} else if ((bod = http_body_read(h, x, &bodsz)) == NULL) {
|
||||
http_close(x);
|
||||
http_free(h);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
http_disconnect(h);
|
||||
|
||||
if ((head = http_head_parse(h, x, &headsz)) == NULL) {
|
||||
http_close(x);
|
||||
http_free(h);
|
||||
return NULL;
|
||||
} else if ((code = http_head_status(h, head, headsz)) < 0) {
|
||||
http_close(x);
|
||||
http_free(h);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((g = calloc(1, sizeof(struct httpget))) == NULL) {
|
||||
warn("calloc");
|
||||
http_close(x);
|
||||
http_free(h);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g->headpart = headr;
|
||||
g->headpartsz = headrsz;
|
||||
g->bodypart = bod;
|
||||
g->bodypartsz = bodsz;
|
||||
g->head = head;
|
||||
g->headsz = headsz;
|
||||
g->code = code;
|
||||
g->xfer = x;
|
||||
g->http = h;
|
||||
return g;
|
||||
}
|
||||
|
||||
#if 0
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
struct httpget *g;
|
||||
struct httphead *httph;
|
||||
size_t i, httphsz;
|
||||
struct source addrs[2];
|
||||
size_t addrsz;
|
||||
|
||||
#if 0
|
||||
addrs[0].ip = "127.0.0.1";
|
||||
addrs[0].family = 4;
|
||||
addrsz = 1;
|
||||
#else
|
||||
addrs[0].ip = "2a00:1450:400a:806::2004";
|
||||
addrs[0].family = 6;
|
||||
addrs[1].ip = "193.135.3.123";
|
||||
addrs[1].family = 4;
|
||||
addrsz = 2;
|
||||
#endif
|
||||
|
||||
if (http_init() == -1)
|
||||
errx(EXIT_FAILURE, "http_init");
|
||||
|
||||
#if 0
|
||||
g = http_get(addrs, addrsz, "localhost", 80, "/index.html");
|
||||
#else
|
||||
g = http_get(addrs, addrsz, "www.google.ch", 80, "/index.html",
|
||||
NULL, 0);
|
||||
#endif
|
||||
|
||||
if (g == NULL)
|
||||
errx(EXIT_FAILURE, "http_get");
|
||||
|
||||
httph = http_head_parse(g->http, g->xfer, &httphsz);
|
||||
warnx("code: %d", g->code);
|
||||
|
||||
for (i = 0; i < httphsz; i++)
|
||||
warnx("head: [%s]=[%s]", httph[i].key, httph[i].val);
|
||||
|
||||
http_get_free(g);
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
#endif
|
92
apps/ocspcheck/http.h
Normal file
92
apps/ocspcheck/http.h
Normal file
@@ -0,0 +1,92 @@
|
||||
/* $Id: http.h,v 1.3 2017/01/25 13:52:53 inoguchi Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
#ifndef HTTP_H
|
||||
#define HTTP_H
|
||||
|
||||
struct source {
|
||||
int family; /* 4 (PF_INET) or 6 (PF_INET6) */
|
||||
char *ip; /* IPV4 or IPV6 address */
|
||||
};
|
||||
|
||||
struct http;
|
||||
|
||||
/*
|
||||
* Write and read callbacks to allow HTTP and HTTPS.
|
||||
* Both of these return the number of bytes read (or written) or -1 on
|
||||
* failure.
|
||||
* 0 bytes read means that the connection has closed.
|
||||
*/
|
||||
typedef ssize_t (*writefp)(const void *, size_t, const struct http *);
|
||||
typedef ssize_t (*readfp)(char *, size_t, const struct http *);
|
||||
|
||||
/*
|
||||
* HTTP/S header pair.
|
||||
* There's also a cooked-up pair, "Status", with the status code.
|
||||
* Both strings are NUL-terminated.
|
||||
*/
|
||||
struct httphead {
|
||||
const char *key;
|
||||
const char *val;
|
||||
};
|
||||
|
||||
/*
|
||||
* Grab all information from a transfer.
|
||||
* DO NOT free any parts of this, and editing the parts (e.g., changing
|
||||
* the underlying strings) will persist; so in short, don't.
|
||||
* All of these values will be set upon http_get() success.
|
||||
*/
|
||||
struct httpget {
|
||||
struct httpxfer *xfer; /* underlying transfer */
|
||||
struct http *http; /* underlying connection */
|
||||
int code; /* return code */
|
||||
struct httphead *head; /* headers */
|
||||
size_t headsz; /* number of headers */
|
||||
char *headpart; /* header buffer */
|
||||
size_t headpartsz; /* size of headpart */
|
||||
char *bodypart; /* body buffer */
|
||||
size_t bodypartsz; /* size of bodypart */
|
||||
};
|
||||
|
||||
int http_init(void);
|
||||
|
||||
/* Convenience functions. */
|
||||
struct httpget *http_get(const struct source *, size_t,
|
||||
const char *, short, const char *,
|
||||
const void *, size_t);
|
||||
void http_get_free(struct httpget *);
|
||||
|
||||
/* Allocation and release. */
|
||||
struct http *http_alloc(const struct source *, size_t,
|
||||
const char *, short, const char *);
|
||||
void http_free(struct http *);
|
||||
struct httpxfer *http_open(const struct http *, const void *, size_t);
|
||||
void http_close(struct httpxfer *);
|
||||
void http_disconnect(struct http *);
|
||||
|
||||
/* Access. */
|
||||
char *http_head_read(const struct http *,
|
||||
struct httpxfer *, size_t *);
|
||||
struct httphead *http_head_parse(const struct http *,
|
||||
struct httpxfer *, size_t *);
|
||||
char *http_body_read(const struct http *,
|
||||
struct httpxfer *, size_t *);
|
||||
int http_head_status(const struct http *,
|
||||
struct httphead *, size_t);
|
||||
struct httphead *http_head_get(const char *,
|
||||
struct httphead *, size_t);
|
||||
|
||||
#endif /* HTTP_H */
|
761
apps/ocspcheck/ocspcheck.c
Normal file
761
apps/ocspcheck/ocspcheck.c
Normal file
@@ -0,0 +1,761 @@
|
||||
/* $OpenBSD: ocspcheck.c,v 1.31 2022/12/28 21:30:17 jmc Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2017,2020 Bob Beck <beck@openbsd.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <err.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <netdb.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/ocsp.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include "http.h"
|
||||
|
||||
#define MAXAGE_SEC (14*24*60*60)
|
||||
#define JITTER_SEC (60)
|
||||
#define OCSP_MAX_RESPONSE_SIZE (20480)
|
||||
|
||||
typedef struct ocsp_request {
|
||||
STACK_OF(X509) *fullchain;
|
||||
OCSP_REQUEST *req;
|
||||
char *url;
|
||||
unsigned char *data;
|
||||
size_t size;
|
||||
int nonce;
|
||||
} ocsp_request;
|
||||
|
||||
int verbose;
|
||||
#define vspew(fmt, ...) \
|
||||
do { if (verbose >= 1) fprintf(stderr, fmt, __VA_ARGS__); } while (0)
|
||||
#define dspew(fmt, ...) \
|
||||
do { if (verbose >= 2) fprintf(stderr, fmt, __VA_ARGS__); } while (0)
|
||||
|
||||
#define MAX_SERVERS_DNS 8
|
||||
|
||||
struct addr {
|
||||
int family; /* 4 for PF_INET, 6 for PF_INET6 */
|
||||
char ip[INET6_ADDRSTRLEN];
|
||||
};
|
||||
|
||||
static ssize_t
|
||||
host_dns(const char *s, struct addr vec[MAX_SERVERS_DNS])
|
||||
{
|
||||
struct addrinfo hints, *res0, *res;
|
||||
int error;
|
||||
ssize_t vecsz;
|
||||
struct sockaddr *sa;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = PF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_DGRAM; /* DUMMY */
|
||||
|
||||
error = getaddrinfo(s, NULL, &hints, &res0);
|
||||
|
||||
if (error == EAI_AGAIN ||
|
||||
#ifdef EAI_NODATA
|
||||
error == EAI_NODATA ||
|
||||
#endif
|
||||
error == EAI_NONAME)
|
||||
return 0;
|
||||
|
||||
if (error) {
|
||||
warnx("%s: parse error: %s", s, gai_strerror(error));
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (vecsz = 0, res = res0;
|
||||
res != NULL && vecsz < MAX_SERVERS_DNS;
|
||||
res = res->ai_next) {
|
||||
if (res->ai_family != AF_INET &&
|
||||
res->ai_family != AF_INET6)
|
||||
continue;
|
||||
|
||||
sa = res->ai_addr;
|
||||
|
||||
if (res->ai_family == AF_INET) {
|
||||
vec[vecsz].family = 4;
|
||||
inet_ntop(AF_INET,
|
||||
&(((struct sockaddr_in *)sa)->sin_addr),
|
||||
vec[vecsz].ip, INET6_ADDRSTRLEN);
|
||||
} else {
|
||||
vec[vecsz].family = 6;
|
||||
inet_ntop(AF_INET6,
|
||||
&(((struct sockaddr_in6 *)sa)->sin6_addr),
|
||||
vec[vecsz].ip, INET6_ADDRSTRLEN);
|
||||
}
|
||||
|
||||
dspew("DNS returns %s for %s\n", vec[vecsz].ip, s);
|
||||
vecsz++;
|
||||
}
|
||||
|
||||
freeaddrinfo(res0);
|
||||
return vecsz;
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract the domain and port from a URL.
|
||||
* The url must be formatted as schema://address[/stuff].
|
||||
* This returns NULL on failure.
|
||||
*/
|
||||
static char *
|
||||
url2host(const char *host, short *port, char **path)
|
||||
{
|
||||
char *url, *ep;
|
||||
|
||||
/* We only understand HTTP and HTTPS. */
|
||||
|
||||
if (strncmp(host, "https://", 8) == 0) {
|
||||
*port = 443;
|
||||
if ((url = strdup(host + 8)) == NULL) {
|
||||
warn("strdup");
|
||||
return (NULL);
|
||||
}
|
||||
} else if (strncmp(host, "http://", 7) == 0) {
|
||||
*port = 80;
|
||||
if ((url = strdup(host + 7)) == NULL) {
|
||||
warn("strdup");
|
||||
return (NULL);
|
||||
}
|
||||
} else {
|
||||
warnx("%s: unknown schema", host);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Terminate path part. */
|
||||
|
||||
if ((ep = strchr(url, '/')) != NULL) {
|
||||
*path = strdup(ep);
|
||||
*ep = '\0';
|
||||
} else
|
||||
*path = strdup("/");
|
||||
|
||||
if (*path == NULL) {
|
||||
warn("strdup");
|
||||
free(url);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Check to see if there is a port in the url */
|
||||
if ((ep = strchr(url, ':')) != NULL) {
|
||||
const char *errstr;
|
||||
short pp;
|
||||
pp = strtonum(ep + 1, 1, SHRT_MAX, &errstr);
|
||||
if (errstr != NULL) {
|
||||
warnx("error parsing port from '%s': %s", url, errstr);
|
||||
free(url);
|
||||
free(*path);
|
||||
return NULL;
|
||||
}
|
||||
*port = pp;
|
||||
*ep = '\0';
|
||||
}
|
||||
|
||||
return (url);
|
||||
}
|
||||
|
||||
static time_t
|
||||
parse_ocsp_time(ASN1_GENERALIZEDTIME *gt)
|
||||
{
|
||||
struct tm tm;
|
||||
time_t rv = -1;
|
||||
|
||||
if (gt == NULL)
|
||||
return -1;
|
||||
/* RFC 6960 specifies that all times in OCSP must be GENERALIZEDTIME */
|
||||
if (ASN1_time_parse(gt->data, gt->length, &tm,
|
||||
V_ASN1_GENERALIZEDTIME) == -1)
|
||||
return -1;
|
||||
if ((rv = timegm(&tm)) == -1)
|
||||
return -1;
|
||||
return rv;
|
||||
}
|
||||
|
||||
static X509_STORE *
|
||||
read_cacerts(const char *file, const char *dir)
|
||||
{
|
||||
X509_STORE *store = NULL;
|
||||
X509_LOOKUP *lookup;
|
||||
|
||||
if (file == NULL && dir == NULL) {
|
||||
warnx("No CA certs to load");
|
||||
goto end;
|
||||
}
|
||||
if ((store = X509_STORE_new()) == NULL) {
|
||||
warnx("Malloc failed");
|
||||
goto end;
|
||||
}
|
||||
if (file != NULL) {
|
||||
if ((lookup = X509_STORE_add_lookup(store,
|
||||
X509_LOOKUP_file())) == NULL) {
|
||||
warnx("Unable to load CA cert file");
|
||||
goto end;
|
||||
}
|
||||
if (!X509_LOOKUP_load_file(lookup, file, X509_FILETYPE_PEM)) {
|
||||
warnx("Unable to load CA certs from file %s", file);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (dir != NULL) {
|
||||
if ((lookup = X509_STORE_add_lookup(store,
|
||||
X509_LOOKUP_hash_dir())) == NULL) {
|
||||
warnx("Unable to load CA cert directory");
|
||||
goto end;
|
||||
}
|
||||
if (!X509_LOOKUP_add_dir(lookup, dir, X509_FILETYPE_PEM)) {
|
||||
warnx("Unable to load CA certs from directory %s", dir);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
return store;
|
||||
|
||||
end:
|
||||
X509_STORE_free(store);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static STACK_OF(X509) *
|
||||
read_fullchain(const char *file, int *count)
|
||||
{
|
||||
int i;
|
||||
BIO *bio;
|
||||
STACK_OF(X509_INFO) *xis = NULL;
|
||||
X509_INFO *xi;
|
||||
STACK_OF(X509) *rv = NULL;
|
||||
|
||||
*count = 0;
|
||||
|
||||
if ((bio = BIO_new_file(file, "r")) == NULL) {
|
||||
warn("Unable to read a certificate from %s", file);
|
||||
goto end;
|
||||
}
|
||||
if ((xis = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL)) == NULL) {
|
||||
warnx("Unable to read PEM format from %s", file);
|
||||
goto end;
|
||||
}
|
||||
if (sk_X509_INFO_num(xis) <= 0) {
|
||||
warnx("No certificates in file %s", file);
|
||||
goto end;
|
||||
}
|
||||
if ((rv = sk_X509_new_null()) == NULL) {
|
||||
warnx("malloc failed");
|
||||
goto end;
|
||||
}
|
||||
|
||||
for (i = 0; i < sk_X509_INFO_num(xis); i++) {
|
||||
xi = sk_X509_INFO_value(xis, i);
|
||||
if (xi->x509 == NULL)
|
||||
continue;
|
||||
if (!sk_X509_push(rv, xi->x509)) {
|
||||
warnx("unable to build x509 chain");
|
||||
sk_X509_pop_free(rv, X509_free);
|
||||
rv = NULL;
|
||||
goto end;
|
||||
}
|
||||
xi->x509 = NULL;
|
||||
(*count)++;
|
||||
}
|
||||
end:
|
||||
BIO_free(bio);
|
||||
sk_X509_INFO_pop_free(xis, X509_INFO_free);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static inline X509 *
|
||||
cert_from_chain(STACK_OF(X509) *fullchain)
|
||||
{
|
||||
return sk_X509_value(fullchain, 0);
|
||||
}
|
||||
|
||||
static const X509 *
|
||||
issuer_from_chain(STACK_OF(X509) *fullchain)
|
||||
{
|
||||
const X509 *cert;
|
||||
X509_NAME *issuer_name;
|
||||
|
||||
cert = cert_from_chain(fullchain);
|
||||
if ((issuer_name = X509_get_issuer_name(cert)) == NULL)
|
||||
return NULL;
|
||||
|
||||
return X509_find_by_subject(fullchain, issuer_name);
|
||||
}
|
||||
|
||||
static ocsp_request *
|
||||
ocsp_request_new_from_cert(const char *cadir, char *file, int nonce)
|
||||
{
|
||||
X509 *cert;
|
||||
int count = 0;
|
||||
OCSP_CERTID *id = NULL;
|
||||
ocsp_request *request = NULL;
|
||||
const EVP_MD *cert_id_md = NULL;
|
||||
const X509 *issuer;
|
||||
STACK_OF(OPENSSL_STRING) *urls = NULL;
|
||||
|
||||
if ((request = calloc(1, sizeof(ocsp_request))) == NULL) {
|
||||
warn("malloc");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if ((request->req = OCSP_REQUEST_new()) == NULL)
|
||||
goto err;
|
||||
|
||||
request->fullchain = read_fullchain(file, &count);
|
||||
if (cadir == NULL) {
|
||||
/* Drop rpath from pledge, we don't need to read anymore */
|
||||
if (pledge("stdio inet dns", NULL) == -1)
|
||||
err(1, "pledge");
|
||||
}
|
||||
if (request->fullchain == NULL) {
|
||||
warnx("Unable to read cert chain from file %s", file);
|
||||
goto err;
|
||||
}
|
||||
if (count <= 1) {
|
||||
warnx("File %s does not contain a cert chain", file);
|
||||
goto err;
|
||||
}
|
||||
if ((cert = cert_from_chain(request->fullchain)) == NULL) {
|
||||
warnx("No certificate found in %s", file);
|
||||
goto err;
|
||||
}
|
||||
if ((issuer = issuer_from_chain(request->fullchain)) == NULL) {
|
||||
warnx("Unable to find issuer for cert in %s", file);
|
||||
goto err;
|
||||
}
|
||||
|
||||
urls = X509_get1_ocsp(cert);
|
||||
if (urls == NULL || sk_OPENSSL_STRING_num(urls) <= 0) {
|
||||
warnx("Certificate in %s contains no OCSP url", file);
|
||||
goto err;
|
||||
}
|
||||
if ((request->url = strdup(sk_OPENSSL_STRING_value(urls, 0))) == NULL)
|
||||
goto err;
|
||||
X509_email_free(urls);
|
||||
urls = NULL;
|
||||
|
||||
cert_id_md = EVP_sha1(); /* XXX. This sucks but OCSP is poopy */
|
||||
if ((id = OCSP_cert_to_id(cert_id_md, cert, issuer)) == NULL) {
|
||||
warnx("Unable to get certificate id from cert in %s", file);
|
||||
goto err;
|
||||
}
|
||||
if (OCSP_request_add0_id(request->req, id) == NULL) {
|
||||
warnx("Unable to add certificate id to request");
|
||||
goto err;
|
||||
}
|
||||
id = NULL;
|
||||
|
||||
request->nonce = nonce;
|
||||
if (request->nonce)
|
||||
OCSP_request_add1_nonce(request->req, NULL, -1);
|
||||
|
||||
if ((request->size = i2d_OCSP_REQUEST(request->req,
|
||||
&request->data)) <= 0) {
|
||||
warnx("Unable to encode ocsp request");
|
||||
goto err;
|
||||
}
|
||||
if (request->data == NULL) {
|
||||
warnx("Unable to allocate memory");
|
||||
goto err;
|
||||
}
|
||||
return request;
|
||||
|
||||
err:
|
||||
if (request != NULL) {
|
||||
sk_X509_pop_free(request->fullchain, X509_free);
|
||||
free(request->url);
|
||||
OCSP_REQUEST_free(request->req);
|
||||
free(request->data);
|
||||
}
|
||||
X509_email_free(urls);
|
||||
OCSP_CERTID_free(id);
|
||||
free(request);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
validate_response(char *buf, size_t size, ocsp_request *request,
|
||||
X509_STORE *store, char *host, char *file)
|
||||
{
|
||||
ASN1_GENERALIZEDTIME *revtime = NULL, *thisupd = NULL, *nextupd = NULL;
|
||||
const unsigned char **p = (const unsigned char **)&buf;
|
||||
int status, cert_status = 0, crl_reason = 0;
|
||||
time_t now, rev_t = -1, this_t, next_t;
|
||||
OCSP_RESPONSE *resp = NULL;
|
||||
OCSP_BASICRESP *bresp = NULL;
|
||||
OCSP_CERTID *cid = NULL;
|
||||
const X509 *cert, *issuer;
|
||||
int ret = 0;
|
||||
|
||||
if ((cert = cert_from_chain(request->fullchain)) == NULL) {
|
||||
warnx("No certificate found in %s", file);
|
||||
goto err;
|
||||
}
|
||||
if ((issuer = issuer_from_chain(request->fullchain)) == NULL) {
|
||||
warnx("Unable to find certificate issuer for cert in %s", file);
|
||||
goto err;
|
||||
}
|
||||
if ((cid = OCSP_cert_to_id(NULL, cert, issuer)) == NULL) {
|
||||
warnx("Unable to get issuer cert/CID in %s", file);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if ((resp = d2i_OCSP_RESPONSE(NULL, p, size)) == NULL) {
|
||||
warnx("OCSP response unserializable from host %s", host);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if ((bresp = OCSP_response_get1_basic(resp)) == NULL) {
|
||||
warnx("Failed to load OCSP response from %s", host);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (OCSP_basic_verify(bresp, request->fullchain, store,
|
||||
OCSP_TRUSTOTHER) != 1) {
|
||||
warnx("OCSP verify failed from %s", host);
|
||||
goto err;
|
||||
}
|
||||
dspew("OCSP response signature validated from %s\n", host);
|
||||
|
||||
status = OCSP_response_status(resp);
|
||||
if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
|
||||
warnx("OCSP Failure: code %d (%s) from host %s",
|
||||
status, OCSP_response_status_str(status), host);
|
||||
goto err;
|
||||
}
|
||||
dspew("OCSP response status %d from host %s\n", status, host);
|
||||
|
||||
/* Check the nonce if we sent one */
|
||||
|
||||
if (request->nonce) {
|
||||
if (OCSP_check_nonce(request->req, bresp) <= 0) {
|
||||
warnx("No OCSP nonce, or mismatch, from host %s", host);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (OCSP_resp_find_status(bresp, cid, &cert_status, &crl_reason,
|
||||
&revtime, &thisupd, &nextupd) != 1) {
|
||||
warnx("OCSP verify failed: no result for cert");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (revtime && (rev_t = parse_ocsp_time(revtime)) == -1) {
|
||||
warnx("Unable to parse revocation time in OCSP reply");
|
||||
goto err;
|
||||
}
|
||||
/*
|
||||
* Belt and suspenders, Treat it as revoked if there is either
|
||||
* a revocation time, or status revoked.
|
||||
*/
|
||||
if (rev_t != -1 || cert_status == V_OCSP_CERTSTATUS_REVOKED) {
|
||||
warnx("Invalid OCSP reply: certificate is revoked");
|
||||
if (rev_t != -1)
|
||||
warnx("Certificate revoked at: %s", ctime(&rev_t));
|
||||
goto err;
|
||||
}
|
||||
if ((this_t = parse_ocsp_time(thisupd)) == -1) {
|
||||
warnx("unable to parse this update time in OCSP reply");
|
||||
goto err;
|
||||
}
|
||||
if ((next_t = parse_ocsp_time(nextupd)) == -1) {
|
||||
warnx("unable to parse next update time in OCSP reply");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Don't allow this update to precede next update */
|
||||
if (this_t >= next_t) {
|
||||
warnx("Invalid OCSP reply: this update >= next update");
|
||||
goto err;
|
||||
}
|
||||
|
||||
now = time(NULL);
|
||||
/*
|
||||
* Check that this update is not more than JITTER seconds
|
||||
* in the future.
|
||||
*/
|
||||
if (this_t > now + JITTER_SEC) {
|
||||
warnx("Invalid OCSP reply: this update is in the future at %s",
|
||||
ctime(&this_t));
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that this update is not more than MAXSEC
|
||||
* in the past.
|
||||
*/
|
||||
if (this_t < now - MAXAGE_SEC) {
|
||||
warnx("Invalid OCSP reply: this update is too old %s",
|
||||
ctime(&this_t));
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that next update is still valid
|
||||
*/
|
||||
if (next_t < now - JITTER_SEC) {
|
||||
warnx("Invalid OCSP reply: reply has expired at %s",
|
||||
ctime(&next_t));
|
||||
goto err;
|
||||
}
|
||||
|
||||
vspew("OCSP response validated from %s\n", host);
|
||||
vspew(" This Update: %s", ctime(&this_t));
|
||||
vspew(" Next Update: %s", ctime(&next_t));
|
||||
ret = 1;
|
||||
err:
|
||||
OCSP_RESPONSE_free(resp);
|
||||
OCSP_BASICRESP_free(bresp);
|
||||
OCSP_CERTID_free(cid);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
usage(void)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"usage: ocspcheck [-Nv] [-C CAfile] [-i staplefile] "
|
||||
"[-o staplefile] file\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
const char *cafile = NULL, *cadir = NULL;
|
||||
char *host = NULL, *path = NULL, *certfile = NULL, *outfile = NULL,
|
||||
*instaple = NULL, *infile = NULL;
|
||||
struct addr addrs[MAX_SERVERS_DNS] = {{0}};
|
||||
struct source sources[MAX_SERVERS_DNS];
|
||||
int i, ch, staplefd = -1, infd = -1, nonce = 1;
|
||||
ocsp_request *request = NULL;
|
||||
size_t rescount, httphsz = 0, instaplesz = 0;
|
||||
struct httphead *httph = NULL;
|
||||
struct httpget *hget;
|
||||
X509_STORE *castore;
|
||||
ssize_t written, w;
|
||||
short port;
|
||||
|
||||
while ((ch = getopt(argc, argv, "C:i:No:v")) != -1) {
|
||||
switch (ch) {
|
||||
case 'C':
|
||||
cafile = optarg;
|
||||
break;
|
||||
case 'N':
|
||||
nonce = 0;
|
||||
break;
|
||||
case 'o':
|
||||
outfile = optarg;
|
||||
break;
|
||||
case 'i':
|
||||
infile = optarg;
|
||||
break;
|
||||
case 'v':
|
||||
verbose++;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
}
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (argc != 1 || (certfile = argv[0]) == NULL)
|
||||
usage();
|
||||
|
||||
if (outfile != NULL) {
|
||||
if (strcmp(outfile, "-") == 0)
|
||||
staplefd = STDOUT_FILENO;
|
||||
else
|
||||
staplefd = open(outfile, O_WRONLY|O_CREAT,
|
||||
S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH);
|
||||
if (staplefd < 0)
|
||||
err(1, "Unable to open output file %s", outfile);
|
||||
}
|
||||
|
||||
if (infile != NULL) {
|
||||
if (strcmp(infile, "-") == 0)
|
||||
infd = STDIN_FILENO;
|
||||
else
|
||||
infd = open(infile, O_RDONLY);
|
||||
if (infd < 0)
|
||||
err(1, "Unable to open input file %s", infile);
|
||||
nonce = 0; /* Can't validate a nonce on a saved reply */
|
||||
}
|
||||
|
||||
if (cafile == NULL) {
|
||||
if (access(X509_get_default_cert_file(), R_OK) == 0)
|
||||
cafile = X509_get_default_cert_file();
|
||||
if (access(X509_get_default_cert_dir(), F_OK) == 0)
|
||||
cadir = X509_get_default_cert_dir();
|
||||
}
|
||||
|
||||
if (cafile != NULL) {
|
||||
if (unveil(cafile, "r") == -1)
|
||||
err(1, "unveil %s", cafile);
|
||||
}
|
||||
if (cadir != NULL) {
|
||||
if (unveil(cadir, "r") == -1)
|
||||
err(1, "unveil %s", cadir);
|
||||
}
|
||||
if (unveil(certfile, "r") == -1)
|
||||
err(1, "unveil %s", certfile);
|
||||
|
||||
if (pledge("stdio inet rpath dns", NULL) == -1)
|
||||
err(1, "pledge");
|
||||
|
||||
/*
|
||||
* Load our certificate and keystore, and build up an
|
||||
* OCSP request based on the full certificate chain
|
||||
* we have been given to check.
|
||||
*/
|
||||
if ((castore = read_cacerts(cafile, cadir)) == NULL)
|
||||
exit(1);
|
||||
if ((request = ocsp_request_new_from_cert(cadir, certfile, nonce))
|
||||
== NULL)
|
||||
exit(1);
|
||||
|
||||
dspew("Built an %zu byte ocsp request\n", request->size);
|
||||
|
||||
if ((host = url2host(request->url, &port, &path)) == NULL)
|
||||
errx(1, "Invalid OCSP url %s from %s", request->url,
|
||||
certfile);
|
||||
|
||||
if (infd == -1) {
|
||||
/* Get a new OCSP response from the indicated server */
|
||||
|
||||
vspew("Using %s to host %s, port %d, path %s\n",
|
||||
port == 443 ? "https" : "http", host, port, path);
|
||||
|
||||
rescount = host_dns(host, addrs);
|
||||
for (i = 0; i < rescount; i++) {
|
||||
sources[i].ip = addrs[i].ip;
|
||||
sources[i].family = addrs[i].family;
|
||||
}
|
||||
|
||||
/*
|
||||
* Do an HTTP post to send our request to the OCSP
|
||||
* server, and hopefully get an answer back
|
||||
*/
|
||||
hget = http_get(sources, rescount, host, port, path,
|
||||
request->data, request->size);
|
||||
if (hget == NULL)
|
||||
errx(1, "http_get");
|
||||
/*
|
||||
* Pledge minimally before fiddling with libcrypto init
|
||||
* routines and parsing untrusted input from someone's OCSP
|
||||
* server.
|
||||
*/
|
||||
if (cadir == NULL) {
|
||||
if (pledge("stdio", NULL) == -1)
|
||||
err(1, "pledge");
|
||||
} else {
|
||||
if (pledge("stdio rpath", NULL) == -1)
|
||||
err(1, "pledge");
|
||||
}
|
||||
|
||||
dspew("Server at %s returns:\n", host);
|
||||
for (i = 0; i < httphsz; i++)
|
||||
dspew(" [%s]=[%s]\n", httph[i].key, httph[i].val);
|
||||
dspew(" [Body]=[%zu bytes]\n", hget->bodypartsz);
|
||||
if (hget->bodypartsz <= 0)
|
||||
errx(1, "No body in reply from %s", host);
|
||||
|
||||
if (hget->code != 200)
|
||||
errx(1, "http reply code %d from %s", hget->code, host);
|
||||
|
||||
/*
|
||||
* Validate the OCSP response we got back
|
||||
*/
|
||||
OPENSSL_add_all_algorithms_noconf();
|
||||
if (!validate_response(hget->bodypart, hget->bodypartsz,
|
||||
request, castore, host, certfile))
|
||||
exit(1);
|
||||
instaple = hget->bodypart;
|
||||
instaplesz = hget->bodypartsz;
|
||||
} else {
|
||||
size_t nr = 0;
|
||||
instaplesz = 0;
|
||||
|
||||
/*
|
||||
* Pledge minimally before fiddling with libcrypto init
|
||||
*/
|
||||
if (cadir == NULL) {
|
||||
if (pledge("stdio", NULL) == -1)
|
||||
err(1, "pledge");
|
||||
} else {
|
||||
if (pledge("stdio rpath", NULL) == -1)
|
||||
err(1, "pledge");
|
||||
}
|
||||
|
||||
dspew("Using ocsp response saved in %s:\n", infile);
|
||||
|
||||
/* Use the existing OCSP response saved in infd */
|
||||
instaple = calloc(OCSP_MAX_RESPONSE_SIZE, 1);
|
||||
if (instaple) {
|
||||
while ((nr = read(infd, instaple + instaplesz,
|
||||
OCSP_MAX_RESPONSE_SIZE - instaplesz)) != -1 &&
|
||||
nr != 0)
|
||||
instaplesz += nr;
|
||||
}
|
||||
if (instaplesz == 0)
|
||||
exit(1);
|
||||
/*
|
||||
* Validate the OCSP staple we read in.
|
||||
*/
|
||||
OPENSSL_add_all_algorithms_noconf();
|
||||
if (!validate_response(instaple, instaplesz,
|
||||
request, castore, host, certfile))
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*
|
||||
* If we have been given a place to save a staple,
|
||||
* write out the DER format response to the staplefd
|
||||
*/
|
||||
if (staplefd >= 0) {
|
||||
while (ftruncate(staplefd, 0) < 0) {
|
||||
if (errno == EINVAL)
|
||||
break;
|
||||
if (errno != EINTR && errno != EAGAIN)
|
||||
err(1, "Write of OCSP response failed");
|
||||
}
|
||||
written = 0;
|
||||
while (written < instaplesz) {
|
||||
w = write(staplefd, instaple + written,
|
||||
instaplesz - written);
|
||||
if (w == -1) {
|
||||
if (errno != EINTR && errno != EAGAIN)
|
||||
err(1, "Write of OCSP response failed");
|
||||
} else
|
||||
written += w;
|
||||
}
|
||||
close(staplefd);
|
||||
}
|
||||
exit(0);
|
||||
}
|
Reference in New Issue
Block a user