From d92fd11c77df0cfbdbe3806539a43544b24f5d74 Mon Sep 17 00:00:00 2001 From: Remi Tricot-Le Breton Date: Thu, 10 Jun 2021 13:51:13 +0200 Subject: [PATCH] MINOR: ssl: Add new "show ssl ocsp-response" CLI command This patch adds the "show ssl ocsp-response []" CLI command. This command can be used to display the IDs of the OCSP tree entries along with details about the entries' certificate ID (issuer's name and key hash + serial number), or to display the details of a single ocsp-response if an ID is given. The details displayed in this latter case are the ones shown by a "openssl ocsp -respin -text" call. --- doc/management.txt | 37 ++++++ include/haproxy/ssl_sock.h | 4 + src/ssl_sock.c | 250 +++++++++++++++++++++++++++++++++++++ 3 files changed, 291 insertions(+) diff --git a/doc/management.txt b/doc/management.txt index 39c3a3160..641f253bf 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -3077,6 +3077,43 @@ show ssl crt-list [-n] [] ecdsa.pem:3 [verify none allow-0rtt ssl-min-ver TLSv1.0 ssl-max-ver TLSv1.3] localhost !www.test1.com ecdsa.pem:4 [verify none allow-0rtt ssl-min-ver TLSv1.0 ssl-max-ver TLSv1.3] +show ssl ocsp-response [] + Display the IDs of the OCSP tree entries corresponding to all the OCSP + responses used in HAProxy, as well as the issuer's name and key hash and the + serial number of the certificate for which the OCSP response was built. + If a valid is provided, display the contents of the corresponding OCSP + response. The information displayed is the same as in an "openssl ocsp -respin + -text" call. + + Example : + + $ echo "show ssl ocsp-response" | socat /var/run/haproxy.master - + # Certificate IDs + Certificate ID key : 303b300906052b0e03021a050004148a83e0060faff709ca7e9b95522a2e81635fda0a0414f652b0e435d5ea923851508f0adbe92d85de007a0202100a + Certificate ID: + + Issuer Name Hash: 8A83E0060FAFF709CA7E9B95522A2E81635FDA0A + Issuer Key Hash: F652B0E435D5EA923851508F0ADBE92D85DE007A + Serial Number: 100A + + $ echo "show ssl ocsp-response 303b300906052b0e03021a050004148a83e0060faff709ca7e9b95522a2e81635fda0a0414f652b0e435d5ea923851508f0adbe92d85de007a0202100a" | socat /var/run/haproxy.master - + OCSP Response Data: + OCSP Response Status: successful (0x0) + Response Type: Basic OCSP Response + Version: 1 (0x0) + Responder Id: C = FR, O = HAProxy Technologies, CN = ocsp.haproxy.com + Produced At: May 27 15:43:38 2021 GMT + Responses: + Certificate ID: + Hash Algorithm: sha1 + Issuer Name Hash: 8A83E0060FAFF709CA7E9B95522A2E81635FDA0A + Issuer Key Hash: F652B0E435D5EA923851508F0ADBE92D85DE007A + Serial Number: 100A + Cert Status: good + This Update: May 27 15:43:38 2021 GMT + Next Update: Oct 12 15:43:38 2048 GMT + [...] + show table Dump general information on all known stick-tables. Their name is returned (the name of the proxy which holds them), their type (currently zero, always diff --git a/include/haproxy/ssl_sock.h b/include/haproxy/ssl_sock.h index bef137a73..88a8a3f7f 100644 --- a/include/haproxy/ssl_sock.h +++ b/include/haproxy/ssl_sock.h @@ -122,6 +122,10 @@ int ssl_sock_load_srv_cert(char *path, struct server *server, char **err); void ssl_free_global_issuers(void); int ssl_sock_load_cert_list_file(char *file, int dir, struct bind_conf *bind_conf, struct proxy *curproxy, char **err); int ssl_init_single_engine(const char *engine_id, const char *def_algorithms); +#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL) +int ssl_get_ocspresponse_detail(unsigned char *ocsp_certid, struct buffer *out); +int ssl_ocsp_response_print(struct buffer *ocsp_response, struct buffer *out); +#endif /* ssl shctx macro */ diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 5ea49d5e6..13e615545 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -6929,6 +6929,254 @@ static int cli_parse_set_ocspresponse(char **args, char *payload, struct appctx } + +#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL) +static int cli_io_handler_show_ocspresponse_detail(struct appctx *appctx); +#endif + +/* parsing function for 'show ssl ocsp-response [id]' */ +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 = NULL; + int key_length = 0; + + 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, &key_length, NULL)) { + + char full_key[OCSP_MAX_CERTID_ASN1_LENGTH] = {}; + memcpy(full_key, key, key_length); + + ocsp = (struct certificate_ocsp *)ebmb_lookup(&cert_ocsp_tree, full_key, OCSP_MAX_CERTID_ASN1_LENGTH); + } + if (key) + ha_free(&key); + + if (!ocsp) { + return cli_err(appctx, "Certificate ID does not match any certificate.\n"); + } + + appctx->ctx.cli.p0 = 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 +} + + +#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL) +/* + * 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, "\n%*sIssuer Name Hash: ", indent, ""); + i2a_ASN1_STRING(bp, piNameHash, 0); + BIO_printf(bp, "\n%*sIssuer Key Hash: ", indent, ""); + i2a_ASN1_STRING(bp, piKeyHash, 0); + BIO_printf(bp, "\n%*sSerial Number: ", indent, ""); + i2a_ASN1_INTEGER(bp, pSerial); + BIO_printf(bp, "\n"); + } + return 1; +} +#endif + +/* + * IO handler of "show ssl ocsp-response". The command taking a specific ID + * is managed in cli_io_handler_show_ocspresponse_detail. + */ +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 stream_interface *si = appctx->owner; + struct certificate_ocsp *ocsp = NULL; + BIO *bio = NULL; + int write = -1; + + if (trash == NULL) + return 1; + + tmp = alloc_trash_chunk(); + if (!tmp) + goto end; + + if ((bio = BIO_new(BIO_s_mem())) == NULL) + goto end; + + if (!appctx->ctx.cli.p0) { + chunk_appendf(trash, "# Certificate IDs\n"); + node = ebmb_first(&cert_ocsp_tree); + } else { + node = &((struct certificate_ocsp *)appctx->ctx.cli.p0)->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); + + /* Dump the CERTID info */ + ocsp_certid_print(bio, certid, 1); + write = BIO_read(bio, tmp->area, tmp->size-1); + tmp->area[write] = '\0'; + + chunk_appendf(trash, "%s\n", tmp->area); + + node = ebmb_next(node); + if (ci_putchk(si_ic(si), trash) == -1) { + si_rx_room_blk(si); + goto yield; + } + } + +end: + appctx->ctx.cli.p0 = NULL; + if (trash) + free_trash_chunk(trash); + if (tmp) + free_trash_chunk(tmp); + if (bio) + BIO_free(bio); + return 1; + +yield: + + if (trash) + free_trash_chunk(trash); + if (tmp) + free_trash_chunk(tmp); + if (bio) + BIO_free(bio); + appctx->ctx.cli.p0 = ocsp; + return 0; +#else + return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n"); +#endif +} + + +#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL) +/* + * Dump the details about an OCSP response in DER format stored in + * into buffer . + * 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; + + 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"); + return -1; + } + + if (OCSP_RESPONSE_print(bio, resp, 0) != 0) { + write = BIO_read(bio, out->area, out->size - 1); + out->area[write] = '\0'; + out->data = write; + } + + if (bio) + BIO_free(bio); + + return 0; +} + +/* + * Dump the details of the OCSP response of ID into buffer . + * Returns 0 in case of success. + */ +int ssl_get_ocspresponse_detail(unsigned char *ocsp_certid, struct buffer *out) +{ + struct certificate_ocsp *ocsp; + + ocsp = (struct certificate_ocsp *)ebmb_lookup(&cert_ocsp_tree, ocsp_certid, OCSP_MAX_CERTID_ASN1_LENGTH); + if (!ocsp) + return -1; + + return ssl_ocsp_response_print(&ocsp->response, out); +} + + +/* IO handler of details "show ssl ocsp-response ". */ +static int cli_io_handler_show_ocspresponse_detail(struct appctx *appctx) +{ + struct buffer *trash = alloc_trash_chunk(); + struct certificate_ocsp *ocsp = NULL; + struct stream_interface *si = appctx->owner; + + ocsp = appctx->ctx.cli.p0; + + if (trash == NULL) + return 1; + + ssl_ocsp_response_print(&ocsp->response, trash); + + if (ci_putchk(si_ic(si), trash) == -1) { + si_rx_room_blk(si); + goto yield; + } + appctx->ctx.cli.p0 = NULL; + if (trash) + free_trash_chunk(trash); + return 1; + +yield: + if (trash) + free_trash_chunk(trash); + + return 0; +} +#endif + /* register cli keywords */ static struct cli_kw_list cli_kws = {{ },{ #if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0) @@ -6936,6 +7184,8 @@ static struct cli_kw_list cli_kws = {{ },{ { { "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 }, + + { { "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 }, { { NULL }, NULL, NULL, NULL } }};