Skip to content

Commit 1930a3e

Browse files
committed
Implementing server certificate verification for secure websockets on Windows desktop.
1 parent 0d7b7eb commit 1930a3e

File tree

9 files changed

+96
-36
lines changed

9 files changed

+96
-36
lines changed

Release/include/cpprest/astreambuf.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ namespace concurrency = Concurrency;
6262

6363
namespace Concurrency
6464
{
65-
/// Library for asychronous streams.
65+
/// Library for asynchronous streams.
6666
namespace streams
6767
{
6868
/// <summary>

Release/include/cpprest/x509_cert_utilities.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
#include <vector>
3131
#include <string>
3232

33-
#if defined(__APPLE__) || defined(ANDROID)
33+
#if defined(__APPLE__) || defined(ANDROID) || (defined(_MS_WINDOWS) && !defined(__cplusplus_winrt))
3434

3535
#include <boost/asio/ssl.hpp>
3636

Release/src/build/sources.proj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@
129129
<ClInclude Include="$(CasablancaIncludeDir)\cpprest\xxpublic.h">
130130
<Filter>Header Files\cpprest</Filter>
131131
</ClInclude>
132+
<ClInclude Include="$(CasablancaIncludeDir)\cpprest\x509_cert_utilities.h">
133+
<Filter>Header Files\cpprest</Filter>
134+
</ClInclude>
132135
<ClInclude Include="$(CasablancaIncludeDir)\pplx\pplxcancellation_token.h">
133136
<Filter>Header Files\pplx</Filter>
134137
</ClInclude>
@@ -152,6 +155,9 @@
152155
<ClCompile Include="$(CasablancaSrcDir)\http\client\http_msg_client.cpp">
153156
<Filter>Source Files</Filter>
154157
</ClCompile>
158+
<ClCompile Include="$(CasablancaSrcDir)\http\client\x509_cert_utilities.cpp">
159+
<Filter>Source Files</Filter>
160+
</ClCompile>
155161
<ClCompile Include="$(CasablancaSrcDir)\http\common\http_helpers.cpp">
156162
<Filter>Source Files</Filter>
157163
</ClCompile>

Release/src/build/vs12/casablanca120.vcxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
</ItemGroup>
4545
<ItemDefinitionGroup>
4646
<ClCompile>
47-
<PreprocessorDefinitions>_ASYNCRT_EXPORT;_PPLX_EXPORT;WIN32;_MBCS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
47+
<PreprocessorDefinitions>_WINSOCK_DEPRECATED_NO_WARNINGS;_ASYNCRT_EXPORT;_PPLX_EXPORT;WIN32;_MBCS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> <!-- TODO stgates -->
4848
<AdditionalIncludeDirectories>$(CasablancaIncludeDir);$(CasablancaSrcDir)\pch;$(WebsocketppIncludeDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
4949
<PrecompiledHeader>Use</PrecompiledHeader>
5050
<PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>

Release/src/http/client/x509_cert_utilities.cpp

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,16 @@
4141

4242
#if defined(ANDROID)
4343
#include <jni.h>
44+
using namespace crossplat;
4445
#endif
4546

46-
using namespace crossplat;
47+
#if defined(_MS_WINDOWS) && !defined(__cplusplus_winrt)
48+
#include <wincrypt.h>
49+
#endif
4750

4851
namespace web { namespace http { namespace client { namespace details {
4952

50-
#if defined(__APPLE__) || defined(ANDROID)
53+
#if defined(__APPLE__) || defined(ANDROID) || (defined(_MS_WINDOWS) && !defined(__cplusplus_winrt))
5154
bool verify_cert_chain_platform_specific(boost::asio::ssl::verify_context &verifyCtx, const std::string &hostName)
5255
{
5356
X509_STORE_CTX *storeContext = verifyCtx.native_handle();
@@ -89,7 +92,83 @@ bool verify_cert_chain_platform_specific(boost::asio::ssl::verify_context &verif
8992
certChain.push_back(std::move(certData));
9093
}
9194

92-
return verify_X509_cert_chain(certChain, hostName);
95+
auto verify_result = verify_X509_cert_chain(certChain, hostName);
96+
97+
// The Windows Crypto APIs don't do host name checks, use Boost's implementation.
98+
#if defined(_MS_WINDOWS)
99+
if (verify_result)
100+
{
101+
boost::asio::ssl::rfc2818_verification rfc2818(hostName);
102+
verify_result = rfc2818(verify_result, verifyCtx);
103+
}
104+
#endif
105+
return verify_result;
106+
}
107+
#endif
108+
109+
#if defined(_MS_WINDOWS) && !defined(__cplusplus_winrt)
110+
111+
// Helper RAII unique_ptrs to free Windows structures.
112+
struct cert_free_certificate_context
113+
{
114+
void operator()(const CERT_CONTEXT *ctx) const
115+
{
116+
CertFreeCertificateContext(ctx);
117+
}
118+
};
119+
typedef std::unique_ptr<const CERT_CONTEXT, cert_free_certificate_context> cert_context;
120+
struct cert_free_certificate_chain
121+
{
122+
void operator()(const CERT_CHAIN_CONTEXT *chain) const
123+
{
124+
CertFreeCertificateChain(chain);
125+
}
126+
};
127+
typedef std::unique_ptr<const CERT_CHAIN_CONTEXT, cert_free_certificate_chain> chain_context;
128+
129+
bool verify_X509_cert_chain(const std::vector<std::string> &certChain, const std::string &)
130+
{
131+
// Create certificate context from server certificate.
132+
cert_context cert(CertCreateCertificateContext(
133+
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
134+
reinterpret_cast<const unsigned char *>(certChain[0].c_str()),
135+
certChain[0].size()));
136+
if (cert == nullptr)
137+
{
138+
return false;
139+
}
140+
141+
// Let the OS build a certificate chain from the server certificate.
142+
CERT_CHAIN_PARA params;
143+
ZeroMemory(&params, sizeof(params));
144+
params.cbSize = sizeof(CERT_CHAIN_PARA);
145+
params.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
146+
LPTSTR usages[] = { szOID_PKIX_KP_SERVER_AUTH };
147+
params.RequestedUsage.Usage.cUsageIdentifier = 1;
148+
params.RequestedUsage.Usage.rgpszUsageIdentifier = usages;
149+
PCCERT_CHAIN_CONTEXT chainContext;
150+
chain_context chain;
151+
if (!CertGetCertificateChain(
152+
nullptr,
153+
cert.get(),
154+
nullptr,
155+
nullptr,
156+
&params,
157+
CERT_CHAIN_REVOCATION_CHECK_CHAIN,
158+
nullptr,
159+
&chainContext))
160+
{
161+
return false;
162+
}
163+
chain.reset(chainContext);
164+
165+
// Check to see if the certificate chain is actually trusted.
166+
if (chain->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR)
167+
{
168+
return false;
169+
}
170+
171+
return true;
93172
}
94173
#endif
95174

Release/src/websockets/client/ws_client.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ class wspp_client : public _websocket_client_impl, public std::enable_shared_fro
109109
m_work(utility::details::make_unique<boost::asio::io_service::work>(m_service)),
110110
m_state(CREATED),
111111
m_num_sends(0)
112-
#if defined(__APPLE__) || defined(ANDROID)
112+
#if defined(__APPLE__) || defined(ANDROID) || defined(_MS_WINDOWS)
113113
, m_openssl_failed(false)
114114
#endif
115115
{}
@@ -174,12 +174,12 @@ class wspp_client : public _websocket_client_impl, public std::enable_shared_fro
174174
sslContext->set_default_verify_paths();
175175
sslContext->set_options(boost::asio::ssl::context::default_workarounds);
176176
sslContext->set_verify_mode(boost::asio::ssl::context::verify_peer);
177-
#if defined(__APPLE__) || defined(ANDROID)
177+
#if defined(__APPLE__) || defined(ANDROID) || defined(_MS_WINDOWS)
178178
m_openssl_failed = false;
179179
#endif
180180
sslContext->set_verify_callback([this](bool preverified, boost::asio::ssl::verify_context &verifyCtx)
181181
{
182-
#if defined(__APPLE__) || defined(ANDROID)
182+
#if defined(__APPLE__) || defined(ANDROID) || defined(_MS_WINDOWS)
183183
// On OS X, iOS, and Android, OpenSSL doesn't have access to where the OS
184184
// stores keychains. If OpenSSL fails we will doing verification at the
185185
// end using the whole certificate chain so wait until the 'leaf' cert.
@@ -190,7 +190,7 @@ class wspp_client : public _websocket_client_impl, public std::enable_shared_fro
190190
}
191191
if(m_openssl_failed)
192192
{
193-
return http::client::details::verify_cert_chain_platform_specific(verifyCtx, m_uri.host());
193+
return http::client::details::verify_cert_chain_platform_specific(verifyCtx, utility::conversions::to_utf8string(m_uri.host()));
194194
}
195195
#endif
196196
boost::asio::ssl::rfc2818_verification rfc2818(utility::conversions::to_utf8string(m_uri.host()));
@@ -711,7 +711,7 @@ class wspp_client : public _websocket_client_impl, public std::enable_shared_fro
711711
// Used to track if any of the OpenSSL server certificate verifications
712712
// failed. This can safely be tracked at the client level since connections
713713
// only happen once for each client.
714-
#if defined(__APPLE__) || defined(ANDROID)
714+
#if defined(__APPLE__) || defined(ANDROID) || defined(_MS_WINDOWS)
715715
bool m_openssl_failed;
716716
#endif
717717

Release/tests/functional/http/client/vs12/HttpClient120_test.vcxproj

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3-
<Import Project="$(_NTDRIVE)$(_NTROOT)\vctools\cpp_rest\cpprest.razzle.props" Condition="exists('$(_NTDRIVE)$(_NTROOT)\vctools\cpp_rest\cpprest.razzle.props')" />
43
<ItemGroup Label="ProjectConfigurations">
54
<ProjectConfiguration Include="Debug|ARM">
65
<Configuration>Debug</Configuration>
@@ -83,15 +82,6 @@
8382
<AdditionalIncludeDirectories>$(CasablancaIncludeDir);$(CommonTestIncludeDir);..\..\utilities\include;$(TestListenerPath)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
8483
</ClCompile>
8584
</ItemDefinitionGroup>
86-
<!-- If testing against a VS installation, need to include the libs. -->
87-
<ItemDefinitionGroup Condition="'$(BuildAgainstVSInstallation)'!=''">
88-
<Link>
89-
<AdditionalLibraryDirectories>$(CasablancaVSLibDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
90-
</Link>
91-
<Link>
92-
<AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
93-
</Link>
94-
</ItemDefinitionGroup>
9585
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
9686
<ClCompile>
9787
<PrecompiledHeader>Use</PrecompiledHeader>
@@ -239,7 +229,6 @@
239229
<ClCompile Include="..\to_string_tests.cpp" />
240230
</ItemGroup>
241231
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
242-
<Import Project="$(BuildRoot)\cpprest.razzle.targets" Condition="'$(BuildInRazzle)'!=''" />
243232
<ImportGroup Label="ExtensionTargets">
244233
</ImportGroup>
245234
</Project>

Release/tests/functional/websockets/client/authentication_tests.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,6 @@ TEST_FIXTURE(uri_address, auth_with_credentials, "Ignore", "245")
8686
}
8787
#endif
8888

89-
// Server certificate verification isn't implemented on Windows desktop yet.
90-
#if !defined(_MS_WINDOWS) || defined(__cplusplus_winrt)
9189
TEST_FIXTURE(uri_address, ssl_test)
9290
{
9391
websocket_client client;
@@ -110,7 +108,6 @@ TEST_FIXTURE(uri_address, ssl_test)
110108
receive_task.wait();
111109
client.close().wait();
112110
}
113-
#endif
114111

115112
} // SUITE(authentication_tests)
116113

Release/tests/functional/websockets/client/vs12/websocketsclient120_test.vcxproj

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,6 @@
6868
</Link>
6969
</ItemDefinitionGroup>
7070

71-
<!-- If testing against a VS installation, need to include the libs. -->
72-
<ItemDefinitionGroup Condition="'$(BuildAgainstVSInstallation)'!=''">
73-
<Link>
74-
<AdditionalLibraryDirectories>$(CasablancaVSLibDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
75-
</Link>
76-
<Link>
77-
<GenerateDebugInformation>true</GenerateDebugInformation>
78-
<AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
79-
</Link>
80-
</ItemDefinitionGroup>
81-
8271
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
8372
<ClCompile>
8473
<Optimization>Disabled</Optimization>

0 commit comments

Comments
 (0)