schannel_verify.c (33629B)
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 "cannot compile SCHANNEL support without SSPI." 37 #endif 38 39 #include "schannel.h" 40 #include "schannel_int.h" 41 42 #include "../curlx/inet_pton.h" 43 #include "vtls.h" 44 #include "vtls_int.h" 45 #include "../sendf.h" 46 #include "../strerror.h" 47 #include "../curlx/winapi.h" 48 #include "../curlx/multibyte.h" 49 #include "../curl_printf.h" 50 #include "hostcheck.h" 51 #include "../curlx/version_win32.h" 52 53 /* The last #include file should be: */ 54 #include "../curl_memory.h" 55 #include "../memdebug.h" 56 57 #define BACKEND ((struct schannel_ssl_backend_data *)connssl->backend) 58 59 #ifdef HAS_MANUAL_VERIFY_API 60 61 #ifdef __MINGW32CE__ 62 #define CERT_QUERY_OBJECT_BLOB 0x00000002 63 #define CERT_QUERY_CONTENT_CERT 1 64 #define CERT_QUERY_CONTENT_FLAG_CERT (1 << CERT_QUERY_CONTENT_CERT) 65 #define CERT_QUERY_FORMAT_BINARY 1 66 #define CERT_QUERY_FORMAT_BASE64_ENCODED 2 67 #define CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED 3 68 #define CERT_QUERY_FORMAT_FLAG_ALL \ 69 (1 << CERT_QUERY_FORMAT_BINARY) | \ 70 (1 << CERT_QUERY_FORMAT_BASE64_ENCODED) | \ 71 (1 << CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED) 72 #define CERT_CHAIN_REVOCATION_CHECK_CHAIN 0x20000000 73 #define CERT_NAME_DISABLE_IE4_UTF8_FLAG 0x00010000 74 #define CERT_TRUST_IS_OFFLINE_REVOCATION 0x01000000 75 #endif /* __MINGW32CE__ */ 76 77 #define MAX_CAFILE_SIZE 1048576 /* 1 MiB */ 78 #define BEGIN_CERT "-----BEGIN CERTIFICATE-----" 79 #define END_CERT "\n-----END CERTIFICATE-----" 80 81 struct cert_chain_engine_config_win8 { 82 DWORD cbSize; 83 HCERTSTORE hRestrictedRoot; 84 HCERTSTORE hRestrictedTrust; 85 HCERTSTORE hRestrictedOther; 86 DWORD cAdditionalStore; 87 HCERTSTORE *rghAdditionalStore; 88 DWORD dwFlags; 89 DWORD dwUrlRetrievalTimeout; 90 DWORD MaximumCachedCertificates; 91 DWORD CycleDetectionModulus; 92 HCERTSTORE hExclusiveRoot; 93 HCERTSTORE hExclusiveTrustedPeople; 94 DWORD dwExclusiveFlags; 95 }; 96 97 /* Not defined before mingw-w64 4.0.0 */ 98 #ifndef CERT_CHAIN_EXCLUSIVE_ENABLE_CA_FLAG 99 #define CERT_CHAIN_EXCLUSIVE_ENABLE_CA_FLAG 0x00000001 100 #endif 101 102 /* Legacy structure to supply size to Win7 clients */ 103 struct cert_chain_engine_config_win7 { 104 DWORD cbSize; 105 HCERTSTORE hRestrictedRoot; 106 HCERTSTORE hRestrictedTrust; 107 HCERTSTORE hRestrictedOther; 108 DWORD cAdditionalStore; 109 HCERTSTORE *rghAdditionalStore; 110 DWORD dwFlags; 111 DWORD dwUrlRetrievalTimeout; 112 DWORD MaximumCachedCertificates; 113 DWORD CycleDetectionModulus; 114 HCERTSTORE hExclusiveRoot; 115 HCERTSTORE hExclusiveTrustedPeople; 116 }; 117 118 #ifndef UNDER_CE 119 static int is_cr_or_lf(char c) 120 { 121 return c == '\r' || c == '\n'; 122 } 123 124 /* Search the substring needle,needlelen into string haystack,haystacklen 125 * Strings do not need to be terminated by a '\0'. 126 * Similar of macOS/Linux memmem (not available on Visual Studio). 127 * Return position of beginning of first occurrence or NULL if not found 128 */ 129 static const char *c_memmem(const void *haystack, size_t haystacklen, 130 const void *needle, size_t needlelen) 131 { 132 const char *p; 133 char first; 134 const char *str_limit = (const char *)haystack + haystacklen; 135 if(!needlelen || needlelen > haystacklen) 136 return NULL; 137 first = *(const char *)needle; 138 for(p = (const char *)haystack; p <= (str_limit - needlelen); p++) 139 if(((*p) == first) && (memcmp(p, needle, needlelen) == 0)) 140 return p; 141 142 return NULL; 143 } 144 145 static CURLcode add_certs_data_to_store(HCERTSTORE trust_store, 146 const char *ca_buffer, 147 size_t ca_buffer_size, 148 const char *ca_file_text, 149 struct Curl_easy *data) 150 { 151 const size_t begin_cert_len = strlen(BEGIN_CERT); 152 const size_t end_cert_len = strlen(END_CERT); 153 CURLcode result = CURLE_OK; 154 int num_certs = 0; 155 bool more_certs = 1; 156 const char *current_ca_file_ptr = ca_buffer; 157 const char *ca_buffer_limit = ca_buffer + ca_buffer_size; 158 159 while(more_certs && (current_ca_file_ptr < ca_buffer_limit)) { 160 const char *begin_cert_ptr = c_memmem(current_ca_file_ptr, 161 ca_buffer_limit-current_ca_file_ptr, 162 BEGIN_CERT, 163 begin_cert_len); 164 if(!begin_cert_ptr || !is_cr_or_lf(begin_cert_ptr[begin_cert_len])) { 165 more_certs = 0; 166 } 167 else { 168 const char *end_cert_ptr = c_memmem(begin_cert_ptr, 169 ca_buffer_limit-begin_cert_ptr, 170 END_CERT, 171 end_cert_len); 172 if(!end_cert_ptr) { 173 failf(data, 174 "schannel: CA file '%s' is not correctly formatted", 175 ca_file_text); 176 result = CURLE_SSL_CACERT_BADFILE; 177 more_certs = 0; 178 } 179 else { 180 CERT_BLOB cert_blob; 181 const CERT_CONTEXT *cert_context = NULL; 182 BOOL add_cert_result = FALSE; 183 DWORD actual_content_type = 0; 184 DWORD cert_size = (DWORD) 185 ((end_cert_ptr + end_cert_len) - begin_cert_ptr); 186 187 cert_blob.pbData = (BYTE *)CURL_UNCONST(begin_cert_ptr); 188 cert_blob.cbData = cert_size; 189 if(!CryptQueryObject(CERT_QUERY_OBJECT_BLOB, 190 &cert_blob, 191 CERT_QUERY_CONTENT_FLAG_CERT, 192 CERT_QUERY_FORMAT_FLAG_ALL, 193 0, 194 NULL, 195 &actual_content_type, 196 NULL, 197 NULL, 198 NULL, 199 (const void **)&cert_context)) { 200 char buffer[WINAPI_ERROR_LEN]; 201 failf(data, 202 "schannel: failed to extract certificate from CA file " 203 "'%s': %s", 204 ca_file_text, 205 curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); 206 result = CURLE_SSL_CACERT_BADFILE; 207 more_certs = 0; 208 } 209 else { 210 current_ca_file_ptr = begin_cert_ptr + cert_size; 211 212 /* Sanity check that the cert_context object is the right type */ 213 if(CERT_QUERY_CONTENT_CERT != actual_content_type) { 214 failf(data, 215 "schannel: unexpected content type '%lu' when extracting " 216 "certificate from CA file '%s'", 217 actual_content_type, ca_file_text); 218 result = CURLE_SSL_CACERT_BADFILE; 219 more_certs = 0; 220 } 221 else { 222 add_cert_result = 223 CertAddCertificateContextToStore(trust_store, 224 cert_context, 225 CERT_STORE_ADD_ALWAYS, 226 NULL); 227 CertFreeCertificateContext(cert_context); 228 if(!add_cert_result) { 229 char buffer[WINAPI_ERROR_LEN]; 230 failf(data, 231 "schannel: failed to add certificate from CA file '%s' " 232 "to certificate store: %s", 233 ca_file_text, 234 curlx_winapi_strerror(GetLastError(), buffer, 235 sizeof(buffer))); 236 result = CURLE_SSL_CACERT_BADFILE; 237 more_certs = 0; 238 } 239 else { 240 num_certs++; 241 } 242 } 243 } 244 } 245 } 246 } 247 248 if(result == CURLE_OK) { 249 if(!num_certs) { 250 infof(data, 251 "schannel: did not add any certificates from CA file '%s'", 252 ca_file_text); 253 } 254 else { 255 infof(data, 256 "schannel: added %d certificate(s) from CA file '%s'", 257 num_certs, ca_file_text); 258 } 259 } 260 return result; 261 } 262 263 static CURLcode add_certs_file_to_store(HCERTSTORE trust_store, 264 const char *ca_file, 265 struct Curl_easy *data) 266 { 267 CURLcode result; 268 HANDLE ca_file_handle = INVALID_HANDLE_VALUE; 269 LARGE_INTEGER file_size; 270 char *ca_file_buffer = NULL; 271 TCHAR *ca_file_tstr = NULL; 272 size_t ca_file_bufsize = 0; 273 DWORD total_bytes_read = 0; 274 275 ca_file_tstr = curlx_convert_UTF8_to_tchar(ca_file); 276 if(!ca_file_tstr) { 277 char buffer[WINAPI_ERROR_LEN]; 278 failf(data, 279 "schannel: invalid path name for CA file '%s': %s", 280 ca_file, 281 curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); 282 result = CURLE_SSL_CACERT_BADFILE; 283 goto cleanup; 284 } 285 286 /* 287 * Read the CA file completely into memory before parsing it. This 288 * optimizes for the common case where the CA file will be relatively 289 * small ( < 1 MiB ). 290 */ 291 ca_file_handle = CreateFile(ca_file_tstr, 292 GENERIC_READ, 293 FILE_SHARE_READ, 294 NULL, 295 OPEN_EXISTING, 296 FILE_ATTRIBUTE_NORMAL, 297 NULL); 298 if(ca_file_handle == INVALID_HANDLE_VALUE) { 299 char buffer[WINAPI_ERROR_LEN]; 300 failf(data, 301 "schannel: failed to open CA file '%s': %s", 302 ca_file, 303 curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); 304 result = CURLE_SSL_CACERT_BADFILE; 305 goto cleanup; 306 } 307 308 if(!GetFileSizeEx(ca_file_handle, &file_size)) { 309 char buffer[WINAPI_ERROR_LEN]; 310 failf(data, 311 "schannel: failed to determine size of CA file '%s': %s", 312 ca_file, 313 curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); 314 result = CURLE_SSL_CACERT_BADFILE; 315 goto cleanup; 316 } 317 318 if(file_size.QuadPart > MAX_CAFILE_SIZE) { 319 failf(data, 320 "schannel: CA file exceeds max size of %u bytes", 321 MAX_CAFILE_SIZE); 322 result = CURLE_SSL_CACERT_BADFILE; 323 goto cleanup; 324 } 325 326 ca_file_bufsize = (size_t)file_size.QuadPart; 327 ca_file_buffer = (char *)malloc(ca_file_bufsize + 1); 328 if(!ca_file_buffer) { 329 result = CURLE_OUT_OF_MEMORY; 330 goto cleanup; 331 } 332 333 while(total_bytes_read < ca_file_bufsize) { 334 DWORD bytes_to_read = (DWORD)(ca_file_bufsize - total_bytes_read); 335 DWORD bytes_read = 0; 336 337 if(!ReadFile(ca_file_handle, ca_file_buffer + total_bytes_read, 338 bytes_to_read, &bytes_read, NULL)) { 339 char buffer[WINAPI_ERROR_LEN]; 340 failf(data, 341 "schannel: failed to read from CA file '%s': %s", 342 ca_file, 343 curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); 344 result = CURLE_SSL_CACERT_BADFILE; 345 goto cleanup; 346 } 347 if(bytes_read == 0) { 348 /* Premature EOF -- adjust the bufsize to the new value */ 349 ca_file_bufsize = total_bytes_read; 350 } 351 else { 352 total_bytes_read += bytes_read; 353 } 354 } 355 356 /* null-terminate the buffer */ 357 ca_file_buffer[ca_file_bufsize] = '\0'; 358 359 result = add_certs_data_to_store(trust_store, 360 ca_file_buffer, ca_file_bufsize, 361 ca_file, 362 data); 363 364 cleanup: 365 if(ca_file_handle != INVALID_HANDLE_VALUE) { 366 CloseHandle(ca_file_handle); 367 } 368 Curl_safefree(ca_file_buffer); 369 curlx_unicodefree(ca_file_tstr); 370 371 return result; 372 } 373 #endif 374 375 #endif /* HAS_MANUAL_VERIFY_API */ 376 377 #ifndef UNDER_CE 378 /* 379 * Returns the number of characters necessary to populate all the host_names. 380 * If host_names is not NULL, populate it with all the hostnames. Each string 381 * in the host_names is null-terminated and the last string is double 382 * null-terminated. If no DNS names are found, a single null-terminated empty 383 * string is returned. 384 */ 385 static DWORD cert_get_name_string(struct Curl_easy *data, 386 CERT_CONTEXT *cert_context, 387 LPTSTR host_names, 388 DWORD length, 389 PCERT_ALT_NAME_INFO alt_name_info, 390 BOOL Win8_compat) 391 { 392 DWORD actual_length = 0; 393 #if defined(CURL_WINDOWS_UWP) 394 (void)data; 395 (void)cert_context; 396 (void)host_names; 397 (void)length; 398 (void)alt_name_info; 399 (void)Win8_compat; 400 #else 401 BOOL compute_content = FALSE; 402 LPTSTR current_pos = NULL; 403 DWORD i; 404 405 #ifdef CERT_NAME_SEARCH_ALL_NAMES_FLAG 406 /* CERT_NAME_SEARCH_ALL_NAMES_FLAG is available from Windows 8 onwards. */ 407 if(Win8_compat) { 408 /* CertGetNameString will provide the 8-bit character string without 409 * any decoding */ 410 DWORD name_flags = 411 CERT_NAME_DISABLE_IE4_UTF8_FLAG | CERT_NAME_SEARCH_ALL_NAMES_FLAG; 412 actual_length = CertGetNameString(cert_context, 413 CERT_NAME_DNS_TYPE, 414 name_flags, 415 NULL, 416 host_names, 417 length); 418 return actual_length; 419 } 420 #else 421 (void)cert_context; 422 (void)Win8_compat; 423 #endif 424 425 if(!alt_name_info) 426 return 0; 427 428 compute_content = host_names != NULL && length != 0; 429 430 /* Initialize default return values. */ 431 actual_length = 1; 432 if(compute_content) { 433 *host_names = '\0'; 434 } 435 436 current_pos = host_names; 437 438 /* Iterate over the alternate names and populate host_names. */ 439 for(i = 0; i < alt_name_info->cAltEntry; i++) { 440 const CERT_ALT_NAME_ENTRY *entry = &alt_name_info->rgAltEntry[i]; 441 wchar_t *dns_w = NULL; 442 size_t current_length = 0; 443 444 if(entry->dwAltNameChoice != CERT_ALT_NAME_DNS_NAME) { 445 continue; 446 } 447 if(!entry->pwszDNSName) { 448 infof(data, "schannel: Empty DNS name."); 449 continue; 450 } 451 current_length = wcslen(entry->pwszDNSName) + 1; 452 if(!compute_content) { 453 actual_length += (DWORD)current_length; 454 continue; 455 } 456 /* Sanity check to prevent buffer overrun. */ 457 if((actual_length + current_length) > length) { 458 failf(data, "schannel: Not enough memory to list all hostnames."); 459 break; 460 } 461 dns_w = entry->pwszDNSName; 462 /* pwszDNSName is in ia5 string format and hence does not contain any 463 * non-ASCII characters. */ 464 while(*dns_w != '\0') { 465 *current_pos++ = (TCHAR)(*dns_w++); 466 } 467 *current_pos++ = '\0'; 468 actual_length += (DWORD)current_length; 469 } 470 if(compute_content) { 471 /* Last string has double null-terminator. */ 472 *current_pos = '\0'; 473 } 474 #endif 475 return actual_length; 476 } 477 478 /* 479 * Returns TRUE if the hostname is a numeric IPv4/IPv6 Address, 480 * and populates the buffer with IPv4/IPv6 info. 481 */ 482 483 static bool get_num_host_info(struct num_ip_data *ip_blob, 484 LPCSTR hostname) 485 { 486 struct in_addr ia; 487 struct in6_addr ia6; 488 bool result = FALSE; 489 490 int res = curlx_inet_pton(AF_INET, hostname, &ia); 491 if(res) { 492 ip_blob->size = sizeof(struct in_addr); 493 memcpy(&ip_blob->bData.ia, &ia, sizeof(struct in_addr)); 494 result = TRUE; 495 } 496 else { 497 res = curlx_inet_pton(AF_INET6, hostname, &ia6); 498 if(res) { 499 ip_blob->size = sizeof(struct in6_addr); 500 memcpy(&ip_blob->bData.ia6, &ia6, sizeof(struct in6_addr)); 501 result = TRUE; 502 } 503 } 504 return result; 505 } 506 507 static bool get_alt_name_info(struct Curl_easy *data, 508 PCCERT_CONTEXT ctx, 509 PCERT_ALT_NAME_INFO *alt_name_info, 510 LPDWORD alt_name_info_size) 511 { 512 bool result = FALSE; 513 #if defined(CURL_WINDOWS_UWP) 514 (void)data; 515 (void)ctx; 516 (void)alt_name_info; 517 (void)alt_name_info_size; 518 #else 519 PCERT_INFO cert_info = NULL; 520 PCERT_EXTENSION extension = NULL; 521 CRYPT_DECODE_PARA decode_para = { sizeof(CRYPT_DECODE_PARA), NULL, NULL }; 522 523 if(!ctx) { 524 failf(data, "schannel: Null certificate context."); 525 return result; 526 } 527 528 cert_info = ctx->pCertInfo; 529 if(!cert_info) { 530 failf(data, "schannel: Null certificate info."); 531 return result; 532 } 533 534 extension = CertFindExtension(szOID_SUBJECT_ALT_NAME2, 535 cert_info->cExtension, 536 cert_info->rgExtension); 537 if(!extension) { 538 failf(data, "schannel: CertFindExtension() returned no extension."); 539 return result; 540 } 541 542 if(!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 543 szOID_SUBJECT_ALT_NAME2, 544 extension->Value.pbData, 545 extension->Value.cbData, 546 CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, 547 &decode_para, 548 alt_name_info, 549 alt_name_info_size)) { 550 failf(data, 551 "schannel: CryptDecodeObjectEx() returned no alternate name " 552 "information."); 553 return result; 554 } 555 result = TRUE; 556 #endif 557 return result; 558 } 559 #endif /* !UNDER_CE */ 560 561 /* Verify the server's hostname */ 562 CURLcode Curl_verify_host(struct Curl_cfilter *cf, 563 struct Curl_easy *data) 564 { 565 CURLcode result = CURLE_PEER_FAILED_VERIFICATION; 566 struct ssl_connect_data *connssl = cf->ctx; 567 CERT_CONTEXT *pCertContextServer = NULL; 568 #ifdef UNDER_CE 569 TCHAR cert_hostname_buff[256]; 570 DWORD len; 571 572 /* This code does not support certificates with multiple alternative names. 573 * Right now we are only asking for the first preferred alternative name. 574 * Instead we would need to do all via CERT_NAME_SEARCH_ALL_NAMES_FLAG 575 * (If Windows CE supports that?) and run this section in a loop for each. 576 * https://msdn.microsoft.com/en-us/library/windows/desktop/aa376086.aspx 577 * curl: (51) schannel: CertGetNameString() certificate hostname 578 * (.google.com) did not match connection (google.com) 579 */ 580 len = CertGetNameString(pCertContextServer, 581 CERT_NAME_DNS_TYPE, 582 CERT_NAME_DISABLE_IE4_UTF8_FLAG, 583 NULL, 584 cert_hostname_buff, 585 256); 586 if(len > 0) { 587 /* Comparing the cert name and the connection hostname encoded as UTF-8 588 * is acceptable since both values are assumed to use ASCII 589 * (or some equivalent) encoding 590 */ 591 char *cert_hostname = curlx_convert_tchar_to_UTF8(cert_hostname_buff); 592 if(!cert_hostname) { 593 result = CURLE_OUT_OF_MEMORY; 594 } 595 else{ 596 const char *conn_hostname = connssl->peer.hostname; 597 if(Curl_cert_hostcheck(cert_hostname, strlen(cert_hostname), 598 conn_hostname, strlen(conn_hostname))) { 599 infof(data, 600 "schannel: connection hostname (%s) validated " 601 "against certificate name (%s)\n", 602 conn_hostname, cert_hostname); 603 result = CURLE_OK; 604 } 605 else{ 606 failf(data, 607 "schannel: connection hostname (%s) " 608 "does not match certificate name (%s)", 609 conn_hostname, cert_hostname); 610 } 611 Curl_safefree(cert_hostname); 612 } 613 } 614 else { 615 failf(data, 616 "schannel: CertGetNameString did not provide any " 617 "certificate name information"); 618 } 619 #else 620 SECURITY_STATUS sspi_status; 621 TCHAR *cert_hostname_buff = NULL; 622 size_t cert_hostname_buff_index = 0; 623 const char *conn_hostname = connssl->peer.hostname; 624 size_t hostlen = strlen(conn_hostname); 625 DWORD len = 0; 626 DWORD actual_len = 0; 627 PCERT_ALT_NAME_INFO alt_name_info = NULL; 628 DWORD alt_name_info_size = 0; 629 struct num_ip_data ip_blob = { 0 }; 630 bool Win8_compat; 631 struct num_ip_data *p = &ip_blob; 632 DWORD i; 633 634 sspi_status = 635 Curl_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle, 636 SECPKG_ATTR_REMOTE_CERT_CONTEXT, 637 &pCertContextServer); 638 639 if((sspi_status != SEC_E_OK) || !pCertContextServer) { 640 char buffer[WINAPI_ERROR_LEN]; 641 failf(data, "schannel: Failed to read remote certificate context: %s", 642 Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); 643 goto cleanup; 644 } 645 646 Win8_compat = curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT, 647 VERSION_GREATER_THAN_EQUAL); 648 if(get_num_host_info(p, conn_hostname) || !Win8_compat) { 649 if(!get_alt_name_info(data, pCertContextServer, 650 &alt_name_info, &alt_name_info_size)) { 651 goto cleanup; 652 } 653 } 654 655 if(p->size && alt_name_info) { 656 for(i = 0; i < alt_name_info->cAltEntry; ++i) { 657 PCERT_ALT_NAME_ENTRY entry = &alt_name_info->rgAltEntry[i]; 658 if(entry->dwAltNameChoice == CERT_ALT_NAME_IP_ADDRESS) { 659 if(entry->IPAddress.cbData == p->size) { 660 if(!memcmp(entry->IPAddress.pbData, &p->bData, 661 entry->IPAddress.cbData)) { 662 result = CURLE_OK; 663 infof(data, 664 "schannel: connection hostname (%s) matched cert's IP address!", 665 conn_hostname); 666 break; 667 } 668 } 669 } 670 } 671 } 672 else { 673 /* Determine the size of the string needed for the cert hostname */ 674 len = cert_get_name_string(data, pCertContextServer, 675 NULL, 0, alt_name_info, Win8_compat); 676 if(len == 0) { 677 failf(data, 678 "schannel: CertGetNameString() returned no " 679 "certificate name information"); 680 goto cleanup; 681 } 682 683 /* CertGetNameString guarantees that the returned name will not contain 684 * embedded null bytes. This appears to be undocumented behavior. 685 */ 686 cert_hostname_buff = (LPTSTR)malloc(len * sizeof(TCHAR)); 687 if(!cert_hostname_buff) { 688 result = CURLE_OUT_OF_MEMORY; 689 goto cleanup; 690 } 691 actual_len = cert_get_name_string(data, pCertContextServer, 692 (LPTSTR)cert_hostname_buff, len, alt_name_info, Win8_compat); 693 694 /* Sanity check */ 695 if(actual_len != len) { 696 failf(data, 697 "schannel: CertGetNameString() returned certificate " 698 "name information of unexpected size"); 699 goto cleanup; 700 } 701 702 /* cert_hostname_buff contains all DNS names, where each name is 703 * null-terminated and the last DNS name is double null-terminated. Due to 704 * this encoding, use the length of the buffer to iterate over all names. 705 */ 706 while(cert_hostname_buff_index < len && 707 cert_hostname_buff[cert_hostname_buff_index] != TEXT('\0') && 708 result == CURLE_PEER_FAILED_VERIFICATION) { 709 710 char *cert_hostname; 711 712 /* Comparing the cert name and the connection hostname encoded as UTF-8 713 * is acceptable since both values are assumed to use ASCII 714 * (or some equivalent) encoding 715 */ 716 cert_hostname = curlx_convert_tchar_to_UTF8( 717 &cert_hostname_buff[cert_hostname_buff_index]); 718 if(!cert_hostname) { 719 result = CURLE_OUT_OF_MEMORY; 720 } 721 else { 722 if(Curl_cert_hostcheck(cert_hostname, strlen(cert_hostname), 723 conn_hostname, hostlen)) { 724 infof(data, 725 "schannel: connection hostname (%s) validated " 726 "against certificate name (%s)", 727 conn_hostname, cert_hostname); 728 result = CURLE_OK; 729 } 730 else { 731 size_t cert_hostname_len; 732 733 infof(data, 734 "schannel: connection hostname (%s) did not match " 735 "against certificate name (%s)", 736 conn_hostname, cert_hostname); 737 738 cert_hostname_len = 739 _tcslen(&cert_hostname_buff[cert_hostname_buff_index]); 740 741 /* Move on to next cert name */ 742 cert_hostname_buff_index += cert_hostname_len + 1; 743 744 result = CURLE_PEER_FAILED_VERIFICATION; 745 } 746 curlx_unicodefree(cert_hostname); 747 } 748 } 749 750 if(result == CURLE_PEER_FAILED_VERIFICATION) { 751 failf(data, 752 "schannel: CertGetNameString() failed to match " 753 "connection hostname (%s) against server certificate names", 754 conn_hostname); 755 } 756 else if(result != CURLE_OK) 757 failf(data, "schannel: server certificate name verification failed"); 758 } 759 760 cleanup: 761 Curl_safefree(cert_hostname_buff); 762 763 if(pCertContextServer) 764 CertFreeCertificateContext(pCertContextServer); 765 #endif /* !UNDER_CE */ 766 767 return result; 768 } 769 770 #ifdef HAS_MANUAL_VERIFY_API 771 /* Verify the server's certificate and hostname */ 772 CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, 773 struct Curl_easy *data) 774 { 775 struct ssl_connect_data *connssl = cf->ctx; 776 struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); 777 struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); 778 SECURITY_STATUS sspi_status; 779 CURLcode result = CURLE_OK; 780 CERT_CONTEXT *pCertContextServer = NULL; 781 const CERT_CHAIN_CONTEXT *pChainContext = NULL; 782 HCERTCHAINENGINE cert_chain_engine = NULL; 783 #ifndef UNDER_CE 784 HCERTSTORE trust_store = NULL; 785 HCERTSTORE own_trust_store = NULL; 786 #endif /* !UNDER_CE */ 787 788 DEBUGASSERT(BACKEND); 789 790 sspi_status = 791 Curl_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle, 792 SECPKG_ATTR_REMOTE_CERT_CONTEXT, 793 &pCertContextServer); 794 795 if((sspi_status != SEC_E_OK) || !pCertContextServer) { 796 char buffer[WINAPI_ERROR_LEN]; 797 failf(data, "schannel: Failed to read remote certificate context: %s", 798 Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); 799 result = CURLE_PEER_FAILED_VERIFICATION; 800 } 801 802 #ifndef UNDER_CE 803 if(result == CURLE_OK && 804 (conn_config->CAfile || conn_config->ca_info_blob) && 805 BACKEND->use_manual_cred_validation) { 806 /* 807 * Create a chain engine that uses the certificates in the CA file as 808 * trusted certificates. This is only supported on Windows 7+. 809 */ 810 811 if(curlx_verify_windows_version(6, 1, 0, PLATFORM_WINNT, 812 VERSION_LESS_THAN)) { 813 failf(data, "schannel: this version of Windows is too old to support " 814 "certificate verification via CA bundle file."); 815 result = CURLE_SSL_CACERT_BADFILE; 816 } 817 else { 818 /* try cache */ 819 trust_store = Curl_schannel_get_cached_cert_store(cf, data); 820 821 if(trust_store) { 822 infof(data, "schannel: reusing certificate store from cache"); 823 } 824 else { 825 /* Open the certificate store */ 826 trust_store = CertOpenStore(CERT_STORE_PROV_MEMORY, 827 0, 828 (HCRYPTPROV)NULL, 829 CERT_STORE_CREATE_NEW_FLAG, 830 NULL); 831 if(!trust_store) { 832 char buffer[WINAPI_ERROR_LEN]; 833 failf(data, "schannel: failed to create certificate store: %s", 834 curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); 835 result = CURLE_SSL_CACERT_BADFILE; 836 } 837 else { 838 const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; 839 own_trust_store = trust_store; 840 841 if(ca_info_blob) { 842 result = add_certs_data_to_store(trust_store, 843 (const char *)ca_info_blob->data, 844 ca_info_blob->len, 845 "(memory blob)", 846 data); 847 } 848 else { 849 result = add_certs_file_to_store(trust_store, 850 conn_config->CAfile, 851 data); 852 } 853 if(result == CURLE_OK) { 854 if(Curl_schannel_set_cached_cert_store(cf, data, trust_store)) { 855 own_trust_store = NULL; 856 } 857 } 858 } 859 } 860 } 861 862 if(result == CURLE_OK) { 863 struct cert_chain_engine_config_win8 engine_config; 864 BOOL create_engine_result; 865 866 memset(&engine_config, 0, sizeof(engine_config)); 867 engine_config.hExclusiveRoot = trust_store; 868 869 /* Win8/Server2012 allows us to match partial chains */ 870 if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT, 871 VERSION_GREATER_THAN_EQUAL) && 872 !ssl_config->no_partialchain) { 873 engine_config.cbSize = sizeof(engine_config); 874 engine_config.dwExclusiveFlags = CERT_CHAIN_EXCLUSIVE_ENABLE_CA_FLAG; 875 } 876 else 877 engine_config.cbSize = sizeof(struct cert_chain_engine_config_win7); 878 879 /* CertCreateCertificateChainEngine will check the expected size of the 880 * CERT_CHAIN_ENGINE_CONFIG structure and fail if the specified size 881 * does not match the expected size. When this occurs, it indicates that 882 * CAINFO is not supported on the version of Windows in use. 883 */ 884 create_engine_result = 885 CertCreateCertificateChainEngine( 886 (CERT_CHAIN_ENGINE_CONFIG *)&engine_config, &cert_chain_engine); 887 if(!create_engine_result) { 888 char buffer[WINAPI_ERROR_LEN]; 889 failf(data, 890 "schannel: failed to create certificate chain engine: %s", 891 curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); 892 result = CURLE_SSL_CACERT_BADFILE; 893 } 894 } 895 } 896 #endif /* !UNDER_CE */ 897 898 if(result == CURLE_OK) { 899 CERT_CHAIN_PARA ChainPara; 900 901 memset(&ChainPara, 0, sizeof(ChainPara)); 902 ChainPara.cbSize = sizeof(ChainPara); 903 904 if(!CertGetCertificateChain(cert_chain_engine, 905 pCertContextServer, 906 NULL, 907 pCertContextServer->hCertStore, 908 &ChainPara, 909 (ssl_config->no_revoke ? 0 : 910 CERT_CHAIN_REVOCATION_CHECK_CHAIN), 911 NULL, 912 &pChainContext)) { 913 char buffer[WINAPI_ERROR_LEN]; 914 failf(data, "schannel: CertGetCertificateChain failed: %s", 915 curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); 916 pChainContext = NULL; 917 result = CURLE_PEER_FAILED_VERIFICATION; 918 } 919 920 if(result == CURLE_OK) { 921 CERT_SIMPLE_CHAIN *pSimpleChain = pChainContext->rgpChain[0]; 922 DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED); 923 dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus; 924 925 if(data->set.ssl.revoke_best_effort) { 926 /* Ignore errors when root certificates are missing the revocation 927 * list URL, or when the list could not be downloaded because the 928 * server is currently unreachable. */ 929 dwTrustErrorMask &= ~(DWORD)(CERT_TRUST_REVOCATION_STATUS_UNKNOWN | 930 CERT_TRUST_IS_OFFLINE_REVOCATION); 931 } 932 933 if(dwTrustErrorMask) { 934 if(dwTrustErrorMask & CERT_TRUST_IS_REVOKED) 935 failf(data, "schannel: CertGetCertificateChain trust error" 936 " CERT_TRUST_IS_REVOKED"); 937 else if(dwTrustErrorMask & CERT_TRUST_IS_PARTIAL_CHAIN) 938 failf(data, "schannel: CertGetCertificateChain trust error" 939 " CERT_TRUST_IS_PARTIAL_CHAIN"); 940 else if(dwTrustErrorMask & CERT_TRUST_IS_UNTRUSTED_ROOT) 941 failf(data, "schannel: CertGetCertificateChain trust error" 942 " CERT_TRUST_IS_UNTRUSTED_ROOT"); 943 else if(dwTrustErrorMask & CERT_TRUST_IS_NOT_TIME_VALID) 944 failf(data, "schannel: CertGetCertificateChain trust error" 945 " CERT_TRUST_IS_NOT_TIME_VALID"); 946 else if(dwTrustErrorMask & CERT_TRUST_REVOCATION_STATUS_UNKNOWN) 947 failf(data, "schannel: CertGetCertificateChain trust error" 948 " CERT_TRUST_REVOCATION_STATUS_UNKNOWN"); 949 else 950 failf(data, "schannel: CertGetCertificateChain error mask: 0x%08lx", 951 dwTrustErrorMask); 952 result = CURLE_PEER_FAILED_VERIFICATION; 953 } 954 } 955 } 956 957 if(result == CURLE_OK) { 958 if(conn_config->verifyhost) { 959 result = Curl_verify_host(cf, data); 960 } 961 } 962 963 #ifndef UNDER_CE 964 if(cert_chain_engine) { 965 CertFreeCertificateChainEngine(cert_chain_engine); 966 } 967 968 if(own_trust_store) { 969 CertCloseStore(own_trust_store, 0); 970 } 971 #endif /* !UNDER_CE */ 972 973 if(pChainContext) 974 CertFreeCertificateChain(pChainContext); 975 976 if(pCertContextServer) 977 CertFreeCertificateContext(pCertContextServer); 978 979 return result; 980 } 981 982 #endif /* HAS_MANUAL_VERIFY_API */ 983 #endif /* USE_SCHANNEL */