Hi,

This patch enables the use of cryptographic keys stored in a
PKCS#11-compatible hardware security module. It uses the OpenSSL engine
interface, and has been tested with engine-pkcs11 and two different
kinds of HSM:

- SoftHSM
- Yubikey

The main benefit of storing cryptographic keys in a HSM is to prevent
them from being accessed or stolen should the host been compromised, by
example using a heartbleed-like vulnerability. Note that while being
very useful for testing purpose, SoftHSM keeps the key in the same
memory space than the main process, and a such should not be considered
as secure as an hardware HSM. While a Yubikey is a nice device and a
hardware HSM, it provides a very low transactions/s rate, and as such
might probably not be suited for production use with HAproxy.

This patch should be considered more a proof-of-concept/WIP than a
production-ready one. It adds the possibility to load a private key from
a HSM while the corresponding certificate is still loaded from a file,
using the following syntax:

bind 127.0.0.1:8443 ssl crt pkcs11:slot_<SLOT NUMBER>-id_<OBJECT
ID>:/path/to/corresponding/cert.pem

For example, this would load the private key from the object named
deadbeef in the slot 0 of softhsm:

bind 127.0.0.1:8443 ssl crt
pkcs11:slot_0-id_deadbeef:/path/to/corresponding/cert.pem

Or from the slot 9a of a yubikey in PIV mode:

bind 127.0.0.1:8443 ssl crt
pkcs11:slot_1-id_1:/path/to/corresponding/cert.pem

This patch also allows the configuration of the OpenSSL application name
via the environment variable HAPROXY_SSL_APP_NAME, in order to use
different configurations for different applications without polluting
the default OpenSSL namespace.

For these examples to work, engine-pkcs11 must be installed and OpenSSL
be configured to use it. For example, these sections can be defined in
/etc/ssl/openssl.conf:

haproxy_yubi_conf    = haproxy_yubi_def
haproxy_shsm_conf    = haproxy_shsm_def

[...]

[haproxy_yubi_def]
engines = engine_ha_yubi_section

[engine_ha_yubi_section]
pkcs11 = pkcs11_ha_yubi_section

[pkcs11_ha_yubi_section]
engine_id = pkcs11
dynamic_path = /usr/lib/engines/engine_pkcs11.so
init = 0
MODULE_PATH = /usr/lib/pkcs11/opensc-pkcs11.so

[haproxy_shsm_def]
engines = engine_ha_shsm_section

[engine_ha_shsm_section]
pkcs11 = pkcs11_ha_shsm_section

[pkcs11_ha_shsm_section]
engine_id = pkcs11
dynamic_path = /usr/lib/engines/engine_pkcs11.so
init = 0
MODULE_PATH = /usr/lib/libsofthsm.so

In this example, in order to load keys from SoftHSM,
HAPROXY_SSL_APP_NAME has to be set to "haproxy_shsm_conf". For loading
from a yubikey (via OpenSC), HAPROXY_SSL_APP_NAME has to be set to
"haproxy_yubi_conf".

There is a few things I am not happy with in this patch:
- the hijacking of the crt syntax, prepending "pkcs11:" to load the key
from a PKCS#11 engine ;
- the use of a environment variable to specify the OpenSSL application
name. However it has to be set before __ssl_sock_init() constructor is
loaded, so the configuration parsing has not been done yet ;
- the PKCS#11 interface specifies that all PKCS#11 handles should be
reinitialized after a fork(), which might be an issue with nbproc > 1.
In practice this is correctly handled by some drivers but it's not
guaranteed to work for every one of them.

I will appreciate any feedback regarding those points or the patch itself.

-- 
Remi
From 5f16d441e7fa5dfbce033faae8a866f178d05967 Mon Sep 17 00:00:00 2001
From: Remi Gacogne <rgacogne-git...@coredump.fr>
Date: Thu, 23 Jul 2015 16:50:49 +0200
Subject: [PATCH] MEDIUM: ssl: add support for using cryptographic keys stored
 in a PKCS#11 HSM

---
 src/ssl_sock.c | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 121 insertions(+), 6 deletions(-)

diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 7f1a070..15460b5 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -50,6 +50,9 @@
 #ifndef OPENSSL_NO_DH
 #include <openssl/dh.h>
 #endif
+#ifndef OPENSSL_NO_ENGINE
+#include <openssl/engine.h>
+#endif
 
 #include <import/lru.h>
 #include <import/xxhash.h>
@@ -134,6 +137,10 @@ static DH *local_dh_2048 = NULL;
 static DH *local_dh_4096 = NULL;
 #endif /* OPENSSL_NO_DH */
 
+#ifndef OPENSSL_NO_ENGINE
+static ENGINE * pkcs11_engine = NULL;
+#endif /* OPENSSL_NO_ENGINE */
+
 #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
 /* X509V3 Extensions that will be added on generated certificates */
 #define X509V3_EXT_SIZE 5
@@ -1632,8 +1639,14 @@ end:
 
 static int ssl_sock_load_cert_file(const char *path, struct bind_conf *bind_conf, struct proxy *curproxy, char **sni_filter, int fcount, char **err)
 {
+	static const char * const pkcs11_engine_name = "pkcs11";
+	static const char pkcs11_prefix[] = "pkcs11:";
+	static const size_t pkcs11_prefix_len = sizeof pkcs11_prefix - 1;
 	int ret;
 	SSL_CTX *ctx;
+	char * pkcs11_slot = NULL;
+	const char * ptr = NULL;
+	EVP_PKEY * pkey = NULL;
 
 	ctx = SSL_CTX_new(SSLv23_server_method());
 	if (!ctx) {
@@ -1642,11 +1655,83 @@ static int ssl_sock_load_cert_file(const char *path, struct bind_conf *bind_conf
 		return 1;
 	}
 
-	if (SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM) <= 0) {
-		memprintf(err, "%sunable to load SSL private key from PEM file '%s'.\n",
+	if (strncmp(path, pkcs11_prefix, pkcs11_prefix_len) == 0) {
+#ifndef OPENSSL_NO_ENGINE
+		if (pkcs11_engine == NULL) {
+			pkcs11_engine = ENGINE_by_id(pkcs11_engine_name);
+			if (pkcs11_engine != NULL) {
+				if (ENGINE_init(pkcs11_engine) == 0) {
+					pkcs11_engine = NULL;
+					memprintf(err, "%sunable to init the PKCS11 SSL engine to load the key '%s'.\n",
+					          err && *err ? *err : "", path);
+					SSL_CTX_free(ctx);
+					return 1;
+				}
+			}
+			else {
+				memprintf(err, "%sunable to find a PKCS11 SSL engine to load the key '%s'.\n",
+				          err && *err ? *err : "", path);
+				SSL_CTX_free(ctx);
+				return 1;
+			}
+		}
+
+		pkcs11_slot = strdup(path);
+		if (pkcs11_slot != NULL) {
+			/* Look for the real certificate file path */
+			ptr = strstr(path + pkcs11_prefix_len, ":");
+			if (ptr != NULL) {
+				/* Remove the certificate path part */
+				pkcs11_slot[ptr - path] = '\0';
+				/* Load the certificate from the PKCS11 engine */
+				pkey = ENGINE_load_private_key(pkcs11_engine,
+				                               pkcs11_slot + pkcs11_prefix_len,
+                                                               UI_OpenSSL(),
+				                               NULL);
+				if (pkey != NULL) {
+					if (SSL_CTX_use_PrivateKey(ctx, pkey) > 0) {
+						path = ptr + 1;
+						EVP_PKEY_free(pkey);
+					}
+					else {
+						memprintf(err, "%sunable to use the SSL private key loaded from PKCS11 '%s'.\n",
+						          err && *err ? *err : "", pkcs11_slot + pkcs11_prefix_len);
+						EVP_PKEY_free(pkey);
+						SSL_CTX_free(ctx);
+						return 1;
+					}
+				}
+				else {
+					memprintf(err, "%sunable to load SSL private key from PKCS11 '%s'.\n",
+					          err && *err ? *err : "", pkcs11_slot + pkcs11_prefix_len);
+					SSL_CTX_free(ctx);
+					return 1;
+				}
+			}
+
+			free(pkcs11_slot);
+			pkcs11_slot = NULL;
+		}
+		else {
+			memprintf(err, "%sunable to allocate memory for SSL private key string '%s'.\n",
+			          err && *err ? *err : "", path);
+			SSL_CTX_free(ctx);
+			return 1;
+		}
+#else
+		memprintf(err, "%sPKCS11 engine support is not unavailable, unable to load SSL private key from '%s'.\n",
 		          err && *err ? *err : "", path);
 		SSL_CTX_free(ctx);
 		return 1;
+#endif /* OPENSSL_NO_ENGINE */
+	}
+	else {
+		if (SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM) <= 0) {
+			memprintf(err, "%sunable to load SSL private key from PEM file '%s'.\n",
+			          err && *err ? *err : "", path);
+			SSL_CTX_free(ctx);
+			return 1;
+		}
 	}
 
 	ret = ssl_sock_load_cert_chain_file(ctx, path, bind_conf, sni_filter, fcount);
@@ -5314,7 +5399,23 @@ static void __ssl_sock_init(void)
 	global.listen_default_ssloptions = BC_SSL_O_NONE;
 	global.connect_default_ssloptions = SRV_SSL_O_NONE;
 
+#if OPENSSL_VERSION_NUMBER >= 0x00907000L
+	OPENSSL_load_builtin_modules();
+
+#ifndef OPENSSL_NO_ENGINE
+	ENGINE_load_builtin_engines();
+#endif
+#endif
+
 	SSL_library_init();
+
+#if OPENSSL_VERSION_NUMBER >= 0x00907000L
+	CONF_modules_load_file(NULL,
+	                       getenv("HAPROXY_SSL_APP_NAME"),
+	                       CONF_MFLAGS_DEFAULT_SECTION |
+	                       CONF_MFLAGS_IGNORE_MISSING_FILE);
+#endif
+
 	cm = SSL_COMP_get_compression_methods();
 	sk_SSL_COMP_zero(cm);
 #if (OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL)
@@ -5370,13 +5471,27 @@ static void __ssl_sock_deinit(void)
 	}
 #endif
 
-        ERR_remove_state(0);
-        ERR_free_strings();
+#ifndef OPENSSL_NO_ENGINE
+	if (pkcs11_engine != NULL) {
+		ENGINE_finish(pkcs11_engine);
+		ENGINE_free(pkcs11_engine);
+		pkcs11_engine = NULL;
+	}
+
+	ENGINE_cleanup();
+#endif /* OPENSSL_NO_ENGINE */
 
-        EVP_cleanup();
+	ERR_remove_state(0);
+	ERR_free_strings();
+
+	EVP_cleanup();
 
 #if OPENSSL_VERSION_NUMBER >= 0x00907000L
-        CRYPTO_cleanup_all_ex_data();
+	CONF_modules_finish();
+	CONF_modules_free();
+	CONF_modules_unload(1);
+
+	CRYPTO_cleanup_all_ex_data();
 #endif
 }
 
-- 
2.4.6

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to