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 "openssl dhparam <size>", where size should be at least 2048, as 1024-bit DH
parameters should not be considered secure anymore. 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 This setting alters the way HAProxy will look for unspecified files during
the loading of the SSL certificates. 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. 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, "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 "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 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 "issuer": Try to load "<basename>.issuer" if the issuer of the OCSP file is
not provided in the PEM file. 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". The default behavior is "all".
Example: Example:
@ -11331,6 +11334,9 @@ crt <cert>
file. Intermediate certificate can also be shared in a directory via file. Intermediate certificate can also be shared in a directory via
"issuers-chain-path" directive. "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 If the OpenSSL used supports Diffie-Hellman, parameters present in this file
are loaded. are loaded.

View File

@ -130,8 +130,9 @@
#define SSL_GF_SCTL 0x00000002 /* try to open the .sctl file */ #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 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_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 */ /* ssl_methods versions */
enum { enum {
@ -3287,8 +3288,8 @@ static int ssl_sock_load_issuer_file_into_ckch(const char *path, char *buf, stru
/* /*
* Try to load a PEM file from a <path> or a buffer <buf> * 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, * The PEM must contain at least a Certificate,
* It could contain a DH and a certificate chain. * 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. * 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 */ /* Read Private Key */
key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL); key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
if (key == NULL) { /* no need to check for errors here, because the private key could be loaded later */
memprintf(err, "%sunable to load private key from file '%s'.\n",
err && *err ? *err : "", path);
goto end;
}
#ifndef OPENSSL_NO_DH #ifndef OPENSSL_NO_DH
/* Seek back to beginning of file */ /* 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; 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 */ /* Look for a Certificate Chain */
while ((ca = PEM_read_bio_X509(in, NULL, NULL, NULL))) { while ((ca = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
if (chain == NULL) if (chain == NULL)
@ -3458,6 +3449,60 @@ static int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct cert_
return ret; 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. * Try to load in a ckch every files related to a ckch.
* (PEM, sctl, ocsp, issuer etc.) * (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; 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) #if (HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL)
/* try to load the sctl file */ /* try to load the sctl file */
if (global_ssl.extra_files & SSL_GF_SCTL) { 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])){ } else if (!strcmp("issuer", args[i])){
gf |= SSL_GF_OCSP_ISSUER; gf |= SSL_GF_OCSP_ISSUER;
} else if (!strcmp("key", args[i])) {
gf |= SSL_GF_KEY;
} else if (!strcmp("none", args[i])) { } else if (!strcmp("none", args[i])) {
if (gf != SSL_GF_NONE) if (gf != SSL_GF_NONE)
goto err_alone; goto err_alone;
@ -10436,6 +10510,7 @@ static int cli_parse_set_tlskeys(char **args, char *payload, struct appctx *appc
enum { enum {
CERT_TYPE_PEM = 0, CERT_TYPE_PEM = 0,
CERT_TYPE_KEY,
#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL) #if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
CERT_TYPE_OCSP, CERT_TYPE_OCSP,
#endif #endif
@ -10453,6 +10528,7 @@ struct {
/* add a parsing callback */ /* add a parsing callback */
} cert_exts[CERT_TYPE_MAX+1] = { } cert_exts[CERT_TYPE_MAX+1] = {
[CERT_TYPE_PEM] = { "", CERT_TYPE_PEM, &ssl_sock_load_pem_into_ckch }, /* default mode, no extensions */ [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) #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 }, [CERT_TYPE_OCSP] = { "ocsp", CERT_TYPE_OCSP, &ssl_sock_load_ocsp_response_from_file },
#endif #endif
@ -10928,6 +11004,25 @@ static int cli_parse_commit_cert(char **args, char *payload, struct appctx *appc
goto error; 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 */ /* init the appctx structure */
appctx->st2 = SETCERT_ST_INIT; appctx->st2 = SETCERT_ST_INIT;
appctx->ctx.ssl.next_ckchi = NULL; appctx->ctx.ssl.next_ckchi = NULL;