4dc2d14e56d49fc0af28738876309fe2b8eba0b8
[platform/upstream/cmake.git] / Utilities / cmcurl / lib / vtls / schannel_verify.c
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 - 2022, 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
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  */
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
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
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   while(total_bytes_read < ca_file_bufsize) {
290     DWORD bytes_to_read = (DWORD)(ca_file_bufsize - total_bytes_read);
291     DWORD bytes_read = 0;
292
293     if(!ReadFile(ca_file_handle, ca_file_buffer + total_bytes_read,
294                  bytes_to_read, &bytes_read, NULL)) {
295       char buffer[STRERROR_LEN];
296       failf(data,
297             "schannel: failed to read from CA file '%s': %s",
298             ca_file,
299             Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
300       result = CURLE_SSL_CACERT_BADFILE;
301       goto cleanup;
302     }
303     if(bytes_read == 0) {
304       /* Premature EOF -- adjust the bufsize to the new value */
305       ca_file_bufsize = total_bytes_read;
306     }
307     else {
308       total_bytes_read += bytes_read;
309     }
310   }
311
312   /* Null terminate the buffer */
313   ca_file_buffer[ca_file_bufsize] = '\0';
314
315   result = add_certs_data_to_store(trust_store,
316                                    ca_file_buffer, ca_file_bufsize,
317                                    ca_file,
318                                    data);
319
320 cleanup:
321   if(ca_file_handle != INVALID_HANDLE_VALUE) {
322     CloseHandle(ca_file_handle);
323   }
324   Curl_safefree(ca_file_buffer);
325   curlx_unicodefree(ca_file_tstr);
326
327   return result;
328 }
329
330 /*
331  * Returns the number of characters necessary to populate all the host_names.
332  * If host_names is not NULL, populate it with all the host names. Each string
333  * in the host_names is null-terminated and the last string is double
334  * null-terminated. If no DNS names are found, a single null-terminated empty
335  * string is returned.
336  */
337 static DWORD cert_get_name_string(struct Curl_easy *data,
338                                   CERT_CONTEXT *cert_context,
339                                   LPTSTR host_names,
340                                   DWORD length)
341 {
342   DWORD actual_length = 0;
343   BOOL compute_content = FALSE;
344   CERT_INFO *cert_info = NULL;
345   CERT_EXTENSION *extension = NULL;
346   CRYPT_DECODE_PARA decode_para = {0, 0, 0};
347   CERT_ALT_NAME_INFO *alt_name_info = NULL;
348   DWORD alt_name_info_size = 0;
349   BOOL ret_val = FALSE;
350   LPTSTR current_pos = NULL;
351   DWORD i;
352
353   /* CERT_NAME_SEARCH_ALL_NAMES_FLAG is available from Windows 8 onwards. */
354   if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT,
355                                   VERSION_GREATER_THAN_EQUAL)) {
356 #ifdef CERT_NAME_SEARCH_ALL_NAMES_FLAG
357     /* CertGetNameString will provide the 8-bit character string without
358      * any decoding */
359     DWORD name_flags =
360       CERT_NAME_DISABLE_IE4_UTF8_FLAG | CERT_NAME_SEARCH_ALL_NAMES_FLAG;
361     actual_length = CertGetNameString(cert_context,
362                                       CERT_NAME_DNS_TYPE,
363                                       name_flags,
364                                       NULL,
365                                       host_names,
366                                       length);
367     return actual_length;
368 #endif
369   }
370
371   compute_content = host_names != NULL && length != 0;
372
373   /* Initialize default return values. */
374   actual_length = 1;
375   if(compute_content) {
376     *host_names = '\0';
377   }
378
379   if(!cert_context) {
380     failf(data, "schannel: Null certificate context.");
381     return actual_length;
382   }
383
384   cert_info = cert_context->pCertInfo;
385   if(!cert_info) {
386     failf(data, "schannel: Null certificate info.");
387     return actual_length;
388   }
389
390   extension = CertFindExtension(szOID_SUBJECT_ALT_NAME2,
391                                 cert_info->cExtension,
392                                 cert_info->rgExtension);
393   if(!extension) {
394     failf(data, "schannel: CertFindExtension() returned no extension.");
395     return actual_length;
396   }
397
398   decode_para.cbSize = sizeof(CRYPT_DECODE_PARA);
399
400   ret_val =
401     CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
402                         szOID_SUBJECT_ALT_NAME2,
403                         extension->Value.pbData,
404                         extension->Value.cbData,
405                         CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
406                         &decode_para,
407                         &alt_name_info,
408                         &alt_name_info_size);
409   if(!ret_val) {
410     failf(data,
411           "schannel: CryptDecodeObjectEx() returned no alternate name "
412           "information.");
413     return actual_length;
414   }
415
416   current_pos = host_names;
417
418   /* Iterate over the alternate names and populate host_names. */
419   for(i = 0; i < alt_name_info->cAltEntry; i++) {
420     const CERT_ALT_NAME_ENTRY *entry = &alt_name_info->rgAltEntry[i];
421     wchar_t *dns_w = NULL;
422     size_t current_length = 0;
423
424     if(entry->dwAltNameChoice != CERT_ALT_NAME_DNS_NAME) {
425       continue;
426     }
427     if(!entry->pwszDNSName) {
428       infof(data, "schannel: Empty DNS name.");
429       continue;
430     }
431     current_length = wcslen(entry->pwszDNSName) + 1;
432     if(!compute_content) {
433       actual_length += (DWORD)current_length;
434       continue;
435     }
436     /* Sanity check to prevent buffer overrun. */
437     if((actual_length + current_length) > length) {
438       failf(data, "schannel: Not enough memory to list all host names.");
439       break;
440     }
441     dns_w = entry->pwszDNSName;
442     /* pwszDNSName is in ia5 string format and hence doesn't contain any
443      * non-ascii characters. */
444     while(*dns_w != '\0') {
445       *current_pos++ = (char)(*dns_w++);
446     }
447     *current_pos++ = '\0';
448     actual_length += (DWORD)current_length;
449   }
450   if(compute_content) {
451     /* Last string has double null-terminator. */
452     *current_pos = '\0';
453   }
454   return actual_length;
455 }
456
457 static CURLcode verify_host(struct Curl_easy *data,
458                             CERT_CONTEXT *pCertContextServer,
459                             const char * const conn_hostname)
460 {
461   CURLcode result = CURLE_PEER_FAILED_VERIFICATION;
462   TCHAR *cert_hostname_buff = NULL;
463   size_t cert_hostname_buff_index = 0;
464   size_t hostlen = strlen(conn_hostname);
465   DWORD len = 0;
466   DWORD actual_len = 0;
467
468   /* Determine the size of the string needed for the cert hostname */
469   len = cert_get_name_string(data, pCertContextServer, NULL, 0);
470   if(len == 0) {
471     failf(data,
472           "schannel: CertGetNameString() returned no "
473           "certificate name information");
474     result = CURLE_PEER_FAILED_VERIFICATION;
475     goto cleanup;
476   }
477
478   /* CertGetNameString guarantees that the returned name will not contain
479    * embedded null bytes. This appears to be undocumented behavior.
480    */
481   cert_hostname_buff = (LPTSTR)malloc(len * sizeof(TCHAR));
482   if(!cert_hostname_buff) {
483     result = CURLE_OUT_OF_MEMORY;
484     goto cleanup;
485   }
486   actual_len = cert_get_name_string(
487     data, pCertContextServer, (LPTSTR)cert_hostname_buff, len);
488
489   /* Sanity check */
490   if(actual_len != len) {
491     failf(data,
492           "schannel: CertGetNameString() returned certificate "
493           "name information of unexpected size");
494     result = CURLE_PEER_FAILED_VERIFICATION;
495     goto cleanup;
496   }
497
498   /* If HAVE_CERT_NAME_SEARCH_ALL_NAMES is available, the output
499    * will contain all DNS names, where each name is null-terminated
500    * and the last DNS name is double null-terminated. Due to this
501    * encoding, use the length of the buffer to iterate over all names.
502    */
503   result = CURLE_PEER_FAILED_VERIFICATION;
504   while(cert_hostname_buff_index < len &&
505         cert_hostname_buff[cert_hostname_buff_index] != TEXT('\0') &&
506         result == CURLE_PEER_FAILED_VERIFICATION) {
507
508     char *cert_hostname;
509
510     /* Comparing the cert name and the connection hostname encoded as UTF-8
511      * is acceptable since both values are assumed to use ASCII
512      * (or some equivalent) encoding
513      */
514     cert_hostname = curlx_convert_tchar_to_UTF8(
515       &cert_hostname_buff[cert_hostname_buff_index]);
516     if(!cert_hostname) {
517       result = CURLE_OUT_OF_MEMORY;
518     }
519     else {
520       if(Curl_cert_hostcheck(cert_hostname, strlen(cert_hostname),
521                              conn_hostname, hostlen)) {
522         infof(data,
523               "schannel: connection hostname (%s) validated "
524               "against certificate name (%s)",
525               conn_hostname, cert_hostname);
526         result = CURLE_OK;
527       }
528       else {
529         size_t cert_hostname_len;
530
531         infof(data,
532               "schannel: connection hostname (%s) did not match "
533               "against certificate name (%s)",
534               conn_hostname, cert_hostname);
535
536         cert_hostname_len =
537           _tcslen(&cert_hostname_buff[cert_hostname_buff_index]);
538
539         /* Move on to next cert name */
540         cert_hostname_buff_index += cert_hostname_len + 1;
541
542         result = CURLE_PEER_FAILED_VERIFICATION;
543       }
544       curlx_unicodefree(cert_hostname);
545     }
546   }
547
548   if(result == CURLE_PEER_FAILED_VERIFICATION) {
549     failf(data,
550           "schannel: CertGetNameString() failed to match "
551           "connection hostname (%s) against server certificate names",
552           conn_hostname);
553   }
554   else if(result != CURLE_OK)
555     failf(data, "schannel: server certificate name verification failed");
556
557 cleanup:
558   Curl_safefree(cert_hostname_buff);
559
560   return result;
561 }
562
563 CURLcode Curl_verify_certificate(struct Curl_easy *data,
564                                  struct connectdata *conn, int sockindex)
565 {
566   SECURITY_STATUS sspi_status;
567   struct ssl_connect_data *connssl = &conn->ssl[sockindex];
568   CURLcode result = CURLE_OK;
569   CERT_CONTEXT *pCertContextServer = NULL;
570   const CERT_CHAIN_CONTEXT *pChainContext = NULL;
571   HCERTCHAINENGINE cert_chain_engine = NULL;
572   HCERTSTORE trust_store = NULL;
573   const char * const conn_hostname = SSL_HOST_NAME();
574
575   DEBUGASSERT(BACKEND);
576
577   sspi_status =
578     s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle,
579                                      SECPKG_ATTR_REMOTE_CERT_CONTEXT,
580                                      &pCertContextServer);
581
582   if((sspi_status != SEC_E_OK) || !pCertContextServer) {
583     char buffer[STRERROR_LEN];
584     failf(data, "schannel: Failed to read remote certificate context: %s",
585           Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
586     result = CURLE_PEER_FAILED_VERIFICATION;
587   }
588
589   if(result == CURLE_OK &&
590       (SSL_CONN_CONFIG(CAfile) || SSL_CONN_CONFIG(ca_info_blob)) &&
591       BACKEND->use_manual_cred_validation) {
592     /*
593      * Create a chain engine that uses the certificates in the CA file as
594      * trusted certificates. This is only supported on Windows 7+.
595      */
596
597     if(curlx_verify_windows_version(6, 1, 0, PLATFORM_WINNT,
598                                     VERSION_LESS_THAN)) {
599       failf(data, "schannel: this version of Windows is too old to support "
600             "certificate verification via CA bundle file.");
601       result = CURLE_SSL_CACERT_BADFILE;
602     }
603     else {
604       /* Open the certificate store */
605       trust_store = CertOpenStore(CERT_STORE_PROV_MEMORY,
606                                   0,
607                                   (HCRYPTPROV)NULL,
608                                   CERT_STORE_CREATE_NEW_FLAG,
609                                   NULL);
610       if(!trust_store) {
611         char buffer[STRERROR_LEN];
612         failf(data, "schannel: failed to create certificate store: %s",
613               Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
614         result = CURLE_SSL_CACERT_BADFILE;
615       }
616       else {
617         const struct curl_blob *ca_info_blob = SSL_CONN_CONFIG(ca_info_blob);
618         if(ca_info_blob) {
619           result = add_certs_data_to_store(trust_store,
620                                            (const char *)ca_info_blob->data,
621                                            ca_info_blob->len,
622                                            "(memory blob)",
623                                            data);
624         }
625         else {
626           result = add_certs_file_to_store(trust_store,
627                                            SSL_CONN_CONFIG(CAfile),
628                                            data);
629         }
630       }
631     }
632
633     if(result == CURLE_OK) {
634       struct cert_chain_engine_config_win7 engine_config;
635       BOOL create_engine_result;
636
637       memset(&engine_config, 0, sizeof(engine_config));
638       engine_config.cbSize = sizeof(engine_config);
639       engine_config.hExclusiveRoot = trust_store;
640
641       /* CertCreateCertificateChainEngine will check the expected size of the
642        * CERT_CHAIN_ENGINE_CONFIG structure and fail if the specified size
643        * does not match the expected size. When this occurs, it indicates that
644        * CAINFO is not supported on the version of Windows in use.
645        */
646       create_engine_result =
647         CertCreateCertificateChainEngine(
648           (CERT_CHAIN_ENGINE_CONFIG *)&engine_config, &cert_chain_engine);
649       if(!create_engine_result) {
650         char buffer[STRERROR_LEN];
651         failf(data,
652               "schannel: failed to create certificate chain engine: %s",
653               Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
654         result = CURLE_SSL_CACERT_BADFILE;
655       }
656     }
657   }
658
659   if(result == CURLE_OK) {
660     CERT_CHAIN_PARA ChainPara;
661
662     memset(&ChainPara, 0, sizeof(ChainPara));
663     ChainPara.cbSize = sizeof(ChainPara);
664
665     if(!CertGetCertificateChain(cert_chain_engine,
666                                 pCertContextServer,
667                                 NULL,
668                                 pCertContextServer->hCertStore,
669                                 &ChainPara,
670                                 (SSL_SET_OPTION(no_revoke) ? 0 :
671                                  CERT_CHAIN_REVOCATION_CHECK_CHAIN),
672                                 NULL,
673                                 &pChainContext)) {
674       char buffer[STRERROR_LEN];
675       failf(data, "schannel: CertGetCertificateChain failed: %s",
676             Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
677       pChainContext = NULL;
678       result = CURLE_PEER_FAILED_VERIFICATION;
679     }
680
681     if(result == CURLE_OK) {
682       CERT_SIMPLE_CHAIN *pSimpleChain = pChainContext->rgpChain[0];
683       DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED);
684       dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus;
685
686       if(data->set.ssl.revoke_best_effort) {
687         /* Ignore errors when root certificates are missing the revocation
688          * list URL, or when the list could not be downloaded because the
689          * server is currently unreachable. */
690         dwTrustErrorMask &= ~(DWORD)(CERT_TRUST_REVOCATION_STATUS_UNKNOWN |
691           CERT_TRUST_IS_OFFLINE_REVOCATION);
692       }
693
694       if(dwTrustErrorMask) {
695         if(dwTrustErrorMask & CERT_TRUST_IS_REVOKED)
696           failf(data, "schannel: CertGetCertificateChain trust error"
697                 " CERT_TRUST_IS_REVOKED");
698         else if(dwTrustErrorMask & CERT_TRUST_IS_PARTIAL_CHAIN)
699           failf(data, "schannel: CertGetCertificateChain trust error"
700                 " CERT_TRUST_IS_PARTIAL_CHAIN");
701         else if(dwTrustErrorMask & CERT_TRUST_IS_UNTRUSTED_ROOT)
702           failf(data, "schannel: CertGetCertificateChain trust error"
703                 " CERT_TRUST_IS_UNTRUSTED_ROOT");
704         else if(dwTrustErrorMask & CERT_TRUST_IS_NOT_TIME_VALID)
705           failf(data, "schannel: CertGetCertificateChain trust error"
706                 " CERT_TRUST_IS_NOT_TIME_VALID");
707         else if(dwTrustErrorMask & CERT_TRUST_REVOCATION_STATUS_UNKNOWN)
708           failf(data, "schannel: CertGetCertificateChain trust error"
709                 " CERT_TRUST_REVOCATION_STATUS_UNKNOWN");
710         else
711           failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x",
712                 dwTrustErrorMask);
713         result = CURLE_PEER_FAILED_VERIFICATION;
714       }
715     }
716   }
717
718   if(result == CURLE_OK) {
719     if(SSL_CONN_CONFIG(verifyhost)) {
720       result = verify_host(data, pCertContextServer, conn_hostname);
721     }
722   }
723
724   if(cert_chain_engine) {
725     CertFreeCertificateChainEngine(cert_chain_engine);
726   }
727
728   if(trust_store) {
729     CertCloseStore(trust_store, 0);
730   }
731
732   if(pChainContext)
733     CertFreeCertificateChain(pChainContext);
734
735   if(pCertContextServer)
736     CertFreeCertificateContext(pCertContextServer);
737
738   return result;
739 }
740
741 #endif /* HAS_MANUAL_VERIFY_API */
742 #endif /* USE_SCHANNEL */