1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 2012 - 2016, Marc Hoersken, <info@marc-hoersken.de>
9  * Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com>
10  * Copyright (C) 2012 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
11  *
12  * This software is licensed as described in the file COPYING, which
13  * you should have received as part of this distribution. The terms
14  * are also available at https://curl.se/docs/copyright.html.
15  *
16  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
17  * copies of the Software, and permit persons to whom the Software is
18  * furnished to do so, under the terms of the COPYING file.
19  *
20  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21  * KIND, either express or implied.
22  *
23  ***************************************************************************/
24 
25 /*
26  * Source file for Schannel-specific certificate verification. This code should
27  * only be invoked by code in schannel.c.
28  */
29 
30 #include "curl_setup.h"
31 
32 #ifdef USE_SCHANNEL
33 #ifndef USE_WINDOWS_SSPI
34 #  error "Can't compile SCHANNEL support without SSPI."
35 #endif
36 
37 #define EXPOSE_SCHANNEL_INTERNAL_STRUCTS
38 #include "schannel.h"
39 
40 #ifdef HAS_MANUAL_VERIFY_API
41 
42 #include "vtls.h"
43 #include "sendf.h"
44 #include "strerror.h"
45 #include "curl_multibyte.h"
46 #include "curl_printf.h"
47 #include "hostcheck.h"
48 #include "version_win32.h"
49 
50 /* The last #include file should be: */
51 #include "curl_memory.h"
52 #include "memdebug.h"
53 
54 #define BACKEND connssl->backend
55 
56 #define MAX_CAFILE_SIZE 1048576 /* 1 MiB */
57 #define BEGIN_CERT "-----BEGIN CERTIFICATE-----"
58 #define END_CERT "\n-----END CERTIFICATE-----"
59 
60 struct cert_chain_engine_config_win7 {
61   DWORD cbSize;
62   HCERTSTORE hRestrictedRoot;
63   HCERTSTORE hRestrictedTrust;
64   HCERTSTORE hRestrictedOther;
65   DWORD cAdditionalStore;
66   HCERTSTORE *rghAdditionalStore;
67   DWORD dwFlags;
68   DWORD dwUrlRetrievalTimeout;
69   DWORD MaximumCachedCertificates;
70   DWORD CycleDetectionModulus;
71   HCERTSTORE hExclusiveRoot;
72   HCERTSTORE hExclusiveTrustedPeople;
73 };
74 
is_cr_or_lf(char c)75 static int is_cr_or_lf(char c)
76 {
77   return c == '\r' || c == '\n';
78 }
79 
80 /* Search the substring needle,needlelen into string haystack,haystacklen
81  * Strings don't need to be terminated by a '\0'.
82  * Similar of OSX/Linux memmem (not available on Visual Studio).
83  * Return position of beginning of first occurrence or NULL if not found
84  */
c_memmem(const void * haystack,size_t haystacklen,const void * needle,size_t needlelen)85 static const char *c_memmem(const void *haystack, size_t haystacklen,
86                             const void *needle, size_t needlelen)
87 {
88   const char *p;
89   char first;
90   const char *str_limit = (const char *)haystack + haystacklen;
91   if(!needlelen || needlelen > haystacklen)
92     return NULL;
93   first = *(const char *)needle;
94   for(p = (const char *)haystack; p <= (str_limit - needlelen); p++)
95     if(((*p) == first) && (memcmp(p, needle, needlelen) == 0))
96       return p;
97 
98   return NULL;
99 }
100 
add_certs_data_to_store(HCERTSTORE trust_store,const char * ca_buffer,size_t ca_buffer_size,const char * ca_file_text,struct Curl_easy * data)101 static CURLcode add_certs_data_to_store(HCERTSTORE trust_store,
102                                         const char *ca_buffer,
103                                         size_t ca_buffer_size,
104                                         const char *ca_file_text,
105                                         struct Curl_easy *data)
106 {
107   const size_t begin_cert_len = strlen(BEGIN_CERT);
108   const size_t end_cert_len = strlen(END_CERT);
109   CURLcode result = CURLE_OK;
110   int num_certs = 0;
111   bool more_certs = 1;
112   const char *current_ca_file_ptr = ca_buffer;
113   const char *ca_buffer_limit = ca_buffer + ca_buffer_size;
114 
115   while(more_certs && (current_ca_file_ptr<ca_buffer_limit)) {
116     const char *begin_cert_ptr = c_memmem(current_ca_file_ptr,
117                                           ca_buffer_limit-current_ca_file_ptr,
118                                           BEGIN_CERT,
119                                           begin_cert_len);
120     if(!begin_cert_ptr || !is_cr_or_lf(begin_cert_ptr[begin_cert_len])) {
121       more_certs = 0;
122     }
123     else {
124       const char *end_cert_ptr = c_memmem(begin_cert_ptr,
125                                           ca_buffer_limit-begin_cert_ptr,
126                                           END_CERT,
127                                           end_cert_len);
128       if(!end_cert_ptr) {
129         failf(data,
130               "schannel: CA file '%s' is not correctly formatted",
131               ca_file_text);
132         result = CURLE_SSL_CACERT_BADFILE;
133         more_certs = 0;
134       }
135       else {
136         CERT_BLOB cert_blob;
137         CERT_CONTEXT *cert_context = NULL;
138         BOOL add_cert_result = FALSE;
139         DWORD actual_content_type = 0;
140         DWORD cert_size = (DWORD)
141           ((end_cert_ptr + end_cert_len) - begin_cert_ptr);
142 
143         cert_blob.pbData = (BYTE *)begin_cert_ptr;
144         cert_blob.cbData = cert_size;
145         if(!CryptQueryObject(CERT_QUERY_OBJECT_BLOB,
146                              &cert_blob,
147                              CERT_QUERY_CONTENT_FLAG_CERT,
148                              CERT_QUERY_FORMAT_FLAG_ALL,
149                              0,
150                              NULL,
151                              &actual_content_type,
152                              NULL,
153                              NULL,
154                              NULL,
155                              (const void **)&cert_context)) {
156           char buffer[STRERROR_LEN];
157           failf(data,
158                 "schannel: failed to extract certificate from CA file "
159                 "'%s': %s",
160                 ca_file_text,
161                 Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
162           result = CURLE_SSL_CACERT_BADFILE;
163           more_certs = 0;
164         }
165         else {
166           current_ca_file_ptr = begin_cert_ptr + cert_size;
167 
168           /* Sanity check that the cert_context object is the right type */
169           if(CERT_QUERY_CONTENT_CERT != actual_content_type) {
170             failf(data,
171                   "schannel: unexpected content type '%d' when extracting "
172                   "certificate from CA file '%s'",
173                   actual_content_type, ca_file_text);
174             result = CURLE_SSL_CACERT_BADFILE;
175             more_certs = 0;
176           }
177           else {
178             add_cert_result =
179               CertAddCertificateContextToStore(trust_store,
180                                                cert_context,
181                                                CERT_STORE_ADD_ALWAYS,
182                                                NULL);
183             CertFreeCertificateContext(cert_context);
184             if(!add_cert_result) {
185               char buffer[STRERROR_LEN];
186               failf(data,
187                     "schannel: failed to add certificate from CA file '%s' "
188                     "to certificate store: %s",
189                     ca_file_text,
190                     Curl_winapi_strerror(GetLastError(), buffer,
191                                          sizeof(buffer)));
192               result = CURLE_SSL_CACERT_BADFILE;
193               more_certs = 0;
194             }
195             else {
196               num_certs++;
197             }
198           }
199         }
200       }
201     }
202   }
203 
204   if(result == CURLE_OK) {
205     if(!num_certs) {
206       infof(data,
207             "schannel: did not add any certificates from CA file '%s'",
208             ca_file_text);
209     }
210     else {
211       infof(data,
212             "schannel: added %d certificate(s) from CA file '%s'",
213             num_certs, ca_file_text);
214     }
215   }
216   return result;
217 }
218 
add_certs_file_to_store(HCERTSTORE trust_store,const char * ca_file,struct Curl_easy * data)219 static CURLcode add_certs_file_to_store(HCERTSTORE trust_store,
220                                         const char *ca_file,
221                                         struct Curl_easy *data)
222 {
223   CURLcode result;
224   HANDLE ca_file_handle = INVALID_HANDLE_VALUE;
225   LARGE_INTEGER file_size;
226   char *ca_file_buffer = NULL;
227   TCHAR *ca_file_tstr = NULL;
228   size_t ca_file_bufsize = 0;
229   DWORD total_bytes_read = 0;
230 
231   ca_file_tstr = curlx_convert_UTF8_to_tchar((char *)ca_file);
232   if(!ca_file_tstr) {
233     char buffer[STRERROR_LEN];
234     failf(data,
235           "schannel: invalid path name for CA file '%s': %s",
236           ca_file,
237           Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
238     result = CURLE_SSL_CACERT_BADFILE;
239     goto cleanup;
240   }
241 
242   /*
243    * Read the CA file completely into memory before parsing it. This
244    * optimizes for the common case where the CA file will be relatively
245    * small ( < 1 MiB ).
246    */
247   ca_file_handle = CreateFile(ca_file_tstr,
248                               GENERIC_READ,
249                               FILE_SHARE_READ,
250                               NULL,
251                               OPEN_EXISTING,
252                               FILE_ATTRIBUTE_NORMAL,
253                               NULL);
254   if(ca_file_handle == INVALID_HANDLE_VALUE) {
255     char buffer[STRERROR_LEN];
256     failf(data,
257           "schannel: failed to open CA file '%s': %s",
258           ca_file,
259           Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
260     result = CURLE_SSL_CACERT_BADFILE;
261     goto cleanup;
262   }
263 
264   if(!GetFileSizeEx(ca_file_handle, &file_size)) {
265     char buffer[STRERROR_LEN];
266     failf(data,
267           "schannel: failed to determine size of CA file '%s': %s",
268           ca_file,
269           Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
270     result = CURLE_SSL_CACERT_BADFILE;
271     goto cleanup;
272   }
273 
274   if(file_size.QuadPart > MAX_CAFILE_SIZE) {
275     failf(data,
276           "schannel: CA file exceeds max size of %u bytes",
277           MAX_CAFILE_SIZE);
278     result = CURLE_SSL_CACERT_BADFILE;
279     goto cleanup;
280   }
281 
282   ca_file_bufsize = (size_t)file_size.QuadPart;
283   ca_file_buffer = (char *)malloc(ca_file_bufsize + 1);
284   if(!ca_file_buffer) {
285     result = CURLE_OUT_OF_MEMORY;
286     goto cleanup;
287   }
288 
289   result = CURLE_OK;
290   while(total_bytes_read < ca_file_bufsize) {
291     DWORD bytes_to_read = (DWORD)(ca_file_bufsize - total_bytes_read);
292     DWORD bytes_read = 0;
293 
294     if(!ReadFile(ca_file_handle, ca_file_buffer + total_bytes_read,
295                  bytes_to_read, &bytes_read, NULL)) {
296       char buffer[STRERROR_LEN];
297       failf(data,
298             "schannel: failed to read from CA file '%s': %s",
299             ca_file,
300             Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
301       result = CURLE_SSL_CACERT_BADFILE;
302       goto cleanup;
303     }
304     if(bytes_read == 0) {
305       /* Premature EOF -- adjust the bufsize to the new value */
306       ca_file_bufsize = total_bytes_read;
307     }
308     else {
309       total_bytes_read += bytes_read;
310     }
311   }
312 
313   /* Null terminate the buffer */
314   ca_file_buffer[ca_file_bufsize] = '\0';
315 
316   if(result != CURLE_OK) {
317     goto cleanup;
318   }
319   result = add_certs_data_to_store(trust_store,
320                                    ca_file_buffer, ca_file_bufsize,
321                                    ca_file,
322                                    data);
323 
324 cleanup:
325   if(ca_file_handle != INVALID_HANDLE_VALUE) {
326     CloseHandle(ca_file_handle);
327   }
328   Curl_safefree(ca_file_buffer);
329   curlx_unicodefree(ca_file_tstr);
330 
331   return result;
332 }
333 
334 /*
335  * Returns the number of characters necessary to populate all the host_names.
336  * If host_names is not NULL, populate it with all the host names. Each string
337  * in the host_names is null-terminated and the last string is double
338  * null-terminated. If no DNS names are found, a single null-terminated empty
339  * string is returned.
340  */
cert_get_name_string(struct Curl_easy * data,CERT_CONTEXT * cert_context,LPTSTR host_names,DWORD length)341 static DWORD cert_get_name_string(struct Curl_easy *data,
342                                   CERT_CONTEXT *cert_context,
343                                   LPTSTR host_names,
344                                   DWORD length)
345 {
346   DWORD actual_length = 0;
347   BOOL compute_content = FALSE;
348   CERT_INFO *cert_info = NULL;
349   CERT_EXTENSION *extension = NULL;
350   CRYPT_DECODE_PARA decode_para = {0, 0, 0};
351   CERT_ALT_NAME_INFO *alt_name_info = NULL;
352   DWORD alt_name_info_size = 0;
353   BOOL ret_val = FALSE;
354   LPTSTR current_pos = NULL;
355   DWORD i;
356 
357   /* CERT_NAME_SEARCH_ALL_NAMES_FLAG is available from Windows 8 onwards. */
358   if(curlx_verify_windows_version(6, 2, PLATFORM_WINNT,
359                                   VERSION_GREATER_THAN_EQUAL)) {
360 #ifdef CERT_NAME_SEARCH_ALL_NAMES_FLAG
361     /* CertGetNameString will provide the 8-bit character string without
362      * any decoding */
363     DWORD name_flags =
364       CERT_NAME_DISABLE_IE4_UTF8_FLAG | CERT_NAME_SEARCH_ALL_NAMES_FLAG;
365     actual_length = CertGetNameString(cert_context,
366                                       CERT_NAME_DNS_TYPE,
367                                       name_flags,
368                                       NULL,
369                                       host_names,
370                                       length);
371     return actual_length;
372 #endif
373   }
374 
375   compute_content = host_names != NULL && length != 0;
376 
377   /* Initialize default return values. */
378   actual_length = 1;
379   if(compute_content) {
380     *host_names = '\0';
381   }
382 
383   if(!cert_context) {
384     failf(data, "schannel: Null certificate context.");
385     return actual_length;
386   }
387 
388   cert_info = cert_context->pCertInfo;
389   if(!cert_info) {
390     failf(data, "schannel: Null certificate info.");
391     return actual_length;
392   }
393 
394   extension = CertFindExtension(szOID_SUBJECT_ALT_NAME2,
395                                 cert_info->cExtension,
396                                 cert_info->rgExtension);
397   if(!extension) {
398     failf(data, "schannel: CertFindExtension() returned no extension.");
399     return actual_length;
400   }
401 
402   decode_para.cbSize = sizeof(CRYPT_DECODE_PARA);
403 
404   ret_val =
405     CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
406                         szOID_SUBJECT_ALT_NAME2,
407                         extension->Value.pbData,
408                         extension->Value.cbData,
409                         CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
410                         &decode_para,
411                         &alt_name_info,
412                         &alt_name_info_size);
413   if(!ret_val) {
414     failf(data,
415           "schannel: CryptDecodeObjectEx() returned no alternate name "
416           "information.");
417     return actual_length;
418   }
419 
420   current_pos = host_names;
421 
422   /* Iterate over the alternate names and populate host_names. */
423   for(i = 0; i < alt_name_info->cAltEntry; i++) {
424     const CERT_ALT_NAME_ENTRY *entry = &alt_name_info->rgAltEntry[i];
425     wchar_t *dns_w = NULL;
426     size_t current_length = 0;
427 
428     if(entry->dwAltNameChoice != CERT_ALT_NAME_DNS_NAME) {
429       continue;
430     }
431     if(!entry->pwszDNSName) {
432       infof(data, "schannel: Empty DNS name.");
433       continue;
434     }
435     current_length = wcslen(entry->pwszDNSName) + 1;
436     if(!compute_content) {
437       actual_length += (DWORD)current_length;
438       continue;
439     }
440     /* Sanity check to prevent buffer overrun. */
441     if((actual_length + current_length) > length) {
442       failf(data, "schannel: Not enough memory to list all host names.");
443       break;
444     }
445     dns_w = entry->pwszDNSName;
446     /* pwszDNSName is in ia5 string format and hence doesn't contain any
447      * non-ascii characters. */
448     while(*dns_w != '\0') {
449       *current_pos++ = (char)(*dns_w++);
450     }
451     *current_pos++ = '\0';
452     actual_length += (DWORD)current_length;
453   }
454   if(compute_content) {
455     /* Last string has double null-terminator. */
456     *current_pos = '\0';
457   }
458   return actual_length;
459 }
460 
verify_host(struct Curl_easy * data,CERT_CONTEXT * pCertContextServer,const char * const conn_hostname)461 static CURLcode verify_host(struct Curl_easy *data,
462                             CERT_CONTEXT *pCertContextServer,
463                             const char * const conn_hostname)
464 {
465   CURLcode result = CURLE_PEER_FAILED_VERIFICATION;
466   TCHAR *cert_hostname_buff = NULL;
467   size_t cert_hostname_buff_index = 0;
468   DWORD len = 0;
469   DWORD actual_len = 0;
470 
471   /* Determine the size of the string needed for the cert hostname */
472   len = cert_get_name_string(data, pCertContextServer, NULL, 0);
473   if(len == 0) {
474     failf(data,
475           "schannel: CertGetNameString() returned no "
476           "certificate name information");
477     result = CURLE_PEER_FAILED_VERIFICATION;
478     goto cleanup;
479   }
480 
481   /* CertGetNameString guarantees that the returned name will not contain
482    * embedded null bytes. This appears to be undocumented behavior.
483    */
484   cert_hostname_buff = (LPTSTR)malloc(len * sizeof(TCHAR));
485   if(!cert_hostname_buff) {
486     result = CURLE_OUT_OF_MEMORY;
487     goto cleanup;
488   }
489   actual_len = cert_get_name_string(
490     data, pCertContextServer, (LPTSTR)cert_hostname_buff, len);
491 
492   /* Sanity check */
493   if(actual_len != len) {
494     failf(data,
495           "schannel: CertGetNameString() returned certificate "
496           "name information of unexpected size");
497     result = CURLE_PEER_FAILED_VERIFICATION;
498     goto cleanup;
499   }
500 
501   /* If HAVE_CERT_NAME_SEARCH_ALL_NAMES is available, the output
502    * will contain all DNS names, where each name is null-terminated
503    * and the last DNS name is double null-terminated. Due to this
504    * encoding, use the length of the buffer to iterate over all names.
505    */
506   result = CURLE_PEER_FAILED_VERIFICATION;
507   while(cert_hostname_buff_index < len &&
508         cert_hostname_buff[cert_hostname_buff_index] != TEXT('\0') &&
509         result == CURLE_PEER_FAILED_VERIFICATION) {
510 
511     char *cert_hostname;
512 
513     /* Comparing the cert name and the connection hostname encoded as UTF-8
514      * is acceptable since both values are assumed to use ASCII
515      * (or some equivalent) encoding
516      */
517     cert_hostname = curlx_convert_tchar_to_UTF8(
518       &cert_hostname_buff[cert_hostname_buff_index]);
519     if(!cert_hostname) {
520       result = CURLE_OUT_OF_MEMORY;
521     }
522     else {
523       int match_result;
524 
525       match_result = Curl_cert_hostcheck(cert_hostname, conn_hostname);
526       if(match_result == CURL_HOST_MATCH) {
527         infof(data,
528               "schannel: connection hostname (%s) validated "
529               "against certificate name (%s)",
530               conn_hostname, cert_hostname);
531         result = CURLE_OK;
532       }
533       else {
534         size_t cert_hostname_len;
535 
536         infof(data,
537               "schannel: connection hostname (%s) did not match "
538               "against certificate name (%s)",
539               conn_hostname, cert_hostname);
540 
541         cert_hostname_len =
542           _tcslen(&cert_hostname_buff[cert_hostname_buff_index]);
543 
544         /* Move on to next cert name */
545         cert_hostname_buff_index += cert_hostname_len + 1;
546 
547         result = CURLE_PEER_FAILED_VERIFICATION;
548       }
549       curlx_unicodefree(cert_hostname);
550     }
551   }
552 
553   if(result == CURLE_PEER_FAILED_VERIFICATION) {
554     failf(data,
555           "schannel: CertGetNameString() failed to match "
556           "connection hostname (%s) against server certificate names",
557           conn_hostname);
558   }
559   else if(result != CURLE_OK)
560     failf(data, "schannel: server certificate name verification failed");
561 
562 cleanup:
563   Curl_safefree(cert_hostname_buff);
564 
565   return result;
566 }
567 
Curl_verify_certificate(struct Curl_easy * data,struct connectdata * conn,int sockindex)568 CURLcode Curl_verify_certificate(struct Curl_easy *data,
569                                  struct connectdata *conn, int sockindex)
570 {
571   SECURITY_STATUS sspi_status;
572   struct ssl_connect_data *connssl = &conn->ssl[sockindex];
573   CURLcode result = CURLE_OK;
574   CERT_CONTEXT *pCertContextServer = NULL;
575   const CERT_CHAIN_CONTEXT *pChainContext = NULL;
576   HCERTCHAINENGINE cert_chain_engine = NULL;
577   HCERTSTORE trust_store = NULL;
578   const char * const conn_hostname = SSL_HOST_NAME();
579 
580   sspi_status =
581     s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle,
582                                      SECPKG_ATTR_REMOTE_CERT_CONTEXT,
583                                      &pCertContextServer);
584 
585   if((sspi_status != SEC_E_OK) || !pCertContextServer) {
586     char buffer[STRERROR_LEN];
587     failf(data, "schannel: Failed to read remote certificate context: %s",
588           Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
589     result = CURLE_PEER_FAILED_VERIFICATION;
590   }
591 
592   if(result == CURLE_OK &&
593       (SSL_CONN_CONFIG(CAfile) || SSL_CONN_CONFIG(ca_info_blob)) &&
594       BACKEND->use_manual_cred_validation) {
595     /*
596      * Create a chain engine that uses the certificates in the CA file as
597      * trusted certificates. This is only supported on Windows 7+.
598      */
599 
600     if(curlx_verify_windows_version(6, 1, PLATFORM_WINNT, VERSION_LESS_THAN)) {
601       failf(data, "schannel: this version of Windows is too old to support "
602             "certificate verification via CA bundle file.");
603       result = CURLE_SSL_CACERT_BADFILE;
604     }
605     else {
606       /* Open the certificate store */
607       trust_store = CertOpenStore(CERT_STORE_PROV_MEMORY,
608                                   0,
609                                   (HCRYPTPROV)NULL,
610                                   CERT_STORE_CREATE_NEW_FLAG,
611                                   NULL);
612       if(!trust_store) {
613         char buffer[STRERROR_LEN];
614         failf(data, "schannel: failed to create certificate store: %s",
615               Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
616         result = CURLE_SSL_CACERT_BADFILE;
617       }
618       else {
619         const struct curl_blob *ca_info_blob = SSL_CONN_CONFIG(ca_info_blob);
620         if(ca_info_blob) {
621           result = add_certs_data_to_store(trust_store,
622                                            (const char *)ca_info_blob->data,
623                                            ca_info_blob->len,
624                                            "(memory blob)",
625                                            data);
626         }
627         else {
628           result = add_certs_file_to_store(trust_store,
629                                            SSL_CONN_CONFIG(CAfile),
630                                            data);
631         }
632       }
633     }
634 
635     if(result == CURLE_OK) {
636       struct cert_chain_engine_config_win7 engine_config;
637       BOOL create_engine_result;
638 
639       memset(&engine_config, 0, sizeof(engine_config));
640       engine_config.cbSize = sizeof(engine_config);
641       engine_config.hExclusiveRoot = trust_store;
642 
643       /* CertCreateCertificateChainEngine will check the expected size of the
644        * CERT_CHAIN_ENGINE_CONFIG structure and fail if the specified size
645        * does not match the expected size. When this occurs, it indicates that
646        * CAINFO is not supported on the version of Windows in use.
647        */
648       create_engine_result =
649         CertCreateCertificateChainEngine(
650           (CERT_CHAIN_ENGINE_CONFIG *)&engine_config, &cert_chain_engine);
651       if(!create_engine_result) {
652         char buffer[STRERROR_LEN];
653         failf(data,
654               "schannel: failed to create certificate chain engine: %s",
655               Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
656         result = CURLE_SSL_CACERT_BADFILE;
657       }
658     }
659   }
660 
661   if(result == CURLE_OK) {
662     CERT_CHAIN_PARA ChainPara;
663 
664     memset(&ChainPara, 0, sizeof(ChainPara));
665     ChainPara.cbSize = sizeof(ChainPara);
666 
667     if(!CertGetCertificateChain(cert_chain_engine,
668                                 pCertContextServer,
669                                 NULL,
670                                 pCertContextServer->hCertStore,
671                                 &ChainPara,
672                                 (SSL_SET_OPTION(no_revoke) ? 0 :
673                                  CERT_CHAIN_REVOCATION_CHECK_CHAIN),
674                                 NULL,
675                                 &pChainContext)) {
676       char buffer[STRERROR_LEN];
677       failf(data, "schannel: CertGetCertificateChain failed: %s",
678             Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
679       pChainContext = NULL;
680       result = CURLE_PEER_FAILED_VERIFICATION;
681     }
682 
683     if(result == CURLE_OK) {
684       CERT_SIMPLE_CHAIN *pSimpleChain = pChainContext->rgpChain[0];
685       DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED);
686       dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus;
687 
688       if(data->set.ssl.revoke_best_effort) {
689         /* Ignore errors when root certificates are missing the revocation
690          * list URL, or when the list could not be downloaded because the
691          * server is currently unreachable. */
692         dwTrustErrorMask &= ~(DWORD)(CERT_TRUST_REVOCATION_STATUS_UNKNOWN |
693           CERT_TRUST_IS_OFFLINE_REVOCATION);
694       }
695 
696       if(dwTrustErrorMask) {
697         if(dwTrustErrorMask & CERT_TRUST_IS_REVOKED)
698           failf(data, "schannel: CertGetCertificateChain trust error"
699                 " CERT_TRUST_IS_REVOKED");
700         else if(dwTrustErrorMask & CERT_TRUST_IS_PARTIAL_CHAIN)
701           failf(data, "schannel: CertGetCertificateChain trust error"
702                 " CERT_TRUST_IS_PARTIAL_CHAIN");
703         else if(dwTrustErrorMask & CERT_TRUST_IS_UNTRUSTED_ROOT)
704           failf(data, "schannel: CertGetCertificateChain trust error"
705                 " CERT_TRUST_IS_UNTRUSTED_ROOT");
706         else if(dwTrustErrorMask & CERT_TRUST_IS_NOT_TIME_VALID)
707           failf(data, "schannel: CertGetCertificateChain trust error"
708                 " CERT_TRUST_IS_NOT_TIME_VALID");
709         else if(dwTrustErrorMask & CERT_TRUST_REVOCATION_STATUS_UNKNOWN)
710           failf(data, "schannel: CertGetCertificateChain trust error"
711                 " CERT_TRUST_REVOCATION_STATUS_UNKNOWN");
712         else
713           failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x",
714                 dwTrustErrorMask);
715         result = CURLE_PEER_FAILED_VERIFICATION;
716       }
717     }
718   }
719 
720   if(result == CURLE_OK) {
721     if(SSL_CONN_CONFIG(verifyhost)) {
722       result = verify_host(data, pCertContextServer, conn_hostname);
723     }
724   }
725 
726   if(cert_chain_engine) {
727     CertFreeCertificateChainEngine(cert_chain_engine);
728   }
729 
730   if(trust_store) {
731     CertCloseStore(trust_store, 0);
732   }
733 
734   if(pChainContext)
735     CertFreeCertificateChain(pChainContext);
736 
737   if(pCertContextServer)
738     CertFreeCertificateContext(pCertContextServer);
739 
740   return result;
741 }
742 
743 #endif /* HAS_MANUAL_VERIFY_API */
744 #endif /* USE_SCHANNEL */
745