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