MINOR: ssl: load the key from a dedicated file

For a certificate on a bind line, if the private key was not found in
the PEM file, look for a .key and load it.

This default behavior can be changed by using the ssl-load-extra-files
directive in the global section

This feature was mentionned in the issue #221.
This commit is contained in:
William Lallemand 2020-02-24 14:23:22 +01:00 committed by William Lallemand
parent d43183d05f
commit 4c5adbf595
2 changed files with 117 additions and 16 deletions

View File

@ -1320,7 +1320,7 @@ ssl-dh-param-file <file>
"openssl dhparam <size>", where size should be at least 2048, as 1024-bit DH
parameters should not be considered secure anymore.
ssl-load-extra-files <none|all|bundle|sctl|ocsp|issuer>*
ssl-load-extra-files <none|all|bundle|sctl|ocsp|issuer|key>*
This setting alters the way HAProxy will look for unspecified files during
the loading of the SSL certificates.
@ -1333,7 +1333,7 @@ ssl-load-extra-files <none|all|bundle|sctl|ocsp|issuer>*
it won't try to bundle the certificates if they have the same basename.
"all": This is the default behavior, it will try to load everything,
bundles, sctl, ocsp, issuer.
bundles, sctl, ocsp, issuer, key.
"bundle": When a file specified in the configuration does not exist, HAProxy
will try to load a certificate bundle. This is done by looking for
@ -1351,6 +1351,9 @@ ssl-load-extra-files <none|all|bundle|sctl|ocsp|issuer>*
"issuer": Try to load "<basename>.issuer" if the issuer of the OCSP file is
not provided in the PEM file.
"key": If the private key was not provided by the PEM file, try to load a
file "<basename>.key" containing a private key.
The default behavior is "all".
Example:
@ -11331,6 +11334,9 @@ crt <cert>
file. Intermediate certificate can also be shared in a directory via
"issuers-chain-path" directive.
If the file does not contain a private key, HAProxy will try to load
the key at the same path suffixed by a ".key".
If the OpenSSL used supports Diffie-Hellman, parameters present in this file
are loaded.

View File

@ -130,8 +130,9 @@
#define SSL_GF_SCTL 0x00000002 /* try to open the .sctl file */
#define SSL_GF_OCSP 0x00000004 /* try to open the .ocsp file */
#define SSL_GF_OCSP_ISSUER 0x00000008 /* try to open the .issuer file if an OCSP file was loaded */
#define SSL_GF_KEY 0x00000010 /* try to open the .key file to load a private key */
#define SSL_GF_ALL (SSL_GF_BUNDLE|SSL_GF_SCTL|SSL_GF_OCSP|SSL_GF_OCSP_ISSUER)
#define SSL_GF_ALL (SSL_GF_BUNDLE|SSL_GF_SCTL|SSL_GF_OCSP|SSL_GF_OCSP_ISSUER|SSL_GF_KEY)
/* ssl_methods versions */
enum {
@ -3287,8 +3288,8 @@ end:
/*
* Try to load a PEM file from a <path> or a buffer <buf>
* The PEM must contain at least a Private Key and a Certificate,
* It could contain a DH and a certificate chain.
* 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.
*
@ -3325,11 +3326,7 @@ static int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct cert_
/* 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;
}
/* 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 */
@ -3358,12 +3355,6 @@ static int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct cert_
goto end;
}
if (!X509_check_private_key(cert, key)) {
memprintf(err, "%sinconsistencies between private key and certificate loaded from PEM 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)
@ -3458,6 +3449,60 @@ end:
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
*/
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.)
@ -3482,6 +3527,32 @@ static int ssl_sock_load_files_into_ckch(const char *path, struct cert_key_and_c
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) {
@ -10179,6 +10250,9 @@ static int ssl_parse_global_extra_files(char **args, int section_type, struct pr
} else if (!strcmp("issuer", args[i])){
gf |= SSL_GF_OCSP_ISSUER;
} else if (!strcmp("key", args[i])) {
gf |= SSL_GF_KEY;
} else if (!strcmp("none", args[i])) {
if (gf != SSL_GF_NONE)
goto err_alone;
@ -10436,6 +10510,7 @@ static int cli_parse_set_tlskeys(char **args, char *payload, struct appctx *appc
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
@ -10453,6 +10528,7 @@ struct {
/* 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
@ -10928,6 +11004,25 @@ static int cli_parse_commit_cert(char **args, char *payload, struct appctx *appc
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;