1 /************************************************************************************
2   Copyright (C) 2019 MariaDB
3 
4   This library is free software; you can redistribute it and/or
5   modify it under the terms of the GNU Library General Public
6   License as published by the Free Software Foundation; either
7   version 2 of the License, or (at your option) any later version.
8 
9   This library is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   Library General Public License for more details.
13 
14   You should have received a copy of the GNU Library General Public
15   License along with this library; if not see <http://www.gnu.org/licenses>
16   or write to the Free Software Foundation, Inc.,
17   51 Franklin St., Fifth Floor, Boston, MA 02110, USA
18 
19  *************************************************************************************/
20 
21  /*
22   This module contain X509 certificate handling on Windows.
23   PEM parsing, loading client certificate and key, server certificate validation
24  */
25 
26  /*
27   CERT_CHAIN_ENGINE_CONFIG has additional members in Windows 8.1
28   To allow client to be work on pre-8.1 Windows, compile
29   with corresponding _WIN32_WINNT
30  */
31 #ifdef _WIN32_WINNT
32 #undef _WIN32_WINNT
33 #define _WIN32_WINNT 0x0601
34 #endif
35 
36 #include "schannel_certs.h"
37 #include <malloc.h>
38 #include <stdio.h>
39 #include <string.h>
40 #include <winsock2.h>
41 #include <ws2tcpip.h>
42 #include <winhttp.h>
43 #include <assert.h>
44 #include "win32_errmsg.h"
45 
46  /*
47    Return GetLastError(), or, if this unexpectedly gives success,
48    return ERROR_INTERNAL_ERROR.
49 
50    Background - in several cases in this module we return GetLastError()
51    after an Windows function fails. However, we do not want the function to
52    return success, even if GetLastError() was suddenly 0.
53  */
54 static DWORD get_last_error()
55 {
56   DWORD ret = GetLastError();
57   if (ret)
58     return ret;
59 
60   // We generally expect last error to be set  API fails.
61   // thus  the debug assertion-
62   assert(0);
63   return ERROR_INTERNAL_ERROR;
64 }
65 
66 #define FAIL(...) \
67    do{\
68      status = get_last_error();\
69      ma_format_win32_error(errmsg, errmsg_len, status, __VA_ARGS__);\
70      goto cleanup;\
71   } while (0)
72 
73 /*
74   Load file into memory. Add null terminator at the end, so it will be a valid C string.
75 */
76 static char* pem_file_to_string(const char* file, char* errmsg, size_t errmsg_len)
77 {
78   LARGE_INTEGER file_size;
79   size_t file_bufsize = 0;
80   size_t total_bytes_read = 0;
81   char* file_buffer = NULL;
82   SECURITY_STATUS status = SEC_E_OK;
83 
84   HANDLE file_handle = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL,
85     OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
86   if (file_handle == INVALID_HANDLE_VALUE)
87   {
88     FAIL("failed to open file '%s'", file);
89   }
90 
91   if (!GetFileSizeEx(file_handle, &file_size))
92   {
93     FAIL("GetFileSizeEx failed on '%s'", file);
94   }
95 
96   if (file_size.QuadPart > ULONG_MAX - 1)
97   {
98     SetLastError(SEC_E_INVALID_PARAMETER);
99     FAIL("file '%s' too large", file);
100   }
101 
102   file_bufsize = (size_t)file_size.QuadPart;
103   file_buffer = (char*)LocalAlloc(0,file_bufsize + 1);
104   if (!file_buffer)
105   {
106     FAIL("LocalAlloc(0,%zu) failed", file_bufsize + 1);
107   }
108 
109   while (total_bytes_read < file_bufsize)
110   {
111     DWORD bytes_to_read = (DWORD)(file_bufsize - total_bytes_read);
112     DWORD bytes_read = 0;
113 
114     if (!ReadFile(file_handle, file_buffer + total_bytes_read,
115       bytes_to_read, &bytes_read, NULL))
116     {
117       FAIL("ReadFile() failed to read  file '%s'", file);
118     }
119     if (bytes_read == 0)
120     {
121       /* Premature EOF -- adjust the bufsize to the new value */
122       file_bufsize = total_bytes_read;
123     }
124     else
125     {
126       total_bytes_read += bytes_read;
127     }
128   }
129 
130   /* Null terminate the buffer */
131   file_buffer[file_bufsize] = '\0';
132 
133 cleanup:
134   if (file_handle != INVALID_HANDLE_VALUE)
135   {
136     CloseHandle(file_handle);
137   }
138   if (status)
139   {
140     /* Some error happened. */
141     LocalFree(file_buffer);
142     file_buffer = NULL;
143   }
144   return file_buffer;
145 }
146 
147 
148 // Structure for parsing BEGIN/END sections inside pem.
149 typedef struct _pem_type_desc
150 {
151   const char* begin_tag;
152   size_t begin_tag_len;
153   const char* end_tag;
154   size_t end_tag_len;
155 } pem_type_desc;
156 
157 #define BEGIN_TAG(x) "-----BEGIN " x  "-----"
158 #define END_TAG(x) "\n-----END " x  "-----"
159 #define PEM_SECTION(tag) {BEGIN_TAG(tag), sizeof(BEGIN_TAG(tag))-1, END_TAG(tag), sizeof(END_TAG(tag))-1}
160 
161 typedef enum {
162   PEM_TYPE_CERTIFICATE = 0,
163   PEM_TYPE_X509_CRL,
164   PEM_TYPE_RSA_PRIVATE_KEY,
165   PEM_TYPE_PRIVATE_KEY
166 } PEM_TYPE;
167 
168 static const pem_type_desc pem_sections[] = {
169   PEM_SECTION("CERTIFICATE"),
170   PEM_SECTION("X509 CRL"),
171   PEM_SECTION("RSA PRIVATE KEY"),
172   PEM_SECTION("PRIVATE KEY")
173 };
174 
175 /*
176   Locate a substring in pem for given type,
177   e.g section between BEGIN CERTIFICATE and END CERTIFICATE
178   in PEMs base64 format, with header and footer.
179 
180   output parameters 'begin' and 'end' are set upon return.
181   it is possible that functions returns 'begin' != NULL but
182   'end' = NULL. This is generally a format error, meaning that
183   the end tag was not found
184 */
185 void pem_locate(char* pem_str,
186   PEM_TYPE type,
187   char** begin,
188   char** end)
189 {
190   *begin = NULL;
191   *end = NULL;
192   char c;
193 
194   const pem_type_desc* desc = &pem_sections[type];
195   *begin = strstr(pem_str, desc->begin_tag);
196   if (!(*begin))
197     return;
198 
199   // We expect newline after the
200   // begin tag, LF or CRLF
201   c = (*begin)[desc->begin_tag_len];
202 
203   if (c != '\r' && c != '\n')
204   {
205     *begin = NULL;
206     return;
207   }
208 
209   *end = strstr(*begin + desc->begin_tag_len + 1, desc->end_tag);
210   if (!*end)
211     return; // error, end marker not found
212 
213   (*end) += desc->end_tag_len;
214   return;
215 }
216 
217 
218 /*
219   Add certificates, or CRLs from a PEM file to Wincrypt store
220 */
221 static SECURITY_STATUS add_certs_to_store(
222   HCERTSTORE  trust_store,
223   const char* file,
224   PEM_TYPE type,
225   char* errmsg,
226   size_t      errmsg_len)
227 {
228   char* file_buffer = NULL;
229   char* cur = NULL;
230   SECURITY_STATUS status = SEC_E_OK;
231   CRL_CONTEXT* crl_context = NULL;
232   CERT_CONTEXT* cert_context = NULL;
233   char* begin;
234   char* end;
235 
236   file_buffer = pem_file_to_string(file, errmsg, errmsg_len);
237   if (!file_buffer)
238     goto cleanup;
239 
240   for (cur = file_buffer; ; cur = end)
241   {
242     pem_locate(cur, type, &begin, &end);
243 
244     if (!begin)
245       break;
246 
247     if (!end)
248     {
249       SetLastError(SEC_E_INVALID_PARAMETER);
250       FAIL("Invalid PEM file '%s', missing end marker corresponding to begin marker '%s' at offset %zu",
251         file, pem_sections[type].begin_tag, (size_t)(begin - file_buffer));
252     }
253     CERT_BLOB cert_blob;
254     void* context = NULL;
255     DWORD actual_content_type = 0;
256 
257     cert_blob.pbData = (BYTE*)begin;
258     cert_blob.cbData = (DWORD)(end - begin);
259     if (!CryptQueryObject(
260       CERT_QUERY_OBJECT_BLOB, &cert_blob,
261       CERT_QUERY_CONTENT_FLAG_CERT | CERT_QUERY_CONTENT_FLAG_CRL,
262       CERT_QUERY_FORMAT_FLAG_ALL, 0, NULL, &actual_content_type,
263       NULL, NULL, NULL, (const void**)&context))
264     {
265       FAIL("failed to extract certificate from PEM file '%s'",file);
266     }
267 
268     if (!context)
269     {
270       SetLastError(SEC_E_INTERNAL_ERROR);
271       FAIL("unexpected result from CryptQueryObject(),cert_context is NULL"
272         " after successful completion, file '%s'",
273         file);
274     }
275 
276     if (actual_content_type == CERT_QUERY_CONTENT_CERT)
277     {
278       CERT_CONTEXT* cert_context = (CERT_CONTEXT*)context;
279       if (!CertAddCertificateContextToStore(
280         trust_store, cert_context,
281         CERT_STORE_ADD_ALWAYS, NULL))
282       {
283         FAIL("CertAddCertificateContextToStore failed");
284       }
285     }
286     else if (actual_content_type == CERT_QUERY_CONTENT_CRL)
287     {
288       CRL_CONTEXT* crl_context = (CRL_CONTEXT*)context;
289       if (!CertAddCRLContextToStore(
290         trust_store, crl_context,
291         CERT_STORE_ADD_ALWAYS, NULL))
292       {
293         FAIL("CertAddCRLContextToStore() failed");
294       }
295     }
296   }
297 cleanup:
298   LocalFree(file_buffer);
299   if (cert_context)
300     CertFreeCertificateContext(cert_context);
301   if (crl_context)
302     CertFreeCRLContext(crl_context);
303   return status;
304 }
305 
306 /*
307 Add a directory to store, i.e try to load all files.
308 (extract certificates and add them to store)
309 
310 @return 0 on success, error only if directory is invalid.
311 */
312 SECURITY_STATUS add_dir_to_store(HCERTSTORE trust_store, const char* dir,
313   PEM_TYPE type, char* errmsg, size_t errmsg_len)
314 {
315   WIN32_FIND_DATAA ffd;
316   char path[MAX_PATH];
317   char pattern[MAX_PATH];
318   DWORD dwAttr;
319   HANDLE hFind = INVALID_HANDLE_VALUE;
320   SECURITY_STATUS status = SEC_E_OK;
321 
322   if ((dwAttr = GetFileAttributes(dir)) == INVALID_FILE_ATTRIBUTES)
323   {
324     SetLastError(SEC_E_INVALID_PARAMETER);
325     FAIL("directory '%s' does not exist", dir);
326   }
327   if (!(dwAttr & FILE_ATTRIBUTE_DIRECTORY))
328   {
329     SetLastError(SEC_E_INVALID_PARAMETER);
330     FAIL("'%s' is not a directory", dir);
331   }
332   sprintf_s(pattern, sizeof(pattern), "%s\\*", dir);
333   hFind = FindFirstFile(pattern, &ffd);
334   if (hFind == INVALID_HANDLE_VALUE)
335   {
336     FAIL("FindFirstFile(%s) failed",pattern);
337   }
338   do
339   {
340     if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
341       continue;
342     sprintf_s(path, sizeof(path), "%s\\%s", dir, ffd.cFileName);
343 
344     // ignore error from add_certs_to_store(), not all file
345     // maybe PEM.
346     add_certs_to_store(trust_store, path, type, errmsg,
347       errmsg_len);
348   } while (FindNextFile(hFind, &ffd) != 0);
349 
350 cleanup:
351   if (hFind != INVALID_HANDLE_VALUE)
352     FindClose(hFind);
353 
354   return status;
355 }
356 
357 /* Count certificates in store. */
358 static int count_certificates(HCERTSTORE store)
359 {
360   int num_certs = 0;
361   PCCERT_CONTEXT c = NULL;
362 
363   while ((c = CertEnumCertificatesInStore(store, c)))
364     num_certs++;
365 
366   return num_certs;
367 }
368 
369 /**
370   Creates certificate store with user defined CA chain and/or CRL.
371   Loads PEM certificate from files or directories.
372 
373   If only CRLFile/CRLPath is defined, the "system" store is duplicated,
374   and new CRLs are added to it.
375 
376   If CAFile/CAPAth is defined, then new empty store is created, and CAs
377   (and CRLs, if defined), are added to it.
378 
379   The function throws an error, if none of the files in CAFile/CAPath have a valid certificate.
380   It is also an error if CRLFile does not exist.
381 */
382 SECURITY_STATUS schannel_create_store(
383   const char* CAFile,
384   const char* CAPath,
385   const char* CRLFile,
386   const char* CRLPath,
387   HCERTSTORE* out_store,
388   char* errmsg,
389   size_t      errmsg_len)
390 {
391 
392   HCERTSTORE store = NULL;
393   HCERTSTORE system_store = NULL;
394   int status = SEC_E_OK;
395 
396   *out_store = NULL;
397   if (!CAFile && !CAPath && !CRLFile && !CRLPath)
398   {
399     /* Nothing to do, caller will use default store*/
400     *out_store = NULL;
401     return SEC_E_OK;
402   }
403   if (CAFile || CAPath)
404   {
405     /* Open the certificate store */
406     store = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, (HCRYPTPROV)NULL,
407       CERT_STORE_CREATE_NEW_FLAG, NULL);
408     if (!store)
409     {
410       FAIL("CertOpenStore failed for memory store");
411     }
412   }
413   else if (CRLFile || CRLPath)
414   {
415     /* Only CRL was provided, copy system store, add revocation list to
416      * it. */
417     system_store =
418       CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, (HCRYPTPROV_LEGACY)NULL,
419         CERT_SYSTEM_STORE_CURRENT_USER, L"MY");
420     if (!system_store)
421     {
422        FAIL("CertOpenStore failed for system store");
423     }
424 
425     store = CertDuplicateStore(system_store);
426     if (!store)
427     {
428       FAIL("CertDuplicateStore failed");
429     }
430   }
431 
432   if (CAFile)
433   {
434     status = add_certs_to_store(store, CAFile,
435       PEM_TYPE_CERTIFICATE, errmsg, errmsg_len);
436     if (status)
437       goto cleanup;
438   }
439   if (CAPath)
440   {
441     status = add_dir_to_store(store, CAPath,
442       PEM_TYPE_CERTIFICATE, errmsg, errmsg_len);
443     if (status)
444       goto cleanup;
445   }
446 
447   if ((CAFile || CAPath) && store && !count_certificates(store))
448   {
449     SetLastError(SEC_E_INVALID_PARAMETER);
450     FAIL("no valid certificates were found, CAFile='%s', CAPath='%s'",
451       CAFile ? CAFile : "<not set>", CAPath ? CAPath : "<not set>");
452   }
453 
454   if (CRLFile)
455   {
456     status = add_certs_to_store(store, CRLFile, PEM_TYPE_X509_CRL,
457       errmsg, errmsg_len);
458   }
459   if (CRLPath)
460   {
461     status = add_dir_to_store(store, CRLPath, PEM_TYPE_X509_CRL,
462       errmsg, errmsg_len);
463   }
464 
465 cleanup:
466   if (system_store)
467     CertCloseStore(system_store, 0);
468   if (status && store)
469   {
470     CertCloseStore(store, 0);
471     store = NULL;
472   }
473   *out_store = store;
474   return status;
475 }
476 
477 /*
478   The main verification logic.
479   Taken almost completely from Windows 2003 Platform SDK 2003
480   (Samples\Security\SSPI\SSL\WebClient.c)
481 
482   The only difference here is is usage of custom store
483   and chain engine.
484 */
485 static SECURITY_STATUS VerifyServerCertificate(
486   PCCERT_CONTEXT  pServerCert,
487   HCERTSTORE      hStore,
488   LPWSTR          pwszServerName,
489   DWORD           dwRevocationCheckFlags,
490   DWORD           dwVerifyFlags,
491   LPSTR           errmsg,
492   size_t          errmsg_len)
493 {
494   SSL_EXTRA_CERT_CHAIN_POLICY_PARA  polExtra;
495   CERT_CHAIN_POLICY_PARA   PolicyPara;
496   CERT_CHAIN_POLICY_STATUS PolicyStatus;
497   CERT_CHAIN_PARA          ChainPara;
498   HCERTCHAINENGINE         hChainEngine = NULL;
499   PCCERT_CHAIN_CONTEXT     pChainContext = NULL;
500   LPSTR rgszUsages[] = { szOID_PKIX_KP_SERVER_AUTH,
501                           szOID_SERVER_GATED_CRYPTO,
502                           szOID_SGC_NETSCAPE };
503   DWORD cUsages = sizeof(rgszUsages) / sizeof(LPSTR);
504   SECURITY_STATUS status = SEC_E_OK;
505 
506   if (pServerCert == NULL)
507   {
508     SetLastError(SEC_E_WRONG_PRINCIPAL);
509     FAIL("Invalid parameter pServerCert passed to VerifyServerCertificate");
510   }
511 
512   ZeroMemory(&ChainPara, sizeof(ChainPara));
513   ChainPara.cbSize = sizeof(ChainPara);
514   ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
515   ChainPara.RequestedUsage.Usage.cUsageIdentifier = cUsages;
516   ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages;
517 
518   if (hStore)
519   {
520     CERT_CHAIN_ENGINE_CONFIG EngineConfig = { 0 };
521     EngineConfig.cbSize = sizeof(EngineConfig);
522     EngineConfig.hExclusiveRoot = hStore;
523     if (!CertCreateCertificateChainEngine(&EngineConfig, &hChainEngine))
524     {
525       FAIL("CertCreateCertificateChainEngine failed");
526     }
527   }
528 
529   if (!CertGetCertificateChain(
530     hChainEngine,
531     pServerCert,
532     NULL,
533     pServerCert->hCertStore,
534     &ChainPara,
535     dwRevocationCheckFlags,
536     NULL,
537     &pChainContext))
538   {
539     FAIL("CertGetCertificateChain failed");
540     goto cleanup;
541   }
542 
543   // Validate certificate chain.
544   ZeroMemory(&polExtra, sizeof(SSL_EXTRA_CERT_CHAIN_POLICY_PARA));
545   polExtra.cbStruct = sizeof(SSL_EXTRA_CERT_CHAIN_POLICY_PARA);
546   polExtra.dwAuthType = AUTHTYPE_SERVER;
547   polExtra.fdwChecks = dwVerifyFlags;
548   polExtra.pwszServerName = pwszServerName;
549 
550   memset(&PolicyPara, 0, sizeof(PolicyPara));
551   PolicyPara.cbSize = sizeof(PolicyPara);
552   PolicyPara.pvExtraPolicyPara = &polExtra;
553 
554   memset(&PolicyStatus, 0, sizeof(PolicyStatus));
555   PolicyStatus.cbSize = sizeof(PolicyStatus);
556 
557   if (!CertVerifyCertificateChainPolicy(
558     CERT_CHAIN_POLICY_SSL,
559     pChainContext,
560     &PolicyPara,
561     &PolicyStatus))
562   {
563     FAIL("CertVerifyCertificateChainPolicy failed");
564   }
565 
566   if (PolicyStatus.dwError)
567   {
568     SetLastError(PolicyStatus.dwError);
569     FAIL("Server certificate validation failed");
570   }
571 
572 cleanup:
573   if (hChainEngine)
574   {
575     CertFreeCertificateChainEngine(hChainEngine);
576   }
577   if (pChainContext)
578   {
579     CertFreeCertificateChain(pChainContext);
580   }
581   return status;
582 }
583 
584 
585 void schannel_free_store(HCERTSTORE store)
586 {
587   if (store)
588     CertCloseStore(store, 0);
589 }
590 
591 
592 /*
593 Verify server certificate against a wincrypt store
594 @return 0 - success, otherwise error occurred.
595 */
596 SECURITY_STATUS schannel_verify_server_certificate(
597   const CERT_CONTEXT* cert,
598   HCERTSTORE store,
599   BOOL check_revocation,
600   const char* server_name,
601   BOOL check_server_name,
602   char* errmsg,
603   size_t errmsg_len)
604 {
605   SECURITY_STATUS status = SEC_E_OK;
606   wchar_t* wserver_name = NULL;
607   DWORD dwVerifyFlags;
608   DWORD dwRevocationFlags;
609 
610   if (check_server_name)
611   {
612     int cchServerName = (int)strlen(server_name) + 1;
613     wserver_name = (wchar_t*)LocalAlloc(0,sizeof(wchar_t) * cchServerName);
614     if (!wserver_name)
615     {
616       FAIL("LocalAlloc() failed");
617     }
618     if (MultiByteToWideChar(CP_UTF8, 0, server_name, cchServerName, wserver_name, cchServerName) < 0)
619     {
620       FAIL("MultiByteToWideChar() failed");
621     }
622   }
623 
624   dwVerifyFlags = 0;
625   dwRevocationFlags = 0;
626   if (check_revocation)
627     dwRevocationFlags |= CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT | CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY;
628   if (!check_server_name)
629     dwVerifyFlags |= SECURITY_FLAG_IGNORE_CERT_CN_INVALID;
630 
631   status = VerifyServerCertificate(cert, store, wserver_name ? wserver_name : L"SERVER_NAME",
632     dwRevocationFlags, dwVerifyFlags, errmsg, errmsg_len);
633 
634 cleanup:
635   LocalFree(wserver_name);
636   return status;
637 }
638 
639 
640 /* Attach private key (in PEM format) to client certificate */
641 static SECURITY_STATUS load_private_key(CERT_CONTEXT* cert, char* private_key_str, size_t len, char* errmsg, size_t errmsg_len)
642 {
643   DWORD derlen = (DWORD)len;
644   BYTE* derbuf = NULL;
645   DWORD keyblob_len = 0;
646   BYTE* keyblob = NULL;
647   HCRYPTPROV hProv = 0;
648   HCRYPTKEY hKey = 0;
649   CERT_KEY_CONTEXT cert_key_context = { 0 };
650   PCRYPT_PRIVATE_KEY_INFO  pki = NULL;
651   DWORD pki_len = 0;
652   SECURITY_STATUS status = SEC_E_OK;
653 
654   derbuf = LocalAlloc(0, derlen);
655   if (!derbuf)
656   {
657     FAIL("LocalAlloc failed");
658   }
659 
660   if (!CryptStringToBinaryA(private_key_str, (DWORD)len, CRYPT_STRING_BASE64HEADER, derbuf, &derlen, NULL, NULL))
661   {
662     FAIL("Failed to convert BASE64 private key");
663   }
664 
665   /*
666    To accommodate for both "BEGIN PRIVATE KEY" vs "BEGIN RSA PRIVATE KEY"
667    sections in PEM, we try to decode with PKCS_PRIVATE_KEY_INFO first,
668    and, if it fails, with PKCS_RSA_PRIVATE_KEY flag.
669   */
670   if (CryptDecodeObjectEx(
671     X509_ASN_ENCODING,
672     PKCS_PRIVATE_KEY_INFO,
673     derbuf, derlen,
674     CRYPT_DECODE_ALLOC_FLAG,
675     NULL, &pki, &pki_len))
676   {
677     // convert private key info to RSA private key blob
678     if (!CryptDecodeObjectEx(
679       X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
680       PKCS_RSA_PRIVATE_KEY,
681       pki->PrivateKey.pbData,
682       pki->PrivateKey.cbData,
683       CRYPT_DECODE_ALLOC_FLAG,
684       NULL, &keyblob, &keyblob_len))
685     {
686       FAIL("Failed to parse private key");
687     }
688   }
689   else if (!CryptDecodeObjectEx(
690     X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
691     PKCS_RSA_PRIVATE_KEY,
692     derbuf, derlen,
693     CRYPT_DECODE_ALLOC_FLAG, NULL,
694     &keyblob, &keyblob_len))
695   {
696     FAIL("Failed to parse private key");
697   }
698 
699   if (!CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
700   {
701     FAIL("CryptAcquireContext failed");
702   }
703 
704   if (!CryptImportKey(hProv, keyblob, keyblob_len, 0, 0, (HCRYPTKEY*)&hKey))
705   {
706     FAIL("CryptImportKey failed");
707   }
708   cert_key_context.hCryptProv = hProv;
709   cert_key_context.dwKeySpec = AT_KEYEXCHANGE;
710   cert_key_context.cbSize = sizeof(cert_key_context);
711 
712   /* assign private key to certificate context */
713   if (!CertSetCertificateContextProperty(cert, CERT_KEY_CONTEXT_PROP_ID,
714                                          CERT_STORE_NO_CRYPT_RELEASE_FLAG,
715                                          &cert_key_context))
716   {
717     FAIL("CertSetCertificateContextProperty failed");
718   }
719 
720 cleanup:
721   LocalFree(derbuf);
722   LocalFree(keyblob);
723   LocalFree(pki);
724   if (hKey)
725     CryptDestroyKey(hKey);
726   if (status)
727   {
728     if (hProv)
729       CryptReleaseContext(hProv, 0);
730   }
731   return status;
732 }
733 
734 /*
735  Given PEM strings for certificate and private key,
736  create a client certificate*
737 */
738 static CERT_CONTEXT* create_client_certificate_mem(
739   char* cert_file_content,
740   char* key_file_content,
741   char* errmsg,
742   size_t errmsg_len)
743 {
744   CERT_CONTEXT* ctx = NULL;
745   char* begin;
746   char* end;
747   CERT_BLOB cert_blob;
748   DWORD actual_content_type = 0;
749   SECURITY_STATUS status = SEC_E_OK;
750 
751   /* Parse certificate */
752   pem_locate(cert_file_content, PEM_TYPE_CERTIFICATE,
753     &begin, &end);
754 
755   if (!begin || !end)
756   {
757     SetLastError(SEC_E_INVALID_PARAMETER);
758     FAIL("Client certificate not found in PEM file");
759   }
760 
761   cert_blob.pbData = (BYTE*)begin;
762   cert_blob.cbData = (DWORD)(end - begin);
763   if (!CryptQueryObject(
764     CERT_QUERY_OBJECT_BLOB, &cert_blob,
765     CERT_QUERY_CONTENT_FLAG_CERT,
766     CERT_QUERY_FORMAT_FLAG_ALL, 0, NULL, &actual_content_type,
767     NULL, NULL, NULL, (const void**)&ctx))
768   {
769     FAIL("Can't parse client certficate");
770   }
771 
772   /* Parse key */
773   PEM_TYPE types[] = { PEM_TYPE_RSA_PRIVATE_KEY, PEM_TYPE_PRIVATE_KEY };
774   for (int i = 0; i < sizeof(types) / sizeof(types[0]); i++)
775   {
776     pem_locate(key_file_content, types[i], &begin, &end);
777     if (begin && end)
778     {
779       /* Assign key to certificate.*/
780       status = load_private_key(ctx, begin, (end - begin), errmsg, errmsg_len);
781       goto cleanup;
782     }
783   }
784 
785   if (!begin || !end)
786   {
787     SetLastError(SEC_E_INVALID_PARAMETER);
788     FAIL("Client private key not found in PEM");
789   }
790 
791 cleanup:
792   if (status && ctx)
793   {
794     CertFreeCertificateContext(ctx);
795     ctx = NULL;
796   }
797   return ctx;
798 }
799 
800 
801 /* Given cert and key, as PEM file names, create a client certificate */
802 CERT_CONTEXT* schannel_create_cert_context(char* cert_file, char* key_file, char* errmsg, size_t errmsg_len)
803 {
804   CERT_CONTEXT* ctx = NULL;
805   char* key_file_content = NULL;
806   char* cert_file_content = NULL;
807 
808   cert_file_content = pem_file_to_string(cert_file, errmsg, errmsg_len);
809 
810   if (!cert_file_content)
811     goto cleanup;
812 
813   if (cert_file == key_file)
814   {
815     key_file_content = cert_file_content;
816   }
817   else
818   {
819     key_file_content = pem_file_to_string(key_file, errmsg, errmsg_len);
820     if (!key_file_content)
821       goto cleanup;
822   }
823 
824   ctx = create_client_certificate_mem(cert_file_content, key_file_content, errmsg, errmsg_len);
825 
826 cleanup:
827   LocalFree(cert_file_content);
828   if (cert_file != key_file)
829     LocalFree(key_file_content);
830 
831   return ctx;
832 }
833 
834 /*
835   Free certificate, and all resources, created by schannel_create_cert_context()
836 */
837 void schannel_free_cert_context(const CERT_CONTEXT* cert)
838 {
839   /* release provider handle which was acquires in load_private_key() */
840   CERT_KEY_CONTEXT cert_key_context = { 0 };
841   cert_key_context.cbSize = sizeof(cert_key_context);
842   DWORD cbData = sizeof(CERT_KEY_CONTEXT);
843   HCRYPTPROV hProv = 0;
844 
845   if (CertGetCertificateContextProperty(cert, CERT_KEY_CONTEXT_PROP_ID, &cert_key_context, &cbData))
846   {
847     hProv = cert_key_context.hCryptProv;
848   }
849   CertFreeCertificateContext(cert);
850   if (hProv)
851   {
852     CryptReleaseContext(cert_key_context.hCryptProv, 0);
853   }
854 }
855