]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: ssl/cli: implement "add ssl ca-file"
authorWilliam Lallemand <wlallemand@haproxy.org>
Fri, 29 Jul 2022 15:50:58 +0000 (17:50 +0200)
committerWilliam Lallemand <wlallemand@haproxy.org>
Fri, 19 Aug 2022 17:58:53 +0000 (19:58 +0200)
In ticket #1805 an user is impacted by the limitation of size of the CLI
buffer when updating a ca-file.

This patch allows a user to append new certificates to a ca-file instead
of trying to put them all with "set ssl ca-file"

The implementation use a new function ssl_store_dup_cafile_entry() which
duplicates a cafile_entry and its X509_STORE.

ssl_store_load_ca_from_buf() was modified to take an apped parameter so
we could share the function for "set" and "add".

doc/management.txt
include/haproxy/ssl_ckch.h
reg-tests/ssl/new_del_ssl_cafile.vtc
reg-tests/ssl/set_ssl_cafile.vtc
src/ssl_ckch.c

index 6aeb08d38b6fc6239acf0408b964284a65328c43..e88b243495bd6649b08f89f0954a11e1393ae9c4 100644 (file)
@@ -1679,6 +1679,21 @@ add server <backend>/<server> [args]*
   Their syntax is similar to the server line from the configuration file,
   please refer to their individual documentation for details.
 
+add ssl ca-file <cafile> <payload>
+   Add a new certificate to a ca-file. This command is useful when you reached
+   the buffer size limit on the CLI and want to add multiple certicates.
+   Instead of doing a "set" with all the certificates you are able to add each
+   certificate individually. A "set ssl ca-file" will reset the ca-file.
+
+  Example:
+    echo -e "set ssl ca-file cafile.pem <<\n$(cat rootCA.crt)\n" | \
+    socat /var/run/haproxy.stat -
+    echo -e "add ssl ca-file cafile.pem <<\n$(cat intermediate1.crt)\n" | \
+    socat /var/run/haproxy.stat -
+    echo -e "add ssl ca-file cafile.pem <<\n$(cat intermediate2.crt)\n" | \
+    socat /var/run/haproxy.stat -
+    echo "commit ssl ca-file cafile.pem" | socat /var/run/haproxy.stat -
+
 add ssl crt-list <crtlist> <certificate>
 add ssl crt-list <crtlist> <payload>
   Add an certificate in a crt-list. It can also be used for directories since
@@ -1821,8 +1836,8 @@ commit ssl ca-file <cafile>
   contexts that use it, you will need to add it to a crt-list with "add ssl
   crt-list".
 
-  See also "new ssl ca-file", "set ssl ca-file", "abort ssl ca-file" and
-  "add ssl crt-list".
+  See also "new ssl ca-file", "set ssl ca-file", "add ssl ca-file",
+  "abort ssl ca-file" and "add ssl crt-list".
 
 commit ssl cert <filename>
   Commit a temporary SSL certificate update transaction.
@@ -2127,7 +2142,7 @@ httpclient <method> <URI>
 new ssl ca-file <cafile>
   Create a new empty CA file tree entry to be filled with a set of CA
   certificates and added to a crt-list. This command should be used in
-  combination with "set ssl ca-file" and "add ssl crt-list".
+  combination with "set ssl ca-file", "add ssl ca-file" and "add ssl crt-list".
 
 new ssl cert <filename>
   Create a new empty SSL certificate store to be filled with a certificate and
@@ -2309,15 +2324,16 @@ set severity-output [ none | number | string ]
   duration of the current session.
 
 set ssl ca-file <cafile> <payload>
-  This command is part of a transaction system, the "commit ssl ca-file" and
+  this command is part of a transaction system, the "commit ssl ca-file" and
   "abort ssl ca-file" commands could be required.
-  If there is no on-going transaction, it will create a CA file tree entry into
-  which the certificates contained in the payload will be stored. The CA file
-  entry will not be stored in the CA file tree and will only be kept in a
-  temporary transaction. If a transaction with the same filename already exists,
-  the previous CA file entry will be deleted and replaced by the new one.
-  Once the modifications are done, you have to commit the transaction through
-  a "commit ssl ca-file" call.
+  if there is no on-going transaction, it will create a ca file tree entry into
+  which the certificates contained in the payload will be stored. the ca file
+  entry will not be stored in the ca file tree and will only be kept in a
+  temporary transaction. if a transaction with the same filename already exists,
+  the previous ca file entry will be deleted and replaced by the new one.
+  once the modifications are done, you have to commit the transaction through
+  a "commit ssl ca-file" call. If you want to add multiple certificates
+  separately, you can use the "add ssl ca-file" command
 
   Example:
     echo -e "set ssl ca-file cafile.pem <<\n$(cat rootCA.crt)\n" | \
index ada30f791aaae474535e4ea10b0b30e90b8a1c17..085c5c0428598193381e21f07b3a34567837f177 100644 (file)
@@ -63,6 +63,7 @@ struct cafile_entry *ssl_store_get_cafile_entry(char *path, int oldest_entry);
 X509_STORE* ssl_store_get0_locations_file(char *path);
 int ssl_store_add_uncommitted_cafile_entry(struct cafile_entry *entry);
 struct cafile_entry *ssl_store_create_cafile_entry(char *path, X509_STORE *store, enum cafile_type type);
+struct cafile_entry *ssl_store_dup_cafile_entry(struct cafile_entry *src);
 void ssl_store_delete_cafile_entry(struct cafile_entry *ca_e);
 int ssl_store_load_ca_from_buf(struct cafile_entry *ca_e, char *cert_buf, int append);
 int ssl_store_load_locations_file(char *path, int create_if_none, enum cafile_type type);
index 4b045715d2e2a60408c7eae8e10630897bbf4460..2123fb030c508ad77d769233899e51f2141a8c3a 100644 (file)
@@ -81,6 +81,26 @@ shell {
     echo "commit ssl ca-file new_cafile.crt" | socat "${tmpdir}/h1/stats" -
 }
 
+# Remove the unliked CA file and create a new one with the "add ssl ca-file method"
+
+haproxy h1 -cli {
+    send "del ssl ca-file new_cafile.crt"
+    expect ~ "CA file 'new_cafile.crt' deleted!"
+
+    send "new ssl ca-file new_cafile.crt"
+    expect ~ "New CA file created 'new_cafile.crt'!"
+}
+
+shell {
+    printf "add ssl ca-file new_cafile.crt <<\n$(cat ${testdir}/set_cafile_interCA1.crt)\n\n" | socat "${tmpdir}/h1/stats" -
+    echo "commit ssl ca-file new_cafile.crt" | socat "${tmpdir}/h1/stats" -
+}
+
+shell {
+    printf "set ssl ca-file new_cafile.crt <<\n$(cat ${testdir}/set_cafile_interCA1.crt)\n\n" | socat "${tmpdir}/h1/stats" -
+    echo "commit ssl ca-file new_cafile.crt" | socat "${tmpdir}/h1/stats" -
+}
+
 haproxy h1 -cli {
     send "show ssl ca-file"
     expect ~ ".*new_cafile.crt - 1 certificate.*"
index bda620f4e42decfe31d13e1bf27b16be5916963e..b93e78acf70294360f6e3a1e605fa8a90df7623c 100644 (file)
@@ -138,7 +138,9 @@ client c1 -connect ${h1_clearverifiedlst_sock} {
 # Update the server line's ca-file. The server certificate should now be accepted by
 # the frontend. We replace the single CA by a list of CAs that includes the correct one.
 shell {
-    printf "set ssl ca-file ${testdir}/set_cafile_interCA1.crt <<\n$(cat ${testdir}/set_cafile_interCA1.crt)\n$(cat ${testdir}/set_cafile_interCA2.crt)\n$(cat ${testdir}/set_cafile_rootCA.crt)\n\n" | socat "${tmpdir}/h1/stats" -
+    printf "set ssl ca-file ${testdir}/set_cafile_interCA1.crt <<\n$(cat ${testdir}/set_cafile_interCA1.crt)\n\n" | socat "${tmpdir}/h1/stats" -
+    printf "add ssl ca-file ${testdir}/set_cafile_interCA1.crt <<\n$(cat ${testdir}/set_cafile_interCA2.crt)\n\n" | socat "${tmpdir}/h1/stats" -
+    printf "add ssl ca-file ${testdir}/set_cafile_interCA1.crt <<\n$(cat ${testdir}/set_cafile_rootCA.crt)\n\n" | socat "${tmpdir}/h1/stats" -
     echo "commit ssl ca-file ${testdir}/set_cafile_interCA1.crt" | socat "${tmpdir}/h1/stats" -
 }
 
index 3f49067370cfacc3c8478aaebfe6156a26ae9ba9..d531d39ae980557349fe7e5e0fbed3b1883d2150 100644 (file)
@@ -1076,6 +1076,67 @@ struct cafile_entry *ssl_store_create_cafile_entry(char *path, X509_STORE *store
        return ca_e;
 }
 
+
+/* Duplicate a cafile_entry
+ * Allocate the X509_STORE and copy the X509 and CRL inside.
+ *
+ * Return the newly allocated cafile_entry or NULL.
+ *
+ */
+struct cafile_entry *ssl_store_dup_cafile_entry(struct cafile_entry *src)
+{
+       struct cafile_entry *dst = NULL;
+       X509_STORE *store = NULL;
+       STACK_OF(X509_OBJECT) *objs;
+       int i;
+
+       if (!src)
+               return NULL;
+
+       if (src->ca_store) {
+               /* if there was a store in the src, copy it */
+               store = X509_STORE_new();
+               if (!store)
+                       goto err;
+
+               objs = X509_STORE_get0_objects(src->ca_store);
+               for (i = 0; i < sk_X509_OBJECT_num(objs); i++) {
+                       X509 *cert;
+                       X509_CRL *crl;
+
+                       cert = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(objs, i));
+                       if (cert) {
+                               if (X509_STORE_add_cert(store, cert) == 0) {
+                                       /* only exits on error if the error is not about duplicate certificates */
+                                       if (!(ERR_GET_REASON(ERR_get_error()) == X509_R_CERT_ALREADY_IN_HASH_TABLE)) {
+                                               goto err;
+                                       }
+                               }
+
+                       }
+                       crl = X509_OBJECT_get0_X509_CRL(sk_X509_OBJECT_value(objs, i));
+                       if (crl) {
+                               if (X509_STORE_add_crl(store, crl) == 0) {
+                                       /* only exits on error if the error is not about duplicate certificates */
+                                       if (!(ERR_GET_REASON(ERR_get_error()) == X509_R_CERT_ALREADY_IN_HASH_TABLE)) {
+                                               goto err;
+                                       }
+                               }
+
+                       }
+               }
+       }
+       dst = ssl_store_create_cafile_entry(src->path, store, src->type);
+
+       return dst;
+
+err:
+       X509_STORE_free(store);
+       ha_free(&dst);
+
+       return NULL;
+}
+
 /* Delete a cafile_entry. The caller is responsible from removing this entry
  * from the cafile_tree first if is was previously added into it. */
 void ssl_store_delete_cafile_entry(struct cafile_entry *ca_e)
@@ -2584,10 +2645,15 @@ static int cli_parse_set_cafile(char **args, char *payload, struct appctx *appct
        char *err = NULL;
        int errcode = 0;
        struct buffer *buf;
+       int add_cmd = 0;
 
        if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
                return 1;
 
+       /* this is "add ssl ca-file" */
+       if (*args[0] == 'a')
+               add_cmd = 1;
+
        if (!*args[3] || !payload)
                return cli_err(appctx, "'set ssl ca-file expects a filename and CAs as a payload\n");
 
@@ -2620,8 +2686,7 @@ static int cli_parse_set_cafile(char **args, char *payload, struct appctx *appct
                        goto end;
                }
                old_cafile_entry = cafile_transaction.old_cafile_entry;
-       }
-       else {
+       } else {
                /* lookup for the certificate in the tree */
                old_cafile_entry = ssl_store_get_cafile_entry(buf->area, 0);
        }
@@ -2633,17 +2698,21 @@ static int cli_parse_set_cafile(char **args, char *payload, struct appctx *appct
                goto end;
        }
 
-       /* Create a new cafile_entry without adding it to the cafile tree. */
-       new_cafile_entry = ssl_store_create_cafile_entry(old_cafile_entry->path, NULL, CAFILE_CERT);
+       /* if the transaction is new, duplicate the old_ca_file_entry, otherwise duplicate the cafile in the current transaction */
+       if (cafile_transaction.new_cafile_entry)
+               new_cafile_entry = ssl_store_dup_cafile_entry(cafile_transaction.new_cafile_entry);
+       else
+               new_cafile_entry = ssl_store_dup_cafile_entry(old_cafile_entry);
+
        if (!new_cafile_entry) {
-               memprintf(&err, "%sCannot allocate memory!\n",
-                         err ? err : "");
+               memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
                errcode |= ERR_ALERT | ERR_FATAL;
                goto end;
        }
 
-       /* Fill the new entry with the new CAs. */
-       if (ssl_store_load_ca_from_buf(new_cafile_entry, payload, 0)) {
+       /* Fill the new entry with the new CAs. The add_cmd variable determine
+          if we flush the X509_STORE or not */
+       if (ssl_store_load_ca_from_buf(new_cafile_entry, payload, add_cmd)) {
                memprintf(&err, "%sInvalid payload\n", err ? err : "");
                errcode |= ERR_ALERT | ERR_FATAL;
                goto end;
@@ -3853,6 +3922,7 @@ static struct cli_kw_list cli_kws = {{ },{
        { { "show", "ssl", "cert", NULL },      "show ssl cert [<certfile>]              : display the SSL certificates used in memory, or the details of a file", cli_parse_show_cert, cli_io_handler_show_cert, cli_release_show_cert },
 
        { { "new", "ssl", "ca-file", NULL },    "new ssl ca-file <cafile>                : create a new CA file to be used in a crt-list",                         cli_parse_new_cafile, NULL, NULL },
+       { { "add", "ssl", "ca-file", NULL },    "add ssl ca-file <cafile> <payload>      : add a certificate into the CA file",                                    cli_parse_set_cafile, NULL, NULL },
        { { "set", "ssl", "ca-file", NULL },    "set ssl ca-file <cafile> <payload>      : replace a CA file",                                                     cli_parse_set_cafile, NULL, NULL },
        { { "commit", "ssl", "ca-file", NULL }, "commit ssl ca-file <cafile>             : commit a CA file",                                                      cli_parse_commit_cafile, cli_io_handler_commit_cafile_crlfile, cli_release_commit_cafile },
        { { "abort", "ssl", "ca-file", NULL },  "abort ssl ca-file <cafile>              : abort a transaction for a CA file",                                     cli_parse_abort_cafile, NULL, NULL },