quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

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 */