From eeaa29b36b99c262b327b409215d9b0e08c08c4f Mon Sep 17 00:00:00 2001 From: Remi Tricot-Le Breton Date: Tue, 20 Dec 2022 11:11:07 +0100 Subject: [PATCH] MINOR: ssl: Add "update ssl ocsp-response" cli command The new "update ssl ocsp-response " 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. --- doc/management.txt | 13 +++ src/ssl_sock.c | 277 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 286 insertions(+), 4 deletions(-) diff --git a/doc/management.txt b/doc/management.txt index c93bff5db..ae3ab9a23 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -3820,6 +3820,19 @@ trace verbosity [] information available at the trace point. The first level above "quiet" is set by default. +update ssl ocsp-response + Create an OCSP request for the specified 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 --------------- diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 44f515c39..d7c2430b7 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -83,6 +83,7 @@ #include #include #include +#include /* ***** READ THIS before adding code here! ***** @@ -8126,17 +8127,285 @@ static int cli_io_handler_show_ocspresponse_detail(struct appctx *appctx) 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] : set the next TLS key for the or listener to ", 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] : set the next TLS key for the or listener to ", cli_parse_set_tlskeys, NULL }, #endif - { { "set", "ssl", "ocsp-response", NULL }, "set ssl ocsp-response : 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 : 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 : 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