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