summaryrefslogtreecommitdiffstats
path: root/src/ssl_ckch.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/ssl_ckch.c3938
1 files changed, 3938 insertions, 0 deletions
diff --git a/src/ssl_ckch.c b/src/ssl_ckch.c
new file mode 100644
index 0000000..19980c3
--- /dev/null
+++ b/src/ssl_ckch.c
@@ -0,0 +1,3938 @@
+/*
+ *
+ * Copyright (C) 2020 HAProxy Technologies, William Lallemand <wlallemand@haproxy.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#define _GNU_SOURCE
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <import/ebpttree.h>
+#include <import/ebsttree.h>
+
+#include <haproxy/applet.h>
+#include <haproxy/base64.h>
+#include <haproxy/channel.h>
+#include <haproxy/cli.h>
+#include <haproxy/errors.h>
+#include <haproxy/sc_strm.h>
+#include <haproxy/ssl_ckch.h>
+#include <haproxy/ssl_sock.h>
+#include <haproxy/ssl_utils.h>
+#include <haproxy/stconn.h>
+#include <haproxy/tools.h>
+
+/* Uncommitted CKCH transaction */
+
+static struct {
+ struct ckch_store *new_ckchs;
+ struct ckch_store *old_ckchs;
+ char *path;
+} ckchs_transaction;
+
+/* Uncommitted CA file transaction */
+
+static struct {
+ struct cafile_entry *old_cafile_entry;
+ struct cafile_entry *new_cafile_entry;
+ char *path;
+} cafile_transaction;
+
+/* Uncommitted CRL file transaction */
+
+static struct {
+ struct cafile_entry *old_crlfile_entry;
+ struct cafile_entry *new_crlfile_entry;
+ char *path;
+} crlfile_transaction;
+
+/* CLI context used by "show cafile" */
+struct show_cafile_ctx {
+ struct cafile_entry *cur_cafile_entry;
+ struct cafile_entry *old_cafile_entry;
+ int ca_index;
+ int show_all;
+};
+
+/* CLI context used by "show crlfile" */
+struct show_crlfile_ctx {
+ struct cafile_entry *cafile_entry;
+ struct cafile_entry *old_crlfile_entry;
+ int index;
+};
+
+/* CLI context used by "show cert" */
+struct show_cert_ctx {
+ struct ckch_store *old_ckchs;
+ struct ckch_store *cur_ckchs;
+ int transaction;
+};
+
+/* CLI context used by "commit cert" */
+struct commit_cert_ctx {
+ struct ckch_store *old_ckchs;
+ struct ckch_store *new_ckchs;
+ struct ckch_inst *next_ckchi;
+ char *err;
+ enum {
+ CERT_ST_INIT = 0,
+ CERT_ST_GEN,
+ CERT_ST_INSERT,
+ CERT_ST_SUCCESS,
+ CERT_ST_FIN,
+ CERT_ST_ERROR,
+ } state;
+};
+
+/* CLI context used by "set cert" */
+struct set_cert_ctx {
+ struct ckch_store *old_ckchs;
+ struct ckch_store *new_ckchs;
+};
+
+/* CLI context used by "set ca-file" */
+struct set_cafile_ctx {
+ struct cafile_entry *old_cafile_entry;
+ struct cafile_entry *new_cafile_entry;
+};
+
+/* CLI context used by "set crl-file" */
+struct set_crlfile_ctx {
+ struct cafile_entry *old_crlfile_entry;
+ struct cafile_entry *new_crlfile_entry;
+};
+
+/* CLI context used by "commit cafile" and "commit crlfile" */
+struct commit_cacrlfile_ctx {
+ struct cafile_entry *old_cafile_entry;
+ struct cafile_entry *new_cafile_entry;
+ struct cafile_entry *old_crlfile_entry;
+ struct cafile_entry *new_crlfile_entry;
+ struct ckch_inst_link *next_ckchi_link;
+ struct ckch_inst *next_ckchi;
+ int cafile_type; /* either CA or CRL, depending on the current command */
+ char *err;
+ enum {
+ CACRL_ST_INIT = 0,
+ CACRL_ST_GEN,
+ CACRL_ST_INSERT,
+ CACRL_ST_SUCCESS,
+ CACRL_ST_FIN,
+ CACRL_ST_ERROR,
+ } state;
+};
+
+
+/******************** cert_key_and_chain functions *************************
+ * These are the functions that fills a cert_key_and_chain structure. For the
+ * functions filling a SSL_CTX from a cert_key_and_chain, see ssl_sock.c
+ */
+
+/*
+ * Try to parse Signed Certificate Timestamp List structure. This function
+ * makes only basic test if the data seems like SCTL. No signature validation
+ * is performed.
+ */
+static int ssl_sock_parse_sctl(struct buffer *sctl)
+{
+ int ret = 1;
+ int len, pos, sct_len;
+ unsigned char *data;
+
+ if (sctl->data < 2)
+ goto out;
+
+ data = (unsigned char *) sctl->area;
+ len = (data[0] << 8) | data[1];
+
+ if (len + 2 != sctl->data)
+ goto out;
+
+ data = data + 2;
+ pos = 0;
+ while (pos < len) {
+ if (len - pos < 2)
+ goto out;
+
+ sct_len = (data[pos] << 8) | data[pos + 1];
+ if (pos + sct_len + 2 > len)
+ goto out;
+
+ pos += sct_len + 2;
+ }
+
+ ret = 0;
+
+out:
+ return ret;
+}
+
+/* Try to load a sctl from a buffer <buf> if not NULL, or read the file <sctl_path>
+ * It fills the ckch->sctl buffer
+ * return 0 on success or != 0 on failure */
+int ssl_sock_load_sctl_from_file(const char *sctl_path, char *buf, struct cert_key_and_chain *ckch, char **err)
+{
+ int fd = -1;
+ int r = 0;
+ int ret = 1;
+ struct buffer tmp;
+ struct buffer *src;
+ struct buffer *sctl;
+
+ if (buf) {
+ chunk_initstr(&tmp, buf);
+ src = &tmp;
+ } else {
+ fd = open(sctl_path, O_RDONLY);
+ if (fd == -1)
+ goto end;
+
+ trash.data = 0;
+ while (trash.data < trash.size) {
+ r = read(fd, trash.area + trash.data, trash.size - trash.data);
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ goto end;
+ }
+ else if (r == 0) {
+ break;
+ }
+ trash.data += r;
+ }
+ src = &trash;
+ }
+
+ ret = ssl_sock_parse_sctl(src);
+ if (ret)
+ goto end;
+
+ sctl = calloc(1, sizeof(*sctl));
+ if (!chunk_dup(sctl, src)) {
+ ha_free(&sctl);
+ goto end;
+ }
+ /* no error, fill ckch with new context, old context must be free */
+ if (ckch->sctl) {
+ ha_free(&ckch->sctl->area);
+ free(ckch->sctl);
+ }
+ ckch->sctl = sctl;
+ ret = 0;
+end:
+ if (fd != -1)
+ close(fd);
+
+ return ret;
+}
+
+#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
+/*
+ * This function load the OCSP Response in DER format contained in file at
+ * path 'ocsp_path' or base64 in a buffer <buf>
+ *
+ * Returns 0 on success, 1 in error case.
+ */
+int ssl_sock_load_ocsp_response_from_file(const char *ocsp_path, char *buf, struct cert_key_and_chain *ckch, char **err)
+{
+ int fd = -1;
+ int r = 0;
+ int ret = 1;
+ struct buffer *ocsp_response;
+ struct buffer *src = NULL;
+
+ if (buf) {
+ int i, j;
+ /* if it's from a buffer it will be base64 */
+
+ /* remove \r and \n from the payload */
+ for (i = 0, j = 0; buf[i]; i++) {
+ if (buf[i] == '\r' || buf[i] == '\n')
+ continue;
+ buf[j++] = buf[i];
+ }
+ buf[j] = 0;
+
+ ret = base64dec(buf, j, trash.area, trash.size);
+ if (ret < 0) {
+ memprintf(err, "Error reading OCSP response in base64 format");
+ goto end;
+ }
+ trash.data = ret;
+ src = &trash;
+ } else {
+ fd = open(ocsp_path, O_RDONLY);
+ if (fd == -1) {
+ memprintf(err, "Error opening OCSP response file");
+ goto end;
+ }
+
+ trash.data = 0;
+ while (trash.data < trash.size) {
+ r = read(fd, trash.area + trash.data, trash.size - trash.data);
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+
+ memprintf(err, "Error reading OCSP response from file");
+ goto end;
+ }
+ else if (r == 0) {
+ break;
+ }
+ trash.data += r;
+ }
+ close(fd);
+ fd = -1;
+ src = &trash;
+ }
+
+ ocsp_response = calloc(1, sizeof(*ocsp_response));
+ if (!chunk_dup(ocsp_response, src)) {
+ ha_free(&ocsp_response);
+ goto end;
+ }
+ /* no error, fill ckch with new context, old context must be free */
+ if (ckch->ocsp_response) {
+ ha_free(&ckch->ocsp_response->area);
+ free(ckch->ocsp_response);
+ }
+ ckch->ocsp_response = ocsp_response;
+ ret = 0;
+end:
+ if (fd != -1)
+ close(fd);
+
+ return ret;
+}
+#endif
+
+/*
+ * Try to load in a ckch every files related to a ckch.
+ * (PEM, sctl, ocsp, issuer etc.)
+ *
+ * This function is only used to load files during the configuration parsing,
+ * it is not used with the CLI.
+ *
+ * This allows us to carry the contents of the file without having to read the
+ * file multiple times. The caller must call
+ * ssl_sock_free_cert_key_and_chain_contents.
+ *
+ * returns:
+ * 0 on Success
+ * 1 on SSL Failure
+ */
+int ssl_sock_load_files_into_ckch(const char *path, struct cert_key_and_chain *ckch, char **err)
+{
+ struct buffer *fp = NULL;
+ int ret = 1;
+ struct stat st;
+
+ /* try to load the PEM */
+ if (ssl_sock_load_pem_into_ckch(path, NULL, ckch , err) != 0) {
+ goto end;
+ }
+
+ fp = alloc_trash_chunk();
+ if (!fp) {
+ memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
+ goto end;
+ }
+
+ if (!chunk_strcpy(fp, path) || (b_data(fp) > MAXPATHLEN)) {
+ memprintf(err, "%s '%s' filename too long'.\n",
+ err && *err ? *err : "", fp->area);
+ ret = 1;
+ goto end;
+ }
+
+ /* remove the ".crt" extension */
+ if (global_ssl.extra_files_noext) {
+ char *ext;
+
+ /* look for the extension */
+ if ((ext = strrchr(fp->area, '.'))) {
+
+ if (strcmp(ext, ".crt") == 0) {
+ *ext = '\0';
+ fp->data = strlen(fp->area);
+ }
+ }
+
+ }
+
+ if (ckch->key == NULL) {
+ /* If no private key was found yet and we cannot look for it in extra
+ * files, raise an error.
+ */
+ if (!(global_ssl.extra_files & SSL_GF_KEY)) {
+ memprintf(err, "%sNo Private Key found in '%s'.\n", err && *err ? *err : "", fp->area);
+ goto end;
+ }
+
+ /* try to load an external private key if it wasn't in the PEM */
+ if (!chunk_strcat(fp, ".key") || (b_data(fp) > MAXPATHLEN)) {
+ memprintf(err, "%s '%s' filename too long'.\n",
+ err && *err ? *err : "", fp->area);
+ ret = 1;
+ goto end;
+ }
+
+ if (stat(fp->area, &st) == 0) {
+ if (ssl_sock_load_key_into_ckch(fp->area, NULL, ckch, err)) {
+ memprintf(err, "%s '%s' is present but cannot be read or parsed'.\n",
+ err && *err ? *err : "", fp->area);
+ goto end;
+ }
+ }
+
+ if (ckch->key == NULL) {
+ memprintf(err, "%sNo Private Key found in '%s'.\n", err && *err ? *err : "", fp->area);
+ goto end;
+ }
+ /* remove the added extension */
+ *(fp->area + fp->data - strlen(".key")) = '\0';
+ b_sub(fp, strlen(".key"));
+ }
+
+
+ if (!X509_check_private_key(ckch->cert, ckch->key)) {
+ memprintf(err, "%sinconsistencies between private key and certificate loaded '%s'.\n",
+ err && *err ? *err : "", path);
+ goto end;
+ }
+
+#ifdef HAVE_SSL_SCTL
+ /* try to load the sctl file */
+ if (global_ssl.extra_files & SSL_GF_SCTL) {
+ struct stat st;
+
+ if (!chunk_strcat(fp, ".sctl") || b_data(fp) > MAXPATHLEN) {
+ memprintf(err, "%s '%s' filename too long'.\n",
+ err && *err ? *err : "", fp->area);
+ ret = 1;
+ goto end;
+ }
+
+ if (stat(fp->area, &st) == 0) {
+ if (ssl_sock_load_sctl_from_file(fp->area, NULL, ckch, err)) {
+ memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n",
+ err && *err ? *err : "", fp->area);
+ ret = 1;
+ goto end;
+ }
+ }
+ /* remove the added extension */
+ *(fp->area + fp->data - strlen(".sctl")) = '\0';
+ b_sub(fp, strlen(".sctl"));
+ }
+#endif
+
+ /* try to load an ocsp response file */
+ if (global_ssl.extra_files & SSL_GF_OCSP) {
+ struct stat st;
+
+ if (!chunk_strcat(fp, ".ocsp") || b_data(fp) > MAXPATHLEN) {
+ memprintf(err, "%s '%s' filename too long'.\n",
+ err && *err ? *err : "", fp->area);
+ ret = 1;
+ goto end;
+ }
+
+ if (stat(fp->area, &st) == 0) {
+ if (ssl_sock_load_ocsp_response_from_file(fp->area, NULL, ckch, err)) {
+ ret = 1;
+ goto end;
+ }
+ }
+ /* remove the added extension */
+ *(fp->area + fp->data - strlen(".ocsp")) = '\0';
+ b_sub(fp, strlen(".ocsp"));
+ }
+
+#ifndef OPENSSL_IS_BORINGSSL /* Useless for BoringSSL */
+ if (ckch->ocsp_response && (global_ssl.extra_files & SSL_GF_OCSP_ISSUER)) {
+ /* if no issuer was found, try to load an issuer from the .issuer */
+ if (!ckch->ocsp_issuer) {
+ struct stat st;
+
+ if (!chunk_strcat(fp, ".issuer") || b_data(fp) > MAXPATHLEN) {
+ memprintf(err, "%s '%s' filename too long'.\n",
+ err && *err ? *err : "", fp->area);
+ ret = 1;
+ goto end;
+ }
+
+ if (stat(fp->area, &st) == 0) {
+ if (ssl_sock_load_issuer_file_into_ckch(fp->area, NULL, ckch, err)) {
+ ret = 1;
+ goto end;
+ }
+
+ if (X509_check_issued(ckch->ocsp_issuer, ckch->cert) != X509_V_OK) {
+ memprintf(err, "%s '%s' is not an issuer'.\n",
+ err && *err ? *err : "", fp->area);
+ ret = 1;
+ goto end;
+ }
+ }
+ /* remove the added extension */
+ *(fp->area + fp->data - strlen(".issuer")) = '\0';
+ b_sub(fp, strlen(".issuer"));
+ }
+ }
+#endif
+
+ ret = 0;
+
+end:
+
+ ERR_clear_error();
+
+ /* Something went wrong in one of the reads */
+ if (ret != 0)
+ ssl_sock_free_cert_key_and_chain_contents(ckch);
+
+ free_trash_chunk(fp);
+
+ return ret;
+}
+
+/*
+ * Try to load a private key file from a <path> or a buffer <buf>
+ *
+ * If it failed you should not attempt to use the ckch but free it.
+ *
+ * Return 0 on success or != 0 on failure
+ */
+int ssl_sock_load_key_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err)
+{
+ BIO *in = NULL;
+ int ret = 1;
+ EVP_PKEY *key = NULL;
+
+ if (buf) {
+ /* reading from a buffer */
+ in = BIO_new_mem_buf(buf, -1);
+ if (in == NULL) {
+ memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
+ goto end;
+ }
+
+ } else {
+ /* reading from a file */
+ in = BIO_new(BIO_s_file());
+ if (in == NULL)
+ goto end;
+
+ if (BIO_read_filename(in, path) <= 0)
+ goto end;
+ }
+
+ /* Read Private Key */
+ key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
+ if (key == NULL) {
+ memprintf(err, "%sunable to load private key from file '%s'.\n",
+ err && *err ? *err : "", path);
+ goto end;
+ }
+
+ ret = 0;
+
+ SWAP(ckch->key, key);
+
+end:
+
+ ERR_clear_error();
+ if (in)
+ BIO_free(in);
+ if (key)
+ EVP_PKEY_free(key);
+
+ return ret;
+}
+
+/*
+ * Try to load a PEM file from a <path> or a buffer <buf>
+ * The PEM must contain at least a Certificate,
+ * It could contain a DH, a certificate chain and a PrivateKey.
+ *
+ * If it failed you should not attempt to use the ckch but free it.
+ *
+ * Return 0 on success or != 0 on failure
+ */
+int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err)
+{
+ BIO *in = NULL;
+ int ret = 1;
+ X509 *ca;
+ X509 *cert = NULL;
+ EVP_PKEY *key = NULL;
+ HASSL_DH *dh = NULL;
+ STACK_OF(X509) *chain = NULL;
+
+ if (buf) {
+ /* reading from a buffer */
+ in = BIO_new_mem_buf(buf, -1);
+ if (in == NULL) {
+ memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
+ goto end;
+ }
+
+ } else {
+ /* reading from a file */
+ in = BIO_new(BIO_s_file());
+ if (in == NULL) {
+ memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
+ goto end;
+ }
+
+ if (BIO_read_filename(in, path) <= 0) {
+ memprintf(err, "%scannot open the file '%s'.\n",
+ err && *err ? *err : "", path);
+ goto end;
+ }
+ }
+
+ /* Read Private Key */
+ key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
+ /* no need to check for errors here, because the private key could be loaded later */
+
+#ifndef OPENSSL_NO_DH
+ /* Seek back to beginning of file */
+ if (BIO_reset(in) == -1) {
+ memprintf(err, "%san error occurred while reading the file '%s'.\n",
+ err && *err ? *err : "", path);
+ goto end;
+ }
+
+ dh = ssl_sock_get_dh_from_bio(in);
+ ERR_clear_error();
+ /* no need to return an error there, dh is not mandatory */
+#endif
+
+ /* Seek back to beginning of file */
+ if (BIO_reset(in) == -1) {
+ memprintf(err, "%san error occurred while reading the file '%s'.\n",
+ err && *err ? *err : "", path);
+ goto end;
+ }
+
+ /* Read Certificate */
+ cert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
+ if (cert == NULL) {
+ memprintf(err, "%sunable to load certificate from file '%s'.\n",
+ err && *err ? *err : "", path);
+ goto end;
+ }
+
+ /* Look for a Certificate Chain */
+ while ((ca = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
+ if (chain == NULL)
+ chain = sk_X509_new_null();
+ if (!sk_X509_push(chain, ca)) {
+ X509_free(ca);
+ goto end;
+ }
+ }
+
+ ret = ERR_get_error();
+ if (ret && (ERR_GET_LIB(ret) != ERR_LIB_PEM && ERR_GET_REASON(ret) != PEM_R_NO_START_LINE)) {
+ memprintf(err, "%sunable to load certificate chain from file '%s'.\n",
+ err && *err ? *err : "", path);
+ goto end;
+ }
+
+ /* once it loaded the PEM, it should remove everything else in the ckch */
+ if (ckch->ocsp_response) {
+ ha_free(&ckch->ocsp_response->area);
+ ha_free(&ckch->ocsp_response);
+ }
+
+ if (ckch->sctl) {
+ ha_free(&ckch->sctl->area);
+ ha_free(&ckch->sctl);
+ }
+
+ if (ckch->ocsp_issuer) {
+ X509_free(ckch->ocsp_issuer);
+ ckch->ocsp_issuer = NULL;
+ }
+
+ /* no error, fill ckch with new context, old context will be free at end: */
+ SWAP(ckch->key, key);
+ SWAP(ckch->dh, dh);
+ SWAP(ckch->cert, cert);
+ SWAP(ckch->chain, chain);
+
+ ret = 0;
+
+end:
+
+ ERR_clear_error();
+ if (in)
+ BIO_free(in);
+ if (key)
+ EVP_PKEY_free(key);
+ if (dh)
+ HASSL_DH_free(dh);
+ if (cert)
+ X509_free(cert);
+ if (chain)
+ sk_X509_pop_free(chain, X509_free);
+
+ return ret;
+}
+
+/* Frees the contents of a cert_key_and_chain
+ */
+void ssl_sock_free_cert_key_and_chain_contents(struct cert_key_and_chain *ckch)
+{
+ if (!ckch)
+ return;
+
+ /* Free the certificate and set pointer to NULL */
+ if (ckch->cert)
+ X509_free(ckch->cert);
+ ckch->cert = NULL;
+
+ /* Free the key and set pointer to NULL */
+ if (ckch->key)
+ EVP_PKEY_free(ckch->key);
+ ckch->key = NULL;
+
+ /* Free each certificate in the chain */
+ if (ckch->chain)
+ sk_X509_pop_free(ckch->chain, X509_free);
+ ckch->chain = NULL;
+
+ if (ckch->dh)
+ HASSL_DH_free(ckch->dh);
+ ckch->dh = NULL;
+
+ if (ckch->sctl) {
+ ha_free(&ckch->sctl->area);
+ ha_free(&ckch->sctl);
+ }
+
+ if (ckch->ocsp_response) {
+ ha_free(&ckch->ocsp_response->area);
+ ha_free(&ckch->ocsp_response);
+ }
+
+ if (ckch->ocsp_issuer)
+ X509_free(ckch->ocsp_issuer);
+ ckch->ocsp_issuer = NULL;
+}
+
+/*
+ *
+ * This function copy a cert_key_and_chain in memory
+ *
+ * It's used to try to apply changes on a ckch before committing them, because
+ * most of the time it's not possible to revert those changes
+ *
+ * Return a the dst or NULL
+ */
+struct cert_key_and_chain *ssl_sock_copy_cert_key_and_chain(struct cert_key_and_chain *src,
+ struct cert_key_and_chain *dst)
+{
+ if (!src || !dst)
+ return NULL;
+
+ if (src->cert) {
+ dst->cert = src->cert;
+ X509_up_ref(src->cert);
+ }
+
+ if (src->key) {
+ dst->key = src->key;
+ EVP_PKEY_up_ref(src->key);
+ }
+
+ if (src->chain) {
+ dst->chain = X509_chain_up_ref(src->chain);
+ }
+
+ if (src->dh) {
+ HASSL_DH_up_ref(src->dh);
+ dst->dh = src->dh;
+ }
+
+ if (src->sctl) {
+ struct buffer *sctl;
+
+ sctl = calloc(1, sizeof(*sctl));
+ if (!chunk_dup(sctl, src->sctl)) {
+ ha_free(&sctl);
+ goto error;
+ }
+ dst->sctl = sctl;
+ }
+
+ if (src->ocsp_response) {
+ struct buffer *ocsp_response;
+
+ ocsp_response = calloc(1, sizeof(*ocsp_response));
+ if (!chunk_dup(ocsp_response, src->ocsp_response)) {
+ ha_free(&ocsp_response);
+ goto error;
+ }
+ dst->ocsp_response = ocsp_response;
+ }
+
+ if (src->ocsp_issuer) {
+ X509_up_ref(src->ocsp_issuer);
+ dst->ocsp_issuer = src->ocsp_issuer;
+ }
+
+ return dst;
+
+error:
+
+ /* free everything */
+ ssl_sock_free_cert_key_and_chain_contents(dst);
+
+ return NULL;
+}
+
+/*
+ * return 0 on success or != 0 on failure
+ */
+int ssl_sock_load_issuer_file_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch, char **err)
+{
+ int ret = 1;
+ BIO *in = NULL;
+ X509 *issuer;
+
+ if (buf) {
+ /* reading from a buffer */
+ in = BIO_new_mem_buf(buf, -1);
+ if (in == NULL) {
+ memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
+ goto end;
+ }
+
+ } else {
+ /* reading from a file */
+ in = BIO_new(BIO_s_file());
+ if (in == NULL)
+ goto end;
+
+ if (BIO_read_filename(in, path) <= 0)
+ goto end;
+ }
+
+ issuer = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
+ if (!issuer) {
+ memprintf(err, "%s'%s' cannot be read or parsed'.\n",
+ err && *err ? *err : "", path);
+ goto end;
+ }
+ /* no error, fill ckch with new context, old context must be free */
+ if (ckch->ocsp_issuer)
+ X509_free(ckch->ocsp_issuer);
+ ckch->ocsp_issuer = issuer;
+ ret = 0;
+
+end:
+
+ ERR_clear_error();
+ if (in)
+ BIO_free(in);
+
+ return ret;
+}
+
+/******************** ckch_store functions ***********************************
+ * The ckch_store is a structure used to cache and index the SSL files used in
+ * configuration
+ */
+
+/*
+ * Free a ckch_store, its ckch, its instances and remove it from the ebtree
+ */
+void ckch_store_free(struct ckch_store *store)
+{
+ struct ckch_inst *inst, *inst_s;
+
+ if (!store)
+ return;
+
+ ssl_sock_free_cert_key_and_chain_contents(store->ckch);
+
+ ha_free(&store->ckch);
+
+ list_for_each_entry_safe(inst, inst_s, &store->ckch_inst, by_ckchs) {
+ ckch_inst_free(inst);
+ }
+ ebmb_delete(&store->node);
+ free(store);
+}
+
+/*
+ * create and initialize a ckch_store
+ * <path> is the key name
+ * <nmemb> is the number of store->ckch objects to allocate
+ *
+ * Return a ckch_store or NULL upon failure.
+ */
+struct ckch_store *ckch_store_new(const char *filename)
+{
+ struct ckch_store *store;
+ int pathlen;
+
+ pathlen = strlen(filename);
+ store = calloc(1, sizeof(*store) + pathlen + 1);
+ if (!store)
+ return NULL;
+
+ memcpy(store->path, filename, pathlen + 1);
+
+ LIST_INIT(&store->ckch_inst);
+ LIST_INIT(&store->crtlist_entry);
+
+ store->ckch = calloc(1, sizeof(*store->ckch));
+ if (!store->ckch)
+ goto error;
+
+ return store;
+error:
+ ckch_store_free(store);
+ return NULL;
+}
+
+/* allocate and duplicate a ckch_store
+ * Return a new ckch_store or NULL */
+struct ckch_store *ckchs_dup(const struct ckch_store *src)
+{
+ struct ckch_store *dst;
+
+ if (!src)
+ return NULL;
+
+ dst = ckch_store_new(src->path);
+ if (!dst)
+ return NULL;
+
+ if (!ssl_sock_copy_cert_key_and_chain(src->ckch, dst->ckch))
+ goto error;
+
+ return dst;
+
+error:
+ ckch_store_free(dst);
+
+ return NULL;
+}
+
+/*
+ * lookup a path into the ckchs tree.
+ */
+struct ckch_store *ckchs_lookup(char *path)
+{
+ struct ebmb_node *eb;
+
+ eb = ebst_lookup(&ckchs_tree, path);
+ if (!eb)
+ return NULL;
+
+ return ebmb_entry(eb, struct ckch_store, node);
+}
+
+/*
+ * This function allocate a ckch_store and populate it with certificates from files.
+ */
+struct ckch_store *ckchs_load_cert_file(char *path, char **err)
+{
+ struct ckch_store *ckchs;
+
+ ckchs = ckch_store_new(path);
+ if (!ckchs) {
+ memprintf(err, "%sunable to allocate memory.\n", err && *err ? *err : "");
+ goto end;
+ }
+
+ if (ssl_sock_load_files_into_ckch(path, ckchs->ckch, err) == 1)
+ goto end;
+
+ /* insert into the ckchs tree */
+ memcpy(ckchs->path, path, strlen(path) + 1);
+ ebst_insert(&ckchs_tree, &ckchs->node);
+ return ckchs;
+
+end:
+ ckch_store_free(ckchs);
+
+ return NULL;
+}
+
+
+/******************** ckch_inst functions ******************************/
+
+/* unlink a ckch_inst, free all SNIs, free the ckch_inst */
+/* The caller must use the lock of the bind_conf if used with inserted SNIs */
+void ckch_inst_free(struct ckch_inst *inst)
+{
+ struct sni_ctx *sni, *sni_s;
+ struct ckch_inst_link_ref *link_ref, *link_ref_s;
+
+ if (inst == NULL)
+ return;
+
+ list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
+ SSL_CTX_free(sni->ctx);
+ LIST_DELETE(&sni->by_ckch_inst);
+ ebmb_delete(&sni->name);
+ free(sni);
+ }
+ SSL_CTX_free(inst->ctx);
+ inst->ctx = NULL;
+ LIST_DELETE(&inst->by_ckchs);
+ LIST_DELETE(&inst->by_crtlist_entry);
+
+ /* Free the cafile_link_refs list */
+ list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
+ if (link_ref->link && LIST_INLIST(&link_ref->link->list)) {
+ /* Try to detach and free the ckch_inst_link only if it
+ * was attached, this way it can be used to loop from
+ * the caller */
+ LIST_DEL_INIT(&link_ref->link->list);
+ ha_free(&link_ref->link);
+ }
+ LIST_DELETE(&link_ref->list);
+ free(link_ref);
+ }
+
+ free(inst);
+}
+
+/* Alloc and init a ckch_inst */
+struct ckch_inst *ckch_inst_new()
+{
+ struct ckch_inst *ckch_inst;
+
+ ckch_inst = calloc(1, sizeof *ckch_inst);
+ if (!ckch_inst)
+ return NULL;
+
+ LIST_INIT(&ckch_inst->sni_ctx);
+ LIST_INIT(&ckch_inst->by_ckchs);
+ LIST_INIT(&ckch_inst->by_crtlist_entry);
+ LIST_INIT(&ckch_inst->cafile_link_refs);
+
+ return ckch_inst;
+}
+
+
+/******************** ssl_store functions ******************************/
+struct eb_root cafile_tree = EB_ROOT;
+
+/*
+ * Returns the cafile_entry found in the cafile_tree indexed by the path 'path'.
+ * If 'oldest_entry' is 1, returns the "original" cafile_entry (since
+ * during a set cafile/commit cafile cycle there might be two entries for any
+ * given path, the original one and the new one set via the CLI but not
+ * committed yet).
+ */
+struct cafile_entry *ssl_store_get_cafile_entry(char *path, int oldest_entry)
+{
+ struct cafile_entry *ca_e = NULL;
+ struct ebmb_node *eb;
+
+ eb = ebst_lookup(&cafile_tree, path);
+ while (eb) {
+ ca_e = ebmb_entry(eb, struct cafile_entry, node);
+ /* The ebst_lookup in a tree that has duplicates returns the
+ * oldest entry first. If we want the latest entry, we need to
+ * iterate over all the duplicates until we find the last one
+ * (in our case there should never be more than two entries for
+ * any given path). */
+ if (oldest_entry)
+ return ca_e;
+ eb = ebmb_next_dup(eb);
+ }
+ return ca_e;
+}
+
+int ssl_store_add_uncommitted_cafile_entry(struct cafile_entry *entry)
+{
+ return (ebst_insert(&cafile_tree, &entry->node) != &entry->node);
+}
+
+X509_STORE* ssl_store_get0_locations_file(char *path)
+{
+ struct cafile_entry *ca_e = ssl_store_get_cafile_entry(path, 0);
+
+ if (ca_e)
+ return ca_e->ca_store;
+
+ return NULL;
+}
+
+/* Create a cafile_entry object, without adding it to the cafile_tree. */
+struct cafile_entry *ssl_store_create_cafile_entry(char *path, X509_STORE *store, enum cafile_type type)
+{
+ struct cafile_entry *ca_e;
+ int pathlen;
+
+ pathlen = strlen(path);
+
+ ca_e = calloc(1, sizeof(*ca_e) + pathlen + 1);
+ if (ca_e) {
+ memcpy(ca_e->path, path, pathlen + 1);
+ ca_e->ca_store = store;
+ ca_e->type = type;
+ LIST_INIT(&ca_e->ckch_inst_link);
+ }
+ return ca_e;
+}
+
+/* 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)
+{
+ struct ckch_inst_link *link, *link_s;
+ if (!ca_e)
+ return;
+
+ X509_STORE_free(ca_e->ca_store);
+
+ list_for_each_entry_safe(link, link_s, &ca_e->ckch_inst_link, list) {
+ struct ckch_inst *inst = link->ckch_inst;
+ struct ckch_inst_link_ref *link_ref, *link_ref_s;
+ list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
+ if (link_ref->link == link) {
+ LIST_DELETE(&link_ref->list);
+ free(link_ref);
+ break;
+ }
+ }
+ LIST_DELETE(&link->list);
+ free(link);
+ }
+
+ free(ca_e);
+}
+
+/*
+ * Build a cafile_entry out of a buffer instead of out of a file.
+ * This function is used when the "commit ssl ca-file" cli command is used.
+ * It can parse CERTIFICATE sections as well as CRL ones.
+ * Returns 0 in case of success, 1 otherwise.
+ */
+int ssl_store_load_ca_from_buf(struct cafile_entry *ca_e, char *cert_buf)
+{
+ int retval = 0;
+
+ if (!ca_e)
+ return 1;
+
+ if (!ca_e->ca_store) {
+ ca_e->ca_store = X509_STORE_new();
+ if (ca_e->ca_store) {
+ BIO *bio = BIO_new_mem_buf(cert_buf, strlen(cert_buf));
+ if (bio) {
+ X509_INFO *info;
+ int i;
+ STACK_OF(X509_INFO) *infos = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
+ if (!infos)
+ {
+ BIO_free(bio);
+ return 1;
+ }
+
+ for (i = 0; i < sk_X509_INFO_num(infos) && !retval; i++) {
+ info = sk_X509_INFO_value(infos, i);
+ /* X509_STORE_add_cert and X509_STORE_add_crl return 1 on success */
+ if (info->x509) {
+ retval = !X509_STORE_add_cert(ca_e->ca_store, info->x509);
+ }
+ if (!retval && info->crl) {
+ retval = !X509_STORE_add_crl(ca_e->ca_store, info->crl);
+ }
+ }
+ /* return an error if we didn't compute all the X509_INFO or if there was none */
+ retval = retval || (i != sk_X509_INFO_num(infos)) || ( sk_X509_INFO_num(infos) == 0);
+
+ /* Cleanup */
+ sk_X509_INFO_pop_free(infos, X509_INFO_free);
+ BIO_free(bio);
+ }
+ }
+ }
+
+ return retval;
+}
+
+/*
+ * Try to load a ca-file from disk into the ca-file cache.
+ * <shuterror> allows you to to stop emitting the errors.
+ * Return 0 upon error
+ */
+int __ssl_store_load_locations_file(char *path, int create_if_none, enum cafile_type type, int shuterror)
+{
+ X509_STORE *store = ssl_store_get0_locations_file(path);
+
+ /* If this function is called by the CLI, we should not call the
+ * 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;
+ const char *file = NULL;
+ const char *dir = NULL;
+ unsigned long e;
+
+ store = X509_STORE_new();
+ if (!store) {
+ if (!shuterror)
+ ha_alert("Cannot allocate memory!\n");
+ goto err;
+ }
+
+ if (strcmp(path, "@system-ca") == 0) {
+ dir = X509_get_default_cert_dir();
+ if (!dir) {
+ if (!shuterror)
+ ha_alert("Couldn't get the system CA directory from X509_get_default_cert_dir().\n");
+ goto err;
+ }
+
+ } else {
+
+ if (stat(path, &buf) == -1) {
+ if (!shuterror)
+ ha_alert("Couldn't open the ca-file '%s' (%s).\n", path, strerror(errno));
+ goto err;
+ }
+
+ if (S_ISDIR(buf.st_mode))
+ dir = path;
+ else
+ file = path;
+ }
+
+ if (file) {
+ if (!X509_STORE_load_locations(store, file, NULL)) {
+ e = ERR_get_error();
+ if (!shuterror)
+ ha_alert("Couldn't open the ca-file '%s' (%s).\n", path, ERR_reason_error_string(e));
+ goto err;
+ }
+ } else if (dir) {
+ 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;;
+
+ ERR_clear_error();
+
+ /* 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". Files starting with a dot
+ * are ignored.
+ */
+ end = strrchr(de->d_name, '.');
+ if (!end || de->d_name[0] == '.' ||
+ (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", dir, 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) {
+ /* 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 scandir_err;
+ }
+ }
+
+ X509_free(ca);
+ BIO_free(in);
+ free(de);
+ continue;
+
+scandir_err:
+ e = ERR_get_error();
+ X509_free(ca);
+ BIO_free(in);
+ free(de);
+ /* warn if it can load one of the files, but don't abort */
+ if (!shuterror)
+ ha_warning("ca-file: '%s' couldn't load '%s' (%s)\n", path, trash.area, ERR_reason_error_string(e));
+
+ }
+ free(de_list);
+ } else {
+ if (!shuterror)
+ ha_alert("ca-file: couldn't load '%s'\n", path);
+ goto err;
+ }
+
+ objs = X509_STORE_get0_objects(store);
+ cert_count = sk_X509_OBJECT_num(objs);
+ if (cert_count == 0) {
+ if (!shuterror)
+ 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) {
+ if (!shuterror)
+ ha_alert("Cannot allocate memory!\n");
+ goto err;
+ }
+ ebst_insert(&cafile_tree, &ca_e->node);
+ }
+ return (store != NULL);
+
+err:
+ X509_STORE_free(store);
+ store = NULL;
+ return 0;
+
+}
+
+int ssl_store_load_locations_file(char *path, int create_if_none, enum cafile_type type)
+{
+ return __ssl_store_load_locations_file(path, create_if_none, type, 0);
+}
+
+/*************************** CLI commands ***********************/
+
+/* Type of SSL payloads that can be updated over the CLI */
+
+struct cert_exts cert_exts[] = {
+ { "", CERT_TYPE_PEM, &ssl_sock_load_pem_into_ckch }, /* default mode, no extensions */
+ { "crt", CERT_TYPE_CRT, &ssl_sock_load_pem_into_ckch },
+ { "key", CERT_TYPE_KEY, &ssl_sock_load_key_into_ckch },
+#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
+ { "ocsp", CERT_TYPE_OCSP, &ssl_sock_load_ocsp_response_from_file },
+#endif
+#ifdef HAVE_SSL_SCTL
+ { "sctl", CERT_TYPE_SCTL, &ssl_sock_load_sctl_from_file },
+#endif
+ { "issuer", CERT_TYPE_ISSUER, &ssl_sock_load_issuer_file_into_ckch },
+ { NULL, CERT_TYPE_MAX, NULL },
+};
+
+
+/* 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>".
+ * It makes use of a show_cert_ctx context, and ckchs_transaction in read-only.
+ */
+static int cli_io_handler_show_cert(struct appctx *appctx)
+{
+ struct show_cert_ctx *ctx = appctx->svcctx;
+ struct buffer *trash = alloc_trash_chunk();
+ struct ebmb_node *node;
+ struct ckch_store *ckchs = NULL;
+
+ if (trash == NULL)
+ return 1;
+
+ if (!ctx->old_ckchs && ckchs_transaction.old_ckchs) {
+ ckchs = ckchs_transaction.old_ckchs;
+ chunk_appendf(trash, "# transaction\n");
+ chunk_appendf(trash, "*%s\n", ckchs->path);
+ if (applet_putchk(appctx, trash) == -1)
+ goto yield;
+ ctx->old_ckchs = ckchs_transaction.old_ckchs;
+ }
+
+ if (!ctx->cur_ckchs) {
+ chunk_appendf(trash, "# filename\n");
+ node = ebmb_first(&ckchs_tree);
+ } else {
+ node = &ctx->cur_ckchs->node;
+ }
+ while (node) {
+ ckchs = ebmb_entry(node, struct ckch_store, node);
+ chunk_appendf(trash, "%s\n", ckchs->path);
+
+ node = ebmb_next(node);
+ if (applet_putchk(appctx, trash) == -1)
+ goto yield;
+ }
+
+ ctx->cur_ckchs = NULL;
+ free_trash_chunk(trash);
+ return 1;
+yield:
+
+ free_trash_chunk(trash);
+ ctx->cur_ckchs = ckchs;
+ return 0; /* should come back */
+}
+
+/*
+ * 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
+
+/*
+ * Build the ckch_inst_link that will be chained in the CA file entry and the
+ * corresponding ckch_inst_link_ref that will be chained in the ckch instance.
+ * Return 0 in case of success.
+ */
+static int do_chain_inst_and_cafile(struct cafile_entry *cafile_entry, struct ckch_inst *ckch_inst)
+{
+ struct ckch_inst_link *new_link;
+ if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
+ struct ckch_inst_link *link = LIST_ELEM(cafile_entry->ckch_inst_link.n,
+ typeof(link), list);
+ /* Do not add multiple references to the same
+ * instance in a cafile_entry */
+ if (link->ckch_inst == ckch_inst) {
+ return 1;
+ }
+ }
+
+ new_link = calloc(1, sizeof(*new_link));
+ if (new_link) {
+ struct ckch_inst_link_ref *new_link_ref = calloc(1, sizeof(*new_link_ref));
+ if (!new_link_ref) {
+ free(new_link);
+ return 1;
+ }
+
+ new_link->ckch_inst = ckch_inst;
+ new_link_ref->link = new_link;
+ LIST_INIT(&new_link->list);
+ LIST_INIT(&new_link_ref->list);
+
+ LIST_APPEND(&cafile_entry->ckch_inst_link, &new_link->list);
+ LIST_APPEND(&ckch_inst->cafile_link_refs, &new_link_ref->list);
+ }
+
+ return 0;
+}
+
+
+/*
+ * Link a CA file tree entry to the ckch instance that uses it.
+ * To determine if and which CA file tree entries need to be linked to the
+ * instance, we follow the same logic performed in ssl_sock_prepare_ctx when
+ * processing the verify option.
+ * This function works for a frontend as well as for a backend, depending on the
+ * configuration parameters given (bind_conf or server).
+ */
+void ckch_inst_add_cafile_link(struct ckch_inst *ckch_inst, struct bind_conf *bind_conf,
+ struct ssl_bind_conf *ssl_conf, const struct server *srv)
+{
+ int verify = SSL_VERIFY_NONE;
+
+ if (srv) {
+
+ if (global.ssl_server_verify == SSL_SERVER_VERIFY_REQUIRED)
+ verify = SSL_VERIFY_PEER;
+ switch (srv->ssl_ctx.verify) {
+ case SSL_SOCK_VERIFY_NONE:
+ verify = SSL_VERIFY_NONE;
+ break;
+ case SSL_SOCK_VERIFY_REQUIRED:
+ verify = SSL_VERIFY_PEER;
+ break;
+ }
+ }
+ else {
+ switch ((ssl_conf && ssl_conf->verify) ? ssl_conf->verify : bind_conf->ssl_conf.verify) {
+ case SSL_SOCK_VERIFY_NONE:
+ verify = SSL_VERIFY_NONE;
+ break;
+ case SSL_SOCK_VERIFY_OPTIONAL:
+ verify = SSL_VERIFY_PEER;
+ break;
+ case SSL_SOCK_VERIFY_REQUIRED:
+ verify = SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+ break;
+ }
+ }
+
+ if (verify & SSL_VERIFY_PEER) {
+ struct cafile_entry *ca_file_entry = NULL;
+ struct cafile_entry *ca_verify_file_entry = NULL;
+ struct cafile_entry *crl_file_entry = NULL;
+ if (srv) {
+ if (srv->ssl_ctx.ca_file) {
+ ca_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.ca_file, 0);
+
+ }
+ if (srv->ssl_ctx.crl_file) {
+ crl_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.crl_file, 0);
+ }
+ }
+ else {
+ char *ca_file = (ssl_conf && ssl_conf->ca_file) ? ssl_conf->ca_file : bind_conf->ssl_conf.ca_file;
+ char *ca_verify_file = (ssl_conf && ssl_conf->ca_verify_file) ? ssl_conf->ca_verify_file : bind_conf->ssl_conf.ca_verify_file;
+ char *crl_file = (ssl_conf && ssl_conf->crl_file) ? ssl_conf->crl_file : bind_conf->ssl_conf.crl_file;
+
+ if (ca_file)
+ ca_file_entry = ssl_store_get_cafile_entry(ca_file, 0);
+ if (ca_verify_file)
+ ca_verify_file_entry = ssl_store_get_cafile_entry(ca_verify_file, 0);
+ if (crl_file)
+ crl_file_entry = ssl_store_get_cafile_entry(crl_file, 0);
+ }
+
+ if (ca_file_entry) {
+ /* If we have a ckch instance that is not already in the
+ * cafile_entry's list, add it to it. */
+ if (do_chain_inst_and_cafile(ca_file_entry, ckch_inst))
+ return;
+
+ }
+ if (ca_verify_file_entry && (ca_file_entry != ca_verify_file_entry)) {
+ /* If we have a ckch instance that is not already in the
+ * cafile_entry's list, add it to it. */
+ if (do_chain_inst_and_cafile(ca_verify_file_entry, ckch_inst))
+ return;
+ }
+ if (crl_file_entry) {
+ /* If we have a ckch instance that is not already in the
+ * cafile_entry's list, add it to it. */
+ if (do_chain_inst_and_cafile(crl_file_entry, ckch_inst))
+ return;
+ }
+ }
+}
+
+
+
+static int show_cert_detail(X509 *cert, STACK_OF(X509) *chain, struct buffer *out)
+{
+ BIO *bio = NULL;
+ struct buffer *tmp = alloc_trash_chunk();
+ int i;
+ int write = -1;
+ unsigned int len = 0;
+ X509_NAME *name = NULL;
+
+ if (!tmp)
+ return -1;
+
+ if (!cert)
+ goto end;
+
+ if (chain == NULL) {
+ struct issuer_chain *issuer;
+ issuer = ssl_get0_issuer_chain(cert);
+ if (issuer) {
+ chain = issuer->chain;
+ chunk_appendf(out, "Chain Filename: ");
+ chunk_appendf(out, "%s\n", issuer->path);
+ }
+ }
+ chunk_appendf(out, "Serial: ");
+ if (ssl_sock_get_serial(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(cert)) == 0)
+ goto end;
+ write = BIO_read(bio, tmp->area, tmp->size-1);
+ tmp->area[write] = '\0';
+ BIO_free(bio);
+ bio = NULL;
+ 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(cert)) == 0)
+ goto end;
+ if ((write = BIO_read(bio, tmp->area, tmp->size-1)) <= 0)
+ goto end;
+ tmp->area[write] = '\0';
+ BIO_free(bio);
+ bio = NULL;
+ 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(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(cert, tmp) == 0)
+ goto end;
+ chunk_appendf(out, "%s\n", tmp->area);
+
+ chunk_reset(tmp);
+ chunk_appendf(out, "SHA1 FingerPrint: ");
+ if (X509_digest(cert, EVP_sha1(), (unsigned char *) tmp->area, &len) == 0)
+ goto end;
+ tmp->data = len;
+ dump_binary(out, tmp->area, tmp->data);
+ chunk_appendf(out, "\n");
+
+ chunk_appendf(out, "Subject: ");
+ if ((name = X509_get_subject_name(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, "Issuer: ");
+ if ((name = X509_get_issuer_name(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);
+
+ /* Displays subject of each certificate in the chain */
+ for (i = 0; i < sk_X509_num(chain); i++) {
+ X509 *ca = sk_X509_value(chain, i);
+
+ chunk_appendf(out, "Chain Subject: ");
+ if ((name = X509_get_subject_name(ca)) == 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, "Chain Issuer: ");
+ if ((name = X509_get_issuer_name(ca)) == 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);
+ }
+
+end:
+ if (bio)
+ BIO_free(bio);
+ free_trash_chunk(tmp);
+
+ return 0;
+}
+
+#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
+/*
+ * Build the OCSP tree entry's key for a given ckch_store.
+ * Returns a negative value in case of error.
+ */
+static int ckch_store_build_certid(struct ckch_store *ckch_store, unsigned char certid[128], unsigned int *key_length)
+{
+ OCSP_RESPONSE *resp;
+ OCSP_BASICRESP *bs = NULL;
+ OCSP_SINGLERESP *sr;
+ OCSP_CERTID *id;
+ unsigned char *p = NULL;
+
+ if (!key_length)
+ return -1;
+
+ *key_length = 0;
+
+ if (!ckch_store->ckch->ocsp_response)
+ return 0;
+
+ p = (unsigned char *) ckch_store->ckch->ocsp_response->area;
+
+ resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p,
+ ckch_store->ckch->ocsp_response->data);
+ if (!resp) {
+ goto end;
+ }
+
+ bs = OCSP_response_get1_basic(resp);
+ if (!bs) {
+ goto end;
+ }
+
+ sr = OCSP_resp_get0(bs, 0);
+ if (!sr) {
+ goto end;
+ }
+
+ id = (OCSP_CERTID*)OCSP_SINGLERESP_get0_id(sr);
+
+ p = certid;
+ *key_length = i2d_OCSP_CERTID(id, &p);
+
+end:
+ return *key_length > 0;
+}
+#endif
+
+/*
+ * Dump the OCSP certificate key (if it exists) of certificate <ckch> into
+ * buffer <out>.
+ * Returns 0 in case of success.
+ */
+static int ckch_store_show_ocsp_certid(struct ckch_store *ckch_store, struct buffer *out)
+{
+#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
+ unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
+ unsigned int key_length = 0;
+ int i;
+
+ if (ckch_store_build_certid(ckch_store, (unsigned char*)key, &key_length) >= 0) {
+ /* Dump the CERTID info */
+ chunk_appendf(out, "OCSP Response Key: ");
+ for (i = 0; i < key_length; ++i) {
+ chunk_appendf(out, "%02x", key[i]);
+ }
+ chunk_appendf(out, "\n");
+ }
+#endif
+
+ return 0;
+}
+
+
+/* IO handler of the details "show ssl cert <filename>".
+ * It uses a struct show_cert_ctx and ckchs_transaction in read-only.
+ */
+static int cli_io_handler_show_cert_detail(struct appctx *appctx)
+{
+ struct show_cert_ctx *ctx = appctx->svcctx;
+ struct ckch_store *ckchs = ctx->cur_ckchs;
+ struct buffer *out = alloc_trash_chunk();
+ int retval = 0;
+
+ if (!out)
+ goto end_no_putchk;
+
+ chunk_appendf(out, "Filename: ");
+ if (ckchs == ckchs_transaction.new_ckchs)
+ chunk_appendf(out, "*");
+ chunk_appendf(out, "%s\n", ckchs->path);
+
+ chunk_appendf(out, "Status: ");
+ if (ckchs->ckch->cert == NULL)
+ chunk_appendf(out, "Empty\n");
+ else if (LIST_ISEMPTY(&ckchs->ckch_inst))
+ chunk_appendf(out, "Unused\n");
+ else
+ chunk_appendf(out, "Used\n");
+
+ retval = show_cert_detail(ckchs->ckch->cert, ckchs->ckch->chain, out);
+ if (retval < 0)
+ goto end_no_putchk;
+ else if (retval)
+ goto end;
+
+ ckch_store_show_ocsp_certid(ckchs, out);
+
+end:
+ if (applet_putchk(appctx, out) == -1)
+ goto yield;
+
+end_no_putchk:
+ free_trash_chunk(out);
+ return 1;
+yield:
+ free_trash_chunk(out);
+ return 0; /* should come back */
+}
+
+
+/* IO handler of the details "show ssl cert <filename.ocsp>".
+ * It uses a show_cert_ctx.
+ */
+static int cli_io_handler_show_cert_ocsp_detail(struct appctx *appctx)
+{
+#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
+ struct show_cert_ctx *ctx = appctx->svcctx;
+ struct ckch_store *ckchs = ctx->cur_ckchs;
+ struct buffer *out = alloc_trash_chunk();
+ int from_transaction = ctx->transaction;
+
+ if (!out)
+ goto end_no_putchk;
+
+ /* If we try to display an ongoing transaction's OCSP response, we
+ * need to dump the ckch's ocsp_response buffer directly.
+ * Otherwise, we must rebuild the certificate's certid in order to
+ * look for the current OCSP response in the tree. */
+ if (from_transaction && ckchs->ckch->ocsp_response) {
+ if (ssl_ocsp_response_print(ckchs->ckch->ocsp_response, out))
+ goto end_no_putchk;
+ }
+ else {
+ unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
+ unsigned int key_length = 0;
+
+ if (ckch_store_build_certid(ckchs, (unsigned char*)key, &key_length) < 0)
+ goto end_no_putchk;
+
+ if (ssl_get_ocspresponse_detail(key, out))
+ goto end_no_putchk;
+ }
+
+ if (applet_putchk(appctx, out) == -1)
+ goto yield;
+
+end_no_putchk:
+ free_trash_chunk(out);
+ return 1;
+yield:
+ free_trash_chunk(out);
+ return 0; /* should come back */
+#else
+ return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
+#endif
+}
+
+/* parsing function for 'show ssl cert [certfile]' */
+static int cli_parse_show_cert(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ struct show_cert_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
+ 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]) {
+ int show_ocsp_detail = 0;
+ int from_transaction = 0;
+ char *end;
+
+ /* We manage the special case "certname.ocsp" through which we
+ * can show the details of an OCSP response. */
+ end = strrchr(args[3], '.');
+ if (end && strcmp(end+1, "ocsp") == 0) {
+ *end = '\0';
+ show_ocsp_detail = 1;
+ }
+
+ if (*args[3] == '*') {
+ from_transaction = 1;
+ if (!ckchs_transaction.new_ckchs)
+ goto error;
+
+ ckchs = ckchs_transaction.new_ckchs;
+
+ if (strcmp(args[3] + 1, ckchs->path) != 0)
+ goto error;
+
+ } else {
+ if ((ckchs = ckchs_lookup(args[3])) == NULL)
+ goto error;
+
+ }
+
+ ctx->cur_ckchs = ckchs;
+ /* use the IO handler that shows details */
+ if (show_ocsp_detail) {
+ ctx->transaction = from_transaction;
+ appctx->io_handler = cli_io_handler_show_cert_ocsp_detail;
+ }
+ else
+ 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)
+{
+ struct commit_cert_ctx *ctx = appctx->svcctx;
+
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ /* free every new sni_ctx and the new store, which are not in the trees so no spinlock there */
+ if (ctx->new_ckchs)
+ ckch_store_free(ctx->new_ckchs);
+ ha_free(&ctx->err);
+}
+
+
+/*
+ * Rebuild a new instance 'new_inst' based on an old instance 'ckchi' and a
+ * specific ckch_store.
+ * Returns 0 in case of success, 1 otherwise.
+ */
+int ckch_inst_rebuild(struct ckch_store *ckch_store, struct ckch_inst *ckchi,
+ struct ckch_inst **new_inst, char **err)
+{
+ int retval = 0;
+ int errcode = 0;
+ struct sni_ctx *sc0, *sc0s;
+ char **sni_filter = NULL;
+ int fcount = 0;
+
+ if (ckchi->crtlist_entry) {
+ sni_filter = ckchi->crtlist_entry->filters;
+ fcount = ckchi->crtlist_entry->fcount;
+ }
+
+ if (ckchi->is_server_instance)
+ errcode |= ckch_inst_new_load_srv_store(ckch_store->path, ckch_store, new_inst, err);
+ else
+ errcode |= ckch_inst_new_load_store(ckch_store->path, ckch_store, ckchi->bind_conf, ckchi->ssl_conf, sni_filter, fcount, new_inst, err);
+
+ if (errcode & ERR_CODE)
+ return 1;
+
+ /* if the previous ckchi was used as the default */
+ if (ckchi->is_default)
+ (*new_inst)->is_default = 1;
+
+ (*new_inst)->is_server_instance = ckchi->is_server_instance;
+ (*new_inst)->server = ckchi->server;
+ /* Create a new SSL_CTX and link it to the new instance. */
+ if ((*new_inst)->is_server_instance) {
+ retval = ssl_sock_prep_srv_ctx_and_inst(ckchi->server, (*new_inst)->ctx, (*new_inst));
+ if (retval)
+ return 1;
+ }
+
+ /* create the link to the crtlist_entry */
+ (*new_inst)->crtlist_entry = ckchi->crtlist_entry;
+
+ /* we need to initialize the SSL_CTX generated */
+ /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
+ list_for_each_entry_safe(sc0, sc0s, &(*new_inst)->sni_ctx, by_ckch_inst) {
+ if (!sc0->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
+ errcode |= ssl_sock_prep_ctx_and_inst(ckchi->bind_conf, ckchi->ssl_conf, sc0->ctx, *new_inst, err);
+ if (errcode & ERR_CODE)
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Load all the new SNIs of a newly built ckch instance in the trees, or replace
+ * a server's main ckch instance.
+ */
+static void __ssl_sock_load_new_ckch_instance(struct ckch_inst *ckchi)
+{
+ /* The bind_conf will be null on server ckch_instances. */
+ if (ckchi->is_server_instance) {
+ int i;
+ /* a lock is needed here since we have to free the SSL cache */
+ HA_RWLOCK_WRLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
+ /* free the server current SSL_CTX */
+ SSL_CTX_free(ckchi->server->ssl_ctx.ctx);
+ /* Actual ssl context update */
+ SSL_CTX_up_ref(ckchi->ctx);
+ ckchi->server->ssl_ctx.ctx = ckchi->ctx;
+ ckchi->server->ssl_ctx.inst = ckchi;
+
+ /* flush the session cache of the server */
+ for (i = 0; i < global.nbthread; i++) {
+ ha_free(&ckchi->server->ssl_ctx.reused_sess[i].sni);
+ ha_free(&ckchi->server->ssl_ctx.reused_sess[i].ptr);
+ }
+ HA_RWLOCK_WRUNLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
+
+ } else {
+ HA_RWLOCK_WRLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
+ ssl_sock_load_cert_sni(ckchi, ckchi->bind_conf);
+ HA_RWLOCK_WRUNLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
+ }
+}
+
+/*
+ * Delete a ckch instance that was replaced after a CLI command.
+ */
+static void __ckch_inst_free_locked(struct ckch_inst *ckchi)
+{
+ if (ckchi->is_server_instance) {
+ /* no lock for servers */
+ ckch_inst_free(ckchi);
+ } else {
+ struct bind_conf __maybe_unused *bind_conf = ckchi->bind_conf;
+
+ HA_RWLOCK_WRLOCK(SNI_LOCK, &bind_conf->sni_lock);
+ ckch_inst_free(ckchi);
+ HA_RWLOCK_WRUNLOCK(SNI_LOCK, &bind_conf->sni_lock);
+ }
+}
+
+/* Replace a ckch_store in the ckch tree and insert the whole dependencies,
+* then free the previous dependencies and store.
+* Used in the case of a certificate update.
+*
+* Every dependencies must allocated before using this function.
+*
+* This function can't fail as it only update pointers, and does not alloc anything.
+*
+* /!\ This function must be used under the ckch lock. /!\
+*
+* - Insert every dependencies (SNI, crtlist_entry, ckch_inst, etc)
+* - Delete the old ckch_store from the tree
+* - Insert the new ckch_store
+* - Free the old dependencies and the old ckch_store
+*/
+void ckch_store_replace(struct ckch_store *old_ckchs, struct ckch_store *new_ckchs)
+{
+ struct crtlist_entry *entry;
+ struct ckch_inst *ckchi, *ckchis;
+
+ LIST_SPLICE(&new_ckchs->crtlist_entry, &old_ckchs->crtlist_entry);
+ list_for_each_entry(entry, &new_ckchs->crtlist_entry, by_ckch_store) {
+ ebpt_delete(&entry->node);
+ /* change the ptr and reinsert the node */
+ entry->node.key = new_ckchs;
+ ebpt_insert(&entry->crtlist->entries, &entry->node);
+ }
+ /* insert the new ckch_insts in the crtlist_entry */
+ list_for_each_entry(ckchi, &new_ckchs->ckch_inst, by_ckchs) {
+ if (ckchi->crtlist_entry)
+ LIST_INSERT(&ckchi->crtlist_entry->ckch_inst, &ckchi->by_crtlist_entry);
+ }
+ /* First, we insert every new SNIs in the trees, also replace the default_ctx */
+ list_for_each_entry_safe(ckchi, ckchis, &new_ckchs->ckch_inst, by_ckchs) {
+ __ssl_sock_load_new_ckch_instance(ckchi);
+ }
+ /* delete the old sni_ctx, the old ckch_insts and the ckch_store */
+ list_for_each_entry_safe(ckchi, ckchis, &old_ckchs->ckch_inst, by_ckchs) {
+ __ckch_inst_free_locked(ckchi);
+ }
+
+ ckch_store_free(old_ckchs);
+ ebst_insert(&ckchs_tree, &new_ckchs->node);
+}
+
+
+/*
+ * This function tries to create the new ckch_inst and their SNIs
+ *
+ * /!\ don't forget to update __hlua_ckch_commit() if you changes things there. /!\
+ */
+static int cli_io_handler_commit_cert(struct appctx *appctx)
+{
+ struct commit_cert_ctx *ctx = appctx->svcctx;
+ struct stconn *sc = appctx_sc(appctx);
+ int y = 0;
+ struct ckch_store *old_ckchs, *new_ckchs = NULL;
+ struct ckch_inst *ckchi;
+
+ if (unlikely(sc_ic(sc)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
+ goto end;
+
+ while (1) {
+ switch (ctx->state) {
+ case CERT_ST_INIT:
+ /* This state just print the update message */
+ chunk_printf(&trash, "Committing %s", ckchs_transaction.path);
+ if (applet_putchk(appctx, &trash) == -1)
+ goto yield;
+
+ ctx->state = CERT_ST_GEN;
+ /* fallthrough */
+ case CERT_ST_GEN:
+ /*
+ * This state generates the ckch instances with their
+ * sni_ctxs and SSL_CTX.
+ *
+ * Since the SSL_CTX generation can be CPU consumer, we
+ * yield every 10 instances.
+ */
+
+ old_ckchs = ctx->old_ckchs;
+ new_ckchs = ctx->new_ckchs;
+
+ /* get the next ckchi to regenerate */
+ ckchi = ctx->next_ckchi;
+ /* we didn't start yet, set it to the first elem */
+ if (ckchi == NULL)
+ ckchi = LIST_ELEM(old_ckchs->ckch_inst.n, typeof(ckchi), by_ckchs);
+
+ /* walk through the old ckch_inst and creates new ckch_inst using the updated ckchs */
+ list_for_each_entry_from(ckchi, &old_ckchs->ckch_inst, by_ckchs) {
+ struct ckch_inst *new_inst;
+
+ /* save the next ckchi to compute in case of yield */
+ ctx->next_ckchi = ckchi;
+
+ /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
+ if (y >= 10) {
+ applet_have_more_data(appctx); /* let's come back later */
+ goto yield;
+ }
+
+ /* display one dot per new instance */
+ if (applet_putstr(appctx, ".") == -1)
+ goto yield;
+
+ ctx->err = NULL;
+ if (ckch_inst_rebuild(new_ckchs, ckchi, &new_inst, &ctx->err)) {
+ ctx->state = CERT_ST_ERROR;
+ goto error;
+ }
+
+ /* link the new ckch_inst to the duplicate */
+ LIST_APPEND(&new_ckchs->ckch_inst, &new_inst->by_ckchs);
+ y++;
+ }
+ ctx->state = CERT_ST_INSERT;
+ /* fallthrough */
+ case CERT_ST_INSERT:
+ /* The generation is finished, we can insert everything */
+
+ old_ckchs = ctx->old_ckchs;
+ new_ckchs = ctx->new_ckchs;
+
+ /* insert everything and remove the previous objects */
+ ckch_store_replace(old_ckchs, new_ckchs);
+ ctx->new_ckchs = ctx->old_ckchs = NULL;
+ ctx->state = CERT_ST_SUCCESS;
+ /* fallthrough */
+ case CERT_ST_SUCCESS:
+ if (applet_putstr(appctx, "\nSuccess!\n") == -1)
+ goto yield;
+ ctx->state = CERT_ST_FIN;
+ /* fallthrough */
+ case CERT_ST_FIN:
+ /* we achieved the transaction, we can set everything to NULL */
+ ckchs_transaction.new_ckchs = NULL;
+ ckchs_transaction.old_ckchs = NULL;
+ ckchs_transaction.path = NULL;
+ goto end;
+
+ case CERT_ST_ERROR:
+ error:
+ chunk_printf(&trash, "\n%sFailed!\n", ctx->err);
+ if (applet_putchk(appctx, &trash) == -1)
+ goto yield;
+ ctx->state = CERT_ST_FIN;
+ break;
+ }
+ }
+end:
+ /* success: call the release function and don't come back */
+ return 1;
+
+yield:
+ return 0; /* should come back */
+}
+
+/*
+ * Parsing function of 'commit ssl cert'
+ */
+static int cli_parse_commit_cert(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ struct commit_cert_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
+ char *err = NULL;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+ return 1;
+
+ if (!*args[3])
+ return cli_err(appctx, "'commit ssl cert expects a filename\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 commit the certificate!\nOperations on certificates are currently locked!\n");
+
+ if (!ckchs_transaction.path) {
+ memprintf(&err, "No ongoing transaction! !\n");
+ goto error;
+ }
+
+ if (strcmp(ckchs_transaction.path, args[3]) != 0) {
+ memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, args[3]);
+ goto error;
+ }
+
+ /* if a certificate is here, a private key must be here too */
+ if (ckchs_transaction.new_ckchs->ckch->cert && !ckchs_transaction.new_ckchs->ckch->key) {
+ memprintf(&err, "The transaction must contain at least a certificate and a private key!\n");
+ goto error;
+ }
+
+ if (!X509_check_private_key(ckchs_transaction.new_ckchs->ckch->cert, ckchs_transaction.new_ckchs->ckch->key)) {
+ memprintf(&err, "inconsistencies between private key and certificate loaded '%s'.\n", ckchs_transaction.path);
+ goto error;
+ }
+
+ /* init the appctx structure */
+ ctx->state = CERT_ST_INIT;
+ ctx->next_ckchi = NULL;
+ ctx->new_ckchs = ckchs_transaction.new_ckchs;
+ ctx->old_ckchs = ckchs_transaction.old_ckchs;
+
+ /* we don't unlock there, it will be unlock after the IO handler, in the release handler */
+ return 0;
+
+error:
+
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
+
+ return cli_dynerr(appctx, err);
+}
+
+
+
+
+/*
+ * Parsing function of `set ssl cert`, it updates or creates a temporary ckch.
+ * It uses a set_cert_ctx context, and ckchs_transaction under a lock.
+ */
+static int cli_parse_set_cert(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ struct set_cert_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
+ struct ckch_store *new_ckchs = NULL;
+ struct ckch_store *old_ckchs = NULL;
+ char *err = NULL;
+ int i;
+ int errcode = 0;
+ char *end;
+ struct cert_exts *cert_ext = &cert_exts[0]; /* default one, PEM */
+ struct cert_key_and_chain *ckch;
+ struct buffer *buf;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+ return 1;
+
+ if (!*args[3] || !payload)
+ return cli_err(appctx, "'set ssl cert expects a filename and a certificate as a payload\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 update the certificate!\nOperations on certificates are currently locked!\n");
+
+ if ((buf = alloc_trash_chunk()) == NULL) {
+ memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+
+ if (!chunk_strcpy(buf, args[3])) {
+ memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+
+ /* check which type of file we want to update */
+ for (i = 0; cert_exts[i].ext != NULL; i++) {
+ end = strrchr(buf->area, '.');
+ if (end && *cert_exts[i].ext && (strcmp(end + 1, cert_exts[i].ext) == 0)) {
+ *end = '\0';
+ buf->data = strlen(buf->area);
+ cert_ext = &cert_exts[i];
+ break;
+ }
+ }
+
+ ctx->old_ckchs = NULL;
+ ctx->new_ckchs = NULL;
+
+ /* if there is an ongoing transaction */
+ if (ckchs_transaction.path) {
+ /* if there is an ongoing transaction, check if this is the same file */
+ if (strcmp(ckchs_transaction.path, buf->area) != 0) {
+ /* we didn't find the transaction, must try more cases below */
+
+ /* if the del-ext option is activated we should try to take a look at a ".crt" too. */
+ if (cert_ext->type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
+ if (!chunk_strcat(buf, ".crt")) {
+ memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+
+ if (strcmp(ckchs_transaction.path, buf->area) != 0) {
+ /* remove .crt of the error message */
+ *(b_orig(buf) + b_data(buf) + strlen(".crt")) = '\0';
+ b_sub(buf, strlen(".crt"));
+
+ memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, buf->area);
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+ }
+ }
+
+ ctx->old_ckchs = ckchs_transaction.new_ckchs;
+
+ } else {
+
+ /* lookup for the certificate in the tree */
+ ctx->old_ckchs = ckchs_lookup(buf->area);
+
+ if (!ctx->old_ckchs) {
+ /* if the del-ext option is activated we should try to take a look at a ".crt" too. */
+ if (cert_ext->type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
+ if (!chunk_strcat(buf, ".crt")) {
+ memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+ ctx->old_ckchs = ckchs_lookup(buf->area);
+ }
+ }
+ }
+
+ if (!ctx->old_ckchs) {
+ memprintf(&err, "%sCan't replace a certificate which is not referenced by the configuration!\n",
+ err ? err : "");
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+
+ old_ckchs = ctx->old_ckchs;
+
+ /* duplicate the ckch store */
+ new_ckchs = ckchs_dup(old_ckchs);
+ if (!new_ckchs) {
+ memprintf(&err, "%sCannot allocate memory!\n",
+ err ? err : "");
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+
+ ckch = new_ckchs->ckch;
+
+ /* appply the change on the duplicate */
+ if (cert_ext->load(buf->area, payload, ckch, &err) != 0) {
+ memprintf(&err, "%sCan't load the payload\n", err ? err : "");
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+
+ ctx->new_ckchs = new_ckchs;
+
+ /* we succeed, we can save the ckchs in the transaction */
+
+ /* if there wasn't a transaction, update the old ckchs */
+ if (!ckchs_transaction.old_ckchs) {
+ ckchs_transaction.old_ckchs = ctx->old_ckchs;
+ ckchs_transaction.path = ctx->old_ckchs->path;
+ err = memprintf(&err, "Transaction created for certificate %s!\n", ckchs_transaction.path);
+ } else {
+ err = memprintf(&err, "Transaction updated for certificate %s!\n", ckchs_transaction.path);
+
+ }
+
+ /* free the previous ckchs if there was a transaction */
+ ckch_store_free(ckchs_transaction.new_ckchs);
+
+ ckchs_transaction.new_ckchs = ctx->new_ckchs;
+
+
+ /* creates the SNI ctxs later in the IO handler */
+
+end:
+ free_trash_chunk(buf);
+
+ if (errcode & ERR_CODE) {
+ ckch_store_free(ctx->new_ckchs);
+ ctx->new_ckchs = NULL;
+ ctx->old_ckchs = NULL;
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
+ } else {
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynmsg(appctx, LOG_NOTICE, err);
+ }
+ /* TODO: handle the ERR_WARN which are not handled because of the io_handler */
+}
+
+/* parsing function of 'abort ssl cert' */
+static int cli_parse_abort_cert(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ char *err = NULL;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+ return 1;
+
+ if (!*args[3])
+ return cli_err(appctx, "'abort ssl cert' expects a filename\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 abort!\nOperations on certificates are currently locked!\n");
+
+ if (!ckchs_transaction.path) {
+ memprintf(&err, "No ongoing transaction!\n");
+ goto error;
+ }
+
+ if (strcmp(ckchs_transaction.path, args[3]) != 0) {
+ memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", ckchs_transaction.path, args[3]);
+ goto error;
+ }
+
+ /* Only free the ckchs there, because the SNI and instances were not generated yet */
+ ckch_store_free(ckchs_transaction.new_ckchs);
+ ckchs_transaction.new_ckchs = NULL;
+ ckchs_transaction.old_ckchs = NULL;
+ ckchs_transaction.path = NULL;
+
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+
+ err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
+ return cli_dynmsg(appctx, LOG_NOTICE, err);
+
+error:
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+
+ return cli_dynerr(appctx, err);
+}
+
+/* parsing function of 'new ssl cert' */
+static int cli_parse_new_cert(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ struct ckch_store *store;
+ char *err = NULL;
+ char *path;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+ return 1;
+
+ if (!*args[3])
+ return cli_err(appctx, "'new ssl cert' expects a filename\n");
+
+ path = args[3];
+
+ /* 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 create a certificate!\nOperations on certificates are currently locked!\n");
+
+ store = ckchs_lookup(path);
+ if (store != NULL) {
+ memprintf(&err, "Certificate '%s' already exists!\n", path);
+ store = NULL; /* we don't want to free it */
+ goto error;
+ }
+ /* we won't support multi-certificate bundle here */
+ store = ckch_store_new(path);
+ if (!store) {
+ memprintf(&err, "unable to allocate memory.\n");
+ goto error;
+ }
+
+ /* insert into the ckchs tree */
+ ebst_insert(&ckchs_tree, &store->node);
+ memprintf(&err, "New empty certificate store '%s'!\n", args[3]);
+
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynmsg(appctx, LOG_NOTICE, err);
+error:
+ free(store);
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynerr(appctx, err);
+}
+
+/* parsing function of 'del ssl cert' */
+static int cli_parse_del_cert(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ struct ckch_store *store;
+ char *err = NULL;
+ char *filename;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+ return 1;
+
+ if (!*args[3])
+ return cli_err(appctx, "'del ssl cert' expects a certificate name\n");
+
+ if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
+ return cli_err(appctx, "Can't delete the certificate!\nOperations on certificates are currently locked!\n");
+
+ filename = args[3];
+
+ if (ckchs_transaction.path && strcmp(ckchs_transaction.path, filename) == 0) {
+ memprintf(&err, "ongoing transaction for the certificate '%s'", filename);
+ goto error;
+ }
+
+ store = ckchs_lookup(filename);
+ if (store == NULL) {
+ memprintf(&err, "certificate '%s' doesn't exist!\n", filename);
+ goto error;
+ }
+ if (!LIST_ISEMPTY(&store->ckch_inst)) {
+ memprintf(&err, "certificate '%s' in use, can't be deleted!\n", filename);
+ goto error;
+ }
+
+ ebmb_delete(&store->node);
+ ckch_store_free(store);
+
+ memprintf(&err, "Certificate '%s' deleted!\n", filename);
+
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynmsg(appctx, LOG_NOTICE, err);
+
+error:
+ memprintf(&err, "Can't remove the certificate: %s\n", err ? err : "");
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynerr(appctx, err);
+}
+
+
+
+/* parsing function of 'new ssl ca-file' */
+static int cli_parse_new_cafile(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ struct cafile_entry *cafile_entry;
+ char *err = NULL;
+ char *path;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+ return 1;
+
+ if (!*args[3])
+ return cli_err(appctx, "'new ssl ca-file' expects a filename\n");
+
+ path = args[3];
+
+ /* 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 create a CA file!\nOperations on certificates are currently locked!\n");
+
+ cafile_entry = ssl_store_get_cafile_entry(path, 0);
+ if (cafile_entry) {
+ memprintf(&err, "CA file '%s' already exists!\n", path);
+ goto error;
+ }
+
+ cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CERT);
+ if (!cafile_entry) {
+ memprintf(&err, "%sCannot allocate memory!\n",
+ err ? err : "");
+ goto error;
+ }
+
+ /* Add the newly created cafile_entry to the tree so that
+ * any new ckch instance created from now can use it. */
+ if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
+ goto error;
+
+ memprintf(&err, "New CA file created '%s'!\n", path);
+
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynmsg(appctx, LOG_NOTICE, err);
+error:
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynerr(appctx, err);
+}
+
+/*
+ * Parsing function of `set ssl ca-file`
+ */
+static int cli_parse_set_cafile(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ struct set_cafile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
+ char *err = NULL;
+ int errcode = 0;
+ struct buffer *buf;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+ return 1;
+
+ if (!*args[3] || !payload)
+ return cli_err(appctx, "'set ssl ca-file expects a filename and CAs as a payload\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 update the CA file!\nOperations on certificates are currently locked!\n");
+
+ if ((buf = alloc_trash_chunk()) == NULL) {
+ memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+
+ if (!chunk_strcpy(buf, args[3])) {
+ memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+
+ ctx->old_cafile_entry = NULL;
+ ctx->new_cafile_entry = NULL;
+
+ /* if there is an ongoing transaction */
+ if (cafile_transaction.path) {
+ /* if there is an ongoing transaction, check if this is the same file */
+ if (strcmp(cafile_transaction.path, buf->area) != 0) {
+ memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, buf->area);
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+ ctx->old_cafile_entry = cafile_transaction.old_cafile_entry;
+ }
+ else {
+ /* lookup for the certificate in the tree */
+ ctx->old_cafile_entry = ssl_store_get_cafile_entry(buf->area, 0);
+ }
+
+ if (!ctx->old_cafile_entry) {
+ memprintf(&err, "%sCan't replace a CA file which is not referenced by the configuration!\n",
+ err ? err : "");
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+
+ if (ctx->new_cafile_entry)
+ ssl_store_delete_cafile_entry(ctx->new_cafile_entry);
+
+ /* Create a new cafile_entry without adding it to the cafile tree. */
+ ctx->new_cafile_entry = ssl_store_create_cafile_entry(ctx->old_cafile_entry->path, NULL, CAFILE_CERT);
+ if (!ctx->new_cafile_entry) {
+ memprintf(&err, "%sCannot 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(ctx->new_cafile_entry, payload)) {
+ memprintf(&err, "%sInvalid payload\n", err ? err : "");
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+
+ /* we succeed, we can save the ca in the transaction */
+
+ /* if there wasn't a transaction, update the old CA */
+ if (!cafile_transaction.old_cafile_entry) {
+ cafile_transaction.old_cafile_entry = ctx->old_cafile_entry;
+ cafile_transaction.path = ctx->old_cafile_entry->path;
+ err = memprintf(&err, "transaction created for CA %s!\n", cafile_transaction.path);
+ } else {
+ err = memprintf(&err, "transaction updated for CA %s!\n", cafile_transaction.path);
+ }
+
+ /* free the previous CA if there was a transaction */
+ ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
+
+ cafile_transaction.new_cafile_entry = ctx->new_cafile_entry;
+
+ /* creates the SNI ctxs later in the IO handler */
+
+end:
+ free_trash_chunk(buf);
+
+ if (errcode & ERR_CODE) {
+ ssl_store_delete_cafile_entry(ctx->new_cafile_entry);
+ ctx->new_cafile_entry = NULL;
+ ctx->old_cafile_entry = NULL;
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
+ } else {
+
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynmsg(appctx, LOG_NOTICE, err);
+ }
+}
+
+
+/*
+ * Parsing function of 'commit ssl ca-file'.
+ * It uses a commit_cacrlfile_ctx that's also shared with "commit ssl crl-file".
+ */
+static int cli_parse_commit_cafile(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ struct commit_cacrlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
+ char *err = NULL;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+ return 1;
+
+ if (!*args[3])
+ return cli_err(appctx, "'commit ssl ca-file expects a filename\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 commit the CA file!\nOperations on certificates are currently locked!\n");
+
+ if (!cafile_transaction.path) {
+ memprintf(&err, "No ongoing transaction! !\n");
+ goto error;
+ }
+
+ if (strcmp(cafile_transaction.path, args[3]) != 0) {
+ memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, args[3]);
+ goto error;
+ }
+ /* init the appctx structure */
+ ctx->state = CACRL_ST_INIT;
+ ctx->next_ckchi_link = NULL;
+ ctx->old_cafile_entry = cafile_transaction.old_cafile_entry;
+ ctx->new_cafile_entry = cafile_transaction.new_cafile_entry;
+ ctx->cafile_type = CAFILE_CERT;
+
+ return 0;
+
+error:
+
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
+
+ return cli_dynerr(appctx, err);
+}
+
+/*
+ * This function tries to create new ckch instances and their SNIs using a newly
+ * set certificate authority (CA file) or a newly set Certificate Revocation
+ * List (CRL), depending on the command being called.
+ */
+static int cli_io_handler_commit_cafile_crlfile(struct appctx *appctx)
+{
+ struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
+ struct stconn *sc = appctx_sc(appctx);
+ int y = 0;
+ struct cafile_entry *old_cafile_entry = NULL, *new_cafile_entry = NULL;
+ struct ckch_inst_link *ckchi_link;
+
+ if (unlikely(sc_ic(sc)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
+ goto end;
+
+ while (1) {
+ switch (ctx->state) {
+ case CACRL_ST_INIT:
+ /* This state just print the update message */
+ switch (ctx->cafile_type) {
+ case CAFILE_CERT:
+ chunk_printf(&trash, "Committing %s", cafile_transaction.path);
+ break;
+ case CAFILE_CRL:
+ chunk_printf(&trash, "Committing %s", crlfile_transaction.path);
+ break;
+ }
+ if (applet_putchk(appctx, &trash) == -1)
+ goto yield;
+
+ ctx->state = CACRL_ST_GEN;
+ /* fallthrough */
+ case CACRL_ST_GEN:
+ /*
+ * This state generates the ckch instances with their
+ * sni_ctxs and SSL_CTX.
+ *
+ * Since the SSL_CTX generation can be CPU consumer, we
+ * yield every 10 instances.
+ */
+ switch (ctx->cafile_type) {
+ case CAFILE_CERT:
+ old_cafile_entry = ctx->old_cafile_entry;
+ new_cafile_entry = ctx->new_cafile_entry;
+ break;
+ case CAFILE_CRL:
+ old_cafile_entry = ctx->old_crlfile_entry;
+ new_cafile_entry = ctx->new_crlfile_entry;
+ break;
+ }
+
+ /* get the next ckchi to regenerate */
+ ckchi_link = ctx->next_ckchi_link;
+
+ /* we didn't start yet, set it to the first elem */
+ if (ckchi_link == NULL) {
+ ckchi_link = LIST_ELEM(old_cafile_entry->ckch_inst_link.n, typeof(ckchi_link), list);
+ /* Add the newly created cafile_entry to the tree so that
+ * any new ckch instance created from now can use it. */
+ if (ssl_store_add_uncommitted_cafile_entry(new_cafile_entry)) {
+ ctx->state = CACRL_ST_ERROR;
+ goto error;
+ }
+ }
+
+ list_for_each_entry_from(ckchi_link, &old_cafile_entry->ckch_inst_link, list) {
+ struct ckch_inst *new_inst;
+
+ /* save the next ckchi to compute */
+ ctx->next_ckchi_link = ckchi_link;
+
+ /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
+ if (y >= 10) {
+ applet_have_more_data(appctx); /* let's come back later */
+ goto yield;
+ }
+
+ /* display one dot per new instance */
+ if (applet_putstr(appctx, ".") == -1)
+ goto yield;
+
+ /* Rebuild a new ckch instance that uses the same ckch_store
+ * than a reference ckchi instance but will use a new CA file. */
+ ctx->err = NULL;
+ if (ckch_inst_rebuild(ckchi_link->ckch_inst->ckch_store, ckchi_link->ckch_inst, &new_inst, &ctx->err)) {
+ ctx->state = CACRL_ST_ERROR;
+ goto error;
+ }
+
+ y++;
+ }
+
+ ctx->state = CACRL_ST_INSERT;
+ /* fallthrough */
+ case CACRL_ST_INSERT:
+ /* The generation is finished, we can insert everything */
+ switch (ctx->cafile_type) {
+ case CAFILE_CERT:
+ old_cafile_entry = ctx->old_cafile_entry;
+ new_cafile_entry = ctx->new_cafile_entry;
+ break;
+ case CAFILE_CRL:
+ old_cafile_entry = ctx->old_crlfile_entry;
+ new_cafile_entry = ctx->new_crlfile_entry;
+ break;
+ }
+ if (!new_cafile_entry)
+ continue;
+
+ /* insert the new ckch_insts in the crtlist_entry */
+ list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
+ if (ckchi_link->ckch_inst->crtlist_entry)
+ LIST_INSERT(&ckchi_link->ckch_inst->crtlist_entry->ckch_inst,
+ &ckchi_link->ckch_inst->by_crtlist_entry);
+ }
+
+ /* First, we insert every new SNIs in the trees, also replace the default_ctx */
+ list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
+ __ssl_sock_load_new_ckch_instance(ckchi_link->ckch_inst);
+ }
+
+ /* delete the old sni_ctx, the old ckch_insts
+ * and the ckch_store. ckch_inst_free() also
+ * manipulates the list so it's cleaner to loop
+ * until it's empty */
+ while (!LIST_ISEMPTY(&old_cafile_entry->ckch_inst_link)) {
+ ckchi_link = LIST_ELEM(old_cafile_entry->ckch_inst_link.n, typeof(ckchi_link), list);
+
+ LIST_DEL_INIT(&ckchi_link->list); /* must reinit because ckch_inst checks the list */
+ __ckch_inst_free_locked(ckchi_link->ckch_inst);
+ free(ckchi_link);
+ }
+
+ /* Remove the old cafile entry from the tree */
+ ebmb_delete(&old_cafile_entry->node);
+ ssl_store_delete_cafile_entry(old_cafile_entry);
+
+ switch (ctx->cafile_type) {
+ case CAFILE_CERT:
+ ctx->old_cafile_entry = ctx->new_cafile_entry = NULL;
+ break;
+ case CAFILE_CRL:
+ ctx->old_crlfile_entry = ctx->new_crlfile_entry = NULL;
+ break;
+ }
+ ctx->state = CACRL_ST_SUCCESS;
+ /* fallthrough */
+ case CACRL_ST_SUCCESS:
+ if (applet_putstr(appctx, "\nSuccess!\n") == -1)
+ goto yield;
+ ctx->state = CACRL_ST_FIN;
+ /* fallthrough */
+ case CACRL_ST_FIN:
+ /* we achieved the transaction, we can set everything to NULL */
+ switch (ctx->cafile_type) {
+ case CAFILE_CERT:
+ cafile_transaction.old_cafile_entry = NULL;
+ cafile_transaction.new_cafile_entry = NULL;
+ cafile_transaction.path = NULL;
+ break;
+ case CAFILE_CRL:
+ crlfile_transaction.old_crlfile_entry = NULL;
+ crlfile_transaction.new_crlfile_entry = NULL;
+ crlfile_transaction.path = NULL;
+ break;
+ }
+ goto end;
+
+ case CACRL_ST_ERROR:
+ error:
+ chunk_printf(&trash, "\n%sFailed!\n", ctx->err);
+ if (applet_putchk(appctx, &trash) == -1)
+ goto yield;
+ ctx->state = CACRL_ST_FIN;
+ break;
+ }
+ }
+end:
+ /* success: call the release function and don't come back */
+ return 1;
+yield:
+ return 0; /* should come back */
+}
+
+
+/* parsing function of 'abort ssl ca-file' */
+static int cli_parse_abort_cafile(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ char *err = NULL;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+ return 1;
+
+ if (!*args[3])
+ return cli_err(appctx, "'abort ssl ca-file' expects a filename\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 abort!\nOperations on certificates are currently locked!\n");
+
+ if (!cafile_transaction.path) {
+ memprintf(&err, "No ongoing transaction!\n");
+ goto error;
+ }
+
+ if (strcmp(cafile_transaction.path, args[3]) != 0) {
+ memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", cafile_transaction.path, args[3]);
+ goto error;
+ }
+
+ /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
+ ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
+ cafile_transaction.new_cafile_entry = NULL;
+ cafile_transaction.old_cafile_entry = NULL;
+ cafile_transaction.path = NULL;
+
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+
+ err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
+ return cli_dynmsg(appctx, LOG_NOTICE, err);
+
+error:
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+
+ return cli_dynerr(appctx, err);
+}
+
+/* release function of the `commit ssl ca-file' command, free things and unlock the spinlock.
+ * It uses a commit_cacrlfile_ctx context.
+ */
+static void cli_release_commit_cafile(struct appctx *appctx)
+{
+ struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
+ struct cafile_entry *new_cafile_entry = ctx->new_cafile_entry;
+
+ /* Remove the uncommitted cafile_entry from the tree. */
+ if (new_cafile_entry) {
+ ebmb_delete(&new_cafile_entry->node);
+ ssl_store_delete_cafile_entry(new_cafile_entry);
+ }
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ ha_free(&ctx->err);
+}
+
+
+/* IO handler of details "show ssl ca-file <filename[:index]>".
+ * It uses a show_cafile_ctx context, and the global
+ * cafile_transaction.new_cafile_entry in read-only.
+ */
+static int cli_io_handler_show_cafile_detail(struct appctx *appctx)
+{
+ struct show_cafile_ctx *ctx = appctx->svcctx;
+ struct cafile_entry *cafile_entry = ctx->cur_cafile_entry;
+ struct buffer *out = alloc_trash_chunk();
+ int i = 0;
+ X509 *cert;
+ STACK_OF(X509_OBJECT) *objs;
+ int retval = 0;
+ int ca_index = ctx->ca_index;
+ int show_all = ctx->show_all;
+
+ 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 = ca_index; i < sk_X509_OBJECT_num(objs); i++) {
+
+ cert = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(objs, i));
+ if (!cert)
+ continue;
+
+ /* file starts at line 1 */
+ 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)
+ goto yield;
+
+ if (applet_putchk(appctx, out) == -1)
+ goto yield;
+
+ if (!show_all) /* only need to dump one certificate */
+ goto end;
+ }
+
+end:
+ free_trash_chunk(out);
+ return 1; /* end, don't come back */
+
+end_no_putchk:
+ free_trash_chunk(out);
+ return 1;
+yield:
+ /* save the current state */
+ ctx->ca_index = i;
+ free_trash_chunk(out);
+ return 0; /* should come back */
+}
+
+
+/* parsing function for 'show ssl ca-file [cafile[:index]]'.
+ * It prepares a show_cafile_ctx context, and checks the global
+ * cafile_transaction under the ckch_lock (read only).
+ */
+static int cli_parse_show_cafile(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ struct show_cafile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
+ struct cafile_entry *cafile_entry;
+ int 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");
+
+ ctx->show_all = 1; /* show all certificates */
+ ctx->ca_index = 0;
+ /* 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';
+ ctx->ca_index = ca_index - 1; /* we start counting at 0 in the ca_store, but at 1 on the CLI */
+ ctx->show_all = 0; /* show only one certificate */
+ }
+
+ 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;
+ }
+
+ ctx->cur_cafile_entry = cafile_entry;
+ /* 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.
+ * It uses a show_cafile_ctx and the global cafile_transaction.new_cafile_entry
+ * in read-only.
+ */
+static int cli_io_handler_show_cafile(struct appctx *appctx)
+{
+ struct show_cafile_ctx *ctx = appctx->svcctx;
+ struct buffer *trash = alloc_trash_chunk();
+ struct ebmb_node *node;
+ struct cafile_entry *cafile_entry = NULL;
+
+ if (trash == NULL)
+ return 1;
+
+ if (!ctx->old_cafile_entry && 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));
+ if (applet_putchk(appctx, trash) == -1)
+ goto yield;
+ ctx->old_cafile_entry = cafile_transaction.new_cafile_entry;
+ }
+
+ /* First time in this io_handler. */
+ if (!ctx->cur_cafile_entry) {
+ chunk_appendf(trash, "# filename\n");
+ node = ebmb_first(&cafile_tree);
+ } else {
+ /* We yielded during a previous call. */
+ node = &ctx->cur_cafile_entry->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 (applet_putchk(appctx, trash) == -1)
+ goto yield;
+ }
+
+ ctx->cur_cafile_entry = NULL;
+ free_trash_chunk(trash);
+ return 1;
+yield:
+
+ free_trash_chunk(trash);
+ ctx->cur_cafile_entry = cafile_entry;
+ return 0; /* should come back */
+}
+
+/* parsing function of 'del ssl ca-file' */
+static int cli_parse_del_cafile(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ struct cafile_entry *cafile_entry;
+ char *err = NULL;
+ char *filename;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+ return 1;
+
+ if (!*args[3])
+ return cli_err(appctx, "'del ssl ca-file' expects a CA file name\n");
+
+ if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
+ return cli_err(appctx, "Can't delete the CA file!\nOperations on certificates are currently locked!\n");
+
+ filename = args[3];
+
+ if (cafile_transaction.path && strcmp(cafile_transaction.path, filename) == 0) {
+ memprintf(&err, "ongoing transaction for the CA file '%s'", filename);
+ goto error;
+ }
+
+ cafile_entry = ssl_store_get_cafile_entry(filename, 0);
+ if (!cafile_entry) {
+ memprintf(&err, "CA file '%s' doesn't exist!\n", filename);
+ goto error;
+ }
+
+ if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
+ memprintf(&err, "CA file '%s' in use, can't be deleted!\n", filename);
+ goto error;
+ }
+
+ /* Remove the cafile_entry from the tree */
+ ebmb_delete(&cafile_entry->node);
+ ssl_store_delete_cafile_entry(cafile_entry);
+
+ memprintf(&err, "CA file '%s' deleted!\n", filename);
+
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynmsg(appctx, LOG_NOTICE, err);
+
+error:
+ memprintf(&err, "Can't remove the CA file: %s\n", err ? err : "");
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynerr(appctx, err);
+}
+
+/* parsing function of 'new ssl crl-file' */
+static int cli_parse_new_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ struct cafile_entry *cafile_entry;
+ char *err = NULL;
+ char *path;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+ return 1;
+
+ if (!*args[3])
+ return cli_err(appctx, "'new ssl crl-file' expects a filename\n");
+
+ path = args[3];
+
+ /* 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 create a CRL file!\nOperations on certificates are currently locked!\n");
+
+ cafile_entry = ssl_store_get_cafile_entry(path, 0);
+ if (cafile_entry) {
+ memprintf(&err, "CRL file '%s' already exists!\n", path);
+ goto error;
+ }
+
+ cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CRL);
+ if (!cafile_entry) {
+ memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
+ goto error;
+ }
+
+ /* Add the newly created cafile_entry to the tree so that
+ * any new ckch instance created from now can use it. */
+ if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
+ goto error;
+
+ memprintf(&err, "New CRL file created '%s'!\n", path);
+
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynmsg(appctx, LOG_NOTICE, err);
+error:
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynerr(appctx, err);
+}
+
+/* Parsing function of `set ssl crl-file` */
+static int cli_parse_set_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ struct set_crlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
+ char *err = NULL;
+ int errcode = 0;
+ struct buffer *buf;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+ return 1;
+
+ if (!*args[3] || !payload)
+ return cli_err(appctx, "'set ssl crl-file expects a filename and CRLs as a payload\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 update the CRL file!\nOperations on certificates are currently locked!\n");
+
+ if ((buf = alloc_trash_chunk()) == NULL) {
+ memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+
+ if (!chunk_strcpy(buf, args[3])) {
+ memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+
+ ctx->old_crlfile_entry = NULL;
+ ctx->new_crlfile_entry = NULL;
+
+ /* if there is an ongoing transaction */
+ if (crlfile_transaction.path) {
+ /* if there is an ongoing transaction, check if this is the same file */
+ if (strcmp(crlfile_transaction.path, buf->area) != 0) {
+ memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, buf->area);
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+ ctx->old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
+ }
+ else {
+ /* lookup for the certificate in the tree */
+ ctx->old_crlfile_entry = ssl_store_get_cafile_entry(buf->area, 0);
+ }
+
+ if (!ctx->old_crlfile_entry) {
+ memprintf(&err, "%sCan't replace a CRL file which is not referenced by the configuration!\n",
+ err ? err : "");
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+
+ if (ctx->new_crlfile_entry)
+ ssl_store_delete_cafile_entry(ctx->new_crlfile_entry);
+
+ /* Create a new cafile_entry without adding it to the cafile tree. */
+ ctx->new_crlfile_entry = ssl_store_create_cafile_entry(ctx->old_crlfile_entry->path, NULL, CAFILE_CRL);
+ if (!ctx->new_crlfile_entry) {
+ memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+
+ /* Fill the new entry with the new CRL. */
+ if (ssl_store_load_ca_from_buf(ctx->new_crlfile_entry, payload)) {
+ memprintf(&err, "%sInvalid payload\n", err ? err : "");
+ errcode |= ERR_ALERT | ERR_FATAL;
+ goto end;
+ }
+
+ /* we succeed, we can save the crl in the transaction */
+
+ /* if there wasn't a transaction, update the old CRL */
+ if (!crlfile_transaction.old_crlfile_entry) {
+ crlfile_transaction.old_crlfile_entry = ctx->old_crlfile_entry;
+ crlfile_transaction.path = ctx->old_crlfile_entry->path;
+ err = memprintf(&err, "transaction created for CRL %s!\n", crlfile_transaction.path);
+ } else {
+ err = memprintf(&err, "transaction updated for CRL %s!\n", crlfile_transaction.path);
+ }
+
+ /* free the previous CRL file if there was a transaction */
+ ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
+
+ crlfile_transaction.new_crlfile_entry = ctx->new_crlfile_entry;
+
+ /* creates the SNI ctxs later in the IO handler */
+
+end:
+ free_trash_chunk(buf);
+
+ if (errcode & ERR_CODE) {
+ ssl_store_delete_cafile_entry(ctx->new_crlfile_entry);
+ ctx->new_crlfile_entry = NULL;
+ ctx->old_crlfile_entry = NULL;
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
+ } else {
+
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynmsg(appctx, LOG_NOTICE, err);
+ }
+}
+
+/* Parsing function of 'commit ssl crl-file'.
+ * It uses a commit_cacrlfile_ctx that's also shared with "commit ssl ca-file".
+ */
+static int cli_parse_commit_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ struct commit_cacrlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
+ char *err = NULL;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+ return 1;
+
+ if (!*args[3])
+ return cli_err(appctx, "'commit ssl ca-file expects a filename\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 commit the CRL file!\nOperations on certificates are currently locked!\n");
+
+ if (!crlfile_transaction.path) {
+ memprintf(&err, "No ongoing transaction! !\n");
+ goto error;
+ }
+
+ if (strcmp(crlfile_transaction.path, args[3]) != 0) {
+ memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, args[3]);
+ goto error;
+ }
+ /* init the appctx structure */
+ ctx->state = CACRL_ST_INIT;
+ ctx->next_ckchi_link = NULL;
+ ctx->old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
+ ctx->new_crlfile_entry = crlfile_transaction.new_crlfile_entry;
+ ctx->cafile_type = CAFILE_CRL;
+
+ return 0;
+
+error:
+
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
+
+ return cli_dynerr(appctx, err);
+}
+
+
+/* release function of the `commit ssl crl-file' command, free things and unlock the spinlock.
+ * it uses a commit_cacrlfile_ctx that's the same as for "commit ssl ca-file".
+ */
+static void cli_release_commit_crlfile(struct appctx *appctx)
+{
+ struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
+ struct cafile_entry *new_crlfile_entry = ctx->new_crlfile_entry;
+
+ /* Remove the uncommitted cafile_entry from the tree. */
+ if (new_crlfile_entry) {
+ ebmb_delete(&new_crlfile_entry->node);
+ ssl_store_delete_cafile_entry(new_crlfile_entry);
+ }
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ ha_free(&ctx->err);
+}
+
+/* parsing function of 'del ssl crl-file' */
+static int cli_parse_del_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ struct cafile_entry *cafile_entry;
+ char *err = NULL;
+ char *filename;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+ return 1;
+
+ if (!*args[3])
+ return cli_err(appctx, "'del ssl crl-file' expects a CRL file name\n");
+
+ if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
+ return cli_err(appctx, "Can't delete the CRL file!\nOperations on certificates are currently locked!\n");
+
+ filename = args[3];
+
+ if (crlfile_transaction.path && strcmp(crlfile_transaction.path, filename) == 0) {
+ memprintf(&err, "ongoing transaction for the CRL file '%s'", filename);
+ goto error;
+ }
+
+ cafile_entry = ssl_store_get_cafile_entry(filename, 0);
+ if (!cafile_entry) {
+ memprintf(&err, "CRL file '%s' doesn't exist!\n", filename);
+ goto error;
+ }
+ if (cafile_entry->type != CAFILE_CRL) {
+ memprintf(&err, "'del ssl crl-file' does not work on CA files!\n");
+ goto error;
+ }
+
+ if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
+ memprintf(&err, "CRL file '%s' in use, can't be deleted!\n", filename);
+ goto error;
+ }
+
+ /* Remove the cafile_entry from the tree */
+ ebmb_delete(&cafile_entry->node);
+ ssl_store_delete_cafile_entry(cafile_entry);
+
+ memprintf(&err, "CRL file '%s' deleted!\n", filename);
+
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynmsg(appctx, LOG_NOTICE, err);
+
+error:
+ memprintf(&err, "Can't remove the CRL file: %s\n", err ? err : "");
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+ return cli_dynerr(appctx, err);
+}
+
+/* parsing function of 'abort ssl crl-file' */
+static int cli_parse_abort_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ char *err = NULL;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+ return 1;
+
+ if (!*args[3])
+ return cli_err(appctx, "'abort ssl crl-file' expects a filename\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 abort!\nOperations on certificates are currently locked!\n");
+
+ if (!crlfile_transaction.path) {
+ memprintf(&err, "No ongoing transaction!\n");
+ goto error;
+ }
+
+ if (strcmp(crlfile_transaction.path, args[3]) != 0) {
+ memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", crlfile_transaction.path, args[3]);
+ goto error;
+ }
+
+ /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
+ ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
+ crlfile_transaction.new_crlfile_entry = NULL;
+ crlfile_transaction.old_crlfile_entry = NULL;
+ crlfile_transaction.path = NULL;
+
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+
+ err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
+ return cli_dynmsg(appctx, LOG_NOTICE, err);
+
+error:
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+
+ return cli_dynerr(appctx, err);
+}
+
+
+/*
+ * Display a Certificate Resignation List's information.
+ * The information displayed is inspired by the output of 'openssl crl -in
+ * crl.pem -text'.
+ * Returns 0 in case of success.
+ */
+static int show_crl_detail(X509_CRL *crl, struct buffer *out)
+{
+ BIO *bio = NULL;
+ struct buffer *tmp = alloc_trash_chunk();
+ long version;
+ X509_NAME *issuer;
+ int write = -1;
+ STACK_OF(X509_REVOKED) *rev = NULL;
+ X509_REVOKED *rev_entry = NULL;
+ int i;
+
+ if (!tmp)
+ return -1;
+
+ if ((bio = BIO_new(BIO_s_mem())) == NULL)
+ goto end;
+
+ /* Version (as displayed by 'openssl crl') */
+ version = X509_CRL_get_version(crl);
+ chunk_appendf(out, "Version %ld\n", version + 1);
+
+ /* Signature Algorithm */
+ chunk_appendf(out, "Signature Algorithm: %s\n", OBJ_nid2ln(X509_CRL_get_signature_nid(crl)));
+
+ /* Issuer */
+ chunk_appendf(out, "Issuer: ");
+ if ((issuer = X509_CRL_get_issuer(crl)) == NULL)
+ goto end;
+ if ((ssl_sock_get_dn_oneline(issuer, tmp)) == -1)
+ goto end;
+ *(tmp->area + tmp->data) = '\0';
+ chunk_appendf(out, "%s\n", tmp->area);
+
+ /* Last Update */
+ chunk_appendf(out, "Last Update: ");
+ chunk_reset(tmp);
+ if (BIO_reset(bio) == -1)
+ goto end;
+ if (ASN1_TIME_print(bio, X509_CRL_get0_lastUpdate(crl)) == 0)
+ goto end;
+ write = BIO_read(bio, tmp->area, tmp->size-1);
+ tmp->area[write] = '\0';
+ chunk_appendf(out, "%s\n", tmp->area);
+
+
+ /* Next Update */
+ chunk_appendf(out, "Next Update: ");
+ chunk_reset(tmp);
+ if (BIO_reset(bio) == -1)
+ goto end;
+ if (ASN1_TIME_print(bio, X509_CRL_get0_nextUpdate(crl)) == 0)
+ goto end;
+ write = BIO_read(bio, tmp->area, tmp->size-1);
+ tmp->area[write] = '\0';
+ chunk_appendf(out, "%s\n", tmp->area);
+
+
+ /* Revoked Certificates */
+ rev = X509_CRL_get_REVOKED(crl);
+ if (sk_X509_REVOKED_num(rev) > 0)
+ chunk_appendf(out, "Revoked Certificates:\n");
+ else
+ chunk_appendf(out, "No Revoked Certificates.\n");
+
+ for (i = 0; i < sk_X509_REVOKED_num(rev); i++) {
+ rev_entry = sk_X509_REVOKED_value(rev, i);
+
+ /* Serial Number and Revocation Date */
+ if (BIO_reset(bio) == -1)
+ goto end;
+ BIO_printf(bio , " Serial Number: ");
+ i2a_ASN1_INTEGER(bio, (ASN1_INTEGER*)X509_REVOKED_get0_serialNumber(rev_entry));
+ BIO_printf(bio, "\n Revocation Date: ");
+ if (ASN1_TIME_print(bio, X509_REVOKED_get0_revocationDate(rev_entry)) == 0)
+ goto end;
+ BIO_printf(bio, "\n");
+
+ write = BIO_read(bio, tmp->area, tmp->size-1);
+ tmp->area[write] = '\0';
+ chunk_appendf(out, "%s", tmp->area);
+ }
+
+end:
+ free_trash_chunk(tmp);
+ if (bio)
+ BIO_free(bio);
+
+ return 0;
+}
+
+/* IO handler of details "show ssl crl-file <filename[:index]>".
+ * It uses show_crlfile_ctx and the global
+ * crlfile_transaction.new_cafile_entry in read-only.
+ */
+static int cli_io_handler_show_crlfile_detail(struct appctx *appctx)
+{
+ struct show_crlfile_ctx *ctx = appctx->svcctx;
+ struct cafile_entry *cafile_entry = ctx->cafile_entry;
+ struct buffer *out = alloc_trash_chunk();
+ int i;
+ X509_CRL *crl;
+ STACK_OF(X509_OBJECT) *objs;
+ int retval = 0;
+ int index = ctx->index;
+
+ if (!out)
+ goto end_no_putchk;
+
+ chunk_appendf(out, "Filename: ");
+ if (cafile_entry == crlfile_transaction.new_crlfile_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++) {
+ crl = X509_OBJECT_get0_X509_CRL(sk_X509_OBJECT_value(objs, i));
+ if (!crl)
+ continue;
+
+ /* CRL indexes start at 1 on the CLI output. */
+ if (index && index-1 != i)
+ continue;
+
+ chunk_appendf(out, " \nCertificate Revocation List #%d:\n", i+1);
+ retval = show_crl_detail(crl, out);
+ if (retval < 0)
+ goto end_no_putchk;
+ else if (retval || index)
+ goto end;
+ }
+
+end:
+ if (applet_putchk(appctx, out) == -1)
+ 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 crl-file [crlfile[:index]]'.
+ * It sets the context to a show_crlfile_ctx, and the global
+ * cafile_transaction.new_crlfile_entry under the ckch_lock.
+ */
+static int cli_parse_show_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ struct show_crlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
+ struct cafile_entry *cafile_entry;
+ long 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 index after the CRL file name */
+ colons = strchr(args[3], ':');
+ if (colons) {
+ char *endptr;
+
+ index = strtol(colons + 1, &endptr, 10);
+ /* Indexes start at 1 */
+ if (colons + 1 == endptr || *endptr != '\0' || index <= 0) {
+ memprintf(&err, "wrong CRL index after colons in '%s'!", args[3]);
+ goto error;
+ }
+ *colons = '\0';
+ }
+
+ if (*args[3] == '*') {
+ if (!crlfile_transaction.new_crlfile_entry)
+ goto error;
+
+ cafile_entry = crlfile_transaction.new_crlfile_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_CRL)
+ goto error;
+ }
+
+ ctx->cafile_entry = cafile_entry;
+ ctx->index = index;
+ /* use the IO handler that shows details */
+ appctx->io_handler = cli_io_handler_show_crlfile_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 CRL file : Not found!\n");
+}
+
+/* IO handler of "show ssl crl-file". The command taking a specific CRL file name
+ * is managed in cli_io_handler_show_crlfile_detail. */
+static int cli_io_handler_show_crlfile(struct appctx *appctx)
+{
+ struct show_crlfile_ctx *ctx = appctx->svcctx;
+ struct buffer *trash = alloc_trash_chunk();
+ struct ebmb_node *node;
+ struct cafile_entry *cafile_entry = NULL;
+
+ if (trash == NULL)
+ return 1;
+
+ if (!ctx->old_crlfile_entry && crlfile_transaction.old_crlfile_entry) {
+ chunk_appendf(trash, "# transaction\n");
+ chunk_appendf(trash, "*%s\n", crlfile_transaction.old_crlfile_entry->path);
+ if (applet_putchk(appctx, trash) == -1)
+ goto yield;
+ ctx->old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
+ }
+
+ /* First time in this io_handler. */
+ if (!ctx->cafile_entry) {
+ chunk_appendf(trash, "# filename\n");
+ node = ebmb_first(&cafile_tree);
+ } else {
+ /* We yielded during a previous call. */
+ node = &ctx->cafile_entry->node;
+ }
+
+ while (node) {
+ cafile_entry = ebmb_entry(node, struct cafile_entry, node);
+ if (cafile_entry->type == CAFILE_CRL) {
+ chunk_appendf(trash, "%s\n", cafile_entry->path);
+ }
+
+ node = ebmb_next(node);
+ if (applet_putchk(appctx, trash) == -1)
+ goto yield;
+ }
+
+ ctx->cafile_entry = NULL;
+ free_trash_chunk(trash);
+ return 1;
+yield:
+
+ free_trash_chunk(trash);
+ ctx->cafile_entry = cafile_entry;
+ return 0; /* should come back */
+}
+
+
+/* release function of the 'show ssl crl-file' command */
+static void cli_release_show_crlfile(struct appctx *appctx)
+{
+ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+}
+
+
+void ckch_deinit()
+{
+ struct eb_node *node, *next;
+ struct ckch_store *store;
+ struct ebmb_node *canode;
+
+ /* deinit the ckch stores */
+ node = eb_first(&ckchs_tree);
+ while (node) {
+ next = eb_next(node);
+ store = ebmb_entry(node, struct ckch_store, node);
+ ckch_store_free(store);
+ node = next;
+ }
+
+ /* deinit the ca-file store */
+ canode = ebmb_first(&cafile_tree);
+ while (canode) {
+ struct cafile_entry *entry = NULL;
+
+ entry = ebmb_entry(canode, struct cafile_entry, node);
+ canode = ebmb_next(canode);
+ ebmb_delete(&entry->node);
+ ssl_store_delete_cafile_entry(entry);
+ }
+}
+
+/* register cli keywords */
+static struct cli_kw_list cli_kws = {{ },{
+ { { "new", "ssl", "cert", NULL }, "new ssl cert <certfile> : create a new certificate file to be used in a crt-list or a directory", cli_parse_new_cert, NULL, NULL },
+ { { "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 },
+ { { "del", "ssl", "cert", NULL }, "del ssl cert <certfile> : delete an unused certificate file", cli_parse_del_cert, NULL, NULL },
+ { { "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 },
+ { { "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 },
+ { { "del", "ssl", "ca-file", NULL }, "del ssl ca-file <cafile> : delete an unused CA file", cli_parse_del_cafile, NULL, NULL },
+ { { "show", "ssl", "ca-file", NULL }, "show ssl ca-file [<cafile>[:<index>]] : display the SSL CA files used in memory, or the details of a <cafile>, or a single certificate of index <index> of a CA file <cafile>", cli_parse_show_cafile, cli_io_handler_show_cafile, cli_release_show_cafile },
+
+ { { "new", "ssl", "crl-file", NULL }, "new ssl crlfile <crlfile> : create a new CRL file to be used in a crt-list", cli_parse_new_crlfile, NULL, NULL },
+ { { "set", "ssl", "crl-file", NULL }, "set ssl crl-file <crlfile> <payload> : replace a CRL file", cli_parse_set_crlfile, NULL, NULL },
+ { { "commit", "ssl", "crl-file", NULL },"commit ssl crl-file <crlfile> : commit a CRL file", cli_parse_commit_crlfile, cli_io_handler_commit_cafile_crlfile, cli_release_commit_crlfile },
+ { { "abort", "ssl", "crl-file", NULL }, "abort ssl crl-file <crlfile> : abort a transaction for a CRL file", cli_parse_abort_crlfile, NULL, NULL },
+ { { "del", "ssl", "crl-file", NULL }, "del ssl crl-file <crlfile> : delete an unused CRL file", cli_parse_del_crlfile, NULL, NULL },
+ { { "show", "ssl", "crl-file", NULL }, "show ssl crl-file [<crlfile[:<index>>]] : display the SSL CRL files used in memory, or the details of a <crlfile>, or a single CRL of index <index> of CRL file <crlfile>", cli_parse_show_crlfile, cli_io_handler_show_crlfile, cli_release_show_crlfile },
+ { { NULL }, NULL, NULL, NULL }
+}};
+
+INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
+