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