diff --git a/Release/src/http/client/http_client_asio.cpp b/Release/src/http/client/http_client_asio.cpp index ec41762b3f..d3002dfc09 100644 --- a/Release/src/http/client/http_client_asio.cpp +++ b/Release/src/http/client/http_client_asio.cpp @@ -39,7 +39,7 @@ #include "cpprest/base_uri.h" #include "cpprest/details/http_helpers.h" -#include "cpprest/details/x509_cert_utilities.h" +#include "../common/x509_cert_utilities.h" #include "http_client_impl.h" #include "pplx/threadpool.h" #include @@ -468,7 +468,7 @@ class asio_context final : public request_context, public std::enable_shared_fro , m_needChunked(false) , m_timer(client->client_config().timeout()) , m_connection(connection) -#if defined(__APPLE__) || (defined(ANDROID) || defined(__ANDROID__)) +#ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE , m_openssl_failed(false) #endif { @@ -1032,11 +1032,11 @@ class asio_context final : public request_context, public std::enable_shared_fro // finally by the root CA self signed certificate. const auto& host = utility::conversions::to_utf8string(m_http_client->base_uri().host()); -#if defined(__APPLE__) || (defined(ANDROID) || defined(__ANDROID__)) - // On OS X, iOS, and Android, OpenSSL doesn't have access to where the OS - // stores keychains. If OpenSSL fails we will doing verification at the - // end using the whole certificate chain so wait until the 'leaf' cert. - // For now return true so OpenSSL continues down the certificate chain. +#ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE + // Attempt to use platform certificate validation when it is available: + // If OpenSSL fails we will doing verification at the end using the whole certificate chain, + // so wait until the 'leaf' cert. For now return true so OpenSSL continues down the certificate + // chain. if (!preverified) { m_openssl_failed = true; @@ -1757,7 +1757,7 @@ class asio_context final : public request_context, public std::enable_shared_fro boost::asio::streambuf m_body_buf; std::shared_ptr m_connection; -#if defined(__APPLE__) || (defined(ANDROID) || defined(__ANDROID__)) +#ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE bool m_openssl_failed; #endif }; diff --git a/Release/src/http/client/http_client_winhttp.cpp b/Release/src/http/client/http_client_winhttp.cpp index ce037a4954..83a60d6ce5 100644 --- a/Release/src/http/client/http_client_winhttp.cpp +++ b/Release/src/http/client/http_client_winhttp.cpp @@ -15,14 +15,55 @@ #include "stdafx.h" #include +#include #include "cpprest/http_headers.h" +#include "../common/x509_cert_utilities.h" #include "http_client_impl.h" #ifndef CPPREST_TARGET_XP #include #endif +namespace +{ +struct security_failure_message +{ + std::uint32_t flag; + const char * text; +}; + +constexpr security_failure_message g_security_failure_messages[] = { + { WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED, + "WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED failed to check revocation status."}, + { WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT, + "WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT SSL certificate is invalid."}, + { WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED, + "WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED SSL certificate was revoked."}, + { WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA, + "WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA SSL invalid CA."}, + { WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID, + "WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID SSL common name does not match."}, + { WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID, + "WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID SLL certificate is expired."}, + { WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR, + "WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR internal error."}, +}; + +std::string generate_security_failure_message(std::uint32_t flags) +{ + std::string result("SSL Error:"); + for (const auto& message : g_security_failure_messages) { + if (flags & message.flag) { + result.push_back(' '); + result.append(message.text); + } + } + + return result; +} + +} // unnamed namespace namespace web { namespace http @@ -255,6 +296,124 @@ class winhttp_request_context final : public request_context } } + void install_custom_cn_check(const utility::string_t& customHost) + { + m_customCnCheck = customHost; + utility::details::inplace_tolower(m_customCnCheck); + } + + void on_send_request_validate_cn() + { + if (m_customCnCheck.empty()) + { + // no custom validation selected; either we've delegated that to winhttp or + // certificate checking is completely disabled + return; + } + + winhttp_cert_context certContext; + DWORD bufferSize = sizeof(certContext.raw); + if (!WinHttpQueryOption( + m_request_handle, + WINHTTP_OPTION_SERVER_CERT_CONTEXT, + &certContext.raw, + &bufferSize)) + { + auto errorCode = GetLastError(); + if (errorCode == HRESULT_CODE(WININET_E_INCORRECT_HANDLE_STATE)) + { + // typically happens when given a custom host with an initially HTTP connection + return; + } + + report_error(errorCode, build_error_msg(errorCode, + "WinHttpQueryOption WINHTTP_OPTION_SERVER_CERT_CONTEXT")); + cleanup(); + return; + } + + const auto encodedFirst = certContext.raw->pbCertEncoded; + const auto encodedLast = encodedFirst + certContext.raw->cbCertEncoded; + if (certContext.raw->cbCertEncoded == m_cachedEncodedCert.size() + && std::equal(encodedFirst, encodedLast, m_cachedEncodedCert.begin())) + { + // already validated OK + return; + } + + char oidPkixKpServerAuth[] = szOID_PKIX_KP_SERVER_AUTH; + char oidServerGatedCrypto[] = szOID_SERVER_GATED_CRYPTO; + char oidSgcNetscape[] = szOID_SGC_NETSCAPE; + char *chainUses[] = { + oidPkixKpServerAuth, + oidServerGatedCrypto, + oidSgcNetscape, + }; + + winhttp_cert_chain_context chainContext; + CERT_CHAIN_PARA chainPara = {sizeof(chainPara)}; + chainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; + chainPara.RequestedUsage.Usage.cUsageIdentifier = + sizeof(chainUses) / sizeof(char *); + chainPara.RequestedUsage.Usage.rgpszUsageIdentifier = chainUses; + + // note that the following chain only checks the end certificate; we + // assume WinHTTP already validated everything but the common name. + if (!CertGetCertificateChain( + NULL, + certContext.raw, + nullptr, + certContext.raw->hCertStore, + &chainPara, + CERT_CHAIN_CACHE_END_CERT, + NULL, + &chainContext.raw)) + { + auto errorCode = GetLastError(); + report_error(errorCode, build_error_msg(errorCode, + "CertGetCertificateChain")); + cleanup(); + return; + } + + HTTPSPolicyCallbackData policyData = { + {sizeof(policyData)}, + AUTHTYPE_SERVER, + // we assume WinHTTP already checked these: + 0x00000080 /* SECURITY_FLAG_IGNORE_REVOCATION */ + | 0x00000100 /* SECURITY_FLAG_IGNORE_UNKNOWN_CA */ + | 0x00000200 /* SECURITY_FLAG_IGNORE_WRONG_USAGE */ + | 0x00002000 /* SECURITY_FLAG_IGNORE_CERT_DATE_INVALID */, + &m_customCnCheck[0], + }; + CERT_CHAIN_POLICY_PARA policyPara = {sizeof(policyPara)}; + policyPara.pvExtraPolicyPara = &policyData; + + CERT_CHAIN_POLICY_STATUS policyStatus = {sizeof(policyStatus)}; + if (!CertVerifyCertificateChainPolicy( + CERT_CHAIN_POLICY_SSL, + chainContext.raw, + &policyPara, + &policyStatus)) + { + auto errorCode = GetLastError(); + report_error(errorCode, build_error_msg(errorCode, + "CertVerifyCertificateChainPolicy")); + cleanup(); + return; + } + + if (policyStatus.dwError) + { + report_error(policyStatus.dwError, + build_error_msg(policyStatus.dwError, "Incorrect common name")); + cleanup(); + return; + } + + m_cachedEncodedCert.assign(encodedFirst, encodedLast); + } + protected: virtual void finish() override @@ -267,6 +426,9 @@ class winhttp_request_context final : public request_context private: + utility::string_t m_customCnCheck; + std::vector m_cachedEncodedCert; + // Can only create on the heap using factory function. winhttp_request_context(const std::shared_ptr<_http_client_communicator> &client, const http_request &request) : request_context(client, request), @@ -393,14 +555,6 @@ class winhttp_client final : public _http_client_communicator protected: - unsigned long report_failure(const utility::string_t& errorMessage) - { - // Should we log? - CASABLANCA_UNREFERENCED_PARAMETER(errorMessage); - - return GetLastError(); - } - // Open session and connection with the server. unsigned long open() { @@ -519,7 +673,7 @@ class winhttp_client final : public _http_client_communicator WINHTTP_FLAG_ASYNC); if(!m_hSession) { - return report_failure(_XPLATSTR("Error opening session")); + return GetLastError(); } // Set timeouts. @@ -531,7 +685,7 @@ class winhttp_client final : public _http_client_communicator milliseconds, milliseconds)) { - return report_failure(_XPLATSTR("Error setting timeouts")); + return GetLastError(); } if(config.guarantee_order()) @@ -540,7 +694,7 @@ class winhttp_client final : public _http_client_communicator DWORD maxConnections = 1; if(!WinHttpSetOption(m_hSession, WINHTTP_OPTION_MAX_CONNS_PER_SERVER, &maxConnections, sizeof(maxConnections))) { - return report_failure(_XPLATSTR("Error setting options")); + return GetLastError(); } } @@ -552,7 +706,7 @@ class winhttp_client final : public _http_client_communicator win32_result = ::WinHttpSetOption(m_hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &secure_protocols, sizeof(secure_protocols)); if(FALSE == win32_result) { - return report_failure(_XPLATSTR("Error setting session options")); + return GetLastError(); } #endif @@ -562,10 +716,13 @@ class winhttp_client final : public _http_client_communicator if(WINHTTP_INVALID_STATUS_CALLBACK == WinHttpSetStatusCallback( m_hSession, &winhttp_client::completion_callback, - WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS | WINHTTP_CALLBACK_FLAG_HANDLES | WINHTTP_CALLBACK_FLAG_SECURE_FAILURE, + WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS + | WINHTTP_CALLBACK_FLAG_HANDLES + | WINHTTP_CALLBACK_FLAG_SECURE_FAILURE + | WINHTTP_CALLBACK_FLAG_SEND_REQUEST, 0)) { - return report_failure(_XPLATSTR("Error registering callback")); + return GetLastError(); } // Open connection. @@ -578,7 +735,7 @@ class winhttp_client final : public _http_client_communicator if(m_hConnection == nullptr) { - return report_failure(_XPLATSTR("Error opening connection")); + return GetLastError(); } m_opened = true; @@ -600,6 +757,7 @@ class winhttp_client final : public _http_client_communicator } http_request &msg = request->m_request; + http_headers &headers = msg.headers(); std::shared_ptr winhttp_context = std::static_pointer_cast(request); std::weak_ptr weak_winhttp_context = winhttp_context; @@ -659,18 +817,6 @@ class winhttp_client final : public _http_client_communicator return; } - // Enable the certificate revocation check - if (m_secure && client_config().validate_certificates()) - { - DWORD dwEnableSSLRevocOpt = WINHTTP_ENABLE_SSL_REVOCATION; - if (!WinHttpSetOption(winhttp_context->m_request_handle, WINHTTP_OPTION_ENABLE_FEATURE, &dwEnableSSLRevocOpt, sizeof(dwEnableSSLRevocOpt))) - { - auto errorCode = GetLastError(); - request->report_error(errorCode, build_error_msg(errorCode, "Error enabling SSL revocation check")); - return; - } - } - if(proxy_info_required) { auto result = WinHttpSetOption( @@ -707,24 +853,49 @@ class winhttp_client final : public _http_client_communicator } // Check to turn off server certificate verification. - if(!client_config().validate_certificates()) + DWORD ignoredCertificateValidationSteps = 0; + if (client_config().validate_certificates()) + { + // if we are validating certificates, also turn on revocation checking + DWORD dwEnableSSLRevocOpt = WINHTTP_ENABLE_SSL_REVOCATION; + if (!WinHttpSetOption(winhttp_context->m_request_handle, WINHTTP_OPTION_ENABLE_FEATURE, + &dwEnableSSLRevocOpt, sizeof(dwEnableSSLRevocOpt))) + { + auto errorCode = GetLastError(); + request->report_error(errorCode, build_error_msg(errorCode, "Error enabling SSL revocation check")); + return; + } + + // check if the user has overridden the desired Common Name with the host header + const auto hostHeader = headers.find(_XPLATSTR("Host")); + if (hostHeader != headers.end()) + { + const auto& requestHost = hostHeader->second; + if (!utility::details::str_iequal(requestHost, m_uri.host())) + { + winhttp_context->install_custom_cn_check(requestHost); + ignoredCertificateValidationSteps = SECURITY_FLAG_IGNORE_CERT_CN_INVALID; + } + } + } + else { - DWORD data = SECURITY_FLAG_IGNORE_UNKNOWN_CA + ignoredCertificateValidationSteps = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE; + } - auto result = WinHttpSetOption( + if(ignoredCertificateValidationSteps + && !WinHttpSetOption( winhttp_context->m_request_handle, WINHTTP_OPTION_SECURITY_FLAGS, - &data, - sizeof(data)); - if(!result) - { - auto errorCode = GetLastError(); - request->report_error(errorCode, build_error_msg(errorCode, "Setting ignore server certificate verification")); - return; - } + &ignoredCertificateValidationSteps, + sizeof(ignoredCertificateValidationSteps))) + { + auto errorCode = GetLastError(); + request->report_error(errorCode, build_error_msg(errorCode, "Setting ignore server certificate verification")); + return; } const size_t content_length = msg._get_impl()->_get_content_length(); @@ -751,7 +922,7 @@ class winhttp_client final : public _http_client_communicator } } - utility::string_t flattened_headers = web::http::details::flatten_http_headers(msg.headers()); + utility::string_t flattened_headers = web::http::details::flatten_http_headers(headers); flattened_headers += winhttp_context->get_accept_encoding_header(); // Add headers. @@ -1218,7 +1389,9 @@ class winhttp_client final : public _http_client_communicator std::weak_ptr* p_weak_request_context = reinterpret_cast *>(context); if (p_weak_request_context == nullptr) + { return; + } if (statusCode == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING) { @@ -1230,8 +1403,10 @@ class winhttp_client final : public _http_client_communicator auto p_request_context = p_weak_request_context->lock(); if (!p_request_context) + { // The request context was already released, probably due to cancellation return; + } switch (statusCode) { @@ -1254,7 +1429,7 @@ class winhttp_client final : public _http_client_communicator } p_request_context->report_error(errorCode, build_error_msg(error_result)); - break; + return; } case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE : { @@ -1288,25 +1463,15 @@ class winhttp_client final : public _http_client_communicator p_request_context->report_error(errorCode, build_error_msg(errorCode, "WinHttpReceiveResponse")); } } - break; + return; } + case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST: + p_request_context->on_send_request_validate_cn(); + return; case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE: - { - auto *flagsPtr = reinterpret_cast(statusInfo); - auto flags = *flagsPtr; - - std::string err = "SSL error: "; - if (flags & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED) err += "WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED failed to check revocation status. "; - if (flags & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT) err += "WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT SSL certificate is invalid. "; - if (flags & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED) err += "WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED SSL certificate was revoked. "; - if (flags & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA) err += "WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA SSL invalid CA. "; - if (flags & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID) err += "WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID SSL common name does not match. "; - if (flags & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID) err += "WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID SLL certificate is expired. "; - if (flags & WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR) err += "WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR internal error. "; - - p_request_context->report_exception(web::http::http_exception(std::move(err))); - break; - } + p_request_context->report_exception(web::http::http_exception( + generate_security_failure_message(*reinterpret_cast(statusInfo)))); + return; case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE : { DWORD bytesWritten = *((DWORD *)statusInfo); @@ -1347,7 +1512,7 @@ class winhttp_client final : public _http_client_communicator p_request_context->report_error(errorCode, build_error_msg(errorCode, "WinHttpReceiveResponse")); } } - break; + return; } case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE : { @@ -1411,7 +1576,7 @@ class winhttp_client final : public _http_client_communicator // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 read_next_response_chunk(p_request_context.get(), 0, true); - break; + return; } case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE : { @@ -1459,7 +1624,7 @@ class winhttp_client final : public _http_client_communicator p_request_context->complete_request(p_request_context->m_downloaded); } - break; + return; } case WINHTTP_CALLBACK_STATUS_READ_COMPLETE : { @@ -1483,7 +1648,7 @@ class winhttp_client final : public _http_client_communicator if (bytesRead == 0) { p_request_context->complete_request(p_request_context->m_downloaded); - break; + return; } auto writebuf = p_request_context->_get_writebuffer(); @@ -1544,9 +1709,9 @@ class winhttp_client final : public _http_client_communicator read_next_response_chunk(p_request_context.get(), bytesRead); }); } - break; + return; } - } + } } std::atomic m_opened; diff --git a/Release/src/http/client/x509_cert_utilities.cpp b/Release/src/http/client/x509_cert_utilities.cpp index baf1ae8488..0a7a762d50 100644 --- a/Release/src/http/client/x509_cert_utilities.cpp +++ b/Release/src/http/client/x509_cert_utilities.cpp @@ -12,11 +12,11 @@ ****/ #include "stdafx.h" +#include "../common/x509_cert_utilities.h" -#if defined(__APPLE__) || (defined(ANDROID) || defined(__ANDROID__)) || \ - (defined(_WIN32) && !defined(__cplusplus_winrt) && !defined(_M_ARM) && !defined(CPPREST_EXCLUDE_WEBSOCKETS)) +#ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE -#include "cpprest/details/x509_cert_utilities.h" +#include #include #if defined(ANDROID) || defined(__ANDROID__) @@ -32,11 +32,6 @@ #include #endif -#if defined(_WIN32) -#include -#include -#endif - namespace web { namespace http @@ -375,55 +370,67 @@ bool verify_X509_cert_chain(const std::vector& certChain, const std #endif #if defined(_WIN32) -namespace -{ -// Helper RAII unique_ptrs to free Windows structures. -struct cert_free_certificate_context -{ - void operator()(const CERT_CONTEXT* ctx) const { CertFreeCertificateContext(ctx); } -}; -typedef std::unique_ptr cert_context; -struct cert_free_certificate_chain -{ - void operator()(const CERT_CHAIN_CONTEXT* chain) const { CertFreeCertificateChain(chain); } -}; -typedef std::unique_ptr chain_context; -} - -bool verify_X509_cert_chain(const std::vector& certChain, const std::string&) +bool verify_X509_cert_chain(const std::vector& certChain, const std::string& hostname) { // Create certificate context from server certificate. - cert_context cert(CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - reinterpret_cast(certChain[0].c_str()), - static_cast(certChain[0].size()))); - if (cert == nullptr) + winhttp_cert_context cert; + cert.raw = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + reinterpret_cast(certChain[0].c_str()), + static_cast(certChain[0].size())); + if (cert.raw == nullptr) { return false; } // Let the OS build a certificate chain from the server certificate. - CERT_CHAIN_PARA params; - ZeroMemory(¶ms, sizeof(params)); - params.cbSize = sizeof(CERT_CHAIN_PARA); - params.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; - LPSTR usages[] = {(LPSTR)szOID_PKIX_KP_SERVER_AUTH, - - // For older servers and to match IE. - (LPSTR)szOID_SERVER_GATED_CRYPTO, - (LPSTR)szOID_SGC_NETSCAPE}; - params.RequestedUsage.Usage.cUsageIdentifier = std::extent::value; - params.RequestedUsage.Usage.rgpszUsageIdentifier = usages; - PCCERT_CHAIN_CONTEXT chainContext; - chain_context chain; - if (!CertGetCertificateChain( - nullptr, cert.get(), nullptr, nullptr, ¶ms, CERT_CHAIN_REVOCATION_CHECK_CHAIN, nullptr, &chainContext)) + char oidPkixKpServerAuth[] = szOID_PKIX_KP_SERVER_AUTH; + char oidServerGatedCrypto[] = szOID_SERVER_GATED_CRYPTO; + char oidSgcNetscape[] = szOID_SGC_NETSCAPE; + char* chainUses[] = { + oidPkixKpServerAuth, + oidServerGatedCrypto, + oidSgcNetscape, + }; + CERT_CHAIN_PARA chainPara = {sizeof(chainPara)}; + chainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; + chainPara.RequestedUsage.Usage.cUsageIdentifier = sizeof(chainUses) / sizeof(char*); + chainPara.RequestedUsage.Usage.rgpszUsageIdentifier = chainUses; + + winhttp_cert_chain_context chainContext; + if (!CertGetCertificateChain(nullptr, + cert.raw, + nullptr, + cert.raw->hCertStore, + &chainPara, + CERT_CHAIN_REVOCATION_CHECK_CHAIN, + nullptr, + &chainContext.raw)) { return false; } - chain.reset(chainContext); // Check to see if the certificate chain is actually trusted. - if (chain->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) + if (chainContext.raw->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) + { + return false; + } + + auto u16HostName = utility::conversions::to_utf16string(hostname); + HTTPSPolicyCallbackData policyData = { + {sizeof(policyData)}, + AUTHTYPE_SERVER, + 0, + &u16HostName[0], + }; + CERT_CHAIN_POLICY_PARA policyPara = {sizeof(policyPara)}; + policyPara.pvExtraPolicyPara = &policyData; + CERT_CHAIN_POLICY_STATUS policyStatus = {sizeof(policyStatus)}; + if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, chainContext.raw, &policyPara, &policyStatus)) + { + return false; + } + + if (policyStatus.dwError) { return false; } diff --git a/Release/include/cpprest/details/x509_cert_utilities.h b/Release/src/http/common/x509_cert_utilities.h similarity index 51% rename from Release/include/cpprest/details/x509_cert_utilities.h rename to Release/src/http/common/x509_cert_utilities.h index cc40d9ccfb..e19af0498a 100644 --- a/Release/include/cpprest/details/x509_cert_utilities.h +++ b/Release/src/http/common/x509_cert_utilities.h @@ -13,9 +13,52 @@ #pragma once -#include +#if defined(_WIN32) +#include + +namespace web { namespace http { namespace client { namespace details { + +struct winhttp_cert_context +{ + PCCERT_CONTEXT raw; + winhttp_cert_context() noexcept : raw(nullptr) {} + winhttp_cert_context(const winhttp_cert_context&) = delete; + winhttp_cert_context& operator=(const winhttp_cert_context&) = delete; + ~winhttp_cert_context() + { + // https://docs.microsoft.com/en-us/windows/desktop/api/wincrypt/nf-wincrypt-certfreecertificatecontext + // "The function always returns nonzero." + if (raw) + { + (void)CertFreeCertificateContext(raw); + } + } +}; + +struct winhttp_cert_chain_context +{ + PCCERT_CHAIN_CONTEXT raw; + winhttp_cert_chain_context() noexcept : raw(nullptr) {} + winhttp_cert_chain_context(const winhttp_cert_chain_context&) = delete; + winhttp_cert_chain_context& operator=(const winhttp_cert_chain_context&) = delete; + ~winhttp_cert_chain_context() + { + if (raw) + { + CertFreeCertificateChain(raw); + } + } +}; -#if defined(__APPLE__) || (defined(ANDROID) || defined(__ANDROID__)) || (defined(_WIN32) && !defined(__cplusplus_winrt) && !defined(_M_ARM) && !defined(CPPREST_EXCLUDE_WEBSOCKETS)) +}}}} // namespaces +#endif // _WIN32 + +#if defined(__APPLE__) || (defined(ANDROID) || defined(__ANDROID__)) || (defined(_WIN32) && !defined(__cplusplus_winrt) && !defined(_M_ARM) && !defined(CPPREST_EXCLUDE_WEBSOCKETS)) + #define CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE +#endif + +#ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE +#include #if defined(_MSC_VER) #pragma warning(push) @@ -37,7 +80,7 @@ namespace web { namespace http { namespace client { namespace details { /// /// Using platform specific APIs verifies server certificate. -/// Currently implemented to work on iOS, Android, and OS X. +/// Currently implemented to work on Windows, iOS, Android, and OS X. /// /// Boost.ASIO context to get certificate chain from. /// Host name from the URI. @@ -46,4 +89,4 @@ bool verify_cert_chain_platform_specific(boost::asio::ssl::verify_context &verif }}}} -#endif \ No newline at end of file +#endif // CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE diff --git a/Release/src/websockets/client/ws_client_wspp.cpp b/Release/src/websockets/client/ws_client_wspp.cpp index c670dd0a61..39b7121d57 100644 --- a/Release/src/websockets/client/ws_client_wspp.cpp +++ b/Release/src/websockets/client/ws_client_wspp.cpp @@ -15,7 +15,7 @@ #if !defined(CPPREST_EXCLUDE_WEBSOCKETS) -#include "cpprest/details/x509_cert_utilities.h" +#include "../../http/common/x509_cert_utilities.h" #include "pplx/threadpool.h" #include "ws_client_impl.h" @@ -122,7 +122,7 @@ class wspp_callback_client : public websocket_client_callback_impl, public std:: wspp_callback_client(websocket_client_config config) : websocket_client_callback_impl(std::move(config)), m_state(CREATED) -#if defined(__APPLE__) || (defined(ANDROID) || defined(__ANDROID__)) || defined(_WIN32) +#ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE , m_openssl_failed(false) #endif {} @@ -188,16 +188,16 @@ class wspp_callback_client : public websocket_client_callback_impl, public std:: sslContext->set_verify_mode(boost::asio::ssl::context::verify_none); } -#if defined(__APPLE__) || (defined(ANDROID) || defined(__ANDROID__)) || defined(_WIN32) +#ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE m_openssl_failed = false; #endif sslContext->set_verify_callback([this](bool preverified, boost::asio::ssl::verify_context &verifyCtx) { -#if defined(__APPLE__) || (defined(ANDROID) || defined(__ANDROID__)) || defined(_WIN32) - // On OS X, iOS, and Android, OpenSSL doesn't have access to where the OS - // stores keychains. If OpenSSL fails we will doing verification at the - // end using the whole certificate chain so wait until the 'leaf' cert. - // For now return true so OpenSSL continues down the certificate chain. +#ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE + // Attempt to use platform certificate validation when it is available: + // If OpenSSL fails we will doing verification at the end using the whole certificate chain, + // so wait until the 'leaf' cert. For now return true so OpenSSL continues down the certificate + // chain. if(!preverified) { m_openssl_failed = true; @@ -778,7 +778,7 @@ class wspp_callback_client : public websocket_client_callback_impl, public std:: // Used to track if any of the OpenSSL server certificate verifications // failed. This can safely be tracked at the client level since connections // only happen once for each client. -#if defined(__APPLE__) || (defined(ANDROID) || defined(__ANDROID__)) || defined(_WIN32) +#ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE bool m_openssl_failed; #endif diff --git a/Release/tests/functional/http/client/outside_tests.cpp b/Release/tests/functional/http/client/outside_tests.cpp index 00404a290f..f6ea1396cf 100644 --- a/Release/tests/functional/http/client/outside_tests.cpp +++ b/Release/tests/functional/http/client/outside_tests.cpp @@ -173,8 +173,8 @@ static void test_ignored_ssl_cert(const uri& base_uri) http_client_config config; config.set_validate_certificates(false); http_client client(base_uri, config); - auto request = client.request(methods::GET).get(); - VERIFY_ARE_EQUAL(status_codes::OK, request.status_code()); + auto response = client.request(methods::GET).get(); + VERIFY_ARE_EQUAL(status_codes::OK, response.status_code()); }); } #endif // !defined(__cplusplus_winrt) @@ -197,10 +197,34 @@ TEST(server_hostname_mismatch) } #if !defined(__cplusplus_winrt) +TEST(server_hostname_host_override, + "Ignore:Android", "229", + "Ignore:Apple", "229", + "Ignore:Linux", "229") +{ + handle_timeout([] + { + http_client client(U("https://wrong.host.badssl.com/")); + http_request req(methods::GET); + req.headers().add(U("Host"), U("badssl.com")); + auto response = client.request(req).get(); + VERIFY_ARE_EQUAL(status_codes::OK, response.status_code()); + }); +} + TEST(server_hostname_mismatch_ignored) { test_ignored_ssl_cert(U("https://wrong.host.badssl.com/")); } + +TEST(server_hostname_host_override_after_upgrade) +{ + http_client client(U("http://198.35.26.96/")); + http_request req(methods::GET); + req.headers().add(U("Host"), U("en.wikipedia.org")); + auto response = client.request(req).get(); + VERIFY_ARE_EQUAL(status_codes::OK, response.status_code()); +} #endif // !defined(__cplusplus_winrt) TEST(server_cert_expired) @@ -215,7 +239,10 @@ TEST(server_cert_expired_ignored) } #endif // !defined(__cplusplus_winrt) -TEST(server_cert_revoked) +TEST(server_cert_revoked, + "Ignore:Android", "229", + "Ignore:Apple", "229", + "Ignore:Linux", "229") { test_failed_ssl_cert(U("https://revoked.badssl.com/")); }