OpenVPN
cryptoapi.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2004 Peter 'Luna' Runestig <peter@runestig.com>
3  * Copyright (c) 2018 Selva Nair <selva.nair@gmail.com>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without modifi-
7  * cation, are permitted provided that the following conditions are met:
8  *
9  * o Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * o Redistributions in binary form must reproduce the above copyright no-
13  * tice, this list of conditions and the following disclaimer in the do-
14  * cumentation and/or other materials provided with the distribution.
15  *
16  * o The names of the contributors may not be used to endorse or promote
17  * products derived from this software without specific prior written
18  * permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LI-
24  * ABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUEN-
25  * TIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEV-
27  * ER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABI-
28  * LITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
29  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #ifdef HAVE_CONFIG_H
33 #include "config.h"
34 #endif
35 
36 #include "syshead.h"
37 
38 #ifdef ENABLE_CRYPTOAPI
39 
40 #include <openssl/ssl.h>
41 #include <openssl/evp.h>
42 #include <openssl/err.h>
43 #include <windows.h>
44 #include <wincrypt.h>
45 #include <ncrypt.h>
46 #include <stdio.h>
47 #include <ctype.h>
48 #include <assert.h>
49 
50 #include "buffer.h"
51 #include "openssl_compat.h"
52 #include "win32.h"
53 #include "xkey_common.h"
54 #include "crypto_openssl.h"
55 
56 #ifndef HAVE_XKEY_PROVIDER
57 
58 int
59 SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
60 {
61  msg(M_NONFATAL, "ERROR: this binary was built without cryptoapicert support");
62  return 0;
63 }
64 
65 #else /* HAVE_XKEY_PROVIDER */
66 
67 static XKEY_EXTERNAL_SIGN_fn xkey_cng_sign;
68 
69 typedef struct _CAPI_DATA {
70  const CERT_CONTEXT *cert_context;
71  HCRYPTPROV_OR_NCRYPT_KEY_HANDLE crypt_prov;
72  EVP_PKEY *pubkey;
73  DWORD key_spec;
74  BOOL free_crypt_prov;
75  int ref_count;
76 } CAPI_DATA;
77 
78 /*
79  * Translate OpenSSL hash OID to CNG algorithm name. Returns
80  * "UNKNOWN" for unsupported algorithms and NULL for MD5+SHA1
81  * mixed hash used in TLS 1.1 and earlier.
82  */
83 static const wchar_t *
84 cng_hash_algo(int md_type)
85 {
86  const wchar_t *alg = L"UNKNOWN";
87  switch (md_type)
88  {
89  case NID_md5:
90  alg = BCRYPT_MD5_ALGORITHM;
91  break;
92 
93  case NID_sha1:
94  alg = BCRYPT_SHA1_ALGORITHM;
95  break;
96 
97  case NID_sha256:
98  alg = BCRYPT_SHA256_ALGORITHM;
99  break;
100 
101  case NID_sha384:
102  alg = BCRYPT_SHA384_ALGORITHM;
103  break;
104 
105  case NID_sha512:
106  alg = BCRYPT_SHA512_ALGORITHM;
107  break;
108 
109  case NID_md5_sha1:
110  case 0:
111  alg = NULL;
112  break;
113 
114  default:
115  msg(M_WARN|M_INFO, "cryptoapicert: Unknown hash type NID=0x%x", md_type);
116  break;
117  }
118  return alg;
119 }
120 
121 static void
122 CAPI_DATA_free(CAPI_DATA *cd)
123 {
124  if (!cd || cd->ref_count-- > 0)
125  {
126  return;
127  }
128  if (cd->free_crypt_prov && cd->crypt_prov)
129  {
130  if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
131  {
132  NCryptFreeObject(cd->crypt_prov);
133  }
134  else
135  {
136  CryptReleaseContext(cd->crypt_prov, 0);
137  }
138  }
139  if (cd->cert_context)
140  {
141  CertFreeCertificateContext(cd->cert_context);
142  }
143  EVP_PKEY_free(cd->pubkey); /* passing NULL is okay */
144 
145  free(cd);
146 }
147 
156 int
157 parse_hexstring(const char *p, unsigned char *arr, size_t capacity)
158 {
159  int i = 0;
160  for ( ; *p && i < capacity; p += 2)
161  {
162  /* skip spaces */
163  while (*p == ' ')
164  {
165  p++;
166  }
167  if (!*p) /* ending with spaces is not an error */
168  {
169  break;
170  }
171 
172  if (!isxdigit(p[0]) || !isxdigit(p[1])
173  || sscanf(p, "%2hhx", &arr[i++]) != 1)
174  {
175  return 0;
176  }
177  }
178  return i;
179 }
180 
181 static const CERT_CONTEXT *
182 find_certificate_in_store(const char *cert_prop, HCERTSTORE cert_store)
183 {
184  /* Find, and use, the desired certificate from the store. The
185  * 'cert_prop' certificate search string can look like this:
186  * SUBJ:<certificate substring to match>
187  * THUMB:<certificate thumbprint hex value>, e.g.
188  * THUMB:f6 49 24 41 01 b4 fb 44 0c ce f4 36 ae d0 c4 c9 df 7a b6 28
189  * The first matching certificate that has not expired is returned.
190  */
191  const CERT_CONTEXT *rv = NULL;
192  DWORD find_type;
193  const void *find_param;
194  unsigned char hash[255];
195  CRYPT_HASH_BLOB blob = {.cbData = 0, .pbData = hash};
196  struct gc_arena gc = gc_new();
197 
198  if (!strncmp(cert_prop, "SUBJ:", 5))
199  {
200  /* skip the tag */
201  find_param = wide_string(cert_prop + 5, &gc);
202  find_type = CERT_FIND_SUBJECT_STR_W;
203  }
204  else if (!strncmp(cert_prop, "ISSUER:", 7))
205  {
206  find_param = wide_string(cert_prop + 7, &gc);
207  find_type = CERT_FIND_ISSUER_STR_W;
208  }
209  else if (!strncmp(cert_prop, "THUMB:", 6))
210  {
211  find_type = CERT_FIND_HASH;
212  find_param = &blob;
213 
214  blob.cbData = parse_hexstring(cert_prop + 6, hash, sizeof(hash));
215  if (blob.cbData == 0)
216  {
217  msg(M_WARN|M_INFO, "WARNING: cryptoapicert: error parsing <%s>.", cert_prop);
218  goto out;
219  }
220  }
221  else
222  {
223  msg(M_NONFATAL, "Error in cryptoapicert: unsupported certificate specification <%s>", cert_prop);
224  goto out;
225  }
226 
227  while (true)
228  {
229  int validity = 1;
230  /* this frees previous rv, if not NULL */
231  rv = CertFindCertificateInStore(cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
232  0, find_type, find_param, rv);
233  if (rv)
234  {
235  validity = CertVerifyTimeValidity(NULL, rv->pCertInfo);
236  }
237  if (!rv || validity == 0)
238  {
239  break;
240  }
241  msg(M_WARN|M_INFO, "WARNING: cryptoapicert: ignoring certificate in store %s.",
242  validity < 0 ? "not yet valid" : "that has expired");
243  }
244 
245 out:
246  gc_free(&gc);
247  return rv;
248 }
249 
251 static int
252 xkey_cng_ec_sign(CAPI_DATA *cd, unsigned char *sig, size_t *siglen, const unsigned char *tbs,
253  size_t tbslen)
254 {
255  DWORD len = *siglen;
256 
257  msg(D_LOW, "Signing using NCryptSignHash with EC key");
258 
259  DWORD status = NCryptSignHash(cd->crypt_prov, NULL, (BYTE *)tbs, tbslen, sig, len, &len, 0);
260 
261  if (status != ERROR_SUCCESS)
262  {
263  SetLastError(status);
264  msg(M_NONFATAL|M_ERRNO, "Error in cryptoapicert: ECDSA signature using CNG failed.");
265  return 0;
266  }
267 
268  /* NCryptSignHash returns r|s -- convert to DER encoded buffer expected by OpenSSL */
269  int derlen = ecdsa_bin2der(sig, (int) len, *siglen);
270  if (derlen <= 0)
271  {
272  return 0;
273  }
274  *siglen = derlen;
275  return 1;
276 }
277 
279 static int
280 xkey_cng_rsa_sign(CAPI_DATA *cd, unsigned char *sig, size_t *siglen, const unsigned char *tbs,
281  size_t tbslen, XKEY_SIGALG sigalg)
282 {
283  dmsg(D_LOW, "In xkey_cng_rsa_sign");
284 
285  ASSERT(cd);
286  ASSERT(sig);
287  ASSERT(tbs);
288 
289  DWORD status = ERROR_SUCCESS;
290  DWORD len = 0;
291 
292  const wchar_t *hashalg = cng_hash_algo(OBJ_sn2nid(sigalg.mdname));
293 
294  if (hashalg && wcscmp(hashalg, L"UNKNOWN") == 0)
295  {
296  msg(M_NONFATAL, "Error in cryptoapicert: Unknown hash name <%s>", sigalg.mdname);
297  return 0;
298  }
299 
300  if (!strcmp(sigalg.padmode, "pkcs1"))
301  {
302  msg(D_LOW, "Signing using NCryptSignHash with PKCS1 padding: hashalg <%s>", sigalg.mdname);
303 
304  BCRYPT_PKCS1_PADDING_INFO padinfo = {hashalg};
305  status = NCryptSignHash(cd->crypt_prov, &padinfo, (BYTE *)tbs, (DWORD)tbslen,
306  sig, (DWORD)*siglen, &len, BCRYPT_PAD_PKCS1);
307  }
308  else if (!strcmp(sigalg.padmode, "pss"))
309  {
310  int saltlen = tbslen; /* digest size by default */
311  if (!strcmp(sigalg.saltlen, "max"))
312  {
313  saltlen = xkey_max_saltlen(EVP_PKEY_bits(cd->pubkey), tbslen);
314  if (saltlen < 0)
315  {
316  msg(M_NONFATAL, "Error in cryptoapicert: invalid salt length (%d)", saltlen);
317  return 0;
318  }
319  }
320 
321  msg(D_LOW, "Signing using NCryptSignHash with PSS padding: hashalg <%s>, saltlen <%d>",
322  sigalg.mdname, saltlen);
323 
324  BCRYPT_PSS_PADDING_INFO padinfo = {hashalg, (DWORD) saltlen}; /* cast is safe as saltlen >= 0 */
325  status = NCryptSignHash(cd->crypt_prov, &padinfo, (BYTE *)tbs, (DWORD) tbslen,
326  sig, (DWORD)*siglen, &len, BCRYPT_PAD_PSS);
327  }
328  else
329  {
330  msg(M_NONFATAL, "Error in cryptoapicert: Unsupported padding mode <%s>", sigalg.padmode);
331  return 0;
332  }
333 
334  if (status != ERROR_SUCCESS)
335  {
336  SetLastError(status);
337  msg(M_NONFATAL|M_ERRNO, "Error in cryptoapicert: RSA signature using CNG failed.");
338  return 0;
339  }
340 
341  *siglen = len;
342  return (*siglen > 0);
343 }
344 
346 static int
347 xkey_cng_sign(void *handle, unsigned char *sig, size_t *siglen, const unsigned char *tbs,
348  size_t tbslen, XKEY_SIGALG sigalg)
349 {
350  dmsg(D_LOW, "In xkey_cng_sign");
351 
352  CAPI_DATA *cd = handle;
353  ASSERT(cd);
354  ASSERT(sig);
355  ASSERT(tbs);
356 
357  unsigned char mdbuf[EVP_MAX_MD_SIZE];
358  size_t buflen = _countof(mdbuf);
359 
360  /* compute digest if required */
361  if (!strcmp(sigalg.op, "DigestSign"))
362  {
363  if (!xkey_digest(tbs, tbslen, mdbuf, &buflen, sigalg.mdname))
364  {
365  return 0;
366  }
367  tbs = mdbuf;
368  tbslen = buflen;
369  }
370 
371  if (!strcmp(sigalg.keytype, "EC"))
372  {
373  return xkey_cng_ec_sign(cd, sig, siglen, tbs, tbslen);
374  }
375  else if (!strcmp(sigalg.keytype, "RSA"))
376  {
377  return xkey_cng_rsa_sign(cd, sig, siglen, tbs, tbslen, sigalg);
378  }
379  else
380  {
381  return 0; /* Unknown keytype -- should not happen */
382  }
383 }
384 
385 static char *
386 get_cert_name(const CERT_CONTEXT *cc, struct gc_arena *gc)
387 {
388  DWORD len = CertGetNameStringW(cc, CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL, NULL, 0);
389  char *name = NULL;
390  if (len)
391  {
392  wchar_t *wname = gc_malloc(len*sizeof(wchar_t), false, gc);
393  if (!wname
394  || CertGetNameStringW(cc, CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL, wname, len) == 0)
395  {
396  return NULL;
397  }
398  name = utf16to8(wname, gc);
399  }
400  return name;
401 }
402 
409 static int
410 Load_CryptoAPI_certificate(const char *cert_prop, X509 **cert, EVP_PKEY **privkey)
411 {
412 
413  HCERTSTORE cs;
414  CAPI_DATA *cd = calloc(1, sizeof(*cd));
415  struct gc_arena gc = gc_new();
416 
417  if (cd == NULL)
418  {
419  msg(M_NONFATAL, "Error in cryptoapicert: out of memory");
420  goto err;
421  }
422  /* search CURRENT_USER first, then LOCAL_MACHINE */
423  cs = CertOpenStore((LPCSTR) CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER
424  |CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG, L"MY");
425  if (cs == NULL)
426  {
427  msg(M_NONFATAL|M_ERRNO, "Error in cryptoapicert: failed to open user certficate store");
428  goto err;
429  }
430  cd->cert_context = find_certificate_in_store(cert_prop, cs);
431  CertCloseStore(cs, 0);
432  if (!cd->cert_context)
433  {
434  cs = CertOpenStore((LPCSTR) CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE
435  |CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG, L"MY");
436  if (cs == NULL)
437  {
438  msg(M_NONFATAL|M_ERRNO, "Error in cryptoapicert: failed to open machine certficate store");
439  goto err;
440  }
441  cd->cert_context = find_certificate_in_store(cert_prop, cs);
442  CertCloseStore(cs, 0);
443  if (cd->cert_context == NULL)
444  {
445  msg(M_NONFATAL, "Error in cryptoapicert: certificate matching <%s> not found", cert_prop);
446  goto err;
447  }
448  }
449 
450  /* try to log the "name" of the selected certificate */
451  char *cert_name = get_cert_name(cd->cert_context, &gc);
452  if (cert_name)
453  {
454  msg(D_LOW, "cryptapicert: using certificate with name <%s>", cert_name);
455  }
456 
457  /* cert_context->pbCertEncoded is the cert X509 DER encoded. */
458  *cert = d2i_X509(NULL, (const unsigned char **) &cd->cert_context->pbCertEncoded,
459  cd->cert_context->cbCertEncoded);
460  if (*cert == NULL)
461  {
462  msg(M_NONFATAL, "Error in cryptoapicert: X509 certificate decode failed");
463  goto err;
464  }
465 
466  /* set up stuff to use the private key */
467  /* We support NCRYPT key handles only */
468  DWORD flags = CRYPT_ACQUIRE_COMPARE_KEY_FLAG
469  | CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG;
470  if (!CryptAcquireCertificatePrivateKey(cd->cert_context, flags, NULL,
471  &cd->crypt_prov, &cd->key_spec, &cd->free_crypt_prov))
472  {
473  /* private key may be in a token not available, or incompatible with CNG */
474  msg(M_NONFATAL|M_ERRNO, "Error in cryptoapicert: failed to acquire key. Key not present or "
475  "is in a legacy token not supported by Windows CNG API");
476  X509_free(*cert);
477  goto err;
478  }
479 
480  /* the public key */
481  EVP_PKEY *pkey = X509_get_pubkey(*cert);
482  cd->pubkey = pkey; /* will be freed with cd */
483 
484  *privkey = xkey_load_generic_key(tls_libctx, cd, pkey,
485  xkey_cng_sign, (XKEY_PRIVKEY_FREE_fn *) CAPI_DATA_free);
486  gc_free(&gc);
487  return 1; /* do not free cd -- its kept by xkey provider */
488 
489 err:
490  CAPI_DATA_free(cd);
491  gc_free(&gc);
492  return 0;
493 }
494 
495 int
496 SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
497 {
498  X509 *cert = NULL;
499  EVP_PKEY *privkey = NULL;
500  int ret = 0;
501 
502  if (!Load_CryptoAPI_certificate(cert_prop, &cert, &privkey))
503  {
504  return ret;
505  }
506  if (SSL_CTX_use_certificate(ssl_ctx, cert)
507  && SSL_CTX_use_PrivateKey(ssl_ctx, privkey))
508  {
510  ret = 1;
511  }
512 
513  /* Always free cert and privkey even if retained by ssl_ctx as
514  * they are reference counted */
515  X509_free(cert);
516  EVP_PKEY_free(privkey);
517  return ret;
518 }
519 
520 #endif /* HAVE_XKEY_PROVIDER */
521 #endif /* _WIN32 */
M_INFO
#define M_INFO
Definition: errlevel.h:55
gc_new
static struct gc_arena gc_new(void)
Definition: buffer.h:1031
M_ERRNO
#define M_ERRNO
Definition: error.h:100
win32.h
M_NONFATAL
#define M_NONFATAL
Definition: error.h:96
hash
Definition: list.h:58
xkey_common.h
dmsg
#define dmsg(flags,...)
Definition: error.h:154
D_LOW
#define D_LOW
Definition: errlevel.h:97
crypto_openssl.h
ASSERT
#define ASSERT(x)
Definition: error.h:201
M_WARN
#define M_WARN
Definition: error.h:97
wide_string
WCHAR * wide_string(const char *utf8, struct gc_arena *gc)
Definition: win32-util.c:41
openssl_compat.h
buffer.h
syshead.h
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
gc_malloc
void * gc_malloc(size_t size, bool clear, struct gc_arena *a)
Definition: buffer.c:380
status
static SERVICE_STATUS status
Definition: interactive.c:52
SSL_CTX_use_CryptoAPI_certificate
int SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
Definition: cryptoapi.c:59
gc_free
static void gc_free(struct gc_arena *a)
Definition: buffer.h:1039
utf16to8
char * utf16to8(const wchar_t *utf16, struct gc_arena *gc)
Definition: win32-util.c:50
config.h
tls_libctx
OSSL_LIB_CTX * tls_libctx
Definition: ssl_openssl.c:72
msg
#define msg(flags,...)
Definition: error.h:150