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.
diff --git a/doc/management.txt b/doc/management.txt
index c93bff5..ae3ab9a 100644
--- a/doc/management.txt
+++ b/doc/management.txt
@@ -3820,6 +3820,19 @@
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
---------------
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 44f515c..d7c2430 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -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 @@
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