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 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 
48 struct management *management; /* global */
49 static OSSL_PROVIDER *prov[2];
50 
51 /* mock a management function that xkey_provider needs */
52 char *
53 management_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() */
63 void
64 crypto_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 */
81 static 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 */
87 static 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 */
94 static 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  */
103 static 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 
114 static bool certs_loaded;
115 static HCERTSTORE user_store;
116 
117 /* Fill-in certs[] array */
118 void
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 */
133 static struct test_cert *
134 lookup_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 */
145 static void
146 import_certs(void **state)
147 {
148  (void) state;
149  if (certs_loaded)
150  {
151  return;
152  }
153  init_cert_data();
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 
229 static int
230 cleanup(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 
261 static void
262 test_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  openvpn_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 
294 static void
295 test_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  openvpn_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 
327 static void
328 test_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  openvpn_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 
360 static int
361 setup_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 
375 static int
376 teardown_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 
392 int 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  */
397 void
398 test_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  openvpn_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  */
431 void
432 test_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  openvpn_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 
466 static void
467 test_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 
487 int
488 main(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 
510 int
511 main(void)
512 {
513  return 0;
514 }
515 
516 #endif /* ifdef HAVE_XKEY_PROVIDER */
cert_data.h
cert2
static const char *const cert2
Definition: cert_data.h:65
cert1
static const char *const cert1
Definition: cert_data.h:39
gc_new
static struct gc_arena gc_new(void)
Definition: buffer.h:1031
key2
#define key2
Definition: cert_data.h:82
openvpn_unit_test_setup
static void openvpn_unit_test_setup()
Sets up the environment for unit tests like making both stderr and stdout non-buffered to avoid messa...
Definition: test_common.h:36
manage.h
hash
Definition: list.h:58
test_common.h
cname4
#define cname4
Definition: cert_data.h:164
key1
static const char *const key1
Definition: cert_data.h:56
xkey_common.h
test_cert::friendly_name
const char *const friendly_name
Definition: test_pkcs11.c:123
hash4
static const char *const hash4
Definition: cert_data.h:163
certs
static struct test_cert certs[5]
SSL_CTX_new_ex
#define SSL_CTX_new_ex(libctx, propq, method)
Reduce SSL_CTX_new_ex() to SSL_CTX_new() for OpenSSL < 3.
Definition: openssl_compat.h:770
test_cert::cert
const char *const cert
Definition: test_pkcs11.c:119
cryptoapi.h
X509_get0_pubkey
static EVP_PKEY * X509_get0_pubkey(const X509 *x)
Get the public key from a X509 certificate.
Definition: openssl_compat.h:204
test_cert
Definition: test_pkcs11.c:117
cname2
static const char *const cname2
Definition: cert_data.h:84
cryptoapi.c
cname3
static const char *const cname3
Definition: cert_data.h:138
management_query_pk_sig
char * management_query_pk_sig(struct management *man, const char *b64_data, const char *algorithm)
Definition: manage.c:3747
test_cert::hash
uint8_t hash[HASHSIZE]
Definition: test_pkcs11.c:124
key3
static const char *const key3
Definition: cert_data.h:108
hash3
static const char *const hash3
Definition: cert_data.h:137
syshead.h
cname1
static const char *const cname1
Definition: cert_data.h:63
gc_arena
Garbage collection arena used to keep track of dynamically allocated memory.
Definition: buffer.h:116
crypto_print_openssl_errors
void crypto_print_openssl_errors(const unsigned int flags)
Retrieve any occurred OpenSSL errors and print those errors.
Definition: crypto_openssl.c:238
key4
#define key4
Definition: cert_data.h:162
hash1
static const char *const hash1
Definition: cert_data.h:62
digest_sign_verify
int digest_sign_verify(EVP_PKEY *privkey, EVP_PKEY *pubkey)
main
int main(void)
Definition: test_cryptoapi.c:511
openvpn_snprintf
bool openvpn_snprintf(char *str, size_t size, const char *format,...)
Definition: buffer.c:294
SSL_CTX_use_CryptoAPI_certificate
int SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
Definition: cryptoapi.c:59
management
Definition: manage.h:335
management
struct management * management
Definition: manage.c:63
gc_free
static void gc_free(struct gc_arena *a)
Definition: buffer.h:1039
OSSL_PROVIDER
void OSSL_PROVIDER
Definition: openssl_compat.h:775
config.h
test_cert::cname
const char *const cname
Definition: test_pkcs11.c:121
tls_libctx
OSSL_LIB_CTX * tls_libctx
Definition: ssl_openssl.c:72
test_cert::key
const char *const key
Definition: test_pkcs11.c:120
test_cert::issuer
const char *const issuer
Definition: test_pkcs11.c:122
hash2
static const char *const hash2
Definition: cert_data.h:83
init_cert_data
void init_cert_data()
Definition: test_pkcs11.c:137
msg
#define msg(flags,...)
Definition: error.h:150
OSSL_LIB_CTX
void OSSL_LIB_CTX
Definition: openssl_compat.h:774
integer.h
cert4
static const char *const cert4
Definition: cert_data.h:140
cert3
static const char *const cert3
Definition: cert_data.h:86
cleanup
static int cleanup(void **state)
Definition: test_pkcs11.c:280