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
signature.asc
Description: OpenPGP digital signature