diff --git a/Makefile b/Makefile index cba041824..24fccf2aa 100644 --- a/Makefile +++ b/Makefile @@ -542,7 +542,7 @@ OPTIONS_LDFLAGS += $(if $(SSL_LIB),-L$(SSL_LIB)) -lssl -lcrypto ifneq ($(USE_DL),) OPTIONS_LDFLAGS += -ldl endif -OPTIONS_OBJS += src/ssl_sock.o src/ssl_crtlist.o +OPTIONS_OBJS += src/ssl_sock.o src/ssl_crtlist.o src/ssl_ckch.o endif # The private cache option affect the way the shctx is built diff --git a/include/proto/ssl_ckch.h b/include/proto/ssl_ckch.h index c869d92fd..52b358cfb 100644 --- a/include/proto/ssl_ckch.h +++ b/include/proto/ssl_ckch.h @@ -23,11 +23,32 @@ #define _PROTO_SSL_CKCH_H #ifdef USE_OPENSSL -#include +/* cert_key_and_chain functions */ + +int ssl_sock_load_files_into_ckch(const char *path, struct cert_key_and_chain *ckch, char **err); +int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err); +void ssl_sock_free_cert_key_and_chain_contents(struct cert_key_and_chain *ckch); + +int ssl_sock_load_key_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err); +int ssl_sock_load_ocsp_response_from_file(const char *ocsp_path, char *buf, struct cert_key_and_chain *ckch, char **err); +int ssl_sock_load_sctl_from_file(const char *sctl_path, char *buf, struct cert_key_and_chain *ckch, char **err); +int ssl_sock_load_issuer_file_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch, char **err); + +/* checks if a key and cert exists in the ckch */ +#if HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL +static inline int ssl_sock_is_ckch_valid(struct cert_key_and_chain *ckch) +{ + return (ckch->cert != NULL && ckch->key != NULL); +} +#endif /* ckch_store functions */ struct ckch_store *ckchs_load_cert_file(char *path, int multi, char **err); struct ckch_store *ckchs_lookup(char *path); +struct ckch_store *ckchs_dup(const struct ckch_store *src); +struct ckch_store *ckch_store_new(const char *filename, int nmemb); +void ckch_store_free(struct ckch_store *store); + /* ckch_inst functions */ void ckch_inst_free(struct ckch_inst *inst); diff --git a/include/proto/ssl_sock.h b/include/proto/ssl_sock.h index 1fa3973a8..5d17b2b52 100644 --- a/include/proto/ssl_sock.h +++ b/include/proto/ssl_sock.h @@ -34,6 +34,8 @@ extern int sslconns; extern int totalsslconns; +extern struct eb_root ckchs_tree; +extern int sctl_ex_index; extern struct global_ssl global_ssl; extern struct ssl_bind_kw ssl_bind_kws[]; diff --git a/include/types/ssl_ckch.h b/include/types/ssl_ckch.h index 3a49b40f1..db59bb7ee 100644 --- a/include/types/ssl_ckch.h +++ b/include/types/ssl_ckch.h @@ -71,6 +71,10 @@ struct ckch_store { char path[0]; }; +/* forward declarations for ckch_inst */ +struct ssl_bind_conf; +struct crtlist_entry; + /* * This structure describe a ckch instance. An instance is generated for each * bind_conf. The instance contains a linked list of the sni ctx which uses diff --git a/src/ssl_ckch.c b/src/ssl_ckch.c new file mode 100644 index 000000000..1a5de31ac --- /dev/null +++ b/src/ssl_ckch.c @@ -0,0 +1,873 @@ +/* + * + * Copyright (C) 2020 HAProxy Technologies, William Lallemand + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#include + +/******************** 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 if not NULL, or read the file + * 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) { + tmp.area = buf; + tmp.data = strlen(buf); + tmp.size = tmp.data + 1; + 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)) { + free(sctl); + sctl = NULL; + goto end; + } + /* no error, fill ckch with new context, old context must be free */ + if (ckch->sctl) { + free(ckch->sctl->area); + ckch->sctl->area = NULL; + 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 Resonse in DER format contained in file at + * path 'ocsp_path' or base64 in a buffer + * + * 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)) { + free(ocsp_response); + ocsp_response = NULL; + goto end; + } + /* no error, fill ckch with new context, old context must be free */ + if (ckch->ocsp_response) { + free(ckch->ocsp_response->area); + ckch->ocsp_response->area = NULL; + 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) +{ + int ret = 1; + + /* try to load the PEM */ + if (ssl_sock_load_pem_into_ckch(path, NULL, ckch , err) != 0) { + goto end; + } + + /* try to load an external private key if it wasn't in the PEM */ + if ((ckch->key == NULL) && (global_ssl.extra_files & SSL_GF_KEY)) { + char fp[MAXPATHLEN+1]; + struct stat st; + + snprintf(fp, MAXPATHLEN+1, "%s.key", path); + if (stat(fp, &st) == 0) { + if (ssl_sock_load_key_into_ckch(fp, NULL, ckch, err)) { + memprintf(err, "%s '%s' is present but cannot be read or parsed'.\n", + err && *err ? *err : "", fp); + goto end; + } + } + } + + if (ckch->key == NULL) { + memprintf(err, "%sNo Private Key found in '%s' or '%s.key'.\n", err && *err ? *err : "", path, path); + goto end; + } + + 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; + } + +#if (HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL) + /* try to load the sctl file */ + if (global_ssl.extra_files & SSL_GF_SCTL) { + char fp[MAXPATHLEN+1]; + struct stat st; + + snprintf(fp, MAXPATHLEN+1, "%s.sctl", path); + if (stat(fp, &st) == 0) { + if (ssl_sock_load_sctl_from_file(fp, NULL, ckch, err)) { + memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n", + err && *err ? *err : "", fp); + ret = 1; + goto end; + } + } + } +#endif + + /* try to load an ocsp response file */ + if (global_ssl.extra_files & SSL_GF_OCSP) { + char fp[MAXPATHLEN+1]; + struct stat st; + + snprintf(fp, MAXPATHLEN+1, "%s.ocsp", path); + if (stat(fp, &st) == 0) { + if (ssl_sock_load_ocsp_response_from_file(fp, NULL, ckch, err)) { + ret = 1; + goto end; + } + } + } + +#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; + char fp[MAXPATHLEN+1]; + + snprintf(fp, MAXPATHLEN+1, "%s.issuer", path); + if (stat(fp, &st) == 0) { + if (ssl_sock_load_issuer_file_into_ckch(fp, 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); + ret = 1; + goto end; + } + } + } + } +#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); + + return ret; +} + +/* + * Try to load a private key file from a or a buffer + * + * 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 or a buffer + * 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; + 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 = PEM_read_bio_DHparams(in, NULL, NULL, NULL); + /* 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) { + free(ckch->ocsp_response->area); + ckch->ocsp_response->area = NULL; + free(ckch->ocsp_response); + ckch->ocsp_response = NULL; + } + + if (ckch->sctl) { + free(ckch->sctl->area); + ckch->sctl->area = NULL; + free(ckch->sctl); + ckch->sctl = NULL; + } + + 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) + 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) + DH_free(ckch->dh); + ckch->dh = NULL; + + if (ckch->sctl) { + free(ckch->sctl->area); + ckch->sctl->area = NULL; + free(ckch->sctl); + ckch->sctl = NULL; + } + + if (ckch->ocsp_response) { + free(ckch->ocsp_response->area); + ckch->ocsp_response->area = NULL; + free(ckch->ocsp_response); + ckch->ocsp_response = NULL; + } + + 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->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) { + 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)) { + free(sctl); + sctl = NULL; + 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)) { + free(ocsp_response); + ocsp_response = NULL; + 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; + +#if HA_OPENSSL_VERSION_NUMBER >= 0x1000200L + if (store->multi) { + int n; + + for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) + ssl_sock_free_cert_key_and_chain_contents(&store->ckch[n]); + } else +#endif + { + ssl_sock_free_cert_key_and_chain_contents(store->ckch); + } + + free(store->ckch); + store->ckch = NULL; + + 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 + * is the key name + * 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, int nmemb) +{ + struct ckch_store *store; + int pathlen; + + pathlen = strlen(filename); + store = calloc(1, sizeof(*store) + pathlen + 1); + if (!store) + return NULL; + + if (nmemb > 1) + store->multi = 1; + else + store->multi = 0; + + memcpy(store->path, filename, pathlen + 1); + + LIST_INIT(&store->ckch_inst); + LIST_INIT(&store->crtlist_entry); + + store->ckch = calloc(nmemb, 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; + + dst = ckch_store_new(src->path, src->multi ? SSL_SOCK_NUM_KEYTYPES : 1); + +#if HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL + if (src->multi) { + int n; + + for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) { + if (&src->ckch[n]) { + if (!ssl_sock_copy_cert_key_and_chain(&src->ckch[n], &dst->ckch[n])) + goto error; + } + } + } else +#endif + { + 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, int multi, char **err) +{ + struct ckch_store *ckchs; + + ckchs = ckch_store_new(path, multi ? SSL_SOCK_NUM_KEYTYPES : 1); + if (!ckchs) { + memprintf(err, "%sunable to allocate memory.\n", err && *err ? *err : ""); + goto end; + } + if (!multi) { + + 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); + } else { + int found = 0; +#if HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL + char fp[MAXPATHLEN+1] = {0}; + int n = 0; + + /* Load all possible certs and keys */ + for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) { + struct stat buf; + snprintf(fp, sizeof(fp), "%s.%s", path, SSL_SOCK_KEYTYPE_NAMES[n]); + if (stat(fp, &buf) == 0) { + if (ssl_sock_load_files_into_ckch(fp, &ckchs->ckch[n], err) == 1) + goto end; + found = 1; + ckchs->multi = 1; + } + } +#endif + + if (!found) { + memprintf(err, "%sDidn't find any certificate for bundle '%s'.\n", err && *err ? *err : "", path); + 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; +} + diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 266b7d5dd..792fca860 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -1032,89 +1032,6 @@ int ssl_sock_update_ocsp_response(struct buffer *ocsp_response, char **err) #endif -#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL) -/* - * This function load the OCSP Resonse in DER format contained in file at - * path 'ocsp_path' or base64 in a buffer - * - * Returns 0 on success, 1 in error case. - */ -static 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)) { - free(ocsp_response); - ocsp_response = NULL; - goto end; - } - /* no error, fill ckch with new context, old context must be free */ - if (ckch->ocsp_response) { - free(ckch->ocsp_response->area); - ckch->ocsp_response->area = NULL; - free(ckch->ocsp_response); - } - ckch->ocsp_response = ocsp_response; - ret = 0; -end: - if (fd != -1) - close(fd); - - return ret; -} -#endif - #if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0) static int ssl_tlsext_ticket_key_cb(SSL *s, unsigned char key_name[16], unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc) { @@ -1521,109 +1438,7 @@ static int ssl_sock_load_ocsp(SSL_CTX *ctx, const struct cert_key_and_chain *ckc #define CT_EXTENSION_TYPE 18 -static int sctl_ex_index = -1; - -/* - * 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 if not NULL, or read the file - * It fills the ckch->sctl buffer - * return 0 on success or != 0 on failure */ -static 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) { - tmp.area = buf; - tmp.data = strlen(buf); - tmp.size = tmp.data + 1; - 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)) { - free(sctl); - sctl = NULL; - goto end; - } - /* no error, fill ckch with new context, old context must be free */ - if (ckch->sctl) { - free(ckch->sctl->area); - ckch->sctl->area = NULL; - free(ckch->sctl); - } - ckch->sctl = sctl; - ret = 0; -end: - if (fd != -1) - close(fd); - - return ret; -} +int sctl_ex_index = -1; int ssl_sock_sctl_add_cbk(SSL *ssl, unsigned ext_type, const unsigned char **out, size_t *outlen, int *al, void *add_arg) { @@ -3125,489 +2940,6 @@ end: } #endif -/* Frees the contents of a cert_key_and_chain - */ -static 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) - DH_free(ckch->dh); - ckch->dh = NULL; - - if (ckch->sctl) { - free(ckch->sctl->area); - ckch->sctl->area = NULL; - free(ckch->sctl); - ckch->sctl = NULL; - } - - if (ckch->ocsp_response) { - free(ckch->ocsp_response->area); - ckch->ocsp_response->area = NULL; - free(ckch->ocsp_response); - ckch->ocsp_response = NULL; - } - - 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 - */ -static 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->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) { - 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)) { - free(sctl); - sctl = NULL; - 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)) { - free(ocsp_response); - ocsp_response = NULL; - 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; -} - - -/* checks if a key and cert exists in the ckch - */ -#if HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL -static int ssl_sock_is_ckch_valid(struct cert_key_and_chain *ckch) -{ - return (ckch->cert != NULL && ckch->key != NULL); -} -#endif - -/* - * return 0 on success or != 0 on failure - */ -static 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; -} - - -/* - * Try to load a PEM file from a or a buffer - * 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 - */ -static 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; - 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 = PEM_read_bio_DHparams(in, NULL, NULL, NULL); - /* 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) { - free(ckch->ocsp_response->area); - ckch->ocsp_response->area = NULL; - free(ckch->ocsp_response); - ckch->ocsp_response = NULL; - } - - if (ckch->sctl) { - free(ckch->sctl->area); - ckch->sctl->area = NULL; - free(ckch->sctl); - ckch->sctl = NULL; - } - - 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) - DH_free(dh); - if (cert) - X509_free(cert); - if (chain) - sk_X509_pop_free(chain, X509_free); - - return ret; -} - -/* - * Try to load a private key file from a or a buffer - * - * If it failed you should not attempt to use the ckch but free it. - * - * Return 0 on success or != 0 on failure - */ -static 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 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 - */ -static int ssl_sock_load_files_into_ckch(const char *path, struct cert_key_and_chain *ckch, char **err) -{ - int ret = 1; - - /* try to load the PEM */ - if (ssl_sock_load_pem_into_ckch(path, NULL, ckch , err) != 0) { - goto end; - } - - /* try to load an external private key if it wasn't in the PEM */ - if ((ckch->key == NULL) && (global_ssl.extra_files & SSL_GF_KEY)) { - char fp[MAXPATHLEN+1]; - struct stat st; - - snprintf(fp, MAXPATHLEN+1, "%s.key", path); - if (stat(fp, &st) == 0) { - if (ssl_sock_load_key_into_ckch(fp, NULL, ckch, err)) { - memprintf(err, "%s '%s' is present but cannot be read or parsed'.\n", - err && *err ? *err : "", fp); - goto end; - } - } - } - - if (ckch->key == NULL) { - memprintf(err, "%sNo Private Key found in '%s' or '%s.key'.\n", err && *err ? *err : "", path, path); - goto end; - } - - 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; - } - -#if (HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL) - /* try to load the sctl file */ - if (global_ssl.extra_files & SSL_GF_SCTL) { - char fp[MAXPATHLEN+1]; - struct stat st; - - snprintf(fp, MAXPATHLEN+1, "%s.sctl", path); - if (stat(fp, &st) == 0) { - if (ssl_sock_load_sctl_from_file(fp, NULL, ckch, err)) { - memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n", - err && *err ? *err : "", fp); - ret = 1; - goto end; - } - } - } -#endif - - /* try to load an ocsp response file */ - if (global_ssl.extra_files & SSL_GF_OCSP) { - char fp[MAXPATHLEN+1]; - struct stat st; - - snprintf(fp, MAXPATHLEN+1, "%s.ocsp", path); - if (stat(fp, &st) == 0) { - if (ssl_sock_load_ocsp_response_from_file(fp, NULL, ckch, err)) { - ret = 1; - goto end; - } - } - } - -#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; - char fp[MAXPATHLEN+1]; - - snprintf(fp, MAXPATHLEN+1, "%s.issuer", path); - if (stat(fp, &st) == 0) { - if (ssl_sock_load_issuer_file_into_ckch(fp, 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); - ret = 1; - goto end; - } - } - } - } -#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); - - return ret; -} - /* Loads the info in ckch into ctx * Returns a bitfield containing the flags: * ERR_FATAL in any fatal error case @@ -3752,176 +3084,6 @@ static int ssl_sock_populate_sni_keytypes_hplr(const char *str, struct eb_root * } #endif -/* - * Free a ckch_store, its ckch, its instances and remove it from the ebtree - */ -static void ckch_store_free(struct ckch_store *store) -{ - struct ckch_inst *inst, *inst_s; - - if (!store) - return; - -#if HA_OPENSSL_VERSION_NUMBER >= 0x1000200L - if (store->multi) { - int n; - - for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) - ssl_sock_free_cert_key_and_chain_contents(&store->ckch[n]); - } else -#endif - { - ssl_sock_free_cert_key_and_chain_contents(store->ckch); - } - - free(store->ckch); - store->ckch = NULL; - - 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 - * is the key name - * is the number of store->ckch objects to allocate - * - * Return a ckch_store or NULL upon failure. - */ -static struct ckch_store *ckch_store_new(const char *filename, int nmemb) -{ - struct ckch_store *store; - int pathlen; - - pathlen = strlen(filename); - store = calloc(1, sizeof(*store) + pathlen + 1); - if (!store) - return NULL; - - if (nmemb > 1) - store->multi = 1; - else - store->multi = 0; - - memcpy(store->path, filename, pathlen + 1); - - LIST_INIT(&store->ckch_inst); - LIST_INIT(&store->crtlist_entry); - - store->ckch = calloc(nmemb, 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 */ -static struct ckch_store *ckchs_dup(const struct ckch_store *src) -{ - struct ckch_store *dst; - - dst = ckch_store_new(src->path, src->multi ? SSL_SOCK_NUM_KEYTYPES : 1); - -#if HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL - if (src->multi) { - int n; - - for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) { - if (&src->ckch[n]) { - if (!ssl_sock_copy_cert_key_and_chain(&src->ckch[n], &dst->ckch[n])) - goto error; - } - } - } else -#endif - { - 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, int multi, char **err) -{ - struct ckch_store *ckchs; - - ckchs = ckch_store_new(path, multi ? SSL_SOCK_NUM_KEYTYPES : 1); - if (!ckchs) { - memprintf(err, "%sunable to allocate memory.\n", err && *err ? *err : ""); - goto end; - } - if (!multi) { - - 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); - } else { - int found = 0; -#if HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL - char fp[MAXPATHLEN+1] = {0}; - int n = 0; - - /* Load all possible certs and keys */ - for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) { - struct stat buf; - snprintf(fp, sizeof(fp), "%s.%s", path, SSL_SOCK_KEYTYPE_NAMES[n]); - if (stat(fp, &buf) == 0) { - if (ssl_sock_load_files_into_ckch(fp, &ckchs->ckch[n], err) == 1) - goto end; - found = 1; - ckchs->multi = 1; - } - } -#endif - - if (!found) { - memprintf(err, "%sDidn't find any certificate for bundle '%s'.\n", err && *err ? *err : "", path); - 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; -} #if HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL