Implement mTLS support in WinHTTP transport. (#6131)

* Very preliminary mTLS implementation

* Tests for TLS client certificate

* Tested mTLS functionality

* Added changelog entry; updated PCCERT_CONTEXT using declaration to be more succinct.
This commit is contained in:
Larry Osterman 2024-11-06 12:03:08 -08:00 committed by GitHub
parent 3687136d04
commit 9770fb77dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1224 additions and 770 deletions

2
.vscode/cspell.json vendored
View File

@ -207,6 +207,8 @@
"opentelemetry", "opentelemetry",
"Osterman", "Osterman",
"otel", "otel",
"PCCERT",
"PCERT",
"PBYTE", "PBYTE",
"pdbs", "pdbs",
"phoebusm", "phoebusm",

View File

@ -4,6 +4,9 @@
### Features Added ### Features Added
- Added mTLS support to WinHTTP transport.
- To enable mTLS, first create an appropriate Windows `PCCERT_CONTEXT` object and set the `TlsClientCertificate` field in `WinHttpTransportOptions` to that certificate before creating the `WinHttpTransport` object.
### Breaking Changes ### Breaking Changes
### Bugs Fixed ### Bugs Fixed

View File

@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // Licensed under the MIT License.
// cspell: words PCCERT
/** /**
* @file * @file
* @brief #Azure::Core::Http::HttpTransport implementation via WinHTTP. * @brief #Azure::Core::Http::HttpTransport implementation via WinHTTP.
@ -20,10 +22,16 @@
#include <string> #include <string>
#include <vector> #include <vector>
/**
* @brief Declaration of a Windows PCCERT_CONTEXT structure from the Windows SDK.
*/
using PCCERT_CONTEXT = const struct _CERT_CONTEXT*;
namespace Azure { namespace Core { namespace Http { namespace Azure { namespace Core { namespace Http {
namespace _detail { namespace _detail {
class WinHttpTransportImpl;
class WinHttpRequest; class WinHttpRequest;
} } // namespace _detail
/** /**
* @brief Sets the WinHTTP session and connection options used to customize the behavior of the * @brief Sets the WinHTTP session and connection options used to customize the behavior of the
@ -91,6 +99,12 @@ namespace Azure { namespace Core { namespace Http {
* the server. * the server.
*/ */
std::vector<std::string> ExpectedTlsRootCertificates; std::vector<std::string> ExpectedTlsRootCertificates;
/**
* @brief TLS Client Certificate Context, used when the TLS Server requests mTLS client
* authentication.
*/
PCCERT_CONTEXT TlsClientCertificate{nullptr};
}; };
/** /**
@ -99,23 +113,16 @@ namespace Azure { namespace Core { namespace Http {
*/ */
class WinHttpTransport : public HttpTransport { class WinHttpTransport : public HttpTransport {
private: private:
WinHttpTransportOptions m_options; std::unique_ptr<_detail::WinHttpTransportImpl> m_impl;
// m_sessionhandle is const to ensure immutability.
const Azure::Core::_internal::UniqueHandle<void*> m_sessionHandle;
Azure::Core::_internal::UniqueHandle<void*> CreateSessionHandle(); protected:
Azure::Core::_internal::UniqueHandle<void*> CreateConnectionHandle( /** @brief Callback to allow a derived transport to extract the request handle. Used for
Azure::Core::Url const& url, * WebSocket transports.
Azure::Core::Context const& context); *
* @param request - Request which contains the WinHttp request handle.
std::unique_ptr<_detail::WinHttpRequest> CreateRequestHandle( */
Azure::Core::_internal::UniqueHandle<void*> const& connectionHandle, virtual void OnUpgradedConnection(
Azure::Core::Url const& url, std::unique_ptr<_detail::WinHttpRequest> const& request) const;
Azure::Core::Http::HttpMethod const& method);
// Callback to allow a derived transport to extract the request handle. Used for WebSocket
// transports.
virtual void OnUpgradedConnection(std::unique_ptr<_detail::WinHttpRequest> const&){};
public: public:
/** /**
@ -125,11 +132,6 @@ namespace Azure { namespace Core { namespace Http {
*/ */
WinHttpTransport(WinHttpTransportOptions const& options = WinHttpTransportOptions()); WinHttpTransport(WinHttpTransportOptions const& options = WinHttpTransportOptions());
/**
* @brief Constructs `%WinHttpTransport`.
*
* @param options Optional parameter to override the default settings.
*/
/** /**
* @brief Constructs `%WinHttpTransport` object based on common Azure HTTP Transport Options * @brief Constructs `%WinHttpTransport` object based on common Azure HTTP Transport Options
* *
@ -148,6 +150,10 @@ namespace Azure { namespace Core { namespace Http {
// and virtual or protected and // and virtual or protected and
// non-virtual"](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c35-a-base-class-destructor-should-be-either-public-and-virtual-or-protected-and-non-virtual) // non-virtual"](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c35-a-base-class-destructor-should-be-either-public-and-virtual-or-protected-and-non-virtual)
virtual ~WinHttpTransport(); virtual ~WinHttpTransport();
// @cond
friend _detail::WinHttpTransportImpl;
// @endcond
}; };
}}} // namespace Azure::Core::Http }}} // namespace Azure::Core::Http

View File

@ -0,0 +1,74 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include <memory>
#include <wincrypt.h>
#include <wil/resource.h>
namespace Azure { namespace Core { namespace Http { namespace _detail {
class WinHttpTransportImpl : HttpTransport {
private:
WinHttpTransport const* m_parent;
WinHttpTransportOptions m_options;
// m_sessionhandle is const to ensure immutability.
const Azure::Core::_internal::UniqueHandle<void*> m_sessionHandle;
wil::unique_cert_context m_tlsClientCertificate;
Azure::Core::_internal::UniqueHandle<void*> CreateSessionHandle();
Azure::Core::_internal::UniqueHandle<void*> CreateConnectionHandle(
Azure::Core::Url const& url,
Azure::Core::Context const& context);
std::unique_ptr<_detail::WinHttpRequest> CreateRequestHandle(
Azure::Core::_internal::UniqueHandle<void*> const& connectionHandle,
Azure::Core::Url const& url,
Azure::Core::Http::HttpMethod const& method);
// Callback to allow a derived transport to extract the request handle. Used for WebSocket
// transports.
virtual void OnUpgradedConnection(std::unique_ptr<_detail::WinHttpRequest> const& request)
{
m_parent->OnUpgradedConnection(request);
};
public:
/**
* @brief Constructs `%WinHttpTransport`.
*
* @param options Optional parameter to override the default settings.
*/
WinHttpTransportImpl(
WinHttpTransport const* parent,
WinHttpTransportOptions const& options = WinHttpTransportOptions());
/**
* @brief Constructs `%WinHttpTransport`.
*
* @param options Optional parameter to override the default settings.
*/
/**
* @brief Constructs `%WinHttpTransport` object based on common Azure HTTP Transport Options
*
*/
WinHttpTransportImpl(
WinHttpTransport const* parent,
Azure::Core::Http::Policies::TransportOptions const& options);
/**
* @brief Implements the HTTP transport interface to send an HTTP Request and produce an
* HTTP RawResponse.
*
*/
virtual std::unique_ptr<RawResponse> Send(Request& request, Context const& context) override;
// See also:
// [Core Guidelines C.35: "A base class destructor should be either public
// and virtual or protected and
// non-virtual"](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c35-a-base-class-destructor-should-be-either-public-and-virtual-or-protected-and-non-virtual)
virtual ~WinHttpTransportImpl();
};
}}}} // namespace Azure::Core::Http::_detail

View File

@ -28,9 +28,10 @@
#pragma warning(disable : 6553) #pragma warning(disable : 6553)
#pragma warning(disable : 6387) // An argument in result_macros.h may be '0', for the function #pragma warning(disable : 6387) // An argument in result_macros.h may be '0', for the function
// 'GetProcAddress'. // 'GetProcAddress'.
#include <wincrypt.h>
#include <wil\resource.h> #include <wil\resource.h>
#pragma warning(pop) #pragma warning(pop)
#include <wincrypt.h>
#include <winhttp.h> #include <winhttp.h>
namespace Azure { namespace Core { namespace Http { namespace _detail { namespace Azure { namespace Core { namespace Http { namespace _detail {
@ -157,6 +158,7 @@ namespace Azure { namespace Core { namespace Http { namespace _detail {
Azure::Core::_internal::UniqueHandle<HINTERNET> m_requestHandle; Azure::Core::_internal::UniqueHandle<HINTERNET> m_requestHandle;
std::unique_ptr<WinHttpAction> m_httpAction; std::unique_ptr<WinHttpAction> m_httpAction;
std::vector<std::string> m_expectedTlsRootCertificates; std::vector<std::string> m_expectedTlsRootCertificates;
wil::unique_cert_context m_tlsClientCertificate;
/* /*
* Adds the specified trusted certificates to the specified certificate store. * Adds the specified trusted certificates to the specified certificate store.
@ -176,6 +178,7 @@ namespace Azure { namespace Core { namespace Http { namespace _detail {
Azure::Core::_internal::UniqueHandle<HINTERNET> const& connectionHandle, Azure::Core::_internal::UniqueHandle<HINTERNET> const& connectionHandle,
Azure::Core::Url const& url, Azure::Core::Url const& url,
Azure::Core::Http::HttpMethod const& method, Azure::Core::Http::HttpMethod const& method,
PCCERT_CONTEXT tlsClientCertificate,
WinHttpTransportOptions const& options); WinHttpTransportOptions const& options);
~WinHttpRequest(); ~WinHttpRequest();

File diff suppressed because it is too large Load Diff

View File

@ -49,7 +49,17 @@ namespace Azure { namespace Core { namespace Test {
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host);
auto response = m_pipeline->Send(request, Context{}); auto response = m_pipeline->Send(request, Context{});
checkResponseCode(response->GetStatusCode(), Azure::Core::Http::HttpStatusCode::NoContent); checkResponseCode(response->GetStatusCode(), Azure::Core::Http::HttpStatusCode::NoContent);
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); std::uint64_t expectedResponseBodySize;
if (response->GetStatusCode() == Azure::Core::Http::HttpStatusCode::NoContent)
{
// http://mt3.google.com/generate_204 returns 204 with no body and thus no content-length
// header
expectedResponseBodySize = 0;
}
else
{
expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
}
CheckBodyFromBuffer(*response, expectedResponseBodySize); CheckBodyFromBuffer(*response, expectedResponseBodySize);
} }