MINOR: ssl: Add new "show ssl ocsp-response" CLI command

This patch adds the "show ssl ocsp-response [<id>]" 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 <ocsp-response>
-text" call.
This commit is contained in:
Remi Tricot-Le Breton 2021-06-10 13:51:13 +02:00 committed by William Lallemand
parent 5aa1dce5ee
commit d92fd11c77
3 changed files with 291 additions and 0 deletions

View File

@ -3077,6 +3077,43 @@ show ssl crt-list [-n] [<filename>]
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 [<id>]
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 <id> is provided, display the contents of the corresponding OCSP
response. The information displayed is the same as in an "openssl ocsp -respin
<ocsp-response> -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

View File

@ -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 */

View File

@ -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
* <ocsp_response> into buffer <out>.
* 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 <ocsp_certid> into buffer <out>.
* 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 <id>". */
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] <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 },
{ { "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 }
}};