diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile.inc | 4 | ||||
-rw-r--r-- | lib/hostcheck.c | 2 | ||||
-rw-r--r-- | lib/url.c | 34 | ||||
-rw-r--r-- | lib/vtls/schannel.c | 233 | ||||
-rw-r--r-- | lib/vtls/schannel.h | 40 | ||||
-rw-r--r-- | lib/vtls/schannel_verify.c | 551 |
6 files changed, 650 insertions, 214 deletions
diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 69f9b403d..61c23411d 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -29,8 +29,8 @@ LIB_VAUTH_HFILES = vauth/vauth.h vauth/digest.h vauth/ntlm.h LIB_VTLS_CFILES = vtls/openssl.c vtls/gtls.c vtls/vtls.c vtls/nss.c \ vtls/polarssl.c vtls/polarssl_threadlock.c vtls/axtls.c \ - vtls/cyassl.c vtls/schannel.c vtls/darwinssl.c vtls/gskit.c \ - vtls/mbedtls.c + vtls/cyassl.c vtls/schannel.c vtls/schannel_verify.c \ + vtls/darwinssl.c vtls/gskit.c vtls/mbedtls.c LIB_VTLS_HFILES = vtls/openssl.h vtls/vtls.h vtls/gtls.h \ vtls/nssg.h vtls/polarssl.h vtls/polarssl_threadlock.h vtls/axtls.h \ diff --git a/lib/hostcheck.c b/lib/hostcheck.c index 37bcc12c1..c9d8112d8 100644 --- a/lib/hostcheck.c +++ b/lib/hostcheck.c @@ -25,7 +25,7 @@ #if defined(USE_OPENSSL) \ || defined(USE_AXTLS) \ || defined(USE_GSKIT) \ - || (defined(USE_SCHANNEL) && defined(_WIN32_WCE)) + || defined(USE_SCHANNEL) /* these backends use functions from this file */ #ifdef HAVE_NETINET_IN_H @@ -488,25 +488,33 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) set->socks5_gssapi_nec = FALSE; #endif - /* This is our preferred CA cert bundle/path since install time */ + /* Set the default CA cert bundle/path detected/specified at build time. + * + * If Schannel (WinSSL) is the selected SSL backend then these locations + * are ignored. We allow setting CA location for schannel only when + * explicitly specified by the user via CURLOPT_CAINFO / --cacert. + */ + if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { #if defined(CURL_CA_BUNDLE) - result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_ORIG], CURL_CA_BUNDLE); - if(result) - return result; + result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_ORIG], CURL_CA_BUNDLE); + if(result) + return result; - result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_PROXY], CURL_CA_BUNDLE); - if(result) - return result; + result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_PROXY], + CURL_CA_BUNDLE); + if(result) + return result; #endif #if defined(CURL_CA_PATH) - result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_ORIG], CURL_CA_PATH); - if(result) - return result; + result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_ORIG], CURL_CA_PATH); + if(result) + return result; - result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_PROXY], CURL_CA_PATH); - if(result) - return result; + result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_PROXY], CURL_CA_PATH); + if(result) + return result; #endif + } set->wildcard_enabled = FALSE; set->chunk_bgn = ZERO_NULL; diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index 4f3f0ce85..5387d8713 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -42,13 +42,12 @@ #ifdef USE_SCHANNEL +#define EXPOSE_SCHANNEL_INTERNAL_STRUCTS + #ifndef USE_WINDOWS_SSPI # error "Can't compile SCHANNEL support without SSPI." #endif -#include <schnlsp.h> -#include <schannel.h> -#include "curl_sspi.h" #include "schannel.h" #include "vtls.h" #include "sendf.h" @@ -61,7 +60,6 @@ #include "x509asn1.h" #include "curl_printf.h" #include "system_win32.h" -#include "hostcheck.h" /* The last #include file should be: */ #include "curl_memory.h" @@ -142,37 +140,6 @@ # define CALG_SHA_256 0x0000800c #endif -/* Structs to store Schannel handles */ -struct curl_schannel_cred { - CredHandle cred_handle; - TimeStamp time_stamp; - int refcount; -}; - -struct curl_schannel_ctxt { - CtxtHandle ctxt_handle; - TimeStamp time_stamp; -}; - -struct ssl_backend_data { - struct curl_schannel_cred *cred; - struct curl_schannel_ctxt *ctxt; - SecPkgContext_StreamSizes stream_sizes; - size_t encdata_length, decdata_length; - size_t encdata_offset, decdata_offset; - unsigned char *encdata_buffer, *decdata_buffer; - /* encdata_is_incomplete: if encdata contains only a partial record that - can't be decrypted without another Curl_read_plain (that is, status is - SEC_E_INCOMPLETE_MESSAGE) then set this true. after Curl_read_plain writes - more bytes into encdata then set this back to false. */ - bool encdata_is_incomplete; - unsigned long req_flags, ret_flags; - CURLcode recv_unrecoverable_err; /* schannel_recv had an unrecoverable err */ - bool recv_sspi_close_notify; /* true if connection closed by close_notify */ - bool recv_connection_closed; /* true if connection closed, regardless how */ - bool use_alpn; /* true if ALPN is used for this connection */ -}; - #define BACKEND connssl->backend static Curl_recv schannel_recv; @@ -181,10 +148,6 @@ static Curl_send schannel_send; static CURLcode pkp_pin_peer_pubkey(struct connectdata *conn, int sockindex, const char *pinnedpubkey); -#ifdef _WIN32_WCE -static CURLcode verify_certificate(struct connectdata *conn, int sockindex); -#endif - static void InitSecBuffer(SecBuffer *buffer, unsigned long BufType, void *BufDataPtr, unsigned long BufByteSize) { @@ -335,6 +298,26 @@ schannel_connect_step1(struct connectdata *conn, int sockindex) BACKEND->use_alpn = false; #endif +#ifdef _WIN32_WCE + /* certificate validation on CE doesn't seem to work right; we'll + * do it following a more manual process. */ + BACKEND->use_manual_cred_validation = true; +#else + if(SSL_CONN_CONFIG(CAfile)) { + if(Curl_verify_windows_version(6, 1, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) { + BACKEND->use_manual_cred_validation = true; + } + else { + failf(data, "schannel: this version of Windows is too old to support " + "certificate verification via CA bundle file."); + return CURLE_SSL_CACERT_BADFILE; + } + } + else + BACKEND->use_manual_cred_validation = false; +#endif + BACKEND->cred = NULL; /* check for an existing re-usable credential handle */ @@ -358,26 +341,23 @@ schannel_connect_step1(struct connectdata *conn, int sockindex) schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; if(conn->ssl_config.verifypeer) { -#ifdef _WIN32_WCE - /* certificate validation on CE doesn't seem to work right; we'll - do it following a more manual process. */ - schannel_cred.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION | - SCH_CRED_IGNORE_NO_REVOCATION_CHECK | - SCH_CRED_IGNORE_REVOCATION_OFFLINE; -#else - schannel_cred.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION; + if(BACKEND->use_manual_cred_validation) + schannel_cred.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION; + else + schannel_cred.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION; + /* TODO s/data->set.ssl.no_revoke/SSL_SET_OPTION(no_revoke)/g */ - if(data->set.ssl.no_revoke) + if(data->set.ssl.no_revoke) { schannel_cred.dwFlags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | SCH_CRED_IGNORE_REVOCATION_OFFLINE; - else - schannel_cred.dwFlags |= SCH_CRED_REVOCATION_CHECK_CHAIN; -#endif - if(data->set.ssl.no_revoke) + infof(data, "schannel: disabled server certificate revocation " "checks\n"); - else + } + else { + schannel_cred.dwFlags |= SCH_CRED_REVOCATION_CHECK_CHAIN; infof(data, "schannel: checking server certificate revocation\n"); + } } else { schannel_cred.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION | @@ -898,12 +878,9 @@ schannel_connect_step2(struct connectdata *conn, int sockindex) } } -#ifdef _WIN32_WCE - /* Windows CE doesn't do any server certificate validation. - We have to do it manually. */ - if(conn->ssl_config.verifypeer) + if(conn->ssl_config.verifypeer && BACKEND->use_manual_cred_validation) { return verify_certificate(conn, sockindex); -#endif + } return CURLE_OK; } @@ -1868,146 +1845,6 @@ static CURLcode pkp_pin_peer_pubkey(struct connectdata *conn, int sockindex, return result; } -#ifdef _WIN32_WCE -static CURLcode verify_certificate(struct connectdata *conn, int sockindex) -{ - SECURITY_STATUS status; - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - CURLcode result = CURLE_OK; - CERT_CONTEXT *pCertContextServer = NULL; - const CERT_CHAIN_CONTEXT *pChainContext = NULL; - const char * const conn_hostname = SSL_IS_PROXY() ? - conn->http_proxy.host.name : - conn->host.name; - - status = s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle, - SECPKG_ATTR_REMOTE_CERT_CONTEXT, - &pCertContextServer); - - if((status != SEC_E_OK) || (pCertContextServer == NULL)) { - failf(data, "schannel: Failed to read remote certificate context: %s", - Curl_sspi_strerror(conn, status)); - result = CURLE_PEER_FAILED_VERIFICATION; - } - - if(result == CURLE_OK) { - CERT_CHAIN_PARA ChainPara; - memset(&ChainPara, 0, sizeof(ChainPara)); - ChainPara.cbSize = sizeof(ChainPara); - - if(!CertGetCertificateChain(NULL, - pCertContextServer, - NULL, - pCertContextServer->hCertStore, - &ChainPara, - (data->set.ssl.no_revoke ? 0 : - CERT_CHAIN_REVOCATION_CHECK_CHAIN), - NULL, - &pChainContext)) { - failf(data, "schannel: CertGetCertificateChain failed: %s", - Curl_sspi_strerror(conn, GetLastError())); - pChainContext = NULL; - result = CURLE_PEER_FAILED_VERIFICATION; - } - - if(result == CURLE_OK) { - CERT_SIMPLE_CHAIN *pSimpleChain = pChainContext->rgpChain[0]; - DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED); - dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus; - if(dwTrustErrorMask) { - if(dwTrustErrorMask & CERT_TRUST_IS_REVOKED) - failf(data, "schannel: CertGetCertificateChain trust error" - " CERT_TRUST_IS_REVOKED"); - else if(dwTrustErrorMask & CERT_TRUST_IS_PARTIAL_CHAIN) - failf(data, "schannel: CertGetCertificateChain trust error" - " CERT_TRUST_IS_PARTIAL_CHAIN"); - else if(dwTrustErrorMask & CERT_TRUST_IS_UNTRUSTED_ROOT) - failf(data, "schannel: CertGetCertificateChain trust error" - " CERT_TRUST_IS_UNTRUSTED_ROOT"); - else if(dwTrustErrorMask & CERT_TRUST_IS_NOT_TIME_VALID) - failf(data, "schannel: CertGetCertificateChain trust error" - " CERT_TRUST_IS_NOT_TIME_VALID"); - else - failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x", - dwTrustErrorMask); - result = CURLE_PEER_FAILED_VERIFICATION; - } - } - } - - if(result == CURLE_OK) { - if(conn->ssl_config.verifyhost) { - TCHAR cert_hostname_buff[256]; - DWORD len; - - /* TODO: Fix this for certificates with multiple alternative names. - Right now we're only asking for the first preferred alternative name. - Instead we'd need to do all via CERT_NAME_SEARCH_ALL_NAMES_FLAG - (if WinCE supports that?) and run this section in a loop for each. - https://msdn.microsoft.com/en-us/library/windows/desktop/aa376086.aspx - curl: (51) schannel: CertGetNameString() certificate hostname - (.google.com) did not match connection (google.com) - */ - len = CertGetNameString(pCertContextServer, - CERT_NAME_DNS_TYPE, - CERT_NAME_DISABLE_IE4_UTF8_FLAG, - NULL, - cert_hostname_buff, - 256); - if(len > 0) { - const char *cert_hostname; - - /* Comparing the cert name and the connection hostname encoded as UTF-8 - * is acceptable since both values are assumed to use ASCII - * (or some equivalent) encoding - */ - cert_hostname = Curl_convert_tchar_to_UTF8(cert_hostname_buff); - if(!cert_hostname) { - result = CURLE_OUT_OF_MEMORY; - } - else{ - int match_result; - - match_result = Curl_cert_hostcheck(cert_hostname, conn->host.name); - if(match_result == CURL_HOST_MATCH) { - infof(data, - "schannel: connection hostname (%s) validated " - "against certificate name (%s)\n", - conn->host.name, - cert_hostname); - result = CURLE_OK; - } - else{ - failf(data, - "schannel: connection hostname (%s) " - "does not match certificate name (%s)", - conn->host.name, - cert_hostname); - result = CURLE_PEER_FAILED_VERIFICATION; - } - Curl_unicodefree(cert_hostname); - } - } - else { - failf(data, - "schannel: CertGetNameString did not provide any " - "certificate name information"); - result = CURLE_PEER_FAILED_VERIFICATION; - } - } - } - - if(pChainContext) - CertFreeCertificateChain(pChainContext); - - if(pCertContextServer) - CertFreeCertificateContext(pCertContextServer); - - return result; -} -#endif /* _WIN32_WCE */ - static void Curl_schannel_checksum(const unsigned char *input, size_t inputlen, unsigned char *checksum, diff --git a/lib/vtls/schannel.h b/lib/vtls/schannel.h index 932103da4..447690027 100644 --- a/lib/vtls/schannel.h +++ b/lib/vtls/schannel.h @@ -26,9 +26,49 @@ #ifdef USE_SCHANNEL +#include <schnlsp.h> +#include <schannel.h> +#include "curl_sspi.h" + #include "urldata.h" extern const struct Curl_ssl Curl_ssl_schannel; +CURLcode verify_certificate(struct connectdata *conn, int sockindex); + +/* structs to expose only in schannel.c and schannel_verify.c */ +#ifdef EXPOSE_SCHANNEL_INTERNAL_STRUCTS +struct curl_schannel_cred { + CredHandle cred_handle; + TimeStamp time_stamp; + int refcount; +}; + +struct curl_schannel_ctxt { + CtxtHandle ctxt_handle; + TimeStamp time_stamp; +}; + +struct ssl_backend_data { + struct curl_schannel_cred *cred; + struct curl_schannel_ctxt *ctxt; + SecPkgContext_StreamSizes stream_sizes; + size_t encdata_length, decdata_length; + size_t encdata_offset, decdata_offset; + unsigned char *encdata_buffer, *decdata_buffer; + /* encdata_is_incomplete: if encdata contains only a partial record that + can't be decrypted without another Curl_read_plain (that is, status is + SEC_E_INCOMPLETE_MESSAGE) then set this true. after Curl_read_plain writes + more bytes into encdata then set this back to false. */ + bool encdata_is_incomplete; + unsigned long req_flags, ret_flags; + CURLcode recv_unrecoverable_err; /* schannel_recv had an unrecoverable err */ + bool recv_sspi_close_notify; /* true if connection closed by close_notify */ + bool recv_connection_closed; /* true if connection closed, regardless how */ + bool use_alpn; /* true if ALPN is used for this connection */ + bool use_manual_cred_validation; /* true if manual cred validation is used */ +}; +#endif /* EXPOSE_SCHANNEL_INTERNAL_STRUCTS */ + #endif /* USE_SCHANNEL */ #endif /* HEADER_CURL_SCHANNEL_H */ diff --git a/lib/vtls/schannel_verify.c b/lib/vtls/schannel_verify.c new file mode 100644 index 000000000..db187dd6b --- /dev/null +++ b/lib/vtls/schannel_verify.c @@ -0,0 +1,551 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2012 - 2016, Marc Hoersken, <info@marc-hoersken.de> + * Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com> + * Copyright (C) 2012 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +/* + * Source file for SChannel-specific certificate verification. This code should + * only be invoked by code in schannel.c. + */ + +#include "curl_setup.h" + +#ifdef USE_SCHANNEL + +#define EXPOSE_SCHANNEL_INTERNAL_STRUCTS + +#ifndef USE_WINDOWS_SSPI +# error "Can't compile SCHANNEL support without SSPI." +#endif + +#include "schannel.h" +#include "vtls.h" +#include "sendf.h" +#include "strerror.h" +#include "curl_multibyte.h" +#include "curl_printf.h" +#include "hostcheck.h" +#include "system_win32.h" + +/* The last #include file should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +#define BACKEND connssl->backend + +#define MAX_CAFILE_SIZE 1048576 /* 1 MiB */ +#define BEGIN_CERT "-----BEGIN CERTIFICATE-----\n" +#define END_CERT "\n-----END CERTIFICATE-----" + +typedef struct { + DWORD cbSize; + HCERTSTORE hRestrictedRoot; + HCERTSTORE hRestrictedTrust; + HCERTSTORE hRestrictedOther; + DWORD cAdditionalStore; + HCERTSTORE *rghAdditionalStore; + DWORD dwFlags; + DWORD dwUrlRetrievalTimeout; + DWORD MaximumCachedCertificates; + DWORD CycleDetectionModulus; + HCERTSTORE hExclusiveRoot; + HCERTSTORE hExclusiveTrustedPeople; +} CERT_CHAIN_ENGINE_CONFIG_WIN7, *PCERT_CHAIN_ENGINE_CONFIG_WIN7; + + +static CURLcode add_certs_to_store(HCERTSTORE trust_store, + const char *ca_file, + struct connectdata *conn) +{ + CURLcode result; + struct Curl_easy *data = conn->data; + HANDLE ca_file_handle = INVALID_HANDLE_VALUE; + LARGE_INTEGER file_size; + char *ca_file_buffer = NULL; + char *current_ca_file_ptr = NULL; + const TCHAR *ca_file_tstr = NULL; + size_t ca_file_bufsize = 0; + DWORD total_bytes_read = 0; + bool more_certs = 0; + int num_certs = 0; + size_t END_CERT_LEN; + + ca_file_tstr = Curl_convert_UTF8_to_tchar(ca_file); + if(!ca_file_tstr) { + failf(data, + "schannel: invalid path name for CA file '%s': %s", + ca_file, Curl_strerror(conn, GetLastError())); + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + + /* + * Read the CA file completely into memory before parsing it. This + * optimizes for the common case where the CA file will be relatively + * small ( < 1 MiB ). + */ + ca_file_handle = CreateFile(ca_file_tstr, + GENERIC_READ, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + if(ca_file_handle == INVALID_HANDLE_VALUE) { + failf(data, + "schannel: failed to open CA file '%s': %s", + ca_file, Curl_strerror(conn, GetLastError())); + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + + if(!GetFileSizeEx(ca_file_handle, &file_size)) { + failf(data, + "schannel: failed to determine size of CA file '%s': %s", + ca_file, Curl_strerror(conn, GetLastError())); + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + + if(file_size.QuadPart > MAX_CAFILE_SIZE) { + failf(data, + "schannel: CA file exceeds max size of %u bytes", + MAX_CAFILE_SIZE); + result = CURLE_OUT_OF_MEMORY; + goto cleanup; + } + + ca_file_bufsize = (size_t)file_size.QuadPart; + ca_file_buffer = (char *)malloc(ca_file_bufsize + 1); + if(!ca_file_buffer) { + result = CURLE_OUT_OF_MEMORY; + goto cleanup; + } + + result = CURLE_OK; + while(total_bytes_read < ca_file_bufsize) { + DWORD bytes_to_read = (DWORD)(ca_file_bufsize - total_bytes_read); + DWORD bytes_read = 0; + + if(!ReadFile(ca_file_handle, ca_file_buffer + total_bytes_read, + bytes_to_read, &bytes_read, NULL)) { + + failf(data, + "schannel: failed to read from CA file '%s': %s", + ca_file, Curl_strerror(conn, GetLastError())); + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + if(bytes_read == 0) { + /* Premature EOF -- adjust the bufsize to the new value */ + ca_file_bufsize = total_bytes_read; + } + else { + total_bytes_read += bytes_read; + } + } + + /* Null terminate the buffer */ + ca_file_buffer[ca_file_bufsize] = '\0'; + + if(result != CURLE_OK) { + goto cleanup; + } + + END_CERT_LEN = strlen(END_CERT); + + more_certs = 1; + current_ca_file_ptr = ca_file_buffer; + while(more_certs && *current_ca_file_ptr != '\0') { + char *begin_cert_ptr = strstr(current_ca_file_ptr, BEGIN_CERT); + if(!begin_cert_ptr) { + more_certs = 0; + } + else { + char *end_cert_ptr = strstr(begin_cert_ptr, END_CERT); + if(!end_cert_ptr) { + failf(data, + "schannel: CA file '%s' is not correctly formatted", + ca_file); + result = CURLE_SSL_CACERT_BADFILE; + more_certs = 0; + } + else { + CERT_BLOB cert_blob; + CERT_CONTEXT *cert_context = NULL; + BOOL add_cert_result = FALSE; + DWORD actual_content_type = 0; + DWORD cert_size = (DWORD) + ((end_cert_ptr + END_CERT_LEN) - begin_cert_ptr); + + cert_blob.pbData = (BYTE *)begin_cert_ptr; + cert_blob.cbData = cert_size; + if(!CryptQueryObject(CERT_QUERY_OBJECT_BLOB, + &cert_blob, + CERT_QUERY_CONTENT_FLAG_CERT, + CERT_QUERY_FORMAT_FLAG_ALL, + 0, + NULL, + &actual_content_type, + NULL, + NULL, + NULL, + &cert_context)) { + + failf(data, + "schannel: failed to extract certificate from CA file " + "'%s': %s", + ca_file, Curl_strerror(conn, GetLastError())); + result = CURLE_SSL_CACERT_BADFILE; + more_certs = 0; + } + else { + current_ca_file_ptr = begin_cert_ptr + cert_size; + + /* Sanity check that the cert_context object is the right type */ + if(CERT_QUERY_CONTENT_CERT != actual_content_type) { + failf(data, + "schannel: unexpected content type '%d' when extracting " + "certificate from CA file '%s'", + actual_content_type, ca_file); + result = CURLE_SSL_CACERT_BADFILE; + more_certs = 0; + } + else { + add_cert_result = + CertAddCertificateContextToStore(trust_store, + cert_context, + CERT_STORE_ADD_ALWAYS, + NULL); + CertFreeCertificateContext(cert_context); + if(!add_cert_result) { + failf(data, + "schannel: failed to add certificate from CA file '%s'" + "to certificate store: %s", + ca_file, Curl_strerror(conn, GetLastError())); + result = CURLE_SSL_CACERT_BADFILE; + more_certs = 0; + } + else { + num_certs++; + } + } + } + } + } + } + + if(result == CURLE_OK) { + if(!num_certs) { + infof(data, + "schannel: did not add any certificates from CA file '%s'\n", + ca_file); + } + else { + infof(data, + "schannel: added %d certificate(s) from CA file '%s'\n", + num_certs, ca_file); + } + } + +cleanup: + if(ca_file_handle != INVALID_HANDLE_VALUE) { + CloseHandle(ca_file_handle); + } + Curl_safefree(ca_file_buffer); + Curl_unicodefree(ca_file_tstr); + + return result; +} + +static CURLcode verify_host(struct Curl_easy *data, + CERT_CONTEXT *pCertContextServer, + const char * const conn_hostname) +{ + CURLcode result = CURLE_PEER_FAILED_VERIFICATION; + TCHAR *cert_hostname_buff = NULL; + size_t cert_hostname_buff_index = 0; + DWORD len = 0; + DWORD actual_len = 0; + + /* CertGetNameString will provide the 8-bit character string without + * any decoding */ + DWORD name_flags = CERT_NAME_DISABLE_IE4_UTF8_FLAG; + +#ifdef CERT_NAME_SEARCH_ALL_NAMES_FLAG + name_flags |= CERT_NAME_SEARCH_ALL_NAMES_FLAG; +#endif + + /* Determine the size of the string needed for the cert hostname */ + len = CertGetNameString(pCertContextServer, + CERT_NAME_DNS_TYPE, + name_flags, + NULL, + NULL, + 0); + if(len == 0) { + failf(data, + "schannel: CertGetNameString() returned no " + "certificate name information"); + result = CURLE_PEER_FAILED_VERIFICATION; + goto cleanup; + } + + /* CertGetNameString guarantees that the returned name will not contain + * embedded null bytes. This appears to be undocumented behavior. + */ + cert_hostname_buff = (LPTSTR)malloc(len * sizeof(TCHAR)); + actual_len = CertGetNameString(pCertContextServer, + CERT_NAME_DNS_TYPE, + name_flags, + NULL, + (LPTSTR) cert_hostname_buff, + len); + + /* Sanity check */ + if(actual_len != len) { + failf(data, + "schannel: CertGetNameString() returned certificate " + "name information of unexpected size"); + result = CURLE_PEER_FAILED_VERIFICATION; + goto cleanup; + } + + /* If HAVE_CERT_NAME_SEARCH_ALL_NAMES is available, the output + * will contain all DNS names, where each name is null-terminated + * and the last DNS name is double null-terminated. Due to this + * encoding, use the length of the buffer to iterate over all names. + */ + result = CURLE_PEER_FAILED_VERIFICATION; + while(cert_hostname_buff_index < len && + cert_hostname_buff[cert_hostname_buff_index] != TEXT('\0') && + result == CURLE_PEER_FAILED_VERIFICATION) { + + char *cert_hostname; + + /* Comparing the cert name and the connection hostname encoded as UTF-8 + * is acceptable since both values are assumed to use ASCII + * (or some equivalent) encoding + */ + cert_hostname = Curl_convert_tchar_to_UTF8( + &cert_hostname_buff[cert_hostname_buff_index]); + if(!cert_hostname) { + result = CURLE_OUT_OF_MEMORY; + } + else { + int match_result; + + match_result = Curl_cert_hostcheck(cert_hostname, conn_hostname); + if(match_result == CURL_HOST_MATCH) { + infof(data, + "schannel: connection hostname (%s) validated " + "against certificate name (%s)\n", + conn_hostname, cert_hostname); + result = CURLE_OK; + } + else { + size_t cert_hostname_len; + + infof(data, + "schannel: connection hostname (%s) did not match " + "against certificate name (%s)\n", + conn_hostname, cert_hostname); + + cert_hostname_len = _tcslen( + &cert_hostname_buff[cert_hostname_buff_index]); + + /* Move on to next cert name */ + cert_hostname_buff_index += cert_hostname_len + 1; + + result = CURLE_PEER_FAILED_VERIFICATION; + } + Curl_unicodefree(cert_hostname); + } + } + + if(result == CURLE_PEER_FAILED_VERIFICATION) { + failf(data, + "schannel: CertGetNameString() failed to match " + "connection hostname (%s) against server certificate names", + conn_hostname); + } + else if(result != CURLE_OK) + failf(data, "schannel: server certificate name verification failed"); + +cleanup: + Curl_unicodefree(cert_hostname_buff); + + return result; +} + +CURLcode verify_certificate(struct connectdata *conn, int sockindex) +{ + SECURITY_STATUS status; + struct Curl_easy *data = conn->data; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + CURLcode result = CURLE_OK; + CERT_CONTEXT *pCertContextServer = NULL; + const CERT_CHAIN_CONTEXT *pChainContext = NULL; + HCERTCHAINENGINE cert_chain_engine = NULL; + HCERTSTORE trust_store = NULL; + const char * const conn_hostname = SSL_IS_PROXY() ? + conn->http_proxy.host.name : + conn->host.name; + + status = s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle, + SECPKG_ATTR_REMOTE_CERT_CONTEXT, + &pCertContextServer); + + if((status != SEC_E_OK) || (pCertContextServer == NULL)) { + failf(data, "schannel: Failed to read remote certificate context: %s", + Curl_sspi_strerror(conn, status)); + result = CURLE_PEER_FAILED_VERIFICATION; + } + + if(result == CURLE_OK && SSL_CONN_CONFIG(CAfile) && + BACKEND->use_manual_cred_validation) { + /* + * Create a chain engine that uses the certificates in the CA file as + * trusted certificates. This is only supported on Windows 7+. + */ + + if(Curl_verify_windows_version(6, 1, PLATFORM_WINNT, VERSION_LESS_THAN)) { + failf(data, "schannel: this version of Windows is too old to support " + "certificate verification via CA bundle file."); + result = CURLE_SSL_CACERT_BADFILE; + } + else { + /* Open the certificate store */ + trust_store = CertOpenStore(CERT_STORE_PROV_MEMORY, + 0, + (HCRYPTPROV)NULL, + CERT_STORE_CREATE_NEW_FLAG, + NULL); + if(!trust_store) { + failf(data, "schannel: failed to create certificate store: %s", + Curl_strerror(conn, GetLastError())); + result = CURLE_SSL_CACERT_BADFILE; + } + else { + result = add_certs_to_store(trust_store, SSL_CONN_CONFIG(CAfile), + conn); + } + } + + if(result == CURLE_OK) { + CERT_CHAIN_ENGINE_CONFIG_WIN7 engine_config; + BOOL create_engine_result; + + memset(&engine_config, 0, sizeof(engine_config)); + engine_config.cbSize = sizeof(engine_config); + engine_config.hExclusiveRoot = trust_store; + + /* CertCreateCertificateChainEngine will check the expected size of the + * CERT_CHAIN_ENGINE_CONFIG structure and fail if the specified size + * does not match the expected size. When this occurs, it indicates that + * CAINFO is not supported on the version of Windows in use. + */ + create_engine_result = + CertCreateCertificateChainEngine( + (CERT_CHAIN_ENGINE_CONFIG *)&engine_config, &cert_chain_engine); + if(!create_engine_result) { + failf(data, + "schannel: failed to create certificate chain engine: %s", + Curl_strerror(conn, GetLastError())); + result = CURLE_SSL_CACERT_BADFILE; + } + } + } + + if(result == CURLE_OK) { + CERT_CHAIN_PARA ChainPara; + + memset(&ChainPara, 0, sizeof(ChainPara)); + ChainPara.cbSize = sizeof(ChainPara); + + if(!CertGetCertificateChain(cert_chain_engine, + pCertContextServer, + NULL, + pCertContextServer->hCertStore, + &ChainPara, + (data->set.ssl.no_revoke ? 0 : + CERT_CHAIN_REVOCATION_CHECK_CHAIN), + NULL, + &pChainContext)) { + failf(data, "schannel: CertGetCertificateChain failed: %s", + Curl_sspi_strerror(conn, GetLastError())); + pChainContext = NULL; + result = CURLE_PEER_FAILED_VERIFICATION; + } + + if(result == CURLE_OK) { + CERT_SIMPLE_CHAIN *pSimpleChain = pChainContext->rgpChain[0]; + DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED); + dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus; + if(dwTrustErrorMask) { + if(dwTrustErrorMask & CERT_TRUST_IS_REVOKED) + failf(data, "schannel: CertGetCertificateChain trust error" + " CERT_TRUST_IS_REVOKED"); + else if(dwTrustErrorMask & CERT_TRUST_IS_PARTIAL_CHAIN) + failf(data, "schannel: CertGetCertificateChain trust error" + " CERT_TRUST_IS_PARTIAL_CHAIN"); + else if(dwTrustErrorMask & CERT_TRUST_IS_UNTRUSTED_ROOT) + failf(data, "schannel: CertGetCertificateChain trust error" + " CERT_TRUST_IS_UNTRUSTED_ROOT"); + else if(dwTrustErrorMask & CERT_TRUST_IS_NOT_TIME_VALID) + failf(data, "schannel: CertGetCertificateChain trust error" + " CERT_TRUST_IS_NOT_TIME_VALID"); + else if(dwTrustErrorMask & CERT_TRUST_REVOCATION_STATUS_UNKNOWN) + failf(data, "schannel: CertGetCertificateChain trust error" + " CERT_TRUST_REVOCATION_STATUS_UNKNOWN"); + else + failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x", + dwTrustErrorMask); + result = CURLE_PEER_FAILED_VERIFICATION; + } + } + } + + if(result == CURLE_OK) { + if(SSL_CONN_CONFIG(verifyhost)) { + result = verify_host(conn->data, pCertContextServer, conn_hostname); + } + } + + if(cert_chain_engine) { + CertFreeCertificateChainEngine(cert_chain_engine); + } + + if(trust_store) { + CertCloseStore(trust_store, 0); + } + + if(pChainContext) + CertFreeCertificateChain(pChainContext); + + if(pCertContextServer) + CertFreeCertificateContext(pCertContextServer); + + return result; +} + +#endif /* USE_SCHANNEL */ |