OpenVPN
test_cryptoapi.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 "integer.h"
31#include "xkey_common.h"
32#include "cert_data.h"
33
34#if defined(HAVE_XKEY_PROVIDER) && defined(ENABLE_CRYPTOAPI)
35#include <setjmp.h>
36#include <cmocka.h>
37#include <openssl/bio.h>
38#include <openssl/pem.h>
39#include <openssl/core_names.h>
40#include <openssl/evp.h>
41#include <openssl/pkcs12.h>
42#include "test_common.h"
43
44#include <cryptoapi.h>
45#include <cryptoapi.c> /* pull-in the whole file to test static functions */
46
47struct management *management; /* global */
48static OSSL_PROVIDER *prov[2];
49
50/* mock a management function that xkey_provider needs */
51char *
52management_query_pk_sig(struct management *man, const char *b64_data, const char *algorithm)
53{
54 (void)man;
55 (void)b64_data;
56 (void)algorithm;
57 return NULL;
58}
59
60/* replacement for crypto_print_openssl_errors() */
61void
62crypto_print_openssl_errors(const unsigned int flags)
63{
64 unsigned long e;
65 while ((e = ERR_get_error()))
66 {
67 msg(flags, "OpenSSL error %lu: %s", e, ERR_error_string(e, NULL));
68 }
69}
70
71/* tls_libctx is defined in ssl_openssl.c which we do not want to compile in */
73
74#ifndef _countof
75#define _countof(x) sizeof((x)) / sizeof(*(x))
76#endif
77
78/* test data */
79static const uint8_t test_hash[] = { 0x77, 0x38, 0x65, 0x00, 0x1e, 0x96, 0x48, 0xc6, 0x57, 0x0b,
80 0xae, 0xc0, 0xb7, 0x96, 0xf9, 0x66, 0x4d, 0x5f, 0xd0, 0xb7 };
81
82/* valid test strings to test with and without embedded and trailing spaces */
83static const char *valid_str[] = {
84 "773865001e9648c6570baec0b796f9664d5fd0b7",
85 " 77 386500 1e 96 48 c6570b aec0b7 96f9664d5f d0 b7",
86 " 773865001e9648c6570baec0b796f9664d5fd0b7 ",
87};
88
89/* some invalid strings to test */
90static const char *invalid_str[] = {
91 "773 865001e9648c6570baec0b796f9664d5fd0b7", /* space within byte */
92 "77:38:65001e9648c6570baec0b796f9664d5fd0b7", /* invalid separator */
93 "7738x5001e9648c6570baec0b796f9664d5fd0b7", /* non hex character */
94};
95
96/* Test certificate database: data for cert1, cert2 .. key1, key2 etc.
97 * are stashed away in cert_data.h
98 */
99static struct test_cert
100{
101 const char *const cert; /* certificate as PEM */
102 const char *const key; /* key as unencrypted PEM */
103 const char *const cname; /* common-name */
104 const char *const issuer; /* issuer common-name */
105 const char *const friendly_name; /* identifies certs loaded to the store -- keep unique */
106 const char *hash; /* SHA1 fingerprint */
107 int valid; /* nonzero if certificate has not expired */
108} certs[5];
109
110static bool certs_loaded;
111static HCERTSTORE user_store;
112
113/* Fill-in certs[] array */
114void
115init_cert_data(void)
116{
117 struct test_cert certs_local[] = {
118 { cert1, key1, cname1, "OVPN TEST CA1", "OVPN Test Cert 1", hash1, 1 },
119 { cert2, key2, cname2, "OVPN TEST CA2", "OVPN Test Cert 2", hash2, 1 },
120 { cert3, key3, cname3, "OVPN TEST CA1", "OVPN Test Cert 3", hash3, 1 },
121 { cert4, key4, cname4, "OVPN TEST CA2", "OVPN Test Cert 4", hash4, 0 },
122 { 0 }
123 };
124 assert(sizeof(certs_local) == sizeof(certs));
125 memcpy(certs, certs_local, sizeof(certs_local));
126}
127
128/* Lookup a certificate in our certificate/key db */
129static struct test_cert *
130lookup_cert(const char *friendly_name)
131{
132 struct test_cert *c = certs;
133 while (c->cert && strcmp(c->friendly_name, friendly_name))
134 {
135 c++;
136 }
137 return c->cert ? c : NULL;
138}
139
140/* import sample certificates into windows cert store */
141static void
142import_certs(void **state)
143{
144 (void)state;
145 if (certs_loaded)
146 {
147 return;
148 }
150 user_store =
151 CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0,
152 CERT_SYSTEM_STORE_CURRENT_USER | CERT_STORE_OPEN_EXISTING_FLAG, L"MY");
153 assert_non_null(user_store);
154 for (struct test_cert *c = certs; c->cert; c++)
155 {
156 /* Convert PEM cert & key to pkcs12 and import */
157 const char *pass = "opensesame"; /* some password */
158 const wchar_t *wpass = L"opensesame"; /* same as a wide string */
159
160 X509 *x509 = NULL;
161 EVP_PKEY *pkey = NULL;
162
163 BIO *buf = BIO_new_mem_buf(c->cert, -1);
164 if (buf)
165 {
166 x509 = PEM_read_bio_X509(buf, NULL, NULL, NULL);
167 }
168 BIO_free(buf);
169
170 buf = BIO_new_mem_buf(c->key, -1);
171 if (buf)
172 {
173 pkey = PEM_read_bio_PrivateKey(buf, NULL, NULL, NULL);
174 }
175 BIO_free(buf);
176
177 if (!x509 || !pkey)
178 {
179 fail_msg("Failed to parse certificate/key data: <%s>", c->friendly_name);
180 return;
181 }
182
183 PKCS12 *p12 = PKCS12_create(pass, c->friendly_name, pkey, x509, NULL, 0, 0, 0, 0, 0);
184 X509_free(x509);
185 EVP_PKEY_free(pkey);
186 if (!p12)
187 {
188 fail_msg("Failed to convert to PKCS12: <%s>", c->friendly_name);
189 return;
190 }
191
192 CRYPT_DATA_BLOB blob = { .cbData = 0, .pbData = NULL };
193 int len = i2d_PKCS12(p12, &blob.pbData); /* pbData will be allocated by OpenSSL */
194 if (len <= 0)
195 {
196 fail_msg("Failed to DER encode PKCS12: <%s>", c->friendly_name);
197 return;
198 }
199 blob.cbData = len;
200
201 DWORD flags = PKCS12_ALLOW_OVERWRITE_KEY | PKCS12_ALWAYS_CNG_KSP;
202 HCERTSTORE tmp_store = PFXImportCertStore(&blob, wpass, flags);
203 PKCS12_free(p12);
204 OPENSSL_free(blob.pbData);
205
206 assert_non_null(tmp_store);
207
208 /* The cert and key get imported into a temp store. We have to move it to
209 * user's store to accumulate all certs in one place and use them for tests.
210 * It seems there is no API to directly import a p12 blob into an existing store.
211 * Nothing in Windows is ever easy.
212 */
213
214 const CERT_CONTEXT *ctx = CertEnumCertificatesInStore(tmp_store, NULL);
215 assert_non_null(ctx);
216 bool added = CertAddCertificateContextToStore(user_store, ctx,
217 CERT_STORE_ADD_REPLACE_EXISTING, NULL);
218 assert_true(added);
219
220 CertFreeCertificateContext(ctx);
221 CertCloseStore(tmp_store, 0);
222 }
223 certs_loaded = true;
224}
225
226static int
227cleanup(void **state)
228{
229 (void)state;
230 struct gc_arena gc = gc_new();
231 if (user_store) /* delete all certs we imported */
232 {
233 const CERT_CONTEXT *ctx = NULL;
234 while ((ctx = CertEnumCertificatesInStore(user_store, ctx)))
235 {
236 char *friendly_name = get_cert_name(ctx, &gc);
237 if (!lookup_cert(friendly_name)) /* not our cert */
238 {
239 continue;
240 }
241
242 /* create a dup context to not destroy the state of loop iterator */
243 const CERT_CONTEXT *ctx_dup = CertDuplicateCertificateContext(ctx);
244 if (ctx_dup)
245 {
246 CertDeleteCertificateFromStore(ctx_dup);
247 /* the above also releases ctx_dup */
248 }
249 }
250 CertCloseStore(user_store, 0);
251 }
252 user_store = NULL;
253 certs_loaded = false;
254 gc_free(&gc);
255 return 0;
256}
257
258static void
259test_find_cert_bythumb(void **state)
260{
261 (void)state;
262 char select_string[64];
263 struct gc_arena gc = gc_new();
264 const CERT_CONTEXT *ctx;
265
266 import_certs(state); /* a no-op if already imported */
267 assert_non_null(user_store);
268
269 for (struct test_cert *c = certs; c->cert; c++)
270 {
271 snprintf(select_string, sizeof(select_string), "THUMB:%s", c->hash);
272 ctx = find_certificate_in_store(select_string, user_store);
273 if (ctx)
274 {
275 /* check we got the right certificate and is valid */
276 assert_int_equal(c->valid, 1);
277 char *friendly_name = get_cert_name(ctx, &gc);
278 assert_string_equal(c->friendly_name, friendly_name);
279 CertFreeCertificateContext(ctx);
280 }
281 else
282 {
283 /* find should fail only if the certificate has expired */
284 assert_int_equal(c->valid, 0);
285 }
286 }
287
288 gc_free(&gc);
289}
290
291static void
292test_find_cert_byname(void **state)
293{
294 (void)state;
295 char select_string[64];
296 struct gc_arena gc = gc_new();
297 const CERT_CONTEXT *ctx;
298
299 import_certs(state); /* a no-op if already imported */
300 assert_non_null(user_store);
301
302 for (struct test_cert *c = certs; c->cert; c++)
303 {
304 snprintf(select_string, sizeof(select_string), "SUBJ:%s", c->cname);
305 ctx = find_certificate_in_store(select_string, user_store);
306 /* In this case we expect a successful return as there is at least one valid
307 * cert that matches the common name. But the returned cert may not exactly match
308 * c->cert as multiple certs with same common names exist in the db. We check that
309 * the return cert is one from our db, has a matching common name and is valid.
310 */
311 assert_non_null(ctx);
312
313 char *friendly_name = get_cert_name(ctx, &gc);
314 struct test_cert *found = lookup_cert(friendly_name);
315 assert_non_null(found);
316 assert_string_equal(found->cname, c->cname);
317 assert_int_equal(found->valid, 1);
318 CertFreeCertificateContext(ctx);
319 }
320
321 gc_free(&gc);
322}
323
324static void
325test_find_cert_byissuer(void **state)
326{
327 (void)state;
328 char select_string[64];
329 struct gc_arena gc = gc_new();
330 const CERT_CONTEXT *ctx;
331
332 import_certs(state); /* a no-op if already imported */
333 assert_non_null(user_store);
334
335 for (struct test_cert *c = certs; c->cert; c++)
336 {
337 snprintf(select_string, sizeof(select_string), "ISSUER:%s", c->issuer);
338 ctx = find_certificate_in_store(select_string, user_store);
339 /* In this case we expect a successful return as there is at least one valid
340 * cert that matches the issuer. But the returned cert may not exactly match
341 * c->cert as multiple certs with same issuer exist in the db. We check that
342 * the returned cert is one from our db, has a matching issuer name and is valid.
343 */
344 assert_non_null(ctx);
345
346 char *friendly_name = get_cert_name(ctx, &gc);
347 struct test_cert *found = lookup_cert(friendly_name);
348 assert_non_null(found);
349 assert_string_equal(found->issuer, c->issuer);
350 assert_int_equal(found->valid, 1);
351 CertFreeCertificateContext(ctx);
352 }
353
354 gc_free(&gc);
355}
356
357static int
358setup_xkey_provider(void **state)
359{
360 (void)state;
361 /* Initialize providers in a way matching what OpenVPN core does */
362 tls_libctx = OSSL_LIB_CTX_new();
363 prov[0] = OSSL_PROVIDER_load(tls_libctx, "default");
364 OSSL_PROVIDER_add_builtin(tls_libctx, "ovpn.xkey", xkey_provider_init);
365 prov[1] = OSSL_PROVIDER_load(tls_libctx, "ovpn.xkey");
366
367 /* set default propq as we do in ssl_openssl.c */
368 EVP_set_default_properties(tls_libctx, "?provider!=ovpn.xkey");
369 return 0;
370}
371
372static int
373teardown_xkey_provider(void **state)
374{
375 (void)state;
376 for (size_t i = 0; i < _countof(prov); i++)
377 {
378 if (prov[i])
379 {
380 OSSL_PROVIDER_unload(prov[i]);
381 prov[i] = NULL;
382 }
383 }
384 OSSL_LIB_CTX_free(tls_libctx);
385 tls_libctx = NULL;
386 return 0;
387}
388
389int digest_sign_verify(EVP_PKEY *privkey, EVP_PKEY *pubkey);
390
391/* Load sample certificates & keys, sign a test message using
392 * them and verify the signature.
393 */
394void
395test_cryptoapi_sign(void **state)
396{
397 (void)state;
398 char select_string[64];
399 X509 *x509 = NULL;
400 EVP_PKEY *privkey = NULL;
401
402 import_certs(state); /* a no-op if already imported */
403 assert_true(certs_loaded);
404
405 for (struct test_cert *c = certs; c->cert; c++)
406 {
407 if (c->valid == 0)
408 {
409 continue;
410 }
411 snprintf(select_string, sizeof(select_string), "THUMB:%s", c->hash);
412 if (Load_CryptoAPI_certificate(select_string, &x509, &privkey) != 1)
413 {
414 fail_msg("Load_CryptoAPI_certificate failed: <%s>", c->friendly_name);
415 return;
416 }
417 EVP_PKEY *pubkey = X509_get0_pubkey(x509);
418 assert_non_null(pubkey);
419 assert_int_equal(digest_sign_verify(privkey, pubkey), 1);
420 X509_free(x509);
421 EVP_PKEY_free(privkey);
422 }
423}
424
425/* Test that SSL_CTX_use_Cryptoapi_certificate() sets a matching certificate
426 * and key in ssl_ctx.
427 */
428void
429test_ssl_ctx_use_cryptoapicert(void **state)
430{
431 (void)state;
432 char select_string[64];
433
434 import_certs(state); /* a no-op if already imported */
435 assert_true(certs_loaded);
436
437 for (struct test_cert *c = certs; c->cert; c++)
438 {
439 if (c->valid == 0)
440 {
441 continue;
442 }
443 SSL_CTX *ssl_ctx = SSL_CTX_new_ex(tls_libctx, NULL, SSLv23_client_method());
444 assert_non_null(ssl_ctx);
445
446 snprintf(select_string, sizeof(select_string), "THUMB:%s", c->hash);
447 if (!SSL_CTX_use_CryptoAPI_certificate(ssl_ctx, select_string))
448 {
449 fail_msg("SSL_CTX_use_CryptoAPI_certificate failed: <%s>", c->friendly_name);
450 return;
451 }
452 /* Use OpenSSL to check that the cert and private key in ssl_ctx "match" */
453 if (!SSL_CTX_check_private_key(ssl_ctx))
454 {
455 fail_msg("Certificate and private key in ssl_ctx do not match for <%s>",
456 c->friendly_name);
457 return;
458 }
459
460 SSL_CTX_free(ssl_ctx);
461 }
462}
463
464static void
465test_parse_hexstring(void **state)
466{
467 unsigned char hash[255];
468 (void)state;
469
470 for (int i = 0; i < _countof(valid_str); i++)
471 {
472 int len = parse_hexstring(valid_str[i], hash, _countof(hash));
473 assert_int_equal(len, sizeof(test_hash));
474 assert_memory_equal(hash, test_hash, sizeof(test_hash));
475 memset(hash, 0, _countof(hash));
476 }
477
478 for (int i = 0; i < _countof(invalid_str); i++)
479 {
480 int len = parse_hexstring(invalid_str[i], hash, _countof(hash));
481 assert_int_equal(len, 0);
482 }
483}
484
485int
486main(void)
487{
489 const struct CMUnitTest tests[] = {
490 cmocka_unit_test(test_parse_hexstring),
491 cmocka_unit_test(import_certs),
492 cmocka_unit_test(test_find_cert_bythumb),
493 cmocka_unit_test(test_find_cert_byname),
494 cmocka_unit_test(test_find_cert_byissuer),
495 cmocka_unit_test_setup_teardown(test_cryptoapi_sign, setup_xkey_provider,
496 teardown_xkey_provider),
497 cmocka_unit_test_setup_teardown(test_ssl_ctx_use_cryptoapicert, setup_xkey_provider,
498 teardown_xkey_provider),
499 };
500
501 int ret = cmocka_run_group_tests_name("cryptoapi tests", tests, NULL, cleanup);
502
503 return ret;
504}
505
506#else /* ifdef HAVE_XKEY_PROVIDER */
507
508int
509main(void)
510{
511 return 0;
512}
513
514#endif /* ifdef HAVE_XKEY_PROVIDER */
static void gc_free(struct gc_arena *a)
Definition buffer.h:1015
static struct gc_arena gc_new(void)
Definition buffer.h:1007
static const char *const cert2
Definition cert_data.h:63
static const char *const hash2
Definition cert_data.h:81
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 hash3
Definition cert_data.h:134
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
static const char *const hash4
Definition cert_data.h:160
#define key4
Definition cert_data.h:159
static const char *const key1
Definition cert_data.h:55
static const char *const hash1
Definition cert_data.h:60
void crypto_print_openssl_errors(const unsigned int flags)
Retrieve any occurred OpenSSL errors and print those errors.
int SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
Definition cryptoapi.c:59
char * management_query_pk_sig(struct management *man, const char *b64_data, const char *algorithm)
Definition manage.c:3768
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 msg(flags,...)
Definition error.h:150
OSSL_LIB_CTX * tls_libctx
Definition ssl_openssl.c:78
Garbage collection arena used to keep track of dynamically allocated memory.
Definition buffer.h:116
Definition list.h:56
const char *const friendly_name
uint8_t hash[HASHSIZE]
const char *const cert
const char *const cname
const char *const issuer
const char *const key
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
int main(void)
static struct test_cert certs[5]
static int cleanup(void **state)
void init_cert_data(void)
int digest_sign_verify(EVP_PKEY *privkey, EVP_PKEY *pubkey)
struct gc_arena gc
Definition test_ssl.c:154