Skip to content

Commit 807a18e

Browse files
stgatesSteve Gates
authored andcommitted
Initial completion of verifying X509 certs on OS X/iOS.
1 parent 7fcf913 commit 807a18e

File tree

4 files changed

+246
-5
lines changed

4 files changed

+246
-5
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/***
2+
* ==++==
3+
*
4+
* Copyright (c) Microsoft Corporation. All rights reserved.
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* ==--==
17+
* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
18+
*
19+
* x509_cert_utilities.h
20+
*
21+
* Contains utility functions for helping to verify server certificates in OS X/iOS.
22+
*
23+
* For the latest on this and related APIs, please see http://casablanca.codeplex.com.
24+
*
25+
* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
26+
****/
27+
28+
#pragma once
29+
30+
#include <vector>
31+
#include <string>
32+
33+
namespace web { namespace http { namespace client { namespace details {
34+
35+
bool verify_X509_cert_chain(const std::vector<std::string> &certChain, const std::string &hostName);
36+
37+
}}}}

Release/src/CMakeLists.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ if(UNIX)
1717
http/common/http_helpers.cpp
1818
http/client/http_msg_client.cpp
1919
http/client/http_linux.cpp
20+
http/client/x509_cert_utilities.cpp
2021
http/listener/http_linux_server.cpp
2122
http/listener/http_listener.cpp
2223
http/listener/http_msg_listen.cpp
@@ -30,7 +31,8 @@ if(UNIX)
3031
if(APPLE)
3132
set(SOURCES ${SOURCES} pplx/apple/pplxapple.cpp)
3233
find_library(COREFOUNDATION CoreFoundation "/")
33-
set(EXTRALINKS ${COREFOUNDATION})
34+
find_library(SECURITY Security "/")
35+
set(EXTRALINKS ${COREFOUNDATION} ${SECURITY})
3436
else()
3537
set(SOURCES ${SOURCES} pplx/linux/pplxlinux.cpp)
3638
endif()
@@ -46,6 +48,7 @@ elseif(WIN32)
4648
http/common/http_msg.cpp
4749
http/common/http_helpers.cpp
4850
http/client/http_msg_client.cpp
51+
http/client/x509_cert_utilities.cpp
4952
http/listener/http_listener.cpp
5053
http/listener/http_msg_listen.cpp
5154
http/listener/http_server_api.cpp
@@ -82,7 +85,6 @@ target_link_libraries(${Casablanca_LIBRARY}
8285
${Boost_LOCALE_LIBRARY}
8386
${Boost_REGEX_LIBRARY}
8487
${Boost_RANDOM_LIBRARY}
85-
${COREFOUNDATION}
8688
${EXTRALINKS}
8789
)
8890

Release/src/http/client/http_linux.cpp

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "stdafx.h"
3131

3232
#include "cpprest/http_client_impl.h"
33+
#include "cpprest/x509_cert_utilities.h"
3334
#include <unordered_set>
3435

3536
using boost::asio::ip::tcp;
@@ -280,7 +281,7 @@ namespace web { namespace http
280281
bool m_timedout;
281282
boost::asio::streambuf m_body_buf;
282283
boost::asio::deadline_timer m_timeout_timer;
283-
284+
284285
virtual ~linux_client_request_context();
285286

286287
void handle_timeout_timer(const boost::system::error_code& ec)
@@ -485,7 +486,7 @@ namespace web { namespace http
485486
if(client_config().validate_certificates())
486487
{
487488
ctx->m_ssl_stream->set_verify_mode(boost::asio::ssl::context::verify_peer);
488-
ctx->m_ssl_stream->set_verify_callback(boost::asio::ssl::rfc2818_verification(m_uri.host()));
489+
ctx->m_ssl_stream->set_verify_callback(boost::bind(&linux_client::handle_cert_verification, shared_from_this(), _1, _2));
489490
}
490491
else
491492
{
@@ -536,7 +537,7 @@ namespace web { namespace http
536537
if(client_config().validate_certificates())
537538
{
538539
ctx->m_ssl_stream->set_verify_mode(boost::asio::ssl::context::verify_peer);
539-
ctx->m_ssl_stream->set_verify_callback(boost::asio::ssl::rfc2818_verification(m_uri.host()));
540+
ctx->m_ssl_stream->set_verify_callback(boost::bind(&linux_client::handle_cert_verification, shared_from_this(), _1, _2));
540541
}
541542
else
542543
{
@@ -548,6 +549,53 @@ namespace web { namespace http
548549
}
549550
}
550551

552+
bool handle_cert_verification(bool preverified, boost::asio::ssl::verify_context &ctx)
553+
{
554+
#if defined(__APPLE__)
555+
// The 'leaf', non-Certificate Authority (CA) certificate, i.e. actual server certificate
556+
// is at the '0' position in the certificate chain, the rest are optional intermediate
557+
// certificates, followed finally by the root CA self signed certificate.
558+
559+
// OpenSSL calls the verification callback once per certificate in the chain,
560+
// starting with the root CA certificate. We will be performing verification all
561+
// at once using the whole certificate chain so wait until the 'leaf' cert.
562+
X509_STORE_CTX *storeContext = ctx.native_handle();
563+
int currentDepth = X509_STORE_CTX_get_error_depth(storeContext);
564+
if(currentDepth == 0)
565+
{
566+
// preverified false means OpenSSL falied to verify the certificate successfully.
567+
// On OS X, iOS, or Android, OpenSSL doesn't have access to where the OS stores keychains.
568+
// To work around this we fall back to the OS facilities to verify the server certificate.
569+
if(!preverified)
570+
{
571+
STACK_OF(X509) *certStack = X509_STORE_CTX_get_chain(ctx.native_handle());
572+
const int numCerts = sk_X509_num(certStack);
573+
std::vector<std::string> certChain;
574+
575+
for(int i = 0; i < numCerts; ++i)
576+
{
577+
X509 *cert = sk_X509_value(certStack, i);
578+
579+
// Encode into DER format into raw memory.
580+
unsigned char * buffer = nullptr;
581+
const int len = i2d_X509(cert, &buffer);
582+
if(len < 0)
583+
{
584+
return false;
585+
}
586+
587+
certChain.emplace_back(reinterpret_cast<char *>(buffer), len);
588+
}
589+
590+
return verify_X509_cert_chain(certChain, m_uri.host());
591+
}
592+
}
593+
#endif
594+
595+
boost::asio::ssl::rfc2818_verification rfc2818(m_uri.host());
596+
return rfc2818(preverified, ctx);
597+
}
598+
551599
void handle_handshake(const boost::system::error_code& ec, std::shared_ptr<linux_client_request_context> ctx)
552600
{
553601
if (!ec)
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/***
2+
* ==++==
3+
*
4+
* Copyright (c) Microsoft Corporation. All rights reserved.
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* ==--==
17+
* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
18+
*
19+
* x509_cert_utilities.cpp
20+
*
21+
* Contains utility functions for helping to verify server certificates in OS X/iOS.
22+
*
23+
* For the latest on this and related APIs, please see http://casablanca.codeplex.com.
24+
*
25+
* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
26+
****/
27+
28+
#include "cpprest/x509_cert_utilities.h"
29+
30+
#include <vector>
31+
#include <string>
32+
33+
#if defined(__APPLE__)
34+
#include <CoreFoundation/CFData.h>
35+
#include <Security/SecBase.h>
36+
#include <Security/SecCertificate.h>
37+
#include <Security/SecPolicy.h>
38+
#include <Security/SecTrust.h>
39+
#endif
40+
41+
namespace web { namespace http { namespace client { namespace details {
42+
43+
#if defined(__APPLE__)
44+
45+
// Simple RAII pattern wrapper to perform CFRelease on objects.
46+
template <typename T>
47+
class CFRef
48+
{
49+
public:
50+
CFRef(T v) : value(v) {}
51+
CFRef() : value(nullptr) {}
52+
53+
~CFRef()
54+
{
55+
if(value != nullptr)
56+
{
57+
CFRelease(value);
58+
}
59+
}
60+
61+
T & Get()
62+
{
63+
return value;
64+
}
65+
private:
66+
T value;
67+
};
68+
69+
template <typename T>
70+
class CFVectorRef
71+
{
72+
public:
73+
CFVectorRef() {}
74+
~CFVectorRef()
75+
{
76+
for(auto & item : container)
77+
{
78+
CFRelease(item);
79+
}
80+
}
81+
82+
void push_back(T item)
83+
{
84+
container.push_back(item);
85+
}
86+
std::vector<T> & items()
87+
{
88+
return container;
89+
}
90+
91+
private:
92+
std::vector<T> container;
93+
};
94+
95+
bool verify_X509_cert_chain(const std::vector<std::string> &certChain, const std::string &hostName)
96+
{
97+
// Build up CFArrayRef with all the certificates.
98+
// All this code is basically just to get into the correct structures for the Apple APIs.
99+
// Copies are avoided whenever possible.
100+
CFVectorRef<SecCertificateRef> certs;
101+
for(const auto & certBuf : certChain)
102+
{
103+
CFRef<CFDataRef> certDataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
104+
reinterpret_cast<const unsigned char*>(certBuf.c_str()),
105+
certBuf.size(),
106+
kCFAllocatorNull);
107+
if(certDataRef.Get() == nullptr)
108+
{
109+
return false;
110+
}
111+
112+
SecCertificateRef certObj = SecCertificateCreateWithData(nullptr, certDataRef.Get());
113+
if(certObj == nullptr)
114+
{
115+
return false;
116+
}
117+
certs.push_back(certObj);
118+
}
119+
CFRef<CFArrayRef> certsArray = CFArrayCreate(kCFAllocatorDefault, (const void **)&certs.items()[0], certs.items().size(), nullptr);
120+
if(certsArray.Get() == nullptr)
121+
{
122+
return false;
123+
}
124+
125+
// Create trust management object with certificates and SSL policy.
126+
// Note: SecTrustCreateWithCertificates expects the certificate to be
127+
// verified is the first element.
128+
CFRef<CFStringRef> cfHostName = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault,
129+
&hostName[0],
130+
kCFStringEncodingASCII,
131+
kCFAllocatorNull);
132+
if(cfHostName.Get() == nullptr)
133+
{
134+
return false;
135+
}
136+
CFRef<SecPolicyRef> policy = SecPolicyCreateSSL(true /* client side */, cfHostName.Get());
137+
CFRef<SecTrustRef> trust;
138+
OSStatus status = SecTrustCreateWithCertificates(certsArray.Get(), policy.Get(), &trust.Get());
139+
if(status == noErr)
140+
{
141+
// Perform actual certificate verification.
142+
SecTrustResultType trustResult;
143+
status = SecTrustEvaluate(trust.Get(), &trustResult);
144+
if(status == noErr && (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed))
145+
{
146+
return true;
147+
}
148+
}
149+
150+
return false;
151+
}
152+
#endif
153+
154+
}}}}

0 commit comments

Comments
 (0)