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