]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: ssl/cli: 'show ssl cert' give information on the certificates
authorWilliam Lallemand <wlallemand@haproxy.com>
Thu, 5 Dec 2019 09:26:40 +0000 (10:26 +0100)
committerWilliam Lallemand <wlallemand@haproxy.org>
Wed, 18 Dec 2019 17:16:34 +0000 (18:16 +0100)
Implement the 'show ssl cert' command on the CLI which list the frontend
certificates. With a certificate name in parameter it will show more
details.

doc/management.txt
src/ssl_sock.c

index d1a11031d0e08b48100c17ccefea8119d9c6f47d..973b6f3a1eaa5cb686b5a98ee2fe52c8df380b1a 100644 (file)
@@ -2507,6 +2507,37 @@ show stat [{<iid>|<proxy>} <type> <sid>] [typed|json] [desc]
   $ echo "show stat json" | socat /var/run/haproxy.sock stdio | \
     python -m json.tool
 
+show ssl cert [<filename>]
+  Display the list of certicates used on frontends. If a filename is prefixed
+  by an asterisk, it is a transaction which is not commited yet.  If a
+  filename is specified, it will show details about the certificate. This
+  command can be useful to check if a certificate was well updated. You can
+  also display details on a transaction by prefixing the filename by an
+  asterisk.
+
+  Example :
+
+    $ echo "@1 show ssl cert" | socat /var/run/haproxy.master -
+    # transaction
+    *test.local.pem
+    # filename
+    test.local.pem
+
+    $ echo "@1 show ssl cert test.local.pem" | socat /var/run/haproxy.master -
+    Filename: test.local.pem
+    Serial: 03ECC19BA54B25E85ABA46EE561B9A10D26F
+    notBefore: Sep 13 21:20:24 2019 GMT
+    notAfter: Dec 12 21:20:24 2019 GMT
+    Issuer: /C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
+    Subject: /CN=test.local
+    Subject Alternative Name: DNS:test.local, DNS:imap.test.local
+    Algorithm: RSA2048
+    SHA1 FingerPrint: 417A11CAE25F607B24F638B4A8AEE51D1E211477
+
+    $ echo "@1 show ssl cert *test.local.pem" | socat /var/run/haproxy.master -
+    Filename: *test.local.pem
+    [...]
+
 show resolvers [<resolvers section id>]
   Dump statistics for the given resolvers section, or all resolvers sections
   if no section is supplied.
index 0a529725e180d6941a34000e8e978264e55f3c13..568bf3b6e52942d4c77a3791fc3b43a63bb58a01 100644 (file)
@@ -6862,23 +6862,14 @@ static void ssl_sock_shutw(struct connection *conn, void *xprt_ctx, int clean)
        }
 }
 
-/* used for ppv2 pkey alog (can be used for logging) */
-int ssl_sock_get_pkey_algo(struct connection *conn, struct buffer *out)
+/* fill a buffer with the algorithm and size of a public key */
+static int cert_get_pkey_algo(X509 *crt, struct buffer *out)
 {
-       struct ssl_sock_ctx *ctx;
        int bits = 0;
        int sig = TLSEXT_signature_anonymous;
        int len = -1;
-       X509 *crt;
        EVP_PKEY *pkey;
 
-       if (!ssl_sock_is_ssl(conn))
-               return 0;
-       ctx = conn->xprt_ctx;
-
-       crt = SSL_get_certificate(ctx->ssl);
-       if (!crt)
-               return 0;
        pkey = X509_get_pubkey(crt);
        if (pkey) {
                bits = EVP_PKEY_bits(pkey);
@@ -6914,6 +6905,24 @@ int ssl_sock_get_pkey_algo(struct connection *conn, struct buffer *out)
        return 1;
 }
 
+/* used for ppv2 pkey alog (can be used for logging) */
+int ssl_sock_get_pkey_algo(struct connection *conn, struct buffer *out)
+{
+       struct ssl_sock_ctx *ctx;
+       X509 *crt;
+
+       if (!ssl_sock_is_ssl(conn))
+               return 0;
+
+       ctx = conn->xprt_ctx;
+
+       crt = SSL_get_certificate(ctx->ssl);
+       if (!crt)
+               return 0;
+
+       return cert_get_pkey_algo(crt, out);
+}
+
 /* used for ppv2 cert signature (can be used for logging) */
 const char *ssl_sock_get_cert_sig(struct connection *conn)
 {
@@ -7113,6 +7122,36 @@ ssl_sock_get_dn_entry(X509_NAME *a, const struct buffer *entry, int pos,
 
 }
 
+/*
+ * Extract and format the DNS SAN extensions and copy result into a chuink
+ * Return 0;
+ */
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+static int ssl_sock_get_san_oneline(X509 *cert, struct buffer *out)
+{
+       int i;
+       char *str;
+       STACK_OF(GENERAL_NAME) *names = NULL;
+
+       names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
+       if (names) {
+               for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
+                       GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
+                       if (i > 0)
+                               chunk_appendf(out, ", ");
+                       if (name->type == GEN_DNS) {
+                               if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) {
+                                       chunk_appendf(out, "DNS:%s", str);
+                                       OPENSSL_free(str);
+                               }
+                       }
+               }
+               sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
+       }
+       return 0;
+}
+#endif
+
 /* Extract and format full DN from a X509_NAME and copy result into a chunk
  * Returns 1 if dn entries exits, 0 if no dn entry found or -1 if output is not large enough.
  */
@@ -10137,6 +10176,225 @@ enum {
        SETCERT_ST_FIN,
 };
 
+/* release function of the  `show ssl cert' command */
+static void cli_release_show_cert(struct appctx *appctx)
+{
+       HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+}
+
+/* IO handler of "show ssl cert <filename>" */
+static int cli_io_handler_show_cert(struct appctx *appctx)
+{
+       struct buffer *trash = alloc_trash_chunk();
+       struct ebmb_node *node;
+       struct stream_interface *si = appctx->owner;
+       struct ckch_store *ckchs;
+       int n;
+
+       if (trash == NULL)
+               return 1;
+
+       if (!appctx->ctx.ssl.old_ckchs) {
+               if (ckchs_transaction.old_ckchs) {
+                       ckchs = ckchs_transaction.old_ckchs;
+                       chunk_appendf(trash, "# transaction\n");
+                       if (!ckchs->multi) {
+                               chunk_appendf(trash, "*%s\n", ckchs->path);
+                       } else {
+                               chunk_appendf(trash, "*%s:", ckchs->path);
+                               for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
+                                       if (ckchs->ckch[n].cert)
+                                               chunk_appendf(trash, " %s.%s\n", ckchs->path, SSL_SOCK_KEYTYPE_NAMES[n]);
+                               }
+                               chunk_appendf(trash, "\n");
+                       }
+               }
+       }
+
+       if (!appctx->ctx.cli.p0) {
+               chunk_appendf(trash, "# filename\n");
+               node = ebmb_first(&ckchs_tree);
+       } else {
+               node = &((struct ckch_store *)appctx->ctx.cli.p0)->node;
+       }
+       while (node) {
+               ckchs = ebmb_entry(node, struct ckch_store, node);
+               if (!ckchs->multi) {
+                       chunk_appendf(trash, "%s\n", ckchs->path);
+               } else {
+                       chunk_appendf(trash, "%s:", ckchs->path);
+                       for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
+                               if (ckchs->ckch[n].cert)
+                                       chunk_appendf(trash, " %s.%s", ckchs->path, SSL_SOCK_KEYTYPE_NAMES[n]);
+                       }
+                       chunk_appendf(trash, "\n");
+               }
+
+               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 = ckchs;
+       return 0; /* should come back */
+}
+
+/* IO handler of the details "show ssl cert <filename>" */
+static int cli_io_handler_show_cert_detail(struct appctx *appctx)
+{
+       struct stream_interface *si = appctx->owner;
+       struct ckch_store *ckchs = appctx->ctx.cli.p0;
+       struct buffer *out = alloc_trash_chunk();
+       struct buffer *tmp = alloc_trash_chunk();
+       X509_NAME *name = NULL;
+       int write = -1;
+       BIO *bio = NULL;
+
+       if (!tmp || !out)
+               goto end;
+
+       if (!ckchs->multi) {
+               chunk_appendf(out, "Filename: ");
+               if (ckchs == ckchs_transaction.new_ckchs)
+                       chunk_appendf(out, "*");
+               chunk_appendf(out, "%s\n", ckchs->path);
+               chunk_appendf(out, "Serial: ");
+               if (ssl_sock_get_serial(ckchs->ckch->cert, tmp) == -1)
+                       goto end;
+               dump_binary(out, tmp->area, tmp->data);
+               chunk_appendf(out, "\n");
+
+               chunk_appendf(out, "notBefore: ");
+               chunk_reset(tmp);
+               if ((bio = BIO_new(BIO_s_mem())) ==  NULL)
+                       goto end;
+               if (ASN1_TIME_print(bio, X509_getm_notBefore(ckchs->ckch->cert)) == 0)
+                       goto end;
+               write = BIO_read(bio, tmp->area, tmp->size-1);
+               tmp->area[write] = '\0';
+               BIO_free(bio);
+               chunk_appendf(out, "%s\n", tmp->area);
+
+               chunk_appendf(out, "notAfter: ");
+               chunk_reset(tmp);
+               if ((bio = BIO_new(BIO_s_mem())) == NULL)
+                       goto end;
+               if (ASN1_TIME_print(bio, X509_getm_notAfter(ckchs->ckch->cert)) == 0)
+                       goto end;
+               if ((write = BIO_read(bio, tmp->area, tmp->size-1)) <= 0)
+                       goto end;
+               tmp->area[write] = '\0';
+               BIO_free(bio);
+               chunk_appendf(out, "%s\n", tmp->area);
+
+
+               chunk_appendf(out, "Issuer: ");
+               if ((name = X509_get_issuer_name(ckchs->ckch->cert)) == NULL)
+                       goto end;
+               if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
+                       goto end;
+               *(tmp->area + tmp->data) = '\0';
+               chunk_appendf(out, "%s\n", tmp->area);
+
+               chunk_appendf(out, "Subject: ");
+               if ((name = X509_get_subject_name(ckchs->ckch->cert)) == NULL)
+                       goto end;
+               if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
+                       goto end;
+               *(tmp->area + tmp->data) = '\0';
+               chunk_appendf(out, "%s\n", tmp->area);
+
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+               chunk_appendf(out, "Subject Alternative Name: ");
+               if (ssl_sock_get_san_oneline(ckchs->ckch->cert, out) == -1)
+                   goto end;
+               *(out->area + out->data) = '\0';
+               chunk_appendf(out, "\n");
+#endif
+               chunk_reset(tmp);
+               chunk_appendf(out, "Algorithm: ");
+               if (cert_get_pkey_algo(ckchs->ckch->cert, tmp) == 0)
+                       goto end;
+               chunk_appendf(out, "%s\n", tmp->area);
+
+               chunk_reset(tmp);
+               chunk_appendf(out, "SHA1 FingerPrint: ");
+               if (X509_digest(ckchs->ckch->cert, EVP_sha1(), (unsigned char *) tmp->area,
+                               (unsigned int *)&tmp->data) == 0)
+                       goto end;
+               dump_binary(out, tmp->area, tmp->data);
+               chunk_appendf(out, "\n");
+       }
+
+       if (ci_putchk(si_ic(si), out) == -1) {
+               si_rx_room_blk(si);
+               goto yield;
+       }
+
+end:
+       free_trash_chunk(tmp);
+       free_trash_chunk(out);
+       return 1;
+yield:
+       free_trash_chunk(tmp);
+       free_trash_chunk(out);
+       return 0; /* should come back */
+}
+
+/* parsing function for 'show ssl cert [certfile]' */
+static int cli_parse_show_cert(char **args, char *payload, struct appctx *appctx, void *private)
+{
+       struct ckch_store *ckchs;
+
+       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]) {
+               if (*args[3] == '*') {
+                       if (!ckchs_transaction.new_ckchs)
+                               goto error;
+
+                       ckchs = ckchs_transaction.new_ckchs;
+
+                       if (strcmp(args[3] + 1, ckchs->path))
+                               goto error;
+
+               } else {
+                       if ((ckchs = ckchs_lookup(args[3])) == NULL)
+                               goto error;
+
+               }
+
+               if (ckchs->multi)
+                       goto error;
+
+               appctx->ctx.cli.p0 = ckchs;
+               /* use the IO handler that shows details */
+               appctx->io_handler = cli_io_handler_show_cert_detail;
+       }
+
+       return 0;
+
+error:
+       HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+       return cli_err(appctx, "Can't display the certificate: Not found or the certificate is a bundle!\n");
+}
+
 /* release function of the  `set ssl cert' command, free things and unlock the spinlock */
 static void cli_release_commit_cert(struct appctx *appctx)
 {
@@ -10859,6 +11117,7 @@ static struct cli_kw_list cli_kws = {{ },{
        { { "set", "ssl", "cert", NULL }, "set ssl cert <certfile> <payload> : replace a certificate file", cli_parse_set_cert, NULL, NULL },
        { { "commit", "ssl", "cert", NULL }, "commit ssl cert <certfile> : commit a certificate file", cli_parse_commit_cert, cli_io_handler_commit_cert, cli_release_commit_cert },
        { { "abort", "ssl", "cert", NULL }, "abort ssl cert <certfile> : abort a transaction for a certificate file", cli_parse_abort_cert, NULL, NULL },
+       { { "show", "ssl", "cert", NULL }, "show ssl cert [<certfile>] : display the SSL certificates used in memory, or the details of a <certfile>", cli_parse_show_cert, cli_io_handler_show_cert, cli_release_show_cert },
        { { NULL }, NULL, NULL, NULL }
 }};