MEDIUM: ssl: allow loading of a directory with the ca-file directive
This patch implements the ability to load a certificate directory with
the "ca-file" directive.
The X509_STORE_load_locations() API does not allow to cache a directory
in memory at startup, it only references the directory to allow a lookup
of the files when needed. But that is not compatible with the way
HAProxy works, without any access to the filesystem.
The current implementation loads every ".pem", ".crt", ".cer", and
".crl" available in the directory which is what is done when using
c_rehash and X509_STORE_load_locations(). Those files are cached in the
same X509_STORE referenced by the directory name. When looking at "show ssl
ca-file", everything will be shown in the same entry.
This will eventually allow to load more easily the CA of the system,
which could already be done with "ca-file /etc/ssl/certs" in the
configuration.
Loading failure intentionally emit a warning instead of an alert,
letting HAProxy starts when one of the files can't be loaded.
Known limitations:
- There is a bug in "show ssl ca-file", once the buffer is full, the
iohandler is not called again to output the next entries.
- The CLI API is kind of limited with this, since it does not allow to
add or remove a entry in a particular ca-file. And with a lot of
CAs you can't push them all in a buffer. It probably needs a "add ssl
ca-file" like its done with the crt-list.
Fix issue #1476.
diff --git a/src/ssl_ckch.c b/src/ssl_ckch.c
index 6920706..f1fba33 100644
--- a/src/ssl_ckch.c
+++ b/src/ssl_ckch.c
@@ -11,6 +11,7 @@
#define _GNU_SOURCE
#include <ctype.h>
+#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
@@ -1096,19 +1097,101 @@
* X509_STORE_load_locations function because it performs forbidden disk
* accesses. */
if (!store && create_if_none) {
+ STACK_OF(X509_OBJECT) *objs;
+ int cert_count = 0;
+ struct stat buf;
struct cafile_entry *ca_e;
+ char *file = NULL;
+ char *dir = NULL;
+
store = X509_STORE_new();
- if (X509_STORE_load_locations(store, path, NULL)) {
- ca_e = ssl_store_create_cafile_entry(path, store, type);
- if (ca_e) {
- ebst_insert(&cafile_tree, &ca_e->node);
+
+ if (stat(path, &buf))
+ goto err;
+
+ if (S_ISDIR(buf.st_mode))
+ dir = path;
+ else
+ file = path;
+
+ if (file) {
+ if (!X509_STORE_load_locations(store, file, NULL)) {
+ goto err;
}
} else {
- X509_STORE_free(store);
- store = NULL;
+ int n, i;
+ struct dirent **de_list;
+
+ n = scandir(dir, &de_list, 0, alphasort);
+ if (n < 0)
+ goto err;
+
+ for (i= 0; i < n; i++) {
+ char *end;
+ struct dirent *de = de_list[i];
+ BIO *in = NULL;
+ X509 *ca = NULL;;
+
+ /* we try to load the files that would have
+ * been loaded in an hashed directory loaded by
+ * X509_LOOKUP_hash_dir, so according to "man 1
+ * c_rehash", we should load ".pem", ".crt",
+ * ".cer", or ".crl"
+ */
+ end = strrchr(de->d_name, '.');
+ if (!end || (strcmp(end, ".pem") != 0 &&
+ strcmp(end, ".crt") != 0 &&
+ strcmp(end, ".cer") != 0 &&
+ strcmp(end, ".crl") != 0)) {
+ free(de);
+ continue;
+ }
+ in = BIO_new(BIO_s_file());
+ if (in == NULL)
+ goto scandir_err;
+
+ chunk_printf(&trash, "%s/%s", path, de->d_name);
+
+ if (BIO_read_filename(in, trash.area) == 0)
+ goto scandir_err;
+
+ if (PEM_read_bio_X509_AUX(in, &ca, NULL, NULL) == NULL)
+ goto scandir_err;
+
+ if (X509_STORE_add_cert(store, ca) == 0)
+ goto scandir_err;
+
+ BIO_free(in);
+ free(de);
+ continue;
+
+scandir_err:
+ BIO_free(in);
+ free(de);
+ ha_warning("ca-file: '%s' couldn't load '%s'\n", path, trash.area);
+ break;
+
+ }
+ free(de_list);
}
+
+ objs = X509_STORE_get0_objects(store);
+ cert_count = sk_X509_OBJECT_num(objs);
+ if (cert_count == 0)
+ ha_warning("ca-file: 0 CA were loaded from '%s'\n", path);
+
+ ca_e = ssl_store_create_cafile_entry(path, store, type);
+ if (!ca_e)
+ goto err;
+ ebst_insert(&cafile_tree, &ca_e->node);
}
return (store != NULL);
+
+err:
+ X509_STORE_free(store);
+ store = NULL;
+ return 0;
+
}