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
49#include "buffer.h"
50#include "cryptoapi.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
58int
59SSL_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
67static XKEY_EXTERNAL_SIGN_fn xkey_cng_sign;
68
69typedef struct _CAPI_DATA
70{
71 const CERT_CONTEXT *cert_context;
72 HCRYPTPROV_OR_NCRYPT_KEY_HANDLE crypt_prov;
73 EVP_PKEY *pubkey;
74 DWORD key_spec;
75 BOOL free_crypt_prov;
76 int ref_count;
77} CAPI_DATA;
78
79/*
80 * Translate OpenSSL hash OID to CNG algorithm name. Returns
81 * "UNKNOWN" for unsupported algorithms and NULL for MD5+SHA1
82 * mixed hash used in TLS 1.1 and earlier.
83 */
84static const wchar_t *
85cng_hash_algo(int md_type)
86{
87 const wchar_t *alg = L"UNKNOWN";
88 switch (md_type)
89 {
90 case NID_md5:
91 alg = BCRYPT_MD5_ALGORITHM;
92 break;
93
94 case NID_sha1:
95 alg = BCRYPT_SHA1_ALGORITHM;
96 break;
97
98 case NID_sha256:
99 alg = BCRYPT_SHA256_ALGORITHM;
100 break;
101
102 case NID_sha384:
103 alg = BCRYPT_SHA384_ALGORITHM;
104 break;
105
106 case NID_sha512:
107 alg = BCRYPT_SHA512_ALGORITHM;
108 break;
109
110 case NID_md5_sha1:
111 case 0:
112 alg = NULL;
113 break;
114
115 default:
116 msg(M_WARN | M_INFO, "cryptoapicert: Unknown hash type NID=0x%x", md_type);
117 break;
118 }
119 return alg;
120}
121
122static void
123CAPI_DATA_free(CAPI_DATA *cd)
124{
125 if (!cd || cd->ref_count-- > 0)
126 {
127 return;
128 }
129 if (cd->free_crypt_prov && cd->crypt_prov)
130 {
131 if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
132 {
133 NCryptFreeObject(cd->crypt_prov);
134 }
135 else
136 {
137 CryptReleaseContext(cd->crypt_prov, 0);
138 }
139 }
140 if (cd->cert_context)
141 {
142 CertFreeCertificateContext(cd->cert_context);
143 }
144 EVP_PKEY_free(cd->pubkey); /* passing NULL is okay */
145
146 free(cd);
147}
148
157static DWORD
158parse_hexstring(const char *p, unsigned char *arr, DWORD capacity)
159{
160 DWORD i = 0;
161 for (; *p && i < capacity; p += 2)
162 {
163 /* skip spaces */
164 while (*p == ' ')
165 {
166 p++;
167 }
168 if (!*p) /* ending with spaces is not an error */
169 {
170 break;
171 }
172
173 unsigned int b;
174 if (!isxdigit(p[0]) || !isxdigit(p[1]) || sscanf(p, "%2x", &b) != 1)
175 {
176 return 0;
177 }
178 arr[i++] = (unsigned char)b;
179 }
180 return i;
181}
182
183static void *
184decode_object(struct gc_arena *gc, LPCSTR struct_type, const CRYPT_OBJID_BLOB *val, DWORD flags,
185 DWORD *cb)
186{
187 /* get byte count for decoding */
188 BYTE *buf;
189 if (!CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, struct_type, val->pbData,
190 val->cbData, flags, NULL, cb))
191 {
192 return NULL;
193 }
194
195 /* do the actual decode */
196 buf = gc_malloc(*cb, false, gc);
197 if (!CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, struct_type, val->pbData,
198 val->cbData, flags, buf, cb))
199 {
200 return NULL;
201 }
202
203 return buf;
204}
205
206static const CRYPT_OID_INFO *
207find_oid(DWORD keytype, const void *key, DWORD groupid)
208{
209 const CRYPT_OID_INFO *info = NULL;
210
211 /* try proper resolve, also including AD */
212 info = CryptFindOIDInfo(keytype, (void *)key, groupid);
213
214 /* fall back to all groups if not found yet */
215 if (!info && groupid)
216 {
217 info = CryptFindOIDInfo(keytype, (void *)key, 0);
218 }
219
220 return info;
221}
222
223static bool
224test_certificate_template(const char *cert_prop, const CERT_CONTEXT *cert_ctx)
225{
226 const CERT_INFO *info = cert_ctx->pCertInfo;
227 const CERT_EXTENSION *ext;
228 DWORD cbext;
229 struct gc_arena gc = gc_new();
230 const WCHAR *tmpl_name = wide_string(cert_prop, &gc);
231
232 /* check for V2 extension (Windows 2003+) */
233 ext = CertFindExtension(szOID_CERTIFICATE_TEMPLATE, info->cExtension, info->rgExtension);
234 if (ext)
235 {
236 const void *pvext = decode_object(&gc, X509_CERTIFICATE_TEMPLATE, &ext->Value, 0, &cbext);
237 if (pvext && cbext >= sizeof(CERT_TEMPLATE_EXT))
238 {
239 const CERT_TEMPLATE_EXT *cte = (const CERT_TEMPLATE_EXT *)pvext;
240 if (!_stricmp(cert_prop, cte->pszObjId))
241 {
242 /* found direct OID match with certificate property specified */
243 gc_free(&gc);
244 return true;
245 }
246
247 const CRYPT_OID_INFO *tmpl_oid =
248 find_oid(CRYPT_OID_INFO_NAME_KEY, tmpl_name, CRYPT_TEMPLATE_OID_GROUP_ID);
249 if (tmpl_oid && !_stricmp(tmpl_oid->pszOID, cte->pszObjId))
250 {
251 /* found OID match in extension against resolved key */
252 gc_free(&gc);
253 return true;
254 }
255 }
256 }
257
258 /* no extension found, exit */
259 gc_free(&gc);
260 return false;
261}
262
263static const CERT_CONTEXT *
264find_certificate_in_store(const char *cert_prop, HCERTSTORE cert_store)
265{
266 /* Find, and use, the desired certificate from the store. The
267 * 'cert_prop' certificate search string can look like this:
268 * SUBJ:<certificate substring to match>
269 * THUMB:<certificate thumbprint hex value>, e.g.
270 * THUMB:f6 49 24 41 01 b4 fb 44 0c ce f4 36 ae d0 c4 c9 df 7a b6 28
271 * TMPL:<template name or OID>
272 * The first matching certificate that has not expired is returned.
273 */
274 const CERT_CONTEXT *rv = NULL;
275 DWORD find_type;
276 const void *find_param;
277 unsigned char hash[255];
278 CRYPT_HASH_BLOB blob = { .cbData = 0, .pbData = hash };
279 struct gc_arena gc = gc_new();
280
281 if (!strncmp(cert_prop, "SUBJ:", 5))
282 {
283 /* skip the tag */
284 find_param = wide_string(cert_prop + 5, &gc);
285 find_type = CERT_FIND_SUBJECT_STR_W;
286 }
287 else if (!strncmp(cert_prop, "ISSUER:", 7))
288 {
289 find_param = wide_string(cert_prop + 7, &gc);
290 find_type = CERT_FIND_ISSUER_STR_W;
291 }
292 else if (!strncmp(cert_prop, "THUMB:", 6))
293 {
294 find_type = CERT_FIND_HASH;
295 find_param = &blob;
296
297 blob.cbData = parse_hexstring(cert_prop + 6, hash, sizeof(hash));
298 if (blob.cbData == 0)
299 {
300 msg(M_WARN | M_INFO, "WARNING: cryptoapicert: error parsing <%s>.", cert_prop);
301 goto out;
302 }
303 }
304 else if (!strncmp(cert_prop, "TMPL:", 5))
305 {
306 cert_prop += 5;
307 find_param = NULL;
308 find_type = CERT_FIND_HAS_PRIVATE_KEY;
309 }
310 else
311 {
312 msg(M_NONFATAL, "Error in cryptoapicert: unsupported certificate specification <%s>",
313 cert_prop);
314 goto out;
315 }
316
317 while (true)
318 {
319 int validity = 1;
320 /* this frees previous rv, if not NULL */
321 rv = CertFindCertificateInStore(cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0,
322 find_type, find_param, rv);
323 if (!rv)
324 {
325 break;
326 }
327 /* if searching by template name, check now if it matches */
328 if (find_type == CERT_FIND_HAS_PRIVATE_KEY && !test_certificate_template(cert_prop, rv))
329 {
330 continue;
331 }
332 validity = CertVerifyTimeValidity(NULL, rv->pCertInfo);
333 if (validity == 0)
334 {
335 break;
336 }
337 msg(M_WARN | M_INFO, "WARNING: cryptoapicert: ignoring certificate in store %s.",
338 validity < 0 ? "not yet valid" : "that has expired");
339 }
340
341out:
342 gc_free(&gc);
343 return rv;
344}
345
347static int
348xkey_cng_ec_sign(CAPI_DATA *cd, unsigned char *sig, size_t *siglen, const unsigned char *tbs,
349 size_t tbslen)
350{
351 ASSERT(*siglen <= UINT_MAX);
352 ASSERT(tbslen <= UINT_MAX);
353 DWORD len = (DWORD)*siglen;
354
355 msg(D_LOW, "Signing using NCryptSignHash with EC key");
356
357 DWORD status = NCryptSignHash(cd->crypt_prov, NULL, (BYTE *)tbs, (DWORD)tbslen, sig, len, &len, 0);
358
359 if (status != ERROR_SUCCESS)
360 {
361 SetLastError(status);
362 msg(M_NONFATAL | M_ERRNO, "Error in cryptoapicert: ECDSA signature using CNG failed.");
363 return 0;
364 }
365
366 /* NCryptSignHash returns r|s -- convert to DER encoded buffer expected by OpenSSL */
367 int derlen = ecdsa_bin2der(sig, (int)len, *siglen);
368 if (derlen <= 0)
369 {
370 return 0;
371 }
372 *siglen = derlen;
373 return 1;
374}
375
377static int
378xkey_cng_rsa_sign(CAPI_DATA *cd, unsigned char *sig, size_t *siglen, const unsigned char *tbs,
379 size_t tbslen, XKEY_SIGALG sigalg)
380{
381 dmsg(D_LOW, "In xkey_cng_rsa_sign");
382
383 ASSERT(cd);
384 ASSERT(sig);
385 ASSERT(*siglen <= UINT_MAX);
386 ASSERT(tbs);
387 ASSERT(tbslen <= INT_MAX);
388
389 DWORD status = ERROR_SUCCESS;
390 DWORD len = 0;
391
392 const wchar_t *hashalg = cng_hash_algo(OBJ_sn2nid(sigalg.mdname));
393
394 if (hashalg && wcscmp(hashalg, L"UNKNOWN") == 0)
395 {
396 msg(M_NONFATAL, "Error in cryptoapicert: Unknown hash name <%s>", sigalg.mdname);
397 return 0;
398 }
399
400 if (!strcmp(sigalg.padmode, "pkcs1"))
401 {
402 msg(D_LOW, "Signing using NCryptSignHash with PKCS1 padding: hashalg <%s>", sigalg.mdname);
403
404 BCRYPT_PKCS1_PADDING_INFO padinfo = { hashalg };
405 status = NCryptSignHash(cd->crypt_prov, &padinfo, (BYTE *)tbs, (DWORD)tbslen, sig,
406 (DWORD)*siglen, &len, BCRYPT_PAD_PKCS1);
407 }
408 else if (!strcmp(sigalg.padmode, "pss"))
409 {
410 int saltlen = (int)tbslen; /* digest size by default */
411 if (!strcmp(sigalg.saltlen, "max"))
412 {
413 saltlen = xkey_max_saltlen(EVP_PKEY_bits(cd->pubkey), saltlen);
414 if (saltlen < 0)
415 {
416 msg(M_NONFATAL, "Error in cryptoapicert: invalid salt length (%d)", saltlen);
417 return 0;
418 }
419 }
420
421 msg(D_LOW, "Signing using NCryptSignHash with PSS padding: hashalg <%s>, saltlen <%d>",
422 sigalg.mdname, saltlen);
423
424 /* cast is safe as saltlen >= 0 */
425 BCRYPT_PSS_PADDING_INFO padinfo = { hashalg, (DWORD)saltlen };
426 status = NCryptSignHash(cd->crypt_prov, &padinfo, (BYTE *)tbs, (DWORD)tbslen, sig,
427 (DWORD)*siglen, &len, BCRYPT_PAD_PSS);
428 }
429 else
430 {
431 msg(M_NONFATAL, "Error in cryptoapicert: Unsupported padding mode <%s>", sigalg.padmode);
432 return 0;
433 }
434
435 if (status != ERROR_SUCCESS)
436 {
437 SetLastError(status);
438 msg(M_NONFATAL | M_ERRNO, "Error in cryptoapicert: RSA signature using CNG failed.");
439 return 0;
440 }
441
442 *siglen = len;
443 return (*siglen > 0);
444}
445
447static int
448xkey_cng_sign(void *handle, unsigned char *sig, size_t *siglen, const unsigned char *tbs,
449 size_t tbslen, XKEY_SIGALG sigalg)
450{
451 dmsg(D_LOW, "In xkey_cng_sign");
452
453 CAPI_DATA *cd = handle;
454 ASSERT(cd);
455 ASSERT(sig);
456 ASSERT(tbs);
457
458 unsigned char mdbuf[EVP_MAX_MD_SIZE];
459 size_t buflen = _countof(mdbuf);
460
461 /* compute digest if required */
462 if (!strcmp(sigalg.op, "DigestSign"))
463 {
464 if (!xkey_digest(tbs, tbslen, mdbuf, &buflen, sigalg.mdname))
465 {
466 return 0;
467 }
468 tbs = mdbuf;
469 tbslen = buflen;
470 }
471
472 if (!strcmp(sigalg.keytype, "EC"))
473 {
474 return xkey_cng_ec_sign(cd, sig, siglen, tbs, tbslen);
475 }
476 else if (!strcmp(sigalg.keytype, "RSA"))
477 {
478 return xkey_cng_rsa_sign(cd, sig, siglen, tbs, tbslen, sigalg);
479 }
480 else
481 {
482 return 0; /* Unknown keytype -- should not happen */
483 }
484}
485
486static char *
487get_cert_name(const CERT_CONTEXT *cc, struct gc_arena *gc)
488{
489 DWORD len = CertGetNameStringW(cc, CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL, NULL, 0);
490 char *name = NULL;
491 if (len)
492 {
493 wchar_t *wname = gc_malloc(len * sizeof(wchar_t), false, gc);
494 if (!wname
495 || CertGetNameStringW(cc, CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL, wname, len) == 0)
496 {
497 return NULL;
498 }
499 name = utf16to8(wname, gc);
500 }
501 return name;
502}
503
510static int
511Load_CryptoAPI_certificate(const char *cert_prop, X509 **cert, EVP_PKEY **privkey)
512{
513 HCERTSTORE cs;
514 CAPI_DATA *cd = calloc(1, sizeof(*cd));
515 struct gc_arena gc = gc_new();
516
517 if (cd == NULL)
518 {
519 msg(M_NONFATAL, "Error in cryptoapicert: out of memory");
520 goto err;
521 }
522 /* search CURRENT_USER first, then LOCAL_MACHINE */
523 cs = CertOpenStore((LPCSTR)CERT_STORE_PROV_SYSTEM, 0, 0,
524 CERT_SYSTEM_STORE_CURRENT_USER | CERT_STORE_OPEN_EXISTING_FLAG
525 | CERT_STORE_READONLY_FLAG,
526 L"MY");
527 if (cs == NULL)
528 {
529 msg(M_NONFATAL | M_ERRNO, "Error in cryptoapicert: failed to open user certficate store");
530 goto err;
531 }
532 cd->cert_context = find_certificate_in_store(cert_prop, cs);
533 CertCloseStore(cs, 0);
534 if (!cd->cert_context)
535 {
536 cs = CertOpenStore((LPCSTR)CERT_STORE_PROV_SYSTEM, 0, 0,
537 CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_OPEN_EXISTING_FLAG
538 | CERT_STORE_READONLY_FLAG,
539 L"MY");
540 if (cs == NULL)
541 {
543 "Error in cryptoapicert: failed to open machine certficate store");
544 goto err;
545 }
546 cd->cert_context = find_certificate_in_store(cert_prop, cs);
547 CertCloseStore(cs, 0);
548 if (cd->cert_context == NULL)
549 {
550 msg(M_NONFATAL, "Error in cryptoapicert: certificate matching <%s> not found",
551 cert_prop);
552 goto err;
553 }
554 }
555
556 /* try to log the "name" of the selected certificate */
557 char *cert_name = get_cert_name(cd->cert_context, &gc);
558 if (cert_name)
559 {
560 msg(D_LOW, "cryptapicert: using certificate with name <%s>", cert_name);
561 }
562
563 /* cert_context->pbCertEncoded is the cert X509 DER encoded. */
564 *cert = d2i_X509(NULL, (const unsigned char **)&cd->cert_context->pbCertEncoded,
565 cd->cert_context->cbCertEncoded);
566 if (*cert == NULL)
567 {
568 msg(M_NONFATAL, "Error in cryptoapicert: X509 certificate decode failed");
569 goto err;
570 }
571
572 /* set up stuff to use the private key */
573 /* We support NCRYPT key handles only */
574 DWORD flags = CRYPT_ACQUIRE_COMPARE_KEY_FLAG | CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG;
575 if (!CryptAcquireCertificatePrivateKey(cd->cert_context, flags, NULL, &cd->crypt_prov,
576 &cd->key_spec, &cd->free_crypt_prov))
577 {
578 /* private key may be in a token not available, or incompatible with CNG */
580 "Error in cryptoapicert: failed to acquire key. Key not present or "
581 "is in a legacy token not supported by Windows CNG API");
582 X509_free(*cert);
583 goto err;
584 }
585
586 /* the public key */
587 EVP_PKEY *pkey = X509_get_pubkey(*cert);
588 cd->pubkey = pkey; /* will be freed with cd */
589
590 *privkey = xkey_load_generic_key(tls_libctx, cd, pkey, xkey_cng_sign,
591 (XKEY_PRIVKEY_FREE_fn *)CAPI_DATA_free);
592 gc_free(&gc);
593 return 1; /* do not free cd -- its kept by xkey provider */
594
595err:
596 CAPI_DATA_free(cd);
597 gc_free(&gc);
598 return 0;
599}
600
601int
602SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
603{
604 X509 *cert = NULL;
605 EVP_PKEY *privkey = NULL;
606 int ret = 0;
607
608 if (!Load_CryptoAPI_certificate(cert_prop, &cert, &privkey))
609 {
610 return ret;
611 }
612 if (SSL_CTX_use_certificate(ssl_ctx, cert) && SSL_CTX_use_PrivateKey(ssl_ctx, privkey))
613 {
615 ret = 1;
616 }
617
618 /* Always free cert and privkey even if retained by ssl_ctx as
619 * they are reference counted */
620 X509_free(cert);
621 EVP_PKEY_free(privkey);
622 return ret;
623}
624
625#endif /* HAVE_XKEY_PROVIDER */
626#endif /* _WIN32 */
void * gc_malloc(size_t size, bool clear, struct gc_arena *a)
Allocate memory and, optionally, zero it.
Definition buffer.c:313
Buffer management functions and garbage collection.
static void gc_free(struct gc_arena *a)
Free all allocations in a garbage collection arena.
Definition buffer.h:1965
static struct gc_arena gc_new(void)
Allocate and return a new, empty garbage collection arena.
Definition buffer.h:1949
void crypto_print_openssl_errors(const unsigned int flags)
Retrieve any occurred OpenSSL errors and print those errors.
Data Channel Cryptography OpenSSL-specific backend interface.
int SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
Definition cryptoapi.c:59
#define D_LOW
Definition errlevel.h:96
#define M_INFO
Definition errlevel.h:54
static SERVICE_STATUS status
Definition interactive.c:52
OpenSSL compatibility stub.
#define M_NONFATAL
Definition error.h:91
#define dmsg(flags,...)
Definition error.h:164
#define msg(flags,...)
Definition error.h:152
#define ASSERT(x)
Definition error.h:221
#define M_WARN
Definition error.h:92
#define M_ERRNO
Definition error.h:95
OSSL_LIB_CTX * tls_libctx
Definition ssl_openssl.c:78
Garbage collection arena used to keep track of dynamically allocated memory.
Definition buffer.h:127
Definition list.h:56
Container for unidirectional cipher and HMAC key material.
Definition crypto.h:152
struct gc_arena gc
Definition test_ssl.c:122
char * utf16to8(const wchar_t *utf16, struct gc_arena *gc)
Definition win32-util.c:119
WCHAR * wide_string(const char *utf8, struct gc_arena *gc)
Definition win32-util.c:40