mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-05-07 18: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
|
- h1-case-adjust-file
|
||||||
- insecure-fork-wanted
|
- insecure-fork-wanted
|
||||||
- insecure-setuid-wanted
|
- insecure-setuid-wanted
|
||||||
|
- issuers-chain-path
|
||||||
- log
|
- log
|
||||||
- log-tag
|
- log-tag
|
||||||
- log-send-hostname
|
- log-send-hostname
|
||||||
@ -949,6 +950,19 @@ insecure-setuid-wanted
|
|||||||
explicitly adding this directive in the global section. If enabled, it is
|
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.
|
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>]
|
log <address> [len <length>] [format <format>] [sample <ranges>:<smp_size>]
|
||||||
<facility> [max level [min level]]
|
<facility> [max level [min level]]
|
||||||
Adds a global syslog server. Several global servers can be defined. They
|
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
|
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
|
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
|
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
|
If the OpenSSL used supports Diffie-Hellman, parameters present in this file
|
||||||
are loaded.
|
are loaded.
|
||||||
|
194
src/ssl_sock.c
194
src/ssl_sock.c
@ -153,6 +153,14 @@ enum {
|
|||||||
SSL_SOCK_VERIFY_NONE = 3,
|
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 sslconns = 0;
|
||||||
int totalsslconns = 0;
|
int totalsslconns = 0;
|
||||||
@ -162,6 +170,9 @@ int nb_engines = 0;
|
|||||||
static struct {
|
static struct {
|
||||||
char *crt_base; /* base directory path for certificates */
|
char *crt_base; /* base directory path for certificates */
|
||||||
char *ca_base; /* base directory path for CAs and CRLs */
|
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 */
|
int async; /* whether we use ssl async mode */
|
||||||
|
|
||||||
char *listen_default_ciphers;
|
char *listen_default_ciphers;
|
||||||
@ -183,6 +194,7 @@ static struct {
|
|||||||
int capture_cipherlist; /* Size of the cipherlist buffer. */
|
int capture_cipherlist; /* Size of the cipherlist buffer. */
|
||||||
int extra_files; /* which files not defined in the configuration file are we looking for */
|
int extra_files; /* which files not defined in the configuration file are we looking for */
|
||||||
} global_ssl = {
|
} global_ssl = {
|
||||||
|
.cert_issuer_tree = EB_ROOT,
|
||||||
#ifdef LISTEN_DEFAULT_CIPHERS
|
#ifdef LISTEN_DEFAULT_CIPHERS
|
||||||
.listen_default_ciphers = LISTEN_DEFAULT_CIPHERS,
|
.listen_default_ciphers = LISTEN_DEFAULT_CIPHERS,
|
||||||
#endif
|
#endif
|
||||||
@ -3361,7 +3373,25 @@ static int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct cert_
|
|||||||
goto end;
|
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 */
|
/* no chain */
|
||||||
if (chain == NULL) {
|
if (chain == NULL) {
|
||||||
chain = sk_X509_new_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;
|
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.
|
/* parse the "ssl-mode-async" keyword in global section.
|
||||||
* Returns <0 on alert, >0 on warning, 0 on success.
|
* 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, {
|
static struct cfg_kw_list cfg_kws = {ILH, {
|
||||||
{ CFG_GLOBAL, "ca-base", ssl_parse_global_ca_crt_base },
|
{ CFG_GLOBAL, "ca-base", ssl_parse_global_ca_crt_base },
|
||||||
{ CFG_GLOBAL, "crt-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, "maxsslconn", ssl_parse_global_int },
|
||||||
{ CFG_GLOBAL, "ssl-default-bind-options", ssl_parse_default_bind_options },
|
{ CFG_GLOBAL, "ssl-default-bind-options", ssl_parse_default_bind_options },
|
||||||
{ CFG_GLOBAL, "ssl-default-server-options", ssl_parse_default_server_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_session_max_cost = SSL_SESSION_MAX_COST;
|
||||||
global.ssl_handshake_max_cost = SSL_HANDSHAKE_MAX_COST;
|
global.ssl_handshake_max_cost = SSL_HANDSHAKE_MAX_COST;
|
||||||
|
|
||||||
|
hap_register_post_deinit(ssl_free_global_issuers);
|
||||||
|
|
||||||
#ifndef OPENSSL_NO_DH
|
#ifndef OPENSSL_NO_DH
|
||||||
ssl_dh_ptr_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
|
ssl_dh_ptr_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
|
||||||
hap_register_post_deinit(ssl_free_dh);
|
hap_register_post_deinit(ssl_free_dh);
|
||||||
|
Loading…
Reference in New Issue
Block a user