mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-05-03 16:28:01 +00:00
MINOR: ssl: add "issuers-chain-path" directive.
Certificates loaded with "crt" and "crt-list" commonly share the same intermediate certificate in PEM file. "issuers-chain-path" is a global directive to share intermediate chain certificates in a directory. If certificates chain is not included in certificate PEM file, haproxy will complete chain if issuer match the first certificate of the chain stored via "issuers-chain-path" directive. Such chains will be shared in memory.
This commit is contained in:
parent
23997daf4e
commit
70df7bf19c
@ -601,6 +601,7 @@ The following keywords are supported in the "global" section :
|
||||
- h1-case-adjust-file
|
||||
- insecure-fork-wanted
|
||||
- insecure-setuid-wanted
|
||||
- issuers-chain-path
|
||||
- log
|
||||
- log-tag
|
||||
- log-send-hostname
|
||||
@ -949,6 +950,19 @@ insecure-setuid-wanted
|
||||
explicitly adding this directive in the global section. If enabled, it is
|
||||
possible to turn it back off by prefixing it with the "no" keyword.
|
||||
|
||||
issuers-chain-path <dir>
|
||||
Assigns a directory to load certificate chain for issuer completion. All
|
||||
files must be in PEM format. For certificates loaded with "crt" or "crt-list",
|
||||
if certificate chain is not included in PEM (also commonly known as
|
||||
intermediate certificate), haproxy will complete chain if the issuer of the
|
||||
certificate corresponds to the first certificate of the chain loaded with
|
||||
"issuers-chain-path".
|
||||
A "crt" file with PrivateKey+Certificate+IntermediateCA2+IntermediateCA1
|
||||
could be replaced with PrivateKey+Certificate. HAProxy will complete the
|
||||
chain if a file with IntermediateCA2+IntermediateCA1 is present in
|
||||
"issuers-chain-path" directory. All other certificates with the same issuer
|
||||
will share the chain in memory.
|
||||
|
||||
log <address> [len <length>] [format <format>] [sample <ranges>:<smp_size>]
|
||||
<facility> [max level [min level]]
|
||||
Adds a global syslog server. Several global servers can be defined. They
|
||||
@ -11309,7 +11323,8 @@ crt <cert>
|
||||
associated private keys. This file can be built by concatenating multiple
|
||||
PEM files into one (e.g. cat cert.pem key.pem > combined.pem). If your CA
|
||||
requires an intermediate certificate, this can also be concatenated into this
|
||||
file.
|
||||
file. Intermediate certificate can also be shared in a directory via
|
||||
"issuers-chain-path" directive.
|
||||
|
||||
If the OpenSSL used supports Diffie-Hellman, parameters present in this file
|
||||
are loaded.
|
||||
|
194
src/ssl_sock.c
194
src/ssl_sock.c
@ -153,6 +153,14 @@ enum {
|
||||
SSL_SOCK_VERIFY_NONE = 3,
|
||||
};
|
||||
|
||||
/* issuer chain store with hash of Subject Key Identifier
|
||||
certificate/issuer matching is verify with X509_check_issued
|
||||
*/
|
||||
struct issuer_chain {
|
||||
struct eb64_node node;
|
||||
STACK_OF(X509) *chain;
|
||||
char *path;
|
||||
};
|
||||
|
||||
int sslconns = 0;
|
||||
int totalsslconns = 0;
|
||||
@ -162,6 +170,9 @@ int nb_engines = 0;
|
||||
static struct {
|
||||
char *crt_base; /* base directory path for certificates */
|
||||
char *ca_base; /* base directory path for CAs and CRLs */
|
||||
char *issuers_chain_path; /* from "issuers-chain-path" */
|
||||
struct eb_root cert_issuer_tree; /* issuers tree from "issuers-chain-path" */
|
||||
|
||||
int async; /* whether we use ssl async mode */
|
||||
|
||||
char *listen_default_ciphers;
|
||||
@ -183,6 +194,7 @@ static struct {
|
||||
int capture_cipherlist; /* Size of the cipherlist buffer. */
|
||||
int extra_files; /* which files not defined in the configuration file are we looking for */
|
||||
} global_ssl = {
|
||||
.cert_issuer_tree = EB_ROOT,
|
||||
#ifdef LISTEN_DEFAULT_CIPHERS
|
||||
.listen_default_ciphers = LISTEN_DEFAULT_CIPHERS,
|
||||
#endif
|
||||
@ -3361,7 +3373,25 @@ static int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct cert_
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
/* Find Certificate Chain in global */
|
||||
if (chain == NULL) {
|
||||
AUTHORITY_KEYID *akid;
|
||||
akid = X509_get_ext_d2i(cert, NID_authority_key_identifier, NULL, NULL);
|
||||
if (akid) {
|
||||
struct issuer_chain *issuer;
|
||||
struct eb64_node *node;
|
||||
u64 hk;
|
||||
hk = XXH64(ASN1_STRING_get0_data(akid->keyid), ASN1_STRING_length(akid->keyid), 0);
|
||||
for (node = eb64_lookup(&global_ssl.cert_issuer_tree, hk); node; node = eb64_next(node)) {
|
||||
issuer = container_of(node, typeof(*issuer), node);
|
||||
if (X509_check_issued(sk_X509_value(issuer->chain, 0), cert) == X509_V_OK) {
|
||||
chain = X509_chain_up_ref(issuer->chain);
|
||||
break;
|
||||
}
|
||||
}
|
||||
AUTHORITY_KEYID_free(akid);
|
||||
}
|
||||
}
|
||||
/* no chain */
|
||||
if (chain == NULL) {
|
||||
chain = sk_X509_new_null();
|
||||
@ -9681,6 +9711,165 @@ static int ssl_parse_global_ca_crt_base(char **args, int section_type, struct pr
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* "issuers-chain-path" load chain certificate in global */
|
||||
static int ssl_load_global_issuer_from_BIO(BIO *in, char *fp, char **err)
|
||||
{
|
||||
X509 *ca;
|
||||
X509_NAME *name = NULL;
|
||||
ASN1_OCTET_STRING *skid = NULL;
|
||||
STACK_OF(X509) *chain = NULL;
|
||||
struct issuer_chain *issuer;
|
||||
struct eb64_node *node;
|
||||
char *path;
|
||||
u64 key;
|
||||
int ret = 0;
|
||||
|
||||
while ((ca = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
|
||||
if (chain == NULL) {
|
||||
chain = sk_X509_new_null();
|
||||
skid = X509_get_ext_d2i(ca, NID_subject_key_identifier, NULL, NULL);
|
||||
name = X509_get_subject_name(ca);
|
||||
}
|
||||
if (!sk_X509_push(chain, ca)) {
|
||||
X509_free(ca);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (!chain) {
|
||||
memprintf(err, "unable to load issuers-chain %s : pem certificate not found.\n", fp);
|
||||
goto end;
|
||||
}
|
||||
if (!skid) {
|
||||
memprintf(err, "unable to load issuers-chain %s : SubjectKeyIdentifier not found.\n", fp);
|
||||
goto end;
|
||||
}
|
||||
if (!name) {
|
||||
memprintf(err, "unable to load issuers-chain %s : SubjectName not found.\n", fp);
|
||||
goto end;
|
||||
}
|
||||
key = XXH64(ASN1_STRING_get0_data(skid), ASN1_STRING_length(skid), 0);
|
||||
for (node = eb64_lookup(&global_ssl.cert_issuer_tree, key); node; node = eb64_next(node)) {
|
||||
issuer = container_of(node, typeof(*issuer), node);
|
||||
if (!X509_NAME_cmp(name, X509_get_subject_name(sk_X509_value(issuer->chain, 0)))) {
|
||||
memprintf(err, "duplicate issuers-chain %s: %s already in store\n", fp, issuer->path);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
issuer = calloc(1, sizeof *issuer);
|
||||
path = strdup(fp);
|
||||
if (!issuer || !path) {
|
||||
free(issuer);
|
||||
free(path);
|
||||
goto end;
|
||||
}
|
||||
issuer->node.key = key;
|
||||
issuer->path = path;
|
||||
issuer->chain = chain;
|
||||
chain = NULL;
|
||||
eb64_insert(&global_ssl.cert_issuer_tree, &issuer->node);
|
||||
ret = 1;
|
||||
end:
|
||||
if (skid)
|
||||
ASN1_OCTET_STRING_free(skid);
|
||||
if (chain)
|
||||
sk_X509_pop_free(chain, X509_free);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ssl_free_global_issuers(void)
|
||||
{
|
||||
struct eb64_node *node, *back;
|
||||
struct issuer_chain *issuer;
|
||||
|
||||
node = eb64_first(&global_ssl.cert_issuer_tree);
|
||||
while (node) {
|
||||
issuer = container_of(node, typeof(*issuer), node);
|
||||
back = eb64_next(node);
|
||||
eb64_delete(node);
|
||||
free(issuer->path);
|
||||
sk_X509_pop_free(issuer->chain, X509_free);
|
||||
free(issuer);
|
||||
node = back;
|
||||
}
|
||||
}
|
||||
|
||||
static int ssl_load_global_issuers_from_path(char **args, int section_type, struct proxy *curpx,
|
||||
struct proxy *defpx, const char *file, int line,
|
||||
char **err)
|
||||
{
|
||||
char *path;
|
||||
struct dirent **de_list;
|
||||
int i, n;
|
||||
struct stat buf;
|
||||
char *end;
|
||||
char fp[MAXPATHLEN+1];
|
||||
|
||||
if (too_many_args(1, args, err, NULL))
|
||||
return -1;
|
||||
|
||||
path = args[1];
|
||||
if (*path == 0 || stat(path, &buf)) {
|
||||
memprintf(err, "%sglobal statement '%s' expects a directory path as an argument.\n",
|
||||
err && *err ? *err : "", args[0]);
|
||||
return -1;
|
||||
}
|
||||
if (S_ISDIR(buf.st_mode) == 0) {
|
||||
memprintf(err, "%sglobal statement '%s': %s is not a directory.\n",
|
||||
err && *err ? *err : "", args[0], path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* strip trailing slashes, including first one */
|
||||
for (end = path + strlen(path) - 1; end >= path && *end == '/'; end--)
|
||||
*end = 0;
|
||||
/* path already parsed? */
|
||||
if (global_ssl.issuers_chain_path && strcmp(global_ssl.issuers_chain_path, path) == 0)
|
||||
return 0;
|
||||
/* overwrite old issuers_chain_path */
|
||||
free(global_ssl.issuers_chain_path);
|
||||
global_ssl.issuers_chain_path = strdup(path);
|
||||
ssl_free_global_issuers();
|
||||
|
||||
n = scandir(path, &de_list, 0, alphasort);
|
||||
if (n < 0) {
|
||||
memprintf(err, "%sglobal statement '%s': unable to scan directory '%s' : %s.\n",
|
||||
err && *err ? *err : "", args[0], path, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; i < n; i++) {
|
||||
struct dirent *de = de_list[i];
|
||||
BIO *in = NULL;
|
||||
char *warn = NULL;
|
||||
|
||||
snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name);
|
||||
free(de);
|
||||
if (stat(fp, &buf) != 0) {
|
||||
ha_warning("unable to stat certificate from file '%s' : %s.\n", fp, strerror(errno));
|
||||
goto next;
|
||||
}
|
||||
if (!S_ISREG(buf.st_mode))
|
||||
goto next;
|
||||
|
||||
in = BIO_new(BIO_s_file());
|
||||
if (in == NULL)
|
||||
goto next;
|
||||
if (BIO_read_filename(in, fp) <= 0)
|
||||
goto next;
|
||||
ssl_load_global_issuer_from_BIO(in, fp, &warn);
|
||||
if (warn) {
|
||||
ha_warning(warn);
|
||||
free(warn);
|
||||
warn = NULL;
|
||||
}
|
||||
next:
|
||||
if (in)
|
||||
BIO_free(in);
|
||||
}
|
||||
free(de_list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* parse the "ssl-mode-async" keyword in global section.
|
||||
* Returns <0 on alert, >0 on warning, 0 on success.
|
||||
*/
|
||||
@ -11466,6 +11655,7 @@ INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
|
||||
static struct cfg_kw_list cfg_kws = {ILH, {
|
||||
{ CFG_GLOBAL, "ca-base", ssl_parse_global_ca_crt_base },
|
||||
{ CFG_GLOBAL, "crt-base", ssl_parse_global_ca_crt_base },
|
||||
{ CFG_GLOBAL, "issuers-chain-path", ssl_load_global_issuers_from_path },
|
||||
{ CFG_GLOBAL, "maxsslconn", ssl_parse_global_int },
|
||||
{ CFG_GLOBAL, "ssl-default-bind-options", ssl_parse_default_bind_options },
|
||||
{ CFG_GLOBAL, "ssl-default-server-options", ssl_parse_default_server_options },
|
||||
@ -11628,6 +11818,8 @@ static void __ssl_sock_init(void)
|
||||
global.ssl_session_max_cost = SSL_SESSION_MAX_COST;
|
||||
global.ssl_handshake_max_cost = SSL_HANDSHAKE_MAX_COST;
|
||||
|
||||
hap_register_post_deinit(ssl_free_global_issuers);
|
||||
|
||||
#ifndef OPENSSL_NO_DH
|
||||
ssl_dh_ptr_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
|
||||
hap_register_post_deinit(ssl_free_dh);
|
||||
|
Loading…
Reference in New Issue
Block a user