]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: ssl: load the key from a dedicated file
authorWilliam Lallemand <wlallemand@haproxy.com>
Mon, 24 Feb 2020 13:23:22 +0000 (14:23 +0100)
committerWilliam Lallemand <wlallemand@haproxy.org>
Mon, 24 Feb 2020 14:39:53 +0000 (15:39 +0100)
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.

doc/configuration.txt
src/ssl_sock.c

index 18ac2c8576c78a60ecb236a8a80d7a6ea81bedd8..61c7d5cea020e9509a76a90f9b57194ddee5f8d9 100644 (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.
 
index ade5ffc84d993c04c17fd3f88a65f6b32d048984..1b3cf55ab5d9443a08b108190db6b13763eb7f0c 100644 (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_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;