From 8c5f2385dd804c98e74960db10cb66ab19b5794c Mon Sep 17 00:00:00 2001
From: Grant Zhang <gzhang@fastly.com>
Date: Sat, 21 Jan 2017 01:10:18 +0000
Subject: [PATCH 1/2] ssl: add basic support for OpenSSL crypto engine

This patch adds the global 'ssl-engine' keyword. First arg is an engine
identifier followed by a list of default_algorithms the engine will
operate.

If the openssl version is too old, an error is reported when the option
is used.
---
 doc/configuration.txt       |   16 ++++++
 include/common/mini-clist.h |    7 +++
 src/ssl_sock.c              |  119 ++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 141 insertions(+), 1 deletion(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index f1afbc9..a2b4fef 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -591,6 +591,7 @@ The following keywords are supported in the "global" section :
    - spread-checks
    - server-state-base
    - server-state-file
+   - ssl-engine
    - tune.buffers.limit
    - tune.buffers.reserve
    - tune.bufsize
@@ -1265,6 +1266,21 @@ spread-checks <0..50, in percent>
   and +/- 50%. A value between 2 and 5 seems to show good results. The
   default value remains at 0.
 
+ssl-engine <name> [algo <comma-seperated list of algorithms>]
+  Sets the OpenSSL engine to <name>. List of valid values for <name> may be
+  obtained using the command "openssl engine".  This statement may be used
+  multiple times, it will simply enable multiple crypto engines. Referencing an
+  unsupported engine will prevent haproxy from starting. Note that many engines
+  will lead to lower HTTPS performance than pure software with recent
+  processors. The optional command "algo" sets the default algorithms an ENGINE
+  will supply using the OPENSSL function ENGINE_set_default_string(). A value
+  of "ALL" uses the engine for all cryptographic operations.  If no list of
+  algo is specified then the value of "ALL" is used.  A comma-seperated list
+  of different algorithms may be specified, including: RSA, DSA, DH, EC, RAND,
+  CIPHERS, DIGESTS, PKEY, PKEY_CRYPTO, PKEY_ASN1. This is the same format that
+  openssl configuration file uses:
+  https://www.openssl.org/docs/man1.0.2/apps/config.html
+
 tune.buffers.limit <number>
   Sets a hard limit on the number of buffers which may be allocated per process.
   The default value is zero which means unlimited. The minimum non-zero value
diff --git a/include/common/mini-clist.h b/include/common/mini-clist.h
index da24b33..7000927 100644
--- a/include/common/mini-clist.h
+++ b/include/common/mini-clist.h
@@ -61,6 +61,13 @@ struct cond_wordlist {
 	char *s;
 };
 
+/* this is the same as above with the additional pointer to an argument. */
+struct arg1_wordlist {
+	struct list list;
+	void *arg1;
+	char *word;
+};
+
 /* First undefine some macros which happen to also be defined on OpenBSD,
  * in sys/queue.h, used by sys/event.h
  */
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 4c1be5a..b216746 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -52,6 +52,7 @@
 #ifndef OPENSSL_NO_DH
 #include <openssl/dh.h>
 #endif
+#include <openssl/engine.h>
 
 #include <import/lru.h>
 #include <import/xxhash.h>
@@ -178,6 +179,9 @@ static int ssl_capture_ptr_index = -1;
 struct list tlskeys_reference = LIST_HEAD_INIT(tlskeys_reference);
 #endif
 
+struct list openssl_engines = LIST_HEAD_INIT(openssl_engines);
+static unsigned int openssl_engines_initialized;
+
 #ifndef OPENSSL_NO_DH
 static int ssl_dh_ptr_index = -1;
 static DH *global_dh = NULL;
@@ -273,6 +277,55 @@ struct ocsp_cbk_arg {
 	};
 };
 
+static int ssl_init_single_engine(const char *engine_id, const char *def_algorithms)
+{
+	int err_code = ERR_ABORT;
+	ENGINE *engine;
+
+	/* grab the structural reference to the engine */
+	engine = ENGINE_by_id(engine_id);
+	if (engine  == NULL) {
+		Alert("ssl-engine %s: failed to get structural reference\n", engine_id);
+		goto fail_get;
+	}
+
+	if (!ENGINE_init(engine)) {
+		/* the engine couldn't initialise, release it */
+		Alert("ssl-engine %s: failed to initialize\n", engine_id);
+		goto fail_init;
+	}
+
+	if (ENGINE_set_default_string(engine, def_algorithms) == 0) {
+		Alert("ssl-engine %s: failed on ENGINE_set_default_string\n", engine_id);
+		goto fail_set_method;
+	}
+	err_code = 0;
+
+fail_set_method:
+	/* release the functional reference from ENGINE_init() */
+	ENGINE_finish(engine);
+
+fail_init:
+	/* release the structural reference from ENGINE_by_id() */
+	ENGINE_free(engine);
+
+fail_get:
+	return err_code;
+}
+
+static int ssl_init_engines(void)
+{
+	int err_code = 0;
+	struct arg1_wordlist *wl, *wlb;
+
+	list_for_each_entry_safe(wl, wlb, &openssl_engines, list) {
+		err_code = ssl_init_single_engine(wl->word, wl->arg1);
+		if (err_code == ERR_ABORT)
+			break;
+	}
+	return err_code;
+}
+
 /*
  *  This function returns the number of seconds  elapsed
  *  since the Epoch, 1970-01-01 00:00:00 +0000 (UTC) and the
@@ -2656,7 +2709,6 @@ static int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct
 	if (BIO_read_filename(in, file) <= 0)
 		goto end;
 
-
 	passwd_cb = SSL_CTX_get_default_passwd_cb(ctx);
 	passwd_cb_userdata = SSL_CTX_get_default_passwd_cb_userdata(ctx);
 
@@ -2853,6 +2905,12 @@ int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, char **err)
 	int j;
 #endif
 
+	if (!openssl_engines_initialized) {
+		if (ssl_init_engines())
+			return 1;
+		openssl_engines_initialized = 1;
+	}
+
 	if (stat(path, &buf) == 0) {
 		dir = opendir(path);
 		if (!dir)
@@ -2994,6 +3052,12 @@ int ssl_sock_load_cert_list_file(char *file, struct bind_conf *bind_conf, struct
 		return 1;
 	}
 
+	if (!openssl_engines_initialized) {
+		if (ssl_init_engines())
+			return 1;
+		openssl_engines_initialized = 1;
+	}
+
 	while (fgets(thisline, sizeof(thisline), f) != NULL) {
 		int arg, newarg, cur_arg, i, ssl_b = 0, ssl_e = 0;
 		char *end;
@@ -6910,6 +6974,48 @@ static int ssl_parse_global_ca_crt_base(char **args, int section_type, struct pr
 	return 0;
 }
 
+/* parse the "ssl-engine" keyword in global section.
+ * Returns <0 on alert, >0 on warning, 0 on success.
+ */
+static int ssl_parse_global_ssl_engine(char **args, int section_type, struct proxy *curpx,
+                                       struct proxy *defpx, const char *file, int line,
+                                       char **err)
+{
+	char *algo;
+	struct arg1_wordlist *wl;
+
+	if (*(args[1]) == 0) {
+		memprintf(err, "global statement '%s' expects a valid engine name as an argument.", args[0]);
+		return -1;
+	}
+
+	if (*(args[2]) == 0) {
+		memprintf(err, "statement '%s' expects algorithm names as an argument.", args[0]);
+		/* if no list of algorithms is given, it defaults to ALL */
+		algo = strdup("ALL");
+		goto add_engine;
+	}
+
+	/* otherwise the expected format is ssl-engine <engine_name> algo <list of algo> */
+	if (strcmp(args[2], "algo") != 0) {
+		memprintf(err, "global statement '%s' expects to have algo keyword.", args[0]);
+		return -1;
+	}
+
+	if (*(args[3]) == 0) {
+		memprintf(err, "global statement '%s' expects algorithm names as an argument.", args[0]);
+		return -1;
+	}
+	algo = strdup(args[3]);
+
+add_engine:
+	wl = calloc(1, sizeof(*wl));
+	wl->word = strdup(args[1]);
+	wl->arg1 = algo;
+	LIST_ADD(&openssl_engines, &wl->list);
+	return 0;
+}
+
 /* parse the "ssl-default-bind-ciphers" / "ssl-default-server-ciphers" keywords
  * in global section. Returns <0 on alert, >0 on warning, 0 on success.
  */
@@ -7517,6 +7623,7 @@ static struct cfg_kw_list cfg_kws = {ILH, {
 #ifndef OPENSSL_NO_DH
 	{ CFG_GLOBAL, "ssl-dh-param-file", ssl_parse_global_dh_param_file },
 #endif
+	{ CFG_GLOBAL, "ssl-engine",  ssl_parse_global_ssl_engine },
 	{ CFG_GLOBAL, "tune.ssl.cachesize", ssl_parse_global_int },
 #ifndef OPENSSL_NO_DH
 	{ CFG_GLOBAL, "tune.ssl.default-dh-param", ssl_parse_global_default_dh },
@@ -7590,6 +7697,7 @@ static void __ssl_sock_init(void)
 	srv_register_keywords(&srv_kws);
 	cfg_register_keywords(&cfg_kws);
 	cli_register_kw(&cli_kws);
+	ENGINE_load_builtin_engines();
 #if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
 	hap_register_post_check(tlskeys_finalize_config);
 #endif
@@ -7642,6 +7750,7 @@ static void __ssl_sock_init(void)
 __attribute__((destructor))
 static void __ssl_sock_deinit(void)
 {
+	struct arg1_wordlist *wl, *wlb;
 #if (defined SSL_CTRL_SET_TLSEXT_HOSTNAME && !defined SSL_NO_GENERATE_CERTIFICATES)
 	lru64_destroy(ssl_ctx_lru_tree);
 #endif
@@ -7668,6 +7777,14 @@ static void __ssl_sock_deinit(void)
 	}
 #endif
 
+	/* free up engine list */
+	list_for_each_entry_safe(wl, wlb, &openssl_engines, list) {
+		free(wl->arg1);
+		free(wl->word);
+		LIST_DEL(&wl->list);
+		free(wl);
+	}
+
         ERR_remove_state(0);
         ERR_free_strings();
 
-- 
1.7.9.5

