Home | History | Annotate | Download | only in vtls
      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) 2012 - 2016, Marc Hoersken, <info (at) marc-hoersken.de>
      9  * Copyright (C) 2012, Mark Salisbury, <mark.salisbury (at) hp.com>
     10  * Copyright (C) 2012 - 2019, Daniel Stenberg, <daniel (at) 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.haxx.se/docs/copyright.html.
     15  *
     16  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
     17  * copies of the Software, and permit persons to whom the Software is
     18  * furnished to do so, under the terms of the COPYING file.
     19  *
     20  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     21  * KIND, either express or implied.
     22  *
     23  ***************************************************************************/
     24 
     25 /*
     26  * Source file for Schannel-specific certificate verification. This code should
     27  * only be invoked by code in schannel.c.
     28  */
     29 
     30 #include "curl_setup.h"
     31 
     32 #ifdef USE_SCHANNEL
     33 #ifndef USE_WINDOWS_SSPI
     34 #  error "Can't compile SCHANNEL support without SSPI."
     35 #endif
     36 
     37 #define EXPOSE_SCHANNEL_INTERNAL_STRUCTS
     38 #include "schannel.h"
     39 
     40 #ifdef HAS_MANUAL_VERIFY_API
     41 
     42 #include "vtls.h"
     43 #include "sendf.h"
     44 #include "strerror.h"
     45 #include "curl_multibyte.h"
     46 #include "curl_printf.h"
     47 #include "hostcheck.h"
     48 #include "system_win32.h"
     49 
     50 /* The last #include file should be: */
     51 #include "curl_memory.h"
     52 #include "memdebug.h"
     53 
     54 #define BACKEND connssl->backend
     55 
     56 #define MAX_CAFILE_SIZE 1048576 /* 1 MiB */
     57 #define BEGIN_CERT "-----BEGIN CERTIFICATE-----"
     58 #define END_CERT "\n-----END CERTIFICATE-----"
     59 
     60 typedef struct {
     61   DWORD cbSize;
     62   HCERTSTORE hRestrictedRoot;
     63   HCERTSTORE hRestrictedTrust;
     64   HCERTSTORE hRestrictedOther;
     65   DWORD cAdditionalStore;
     66   HCERTSTORE *rghAdditionalStore;
     67   DWORD dwFlags;
     68   DWORD dwUrlRetrievalTimeout;
     69   DWORD MaximumCachedCertificates;
     70   DWORD CycleDetectionModulus;
     71   HCERTSTORE hExclusiveRoot;
     72   HCERTSTORE hExclusiveTrustedPeople;
     73 } CERT_CHAIN_ENGINE_CONFIG_WIN7, *PCERT_CHAIN_ENGINE_CONFIG_WIN7;
     74 
     75 static int is_cr_or_lf(char c)
     76 {
     77   return c == '\r' || c == '\n';
     78 }
     79 
     80 static CURLcode add_certs_to_store(HCERTSTORE trust_store,
     81                                    const char *ca_file,
     82                                    struct connectdata *conn)
     83 {
     84   CURLcode result;
     85   struct Curl_easy *data = conn->data;
     86   HANDLE ca_file_handle = INVALID_HANDLE_VALUE;
     87   LARGE_INTEGER file_size;
     88   char *ca_file_buffer = NULL;
     89   char *current_ca_file_ptr = NULL;
     90   TCHAR *ca_file_tstr = NULL;
     91   size_t ca_file_bufsize = 0;
     92   DWORD total_bytes_read = 0;
     93   bool more_certs = 0;
     94   int num_certs = 0;
     95   size_t END_CERT_LEN;
     96 
     97   ca_file_tstr = Curl_convert_UTF8_to_tchar((char *)ca_file);
     98   if(!ca_file_tstr) {
     99     char buffer[STRERROR_LEN];
    100     failf(data,
    101           "schannel: invalid path name for CA file '%s': %s",
    102           ca_file, Curl_strerror(GetLastError(), buffer, sizeof(buffer)));
    103     result = CURLE_SSL_CACERT_BADFILE;
    104     goto cleanup;
    105   }
    106 
    107   /*
    108    * Read the CA file completely into memory before parsing it. This
    109    * optimizes for the common case where the CA file will be relatively
    110    * small ( < 1 MiB ).
    111    */
    112   ca_file_handle = CreateFile(ca_file_tstr,
    113                               GENERIC_READ,
    114                               0,
    115                               NULL,
    116                               OPEN_EXISTING,
    117                               FILE_ATTRIBUTE_NORMAL,
    118                               NULL);
    119   if(ca_file_handle == INVALID_HANDLE_VALUE) {
    120     char buffer[STRERROR_LEN];
    121     failf(data,
    122           "schannel: failed to open CA file '%s': %s",
    123           ca_file, Curl_strerror(GetLastError(), buffer, sizeof(buffer)));
    124     result = CURLE_SSL_CACERT_BADFILE;
    125     goto cleanup;
    126   }
    127 
    128   if(!GetFileSizeEx(ca_file_handle, &file_size)) {
    129     char buffer[STRERROR_LEN];
    130     failf(data,
    131           "schannel: failed to determine size of CA file '%s': %s",
    132           ca_file, Curl_strerror(GetLastError(), buffer, sizeof(buffer)));
    133     result = CURLE_SSL_CACERT_BADFILE;
    134     goto cleanup;
    135   }
    136 
    137   if(file_size.QuadPart > MAX_CAFILE_SIZE) {
    138     failf(data,
    139           "schannel: CA file exceeds max size of %u bytes",
    140           MAX_CAFILE_SIZE);
    141     result = CURLE_SSL_CACERT_BADFILE;
    142     goto cleanup;
    143   }
    144 
    145   ca_file_bufsize = (size_t)file_size.QuadPart;
    146   ca_file_buffer = (char *)malloc(ca_file_bufsize + 1);
    147   if(!ca_file_buffer) {
    148     result = CURLE_OUT_OF_MEMORY;
    149     goto cleanup;
    150   }
    151 
    152   result = CURLE_OK;
    153   while(total_bytes_read < ca_file_bufsize) {
    154     DWORD bytes_to_read = (DWORD)(ca_file_bufsize - total_bytes_read);
    155     DWORD bytes_read = 0;
    156 
    157     if(!ReadFile(ca_file_handle, ca_file_buffer + total_bytes_read,
    158                  bytes_to_read, &bytes_read, NULL)) {
    159       char buffer[STRERROR_LEN];
    160       failf(data,
    161             "schannel: failed to read from CA file '%s': %s",
    162             ca_file, Curl_strerror(GetLastError(), buffer, sizeof(buffer)));
    163       result = CURLE_SSL_CACERT_BADFILE;
    164       goto cleanup;
    165     }
    166     if(bytes_read == 0) {
    167       /* Premature EOF -- adjust the bufsize to the new value */
    168       ca_file_bufsize = total_bytes_read;
    169     }
    170     else {
    171       total_bytes_read += bytes_read;
    172     }
    173   }
    174 
    175   /* Null terminate the buffer */
    176   ca_file_buffer[ca_file_bufsize] = '\0';
    177 
    178   if(result != CURLE_OK) {
    179     goto cleanup;
    180   }
    181 
    182   END_CERT_LEN = strlen(END_CERT);
    183 
    184   more_certs = 1;
    185   current_ca_file_ptr = ca_file_buffer;
    186   while(more_certs && *current_ca_file_ptr != '\0') {
    187     char *begin_cert_ptr = strstr(current_ca_file_ptr, BEGIN_CERT);
    188     if(!begin_cert_ptr || !is_cr_or_lf(begin_cert_ptr[strlen(BEGIN_CERT)])) {
    189       more_certs = 0;
    190     }
    191     else {
    192       char *end_cert_ptr = strstr(begin_cert_ptr, END_CERT);
    193       if(!end_cert_ptr) {
    194         failf(data,
    195               "schannel: CA file '%s' is not correctly formatted",
    196               ca_file);
    197         result = CURLE_SSL_CACERT_BADFILE;
    198         more_certs = 0;
    199       }
    200       else {
    201         CERT_BLOB cert_blob;
    202         CERT_CONTEXT *cert_context = NULL;
    203         BOOL add_cert_result = FALSE;
    204         DWORD actual_content_type = 0;
    205         DWORD cert_size = (DWORD)
    206           ((end_cert_ptr + END_CERT_LEN) - begin_cert_ptr);
    207 
    208         cert_blob.pbData = (BYTE *)begin_cert_ptr;
    209         cert_blob.cbData = cert_size;
    210         if(!CryptQueryObject(CERT_QUERY_OBJECT_BLOB,
    211                              &cert_blob,
    212                              CERT_QUERY_CONTENT_FLAG_CERT,
    213                              CERT_QUERY_FORMAT_FLAG_ALL,
    214                              0,
    215                              NULL,
    216                              &actual_content_type,
    217                              NULL,
    218                              NULL,
    219                              NULL,
    220                              (const void **)&cert_context)) {
    221           char buffer[STRERROR_LEN];
    222           failf(data,
    223                 "schannel: failed to extract certificate from CA file "
    224                 "'%s': %s",
    225                 ca_file,
    226                 Curl_strerror(GetLastError(), buffer, sizeof(buffer)));
    227           result = CURLE_SSL_CACERT_BADFILE;
    228           more_certs = 0;
    229         }
    230         else {
    231           current_ca_file_ptr = begin_cert_ptr + cert_size;
    232 
    233           /* Sanity check that the cert_context object is the right type */
    234           if(CERT_QUERY_CONTENT_CERT != actual_content_type) {
    235             failf(data,
    236                   "schannel: unexpected content type '%d' when extracting "
    237                   "certificate from CA file '%s'",
    238                   actual_content_type, ca_file);
    239             result = CURLE_SSL_CACERT_BADFILE;
    240             more_certs = 0;
    241           }
    242           else {
    243             add_cert_result =
    244               CertAddCertificateContextToStore(trust_store,
    245                                                cert_context,
    246                                                CERT_STORE_ADD_ALWAYS,
    247                                                NULL);
    248             CertFreeCertificateContext(cert_context);
    249             if(!add_cert_result) {
    250               char buffer[STRERROR_LEN];
    251               failf(data,
    252                     "schannel: failed to add certificate from CA file '%s' "
    253                     "to certificate store: %s",
    254                     ca_file,
    255                     Curl_strerror(GetLastError(), buffer, sizeof(buffer)));
    256               result = CURLE_SSL_CACERT_BADFILE;
    257               more_certs = 0;
    258             }
    259             else {
    260               num_certs++;
    261             }
    262           }
    263         }
    264       }
    265     }
    266   }
    267 
    268   if(result == CURLE_OK) {
    269     if(!num_certs) {
    270       infof(data,
    271             "schannel: did not add any certificates from CA file '%s'\n",
    272             ca_file);
    273     }
    274     else {
    275       infof(data,
    276             "schannel: added %d certificate(s) from CA file '%s'\n",
    277             num_certs, ca_file);
    278     }
    279   }
    280 
    281 cleanup:
    282   if(ca_file_handle != INVALID_HANDLE_VALUE) {
    283     CloseHandle(ca_file_handle);
    284   }
    285   Curl_safefree(ca_file_buffer);
    286   Curl_unicodefree(ca_file_tstr);
    287 
    288   return result;
    289 }
    290 
    291 static CURLcode verify_host(struct Curl_easy *data,
    292                             CERT_CONTEXT *pCertContextServer,
    293                             const char * const conn_hostname)
    294 {
    295   CURLcode result = CURLE_PEER_FAILED_VERIFICATION;
    296   TCHAR *cert_hostname_buff = NULL;
    297   size_t cert_hostname_buff_index = 0;
    298   DWORD len = 0;
    299   DWORD actual_len = 0;
    300 
    301   /* CertGetNameString will provide the 8-bit character string without
    302    * any decoding */
    303   DWORD name_flags = CERT_NAME_DISABLE_IE4_UTF8_FLAG;
    304 
    305 #ifdef CERT_NAME_SEARCH_ALL_NAMES_FLAG
    306   name_flags |= CERT_NAME_SEARCH_ALL_NAMES_FLAG;
    307 #endif
    308 
    309   /* Determine the size of the string needed for the cert hostname */
    310   len = CertGetNameString(pCertContextServer,
    311                           CERT_NAME_DNS_TYPE,
    312                           name_flags,
    313                           NULL,
    314                           NULL,
    315                           0);
    316   if(len == 0) {
    317     failf(data,
    318           "schannel: CertGetNameString() returned no "
    319           "certificate name information");
    320     result = CURLE_PEER_FAILED_VERIFICATION;
    321     goto cleanup;
    322   }
    323 
    324   /* CertGetNameString guarantees that the returned name will not contain
    325    * embedded null bytes. This appears to be undocumented behavior.
    326    */
    327   cert_hostname_buff = (LPTSTR)malloc(len * sizeof(TCHAR));
    328   if(!cert_hostname_buff) {
    329     result = CURLE_OUT_OF_MEMORY;
    330     goto cleanup;
    331   }
    332   actual_len = CertGetNameString(pCertContextServer,
    333                                  CERT_NAME_DNS_TYPE,
    334                                  name_flags,
    335                                  NULL,
    336                                  (LPTSTR) cert_hostname_buff,
    337                                  len);
    338 
    339   /* Sanity check */
    340   if(actual_len != len) {
    341     failf(data,
    342           "schannel: CertGetNameString() returned certificate "
    343           "name information of unexpected size");
    344     result = CURLE_PEER_FAILED_VERIFICATION;
    345     goto cleanup;
    346   }
    347 
    348   /* If HAVE_CERT_NAME_SEARCH_ALL_NAMES is available, the output
    349    * will contain all DNS names, where each name is null-terminated
    350    * and the last DNS name is double null-terminated. Due to this
    351    * encoding, use the length of the buffer to iterate over all names.
    352    */
    353   result = CURLE_PEER_FAILED_VERIFICATION;
    354   while(cert_hostname_buff_index < len &&
    355         cert_hostname_buff[cert_hostname_buff_index] != TEXT('\0') &&
    356         result == CURLE_PEER_FAILED_VERIFICATION) {
    357 
    358     char *cert_hostname;
    359 
    360     /* Comparing the cert name and the connection hostname encoded as UTF-8
    361      * is acceptable since both values are assumed to use ASCII
    362      * (or some equivalent) encoding
    363      */
    364     cert_hostname = Curl_convert_tchar_to_UTF8(
    365         &cert_hostname_buff[cert_hostname_buff_index]);
    366     if(!cert_hostname) {
    367       result = CURLE_OUT_OF_MEMORY;
    368     }
    369     else {
    370       int match_result;
    371 
    372       match_result = Curl_cert_hostcheck(cert_hostname, conn_hostname);
    373       if(match_result == CURL_HOST_MATCH) {
    374         infof(data,
    375               "schannel: connection hostname (%s) validated "
    376               "against certificate name (%s)\n",
    377               conn_hostname, cert_hostname);
    378         result = CURLE_OK;
    379       }
    380       else {
    381         size_t cert_hostname_len;
    382 
    383         infof(data,
    384               "schannel: connection hostname (%s) did not match "
    385               "against certificate name (%s)\n",
    386               conn_hostname, cert_hostname);
    387 
    388         cert_hostname_len = _tcslen(
    389             &cert_hostname_buff[cert_hostname_buff_index]);
    390 
    391         /* Move on to next cert name */
    392         cert_hostname_buff_index += cert_hostname_len + 1;
    393 
    394         result = CURLE_PEER_FAILED_VERIFICATION;
    395       }
    396       Curl_unicodefree(cert_hostname);
    397     }
    398   }
    399 
    400   if(result == CURLE_PEER_FAILED_VERIFICATION) {
    401     failf(data,
    402           "schannel: CertGetNameString() failed to match "
    403           "connection hostname (%s) against server certificate names",
    404           conn_hostname);
    405   }
    406   else if(result != CURLE_OK)
    407     failf(data, "schannel: server certificate name verification failed");
    408 
    409 cleanup:
    410   Curl_unicodefree(cert_hostname_buff);
    411 
    412   return result;
    413 }
    414 
    415 CURLcode Curl_verify_certificate(struct connectdata *conn, int sockindex)
    416 {
    417   SECURITY_STATUS sspi_status;
    418   struct Curl_easy *data = conn->data;
    419   struct ssl_connect_data *connssl = &conn->ssl[sockindex];
    420   CURLcode result = CURLE_OK;
    421   CERT_CONTEXT *pCertContextServer = NULL;
    422   const CERT_CHAIN_CONTEXT *pChainContext = NULL;
    423   HCERTCHAINENGINE cert_chain_engine = NULL;
    424   HCERTSTORE trust_store = NULL;
    425   const char * const conn_hostname = SSL_IS_PROXY() ?
    426     conn->http_proxy.host.name :
    427     conn->host.name;
    428 
    429   sspi_status =
    430     s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle,
    431                                      SECPKG_ATTR_REMOTE_CERT_CONTEXT,
    432                                      &pCertContextServer);
    433 
    434   if((sspi_status != SEC_E_OK) || (pCertContextServer == NULL)) {
    435     char buffer[STRERROR_LEN];
    436     failf(data, "schannel: Failed to read remote certificate context: %s",
    437           Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
    438     result = CURLE_PEER_FAILED_VERIFICATION;
    439   }
    440 
    441   if(result == CURLE_OK && SSL_CONN_CONFIG(CAfile) &&
    442       BACKEND->use_manual_cred_validation) {
    443     /*
    444      * Create a chain engine that uses the certificates in the CA file as
    445      * trusted certificates. This is only supported on Windows 7+.
    446      */
    447 
    448     if(Curl_verify_windows_version(6, 1, PLATFORM_WINNT, VERSION_LESS_THAN)) {
    449       failf(data, "schannel: this version of Windows is too old to support "
    450             "certificate verification via CA bundle file.");
    451       result = CURLE_SSL_CACERT_BADFILE;
    452     }
    453     else {
    454       /* Open the certificate store */
    455       trust_store = CertOpenStore(CERT_STORE_PROV_MEMORY,
    456                                   0,
    457                                   (HCRYPTPROV)NULL,
    458                                   CERT_STORE_CREATE_NEW_FLAG,
    459                                   NULL);
    460       if(!trust_store) {
    461         char buffer[STRERROR_LEN];
    462         failf(data, "schannel: failed to create certificate store: %s",
    463               Curl_strerror(GetLastError(), buffer, sizeof(buffer)));
    464         result = CURLE_SSL_CACERT_BADFILE;
    465       }
    466       else {
    467         result = add_certs_to_store(trust_store, SSL_CONN_CONFIG(CAfile),
    468                                     conn);
    469       }
    470     }
    471 
    472     if(result == CURLE_OK) {
    473       CERT_CHAIN_ENGINE_CONFIG_WIN7 engine_config;
    474       BOOL create_engine_result;
    475 
    476       memset(&engine_config, 0, sizeof(engine_config));
    477       engine_config.cbSize = sizeof(engine_config);
    478       engine_config.hExclusiveRoot = trust_store;
    479 
    480       /* CertCreateCertificateChainEngine will check the expected size of the
    481        * CERT_CHAIN_ENGINE_CONFIG structure and fail if the specified size
    482        * does not match the expected size. When this occurs, it indicates that
    483        * CAINFO is not supported on the version of Windows in use.
    484        */
    485       create_engine_result =
    486         CertCreateCertificateChainEngine(
    487           (CERT_CHAIN_ENGINE_CONFIG *)&engine_config, &cert_chain_engine);
    488       if(!create_engine_result) {
    489         char buffer[STRERROR_LEN];
    490         failf(data,
    491               "schannel: failed to create certificate chain engine: %s",
    492               Curl_strerror(GetLastError(), buffer, sizeof(buffer)));
    493         result = CURLE_SSL_CACERT_BADFILE;
    494       }
    495     }
    496   }
    497 
    498   if(result == CURLE_OK) {
    499     CERT_CHAIN_PARA ChainPara;
    500 
    501     memset(&ChainPara, 0, sizeof(ChainPara));
    502     ChainPara.cbSize = sizeof(ChainPara);
    503 
    504     if(!CertGetCertificateChain(cert_chain_engine,
    505                                 pCertContextServer,
    506                                 NULL,
    507                                 pCertContextServer->hCertStore,
    508                                 &ChainPara,
    509                                 (data->set.ssl.no_revoke ? 0 :
    510                                  CERT_CHAIN_REVOCATION_CHECK_CHAIN),
    511                                 NULL,
    512                                 &pChainContext)) {
    513       char buffer[STRERROR_LEN];
    514       failf(data, "schannel: CertGetCertificateChain failed: %s",
    515             Curl_strerror(GetLastError(), buffer, sizeof(buffer)));
    516       pChainContext = NULL;
    517       result = CURLE_PEER_FAILED_VERIFICATION;
    518     }
    519 
    520     if(result == CURLE_OK) {
    521       CERT_SIMPLE_CHAIN *pSimpleChain = pChainContext->rgpChain[0];
    522       DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED);
    523       dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus;
    524       if(dwTrustErrorMask) {
    525         if(dwTrustErrorMask & CERT_TRUST_IS_REVOKED)
    526           failf(data, "schannel: CertGetCertificateChain trust error"
    527                 " CERT_TRUST_IS_REVOKED");
    528         else if(dwTrustErrorMask & CERT_TRUST_IS_PARTIAL_CHAIN)
    529           failf(data, "schannel: CertGetCertificateChain trust error"
    530                 " CERT_TRUST_IS_PARTIAL_CHAIN");
    531         else if(dwTrustErrorMask & CERT_TRUST_IS_UNTRUSTED_ROOT)
    532           failf(data, "schannel: CertGetCertificateChain trust error"
    533                 " CERT_TRUST_IS_UNTRUSTED_ROOT");
    534         else if(dwTrustErrorMask & CERT_TRUST_IS_NOT_TIME_VALID)
    535           failf(data, "schannel: CertGetCertificateChain trust error"
    536                 " CERT_TRUST_IS_NOT_TIME_VALID");
    537         else if(dwTrustErrorMask & CERT_TRUST_REVOCATION_STATUS_UNKNOWN)
    538           failf(data, "schannel: CertGetCertificateChain trust error"
    539                 " CERT_TRUST_REVOCATION_STATUS_UNKNOWN");
    540         else
    541           failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x",
    542                 dwTrustErrorMask);
    543         result = CURLE_PEER_FAILED_VERIFICATION;
    544       }
    545     }
    546   }
    547 
    548   if(result == CURLE_OK) {
    549     if(SSL_CONN_CONFIG(verifyhost)) {
    550       result = verify_host(conn->data, pCertContextServer, conn_hostname);
    551     }
    552   }
    553 
    554   if(cert_chain_engine) {
    555     CertFreeCertificateChainEngine(cert_chain_engine);
    556   }
    557 
    558   if(trust_store) {
    559     CertCloseStore(trust_store, 0);
    560   }
    561 
    562   if(pChainContext)
    563     CertFreeCertificateChain(pChainContext);
    564 
    565   if(pCertContextServer)
    566     CertFreeCertificateContext(pCertContextServer);
    567 
    568   return result;
    569 }
    570 
    571 #endif /* HAS_MANUAL_VERIFY_API */
    572 #endif /* USE_SCHANNEL */
    573