Browse code

Add unit tests for engine keys

Testing engines is problematic, so one of the prerequisites built for
the tests is a simple openssl engine that reads a non-standard PEM
guarded key. The test is simply can we run a client/server
configuration with the usual sample key replaced by an engine key.
The trivial engine prints out some operations and we check for these
in the log to make sure the engine was used to load the key and that
it correctly got the password.

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>

Acked-by: Gert Doering <gert@greenie.muc.de>
Message-Id: <20200622232319.8143-2-James.Bottomley@HansenPartnership.com>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg20075.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>

James Bottomley authored on 2020/06/23 08:23:19
Showing 6 changed files
... ...
@@ -1387,6 +1387,7 @@ AM_CONDITIONAL([GIT_CHECKOUT], [test "${GIT_CHECKOUT}" = "yes"])
1387 1387
 AM_CONDITIONAL([ENABLE_PLUGIN_AUTH_PAM], [test "${enable_plugin_auth_pam}" = "yes"])
1388 1388
 AM_CONDITIONAL([ENABLE_PLUGIN_DOWN_ROOT], [test "${enable_plugin_down_root}" = "yes"])
1389 1389
 AM_CONDITIONAL([HAVE_LD_WRAP_SUPPORT], [test "${have_ld_wrap_support}" = "yes"])
1390
+AM_CONDITIONAL([OPENSSL_ENGINE], [test "${have_openssl_engine}" = "yes"])
1390 1391
 
1391 1392
 sampledir="\$(docdir)/sample"
1392 1393
 AC_SUBST([plugindir])
... ...
@@ -1448,6 +1449,7 @@ AC_CONFIG_FILES([
1448 1448
         tests/unit_tests/openvpn/Makefile
1449 1449
         tests/unit_tests/plugins/Makefile
1450 1450
         tests/unit_tests/plugins/auth-pam/Makefile
1451
+	tests/unit_tests/engine-key/Makefile
1451 1452
 	sample/Makefile
1452 1453
 ])
1453 1454
 AC_CONFIG_FILES([tests/t_client.sh], [chmod +x tests/t_client.sh])
... ...
@@ -2,4 +2,7 @@ AUTOMAKE_OPTIONS = foreign
2 2
 
3 3
 if ENABLE_UNITTESTS
4 4
 SUBDIRS = example_test openvpn plugins
5
+if OPENSSL_ENGINE
6
+SUBDIRS += engine-key
7
+endif
5 8
 endif
6 9
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+AUTOMAKE_OPTIONS = foreign
1
+
2
+check_LTLIBRARIES = libtestengine.la
3
+conffiles = openssl.cnf
4
+
5
+TESTS_ENVIRONMENT = srcdir="$(abs_srcdir)"; \
6
+	builddir="$(abs_builddir)"; \
7
+	top_builddir="$(top_builddir)"; \
8
+	top_srcdir="$(top_srcdir)"; \
9
+	export srcdir builddir top_builddir top_srcdir;
10
+
11
+TESTS = check_engine_keys.sh
12
+check_engine_keys.sh: $(conffiles)
13
+
14
+clean-local:
15
+	rm -f $(conffiles)
16
+
17
+$(builddir)/openssl.cnf: $(srcdir)/openssl.cnf.in
18
+	sed "s|ABSBUILDDIR|$(abs_builddir)|" < $< > $@
19
+
20
+libtestengine_la_SOURCES = libtestengine.c
21
+libtestengine_la_LDFLAGS = @TEST_LDFLAGS@ -rpath /lib -shrext .so
22
+libtestengine_la_CFLAGS = @TEST_CFLAGS@ -I$(openvpn_srcdir) -I$(compat_srcdir)
0 23
new file mode 100755
... ...
@@ -0,0 +1,30 @@
0
+#!/bin/sh
1
+
2
+OPENSSL_CONF="${builddir}/openssl.cnf"
3
+export OPENSSL_CONF
4
+
5
+password='AT3S4PASSWD'
6
+
7
+key="${builddir}/client.key"
8
+pwdfile="${builddir}/passwd"
9
+
10
+# create an engine key for us
11
+sed 's/PRIVATE KEY/TEST ENGINE KEY/' < ${top_srcdir}/sample/sample-keys/client.key > ${key}
12
+echo "$password" > $pwdfile
13
+
14
+# note here we've induced a mismatch in the client key and the server
15
+# cert which openvpn should report and die.  Check that it does.  Note
16
+# also that this mismatch depends on openssl not openvpn, so it is
17
+# somewhat fragile
18
+${top_builddir}/src/openvpn/openvpn --cd ${top_srcdir}/sample --config sample-config-files/loopback-server --engine testengine --key ${key} --askpass $pwdfile > log.txt 2>&1
19
+
20
+# first off check we died because of a key mismatch.  If this doesn't
21
+# pass, suspect openssl of returning different messages and update the
22
+# test accordingly
23
+grep -q 'X509_check_private_key:key values mismatch' log.txt || { echo "Key mismatch not detected"; exit 1; }
24
+
25
+# now look for the engine prints (these are under our control)
26
+grep -q 'ENGINE: engine_init called' log.txt || { echo "Engine initialization not detected"; exit 1; }
27
+grep -q 'ENGINE: engine_load_key called' log.txt || { echo "Key was not loaded from engine"; exit 1; }
28
+grep -q "ENGINE: engine_load_key got password ${password}" log.txt || { echo "Key password was not retrieved by the engine"; exit 1; }
29
+exit 0
0 30
new file mode 100644
... ...
@@ -0,0 +1,101 @@
0
+#include <string.h>
1
+#include <openssl/engine.h>
2
+#include <openssl/evp.h>
3
+#include <openssl/pem.h>
4
+
5
+static char *engine_id = "testengine";
6
+static char *engine_name = "Engine for testing openvpn engine key support";
7
+
8
+static int is_initialized = 0;
9
+
10
+static int engine_init(ENGINE *e)
11
+{
12
+	is_initialized = 1;
13
+	fprintf(stderr, "ENGINE: engine_init called\n");
14
+	return 1;
15
+}
16
+
17
+static int engine_finish(ENGINE *e)
18
+{
19
+	fprintf(stderr, "ENGINE: engine_finsh called\n");
20
+	is_initialized = 0;
21
+	return 1;
22
+}
23
+
24
+static EVP_PKEY *engine_load_key(ENGINE *e, const char *key_id,
25
+				 UI_METHOD *ui_method, void *cb_data)
26
+{
27
+	BIO *b;
28
+	EVP_PKEY *pkey;
29
+	PKCS8_PRIV_KEY_INFO *p8inf;
30
+	UI *ui;
31
+	char auth[256];
32
+
33
+	fprintf(stderr, "ENGINE: engine_load_key called\n");
34
+
35
+	if (!is_initialized) {
36
+		fprintf(stderr, "Load Key called without correct initialization\n");
37
+		return NULL;
38
+	}
39
+	b = BIO_new_file(key_id, "r");
40
+	if (!b) {
41
+		fprintf(stderr, "File %s does not exist or cannot be read\n", key_id);
42
+		return 0;
43
+	}
44
+	/* Basically read an EVP_PKEY private key file with different
45
+	 * PEM guards --- we are a test engine */
46
+	p8inf = PEM_ASN1_read_bio((d2i_of_void *)d2i_PKCS8_PRIV_KEY_INFO,
47
+				 "TEST ENGINE KEY", b,
48
+				 NULL, NULL, NULL);
49
+	BIO_free(b);
50
+	if (!p8inf) {
51
+		fprintf(stderr, "Failed to read engine private key\n");
52
+		return NULL;
53
+	}
54
+	pkey = EVP_PKCS82PKEY(p8inf);
55
+
56
+	/* now we have a private key, pretend it had a password
57
+	 * this verifies the password makes it through openvpn OK */
58
+	ui = UI_new();
59
+
60
+	if (ui_method)
61
+		UI_set_method(ui, ui_method);
62
+
63
+	UI_add_user_data(ui, cb_data);
64
+
65
+	if (UI_add_input_string(ui, "enter test engine key",
66
+				UI_INPUT_FLAG_DEFAULT_PWD,
67
+				auth, 0, sizeof(auth)) == 0) {
68
+		fprintf(stderr, "UI_add_input_string failed\n");
69
+		goto out;
70
+	}
71
+
72
+	if (UI_process(ui)) {
73
+		fprintf(stderr, "UI_process failed\n");
74
+		goto out;
75
+	}
76
+
77
+	fprintf(stderr, "ENGINE: engine_load_key got password %s\n", auth);
78
+
79
+ out:
80
+	UI_free(ui);
81
+
82
+	return pkey;
83
+}
84
+
85
+
86
+static int engine_bind_fn(ENGINE *e, const char *id)
87
+{
88
+	if (id && strcmp(id, engine_id) != 0)
89
+		return 0;
90
+	if (!ENGINE_set_id(e, engine_id) ||
91
+	    !ENGINE_set_name(e, engine_name) ||
92
+	    !ENGINE_set_init_function(e, engine_init) ||
93
+	    !ENGINE_set_finish_function(e, engine_finish) ||
94
+	    !ENGINE_set_load_privkey_function(e, engine_load_key))
95
+		return 0;
96
+	return 1;
97
+}
98
+
99
+IMPLEMENT_DYNAMIC_CHECK_FN()
100
+IMPLEMENT_DYNAMIC_BIND_FN(engine_bind_fn)
0 101
new file mode 100644
... ...
@@ -0,0 +1,12 @@
0
+HOME		= .
1
+openssl_conf	= openssl_init
2
+
3
+[req]
4
+[openssl_init]
5
+engines		= engines_section
6
+
7
+[engines_section]
8
+testengine	= testengine_section
9
+
10
+[testengine_section]
11
+dynamic_path	= ABSBUILDDIR/.libs/libtestengine.so