diff options
Diffstat (limited to '')
-rw-r--r-- | src/ssl_ckch.c | 3938 |
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); + |