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-2026 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, see <https://www.gnu.org/licenses/>.
22 */
23
24#ifdef HAVE_CONFIG_H
25#include "config.h"
26#endif
27
28#include "syshead.h"
29#include "manage.h"
30#include "base64.h"
31#include "run_command.h"
32#include "xkey_common.h"
33#include "cert_data.h"
34#include "pkcs11.h"
35#include "ssl.h"
36
37#include <setjmp.h>
38#include <cmocka.h>
39#include "test_common.h"
40
41#define token_name "Test Token"
42#define PIN "12345"
43#define HASHSIZE 20
44
45
46struct management *management; /* global */
47
48#if defined(ENABLE_CRYPTO_OPENSSL)
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", e, ERR_error_string(e, NULL));
57 }
58}
59#endif
60
61/* stubs for some unused functions instead of pulling in too many dependencies */
62int
63parse_line(const char *line, char **p, const int n, const char *file, const int line_num,
64 msglvl_t msglevel, struct gc_arena *gc)
65{
66 assert_true(0);
67 return 0;
68}
69char *
71{
72 return "N/A";
73}
74void
76{
77 assert_true(0);
78}
79#if defined(ENABLE_SYSTEMD)
80bool
81query_user_exec_systemd(void)
82{
83 assert_true(0);
84 return false;
85}
86#endif
87bool
89{
90 assert_true(0);
91 return false;
92}
93void
94query_user_add(char *prompt, size_t prompt_len, char *resp, size_t resp_len, bool echo)
95{
96 (void)prompt;
97 (void)prompt_len;
98 (void)resp;
99 (void)resp_len;
100 (void)echo;
101 assert_true(0);
102}
103void
104purge_user_pass(struct user_pass *up, const bool force)
105{
106 (void)force;
107 secure_memzero(up, sizeof(*up));
108}
109
110char *
111management_query_pk_sig(struct management *man, const char *b64_data, const char *algorithm)
112{
113 (void)man;
114 (void)b64_data;
115 (void)algorithm;
116 return NULL;
117}
118
119int digest_sign_verify(EVP_PKEY *privkey, EVP_PKEY *pubkey);
120
121/* Test certificate database: data for cert1, cert2 .. key1, key2 etc.
122 * are defined in cert_data.h
123 */
124static struct test_cert
125{
126 const char *const cert; /* certificate as PEM */
127 const char *const key; /* key as unencrypted PEM */
128 const char *const cname; /* common-name */
129 const char *const issuer; /* issuer common-name */
130 const char *const friendly_name; /* identifies certs loaded to the store -- keep unique */
131 uint8_t hash[HASHSIZE]; /* SHA1 fingerprint: computed and filled in later */
132 char *p11_id; /* PKCS#11 id -- filled in later */
133} certs[5];
134
136static char softhsm2_tokens_path[] = "softhsm2_tokens_XXXXXX";
137static char softhsm2_conf_path[] = "softhsm2_conf_XXXXXX";
139static const char *pkcs11_id_current;
140struct env_set *es;
141
142/* Fill-in certs[] array */
143void
145{
146 struct test_cert certs_local[] = {
147 { cert1, privkey1, cname1, "OVPN TEST CA1", "OVPN Test Cert 1", { 0 }, NULL },
148 { cert2, privkey2, cname2, "OVPN TEST CA2", "OVPN Test Cert 2", { 0 }, NULL },
149 { cert3, privkey3, cname3, "OVPN TEST CA1", "OVPN Test Cert 3", { 0 }, NULL },
150 { cert4, privkey4, cname4, "OVPN TEST CA2", "OVPN Test Cert 4", { 0 }, NULL },
151 { 0 }
152 };
153 assert_int_equal(sizeof(certs_local), sizeof(certs));
154 memcpy(certs, certs_local, sizeof(certs_local));
155}
156
157/* Intercept get_user_pass for PIN and other prompts */
158bool
159get_user_pass_cr(struct user_pass *up, const char *auth_file, const char *prefix,
160 const unsigned int flags, const char *unused)
161{
162 (void)unused;
163 bool ret = true;
164 if (!strcmp(prefix, "pkcs11-id-request") && flags & GET_USER_PASS_NEED_STR)
165 {
166 assert_true(pkcs11_id_management);
167 strncpynt(up->password, pkcs11_id_current, sizeof(up->password));
168 }
169 else if (flags & GET_USER_PASS_PASSWORD_ONLY)
170 {
171 snprintf(up->password, sizeof(up->password), "%s", PIN);
172 }
173 else
174 {
175 msg(M_NONFATAL, "ERROR: get_user_pass called with unknown request <%s> ignored", prefix);
176 ret = false;
177 }
178
179 return ret;
180}
181
182/* Compute sha1 hash of a X509 certificate */
183static void
184sha1_fingerprint(X509 *x509, uint8_t *hash, int capacity)
185{
186 assert_true(capacity >= EVP_MD_size(EVP_sha1()));
187 assert_int_equal(X509_digest(x509, EVP_sha1(), hash, NULL), 1);
188}
189
190#if defined(HAVE_XKEY_PROVIDER)
192OSSL_PROVIDER *prov[2];
193#endif
194
195static int
196init(void **state)
197{
198 (void)state;
199
200 umask(0077); /* ensure all files and directories we create get user only access */
201 char config[256];
202
204 if (!mkdtemp(softhsm2_tokens_path))
205 {
206 fail_msg("make tmpdir using template <%s> failed (error = %d)", softhsm2_tokens_path,
207 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/", softhsm2_tokens_path);
216 assert_int_equal(write(fd, config, strlen(config)), strlen(config));
217 close(fd);
218
219 /* environment */
220 setenv("SOFTHSM2_CONF", softhsm2_conf_path, 1);
221 es = env_set_create(NULL);
222 setenv_str(es, "SOFTHSM2_CONF", softhsm2_conf_path);
223 setenv_str(es, "GNUTLS_PIN", PIN);
224
225 /* init the token using the temporary location as storage */
226 struct argv a = argv_new();
227 argv_printf(&a, "%s --init-token --free --label \"%s\" --so-pin %s --pin %s",
228 SOFTHSM2_UTIL_PATH, token_name, PIN, PIN);
229 assert_true(openvpn_execve_check(&a, es, 0, "Failed to initialize token"));
230
231 /* Import certificates and keys in our test database into the token */
232 char cert[] = "cert_XXXXXX";
233 char key[] = "key_XXXXXX";
234 int cert_fd = mkstemp(cert);
235 int key_fd = mkstemp(key);
236 if (cert_fd < 0 || key_fd < 0)
237 {
238 fail_msg("make tmpfile for certificate or key data failed (error = %d)", errno);
239 }
240
241 for (struct test_cert *c = certs; c->cert; c++)
242 {
243 /* fill-in the hash of the cert */
244 BIO *buf = BIO_new_mem_buf(c->cert, -1);
245 X509 *x509 = NULL;
246 if (buf)
247 {
248 x509 = PEM_read_bio_X509(buf, NULL, NULL, NULL);
249 BIO_free(buf);
250 }
251 assert_non_null(x509);
252 sha1_fingerprint(x509, c->hash, HASHSIZE);
253 X509_free(x509);
254
255 /* we load all cert/key pairs even if expired as
256 * signing should still work */
257 assert_int_equal(write(cert_fd, c->cert, strlen(c->cert)), strlen(c->cert));
258 assert_int_equal(write(key_fd, c->key, strlen(c->key)), strlen(c->key));
259
260 argv_free(&a);
261 a = argv_new();
262 /* Use numcerts+1 as a unique id of the object -- same id for matching cert and key */
264 &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,
271 "%s --provider %s --load-privkey %s --label \"%s\" --id %08x --login --write",
272 P11TOOL_PATH, SOFTHSM2_MODULE_PATH, key, c->friendly_name, num_certs + 1);
273 assert_true(openvpn_execve_check(&a, es, 0, "Failed to upload key into token"));
274
275 assert_int_equal(ftruncate(cert_fd, 0), 0);
276 assert_int_equal(ftruncate(key_fd, 0), 0);
277 assert_int_equal(lseek(cert_fd, 0, SEEK_SET), 0);
278 assert_int_equal(lseek(key_fd, 0, SEEK_SET), 0);
279 num_certs++;
280 }
281
282 argv_free(&a);
283 close(cert_fd);
284 close(key_fd);
285 unlink(cert);
286 unlink(key);
287 return 0;
288}
289
290static int
291cleanup(void **state)
292{
293 (void)state;
294 struct argv a = argv_new();
295
296 argv_printf(&a, "%s --delete-token --token \"%s\"", SOFTHSM2_UTIL_PATH, token_name);
297 assert_true(openvpn_execve_check(&a, es, 0, "Failed to delete token"));
298 argv_free(&a);
299
300 rmdir(softhsm2_tokens_path); /* this must be empty after delete token */
301 unlink(softhsm2_conf_path);
302 for (struct test_cert *c = certs; c->cert; c++)
303 {
304 free(c->p11_id);
305 c->p11_id = NULL;
306 }
308 return 0;
309}
310
311static int
312setup_pkcs11(void **state)
313{
314#if defined(HAVE_XKEY_PROVIDER)
315 /* Initialize providers in a way matching what OpenVPN core does */
316 tls_libctx = OSSL_LIB_CTX_new();
317 prov[0] = OSSL_PROVIDER_load(tls_libctx, "default");
318 OSSL_PROVIDER_add_builtin(tls_libctx, "ovpn.xkey", xkey_provider_init);
319 prov[1] = OSSL_PROVIDER_load(tls_libctx, "ovpn.xkey");
320 assert_non_null(prov[1]);
321
322 /* set default propq as we do in ssl_openssl.c */
323 EVP_set_default_properties(tls_libctx, "?provider!=ovpn.xkey");
324#endif
325 pkcs11_initialize(true, 60); /* protected auth enabled, pin-cache = 60s */
326 pkcs11_addProvider(SOFTHSM2_MODULE_PATH, false, 0, false);
327 return 0;
328}
329
330static int
331teardown_pkcs11(void **state)
332{
333 pkcs11_terminate();
334#if defined(HAVE_XKEY_PROVIDER)
335 for (size_t i = 0; i < SIZE(prov); i++)
336 {
337 if (prov[i])
338 {
339 OSSL_PROVIDER_unload(prov[i]);
340 prov[i] = NULL;
341 }
342 }
343 OSSL_LIB_CTX_free(tls_libctx);
344#endif
345 return 0;
346}
347
348static struct test_cert *
349lookup_cert_byhash(uint8_t *sha1)
350{
351 struct test_cert *c = certs;
352 while (c->cert && memcmp(c->hash, sha1, HASHSIZE))
353 {
354 c++;
355 }
356 return c->cert ? c : NULL;
357}
358
359/* Enumerate usable items in the token and collect their pkcs11-ids */
360static void
361test_pkcs11_ids(void **state)
362{
363 char *p11_id = NULL;
364 char *base64 = NULL;
365
366 int n = pkcs11_management_id_count();
367 assert_int_equal(n, num_certs);
368
369 for (int i = 0; i < n; i++)
370 {
371 X509 *x509 = NULL;
372 uint8_t sha1[HASHSIZE];
373
374 /* We use the management interface functions as a quick way
375 * to enumerate objects available for private key operations */
376 if (!pkcs11_management_id_get(i, &p11_id, &base64))
377 {
378 fail_msg("Failed to get pkcs11-id for index (%d) from pkcs11-helper", i);
379 }
380 /* decode the base64 data and convert to X509 and get its sha1 fingerprint */
381 unsigned char *der = malloc(strlen(base64));
382 assert_non_null(der);
383 int derlen = openvpn_base64_decode(base64, der, (int)strlen(base64));
384 free(base64);
385 assert_true(derlen > 0);
386
387 const unsigned char *ppin = der; /* alias needed as d2i_X509 alters the pointer */
388 assert_non_null(d2i_X509(&x509, &ppin, derlen));
389 sha1_fingerprint(x509, sha1, HASHSIZE);
390 X509_free(x509);
391 free(der);
392
393 /* Save the pkcs11-id of this ceritificate in our database */
394 struct test_cert *c = lookup_cert_byhash(sha1);
395 assert_non_null(c);
396 c->p11_id = p11_id; /* p11_id is freed in cleanup */
397 assert_memory_equal(c->hash, sha1, HASHSIZE);
398 }
399 /* check whether all certs in our db were found by pkcs11-helper*/
400 for (struct test_cert *c = certs; c->cert; c++)
401 {
402 if (!c->p11_id)
403 {
404 fail_msg("Certificate <%s> not enumerated by pkcs11-helper", c->friendly_name);
405 }
406 }
407}
408
409/* For each available pkcs11-id, load it into an SSL_CTX
410 * and test signing with it.
411 */
412static void
414{
415 (void)state;
416 struct tls_root_ctx tls_ctx = { 0 };
417 uint8_t sha1[HASHSIZE];
418 for (struct test_cert *c = certs; c->cert; c++)
419 {
420#ifdef HAVE_XKEY_PROVIDER
421 tls_ctx.ctx = SSL_CTX_new_ex(tls_libctx, NULL, SSLv23_client_method());
422#else
423 tls_ctx.ctx = SSL_CTX_new(SSLv23_client_method());
424#endif
426 {
427 /* The management callback will return pkcs11_id_current as the
428 * selection. Set it here as the current certificate's p11_id
429 */
430 pkcs11_id_current = c->p11_id;
431 tls_ctx_use_pkcs11(&tls_ctx, 1, NULL);
432 }
433 else
434 {
435 /* directly use c->p11_id */
436 tls_ctx_use_pkcs11(&tls_ctx, 0, c->p11_id);
437 }
438
439 /* check that the cert set in SSL_CTX is what we intended */
440 X509 *x509 = SSL_CTX_get0_certificate(tls_ctx.ctx);
441 assert_non_null(x509);
442 sha1_fingerprint(x509, sha1, HASHSIZE);
443 assert_memory_equal(sha1, c->hash, HASHSIZE);
444
445 /* Test signing with the private key in SSL_CTX */
446 EVP_PKEY *pubkey = X509_get0_pubkey(x509);
447 EVP_PKEY *privkey = SSL_CTX_get0_privatekey(tls_ctx.ctx);
448 assert_non_null(pubkey);
449 assert_non_null(privkey);
450#ifdef HAVE_XKEY_PROVIDER
451 /* this will exercise signing via pkcs11 backend */
452 assert_int_equal(digest_sign_verify(privkey, pubkey), 1);
453#else
454 if (!SSL_CTX_check_private_key(tls_ctx.ctx))
455 {
456 fail_msg("Certificate and private key in ssl_ctx do not match for <%s>",
457 c->friendly_name);
458 return;
459 }
460#endif
461 SSL_CTX_free(tls_ctx.ctx);
462 }
463}
464
465/* same test as test_tls_ctx_use_pkcs11, with id selected via management i/f */
466static void
472
473int
474main(void)
475{
477 const struct CMUnitTest tests[] = {
478 cmocka_unit_test_setup_teardown(test_pkcs11_ids, setup_pkcs11, teardown_pkcs11),
479 cmocka_unit_test_setup_teardown(test_tls_ctx_use_pkcs11, setup_pkcs11, teardown_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:101
bool argv_printf(struct argv *argres, const char *format,...)
printf() variant which populates a struct argv.
Definition argv.c:438
struct argv argv_new(void)
Allocates a new struct argv and ensures it is initialised.
Definition argv.c:87
static void secure_memzero(void *data, size_t len)
Securely zeroise memory.
Definition buffer.h:705
static void strncpynt(char *dest, const char *src, size_t maxlen)
Like strncpy() but always null-terminates the destination.
Definition buffer.h:646
#define privkey2
Definition cert_data.h:80
static const char *const cert2
Definition cert_data.h:63
static const char *const cert3
Definition cert_data.h:84
static const char *const cert4
Definition cert_data.h:137
static const char *const cname2
Definition cert_data.h:82
static const char *const cname1
Definition cert_data.h:61
static const char *const cname3
Definition cert_data.h:135
static const char *const privkey1
Definition cert_data.h:55
#define cname4
Definition cert_data.h:161
static const char *const cert1
Definition cert_data.h:38
#define privkey4
Definition cert_data.h:159
static const char *const privkey3
Definition cert_data.h:106
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:307
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:115
#define GET_USER_PASS_NEED_STR
Definition misc.h:118
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:29
#define M_NONFATAL
Definition error.h:91
#define msg(flags,...)
Definition error.h:152
unsigned int msglvl_t
Definition error.h:77
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:160
Control Channel SSL/Data channel negotiation module.
OSSL_LIB_CTX * tls_libctx
Definition ssl_openssl.c:78
Definition argv.h:35
Garbage collection arena used to keep track of dynamically allocated memory.
Definition buffer.h:127
Definition list.h:56
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:71
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:61
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:46
int parse_line(const char *line, char **p, const int n, const char *file, const int line_num, msglvl_t msglevel, struct gc_arena *gc)
Definition test_pkcs11.c:63
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:75
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:88
#define HASHSIZE
Definition test_pkcs11.c:43
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:70
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:41
#define PIN
Definition test_pkcs11.c:42
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)
Definition test_pkcs11.c:94
int digest_sign_verify(EVP_PKEY *privkey, EVP_PKEY *pubkey)
struct gc_arena gc
Definition test_ssl.c:122