diff --git a/reg-tests/ssl/set_ssl_cafile.vtc b/reg-tests/ssl/set_ssl_cafile.vtc index 0cfed1dec..04f892c68 100644 --- a/reg-tests/ssl/set_ssl_cafile.vtc +++ b/reg-tests/ssl/set_ssl_cafile.vtc @@ -1,7 +1,7 @@ #REGTEST_TYPE=devel # This reg-test uses the "set ssl ca-file" command to update a CA file over the CLI. -# It also tests the "abort ssl ca-file" command. +# It also tests the "abort ssl ca-file" and "show ssl ca-file" commands. # # It is based on two CA certificates, set_cafile_interCA1.crt and set_cafile_interCA2.crt, # and a client certificate that was signed with set_cafile_interCA1.crt (set_cafile_client.pem) @@ -58,6 +58,18 @@ haproxy h1 -conf { } -start +# Test the "show ssl ca-file" command +haproxy h1 -cli { + send "show ssl ca-file" + expect ~ ".*${testdir}/set_cafile_interCA1.crt - 1 certificate.*" + send "show ssl ca-file" + expect ~ ".*${testdir}/set_cafile_interCA2.crt - 1 certificate.*" + + send "show ssl ca-file ${testdir}/set_cafile_interCA2.crt" + expect ~ ".*SHA1 FingerPrint: 3D3D1D10AD74A8135F05A818E10E5FA91433954D" +} + + # This first connection should fail because the client's certificate was signed with the # set_cafile_interCA1.crt certificate which is not known by the backend. client c1 -connect ${h1_clearlst_sock} { @@ -70,7 +82,22 @@ client c1 -connect ${h1_clearlst_sock} { # Set a new ca-file without committing it and check that the new ca-file is not taken into account shell { - printf "set ssl ca-file ${testdir}/set_cafile_ca2.crt <<\n$(cat ${testdir}/set_cafile_ca1.crt)\n\n" | socat "${tmpdir}/h1/stats" - + printf "set ssl ca-file ${testdir}/set_cafile_interCA2.crt <<\n$(cat ${testdir}/set_cafile_interCA1.crt)\n\n" | socat "${tmpdir}/h1/stats" - +} + +# Test the "show ssl ca-file" command +# The transaction should be mentioned in the list +haproxy h1 -cli { + send "show ssl ca-file" + expect ~ "\\*${testdir}/set_cafile_interCA2.crt - 1 certificate.*" + +# The original CA file did not change + send "show ssl ca-file ${testdir}/set_cafile_interCA2.crt" + expect ~ ".*SHA1 FingerPrint: 3D3D1D10AD74A8135F05A818E10E5FA91433954D" + +# Only the current transaction displays a new certificate + send "show ssl ca-file *${testdir}/set_cafile_interCA2.crt" + expect ~ ".*SHA1 FingerPrint: 4FFF535278883264693CEA72C4FAD13F995D0098" } # This connection should still fail for the same reasons as previously @@ -83,9 +110,9 @@ client c1 -connect ${h1_clearlst_sock} { } -run haproxy h1 -cli { - send "abort ssl ca-file ${testdir}/set_cafile_ca2.crt" - expect ~ "Transaction aborted for certificate '${testdir}/set_cafile_ca2.crt'!" - send "commit ssl ca-file ${testdir}/set_cafile_ca2.crt" + send "abort ssl ca-file ${testdir}/set_cafile_interCA2.crt" + expect ~ "Transaction aborted for certificate '${testdir}/set_cafile_interCA2.crt'!" + send "commit ssl ca-file ${testdir}/set_cafile_interCA2.crt" expect ~ "No ongoing transaction!" } @@ -113,6 +140,19 @@ shell { echo "commit ssl ca-file ${testdir}/set_cafile_interCA1.crt" | socat "${tmpdir}/h1/stats" - } +# Test the "show ssl ca-file" with a certificate index +haproxy h1 -cli { + send "show ssl ca-file" + expect ~ ".*${testdir}/set_cafile_interCA1.crt - 3 certificate.*" + + send "show ssl ca-file ${testdir}/set_cafile_interCA1.crt:1" + expect ~ ".*SHA1 FingerPrint: 4FFF535278883264693CEA72C4FAD13F995D0098" + + send "show ssl ca-file ${testdir}/set_cafile_interCA1.crt:2" + expect !~ ".*SHA1 FingerPrint: 4FFF535278883264693CEA72C4FAD13F995D0098" + send "show ssl ca-file ${testdir}/set_cafile_interCA1.crt:2" + expect ~ ".*SHA1 FingerPrint: 3D3D1D10AD74A8135F05A818E10E5FA91433954D" +} client c1 -connect ${h1_clearverifiedlst_sock} { txreq diff --git a/src/ssl_ckch.c b/src/ssl_ckch.c index 0154230c8..3f07fbc35 100644 --- a/src/ssl_ckch.c +++ b/src/ssl_ckch.c @@ -2584,6 +2584,212 @@ static void cli_release_commit_cafile(struct appctx *appctx) } +/* IO handler of details "show ssl ca-file " */ +static int cli_io_handler_show_cafile_detail(struct appctx *appctx) +{ + struct stream_interface *si = appctx->owner; + struct cafile_entry *cafile_entry = appctx->ctx.cli.p0; + struct buffer *out = alloc_trash_chunk(); + int i; + X509 *cert; + STACK_OF(X509_OBJECT) *objs; + int retval = 0; + long ca_index = (long)appctx->ctx.cli.p1; + + if (!out) + goto end_no_putchk; + + chunk_appendf(out, "Filename: "); + if (cafile_entry == cafile_transaction.new_cafile_entry) + chunk_appendf(out, "*"); + chunk_appendf(out, "%s\n", cafile_entry->path); + + chunk_appendf(out, "Status: "); + if (!cafile_entry->ca_store) + chunk_appendf(out, "Empty\n"); + else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) + chunk_appendf(out, "Unused\n"); + else + chunk_appendf(out, "Used\n"); + + if (!cafile_entry->ca_store) + goto end; + + objs = X509_STORE_get0_objects(cafile_entry->ca_store); + for (i = 0; i < sk_X509_OBJECT_num(objs); i++) { + cert = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(objs, i)); + if (!cert) + continue; + + /* Certificate indexes start at 1 on the CLI output. */ + if (ca_index && ca_index-1 != i) + continue; + + chunk_appendf(out, "\nCertificate #%d:\n", i+1); + retval = show_cert_detail(cert, NULL, out); + if (retval < 0) + goto end_no_putchk; + else if (retval || ca_index) + goto end; + } + +end: + if (ci_putchk(si_ic(si), out) == -1) { + si_rx_room_blk(si); + goto yield; + } + +end_no_putchk: + free_trash_chunk(out); + return 1; +yield: + free_trash_chunk(out); + return 0; /* should come back */ +} + + +/* parsing function for 'show ssl ca-file [cafile[:index]]' */ +static int cli_parse_show_cafile(char **args, char *payload, struct appctx *appctx, void *private) +{ + struct cafile_entry *cafile_entry; + long ca_index = 0; + char *colons; + char *err = NULL; + + if (!cli_has_level(appctx, ACCESS_LVL_OPER)) + return cli_err(appctx, "Can't allocate memory!\n"); + + /* 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)) + return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n"); + + /* check if there is a certificate to lookup */ + if (*args[3]) { + + /* Look for an optional CA index after the CA file name */ + colons = strchr(args[3], ':'); + if (colons) { + char *endptr; + + ca_index = strtol(colons + 1, &endptr, 10); + /* Indexes start at 1 */ + if (colons + 1 == endptr || *endptr != '\0' || ca_index <= 0) { + memprintf(&err, "wrong CA index after colons in '%s'!", args[3]); + goto error; + } + *colons = '\0'; + } + + if (*args[3] == '*') { + if (!cafile_transaction.new_cafile_entry) + goto error; + + cafile_entry = cafile_transaction.new_cafile_entry; + + if (strcmp(args[3] + 1, cafile_entry->path) != 0) + goto error; + + } else { + /* Get the "original" cafile_entry and not the + * uncommitted one if it exists. */ + if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CERT) + goto error; + } + + appctx->ctx.cli.p0 = cafile_entry; + appctx->ctx.cli.p1 = (void*)ca_index; + /* use the IO handler that shows details */ + appctx->io_handler = cli_io_handler_show_cafile_detail; + } + + return 0; + +error: + HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock); + if (err) + return cli_dynerr(appctx, err); + return cli_err(appctx, "Can't display the CA file : Not found!\n"); +} + + +/* release function of the 'show ssl ca-file' command */ +static void cli_release_show_cafile(struct appctx *appctx) +{ + HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock); +} + + +/* This function returns the number of certificates in a cafile_entry. */ +static int get_certificate_count(struct cafile_entry *cafile_entry) +{ + int cert_count = 0; + STACK_OF(X509_OBJECT) *objs; + + if (cafile_entry && cafile_entry->ca_store) { + objs = X509_STORE_get0_objects(cafile_entry->ca_store); + if (objs) + cert_count = sk_X509_OBJECT_num(objs); + } + return cert_count; +} + +/* IO handler of "show ssl ca-file". The command taking a specific CA file name + * is managed in cli_io_handler_show_cafile_detail. */ +static int cli_io_handler_show_cafile(struct appctx *appctx) +{ + struct buffer *trash = alloc_trash_chunk(); + struct ebmb_node *node; + struct stream_interface *si = appctx->owner; + struct cafile_entry *cafile_entry; + + if (trash == NULL) + return 1; + + if (!appctx->ctx.ssl.old_cafile_entry) { + if (cafile_transaction.old_cafile_entry) { + chunk_appendf(trash, "# transaction\n"); + chunk_appendf(trash, "*%s", cafile_transaction.old_cafile_entry->path); + + chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_transaction.new_cafile_entry)); + } + } + + /* First time in this io_handler. */ + if (!appctx->ctx.cli.p0) { + chunk_appendf(trash, "# filename\n"); + node = ebmb_first(&cafile_tree); + } else { + /* We yielded during a previous call. */ + node = &((struct cafile_entry*)appctx->ctx.cli.p0)->node; + } + + while (node) { + cafile_entry = ebmb_entry(node, struct cafile_entry, node); + if (cafile_entry->type == CAFILE_CERT) { + chunk_appendf(trash, "%s", cafile_entry->path); + + chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_entry)); + } + + node = ebmb_next(node); + if (ci_putchk(si_ic(si), trash) == -1) { + si_rx_room_blk(si); + goto yield; + } + } + + appctx->ctx.cli.p0 = NULL; + free_trash_chunk(trash); + return 1; +yield: + + free_trash_chunk(trash); + appctx->ctx.cli.p0 = cafile_entry; + return 0; /* should come back */ +} + + void ckch_deinit() { struct eb_node *node, *next; @@ -2610,6 +2816,7 @@ static struct cli_kw_list cli_kws = {{ },{ { { "set", "ssl", "ca-file", NULL }, "set ssl ca-file : replace a CA file", cli_parse_set_cafile, NULL, NULL }, { { "commit", "ssl", "ca-file", NULL }, "commit ssl ca-file : commit a CA file", cli_parse_commit_cafile, cli_io_handler_commit_cafile, cli_release_commit_cafile }, { { "abort", "ssl", "ca-file", NULL }, "abort ssl ca-file : abort a transaction for a CA file", cli_parse_abort_cafile, NULL, NULL }, + { { "show", "ssl", "ca-file", NULL }, "show ssl ca-file [[:]] : display the SSL CA files used in memory, or the details of a , or a single certificate of index of a CA file ", cli_parse_show_cafile, cli_io_handler_show_cafile, cli_release_show_cafile }, { { NULL }, NULL, NULL, NULL } }};