And also rename standard.c to tools.c. The original split between tools.h and standard.h dates from version 1.3-dev and was mostly an accident. This patch moves the files back to what they were expected to be, and takes care of not changing anything else. However this time tools.h was split between functions and types, because it contains a small number of commonly used macros and structures (e.g. name_desc) which in turn cause the massive list of includes of tools.h to conflict with the callers. They remain the ugliest files of the whole project and definitely need to be cleaned and split apart. A few types are defined there only for functions provided there, and some parts are even OS-specific and should move somewhere else, such as the symbol resolution code.
1910 lines
50 KiB
C
1910 lines
50 KiB
C
/*
|
|
*
|
|
* 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 <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <haproxy/base64.h>
|
|
#include <haproxy/errors.h>
|
|
#include <haproxy/tools.h>
|
|
|
|
#include <import/ebsttree.h>
|
|
|
|
#include <types/cli.h>
|
|
#include <types/ssl_ckch.h>
|
|
#include <types/ssl_sock.h>
|
|
|
|
#include <proto/cli.h>
|
|
#include <proto/channel.h>
|
|
#include <proto/ssl_ckch.h>
|
|
#include <proto/ssl_sock.h>
|
|
#include <proto/ssl_utils.h>
|
|
#include <proto/stream_interface.h>
|
|
|
|
/* Uncommitted CKCH transaction */
|
|
|
|
static struct {
|
|
struct ckch_store *new_ckchs;
|
|
struct ckch_store *old_ckchs;
|
|
char *path;
|
|
} ckchs_transaction;
|
|
|
|
|
|
|
|
/******************** 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) {
|
|
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 <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)) {
|
|
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 <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;
|
|
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
|
|
* <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, 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;
|
|
}
|
|
|
|
|
|
/******************** 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;
|
|
|
|
if (inst == NULL)
|
|
return;
|
|
|
|
list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
|
|
SSL_CTX_free(sni->ctx);
|
|
LIST_DEL(&sni->by_ckch_inst);
|
|
ebmb_delete(&sni->name);
|
|
free(sni);
|
|
}
|
|
LIST_DEL(&inst->by_ckchs);
|
|
LIST_DEL(&inst->by_crtlist_entry);
|
|
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);
|
|
|
|
return ckch_inst;
|
|
}
|
|
|
|
/*************************** CLI commands ***********************/
|
|
|
|
/* Type of SSL payloads that can be updated over the CLI */
|
|
|
|
enum {
|
|
CERT_TYPE_PEM = 0,
|
|
CERT_TYPE_KEY,
|
|
#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
|
|
CERT_TYPE_OCSP,
|
|
#endif
|
|
CERT_TYPE_ISSUER,
|
|
#if (HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL)
|
|
CERT_TYPE_SCTL,
|
|
#endif
|
|
CERT_TYPE_MAX,
|
|
};
|
|
|
|
struct {
|
|
const char *ext;
|
|
int type;
|
|
int (*load)(const char *path, char *payload, struct cert_key_and_chain *ckch, char **err);
|
|
/* add a parsing callback */
|
|
} cert_exts[CERT_TYPE_MAX+1] = {
|
|
[CERT_TYPE_PEM] = { "", CERT_TYPE_PEM, &ssl_sock_load_pem_into_ckch }, /* default mode, no extensions */
|
|
[CERT_TYPE_KEY] = { "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)
|
|
[CERT_TYPE_OCSP] = { "ocsp", CERT_TYPE_OCSP, &ssl_sock_load_ocsp_response_from_file },
|
|
#endif
|
|
#if (HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL)
|
|
[CERT_TYPE_SCTL] = { "sctl", CERT_TYPE_SCTL, &ssl_sock_load_sctl_from_file },
|
|
#endif
|
|
[CERT_TYPE_ISSUER] = { "issuer", CERT_TYPE_ISSUER, &ssl_sock_load_issuer_file_into_ckch },
|
|
[CERT_TYPE_MAX] = { 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>" */
|
|
static int cli_io_handler_show_cert(struct appctx *appctx)
|
|
{
|
|
struct buffer *trash = alloc_trash_chunk();
|
|
struct ebmb_node *node;
|
|
struct stream_interface *si = appctx->owner;
|
|
struct ckch_store *ckchs;
|
|
|
|
if (trash == NULL)
|
|
return 1;
|
|
|
|
if (!appctx->ctx.ssl.old_ckchs) {
|
|
if (ckchs_transaction.old_ckchs) {
|
|
ckchs = ckchs_transaction.old_ckchs;
|
|
chunk_appendf(trash, "# transaction\n");
|
|
if (!ckchs->multi) {
|
|
chunk_appendf(trash, "*%s\n", ckchs->path);
|
|
#if HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL
|
|
} else {
|
|
int n;
|
|
|
|
chunk_appendf(trash, "*%s:", ckchs->path);
|
|
for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
|
|
if (ckchs->ckch[n].cert)
|
|
chunk_appendf(trash, " %s.%s\n", ckchs->path, SSL_SOCK_KEYTYPE_NAMES[n]);
|
|
}
|
|
chunk_appendf(trash, "\n");
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!appctx->ctx.cli.p0) {
|
|
chunk_appendf(trash, "# filename\n");
|
|
node = ebmb_first(&ckchs_tree);
|
|
} else {
|
|
node = &((struct ckch_store *)appctx->ctx.cli.p0)->node;
|
|
}
|
|
while (node) {
|
|
ckchs = ebmb_entry(node, struct ckch_store, node);
|
|
if (!ckchs->multi) {
|
|
chunk_appendf(trash, "%s\n", ckchs->path);
|
|
#if HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL
|
|
} else {
|
|
int n;
|
|
|
|
chunk_appendf(trash, "%s:", ckchs->path);
|
|
for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
|
|
if (ckchs->ckch[n].cert)
|
|
chunk_appendf(trash, " %s.%s", ckchs->path, SSL_SOCK_KEYTYPE_NAMES[n]);
|
|
}
|
|
chunk_appendf(trash, "\n");
|
|
#endif
|
|
}
|
|
|
|
node = ebmb_next(node);
|
|
if (ci_putchk(si_ic(si), trash) == -1) {
|
|
si_rx_room_blk(si);
|
|
goto yield;
|
|
}
|
|
}
|
|
|
|
appctx->ctx.cli.p0 = NULL;
|
|
free_trash_chunk(trash);
|
|
return 1;
|
|
yield:
|
|
|
|
free_trash_chunk(trash);
|
|
appctx->ctx.cli.p0 = 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
|
|
|
|
|
|
|
|
|
|
/* IO handler of the details "show ssl cert <filename>" */
|
|
static int cli_io_handler_show_cert_detail(struct appctx *appctx)
|
|
{
|
|
struct stream_interface *si = appctx->owner;
|
|
struct ckch_store *ckchs = appctx->ctx.cli.p0;
|
|
struct buffer *out = alloc_trash_chunk();
|
|
struct buffer *tmp = alloc_trash_chunk();
|
|
X509_NAME *name = NULL;
|
|
STACK_OF(X509) *chain;
|
|
unsigned int len = 0;
|
|
int write = -1;
|
|
BIO *bio = NULL;
|
|
int i;
|
|
|
|
if (!tmp || !out)
|
|
goto end_no_putchk;
|
|
|
|
if (!ckchs->multi) {
|
|
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");
|
|
|
|
if (ckchs->ckch->cert == NULL)
|
|
goto end;
|
|
|
|
chain = ckchs->ckch->chain;
|
|
if (chain == NULL) {
|
|
struct issuer_chain *issuer;
|
|
issuer = ssl_get0_issuer_chain(ckchs->ckch->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(ckchs->ckch->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(ckchs->ckch->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(ckchs->ckch->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(ckchs->ckch->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(ckchs->ckch->cert, tmp) == 0)
|
|
goto end;
|
|
chunk_appendf(out, "%s\n", tmp->area);
|
|
|
|
chunk_reset(tmp);
|
|
chunk_appendf(out, "SHA1 FingerPrint: ");
|
|
if (X509_digest(ckchs->ckch->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(ckchs->ckch->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(ckchs->ckch->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 (ci_putchk(si_ic(si), out) == -1) {
|
|
si_rx_room_blk(si);
|
|
goto yield;
|
|
}
|
|
|
|
end_no_putchk:
|
|
if (bio)
|
|
BIO_free(bio);
|
|
free_trash_chunk(tmp);
|
|
free_trash_chunk(out);
|
|
return 1;
|
|
yield:
|
|
free_trash_chunk(tmp);
|
|
free_trash_chunk(out);
|
|
return 0; /* should come back */
|
|
}
|
|
|
|
/* parsing function for 'show ssl cert [certfile]' */
|
|
static int cli_parse_show_cert(char **args, char *payload, struct appctx *appctx, void *private)
|
|
{
|
|
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]) {
|
|
if (*args[3] == '*') {
|
|
if (!ckchs_transaction.new_ckchs)
|
|
goto error;
|
|
|
|
ckchs = ckchs_transaction.new_ckchs;
|
|
|
|
if (strcmp(args[3] + 1, ckchs->path))
|
|
goto error;
|
|
|
|
} else {
|
|
if ((ckchs = ckchs_lookup(args[3])) == NULL)
|
|
goto error;
|
|
|
|
}
|
|
|
|
if (ckchs->multi)
|
|
goto error;
|
|
|
|
appctx->ctx.cli.p0 = ckchs;
|
|
/* use the IO handler that shows details */
|
|
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 ckch_store *new_ckchs;
|
|
|
|
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
|
|
|
|
if (appctx->st2 != SETCERT_ST_FIN) {
|
|
/* free every new sni_ctx and the new store, which are not in the trees so no spinlock there */
|
|
new_ckchs = appctx->ctx.ssl.new_ckchs;
|
|
|
|
/* if the allocation failed, we need to free everything from the temporary list */
|
|
ckch_store_free(new_ckchs);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function tries to create the new ckch_inst and their SNIs
|
|
*/
|
|
static int cli_io_handler_commit_cert(struct appctx *appctx)
|
|
{
|
|
struct stream_interface *si = appctx->owner;
|
|
int y = 0;
|
|
char *err = NULL;
|
|
int errcode = 0;
|
|
struct ckch_store *old_ckchs, *new_ckchs = NULL;
|
|
struct ckch_inst *ckchi, *ckchis;
|
|
struct buffer *trash = alloc_trash_chunk();
|
|
struct sni_ctx *sc0, *sc0s;
|
|
struct crtlist_entry *entry;
|
|
|
|
if (trash == NULL)
|
|
goto error;
|
|
|
|
if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
|
|
goto error;
|
|
|
|
while (1) {
|
|
switch (appctx->st2) {
|
|
case SETCERT_ST_INIT:
|
|
/* This state just print the update message */
|
|
chunk_printf(trash, "Committing %s", ckchs_transaction.path);
|
|
if (ci_putchk(si_ic(si), trash) == -1) {
|
|
si_rx_room_blk(si);
|
|
goto yield;
|
|
}
|
|
appctx->st2 = SETCERT_ST_GEN;
|
|
/* fallthrough */
|
|
case SETCERT_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 = appctx->ctx.ssl.old_ckchs;
|
|
new_ckchs = appctx->ctx.ssl.new_ckchs;
|
|
|
|
if (!new_ckchs)
|
|
continue;
|
|
|
|
/* get the next ckchi to regenerate */
|
|
ckchi = appctx->ctx.ssl.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;
|
|
char **sni_filter = NULL;
|
|
int fcount = 0;
|
|
|
|
/* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
|
|
if (y >= 10) {
|
|
/* save the next ckchi to compute */
|
|
appctx->ctx.ssl.next_ckchi = ckchi;
|
|
goto yield;
|
|
}
|
|
|
|
if (ckchi->crtlist_entry) {
|
|
sni_filter = ckchi->crtlist_entry->filters;
|
|
fcount = ckchi->crtlist_entry->fcount;
|
|
}
|
|
|
|
if (new_ckchs->multi)
|
|
errcode |= ckch_inst_new_load_multi_store(new_ckchs->path, new_ckchs, ckchi->bind_conf, ckchi->ssl_conf, sni_filter, fcount, &new_inst, &err);
|
|
else
|
|
errcode |= ckch_inst_new_load_store(new_ckchs->path, new_ckchs, ckchi->bind_conf, ckchi->ssl_conf, sni_filter, fcount, &new_inst, &err);
|
|
|
|
if (errcode & ERR_CODE)
|
|
goto error;
|
|
|
|
/* if the previous ckchi was used as the default */
|
|
if (ckchi->is_default)
|
|
new_inst->is_default = 1;
|
|
|
|
/* 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_prepare_ctx(ckchi->bind_conf, ckchi->ssl_conf, sc0->ctx, &err);
|
|
if (errcode & ERR_CODE)
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
|
|
/* display one dot per new instance */
|
|
chunk_appendf(trash, ".");
|
|
/* link the new ckch_inst to the duplicate */
|
|
LIST_ADDQ(&new_ckchs->ckch_inst, &new_inst->by_ckchs);
|
|
y++;
|
|
}
|
|
appctx->st2 = SETCERT_ST_INSERT;
|
|
/* fallthrough */
|
|
case SETCERT_ST_INSERT:
|
|
/* The generation is finished, we can insert everything */
|
|
|
|
old_ckchs = appctx->ctx.ssl.old_ckchs;
|
|
new_ckchs = appctx->ctx.ssl.new_ckchs;
|
|
|
|
if (!new_ckchs)
|
|
continue;
|
|
|
|
/* get the list of crtlist_entry in the old store, and update the pointers to the store */
|
|
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);
|
|
}
|
|
|
|
/* 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) {
|
|
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 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) {
|
|
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 the old ckchs by the new one */
|
|
ckch_store_free(old_ckchs);
|
|
ebst_insert(&ckchs_tree, &new_ckchs->node);
|
|
appctx->st2 = SETCERT_ST_FIN;
|
|
/* fallthrough */
|
|
case SETCERT_ST_FIN:
|
|
/* we achieved the transaction, we can set everything to NULL */
|
|
free(ckchs_transaction.path);
|
|
ckchs_transaction.path = NULL;
|
|
ckchs_transaction.new_ckchs = NULL;
|
|
ckchs_transaction.old_ckchs = NULL;
|
|
goto end;
|
|
}
|
|
}
|
|
end:
|
|
|
|
chunk_appendf(trash, "\n");
|
|
if (errcode & ERR_WARN)
|
|
chunk_appendf(trash, "%s", err);
|
|
chunk_appendf(trash, "Success!\n");
|
|
if (ci_putchk(si_ic(si), trash) == -1)
|
|
si_rx_room_blk(si);
|
|
free_trash_chunk(trash);
|
|
/* success: call the release function and don't come back */
|
|
return 1;
|
|
yield:
|
|
/* store the state */
|
|
if (ci_putchk(si_ic(si), trash) == -1)
|
|
si_rx_room_blk(si);
|
|
free_trash_chunk(trash);
|
|
si_rx_endp_more(si); /* let's come back later */
|
|
return 0; /* should come back */
|
|
|
|
error:
|
|
/* spin unlock and free are done in the release function */
|
|
if (trash) {
|
|
chunk_appendf(trash, "\n%sFailed!\n", err);
|
|
if (ci_putchk(si_ic(si), trash) == -1)
|
|
si_rx_room_blk(si);
|
|
free_trash_chunk(trash);
|
|
}
|
|
/* error: call the release function and don't come back */
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Parsing function of 'commit ssl cert'
|
|
*/
|
|
static int cli_parse_commit_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, "'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 HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL
|
|
if (ckchs_transaction.new_ckchs->multi) {
|
|
int n;
|
|
|
|
for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
|
|
if (ckchs_transaction.new_ckchs->ckch[n].cert && !X509_check_private_key(ckchs_transaction.new_ckchs->ckch[n].cert, ckchs_transaction.new_ckchs->ckch[n].key)) {
|
|
memprintf(&err, "inconsistencies between private key and certificate loaded '%s'.\n", ckchs_transaction.path);
|
|
goto error;
|
|
}
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
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 */
|
|
appctx->st2 = SETCERT_ST_INIT;
|
|
appctx->ctx.ssl.next_ckchi = NULL;
|
|
appctx->ctx.ssl.new_ckchs = ckchs_transaction.new_ckchs;
|
|
appctx->ctx.ssl.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.
|
|
*/
|
|
static int cli_parse_set_cert(char **args, char *payload, struct appctx *appctx, void *private)
|
|
{
|
|
struct ckch_store *new_ckchs = NULL;
|
|
struct ckch_store *old_ckchs = NULL;
|
|
char *err = NULL;
|
|
int i;
|
|
int bundle = -1; /* TRUE if >= 0 (ckch index) */
|
|
int errcode = 0;
|
|
char *end;
|
|
int type = CERT_TYPE_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)
|
|
return cli_err(appctx, "Can't allocate memory\n");
|
|
|
|
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].type < CERT_TYPE_MAX; i++) {
|
|
end = strrchr(buf->area, '.');
|
|
if (end && *cert_exts[i].ext && (!strcmp(end + 1, cert_exts[i].ext))) {
|
|
*end = '\0';
|
|
type = cert_exts[i].type;
|
|
break;
|
|
}
|
|
}
|
|
|
|
appctx->ctx.ssl.old_ckchs = NULL;
|
|
appctx->ctx.ssl.new_ckchs = NULL;
|
|
|
|
/* if there is an ongoing transaction */
|
|
if (ckchs_transaction.path) {
|
|
/* if the ongoing transaction is a bundle, we need to find which part of the bundle need to be updated */
|
|
#if HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL
|
|
if (ckchs_transaction.new_ckchs->multi) {
|
|
char *end;
|
|
int j;
|
|
|
|
/* check if it was used in a bundle by removing the
|
|
* .dsa/.rsa/.ecdsa at the end of the filename */
|
|
end = strrchr(buf->area, '.');
|
|
for (j = 0; end && j < SSL_SOCK_NUM_KEYTYPES; j++) {
|
|
if (!strcmp(end + 1, SSL_SOCK_KEYTYPE_NAMES[j])) {
|
|
bundle = j; /* keep the type of certificate so we insert it at the right place */
|
|
*end = '\0'; /* it's a bundle let's end the string*/
|
|
break;
|
|
}
|
|
}
|
|
if (bundle < 0) {
|
|
memprintf(&err, "The ongoing transaction is the '%s' bundle. You need to specify which part of the bundle you want to update ('%s.{rsa,ecdsa,dsa}')\n", ckchs_transaction.path, buf->area);
|
|
errcode |= ERR_ALERT | ERR_FATAL;
|
|
goto end;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* if there is an ongoing transaction, check if this is the same file */
|
|
if (strcmp(ckchs_transaction.path, buf->area) != 0) {
|
|
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;
|
|
}
|
|
|
|
appctx->ctx.ssl.old_ckchs = ckchs_transaction.new_ckchs;
|
|
|
|
} else {
|
|
struct ckch_store *find_ckchs[2] = { NULL, NULL };
|
|
|
|
/* lookup for the certificate in the tree:
|
|
* check if this is used as a bundle AND as a unique certificate */
|
|
for (i = 0; i < 2; i++) {
|
|
|
|
if ((find_ckchs[i] = ckchs_lookup(buf->area)) != NULL) {
|
|
/* only the bundle name is in the tree and you should
|
|
* never update a bundle name, only a filename */
|
|
if (bundle < 0 && find_ckchs[i]->multi) {
|
|
/* we tried to look for a non-bundle and we found a bundle */
|
|
memprintf(&err, "%s%s is a multi-cert bundle. Try updating %s.{dsa,rsa,ecdsa}\n",
|
|
err ? err : "", args[3], args[3]);
|
|
errcode |= ERR_ALERT | ERR_FATAL;
|
|
goto end;
|
|
}
|
|
/* If we want a bundle but this is not a bundle
|
|
* example: When you try to update <file>.rsa, but
|
|
* <file> is a regular file */
|
|
if (bundle >= 0 && find_ckchs[i]->multi == 0) {
|
|
find_ckchs[i] = NULL;
|
|
break;
|
|
}
|
|
}
|
|
#if HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL
|
|
{
|
|
char *end;
|
|
int j;
|
|
|
|
/* check if it was used in a bundle by removing the
|
|
* .dsa/.rsa/.ecdsa at the end of the filename */
|
|
end = strrchr(buf->area, '.');
|
|
for (j = 0; end && j < SSL_SOCK_NUM_KEYTYPES; j++) {
|
|
if (!strcmp(end + 1, SSL_SOCK_KEYTYPE_NAMES[j])) {
|
|
bundle = j; /* keep the type of certificate so we insert it at the right place */
|
|
*end = '\0'; /* it's a bundle let's end the string*/
|
|
break;
|
|
}
|
|
}
|
|
if (bundle < 0) /* we didn't find a bundle extension */
|
|
break;
|
|
}
|
|
#else
|
|
/* bundles are not supported here, so we don't need to lookup again */
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
if (find_ckchs[0] && find_ckchs[1]) {
|
|
memprintf(&err, "%sUpdating a certificate which is used in the HAProxy configuration as a bundle and as a unique certificate is not supported. ('%s' and '%s')\n",
|
|
err ? err : "", find_ckchs[0]->path, find_ckchs[1]->path);
|
|
errcode |= ERR_ALERT | ERR_FATAL;
|
|
goto end;
|
|
}
|
|
|
|
appctx->ctx.ssl.old_ckchs = find_ckchs[0] ? find_ckchs[0] : find_ckchs[1];
|
|
}
|
|
|
|
if (!appctx->ctx.ssl.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;
|
|
}
|
|
|
|
if (!appctx->ctx.ssl.path) {
|
|
/* this is a new transaction, set the path of the transaction */
|
|
appctx->ctx.ssl.path = strdup(appctx->ctx.ssl.old_ckchs->path);
|
|
if (!appctx->ctx.ssl.path) {
|
|
memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
|
|
errcode |= ERR_ALERT | ERR_FATAL;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
old_ckchs = appctx->ctx.ssl.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;
|
|
}
|
|
|
|
if (!new_ckchs->multi)
|
|
ckch = new_ckchs->ckch;
|
|
else
|
|
ckch = &new_ckchs->ckch[bundle];
|
|
|
|
/* appply the change on the duplicate */
|
|
if (cert_exts[type].load(buf->area, payload, ckch, &err) != 0) {
|
|
memprintf(&err, "%sCan't load the payload\n", err ? err : "");
|
|
errcode |= ERR_ALERT | ERR_FATAL;
|
|
goto end;
|
|
}
|
|
|
|
appctx->ctx.ssl.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 = appctx->ctx.ssl.old_ckchs;
|
|
ckchs_transaction.path = appctx->ctx.ssl.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 = appctx->ctx.ssl.new_ckchs;
|
|
|
|
|
|
/* creates the SNI ctxs later in the IO handler */
|
|
|
|
end:
|
|
free_trash_chunk(buf);
|
|
|
|
if (errcode & ERR_CODE) {
|
|
|
|
ckch_store_free(appctx->ctx.ssl.new_ckchs);
|
|
appctx->ctx.ssl.new_ckchs = NULL;
|
|
|
|
appctx->ctx.ssl.old_ckchs = NULL;
|
|
|
|
free(appctx->ctx.ssl.path);
|
|
appctx->ctx.ssl.path = 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;
|
|
ckch_store_free(ckchs_transaction.old_ckchs);
|
|
ckchs_transaction.old_ckchs = NULL;
|
|
free(ckchs_transaction.path);
|
|
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, 1);
|
|
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];
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
/* 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 <certfile>", cli_parse_show_cert, cli_io_handler_show_cert, cli_release_show_cert },
|
|
{ { NULL }, NULL, NULL, NULL }
|
|
}};
|
|
|
|
INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
|
|
|