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 "openssl_compat.h"
51#include "win32.h"
52#include "xkey_common.h"
53#include "crypto_openssl.h"
54
55#ifndef HAVE_XKEY_PROVIDER
56
57int
58SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
59{
60 msg(M_NONFATAL, "ERROR: this binary was built without cryptoapicert support");
61 return 0;
62}
63
64#else /* HAVE_XKEY_PROVIDER */
65
66static XKEY_EXTERNAL_SIGN_fn xkey_cng_sign;
67
68typedef struct _CAPI_DATA
69{
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 */
83static const wchar_t *
84cng_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
121static void
122CAPI_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
156int
157parse_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]) || sscanf(p, "%2hhx", &arr[i++]) != 1)
173 {
174 return 0;
175 }
176 }
177 return i;
178}
179
180static void *
181decode_object(struct gc_arena *gc, LPCSTR struct_type, const CRYPT_OBJID_BLOB *val, DWORD flags,
182 DWORD *cb)
183{
184 /* get byte count for decoding */
185 BYTE *buf;
186 if (!CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, struct_type, val->pbData,
187 val->cbData, flags, NULL, cb))
188 {
189 return NULL;
190 }
191
192 /* do the actual decode */
193 buf = gc_malloc(*cb, false, gc);
194 if (!CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, struct_type, val->pbData,
195 val->cbData, flags, buf, cb))
196 {
197 return NULL;
198 }
199
200 return buf;
201}
202
203static const CRYPT_OID_INFO *
204find_oid(DWORD keytype, const void *key, DWORD groupid)
205{
206 const CRYPT_OID_INFO *info = NULL;
207
208 /* try proper resolve, also including AD */
209 info = CryptFindOIDInfo(keytype, (void *)key, groupid);
210
211 /* fall back to all groups if not found yet */
212 if (!info && groupid)
213 {
214 info = CryptFindOIDInfo(keytype, (void *)key, 0);
215 }
216
217 return info;
218}
219
220static bool
221test_certificate_template(const char *cert_prop, const CERT_CONTEXT *cert_ctx)
222{
223 const CERT_INFO *info = cert_ctx->pCertInfo;
224 const CERT_EXTENSION *ext;
225 DWORD cbext;
226 void *pvext;
227 struct gc_arena gc = gc_new();
228 const WCHAR *tmpl_name = wide_string(cert_prop, &gc);
229
230 /* check for V2 extension (Windows 2003+) */
231 ext = CertFindExtension(szOID_CERTIFICATE_TEMPLATE, info->cExtension, info->rgExtension);
232 if (ext)
233 {
234 pvext = decode_object(&gc, X509_CERTIFICATE_TEMPLATE, &ext->Value, 0, &cbext);
235 if (pvext && cbext >= sizeof(CERT_TEMPLATE_EXT))
236 {
237 const CERT_TEMPLATE_EXT *cte = (const CERT_TEMPLATE_EXT *)pvext;
238 if (!stricmp(cert_prop, cte->pszObjId))
239 {
240 /* found direct OID match with certificate property specified */
241 gc_free(&gc);
242 return true;
243 }
244
245 const CRYPT_OID_INFO *tmpl_oid =
246 find_oid(CRYPT_OID_INFO_NAME_KEY, tmpl_name, CRYPT_TEMPLATE_OID_GROUP_ID);
247 if (tmpl_oid && !stricmp(tmpl_oid->pszOID, cte->pszObjId))
248 {
249 /* found OID match in extension against resolved key */
250 gc_free(&gc);
251 return true;
252 }
253 }
254 }
255
256 /* no extension found, exit */
257 gc_free(&gc);
258 return false;
259}
260
261static const CERT_CONTEXT *
262find_certificate_in_store(const char *cert_prop, HCERTSTORE cert_store)
263{
264 /* Find, and use, the desired certificate from the store. The
265 * 'cert_prop' certificate search string can look like this:
266 * SUBJ:<certificate substring to match>
267 * THUMB:<certificate thumbprint hex value>, e.g.
268 * THUMB:f6 49 24 41 01 b4 fb 44 0c ce f4 36 ae d0 c4 c9 df 7a b6 28
269 * TMPL:<template name or OID>
270 * The first matching certificate that has not expired is returned.
271 */
272 const CERT_CONTEXT *rv = NULL;
273 DWORD find_type;
274 const void *find_param;
275 unsigned char hash[255];
276 CRYPT_HASH_BLOB blob = { .cbData = 0, .pbData = hash };
277 struct gc_arena gc = gc_new();
278
279 if (!strncmp(cert_prop, "SUBJ:", 5))
280 {
281 /* skip the tag */
282 find_param = wide_string(cert_prop + 5, &gc);
283 find_type = CERT_FIND_SUBJECT_STR_W;
284 }
285 else if (!strncmp(cert_prop, "ISSUER:", 7))
286 {
287 find_param = wide_string(cert_prop + 7, &gc);
288 find_type = CERT_FIND_ISSUER_STR_W;
289 }
290 else if (!strncmp(cert_prop, "THUMB:", 6))
291 {
292 find_type = CERT_FIND_HASH;
293 find_param = &blob;
294
295 blob.cbData = parse_hexstring(cert_prop + 6, hash, sizeof(hash));
296 if (blob.cbData == 0)
297 {
298 msg(M_WARN | M_INFO, "WARNING: cryptoapicert: error parsing <%s>.", cert_prop);
299 goto out;
300 }
301 }
302 else if (!strncmp(cert_prop, "TMPL:", 5))
303 {
304 cert_prop += 5;
305 find_param = NULL;
306 find_type = CERT_FIND_HAS_PRIVATE_KEY;
307 }
308 else
309 {
310 msg(M_NONFATAL, "Error in cryptoapicert: unsupported certificate specification <%s>",
311 cert_prop);
312 goto out;
313 }
314
315 while (true)
316 {
317 int validity = 1;
318 /* this frees previous rv, if not NULL */
319 rv = CertFindCertificateInStore(cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0,
320 find_type, find_param, rv);
321 if (!rv)
322 {
323 break;
324 }
325 /* if searching by template name, check now if it matches */
326 if (find_type == CERT_FIND_HAS_PRIVATE_KEY && !test_certificate_template(cert_prop, rv))
327 {
328 continue;
329 }
330 validity = CertVerifyTimeValidity(NULL, rv->pCertInfo);
331 if (validity == 0)
332 {
333 break;
334 }
335 msg(M_WARN | M_INFO, "WARNING: cryptoapicert: ignoring certificate in store %s.",
336 validity < 0 ? "not yet valid" : "that has expired");
337 }
338
339out:
340 gc_free(&gc);
341 return rv;
342}
343
344#if defined(__GNUC__) || defined(__clang__)
345#pragma GCC diagnostic push
346#pragma GCC diagnostic ignored "-Wconversion"
347#endif
348
350static int
351xkey_cng_ec_sign(CAPI_DATA *cd, unsigned char *sig, size_t *siglen, const unsigned char *tbs,
352 size_t tbslen)
353{
354 DWORD len = *siglen;
355
356 msg(D_LOW, "Signing using NCryptSignHash with EC key");
357
358 DWORD status = NCryptSignHash(cd->crypt_prov, NULL, (BYTE *)tbs, tbslen, sig, len, &len, 0);
359
360 if (status != ERROR_SUCCESS)
361 {
362 SetLastError(status);
363 msg(M_NONFATAL | M_ERRNO, "Error in cryptoapicert: ECDSA signature using CNG failed.");
364 return 0;
365 }
366
367 /* NCryptSignHash returns r|s -- convert to DER encoded buffer expected by OpenSSL */
368 int derlen = ecdsa_bin2der(sig, (int)len, *siglen);
369 if (derlen <= 0)
370 {
371 return 0;
372 }
373 *siglen = derlen;
374 return 1;
375}
376
378static int
379xkey_cng_rsa_sign(CAPI_DATA *cd, unsigned char *sig, size_t *siglen, const unsigned char *tbs,
380 size_t tbslen, XKEY_SIGALG sigalg)
381{
382 dmsg(D_LOW, "In xkey_cng_rsa_sign");
383
384 ASSERT(cd);
385 ASSERT(sig);
386 ASSERT(tbs);
387
388 DWORD status = ERROR_SUCCESS;
389 DWORD len = 0;
390
391 const wchar_t *hashalg = cng_hash_algo(OBJ_sn2nid(sigalg.mdname));
392
393 if (hashalg && wcscmp(hashalg, L"UNKNOWN") == 0)
394 {
395 msg(M_NONFATAL, "Error in cryptoapicert: Unknown hash name <%s>", sigalg.mdname);
396 return 0;
397 }
398
399 if (!strcmp(sigalg.padmode, "pkcs1"))
400 {
401 msg(D_LOW, "Signing using NCryptSignHash with PKCS1 padding: hashalg <%s>", sigalg.mdname);
402
403 BCRYPT_PKCS1_PADDING_INFO padinfo = { hashalg };
404 status = NCryptSignHash(cd->crypt_prov, &padinfo, (BYTE *)tbs, (DWORD)tbslen, sig,
405 (DWORD)*siglen, &len, BCRYPT_PAD_PKCS1);
406 }
407 else if (!strcmp(sigalg.padmode, "pss"))
408 {
409 int saltlen = tbslen; /* digest size by default */
410 if (!strcmp(sigalg.saltlen, "max"))
411 {
412 saltlen = xkey_max_saltlen(EVP_PKEY_bits(cd->pubkey), tbslen);
413 if (saltlen < 0)
414 {
415 msg(M_NONFATAL, "Error in cryptoapicert: invalid salt length (%d)", saltlen);
416 return 0;
417 }
418 }
419
420 msg(D_LOW, "Signing using NCryptSignHash with PSS padding: hashalg <%s>, saltlen <%d>",
421 sigalg.mdname, saltlen);
422
423 BCRYPT_PSS_PADDING_INFO padinfo = { hashalg,
424 (DWORD)saltlen }; /* cast is safe as saltlen >= 0 */
425 status = NCryptSignHash(cd->crypt_prov, &padinfo, (BYTE *)tbs, (DWORD)tbslen, sig,
426 (DWORD)*siglen, &len, BCRYPT_PAD_PSS);
427 }
428 else
429 {
430 msg(M_NONFATAL, "Error in cryptoapicert: Unsupported padding mode <%s>", sigalg.padmode);
431 return 0;
432 }
433
434 if (status != ERROR_SUCCESS)
435 {
436 SetLastError(status);
437 msg(M_NONFATAL | M_ERRNO, "Error in cryptoapicert: RSA signature using CNG failed.");
438 return 0;
439 }
440
441 *siglen = len;
442 return (*siglen > 0);
443}
444
445#if defined(__GNUC__) || defined(__clang__)
446#pragma GCC diagnostic pop
447#endif
448
450static int
451xkey_cng_sign(void *handle, unsigned char *sig, size_t *siglen, const unsigned char *tbs,
452 size_t tbslen, XKEY_SIGALG sigalg)
453{
454 dmsg(D_LOW, "In xkey_cng_sign");
455
456 CAPI_DATA *cd = handle;
457 ASSERT(cd);
458 ASSERT(sig);
459 ASSERT(tbs);
460
461 unsigned char mdbuf[EVP_MAX_MD_SIZE];
462 size_t buflen = _countof(mdbuf);
463
464 /* compute digest if required */
465 if (!strcmp(sigalg.op, "DigestSign"))
466 {
467 if (!xkey_digest(tbs, tbslen, mdbuf, &buflen, sigalg.mdname))
468 {
469 return 0;
470 }
471 tbs = mdbuf;
472 tbslen = buflen;
473 }
474
475 if (!strcmp(sigalg.keytype, "EC"))
476 {
477 return xkey_cng_ec_sign(cd, sig, siglen, tbs, tbslen);
478 }
479 else if (!strcmp(sigalg.keytype, "RSA"))
480 {
481 return xkey_cng_rsa_sign(cd, sig, siglen, tbs, tbslen, sigalg);
482 }
483 else
484 {
485 return 0; /* Unknown keytype -- should not happen */
486 }
487}
488
489static char *
490get_cert_name(const CERT_CONTEXT *cc, struct gc_arena *gc)
491{
492 DWORD len = CertGetNameStringW(cc, CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL, NULL, 0);
493 char *name = NULL;
494 if (len)
495 {
496 wchar_t *wname = gc_malloc(len * sizeof(wchar_t), false, gc);
497 if (!wname
498 || CertGetNameStringW(cc, CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL, wname, len) == 0)
499 {
500 return NULL;
501 }
502 name = utf16to8(wname, gc);
503 }
504 return name;
505}
506
513static int
514Load_CryptoAPI_certificate(const char *cert_prop, X509 **cert, EVP_PKEY **privkey)
515{
516 HCERTSTORE cs;
517 CAPI_DATA *cd = calloc(1, sizeof(*cd));
518 struct gc_arena gc = gc_new();
519
520 if (cd == NULL)
521 {
522 msg(M_NONFATAL, "Error in cryptoapicert: out of memory");
523 goto err;
524 }
525 /* search CURRENT_USER first, then LOCAL_MACHINE */
526 cs = CertOpenStore((LPCSTR)CERT_STORE_PROV_SYSTEM, 0, 0,
527 CERT_SYSTEM_STORE_CURRENT_USER | CERT_STORE_OPEN_EXISTING_FLAG
528 | CERT_STORE_READONLY_FLAG,
529 L"MY");
530 if (cs == NULL)
531 {
532 msg(M_NONFATAL | M_ERRNO, "Error in cryptoapicert: failed to open user certficate store");
533 goto err;
534 }
535 cd->cert_context = find_certificate_in_store(cert_prop, cs);
536 CertCloseStore(cs, 0);
537 if (!cd->cert_context)
538 {
539 cs = CertOpenStore((LPCSTR)CERT_STORE_PROV_SYSTEM, 0, 0,
540 CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_OPEN_EXISTING_FLAG
541 | CERT_STORE_READONLY_FLAG,
542 L"MY");
543 if (cs == NULL)
544 {
546 "Error in cryptoapicert: failed to open machine certficate store");
547 goto err;
548 }
549 cd->cert_context = find_certificate_in_store(cert_prop, cs);
550 CertCloseStore(cs, 0);
551 if (cd->cert_context == NULL)
552 {
553 msg(M_NONFATAL, "Error in cryptoapicert: certificate matching <%s> not found",
554 cert_prop);
555 goto err;
556 }
557 }
558
559 /* try to log the "name" of the selected certificate */
560 char *cert_name = get_cert_name(cd->cert_context, &gc);
561 if (cert_name)
562 {
563 msg(D_LOW, "cryptapicert: using certificate with name <%s>", cert_name);
564 }
565
566 /* cert_context->pbCertEncoded is the cert X509 DER encoded. */
567 *cert = d2i_X509(NULL, (const unsigned char **)&cd->cert_context->pbCertEncoded,
568 cd->cert_context->cbCertEncoded);
569 if (*cert == NULL)
570 {
571 msg(M_NONFATAL, "Error in cryptoapicert: X509 certificate decode failed");
572 goto err;
573 }
574
575 /* set up stuff to use the private key */
576 /* We support NCRYPT key handles only */
577 DWORD flags = CRYPT_ACQUIRE_COMPARE_KEY_FLAG | CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG;
578 if (!CryptAcquireCertificatePrivateKey(cd->cert_context, flags, NULL, &cd->crypt_prov,
579 &cd->key_spec, &cd->free_crypt_prov))
580 {
581 /* private key may be in a token not available, or incompatible with CNG */
583 "Error in cryptoapicert: failed to acquire key. Key not present or "
584 "is in a legacy token not supported by Windows CNG API");
585 X509_free(*cert);
586 goto err;
587 }
588
589 /* the public key */
590 EVP_PKEY *pkey = X509_get_pubkey(*cert);
591 cd->pubkey = pkey; /* will be freed with cd */
592
593 *privkey = xkey_load_generic_key(tls_libctx, cd, pkey, xkey_cng_sign,
594 (XKEY_PRIVKEY_FREE_fn *)CAPI_DATA_free);
595 gc_free(&gc);
596 return 1; /* do not free cd -- its kept by xkey provider */
597
598err:
599 CAPI_DATA_free(cd);
600 gc_free(&gc);
601 return 0;
602}
603
604int
605SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
606{
607 X509 *cert = NULL;
608 EVP_PKEY *privkey = NULL;
609 int ret = 0;
610
611 if (!Load_CryptoAPI_certificate(cert_prop, &cert, &privkey))
612 {
613 return ret;
614 }
615 if (SSL_CTX_use_certificate(ssl_ctx, cert) && SSL_CTX_use_PrivateKey(ssl_ctx, privkey))
616 {
618 ret = 1;
619 }
620
621 /* Always free cert and privkey even if retained by ssl_ctx as
622 * they are reference counted */
623 X509_free(cert);
624 EVP_PKEY_free(privkey);
625 return ret;
626}
627
628#endif /* HAVE_XKEY_PROVIDER */
629#endif /* _WIN32 */
void * gc_malloc(size_t size, bool clear, struct gc_arena *a)
Definition buffer.c:336
static void gc_free(struct gc_arena *a)
Definition buffer.h:1025
static struct gc_arena gc_new(void)
Definition buffer.h:1017
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:58
#define D_LOW
Definition errlevel.h:96
#define M_INFO
Definition errlevel.h:54
static SERVICE_STATUS status
Definition interactive.c:51
OpenSSL compatibility stub.
#define M_NONFATAL
Definition error.h:91
#define dmsg(flags,...)
Definition error.h:172
#define msg(flags,...)
Definition error.h:152
#define ASSERT(x)
Definition error.h:219
#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:116
Definition list.h:53
Container for unidirectional cipher and HMAC key material.
Definition crypto.h:152
struct gc_arena gc
Definition test_ssl.c:131
char * utf16to8(const wchar_t *utf16, struct gc_arena *gc)
Definition win32-util.c:49
WCHAR * wide_string(const char *utf8, struct gc_arena *gc)
Definition win32-util.c:40