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