OpenVPN
test_pkcs11.c
Go to the documentation of this file.
1/*
2 * OpenVPN -- An application to securely tunnel IP networks
3 * over a single UDP port, with support for SSL/TLS-based
4 * session authentication and key exchange,
5 * packet encryption, packet authentication, and
6 * packet compression.
7 *
8 * Copyright (C) 2023-2024 Selva Nair <selva.nair@gmail.com>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by the
12 * Free Software Foundation, either version 2 of the License,
13 * or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License along
21 * with this program; if not, write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 */
24
25#ifdef HAVE_CONFIG_H
26#include "config.h"
27#endif
28
29#include "syshead.h"
30#include "manage.h"
31#include "base64.h"
32#include "run_command.h"
33#include "xkey_common.h"
34#include "cert_data.h"
35#include "pkcs11.h"
36#include "ssl.h"
37
38#include <setjmp.h>
39#include <cmocka.h>
40#include "test_common.h"
41
42#define token_name "Test Token"
43#define PIN "12345"
44#define HASHSIZE 20
45
46
47struct management *management; /* global */
48
49/* replacement for crypto_print_openssl_errors() */
50void
51crypto_print_openssl_errors(const unsigned int flags)
52{
53 unsigned long e;
54 while ((e = ERR_get_error()))
55 {
56 msg(flags, "OpenSSL error %lu: %s\n", e, ERR_error_string(e, NULL));
57 }
58}
59
60/* stubs for some unused functions instead of pulling in too many dependencies */
61int
62parse_line(const char *line, char **p, const int n, const char *file,
63 const int line_num, int msglevel, struct gc_arena *gc)
64{
65 assert_true(0);
66 return 0;
67}
68char *
70{
71 return "N/A";
72}
73void
75{
76 assert_true(0);
77}
78#if defined(ENABLE_SYSTEMD)
79bool
80query_user_exec_systemd(void)
81{
82 assert_true(0);
83 return false;
84}
85#endif
86bool
88{
89 assert_true(0);
90 return false;
91}
92void
93query_user_add(char *prompt, size_t prompt_len, char *resp, size_t resp_len, bool echo)
94{
95 (void) prompt;
96 (void) prompt_len;
97 (void) resp;
98 (void) resp_len;
99 (void) echo;
100 assert_true(0);
101}
102void
103purge_user_pass(struct user_pass *up, const bool force)
104{
105 (void) force;
106 secure_memzero(up, sizeof(*up));
107}
108
109char *
110management_query_pk_sig(struct management *man, const char *b64_data,
111 const char *algorithm)
112{
113 (void) man;
114 (void) b64_data;
115 (void) algorithm;
116 return NULL;
117}
118
119int
120digest_sign_verify(EVP_PKEY *privkey, EVP_PKEY *pubkey);
121
122/* Test certificate database: data for cert1, cert2 .. key1, key2 etc.
123 * are defined in cert_data.h
124 */
125static struct test_cert
126{
127 const char *const cert; /* certificate as PEM */
128 const char *const key; /* key as unencrypted PEM */
129 const char *const cname; /* common-name */
130 const char *const issuer; /* issuer common-name */
131 const char *const friendly_name; /* identifies certs loaded to the store -- keep unique */
132 uint8_t hash[HASHSIZE]; /* SHA1 fingerprint: computed and filled in later */
133 char *p11_id; /* PKCS#11 id -- filled in later */
134} certs[5];
135
137static char softhsm2_tokens_path[] = "softhsm2_tokens_XXXXXX";
138static char softhsm2_conf_path[] = "softhsm2_conf_XXXXXX";
140static const char *pkcs11_id_current;
141struct env_set *es;
142
143/* Fill-in certs[] array */
144void
146{
147 struct test_cert certs_local[] = {
148 {cert1, key1, cname1, "OVPN TEST CA1", "OVPN Test Cert 1", {0}, NULL},
149 {cert2, key2, cname2, "OVPN TEST CA2", "OVPN Test Cert 2", {0}, NULL},
150 {cert3, key3, cname3, "OVPN TEST CA1", "OVPN Test Cert 3", {0}, NULL},
151 {cert4, key4, cname4, "OVPN TEST CA2", "OVPN Test Cert 4", {0}, NULL},
152 {0}
153 };
154 assert(sizeof(certs_local) == sizeof(certs));
155 memcpy(certs, certs_local, sizeof(certs_local));
156}
157
158/* Intercept get_user_pass for PIN and other prompts */
159bool
160get_user_pass_cr(struct user_pass *up, const char *auth_file, const char *prefix,
161 const unsigned int flags, const char *unused)
162{
163 (void) unused;
164 bool ret = true;
165 if (!strcmp(prefix, "pkcs11-id-request") && flags&GET_USER_PASS_NEED_STR)
166 {
167 assert_true(pkcs11_id_management);
168 strncpynt(up->password, pkcs11_id_current, sizeof(up->password));
169 }
170 else if (flags & GET_USER_PASS_PASSWORD_ONLY)
171 {
172 snprintf(up->password, sizeof(up->password), "%s", PIN);
173 }
174 else
175 {
176 msg(M_NONFATAL, "ERROR: get_user_pass called with unknown request <%s> ignored\n", prefix);
177 ret = false;
178 }
179
180 return ret;
181}
182
183/* Compute sha1 hash of a X509 certificate */
184static void
185sha1_fingerprint(X509 *x509, uint8_t *hash, int capacity)
186{
187 assert_true(capacity >= EVP_MD_size(EVP_sha1()));
188 assert_int_equal(X509_digest(x509, EVP_sha1(), hash, NULL), 1);
189}
190
191#if defined(HAVE_XKEY_PROVIDER)
193OSSL_PROVIDER *prov[2];
194#endif
195
196static int
197init(void **state)
198{
199 (void) state;
200
201 umask(0077); /* ensure all files and directories we create get user only access */
202 char config[256];
203
205 if (!mkdtemp(softhsm2_tokens_path))
206 {
207 fail_msg("make tmpdir using template <%s> failed (error = %d)", softhsm2_tokens_path, errno);
208 }
209
210 int fd = mkstemp(softhsm2_conf_path);
211 if (fd < 0)
212 {
213 fail_msg("make tmpfile using template <%s> failed (error = %d)", softhsm2_conf_path, errno);
214 }
215 snprintf(config, sizeof(config), "directories.tokendir=%s/",
217 assert_int_equal(write(fd, config, strlen(config)), strlen(config));
218 close(fd);
219
220 /* environment */
221 setenv("SOFTHSM2_CONF", softhsm2_conf_path, 1);
222 es = env_set_create(NULL);
223 setenv_str(es, "SOFTHSM2_CONF", softhsm2_conf_path);
224 setenv_str(es, "GNUTLS_PIN", PIN);
225
226 /* init the token using the temporary location as storage */
227 struct argv a = argv_new();
228 argv_printf(&a, "%s --init-token --free --label \"%s\" --so-pin %s --pin %s",
229 SOFTHSM2_UTIL_PATH, token_name, PIN, PIN);
230 assert_true(openvpn_execve_check(&a, es, 0, "Failed to initialize token"));
231
232 /* Import certificates and keys in our test database into the token */
233 char cert[] = "cert_XXXXXX";
234 char key[] = "key_XXXXXX";
235 int cert_fd = mkstemp(cert);
236 int key_fd = mkstemp(key);
237 if (cert_fd < 0 || key_fd < 0)
238 {
239 fail_msg("make tmpfile for certificate or key data failed (error = %d)", errno);
240 }
241
242 for (struct test_cert *c = certs; c->cert; c++)
243 {
244 /* fill-in the hash of the cert */
245 BIO *buf = BIO_new_mem_buf(c->cert, -1);
246 X509 *x509 = NULL;
247 if (buf)
248 {
249 x509 = PEM_read_bio_X509(buf, NULL, NULL, NULL);
250 BIO_free(buf);
251 }
252 assert_non_null(x509);
253 sha1_fingerprint(x509, c->hash, HASHSIZE);
254 X509_free(x509);
255
256 /* we load all cert/key pairs even if expired as
257 * signing should still work */
258 assert_int_equal(write(cert_fd, c->cert, strlen(c->cert)), strlen(c->cert));
259 assert_int_equal(write(key_fd, c->key, strlen(c->key)), strlen(c->key));
260
261 argv_free(&a);
262 a = argv_new();
263 /* Use numcerts+1 as a unique id of the object -- same id for matching cert and key */
264 argv_printf(&a, "%s --provider %s --load-certificate %s --label \"%s\" --id %08x --login --write",
265 P11TOOL_PATH, SOFTHSM2_MODULE_PATH, cert, c->friendly_name, num_certs+1);
266 assert_true(openvpn_execve_check(&a, es, 0, "Failed to upload certificate into token"));
267
268 argv_free(&a);
269 a = argv_new();
270 argv_printf(&a, "%s --provider %s --load-privkey %s --label \"%s\" --id %08x --login --write",
271 P11TOOL_PATH, SOFTHSM2_MODULE_PATH, key, c->friendly_name, num_certs+1);
272 assert_true(openvpn_execve_check(&a, es, 0, "Failed to upload key into token"));
273
274 assert_int_equal(ftruncate(cert_fd, 0), 0);
275 assert_int_equal(ftruncate(key_fd, 0), 0);
276 assert_int_equal(lseek(cert_fd, 0, SEEK_SET), 0);
277 assert_int_equal(lseek(key_fd, 0, SEEK_SET), 0);
278 num_certs++;
279 }
280
281 argv_free(&a);
282 close(cert_fd);
283 close(key_fd);
284 unlink(cert);
285 unlink(key);
286 return 0;
287}
288
289static int
290cleanup(void **state)
291{
292 (void) state;
293 struct argv a = argv_new();
294
295 argv_printf(&a, "%s --delete-token --token \"%s\"", SOFTHSM2_UTIL_PATH, token_name);
296 assert_true(openvpn_execve_check(&a, es, 0, "Failed to delete token"));
297 argv_free(&a);
298
299 rmdir(softhsm2_tokens_path); /* this must be empty after delete token */
300 unlink(softhsm2_conf_path);
301 for (struct test_cert *c = certs; c->cert; c++)
302 {
303 free(c->p11_id);
304 c->p11_id = NULL;
305 }
307 return 0;
308}
309
310static int
311setup_pkcs11(void **state)
312{
313#if defined(HAVE_XKEY_PROVIDER)
314 /* Initialize providers in a way matching what OpenVPN core does */
315 tls_libctx = OSSL_LIB_CTX_new();
316 prov[0] = OSSL_PROVIDER_load(tls_libctx, "default");
317 OSSL_PROVIDER_add_builtin(tls_libctx, "ovpn.xkey", xkey_provider_init);
318 prov[1] = OSSL_PROVIDER_load(tls_libctx, "ovpn.xkey");
319 assert_non_null(prov[1]);
320
321 /* set default propq as we do in ssl_openssl.c */
322 EVP_set_default_properties(tls_libctx, "?provider!=ovpn.xkey");
323#endif
324 pkcs11_initialize(true, 0); /* protected auth enabled, pin-cache = 0 */
325 pkcs11_addProvider(SOFTHSM2_MODULE_PATH, false, 0, false);
326 return 0;
327}
328
329static int
330teardown_pkcs11(void **state)
331{
332 pkcs11_terminate();
333#if defined(HAVE_XKEY_PROVIDER)
334 for (size_t i = 0; i < SIZE(prov); i++)
335 {
336 if (prov[i])
337 {
338 OSSL_PROVIDER_unload(prov[i]);
339 prov[i] = NULL;
340 }
341 }
342 OSSL_LIB_CTX_free(tls_libctx);
343#endif
344 return 0;
345}
346
347static struct test_cert *
348lookup_cert_byhash(uint8_t *sha1)
349{
350 struct test_cert *c = certs;
351 while (c->cert && memcmp(c->hash, sha1, HASHSIZE))
352 {
353 c++;
354 }
355 return c->cert ? c : NULL;
356}
357
358/* Enumerate usable items in the token and collect their pkcs11-ids */
359static void
360test_pkcs11_ids(void **state)
361{
362 char *p11_id = NULL;
363 char *base64 = NULL;
364
365 int n = pkcs11_management_id_count();
366 assert_int_equal(n, num_certs);
367
368 for (int i = 0; i < n; i++)
369 {
370 X509 *x509 = NULL;
371 uint8_t sha1[HASHSIZE];
372
373 /* We use the management interface functions as a quick way
374 * to enumerate objects available for private key operations */
375 if (!pkcs11_management_id_get(i, &p11_id, &base64))
376 {
377 fail_msg("Failed to get pkcs11-id for index (%d) from pkcs11-helper", i);
378 }
379 /* decode the base64 data and convert to X509 and get its sha1 fingerprint */
380 unsigned char *der = malloc(strlen(base64));
381 assert_non_null(der);
382 int derlen = openvpn_base64_decode(base64, der, strlen(base64));
383 free(base64);
384 assert_true(derlen > 0);
385
386 const unsigned char *ppin = der; /* alias needed as d2i_X509 alters the pointer */
387 assert_non_null(d2i_X509(&x509, &ppin, derlen));
388 sha1_fingerprint(x509, sha1, HASHSIZE);
389 X509_free(x509);
390 free(der);
391
392 /* Save the pkcs11-id of this ceritificate in our database */
393 struct test_cert *c = lookup_cert_byhash(sha1);
394 assert_non_null(c);
395 c->p11_id = p11_id; /* p11_id is freed in cleanup */
396 assert_memory_equal(c->hash, sha1, HASHSIZE);
397 }
398 /* check whether all certs in our db were found by pkcs11-helper*/
399 for (struct test_cert *c = certs; c->cert; c++)
400 {
401 if (!c->p11_id)
402 {
403 fail_msg("Certificate <%s> not enumerated by pkcs11-helper", c->friendly_name);
404 }
405 }
406}
407
408/* For each available pkcs11-id, load it into an SSL_CTX
409 * and test signing with it.
410 */
411static void
413{
414 (void) state;
415 struct tls_root_ctx tls_ctx = {};
416 uint8_t sha1[HASHSIZE];
417 for (struct test_cert *c = certs; c->cert; c++)
418 {
419#ifdef HAVE_XKEY_PROVIDER
420 tls_ctx.ctx = SSL_CTX_new_ex(tls_libctx, NULL, SSLv23_client_method());
421#else
422 tls_ctx.ctx = SSL_CTX_new(SSLv23_client_method());
423#endif
425 {
426 /* The management callback will return pkcs11_id_current as the
427 * selection. Set it here as the current certificate's p11_id
428 */
429 pkcs11_id_current = c->p11_id;
430 tls_ctx_use_pkcs11(&tls_ctx, 1, NULL);
431 }
432 else
433 {
434 /* directly use c->p11_id */
435 tls_ctx_use_pkcs11(&tls_ctx, 0, c->p11_id);
436 }
437
438 /* check that the cert set in SSL_CTX is what we intended */
439 X509 *x509 = SSL_CTX_get0_certificate(tls_ctx.ctx);
440 assert_non_null(x509);
441 sha1_fingerprint(x509, sha1, HASHSIZE);
442 assert_memory_equal(sha1, c->hash, HASHSIZE);
443
444 /* Test signing with the private key in SSL_CTX */
445 EVP_PKEY *pubkey = X509_get0_pubkey(x509);
446 EVP_PKEY *privkey = SSL_CTX_get0_privatekey(tls_ctx.ctx);
447 assert_non_null(pubkey);
448 assert_non_null(privkey);
449#ifdef HAVE_XKEY_PROVIDER
450 /* this will exercise signing via pkcs11 backend */
451 assert_int_equal(digest_sign_verify(privkey, pubkey), 1);
452#else
453 if (!SSL_CTX_check_private_key(tls_ctx.ctx))
454 {
455 fail_msg("Certificate and private key in ssl_ctx do not match for <%s>", c->friendly_name);
456 return;
457 }
458#endif
459 SSL_CTX_free(tls_ctx.ctx);
460 }
461}
462
463/* same test as test_tls_ctx_use_pkcs11, with id selected via management i/f */
464static void
470
471int
472main(void)
473{
475 const struct CMUnitTest tests[] = {
476 cmocka_unit_test_setup_teardown(test_pkcs11_ids, setup_pkcs11,
478 cmocka_unit_test_setup_teardown(test_tls_ctx_use_pkcs11, setup_pkcs11,
480 cmocka_unit_test_setup_teardown(test_tls_ctx_use_pkcs11__management, setup_pkcs11,
482 };
483 int ret = cmocka_run_group_tests_name("pkcs11_tests", tests, init, cleanup);
484
485 return ret;
486}
void argv_free(struct argv *a)
Frees all memory allocations allocated by the struct argv related functions.
Definition argv.c:102
bool argv_printf(struct argv *argres, const char *format,...)
printf() variant which populates a struct argv.
Definition argv.c:440
struct argv argv_new(void)
Allocates a new struct argv and ensures it is initialised.
Definition argv.c:88
static void secure_memzero(void *data, size_t len)
Securely zeroise memory.
Definition buffer.h:414
static void strncpynt(char *dest, const char *src, size_t maxlen)
Definition buffer.h:361
static const char *const cert2
Definition cert_data.h:65
static const char *const cert3
Definition cert_data.h:86
static const char *const key3
Definition cert_data.h:108
static const char *const cert4
Definition cert_data.h:140
static const char *const cname2
Definition cert_data.h:84
static const char *const cname1
Definition cert_data.h:63
static const char *const cname3
Definition cert_data.h:138
#define cname4
Definition cert_data.h:164
static const char *const cert1
Definition cert_data.h:39
#define key2
Definition cert_data.h:82
#define key4
Definition cert_data.h:162
static const char *const key1
Definition cert_data.h:56
void env_set_destroy(struct env_set *es)
Definition env_set.c:166
void setenv_str(struct env_set *es, const char *name, const char *value)
Definition env_set.c:283
struct env_set * env_set_create(struct gc_arena *gc)
Definition env_set.c:156
@ write
#define GET_USER_PASS_PASSWORD_ONLY
Definition misc.h:111
#define GET_USER_PASS_NEED_STR
Definition misc.h:114
void OSSL_PROVIDER
void OSSL_LIB_CTX
#define SSL_CTX_new_ex(libctx, propq, method)
Reduce SSL_CTX_new_ex() to SSL_CTX_new() for OpenSSL < 3.
X509 openvpn_x509_cert_t
#define SIZE(x)
Definition basic.h:30
#define M_NONFATAL
Definition error.h:90
#define msg(flags,...)
Definition error.h:144
int openvpn_execve_check(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *error_message)
int openvpn_base64_decode(const char *str, void *data, int size)
Definition base64.c:158
Control Channel SSL/Data channel negotiation module.
OSSL_LIB_CTX * tls_libctx
Definition ssl_openssl.c:79
Definition argv.h:35
Garbage collection arena used to keep track of dynamically allocated memory.
Definition buffer.h:117
Definition list.h:57
Container for unidirectional cipher and HMAC key material.
Definition crypto.h:152
const char *const friendly_name
uint8_t hash[HASHSIZE]
const char *const cert
const char *const cname
const char *const issuer
char * p11_id
const char *const key
Structure that wraps the TLS context.
SSL_CTX * ctx
Definition ssl_openssl.h:41
char password[USER_PASS_LEN]
Definition misc.h:73
static void openvpn_unit_test_setup(void)
Sets up the environment for unit tests like making both stderr and stdout non-buffered to avoid messa...
Definition test_common.h:36
void crypto_print_openssl_errors(const unsigned int flags)
Retrieve any occurred OpenSSL errors and print those errors.
Definition test_pkcs11.c:51
struct management * management
Definition test_pkcs11.c:47
static struct test_cert certs[5]
static void sha1_fingerprint(X509 *x509, uint8_t *hash, int capacity)
void query_user_clear(void)
Wipes all data put into all of the query_user structs.
Definition test_pkcs11.c:74
bool query_user_exec_builtin(void)
Loop through configured query_user slots, using the built-in method for querying the user.
Definition test_pkcs11.c:87
#define HASHSIZE
Definition test_pkcs11.c:44
void purge_user_pass(struct user_pass *up, const bool force)
static void test_tls_ctx_use_pkcs11__management(void **state)
struct env_set * es
static void test_tls_ctx_use_pkcs11(void **state)
static int cleanup(void **state)
void init_cert_data(void)
static bool pkcs11_id_management
static int teardown_pkcs11(void **state)
int main(void)
static void test_pkcs11_ids(void **state)
int num_certs
char * x509_get_subject(openvpn_x509_cert_t *cert, struct gc_arena *gc)
Definition test_pkcs11.c:69
static int setup_pkcs11(void **state)
static const char * pkcs11_id_current
char * management_query_pk_sig(struct management *man, const char *b64_data, const char *algorithm)
#define token_name
Definition test_pkcs11.c:42
#define PIN
Definition test_pkcs11.c:43
static struct test_cert * lookup_cert_byhash(uint8_t *sha1)
static int init(void **state)
bool get_user_pass_cr(struct user_pass *up, const char *auth_file, const char *prefix, const unsigned int flags, const char *unused)
Retrieves the user credentials from various sources depending on the flags.
static char softhsm2_tokens_path[]
static char softhsm2_conf_path[]
void query_user_add(char *prompt, size_t prompt_len, char *resp, size_t resp_len, bool echo)
Adds an item to ask the user for.
Definition test_pkcs11.c:93
int parse_line(const char *line, char **p, const int n, const char *file, const int line_num, int msglevel, struct gc_arena *gc)
Definition test_pkcs11.c:62
int digest_sign_verify(EVP_PKEY *privkey, EVP_PKEY *pubkey)
struct gc_arena gc
Definition test_ssl.c:155