File: [local] / src / usr.bin / openssl / ocsp.c (download)
Revision 1.21, Tue Oct 13 18:25:35 2020 UTC (3 years, 7 months ago) by tb
Branch: MAIN
CVS Tags: OPENBSD_7_2_BASE, OPENBSD_7_2, OPENBSD_7_1_BASE, OPENBSD_7_1, OPENBSD_7_0_BASE, OPENBSD_7_0, OPENBSD_6_9_BASE, OPENBSD_6_9 Changes since 1.20: +13 -4 lines
Make sure an OCSP query sends a host header
While OCSP uses HTTP/1.0 where a host header is optional, some widely
used OCSP responders will return 400 bad request if it is missing. Add
such a header unless it's already provided in the user's custom headers.
OpenSSL did something similar in ff4a9394a23 and 76e0cd12f68
(both commits are under the old license)
ok inoguchi
|
/* $OpenBSD: ocsp.c,v 1.21 2020/10/13 18:25:35 tb Exp $ */
/* Written by Dr Stephen N Henson (steve@openssl.org) for the OpenSSL
* project 2000.
*/
/* ====================================================================
* Copyright (c) 1999 The OpenSSL Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)"
*
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* licensing@OpenSSL.org.
*
* 5. Products derived from this software may not be called "OpenSSL"
* nor may "OpenSSL" appear in their names without prior written
* permission of the OpenSSL Project.
*
* 6. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)"
*
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This product includes cryptographic software written by Eric Young
* (eay@cryptsoft.com). This product includes software written by Tim
* Hudson (tjh@cryptsoft.com).
*
*/
#ifndef OPENSSL_NO_OCSP
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <poll.h>
#include <time.h>
/* Needs to be included before the openssl headers! */
#include "apps.h"
#include <openssl/bn.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
/* Maximum leeway in validity period: default 5 minutes */
#define MAX_VALIDITY_PERIOD (5 * 60)
static int add_ocsp_cert(OCSP_REQUEST **req, X509 *cert,
const EVP_MD *cert_id_md, X509 *issuer, STACK_OF(OCSP_CERTID) *ids);
static int add_ocsp_serial(OCSP_REQUEST **req, char *serial,
const EVP_MD *cert_id_md, X509 *issuer, STACK_OF(OCSP_CERTID) *ids);
static int print_ocsp_summary(BIO *out, OCSP_BASICRESP *bs, OCSP_REQUEST *req,
STACK_OF(OPENSSL_STRING) *names, STACK_OF(OCSP_CERTID) *ids, long nsec,
long maxage);
static int make_ocsp_response(OCSP_RESPONSE **resp, OCSP_REQUEST *req,
CA_DB *db, X509 *ca, X509 *rcert, EVP_PKEY *rkey, STACK_OF(X509) *rother,
unsigned long flags, int nmin, int ndays);
static char **lookup_serial(CA_DB *db, ASN1_INTEGER *ser);
static BIO *init_responder(char *port);
static int do_responder(OCSP_REQUEST **preq, BIO **pcbio, BIO *acbio,
char *port);
static int send_ocsp_response(BIO *cbio, OCSP_RESPONSE *resp);
static OCSP_RESPONSE *query_responder(BIO *err, BIO *cbio, char *path,
STACK_OF(CONF_VALUE) *headers, const char *host, OCSP_REQUEST *req,
int req_timeout);
static struct {
int accept_count;
int add_nonce;
char *CAfile;
char *CApath;
X509 *cert;
const EVP_MD *cert_id_md;
STACK_OF(CONF_VALUE) *headers;
char *host;
STACK_OF(OCSP_CERTID) *ids;
int ignore_err;
X509 *issuer;
char *keyfile;
long maxage;
int ndays;
int nmin;
int no_usage;
int noverify;
long nsec;
char *outfile;
char *path;
char *port;
char *rca_filename;
char *rcertfile;
OCSP_REQUEST *req;
int req_text;
int req_timeout;
char *reqin;
STACK_OF(OPENSSL_STRING) *reqnames;
char *reqout;
int resp_text;
char *respin;
char *respout;
unsigned long rflags;
char *ridx_filename;
char *rkeyfile;
char *rsignfile;
char *sign_certfile;
unsigned long sign_flags;
char *signfile;
int use_ssl;
char *verify_certfile;
unsigned long verify_flags;
} ocsp_config;
static int
ocsp_opt_cert(char *arg)
{
X509_free(ocsp_config.cert);
ocsp_config.cert = load_cert(bio_err, arg, FORMAT_PEM, NULL,
"certificate");
if (ocsp_config.cert == NULL) {
ocsp_config.no_usage = 1;
return (1);
}
if (ocsp_config.cert_id_md == NULL)
ocsp_config.cert_id_md = EVP_sha1();
if (!add_ocsp_cert(&ocsp_config.req, ocsp_config.cert,
ocsp_config.cert_id_md, ocsp_config.issuer, ocsp_config.ids)) {
ocsp_config.no_usage = 1;
return (1);
}
if (!sk_OPENSSL_STRING_push(ocsp_config.reqnames, arg)) {
ocsp_config.no_usage = 1;
return (1);
}
return (0);
}
static int
ocsp_opt_cert_id_md(int argc, char **argv, int *argsused)
{
char *name = argv[0];
if (*name++ != '-')
return (1);
if ((ocsp_config.cert_id_md = EVP_get_digestbyname(name)) == NULL)
return (1);
*argsused = 1;
return (0);
}
static int
ocsp_opt_header(int argc, char **argv, int *argsused)
{
if (argc < 3 || argv[1] == NULL || argv[2] == NULL)
return (1);
if (!X509V3_add_value(argv[1], argv[2], &ocsp_config.headers)) {
ocsp_config.no_usage = 1;
return (1);
}
*argsused = 3;
return (0);
}
static int
ocsp_opt_host(char *arg)
{
if (ocsp_config.use_ssl != -1)
return (1);
ocsp_config.host = arg;
return (0);
}
static int
ocsp_opt_issuer(char *arg)
{
X509_free(ocsp_config.issuer);
ocsp_config.issuer = load_cert(bio_err, arg, FORMAT_PEM, NULL,
"issuer certificate");
if (ocsp_config.issuer == NULL) {
ocsp_config.no_usage = 1;
return (1);
}
return (0);
}
static int
ocsp_opt_ndays(char *arg)
{
const char *errstr = NULL;
ocsp_config.ndays = strtonum(arg, 0, INT_MAX, &errstr);
if (errstr != NULL) {
BIO_printf(bio_err, "Illegal update period %s: %s\n",
arg, errstr);
return (1);
}
return (0);
}
static int
ocsp_opt_nmin(char *arg)
{
const char *errstr = NULL;
ocsp_config.nmin = strtonum(arg, 0, INT_MAX, &errstr);
if (errstr != NULL) {
BIO_printf(bio_err, "Illegal update period %s: %s\n",
arg, errstr);
return (1);
}
if (ocsp_config.ndays != -1)
return (1);
ocsp_config.ndays = 0;
return (0);
}
static int
ocsp_opt_nrequest(char *arg)
{
const char *errstr = NULL;
ocsp_config.accept_count = strtonum(arg, 0, INT_MAX, &errstr);
if (errstr != NULL) {
BIO_printf(bio_err, "Illegal accept count %s: %s\n",
arg, errstr);
return (1);
}
return (0);
}
static int
ocsp_opt_port(char *arg)
{
if (ocsp_config.use_ssl != -1)
return (1);
ocsp_config.port = arg;
return (0);
}
static int
ocsp_opt_serial(char *arg)
{
if (ocsp_config.cert_id_md == NULL)
ocsp_config.cert_id_md = EVP_sha1();
if (!add_ocsp_serial(&ocsp_config.req, arg, ocsp_config.cert_id_md,
ocsp_config.issuer, ocsp_config.ids)) {
ocsp_config.no_usage = 1;
return (1);
}
if (!sk_OPENSSL_STRING_push(ocsp_config.reqnames, arg)) {
ocsp_config.no_usage = 1;
return (1);
}
return (0);
}
static int
ocsp_opt_status_age(char *arg)
{
const char *errstr = NULL;
ocsp_config.maxage = strtonum(arg, 0, LONG_MAX, &errstr);
if (errstr != NULL) {
BIO_printf(bio_err, "Illegal validity age %s: %s\n",
arg, errstr);
return (1);
}
return (0);
}
static int
ocsp_opt_text(void)
{
ocsp_config.req_text = 1;
ocsp_config.resp_text = 1;
return (0);
}
static int
ocsp_opt_timeout(char *arg)
{
const char *errstr = NULL;
ocsp_config.req_timeout = strtonum(arg, 0, INT_MAX, &errstr);
if (errstr != NULL) {
BIO_printf(bio_err, "Illegal timeout value %s: %s\n",
arg, errstr);
return (1);
}
return (0);
}
static int
ocsp_opt_url(char *arg)
{
if (ocsp_config.host == NULL && ocsp_config.port == NULL &&
ocsp_config.path == NULL) {
if (!OCSP_parse_url(arg, &ocsp_config.host, &ocsp_config.port,
&ocsp_config.path, &ocsp_config.use_ssl)) {
BIO_printf(bio_err, "Error parsing URL\n");
return (1);
}
}
return (0);
}
static int
ocsp_opt_vafile(char *arg)
{
ocsp_config.verify_certfile = arg;
ocsp_config.verify_flags |= OCSP_TRUSTOTHER;
return (0);
}
static int
ocsp_opt_validity_period(char *arg)
{
const char *errstr = NULL;
ocsp_config.nsec = strtonum(arg, 0, LONG_MAX, &errstr);
if (errstr != NULL) {
BIO_printf(bio_err, "Illegal validity period %s: %s\n",
arg, errstr);
return (1);
}
return (0);
}
static const struct option ocsp_options[] = {
{
.name = "CA",
.argname = "file",
.desc = "CA certificate corresponding to the revocation information",
.type = OPTION_ARG,
.opt.arg = &ocsp_config.rca_filename,
},
{
.name = "CAfile",
.argname = "file",
.desc = "Trusted certificates file",
.type = OPTION_ARG,
.opt.arg = &ocsp_config.CAfile,
},
{
.name = "CApath",
.argname = "directory",
.desc = "Trusted certificates directory",
.type = OPTION_ARG,
.opt.arg = &ocsp_config.CApath,
},
{
.name = "cert",
.argname = "file",
.desc = "Certificate to check",
.type = OPTION_ARG_FUNC,
.opt.argfunc = ocsp_opt_cert,
},
{
.name = "header",
.argname = "name value",
.desc = "Add the header name with the value to the request",
.type = OPTION_ARGV_FUNC,
.opt.argvfunc = ocsp_opt_header,
},
{
.name = "host",
.argname = "hostname:port",
.desc = "Send OCSP request to host on port",
.type = OPTION_ARG_FUNC,
.opt.argfunc = ocsp_opt_host,
},
{
.name = "ignore_err",
.desc = "Ignore the invalid response",
.type = OPTION_FLAG,
.opt.flag = &ocsp_config.ignore_err,
},
{
.name = "index",
.argname = "indexfile",
.desc = "Certificate status index file",
.type = OPTION_ARG,
.opt.arg = &ocsp_config.ridx_filename,
},
{
.name = "issuer",
.argname = "file",
.desc = "Issuer certificate",
.type = OPTION_ARG_FUNC,
.opt.argfunc = ocsp_opt_issuer,
},
{
.name = "ndays",
.argname = "days",
.desc = "Number of days before next update",
.type = OPTION_ARG_FUNC,
.opt.argfunc = ocsp_opt_ndays,
},
{
.name = "nmin",
.argname = "minutes",
.desc = "Number of minutes before next update",
.type = OPTION_ARG_FUNC,
.opt.argfunc = ocsp_opt_nmin,
},
{
.name = "no_cert_checks",
.desc = "Don't do additional checks on signing certificate",
.type = OPTION_UL_VALUE_OR,
.opt.ulvalue = &ocsp_config.verify_flags,
.ulvalue = OCSP_NOCHECKS,
},
{
.name = "no_cert_verify",
.desc = "Don't check signing certificate",
.type = OPTION_UL_VALUE_OR,
.opt.ulvalue = &ocsp_config.verify_flags,
.ulvalue = OCSP_NOVERIFY,
},
{
.name = "no_certs",
.desc = "Don't include any certificates in signed request",
.type = OPTION_UL_VALUE_OR,
.opt.ulvalue = &ocsp_config.sign_flags,
.ulvalue = OCSP_NOCERTS,
},
{
.name = "no_chain",
.desc = "Don't use certificates in the response",
.type = OPTION_UL_VALUE_OR,
.opt.ulvalue = &ocsp_config.verify_flags,
.ulvalue = OCSP_NOCHAIN,
},
{
.name = "no_explicit",
.desc = "Don't check the explicit trust for OCSP signing",
.type = OPTION_UL_VALUE_OR,
.opt.ulvalue = &ocsp_config.verify_flags,
.ulvalue = OCSP_NOEXPLICIT,
},
{
.name = "no_intern",
.desc = "Don't search certificates contained in response for signer",
.type = OPTION_UL_VALUE_OR,
.opt.ulvalue = &ocsp_config.verify_flags,
.ulvalue = OCSP_NOINTERN,
},
{
.name = "no_nonce",
.desc = "Don't add OCSP nonce to request",
.type = OPTION_VALUE,
.opt.value = &ocsp_config.add_nonce,
.value = 0,
},
{
.name = "no_signature_verify",
.desc = "Don't check signature on response",
.type = OPTION_UL_VALUE_OR,
.opt.ulvalue = &ocsp_config.verify_flags,
.ulvalue = OCSP_NOSIGS,
},
{
.name = "nonce",
.desc = "Add OCSP nonce to request",
.type = OPTION_VALUE,
.opt.value = &ocsp_config.add_nonce,
.value = 2,
},
{
.name = "noverify",
.desc = "Don't verify response at all",
.type = OPTION_FLAG,
.opt.flag = &ocsp_config.noverify,
},
{
.name = "nrequest",
.argname = "number",
.desc = "Number of requests to accept (default unlimited)",
.type = OPTION_ARG_FUNC,
.opt.argfunc = ocsp_opt_nrequest,
},
{
.name = "out",
.argname = "file",
.desc = "Output filename",
.type = OPTION_ARG,
.opt.arg = &ocsp_config.outfile,
},
{
.name = "path",
.argname = "path",
.desc = "Path to use in OCSP request",
.type = OPTION_ARG,
.opt.arg = &ocsp_config.path,
},
{
.name = "port",
.argname = "portnum",
.desc = "Port to run responder on",
.type = OPTION_ARG_FUNC,
.opt.argfunc = ocsp_opt_port,
},
{
.name = "req_text",
.desc = "Print text form of request",
.type = OPTION_FLAG,
.opt.flag = &ocsp_config.req_text,
},
{
.name = "reqin",
.argname = "file",
.desc = "Read DER encoded OCSP request from \"file\"",
.type = OPTION_ARG,
.opt.arg = &ocsp_config.reqin,
},
{
.name = "reqout",
.argname = "file",
.desc = "Write DER encoded OCSP request to \"file\"",
.type = OPTION_ARG,
.opt.arg = &ocsp_config.reqout,
},
{
.name = "resp_key_id",
.desc = "Identify response by signing certificate key ID",
.type = OPTION_UL_VALUE_OR,
.opt.ulvalue = &ocsp_config.rflags,
.ulvalue = OCSP_RESPID_KEY,
},
{
.name = "resp_no_certs",
.desc = "Don't include any certificates in response",
.type = OPTION_UL_VALUE_OR,
.opt.ulvalue = &ocsp_config.rflags,
.ulvalue = OCSP_NOCERTS,
},
{
.name = "resp_text",
.desc = "Print text form of response",
.type = OPTION_FLAG,
.opt.flag = &ocsp_config.resp_text,
},
{
.name = "respin",
.argname = "file",
.desc = "Read DER encoded OCSP response from \"file\"",
.type = OPTION_ARG,
.opt.arg = &ocsp_config.respin,
},
{
.name = "respout",
.argname = "file",
.desc = "Write DER encoded OCSP response to \"file\"",
.type = OPTION_ARG,
.opt.arg = &ocsp_config.respout,
},
{
.name = "rkey",
.argname = "file",
.desc = "Responder key to sign responses with",
.type = OPTION_ARG,
.opt.arg = &ocsp_config.rkeyfile,
},
{
.name = "rother",
.argname = "file",
.desc = "Other certificates to include in response",
.type = OPTION_ARG,
.opt.arg = &ocsp_config.rcertfile,
},
{
.name = "rsigner",
.argname = "file",
.desc = "Responder certificate to sign responses with",
.type = OPTION_ARG,
.opt.arg = &ocsp_config.rsignfile,
},
{
.name = "serial",
.argname = "num",
.desc = "Serial number to check",
.type = OPTION_ARG_FUNC,
.opt.argfunc = ocsp_opt_serial,
},
{
.name = "sign_other",
.argname = "file",
.desc = "Additional certificates to include in signed request",
.type = OPTION_ARG,
.opt.arg = &ocsp_config.sign_certfile,
},
{
.name = "signer",
.argname = "file",
.desc = "Certificate to sign OCSP request with",
.type = OPTION_ARG,
.opt.arg = &ocsp_config.signfile,
},
{
.name = "signkey",
.argname = "file",
.desc = "Private key to sign OCSP request with",
.type = OPTION_ARG,
.opt.arg = &ocsp_config.keyfile,
},
{
.name = "status_age",
.argname = "age",
.desc = "Maximum status age in seconds",
.type = OPTION_ARG_FUNC,
.opt.argfunc = ocsp_opt_status_age,
},
{
.name = "text",
.desc = "Print text form of request and response",
.type = OPTION_FUNC,
.opt.func = ocsp_opt_text,
},
{
.name = "timeout",
.argname = "seconds",
.desc = "Connection timeout to the OCSP responder in seconds",
.type = OPTION_ARG_FUNC,
.opt.argfunc = ocsp_opt_timeout,
},
{
.name = "trust_other",
.desc = "Don't verify additional certificates",
.type = OPTION_UL_VALUE_OR,
.opt.ulvalue = &ocsp_config.verify_flags,
.ulvalue = OCSP_TRUSTOTHER,
},
{
.name = "url",
.argname = "responder_url",
.desc = "OCSP responder URL",
.type = OPTION_ARG_FUNC,
.opt.argfunc = ocsp_opt_url,
},
{
.name = "VAfile",
.argname = "file",
.desc = "Explicitly trusted responder certificates",
.type = OPTION_ARG_FUNC,
.opt.argfunc = ocsp_opt_vafile,
},
{
.name = "validity_period",
.argname = "n",
.desc = "Maximum validity discrepancy in seconds",
.type = OPTION_ARG_FUNC,
.opt.argfunc = ocsp_opt_validity_period,
},
{
.name = "verify_other",
.argname = "file",
.desc = "Additional certificates to search for signer",
.type = OPTION_ARG,
.opt.arg = &ocsp_config.verify_certfile,
},
{
.name = NULL,
.desc = "",
.type = OPTION_ARGV_FUNC,
.opt.argvfunc = ocsp_opt_cert_id_md,
},
{ NULL },
};
static void
ocsp_usage(void)
{
fprintf(stderr, "usage: ocsp "
"[-CA file] [-CAfile file] [-CApath directory] [-cert file]\n"
" [-dgst alg] [-header name value] [-host hostname:port]\n"
" [-ignore_err] [-index indexfile] [-issuer file]\n"
" [-ndays days] [-nmin minutes] [-no_cert_checks]\n"
" [-no_cert_verify] [-no_certs] [-no_chain] [-no_explicit]\n"
" [-no_intern] [-no_nonce] [-no_signature_verify] [-nonce]\n"
" [-noverify] [-nrequest number] [-out file] [-path path]\n"
" [-port portnum] [-req_text] [-reqin file] [-reqout file]\n"
" [-resp_key_id] [-resp_no_certs] [-resp_text] [-respin file]\n"
" [-respout file] [-rkey file] [-rother file] [-rsigner file]\n"
" [-serial num] [-sign_other file] [-signer file]\n"
" [-signkey file] [-status_age age] [-text]\n"
" [-timeout seconds] [-trust_other] [-url responder_url]\n"
" [-VAfile file] [-validity_period nsec] [-verify_other file]\n");
fprintf(stderr, "\n");
options_usage(ocsp_options);
fprintf(stderr, "\n");
}
int
ocsp_main(int argc, char **argv)
{
OCSP_RESPONSE *resp = NULL;
OCSP_BASICRESP *bs = NULL;
X509 *signer = NULL, *rsigner = NULL;
EVP_PKEY *key = NULL, *rkey = NULL;
BIO *acbio = NULL, *cbio = NULL;
BIO *derbio = NULL;
BIO *out = NULL;
X509_STORE *store = NULL;
STACK_OF(X509) *sign_other = NULL, *verify_other = NULL, *rother = NULL;
int ret = 1;
int badarg = 0;
int i;
X509 *rca_cert = NULL;
CA_DB *rdb = NULL;
if (single_execution) {
if (pledge("stdio cpath wpath rpath inet dns tty", NULL) == -1) {
perror("pledge");
exit(1);
}
}
memset(&ocsp_config, 0, sizeof(ocsp_config));
ocsp_config.accept_count = -1;
ocsp_config.add_nonce = 1;
if ((ocsp_config.ids = sk_OCSP_CERTID_new_null()) == NULL)
goto end;
ocsp_config.maxage = -1;
ocsp_config.ndays = -1;
ocsp_config.nsec = MAX_VALIDITY_PERIOD;
ocsp_config.req_timeout = -1;
if ((ocsp_config.reqnames = sk_OPENSSL_STRING_new_null()) == NULL)
goto end;
ocsp_config.use_ssl = -1;
if (options_parse(argc, argv, ocsp_options, NULL, NULL) != 0) {
if (ocsp_config.no_usage)
goto end;
else
badarg = 1;
}
/* Have we anything to do? */
if (!ocsp_config.req && !ocsp_config.reqin && !ocsp_config.respin &&
!(ocsp_config.port && ocsp_config.ridx_filename))
badarg = 1;
if (badarg) {
ocsp_usage();
goto end;
}
if (ocsp_config.outfile)
out = BIO_new_file(ocsp_config.outfile, "w");
else
out = BIO_new_fp(stdout, BIO_NOCLOSE);
if (!out) {
BIO_printf(bio_err, "Error opening output file\n");
goto end;
}
if (!ocsp_config.req && (ocsp_config.add_nonce != 2))
ocsp_config.add_nonce = 0;
if (!ocsp_config.req && ocsp_config.reqin) {
derbio = BIO_new_file(ocsp_config.reqin, "rb");
if (!derbio) {
BIO_printf(bio_err,
"Error Opening OCSP request file\n");
goto end;
}
ocsp_config.req = d2i_OCSP_REQUEST_bio(derbio, NULL);
BIO_free(derbio);
if (!ocsp_config.req) {
BIO_printf(bio_err, "Error reading OCSP request\n");
goto end;
}
}
if (!ocsp_config.req && ocsp_config.port) {
acbio = init_responder(ocsp_config.port);
if (!acbio)
goto end;
}
if (ocsp_config.rsignfile && !rdb) {
if (!ocsp_config.rkeyfile)
ocsp_config.rkeyfile = ocsp_config.rsignfile;
rsigner = load_cert(bio_err, ocsp_config.rsignfile, FORMAT_PEM,
NULL, "responder certificate");
if (!rsigner) {
BIO_printf(bio_err,
"Error loading responder certificate\n");
goto end;
}
rca_cert = load_cert(bio_err, ocsp_config.rca_filename,
FORMAT_PEM, NULL, "CA certificate");
if (ocsp_config.rcertfile) {
rother = load_certs(bio_err, ocsp_config.rcertfile,
FORMAT_PEM, NULL, "responder other certificates");
if (!rother)
goto end;
}
rkey = load_key(bio_err, ocsp_config.rkeyfile, FORMAT_PEM, 0,
NULL, "responder private key");
if (!rkey)
goto end;
}
if (acbio)
BIO_printf(bio_err, "Waiting for OCSP client connections...\n");
redo_accept:
if (acbio) {
if (!do_responder(&ocsp_config.req, &cbio, acbio,
ocsp_config.port))
goto end;
if (!ocsp_config.req) {
resp = OCSP_response_create(
OCSP_RESPONSE_STATUS_MALFORMEDREQUEST, NULL);
send_ocsp_response(cbio, resp);
goto done_resp;
}
}
if (!ocsp_config.req &&
(ocsp_config.signfile || ocsp_config.reqout || ocsp_config.host ||
ocsp_config.add_nonce || ocsp_config.ridx_filename)) {
BIO_printf(bio_err,
"Need an OCSP request for this operation!\n");
goto end;
}
if (ocsp_config.req && ocsp_config.add_nonce)
OCSP_request_add1_nonce(ocsp_config.req, NULL, -1);
if (ocsp_config.signfile) {
if (!ocsp_config.keyfile)
ocsp_config.keyfile = ocsp_config.signfile;
signer = load_cert(bio_err, ocsp_config.signfile, FORMAT_PEM,
NULL, "signer certificate");
if (!signer) {
BIO_printf(bio_err,
"Error loading signer certificate\n");
goto end;
}
if (ocsp_config.sign_certfile) {
sign_other = load_certs(bio_err,
ocsp_config.sign_certfile, FORMAT_PEM, NULL,
"signer certificates");
if (!sign_other)
goto end;
}
key = load_key(bio_err, ocsp_config.keyfile, FORMAT_PEM, 0,
NULL, "signer private key");
if (!key)
goto end;
if (!OCSP_request_sign(ocsp_config.req, signer, key, NULL,
sign_other, ocsp_config.sign_flags)) {
BIO_printf(bio_err, "Error signing OCSP request\n");
goto end;
}
}
if (ocsp_config.req_text && ocsp_config.req)
OCSP_REQUEST_print(out, ocsp_config.req, 0);
if (ocsp_config.reqout) {
derbio = BIO_new_file(ocsp_config.reqout, "wb");
if (!derbio) {
BIO_printf(bio_err, "Error opening file %s\n",
ocsp_config.reqout);
goto end;
}
i2d_OCSP_REQUEST_bio(derbio, ocsp_config.req);
BIO_free(derbio);
}
if (ocsp_config.ridx_filename && (!rkey || !rsigner || !rca_cert)) {
BIO_printf(bio_err,
"Need a responder certificate, key and CA for this operation!\n");
goto end;
}
if (ocsp_config.ridx_filename && !rdb) {
rdb = load_index(ocsp_config.ridx_filename, NULL);
if (!rdb)
goto end;
if (!index_index(rdb))
goto end;
}
if (rdb) {
i = make_ocsp_response(&resp, ocsp_config.req, rdb, rca_cert,
rsigner, rkey, rother, ocsp_config.rflags,
ocsp_config.nmin, ocsp_config.ndays);
if (cbio)
send_ocsp_response(cbio, resp);
} else if (ocsp_config.host) {
resp = process_responder(bio_err, ocsp_config.req,
ocsp_config.host,
ocsp_config.path ? ocsp_config.path : "/",
ocsp_config.port, ocsp_config.use_ssl, ocsp_config.headers,
ocsp_config.req_timeout);
if (!resp)
goto end;
} else if (ocsp_config.respin) {
derbio = BIO_new_file(ocsp_config.respin, "rb");
if (!derbio) {
BIO_printf(bio_err,
"Error Opening OCSP response file\n");
goto end;
}
resp = d2i_OCSP_RESPONSE_bio(derbio, NULL);
BIO_free(derbio);
if (!resp) {
BIO_printf(bio_err, "Error reading OCSP response\n");
goto end;
}
} else {
ret = 0;
goto end;
}
done_resp:
if (ocsp_config.respout) {
derbio = BIO_new_file(ocsp_config.respout, "wb");
if (!derbio) {
BIO_printf(bio_err, "Error opening file %s\n",
ocsp_config.respout);
goto end;
}
i2d_OCSP_RESPONSE_bio(derbio, resp);
BIO_free(derbio);
}
i = OCSP_response_status(resp);
if (i != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
BIO_printf(bio_err, "Responder Error: %s (%d)\n",
OCSP_response_status_str(i), i);
if (ocsp_config.ignore_err)
goto redo_accept;
ret = 1;
goto end;
}
if (ocsp_config.resp_text)
OCSP_RESPONSE_print(out, resp, 0);
/* If running as responder don't verify our own response */
if (cbio) {
if (ocsp_config.accept_count > 0)
ocsp_config.accept_count--;
/* Redo if more connections needed */
if (ocsp_config.accept_count) {
BIO_free_all(cbio);
cbio = NULL;
OCSP_REQUEST_free(ocsp_config.req);
ocsp_config.req = NULL;
OCSP_RESPONSE_free(resp);
resp = NULL;
goto redo_accept;
}
goto end;
}
if (!store)
store = setup_verify(bio_err, ocsp_config.CAfile,
ocsp_config.CApath);
if (!store)
goto end;
if (ocsp_config.verify_certfile) {
verify_other = load_certs(bio_err, ocsp_config.verify_certfile,
FORMAT_PEM, NULL, "validator certificate");
if (!verify_other)
goto end;
}
bs = OCSP_response_get1_basic(resp);
if (!bs) {
BIO_printf(bio_err, "Error parsing response\n");
goto end;
}
if (!ocsp_config.noverify) {
if (ocsp_config.req &&
((i = OCSP_check_nonce(ocsp_config.req, bs)) <= 0)) {
if (i == -1) {
BIO_printf(bio_err,
"WARNING: no nonce in response\n");
} else {
BIO_printf(bio_err, "Nonce Verify error\n");
goto end;
}
}
i = OCSP_basic_verify(bs, verify_other, store,
ocsp_config.verify_flags);
if (i < 0)
i = OCSP_basic_verify(bs, NULL, store, 0);
if (i <= 0) {
BIO_printf(bio_err, "Response Verify Failure\n");
ERR_print_errors(bio_err);
} else {
BIO_printf(bio_err, "Response verify OK\n");
}
}
if (!print_ocsp_summary(out, bs, ocsp_config.req, ocsp_config.reqnames,
ocsp_config.ids, ocsp_config.nsec, ocsp_config.maxage))
goto end;
ret = 0;
end:
ERR_print_errors(bio_err);
X509_free(signer);
X509_STORE_free(store);
EVP_PKEY_free(key);
EVP_PKEY_free(rkey);
X509_free(ocsp_config.issuer);
X509_free(ocsp_config.cert);
X509_free(rsigner);
X509_free(rca_cert);
free_index(rdb);
BIO_free_all(cbio);
BIO_free_all(acbio);
BIO_free(out);
OCSP_REQUEST_free(ocsp_config.req);
OCSP_RESPONSE_free(resp);
OCSP_BASICRESP_free(bs);
sk_OPENSSL_STRING_free(ocsp_config.reqnames);
sk_OCSP_CERTID_free(ocsp_config.ids);
sk_X509_pop_free(sign_other, X509_free);
sk_X509_pop_free(verify_other, X509_free);
sk_CONF_VALUE_pop_free(ocsp_config.headers, X509V3_conf_free);
if (ocsp_config.use_ssl != -1) {
free(ocsp_config.host);
free(ocsp_config.port);
free(ocsp_config.path);
}
return (ret);
}
static int
add_ocsp_cert(OCSP_REQUEST **req, X509 *cert, const EVP_MD *cert_id_md,
X509 *issuer, STACK_OF(OCSP_CERTID) *ids)
{
OCSP_CERTID *id;
if (!issuer) {
BIO_printf(bio_err, "No issuer certificate specified\n");
return 0;
}
if (!*req)
*req = OCSP_REQUEST_new();
if (!*req)
goto err;
id = OCSP_cert_to_id(cert_id_md, cert, issuer);
if (!id || !sk_OCSP_CERTID_push(ids, id))
goto err;
if (!OCSP_request_add0_id(*req, id))
goto err;
return 1;
err:
BIO_printf(bio_err, "Error Creating OCSP request\n");
return 0;
}
static int
add_ocsp_serial(OCSP_REQUEST **req, char *serial, const EVP_MD *cert_id_md,
X509 *issuer, STACK_OF(OCSP_CERTID) *ids)
{
OCSP_CERTID *id;
X509_NAME *iname;
ASN1_BIT_STRING *ikey;
ASN1_INTEGER *sno;
if (!issuer) {
BIO_printf(bio_err, "No issuer certificate specified\n");
return 0;
}
if (!*req)
*req = OCSP_REQUEST_new();
if (!*req)
goto err;
iname = X509_get_subject_name(issuer);
ikey = X509_get0_pubkey_bitstr(issuer);
sno = s2i_ASN1_INTEGER(NULL, serial);
if (!sno) {
BIO_printf(bio_err, "Error converting serial number %s\n",
serial);
return 0;
}
id = OCSP_cert_id_new(cert_id_md, iname, ikey, sno);
ASN1_INTEGER_free(sno);
if (!id || !sk_OCSP_CERTID_push(ids, id))
goto err;
if (!OCSP_request_add0_id(*req, id))
goto err;
return 1;
err:
BIO_printf(bio_err, "Error Creating OCSP request\n");
return 0;
}
static int
print_ocsp_summary(BIO *out, OCSP_BASICRESP *bs, OCSP_REQUEST *req,
STACK_OF(OPENSSL_STRING) *names, STACK_OF(OCSP_CERTID) *ids, long nsec,
long maxage)
{
OCSP_CERTID *id;
char *name;
int i;
int status, reason;
ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
if (!bs || !req || !sk_OPENSSL_STRING_num(names) ||
!sk_OCSP_CERTID_num(ids))
return 1;
for (i = 0; i < sk_OCSP_CERTID_num(ids); i++) {
id = sk_OCSP_CERTID_value(ids, i);
name = sk_OPENSSL_STRING_value(names, i);
BIO_printf(out, "%s: ", name);
if (!OCSP_resp_find_status(bs, id, &status, &reason,
&rev, &thisupd, &nextupd)) {
BIO_puts(out, "ERROR: No Status found.\n");
continue;
}
/*
* Check validity: if invalid write to output BIO so we know
* which response this refers to.
*/
if (!OCSP_check_validity(thisupd, nextupd, nsec, maxage)) {
BIO_puts(out, "WARNING: Status times invalid.\n");
ERR_print_errors(out);
}
BIO_printf(out, "%s\n", OCSP_cert_status_str(status));
BIO_puts(out, "\tThis Update: ");
ASN1_GENERALIZEDTIME_print(out, thisupd);
BIO_puts(out, "\n");
if (nextupd) {
BIO_puts(out, "\tNext Update: ");
ASN1_GENERALIZEDTIME_print(out, nextupd);
BIO_puts(out, "\n");
}
if (status != V_OCSP_CERTSTATUS_REVOKED)
continue;
if (reason != -1)
BIO_printf(out, "\tReason: %s\n",
OCSP_crl_reason_str(reason));
BIO_puts(out, "\tRevocation Time: ");
ASN1_GENERALIZEDTIME_print(out, rev);
BIO_puts(out, "\n");
}
return 1;
}
static int
make_ocsp_response(OCSP_RESPONSE **resp, OCSP_REQUEST *req, CA_DB *db,
X509 *ca, X509 *rcert, EVP_PKEY *rkey, STACK_OF(X509) *rother,
unsigned long flags, int nmin, int ndays)
{
ASN1_TIME *thisupd = NULL, *nextupd = NULL;
OCSP_CERTID *cid, *ca_id = NULL;
OCSP_BASICRESP *bs = NULL;
int i, id_count, ret = 1;
id_count = OCSP_request_onereq_count(req);
if (id_count <= 0) {
*resp = OCSP_response_create(
OCSP_RESPONSE_STATUS_MALFORMEDREQUEST, NULL);
goto end;
}
bs = OCSP_BASICRESP_new();
thisupd = X509_gmtime_adj(NULL, 0);
if (ndays != -1)
nextupd = X509_gmtime_adj(NULL, nmin * 60 + ndays * 3600 * 24);
/* Examine each certificate id in the request */
for (i = 0; i < id_count; i++) {
OCSP_ONEREQ *one;
ASN1_INTEGER *serial;
char **inf;
ASN1_OBJECT *cert_id_md_oid;
const EVP_MD *cert_id_md;
one = OCSP_request_onereq_get0(req, i);
cid = OCSP_onereq_get0_id(one);
OCSP_id_get0_info(NULL, &cert_id_md_oid, NULL, NULL, cid);
cert_id_md = EVP_get_digestbyobj(cert_id_md_oid);
if (!cert_id_md) {
*resp = OCSP_response_create(
OCSP_RESPONSE_STATUS_INTERNALERROR, NULL);
goto end;
}
OCSP_CERTID_free(ca_id);
ca_id = OCSP_cert_to_id(cert_id_md, NULL, ca);
/* Is this request about our CA? */
if (OCSP_id_issuer_cmp(ca_id, cid)) {
OCSP_basic_add1_status(bs, cid,
V_OCSP_CERTSTATUS_UNKNOWN, 0, NULL,
thisupd, nextupd);
continue;
}
OCSP_id_get0_info(NULL, NULL, NULL, &serial, cid);
inf = lookup_serial(db, serial);
if (!inf) {
OCSP_basic_add1_status(bs, cid,
V_OCSP_CERTSTATUS_UNKNOWN, 0, NULL,
thisupd, nextupd);
} else if (inf[DB_type][0] == DB_TYPE_VAL) {
OCSP_basic_add1_status(bs, cid,
V_OCSP_CERTSTATUS_GOOD, 0, NULL,
thisupd, nextupd);
} else if (inf[DB_type][0] == DB_TYPE_REV) {
ASN1_OBJECT *inst = NULL;
ASN1_TIME *revtm = NULL;
ASN1_GENERALIZEDTIME *invtm = NULL;
OCSP_SINGLERESP *single;
int reason = -1;
unpack_revinfo(&revtm, &reason, &inst, &invtm,
inf[DB_rev_date]);
single = OCSP_basic_add1_status(bs, cid,
V_OCSP_CERTSTATUS_REVOKED,
reason, revtm,
thisupd, nextupd);
if (invtm)
OCSP_SINGLERESP_add1_ext_i2d(single,
NID_invalidity_date, invtm, 0, 0);
else if (inst)
OCSP_SINGLERESP_add1_ext_i2d(single,
NID_hold_instruction_code, inst, 0, 0);
ASN1_OBJECT_free(inst);
ASN1_TIME_free(revtm);
ASN1_GENERALIZEDTIME_free(invtm);
}
}
OCSP_copy_nonce(bs, req);
OCSP_basic_sign(bs, rcert, rkey, NULL, rother, flags);
*resp = OCSP_response_create(OCSP_RESPONSE_STATUS_SUCCESSFUL, bs);
end:
ASN1_TIME_free(thisupd);
ASN1_TIME_free(nextupd);
OCSP_CERTID_free(ca_id);
OCSP_BASICRESP_free(bs);
return ret;
}
static char **
lookup_serial(CA_DB *db, ASN1_INTEGER *ser)
{
int i;
BIGNUM *bn = NULL;
char *itmp, *row[DB_NUMBER], **rrow;
for (i = 0; i < DB_NUMBER; i++)
row[i] = NULL;
bn = ASN1_INTEGER_to_BN(ser, NULL);
OPENSSL_assert(bn); /* FIXME: should report an error at this
* point and abort */
if (BN_is_zero(bn))
itmp = strdup("00");
else
itmp = BN_bn2hex(bn);
row[DB_serial] = itmp;
BN_free(bn);
rrow = TXT_DB_get_by_index(db->db, DB_serial, row);
free(itmp);
return rrow;
}
/* Quick and dirty OCSP server: read in and parse input request */
static BIO *
init_responder(char *port)
{
BIO *acbio = NULL, *bufbio = NULL;
bufbio = BIO_new(BIO_f_buffer());
if (!bufbio)
goto err;
acbio = BIO_new_accept(port);
if (!acbio)
goto err;
BIO_set_bind_mode(acbio, BIO_BIND_REUSEADDR);
BIO_set_accept_bios(acbio, bufbio);
bufbio = NULL;
if (BIO_do_accept(acbio) <= 0) {
BIO_printf(bio_err, "Error setting up accept BIO\n");
ERR_print_errors(bio_err);
goto err;
}
return acbio;
err:
BIO_free_all(acbio);
BIO_free(bufbio);
return NULL;
}
static int
do_responder(OCSP_REQUEST **preq, BIO **pcbio, BIO *acbio, char *port)
{
int have_post = 0, len;
OCSP_REQUEST *req = NULL;
char inbuf[1024];
BIO *cbio = NULL;
if (BIO_do_accept(acbio) <= 0) {
BIO_printf(bio_err, "Error accepting connection\n");
ERR_print_errors(bio_err);
return 0;
}
cbio = BIO_pop(acbio);
*pcbio = cbio;
for (;;) {
len = BIO_gets(cbio, inbuf, sizeof inbuf);
if (len <= 0)
return 1;
/* Look for "POST" signalling start of query */
if (!have_post) {
if (strncmp(inbuf, "POST", 4)) {
BIO_printf(bio_err, "Invalid request\n");
return 1;
}
have_post = 1;
}
/* Look for end of headers */
if ((inbuf[0] == '\r') || (inbuf[0] == '\n'))
break;
}
/* Try to read OCSP request */
req = d2i_OCSP_REQUEST_bio(cbio, NULL);
if (!req) {
BIO_printf(bio_err, "Error parsing OCSP request\n");
ERR_print_errors(bio_err);
}
*preq = req;
return 1;
}
static int
send_ocsp_response(BIO *cbio, OCSP_RESPONSE *resp)
{
static const char http_resp[] =
"HTTP/1.0 200 OK\r\nContent-type: application/ocsp-response\r\n"
"Content-Length: %d\r\n\r\n";
if (!cbio)
return 0;
BIO_printf(cbio, http_resp, i2d_OCSP_RESPONSE(resp, NULL));
i2d_OCSP_RESPONSE_bio(cbio, resp);
(void) BIO_flush(cbio);
return 1;
}
static OCSP_RESPONSE *
query_responder(BIO *err, BIO *cbio, char *path, STACK_OF(CONF_VALUE) *headers,
const char *host, OCSP_REQUEST *req, int req_timeout)
{
int fd;
int rv;
int i;
int have_host = 0;
OCSP_REQ_CTX *ctx = NULL;
OCSP_RESPONSE *rsp = NULL;
struct pollfd pfd[1];
if (req_timeout != -1)
BIO_set_nbio(cbio, 1);
rv = BIO_do_connect(cbio);
if ((rv <= 0) && ((req_timeout == -1) || !BIO_should_retry(cbio))) {
BIO_puts(err, "Error connecting BIO\n");
return NULL;
}
if (BIO_get_fd(cbio, &fd) < 0) {
BIO_puts(err, "Can't get connection fd\n");
goto err;
}
if (req_timeout != -1 && rv <= 0) {
pfd[0].fd = fd;
pfd[0].events = POLLOUT;
rv = poll(pfd, 1, req_timeout * 1000);
if (rv == 0) {
BIO_puts(err, "Timeout on connect\n");
return NULL;
}
if (rv == -1) {
BIO_puts(err, "Poll error\n");
return NULL;
}
}
ctx = OCSP_sendreq_new(cbio, path, NULL, -1);
if (!ctx)
return NULL;
for (i = 0; i < sk_CONF_VALUE_num(headers); i++) {
CONF_VALUE *hdr = sk_CONF_VALUE_value(headers, i);
if (strcasecmp("host", hdr->name) == 0)
have_host = 1;
if (!OCSP_REQ_CTX_add1_header(ctx, hdr->name, hdr->value))
goto err;
}
if (!have_host) {
if (!OCSP_REQ_CTX_add1_header(ctx, "Host", host))
goto err;
}
if (!OCSP_REQ_CTX_set1_req(ctx, req))
goto err;
for (;;) {
rv = OCSP_sendreq_nbio(&rsp, ctx);
if (rv != -1)
break;
if (req_timeout == -1)
continue;
pfd[0].fd = fd;
if (BIO_should_read(cbio)) {
pfd[0].events = POLLIN;
} else if (BIO_should_write(cbio)) {
pfd[0].events = POLLOUT;
} else {
BIO_puts(err, "Unexpected retry condition\n");
goto err;
}
rv = poll(pfd, 1, req_timeout * 1000);
if (rv == 0) {
BIO_puts(err, "Timeout on request\n");
break;
}
if (rv == -1 || (pfd[0].revents & (POLLERR|POLLNVAL))) {
BIO_puts(err, "Poll error\n");
break;
}
}
err:
OCSP_REQ_CTX_free(ctx);
return rsp;
}
OCSP_RESPONSE *
process_responder(BIO *err, OCSP_REQUEST *req, char *host, char *path,
char *port, int use_ssl, STACK_OF(CONF_VALUE) *headers, int req_timeout)
{
BIO *cbio = NULL;
SSL_CTX *ctx = NULL;
OCSP_RESPONSE *resp = NULL;
cbio = BIO_new_connect(host);
if (!cbio) {
BIO_printf(err, "Error creating connect BIO\n");
goto end;
}
if (port)
BIO_set_conn_port(cbio, port);
if (use_ssl == 1) {
BIO *sbio;
ctx = SSL_CTX_new(TLS_client_method());
if (ctx == NULL) {
BIO_printf(err, "Error creating SSL context.\n");
goto end;
}
SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
sbio = BIO_new_ssl(ctx, 1);
cbio = BIO_push(sbio, cbio);
}
resp = query_responder(err, cbio, path, headers, host, req, req_timeout);
if (!resp)
BIO_printf(bio_err, "Error querying OCSP responder\n");
end:
BIO_free_all(cbio);
SSL_CTX_free(ctx);
return resp;
}
#endif