MINOR: ssl: Add "update ssl ocsp-response" cli command

The new "update ssl ocsp-response <certfile>" CLI command allows to
update the stored OCSP response for a given certificate. It relies on
the http_client which is used to send an HTTP request to the OCSP
responder whose URI can be extracted from the certificate.
This command won't work for a certificate that did not have a stored
OCSP response yet.
This commit is contained in:
Remi Tricot-Le Breton 2022-12-20 11:11:07 +01:00 committed by William Lallemand
parent c0b4058e7e
commit eeaa29b36b
2 changed files with 286 additions and 4 deletions

View File

@ -3820,6 +3820,19 @@ trace <source> verbosity [<level>]
information available at the trace point. The first level above "quiet" is
set by default.
update ssl ocsp-response <certfile>
Create an OCSP request for the specified <certfile> and send it to the OCSP
responder whose URI should be specified in the "Authority Information Access"
section of the certificate. Only the first URI is taken into account. The
OCSP response that we should receive in return is then checked and inserted
in the local OCSP response tree. This command will only work for certificates
that already had a stored OCSP response, either because it was provided
during init or if it was previously set through the "set ssl cert" or "set
ssl ocsp-response" commands.
If the received OCSP response is valid and was properly inserted into the
local tree, its contents will be displayed on the standard output. The format
is the same as the one described in "show ssl ocsp-response".
9.4. Master CLI
---------------

View File

@ -83,6 +83,7 @@
#include <haproxy/vars.h>
#include <haproxy/xxhash.h>
#include <haproxy/istbuf.h>
#include <haproxy/http_client.h>
/* ***** READ THIS before adding code here! *****
@ -8126,17 +8127,285 @@ end:
appctx->svcctx = NULL;
return 1;
}
struct ocsp_cli_ctx {
struct httpclient *hc;
struct ckch_data *ckch_data;
uint flags;
uint do_update;
};
const struct http_hdr ocsp_request_hdrs[] = {
{ IST("Content-Type"), IST("application/ocsp-request") },
{ IST_NULL, IST_NULL }
};
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, "%sCkch_store not found!\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;
}
certid = OCSP_cert_to_id(NULL, ctx->ckch_data->cert, ctx->ckch_data->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->ckch_data->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;
/* Everything possible was printed on the CLI, we can destroy the client */
httpclient_stop_and_destroy(hc);
return;
}
#endif
/* register cli keywords */
static struct cli_kw_list cli_kws = {{ },{
#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
{ { "show", "tls-keys", NULL }, "show tls-keys [id|*] : show tls keys references or dump tls ticket keys when id specified", cli_parse_show_tlskeys, cli_io_handler_tlskeys_files },
{ { "set", "ssl", "tls-key", NULL }, "set ssl tls-key [id|file] <key> : set the next TLS key for the <id> or <file> listener to <key>", cli_parse_set_tlskeys, NULL },
{ { "show", "tls-keys", NULL }, "show tls-keys [id|*] : show tls keys references or dump tls ticket keys when id specified", cli_parse_show_tlskeys, cli_io_handler_tlskeys_files },
{ { "set", "ssl", "tls-key", NULL }, "set ssl tls-key [id|file] <key> : set the next TLS key for the <id> or <file> listener to <key>", cli_parse_set_tlskeys, NULL },
#endif
{ { "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 },
{ { "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 },
{ { "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
#ifdef HAVE_SSL_PROVIDERS
{ { "show", "ssl", "providers", NULL }, "show ssl providers : show loaded SSL providers", NULL, cli_io_handler_show_providers },
#endif