]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: ssl: Add "update ssl ocsp-response" cli command
authorRemi Tricot-Le Breton <rlebreton@haproxy.com>
Tue, 20 Dec 2022 10:11:07 +0000 (11:11 +0100)
committerWilliam Lallemand <wlallemand@haproxy.org>
Wed, 21 Dec 2022 10:21:07 +0000 (11:21 +0100)
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.

doc/management.txt
src/ssl_sock.c

index c93bff5db9c4ecb458d80a6620be0b39c3cfb496..ae3ab9a230c892cc9d6152c0b7edf9fd6ad74d7a 100644 (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
 ---------------
index 44f515c39d7d35420921f9e7e695af412d4e9940..d7c2430b726d126f4aa8fdf5b21b8073caa1a109 100644 (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