haproxy/src/ssl_ocsp.c
Remi Tricot-Le Breton 10f113ec55 MINOR: ssl: Reinsert updated ocsp response later in tree in case of http error
When updating an OCSP response, in case of HTTP error (host unreachable
for instance) we do not want to reinsert the entry at the same place in
the update tree otherwise we might retry immediately the update of the
same response. This patch adds an arbitrary 1min time to the next_update
of a response in such a case.
After an HTTP error, instead of waking the update task up after an
arbitrary 10s time, we look for the first entry of the update tree and
sleep for the apropriate time.
2023-01-12 13:13:45 +01:00

1683 lines
45 KiB
C

/*
* SSL/TLS OCSP-related functions
*
* Copyright (C) 2022 HAProxy Technologies, Remi Tricot-Le Breton <rlebreton@haproxy.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Acknowledgement:
* We'd like to specially thank the Stud project authors for a very clean
* and well documented code which helped us understand how the OpenSSL API
* ought to be used in non-blocking mode. This is one difficult part which
* is not easy to get from the OpenSSL doc, and reading the Stud code made
* it much more obvious than the examples in the OpenSSL package. Keep up
* the good works, guys !
*
* Stud is an extremely efficient and scalable SSL/TLS proxy which combines
* particularly well with haproxy. For more info about this project, visit :
* https://github.com/bumptech/stud
*
*/
/* Note: do NOT include openssl/xxx.h here, do it in openssl-compat.h */
#define _GNU_SOURCE
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <import/ebpttree.h>
#include <import/ebsttree.h>
#include <import/lru.h>
#include <haproxy/api.h>
#include <haproxy/applet.h>
#include <haproxy/arg.h>
#include <haproxy/base64.h>
#include <haproxy/channel.h>
#include <haproxy/chunk.h>
#include <haproxy/cli.h>
#include <haproxy/connection.h>
#include <haproxy/dynbuf.h>
#include <haproxy/errors.h>
#include <haproxy/fd.h>
#include <haproxy/freq_ctr.h>
#include <haproxy/frontend.h>
#include <haproxy/global.h>
#include <haproxy/http_rules.h>
#include <haproxy/log.h>
#include <haproxy/openssl-compat.h>
#include <haproxy/pattern-t.h>
#include <haproxy/proto_tcp.h>
#include <haproxy/proxy.h>
#include <haproxy/sample.h>
#include <haproxy/sc_strm.h>
#include <haproxy/quic_conn.h>
#include <haproxy/quic_tp.h>
#include <haproxy/server.h>
#include <haproxy/shctx.h>
#include <haproxy/ssl_ckch.h>
#include <haproxy/ssl_crtlist.h>
#include <haproxy/ssl_sock.h>
#include <haproxy/ssl_utils.h>
#include <haproxy/stats.h>
#include <haproxy/stconn.h>
#include <haproxy/stream-t.h>
#include <haproxy/task.h>
#include <haproxy/ticks.h>
#include <haproxy/time.h>
#include <haproxy/tools.h>
#include <haproxy/vars.h>
#include <haproxy/xxhash.h>
#include <haproxy/istbuf.h>
#include <haproxy/ssl_ocsp-t.h>
#include <haproxy/http_client.h>
/* ***** READ THIS before adding code here! *****
*
* Due to API incompatibilities between multiple OpenSSL versions and their
* derivatives, it's often tempting to add macros to (re-)define certain
* symbols. Please do not do this here, and do it in common/openssl-compat.h
* exclusively so that the whole code consistently uses the same macros.
*
* Whenever possible if a macro is missing in certain versions, it's better
* to conditionally define it in openssl-compat.h than using lots of ifdefs.
*/
#ifndef OPENSSL_NO_OCSP
int ocsp_ex_index = -1;
int ssl_sock_get_ocsp_arg_kt_index(int evp_keytype)
{
switch (evp_keytype) {
case EVP_PKEY_RSA:
return 2;
case EVP_PKEY_DSA:
return 0;
case EVP_PKEY_EC:
return 1;
}
return -1;
}
/*
* Callback used to set OCSP status extension content in server hello.
*/
int ssl_sock_ocsp_stapling_cbk(SSL *ssl, void *arg)
{
struct certificate_ocsp *ocsp;
struct ocsp_cbk_arg *ocsp_arg;
char *ssl_buf;
SSL_CTX *ctx;
EVP_PKEY *ssl_pkey;
int key_type;
int index;
ctx = SSL_get_SSL_CTX(ssl);
if (!ctx)
return SSL_TLSEXT_ERR_NOACK;
ocsp_arg = SSL_CTX_get_ex_data(ctx, ocsp_ex_index);
if (!ocsp_arg)
return SSL_TLSEXT_ERR_NOACK;
ssl_pkey = SSL_get_privatekey(ssl);
if (!ssl_pkey)
return SSL_TLSEXT_ERR_NOACK;
key_type = EVP_PKEY_base_id(ssl_pkey);
if (ocsp_arg->is_single && ocsp_arg->single_kt == key_type)
ocsp = ocsp_arg->s_ocsp;
else {
/* For multiple certs per context, we have to find the correct OCSP response based on
* the certificate type
*/
index = ssl_sock_get_ocsp_arg_kt_index(key_type);
if (index < 0)
return SSL_TLSEXT_ERR_NOACK;
ocsp = ocsp_arg->m_ocsp[index];
}
if (!ocsp ||
!ocsp->response.area ||
!ocsp->response.data ||
(ocsp->expire < now.tv_sec))
return SSL_TLSEXT_ERR_NOACK;
ssl_buf = OPENSSL_malloc(ocsp->response.data);
if (!ssl_buf)
return SSL_TLSEXT_ERR_NOACK;
memcpy(ssl_buf, ocsp->response.area, ocsp->response.data);
SSL_set_tlsext_status_ocsp_resp(ssl, (unsigned char*)ssl_buf, ocsp->response.data);
return SSL_TLSEXT_ERR_OK;
}
#endif /* !defined(OPENSSL_NO_OCSP) */
#if (defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP)
struct eb_root cert_ocsp_tree = EB_ROOT_UNIQUE;
__decl_thread(HA_SPINLOCK_T ocsp_tree_lock);
struct eb_root ocsp_update_tree = EB_ROOT; /* updatable ocsp responses sorted by next_update in absolute time */
#define SSL_OCSP_UPDATE_DELAY_MAX 60*60 /* 1H */
#define SSL_OCSP_UPDATE_DELAY_MIN 5*60 /* 5 minutes */
#define SSL_OCSP_UPDATE_MARGIN 60 /* 1 minute */
#define SSL_OCSP_HTTP_ERR_REPLAY 60 /* 1 minute */
/* This function starts to check if the OCSP response (in DER format) contained
* in chunk 'ocsp_response' is valid (else exits on error).
* If 'cid' is not NULL, it will be compared to the OCSP certificate ID
* contained in the OCSP Response and exits on error if no match.
* If it's a valid OCSP Response:
* If 'ocsp' is not NULL, the chunk is copied in the OCSP response's container
* pointed by 'ocsp'.
* If 'ocsp' is NULL, the function looks up into the OCSP response's
* containers tree (using as index the ASN1 form of the OCSP Certificate ID extracted
* from the response) and exits on error if not found. Finally, If an OCSP response is
* already present in the container, it will be overwritten.
*
* Note: OCSP response containing more than one OCSP Single response is not
* considered valid.
*
* Returns 0 on success, 1 in error case.
*/
int ssl_sock_load_ocsp_response(struct buffer *ocsp_response,
struct certificate_ocsp *ocsp,
OCSP_CERTID *cid, char **err)
{
OCSP_RESPONSE *resp;
OCSP_BASICRESP *bs = NULL;
OCSP_SINGLERESP *sr;
OCSP_CERTID *id;
unsigned char *p = (unsigned char *) ocsp_response->area;
int rc , count_sr;
ASN1_GENERALIZEDTIME *revtime, *thisupd, *nextupd = NULL;
int reason;
int ret = 1;
#ifdef HAVE_ASN1_TIME_TO_TM
struct tm nextupd_tm = {0};
#endif
resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p,
ocsp_response->data);
if (!resp) {
memprintf(err, "Unable to parse OCSP response");
goto out;
}
rc = OCSP_response_status(resp);
if (rc != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
memprintf(err, "OCSP response status not successful");
goto out;
}
bs = OCSP_response_get1_basic(resp);
if (!bs) {
memprintf(err, "Failed to get basic response from OCSP Response");
goto out;
}
count_sr = OCSP_resp_count(bs);
if (count_sr > 1) {
memprintf(err, "OCSP response ignored because contains multiple single responses (%d)", count_sr);
goto out;
}
sr = OCSP_resp_get0(bs, 0);
if (!sr) {
memprintf(err, "Failed to get OCSP single response");
goto out;
}
id = (OCSP_CERTID*)OCSP_SINGLERESP_get0_id(sr);
rc = OCSP_single_get0_status(sr, &reason, &revtime, &thisupd, &nextupd);
if (rc != V_OCSP_CERTSTATUS_GOOD && rc != V_OCSP_CERTSTATUS_REVOKED) {
memprintf(err, "OCSP single response: certificate status is unknown");
goto out;
}
if (!nextupd) {
memprintf(err, "OCSP single response: missing nextupdate");
goto out;
}
rc = OCSP_check_validity(thisupd, nextupd, OCSP_MAX_RESPONSE_TIME_SKEW, -1);
if (!rc) {
memprintf(err, "OCSP single response: no longer valid.");
goto out;
}
if (cid) {
if (OCSP_id_cmp(id, cid)) {
memprintf(err, "OCSP single response: Certificate ID does not match certificate and issuer");
goto out;
}
}
if (!ocsp) {
unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH];
unsigned char *p;
rc = i2d_OCSP_CERTID(id, NULL);
if (!rc) {
memprintf(err, "OCSP single response: Unable to encode Certificate ID");
goto out;
}
if (rc > OCSP_MAX_CERTID_ASN1_LENGTH) {
memprintf(err, "OCSP single response: Certificate ID too long");
goto out;
}
p = key;
memset(key, 0, OCSP_MAX_CERTID_ASN1_LENGTH);
i2d_OCSP_CERTID(id, &p);
HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
ocsp = (struct certificate_ocsp *)ebmb_lookup(&cert_ocsp_tree, key, OCSP_MAX_CERTID_ASN1_LENGTH);
if (!ocsp) {
HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
memprintf(err, "OCSP single response: Certificate ID does not match any certificate or issuer");
goto out;
}
HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
}
/* According to comments on "chunk_dup", the
previous chunk buffer will be freed */
if (!chunk_dup(&ocsp->response, ocsp_response)) {
memprintf(err, "OCSP response: Memory allocation error");
goto out;
}
#ifdef HAVE_ASN1_TIME_TO_TM
if (ASN1_TIME_to_tm(nextupd, &nextupd_tm) == 0) {
memprintf(err, "OCSP single response: Invalid \"Next Update\" time");
goto out;
}
ocsp->expire = my_timegm(&nextupd_tm) - OCSP_MAX_RESPONSE_TIME_SKEW;
#else
ocsp->expire = asn1_generalizedtime_to_epoch(nextupd) - OCSP_MAX_RESPONSE_TIME_SKEW;
if (ocsp->expire < 0) {
memprintf(err, "OCSP single response: Invalid \"Next Update\" time");
goto out;
}
#endif
ret = 0;
out:
ERR_clear_error();
if (bs)
OCSP_BASICRESP_free(bs);
if (resp)
OCSP_RESPONSE_free(resp);
return ret;
}
/*
* External function use to update the OCSP response in the OCSP response's
* containers tree. The chunk 'ocsp_response' must contain the OCSP response
* to update in DER format.
*
* Returns 0 on success, 1 in error case.
*/
int ssl_sock_update_ocsp_response(struct buffer *ocsp_response, char **err)
{
return ssl_sock_load_ocsp_response(ocsp_response, NULL, NULL, err);
}
#if !defined OPENSSL_IS_BORINGSSL
/*
* Decrease the refcount of the struct ocsp_response and frees it if it's not
* used anymore. Also removes it from the tree if free'd.
*/
void ssl_sock_free_ocsp(struct certificate_ocsp *ocsp)
{
if (!ocsp)
return;
HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
ocsp->refcount--;
if (ocsp->refcount <= 0) {
ebmb_delete(&ocsp->key);
eb64_delete(&ocsp->next_update);
X509_free(ocsp->issuer);
ocsp->issuer = NULL;
sk_X509_pop_free(ocsp->chain, X509_free);
ocsp->chain = NULL;
chunk_destroy(&ocsp->response);
if (ocsp->uri) {
ha_free(&ocsp->uri->area);
ha_free(&ocsp->uri);
}
free(ocsp);
}
HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
}
/*
* This function dumps the details of an OCSP_CERTID. It is based on
* ocsp_certid_print in OpenSSL.
*/
static inline int ocsp_certid_print(BIO *bp, OCSP_CERTID *certid, int indent)
{
ASN1_OCTET_STRING *piNameHash = NULL;
ASN1_OCTET_STRING *piKeyHash = NULL;
ASN1_INTEGER *pSerial = NULL;
if (OCSP_id_get0_info(&piNameHash, NULL, &piKeyHash, &pSerial, certid)) {
BIO_printf(bp, "%*sCertificate ID:\n", indent, "");
indent += 2;
BIO_printf(bp, "%*sIssuer Name Hash: ", indent, "");
#ifndef USE_OPENSSL_WOLFSSL
i2a_ASN1_STRING(bp, piNameHash, 0);
#else
wolfSSL_ASN1_STRING_print(bp, piNameHash);
#endif
BIO_printf(bp, "\n%*sIssuer Key Hash: ", indent, "");
#ifndef USE_OPENSSL_WOLFSSL
i2a_ASN1_STRING(bp, piKeyHash, 0);
#else
wolfSSL_ASN1_STRING_print(bp, piNameHash);
#endif
BIO_printf(bp, "\n%*sSerial Number: ", indent, "");
i2a_ASN1_INTEGER(bp, pSerial);
}
return 1;
}
/*
* Dump the details about an OCSP response in DER format stored in
* <ocsp_response> into buffer <out>.
* Returns 0 in case of success.
*/
int ssl_ocsp_response_print(struct buffer *ocsp_response, struct buffer *out)
{
BIO *bio = NULL;
int write = -1;
OCSP_RESPONSE *resp;
const unsigned char *p;
int retval = -1;
if (!ocsp_response)
return -1;
if ((bio = BIO_new(BIO_s_mem())) == NULL)
return -1;
p = (const unsigned char*)ocsp_response->area;
resp = d2i_OCSP_RESPONSE(NULL, &p, ocsp_response->data);
if (!resp) {
chunk_appendf(out, "Unable to parse OCSP response");
goto end;
}
#ifndef USE_OPENSSL_WOLFSSL
if (OCSP_RESPONSE_print(bio, resp, 0) != 0) {
#else
if (wolfSSL_d2i_OCSP_RESPONSE_bio(bio, &resp) != 0) {
#endif
struct buffer *trash = get_trash_chunk();
struct ist ist_block = IST_NULL;
struct ist ist_double_lf = IST_NULL;
static struct ist double_lf = IST("\n\n");
write = BIO_read(bio, trash->area, trash->size - 1);
if (write <= 0)
goto end;
trash->data = write;
/* Look for empty lines in the 'trash' buffer and add a space to
* the beginning to avoid having empty lines in the output
* (without changing the appearance of the information
* displayed).
*/
ist_block = ist2(b_orig(trash), b_data(trash));
ist_double_lf = istist(ist_block, double_lf);
while (istlen(ist_double_lf)) {
/* istptr(ist_double_lf) points to the first \n of a
* \n\n pattern.
*/
uint empty_line_offset = istptr(ist_double_lf) + 1 - istptr(ist_block);
/* Write up to the first '\n' of the "\n\n" pattern into
* the output buffer.
*/
b_putblk(out, istptr(ist_block), empty_line_offset);
/* Add an extra space. */
b_putchr(out, ' ');
/* Keep looking for empty lines in the rest of the data. */
ist_block = istadv(ist_block, empty_line_offset);
ist_double_lf = istist(ist_block, double_lf);
}
retval = (b_istput(out, ist_block) <= 0);
}
end:
if (bio)
BIO_free(bio);
OCSP_RESPONSE_free(resp);
return retval;
}
/*
* Dump the details of the OCSP response of ID <ocsp_certid> into buffer <out>.
* Returns 0 in case of success.
*/
int ssl_get_ocspresponse_detail(unsigned char *ocsp_certid, struct buffer *out)
{
struct certificate_ocsp *ocsp;
int ret = 0;
HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
ocsp = (struct certificate_ocsp *)ebmb_lookup(&cert_ocsp_tree, ocsp_certid, OCSP_MAX_CERTID_ASN1_LENGTH);
if (!ocsp) {
HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
return -1;
}
ret = ssl_ocsp_response_print(&ocsp->response, out);
HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
return ret;
}
/* IO handler of details "show ssl ocsp-response <id>".
* The current entry is taken from appctx->svcctx.
*/
static int cli_io_handler_show_ocspresponse_detail(struct appctx *appctx)
{
struct buffer *trash = alloc_trash_chunk();
struct certificate_ocsp *ocsp = appctx->svcctx;
if (trash == NULL)
return 1;
if (ssl_ocsp_response_print(&ocsp->response, trash)) {
free_trash_chunk(trash);
return 1;
}
if (applet_putchk(appctx, trash) == -1)
goto yield;
appctx->svcctx = NULL;
if (trash)
free_trash_chunk(trash);
return 1;
yield:
if (trash)
free_trash_chunk(trash);
return 0;
}
void ssl_sock_ocsp_free_func(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx, long argl, void *argp)
{
struct ocsp_cbk_arg *ocsp_arg;
if (ptr) {
ocsp_arg = ptr;
if (ocsp_arg->is_single) {
ssl_sock_free_ocsp(ocsp_arg->s_ocsp);
ocsp_arg->s_ocsp = NULL;
} else {
int i;
for (i = 0; i < SSL_SOCK_NUM_KEYTYPES; i++) {
ssl_sock_free_ocsp(ocsp_arg->m_ocsp[i]);
ocsp_arg->m_ocsp[i] = NULL;
}
}
free(ocsp_arg);
}
}
/*
* Extract the first OCSP URI (if any) contained in <cert> and write it into
* <out>.
* Returns 0 in case of success, 1 otherwise.
*/
int ssl_ocsp_get_uri_from_cert(X509 *cert, struct buffer *out, char **err)
{
STACK_OF(OPENSSL_STRING) *ocsp_uri_stk = NULL;
int ret = 1;
if (!cert || !out)
goto end;
ocsp_uri_stk = X509_get1_ocsp(cert);
if (ocsp_uri_stk == NULL) {
memprintf(err, "%sNo OCSP URL stack!\n", *err ? *err : "");
goto end;
}
if (!chunk_strcpy(out, sk_OPENSSL_STRING_value(ocsp_uri_stk, 0))) {
memprintf(err, "%sOCSP URI too long!\n", *err ? *err : "");
goto end;
}
if (b_data(out) == 0) {
memprintf(err, "%sNo OCSP URL!\n", *err ? *err : "");
goto end;
}
ret = 0;
end:
X509_email_free(ocsp_uri_stk);
return ret;
}
/*
* Create the url and request body that make a proper OCSP request for the
* <certid>. The <req_url> parameter should already hold the OCSP URI that was
* extracted from the corresponding certificate. Depending on the size of the
* certid we will either append data to the <req_url> to create a proper URL
* that will be sent with a GET command, or the <req_body> will be constructed
* in case of a POST.
* Returns 0 in case of success.
*/
int ssl_ocsp_create_request_details(const OCSP_CERTID *certid, struct buffer *req_url,
struct buffer *req_body, char **err)
{
int errcode = -1;
OCSP_REQUEST *ocsp;
struct buffer *bin_request = get_trash_chunk();
unsigned char *outbuf = (unsigned char*)b_orig(bin_request);
ocsp = OCSP_REQUEST_new();
if (ocsp == NULL) {
memprintf(err, "%sCan't create OCSP_REQUEST\n", *err ? *err : "");
goto end;
}
if (OCSP_request_add0_id(ocsp, (OCSP_CERTID*)certid) == NULL) {
memprintf(err, "%sOCSP_request_add0_id() error\n", *err ? *err : "");
goto end;
}
bin_request->data = i2d_OCSP_REQUEST(ocsp, &outbuf);
if (b_data(bin_request) <= 0) {
memprintf(err, "%si2d_OCSP_REQUEST() error\n", *err ? *err : "");
goto end;
}
/* HTTP based OCSP requests can use either the GET or the POST method to
* submit their requests. To enable HTTP caching, small requests (that
* after encoding are less than 255 bytes), MAY be submitted using GET.
* If HTTP caching is not important, or the request is greater than 255
* bytes, the request SHOULD be submitted using POST.
*/
if (b_data(bin_request) + b_data(req_url) < 0xff) {
struct buffer *b64buf = get_trash_chunk();
char *ret = NULL;
int base64_ret = 0;
chunk_strcat(req_url, "/");
base64_ret = a2base64(b_orig(bin_request), b_data(bin_request),
b_orig(b64buf), b_size(b64buf));
if (base64_ret < 0) {
memprintf(err, "%sa2base64() error\n", *err ? *err : "");
goto end;
}
b64buf->data = base64_ret;
ret = encode_chunk((char*)b_stop(req_url), b_orig(req_url) + b_size(req_url), '%',
query_encode_map, b64buf);
if (ret && *ret == '\0') {
req_url->data = ret - b_orig(req_url);
errcode = 0;
}
}
else {
chunk_cpy(req_body, bin_request);
errcode = 0;
}
end:
OCSP_REQUEST_free(ocsp);
return errcode;
}
/*
* Parse an OCSP_RESPONSE contained in <respbuf> and check its validity in
* regard to the contents of <ckch> or the <issuer> certificate.
* Certificate_ocsp structure does not keep a reference to the corresponding
* ckch_store so outside of a CLI context (see "send ssl ocsp-response"
* command), we only have an easy access to the issuer's certificate whose
* reference is held in the structure.
* Return 0 in case of success, 1 otherwise.
*/
int ssl_ocsp_check_response(STACK_OF(X509) *chain, X509 *issuer,
struct buffer *respbuf, char **err)
{
int ret = 1;
int n;
OCSP_RESPONSE *response = NULL;
OCSP_BASICRESP *basic = NULL;
X509_STORE *store = NULL;
const unsigned char *start = (const unsigned char*)b_orig(respbuf);
if (!chain && !issuer) {
memprintf(err, "check_ocsp_response needs a certificate validation chain or an issuer certificate");
goto end;
}
response = d2i_OCSP_RESPONSE(NULL, &start, b_data(respbuf));
if (!response) {
memprintf(err, "d2i_OCSP_RESPONSE() failed");
goto end;
}
n = OCSP_response_status(response);
if (n != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
memprintf(err, "OCSP response not successful (%d: %s)",
n, OCSP_response_status_str(n));
goto end;
}
basic = OCSP_response_get1_basic(response);
if (basic == NULL) {
memprintf(err, "OCSP_response_get1_basic() failed");
goto end;
}
/* Create a temporary store in which we add the certificate's chain
* certificates. We assume that all those certificates can be trusted
* because they were provided by the user.
* The only ssl item that needs to be verified here is the OCSP
* response.
*/
store = X509_STORE_new();
if (!store) {
memprintf(err, "X509_STORE_new() failed");
goto end;
}
if (chain) {
int i = 0;
for (i = 0; i < sk_X509_num(chain); i++) {
X509 *cert = sk_X509_value(chain, i);
X509_STORE_add_cert(store, cert);
}
}
if (issuer)
X509_STORE_add_cert(store, issuer);
if (OCSP_basic_verify(basic, chain, store, OCSP_TRUSTOTHER) != 1) {
memprintf(err, "OCSP_basic_verify() failed");
goto end;
}
ret = 0;
end:
X509_STORE_free(store);
OCSP_RESPONSE_free(response);
OCSP_BASICRESP_free(basic);
return ret;
}
/*
* OCSP-UPDATE RELATED FUNCTIONS AND STRUCTURES
*/
struct task *ocsp_update_task __read_mostly = NULL;
static struct ssl_ocsp_task_ctx {
struct certificate_ocsp *cur_ocsp;
struct httpclient *hc;
int flags;
} ssl_ocsp_task_ctx;
const struct http_hdr ocsp_request_hdrs[] = {
{ IST("Content-Type"), IST("application/ocsp-request") },
{ IST_NULL, IST_NULL }
};
static struct task *ssl_ocsp_update_responses(struct task *task, void *context, unsigned int state);
/*
* Create the main OCSP update task that will iterate over the OCSP responses
* stored in ocsp_update_tree and send an OCSP request via the http_client
* applet to the corresponding OCSP responder. The task will then be in charge
* of processing the response, verifying it and resinserting it in the actual
* ocsp response tree if the response is valid.
* Returns 0 in case of success.
*/
int ssl_create_ocsp_update_task(char **err)
{
if (ocsp_update_task)
return 0; /* Already created */
ocsp_update_task = task_new_anywhere();
if (!ocsp_update_task) {
memprintf(err, "parsing : failed to allocate global ocsp update task.");
return -1;
}
ocsp_update_task->process = ssl_ocsp_update_responses;
ocsp_update_task->context = NULL;
return 0;
}
static int ssl_ocsp_task_schedule()
{
if (ocsp_update_task)
task_schedule(ocsp_update_task, now_ms);
return 0;
}
REGISTER_POST_CHECK(ssl_ocsp_task_schedule);
void ssl_sock_free_ocsp(struct certificate_ocsp *ocsp);
void ssl_destroy_ocsp_update_task(void)
{
struct eb64_node *node, *next;
if (!ocsp_update_task)
return;
HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
node = eb64_first(&ocsp_update_tree);
while (node) {
next = eb64_next(node);
eb64_delete(node);
node = next;
}
HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
task_destroy(ocsp_update_task);
ocsp_update_task = NULL;
ssl_sock_free_ocsp(ssl_ocsp_task_ctx.cur_ocsp);
ssl_ocsp_task_ctx.cur_ocsp = NULL;
}
static inline void ssl_ocsp_set_next_update(struct certificate_ocsp *ocsp)
{
int update_margin = (ocsp->expire >= SSL_OCSP_UPDATE_MARGIN) ? SSL_OCSP_UPDATE_MARGIN : 0;
ocsp->next_update.key = MIN(now.tv_sec + SSL_OCSP_UPDATE_DELAY_MAX,
ocsp->expire - update_margin);
/* An already existing valid OCSP response that expires within less than
* SSL_OCSP_UPDATE_DELAY_MIN or has no 'Next Update' field should not be
* updated more than once every 5 minutes in order to avoid continuous
* update of the same response. */
if (b_data(&ocsp->response))
ocsp->next_update.key = MAX(ocsp->next_update.key,
now.tv_sec + SSL_OCSP_UPDATE_DELAY_MIN);
}
/*
* Insert a certificate_ocsp structure into the ocsp_update_tree tree, in which
* entries are sorted by absolute date of the next update. The next_update key
* will be the smallest out of the actual expire value of the response and
* now+1H. This arbitrary 1H value ensures that ocsp responses are updated
* periodically even when they have a long expire time, while not overloading
* the system too much (in theory). Likewise, a minimum 5 minutes interval is
* defined in order to avoid updating too often responses that have a really
* short expire time or even no 'Next Update' at all.
*/
int ssl_ocsp_update_insert(struct certificate_ocsp *ocsp)
{
/* Set next_update based on current time and the various OCSP
* minimum/maximum update times.
*/
ssl_ocsp_set_next_update(ocsp);
HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
eb64_insert(&ocsp_update_tree, &ocsp->next_update);
HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
return 0;
}
/*
* Reinsert an entry in the update tree. The entry's next update time can not
* occur before now+SSL_OCSP_HTTP_ERR_REPLAY.
* This is supposed to be used in case of http error (ocsp responder unreachable
* for instance). This ensures that the entry does not get reinserted at the
* beginning of the tree every time.
*/
int ssl_ocsp_update_insert_after_error(struct certificate_ocsp *ocsp)
{
/* Set next_update based on current time and the various OCSP
* minimum/maximum update times.
*/
ssl_ocsp_set_next_update(ocsp);
if (ocsp->next_update.key < now.tv_sec + SSL_OCSP_HTTP_ERR_REPLAY)
ocsp->next_update.key = now.tv_sec + SSL_OCSP_HTTP_ERR_REPLAY;
HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
eb64_insert(&ocsp_update_tree, &ocsp->next_update);
HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
return 0;
}
void ocsp_update_response_stline_cb(struct httpclient *hc)
{
struct task *task = hc->caller;
if (!task)
return;
ssl_ocsp_task_ctx.flags |= HC_F_RES_STLINE;
task_wakeup(task, TASK_WOKEN_MSG);
}
void ocsp_update_response_headers_cb(struct httpclient *hc)
{
struct task *task = hc->caller;
if (!task)
return;
ssl_ocsp_task_ctx.flags |= HC_F_RES_HDR;
task_wakeup(task, TASK_WOKEN_MSG);
}
void ocsp_update_response_body_cb(struct httpclient *hc)
{
struct task *task = hc->caller;
if (!task)
return;
ssl_ocsp_task_ctx.flags |= HC_F_RES_BODY;
task_wakeup(task, TASK_WOKEN_MSG);
}
void ocsp_update_response_end_cb(struct httpclient *hc)
{
struct task *task = hc->caller;
if (!task)
return;
ssl_ocsp_task_ctx.flags |= HC_F_RES_END;
task_wakeup(task, TASK_WOKEN_MSG);
}
/*
* This is the main function of the ocsp auto update mechanism. It has two
* distinct parts and the branching to one or the other is completely based on
* the fact that the cur_ocsp pointer of the ssl_ocsp_task_ctx member is set.
*
* If the pointer is not set, we need to look at the first item of the update
* tree and see if it needs to be updated. If it does not we simply wait until
* the time is right and let the task asleep. If it does need to be updated, we
* simply build and send the corresponding ocsp request thanks to the
* http_client. The task is then sent to sleep with an expire time set to
* infinity. The http_client will wake it back up once the response is received
* (or a timeout occurs). Just note that during this whole process the
* cetificate_ocsp object corresponding to the entry being updated is taken out
* of the update tree and only stored in the ssl_ocsp_task_ctx context.
*
* Once the task is waken up by the http_client, it branches on the response
* processing part of the function which basically checks that the response is
* valid and inserts it into the ocsp_response tree. The task then goes back to
* sleep until another entry needs to be updated.
*/
static struct task *ssl_ocsp_update_responses(struct task *task, void *context, unsigned int state)
{
unsigned int next_wakeup = 0;
struct eb64_node *eb;
struct certificate_ocsp *ocsp;
struct httpclient *hc = NULL;
struct buffer *req_url = NULL;
struct buffer *req_body = NULL;
OCSP_CERTID *certid = NULL;
struct ssl_ocsp_task_ctx *ctx = &ssl_ocsp_task_ctx;
if (ctx->cur_ocsp) {
/* An update is in process */
ocsp = ctx->cur_ocsp;
hc = ctx->hc;
if (ctx->flags & HC_F_RES_STLINE) {
if (hc->res.status != 200) {
goto http_error;
}
ctx->flags &= ~HC_F_RES_STLINE;
}
if (ctx->flags & HC_F_RES_HDR) {
struct http_hdr *hdr;
int found = 0;
/* Look for "Content-Type" header which should have
* "application/ocsp-response" value. */
for (hdr = hc->res.hdrs; isttest(hdr->v); hdr++) {
if (isteqi(hdr->n, ist("Content-Type")) &&
isteqi(hdr->v, ist("application/ocsp-response"))) {
found = 1;
break;
}
}
if (!found) {
goto http_error;
}
ctx->flags &= ~HC_F_RES_HDR;
}
/* If the HC_F_RES_BODY is set, we still need for the
* HC_F_RES_END flag to be set as well in order to be sure that
* the body is complete. */
/* we must close only if F_RES_END is the last flag */
if (ctx->flags & HC_F_RES_END) {
/* Process the body that must be complete since
* HC_F_RES_END is set. */
if (ctx->flags & HC_F_RES_BODY) {
if (ssl_ocsp_check_response(ocsp->chain, ocsp->issuer, &hc->res.buf, NULL))
goto http_error;
if (ssl_sock_update_ocsp_response(&hc->res.buf, NULL) != 0) {
goto http_error;
}
ctx->flags &= ~HC_F_RES_BODY;
}
ctx->flags &= ~HC_F_RES_END;
/* Reinsert the entry into the update list so that it can be updated later */
ssl_ocsp_update_insert(ocsp);
/* Release the reference kept on the updated ocsp response. */
ssl_sock_free_ocsp(ctx->cur_ocsp);
ctx->cur_ocsp = NULL;
HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
/* Set next_wakeup to the new first entry of the tree */
eb = eb64_first(&ocsp_update_tree);
if (eb) {
if (eb->key > now.tv_sec)
next_wakeup = (eb->key - now.tv_sec)*1000;
else
next_wakeup = 0;
}
HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
goto leave;
}
/* We did not receive the HC_F_RES_END flag yet, wait for it
* before trying to update a new ocsp response. */
goto wait;
} else {
/* Look for next entry that needs to be updated. */
const unsigned char *p = NULL;
HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
eb = eb64_first(&ocsp_update_tree);
if (!eb) {
HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
goto wait;
}
if (eb->key > now.tv_sec) {
next_wakeup = (eb->key - now.tv_sec)*1000;
HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
goto leave;
}
ocsp = eb64_entry(eb, struct certificate_ocsp, next_update);
/* Take the current entry out of the update tree, it will be
* reinserted after the response is processed. */
eb64_delete(&ocsp->next_update);
++ocsp->refcount;
ctx->cur_ocsp = ocsp;
HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
req_url = alloc_trash_chunk();
if (!req_url) {
goto leave;
}
req_body = alloc_trash_chunk();
if (!req_body) {
goto leave;
}
p = ocsp->key_data;
d2i_OCSP_CERTID(&certid, &p, ocsp->key_length);
if (!certid)
goto leave;
/* Copy OCSP URI stored in ocsp structure into req_url */
chunk_cpy(req_url, ocsp->uri);
/* Create ocsp request */
if (ssl_ocsp_create_request_details(certid, req_url, req_body, NULL) != 0) {
goto leave;
}
/* Depending on the processing that occurred in
* ssl_ocsp_create_request_details we could either have to send
* a GET or a POST request. */
hc = httpclient_new(task, b_data(req_body) ? HTTP_METH_POST : HTTP_METH_GET, ist2(b_orig(req_url), b_data(req_url)));
if (!hc) {
goto leave;
}
if (httpclient_req_gen(hc, hc->req.url, hc->req.meth,
b_data(req_body) ? ocsp_request_hdrs : NULL,
b_data(req_body) ? ist2(b_orig(req_body), b_data(req_body)) : IST_NULL) != ERR_NONE) {
goto leave;
}
hc->ops.res_stline = ocsp_update_response_stline_cb;
hc->ops.res_headers = ocsp_update_response_headers_cb;
hc->ops.res_payload = ocsp_update_response_body_cb;
hc->ops.res_end = ocsp_update_response_end_cb;
if (!httpclient_start(hc)) {
goto leave;
}
ctx->flags = 0;
ctx->hc = hc;
/* We keep the lock, this indicates that an update is in process. */
goto wait;
}
leave:
if (ctx->cur_ocsp) {
/* Something went wrong, reinsert the entry in the tree. */
ssl_ocsp_update_insert(ctx->cur_ocsp);
/* Release the reference kept on the updated ocsp response. */
ssl_sock_free_ocsp(ctx->cur_ocsp);
ctx->cur_ocsp = NULL;
}
if (hc)
httpclient_stop_and_destroy(hc);
free_trash_chunk(req_url);
free_trash_chunk(req_body);
task->expire = tick_add(now_ms, next_wakeup);
return task;
wait:
free_trash_chunk(req_url);
free_trash_chunk(req_body);
task->expire = TICK_ETERNITY;
return task;
http_error:
/* Reinsert certificate into update list so that it can be updated later */
if (ocsp)
ssl_ocsp_update_insert_after_error(ocsp);
if (hc)
httpclient_stop_and_destroy(hc);
/* Release the reference kept on the updated ocsp response. */
ssl_sock_free_ocsp(ctx->cur_ocsp);
HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
/* Set next_wakeup to the new first entry of the tree */
eb = eb64_first(&ocsp_update_tree);
if (eb) {
if (eb->key > now.tv_sec)
next_wakeup = (eb->key - now.tv_sec)*1000;
else
next_wakeup = 0;
}
HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
ctx->cur_ocsp = NULL;
ctx->hc = NULL;
ctx->flags = 0;
task->expire = tick_add(now_ms, next_wakeup);
return task;
}
struct ocsp_cli_ctx {
struct httpclient *hc;
struct ckch_data *ckch_data;
X509 *ocsp_issuer;
uint flags;
uint do_update;
};
void cli_ocsp_res_stline_cb(struct httpclient *hc)
{
struct appctx *appctx = hc->caller;
struct ocsp_cli_ctx *ctx;
if (!appctx)
return;
ctx = appctx->svcctx;
ctx->flags |= HC_F_RES_STLINE;
appctx_wakeup(appctx);
}
void cli_ocsp_res_headers_cb(struct httpclient *hc)
{
struct appctx *appctx = hc->caller;
struct ocsp_cli_ctx *ctx;
if (!appctx)
return;
ctx = appctx->svcctx;
ctx->flags |= HC_F_RES_HDR;
appctx_wakeup(appctx);
}
void cli_ocsp_res_body_cb(struct httpclient *hc)
{
struct appctx *appctx = hc->caller;
struct ocsp_cli_ctx *ctx;
if (!appctx)
return;
ctx = appctx->svcctx;
ctx->flags |= HC_F_RES_BODY;
appctx_wakeup(appctx);
}
void cli_ocsp_res_end_cb(struct httpclient *hc)
{
struct appctx *appctx = hc->caller;
struct ocsp_cli_ctx *ctx;
if (!appctx)
return;
ctx = appctx->svcctx;
ctx->flags |= HC_F_RES_END;
appctx_wakeup(appctx);
}
static int cli_parse_update_ocsp_response(char **args, char *payload, struct appctx *appctx, void *private)
{
int errcode = 0;
char *err = NULL;
struct ckch_store *ckch_store = NULL;
X509 *cert = NULL;
struct ocsp_cli_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
struct httpclient *hc = NULL;
struct buffer *req_url = NULL;
struct buffer *req_body = NULL;
OCSP_CERTID *certid = NULL;
if (!*args[3]) {
memprintf(&err, "'update ssl ocsp-response' expects a filename\n");
return cli_dynerr(appctx, err);
}
req_url = alloc_trash_chunk();
if (!req_url) {
memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
errcode |= ERR_ALERT | ERR_FATAL;
goto end;
}
req_body = alloc_trash_chunk();
if (!req_body) {
memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
errcode |= ERR_ALERT | ERR_FATAL;
goto end;
}
/* The operations on the CKCH architecture are locked so we can
* manipulate ckch_store and ckch_inst */
if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock)) {
memprintf(&err, "%sCan't update the certificate!\nOperations on certificates are currently locked!\n", err ? err : "");
errcode |= ERR_ALERT | ERR_FATAL;
goto end;
}
ckch_store = ckchs_lookup(args[3]);
if (!ckch_store) {
memprintf(&err, "%sUnknown certificate! 'update ssl ocsp-response' expects an already known certificate file name.\n", err ? err : "");
errcode |= ERR_ALERT | ERR_FATAL;
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
goto end;
}
ctx->ckch_data = ckch_store->data;
cert = ckch_store->data->cert;
if (ssl_ocsp_get_uri_from_cert(cert, req_url, &err)) {
errcode |= ERR_ALERT | ERR_FATAL;
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
goto end;
}
/* Look for the ocsp issuer in the ckch_data or in the certificate
* chain, the same way it is done in ssl_sock_load_ocsp. */
ctx->ocsp_issuer = ctx->ckch_data->ocsp_issuer;
/* take issuer from chain over ocsp_issuer, is what is done historicaly */
if (ctx->ckch_data->chain) {
int i = 0;
/* check if one of the certificate of the chain is the issuer */
for (i = 0; i < sk_X509_num(ctx->ckch_data->chain); i++) {
X509 *ti = sk_X509_value(ctx->ckch_data->chain, i);
if (X509_check_issued(ti, cert) == X509_V_OK) {
ctx->ocsp_issuer = ti;
break;
}
}
}
if (!ctx->ocsp_issuer) {
memprintf(&err, "%sOCSP issuer not found\n", err ? err : "");
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
goto end;
}
X509_up_ref(ctx->ocsp_issuer);
certid = OCSP_cert_to_id(NULL, cert, ctx->ocsp_issuer);
if (certid == NULL) {
memprintf(&err, "%sOCSP_cert_to_id() error\n", err ? err : "");
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
goto end;
}
/* From here on the lock is not needed anymore. */
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
/* Create ocsp request */
if (ssl_ocsp_create_request_details(certid, req_url, req_body, &err) != 0) {
memprintf(&err, "%sCreate ocsp request error\n", err ? err : "");
goto end;
}
hc = httpclient_new(appctx, b_data(req_body) ? HTTP_METH_POST : HTTP_METH_GET, ist2(b_orig(req_url), b_data(req_url)));
if (!hc) {
memprintf(&err, "%sCan't allocate httpclient\n", err ? err : "");
goto end;
}
if (httpclient_req_gen(hc, hc->req.url, hc->req.meth, b_data(req_body) ? ocsp_request_hdrs : NULL,
ist2(b_orig(req_body), b_data(req_body))) != ERR_NONE) {
memprintf(&err, "%shttpclient_req_gen() error\n", err ? err : "");
goto end;
}
hc->ops.res_stline = cli_ocsp_res_stline_cb;
hc->ops.res_headers = cli_ocsp_res_headers_cb;
hc->ops.res_payload = cli_ocsp_res_body_cb;
hc->ops.res_end = cli_ocsp_res_end_cb;
ctx->hc = hc; /* store the httpclient ptr in the applet */
ctx->flags = 0;
if (!httpclient_start(hc)) {
memprintf(&err, "%shttpclient_start() error\n", err ? err : "");
goto end;
}
free_trash_chunk(req_url);
return 0;
end:
free_trash_chunk(req_url);
if (errcode & ERR_CODE) {
return cli_dynerr(appctx, memprintf(&err, "%sCan't send ocsp request for %s!\n", err ? err : "", args[3]));
}
return cli_dynmsg(appctx, LOG_NOTICE, err);
}
static int cli_io_handler_update_ocsp_response(struct appctx *appctx)
{
struct ocsp_cli_ctx *ctx = appctx->svcctx;
struct httpclient *hc = ctx->hc;
if (ctx->flags & HC_F_RES_STLINE) {
if (hc->res.status != 200) {
chunk_printf(&trash, "OCSP response error (status %d)\n", hc->res.status);
if (applet_putchk(appctx, &trash) == -1)
goto more;
goto end;
}
ctx->flags &= ~HC_F_RES_STLINE;
}
if (ctx->flags & HC_F_RES_HDR) {
struct http_hdr *hdr;
int found = 0;
/* Look for "Content-Type" header which should have
* "application/ocsp-response" value. */
for (hdr = hc->res.hdrs; isttest(hdr->v); hdr++) {
if (isteqi(hdr->n, ist("Content-Type")) &&
isteqi(hdr->v, ist("application/ocsp-response"))) {
found = 1;
break;
}
}
if (!found) {
fprintf(stderr, "Missing 'Content-Type: application/ocsp-response' header\n");
goto end;
}
ctx->flags &= ~HC_F_RES_HDR;
}
if (ctx->flags & HC_F_RES_BODY) {
/* Wait until the full body is received and HC_F_RES_END flag is
* set. */
}
/* we must close only if F_END is the last flag */
if (ctx->flags & HC_F_RES_END) {
char *err = NULL;
if (ssl_ocsp_check_response(ctx->ckch_data->chain, ctx->ocsp_issuer, &hc->res.buf, &err)) {
chunk_printf(&trash, "%s", err);
if (applet_putchk(appctx, &trash) == -1)
goto more;
goto end;
}
if (ssl_sock_update_ocsp_response(&hc->res.buf, &err) != 0) {
chunk_printf(&trash, "%s", err);
if (applet_putchk(appctx, &trash) == -1)
goto more;
goto end;
}
chunk_reset(&trash);
if (ssl_ocsp_response_print(&hc->res.buf, &trash))
goto end;
if (applet_putchk(appctx, &trash) == -1)
goto more;
ctx->flags &= ~HC_F_RES_BODY;
ctx->flags &= ~HC_F_RES_END;
goto end;
}
more:
if (!ctx->flags)
applet_have_no_more_data(appctx);
return 0;
end:
return 1;
}
static void cli_release_update_ocsp_response(struct appctx *appctx)
{
struct ocsp_cli_ctx *ctx = appctx->svcctx;
struct httpclient *hc = ctx->hc;
X509_free(ctx->ocsp_issuer);
/* Everything possible was printed on the CLI, we can destroy the client */
httpclient_stop_and_destroy(hc);
return;
}
#endif /* !defined OPENSSL_IS_BORINGSSL */
#endif /* (defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) */
static int cli_parse_set_ocspresponse(char **args, char *payload, struct appctx *appctx, void *private)
{
#if (defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP)
char *err = NULL;
int i, j, ret;
if (!payload)
payload = args[3];
/* Expect one parameter: the new response in base64 encoding */
if (!*payload)
return cli_err(appctx, "'set ssl ocsp-response' expects response in base64 encoding.\n");
/* remove \r and \n from the payload */
for (i = 0, j = 0; payload[i]; i++) {
if (payload[i] == '\r' || payload[i] == '\n')
continue;
payload[j++] = payload[i];
}
payload[j] = 0;
ret = base64dec(payload, j, trash.area, trash.size);
if (ret < 0)
return cli_err(appctx, "'set ssl ocsp-response' received invalid base64 encoded response.\n");
trash.data = ret;
if (ssl_sock_update_ocsp_response(&trash, &err)) {
if (err)
return cli_dynerr(appctx, memprintf(&err, "%s.\n", err));
else
return cli_err(appctx, "Failed to update OCSP response.\n");
}
return cli_msg(appctx, LOG_INFO, "OCSP Response updated!\n");
#else
return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
#endif
}
/* parsing function for 'show ssl ocsp-response [id]'. If an entry is forced,
* it's set into appctx->svcctx.
*/
static int cli_parse_show_ocspresponse(char **args, char *payload, struct appctx *appctx, void *private)
{
#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
if (*args[3]) {
struct certificate_ocsp *ocsp = NULL;
char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
int key_length = OCSP_MAX_CERTID_ASN1_LENGTH;
char *key_ptr = key;
if (strlen(args[3]) > OCSP_MAX_CERTID_ASN1_LENGTH*2) {
return cli_err(appctx, "'show ssl ocsp-response' received a too big key.\n");
}
if (!parse_binary(args[3], &key_ptr, &key_length, NULL)) {
return cli_err(appctx, "'show ssl ocsp-response' received an invalid key.\n");
}
HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
ocsp = (struct certificate_ocsp *)ebmb_lookup(&cert_ocsp_tree, key, OCSP_MAX_CERTID_ASN1_LENGTH);
if (!ocsp) {
HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
return cli_err(appctx, "Certificate ID does not match any certificate.\n");
}
++ocsp->refcount;
HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
appctx->svcctx = ocsp;
appctx->io_handler = cli_io_handler_show_ocspresponse_detail;
}
return 0;
#else
return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
#endif
}
/*
* IO handler of "show ssl ocsp-response". The command taking a specific ID
* is managed in cli_io_handler_show_ocspresponse_detail.
* The current entry is taken from appctx->svcctx.
*/
static int cli_io_handler_show_ocspresponse(struct appctx *appctx)
{
#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
struct buffer *trash = alloc_trash_chunk();
struct buffer *tmp = NULL;
struct ebmb_node *node;
struct certificate_ocsp *ocsp = NULL;
BIO *bio = NULL;
int write = -1;
if (trash == NULL)
return 1;
HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
tmp = alloc_trash_chunk();
if (!tmp)
goto end;
if ((bio = BIO_new(BIO_s_mem())) == NULL)
goto end;
if (!appctx->svcctx) {
chunk_appendf(trash, "# Certificate IDs\n");
node = ebmb_first(&cert_ocsp_tree);
} else {
node = &((struct certificate_ocsp *)appctx->svcctx)->key;
}
while (node) {
OCSP_CERTID *certid = NULL;
const unsigned char *p = NULL;
int i;
ocsp = ebmb_entry(node, struct certificate_ocsp, key);
/* Dump the key in hexadecimal */
chunk_appendf(trash, "Certificate ID key : ");
for (i = 0; i < ocsp->key_length; ++i) {
chunk_appendf(trash, "%02x", ocsp->key_data[i]);
}
chunk_appendf(trash, "\n");
p = ocsp->key_data;
/* Decode the certificate ID (serialized into the key). */
d2i_OCSP_CERTID(&certid, &p, ocsp->key_length);
if (!certid)
goto end;
/* Dump the CERTID info */
ocsp_certid_print(bio, certid, 1);
OCSP_CERTID_free(certid);
write = BIO_read(bio, tmp->area, tmp->size-1);
/* strip trailing LFs */
while (write > 0 && tmp->area[write-1] == '\n')
write--;
tmp->area[write] = '\0';
chunk_appendf(trash, "%s\n", tmp->area);
node = ebmb_next(node);
if (applet_putchk(appctx, trash) == -1)
goto yield;
}
end:
HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
appctx->svcctx = NULL;
free_trash_chunk(trash);
free_trash_chunk(tmp);
BIO_free(bio);
return 1;
yield:
free_trash_chunk(trash);
free_trash_chunk(tmp);
BIO_free(bio);
++ocsp->refcount;
appctx->svcctx = ocsp;
HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
return 0;
#else
return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
#endif
}
static struct cli_kw_list cli_kws = {{ },{
{ { "set", "ssl", "ocsp-response", NULL }, "set ssl ocsp-response <resp|payload> : update a certificate's OCSP Response from a base64-encode DER", cli_parse_set_ocspresponse, NULL },
{ { "show", "ssl", "ocsp-response", NULL },"show ssl ocsp-response [id] : display the IDs of the OCSP responses used in memory, or the details of a single OCSP response", cli_parse_show_ocspresponse, cli_io_handler_show_ocspresponse, NULL },
#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
{ { "update", "ssl", "ocsp-response", NULL }, "update ssl ocsp-response <certfile> : send ocsp request and update stored ocsp response", cli_parse_update_ocsp_response, cli_io_handler_update_ocsp_response, cli_release_update_ocsp_response },
#endif
{ { NULL }, NULL, NULL, NULL }
}};
INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*/