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