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.
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index ade5ffc..1b3cf55 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -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 @@
 
 /*
  *  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 @@
 
 	/* 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 @@
 		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)
@@ -3459,6 +3450,60 @@
 }
 
 /*
+ *  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 @@
 		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 @@
 		} 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 @@
 
 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 @@
 	/* 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 @@
 		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;