Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for cert-pinning on Windows and Mac. #702

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Gery Vessere ([email protected])
Cisco Systems
Gergely Lukacsy (glukacsy)
Chris Deering (deeringc)
Chris O'Gorman (chogorma)

Ocedo GmbH
Henning Pfeiffer (megaposer)
Expand Down
48 changes: 48 additions & 0 deletions Release/include/cpprest/certificate_info.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/***
* ==++==
*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* ==--==
* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*
* Certificate info
*
* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk
*
* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
****/

#pragma once

#include <vector>
#include <string>

namespace web { namespace http { namespace client {

using CertificateChain = std::vector<std::vector<unsigned char>>;

struct certificate_info
{
CertificateChain certificate_chain;
std::string host_name;
long certificate_error{ 0 };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do I interpret this error code? I suppose it's platform dependent?
Please add some description in the comments.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the error code is platform dependent, we use it for debugging certificate issues.
Mac: https://developer.apple.com/documentation/security/sectrustresulttype
Windows: https://msdn.microsoft.com/en-us/library/windows/desktop/aa377590(v=vs.85).aspx

Will add a comment for that.

bool verified{ false };

certificate_info(const std::string host) : host_name(host) {};
certificate_info(const std::string host, CertificateChain chain, long error = 0) : host_name(host), certificate_chain(chain), certificate_error(error) {};
};

using CertificateChainFunction = std::function<bool(const std::shared_ptr<certificate_info> certificate_Info)>;

}}}
14 changes: 12 additions & 2 deletions Release/include/cpprest/details/x509_cert_utilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#pragma once

#include <string>
#include "cpprest/certificate_info.h"

#if defined(__APPLE__) || (defined(ANDROID) || defined(__ANDROID__)) || (defined(_WIN32) && !defined(__cplusplus_winrt) && !defined(_M_ARM) && !defined(CPPREST_EXCLUDE_WEBSOCKETS))

Expand All @@ -35,15 +36,24 @@

namespace web { namespace http { namespace client { namespace details {

using namespace utility;


bool is_end_certificate_in_chain(boost::asio::ssl::verify_context &verifyCtx);

/// <summary>
/// Using platform specific APIs verifies server certificate.
/// Currently implemented to work on iOS, Android, and OS X.
/// </summary>
/// <param name="verifyCtx">Boost.ASIO context to get certificate chain from.</param>
/// <param name="hostName">Host name from the URI.</param>
/// <returns>True if verification passed and server can be trusted, false otherwise.</returns>
bool verify_cert_chain_platform_specific(boost::asio::ssl::verify_context &verifyCtx, const std::string &hostName);
bool verify_cert_chain_platform_specific(boost::asio::ssl::verify_context &verifyCtx, const std::string &hostName, const CertificateChainFunction& func = nullptr);

bool verify_X509_cert_chain(const std::vector<std::string> &certChain, const std::string &hostName, const CertificateChainFunction& func = nullptr);

std::vector<std::vector<unsigned char>> get_X509_cert_chain_encoded_data(boost::asio::ssl::verify_context &verifyCtx);

}}}}

#endif
#endif
24 changes: 23 additions & 1 deletion Release/include/cpprest/http_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ typedef void* native_handle;}}}
#include "cpprest/http_msg.h"
#include "cpprest/json.h"
#include "cpprest/uri.h"
#include "cpprest/certificate_info.h"
#include "cpprest/details/web_utilities.h"
#include "cpprest/details/basic_types.h"
#include "cpprest/asyncrt_utils.h"
Expand Down Expand Up @@ -87,6 +88,7 @@ class http_client_config
#if !defined(__cplusplus_winrt)
, m_validate_certificates(true)
#endif
, m_certificate_chain_callback([](const std::shared_ptr<certificate_info>&)->bool { return true; })
#if !defined(_WIN32) && !defined(__cplusplus_winrt) || defined(CPPREST_FORCE_HTTP_CLIENT_ASIO)
, m_tlsext_sni_enabled(true)
#endif
Expand Down Expand Up @@ -349,7 +351,7 @@ class http_client_config
/// <param name="callback">A user callback allowing for customization of the request</param>
void set_nativehandle_options(const std::function<void(native_handle)> &callback)
{
m_set_user_nativehandle_options = callback;
m_set_user_nativehandle_options = callback;
}

/// <summary>
Expand All @@ -362,6 +364,25 @@ class http_client_config
m_set_user_nativehandle_options(handle);
}


/// <summary>
/// Set the certificate chain callback. If set, HTTP client will call this callback in a blocking manner during HTTP connection.
/// </summary>
void set_user_certificate_chain_callback(const CertificateChainFunction& callback)
{
m_certificate_chain_callback = callback;
}

/// <summary>
/// Invokes the certificate chain callback.
/// </summary>
/// <param name="certificate_info">Pointer to the certificate_info struct that has the certificate information.</param>
/// <returns>True if the consumer code allows the connection, false otherwise. False will terminate the HTTP connection.</returns>
bool invoke_certificate_chain_callback(const std::shared_ptr<certificate_info>& certificate_Info) const
{
return m_certificate_chain_callback(certificate_Info);
}

#if !defined(_WIN32) && !defined(__cplusplus_winrt) || defined(CPPREST_FORCE_HTTP_CLIENT_ASIO)
/// <summary>
/// Sets a callback to enable custom setting of the ssl context, at construction time.
Expand Down Expand Up @@ -420,6 +441,7 @@ class http_client_config
bool m_validate_certificates;
#endif

CertificateChainFunction m_certificate_chain_callback;
std::function<void(native_handle)> m_set_user_nativehandle_options;
std::function<void(native_handle)> m_set_user_nativesessionhandle_options;

Expand Down
17 changes: 16 additions & 1 deletion Release/include/cpprest/oauth2.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#define _CASA_OAUTH2_H

#include "cpprest/http_msg.h"
#include "cpprest/certificate_info.h"
#include "cpprest/details/web_utilities.h"

namespace web
Expand Down Expand Up @@ -216,7 +217,8 @@ class oauth2_config
m_implicit_grant(false),
m_bearer_auth(true),
m_http_basic_auth(true),
m_access_token_key(details::oauth2_strings::access_token)
m_access_token_key(details::oauth2_strings::access_token),
m_certificate_chain_callback([](const std::shared_ptr<web::http::client::certificate_info>&)->bool { return true; })
{}

/// <summary>
Expand Down Expand Up @@ -467,6 +469,17 @@ class oauth2_config
/// </summary>
void set_user_agent(utility::string_t user_agent) { m_user_agent = std::move(user_agent); }

/// <summary>
/// Set the certificate chain callback to be used by the http client.
/// </summary>
void set_user_certificate_chain_callback(const web::http::client::CertificateChainFunction& callback) { m_certificate_chain_callback = callback; }

/// <summary>
/// Get the cert chain callback.
/// </summary>
/// <returns>A reference to cert chain callback user by the client.</returns>
const web::http::client::CertificateChainFunction& user_certificate_chain_callback() { return m_certificate_chain_callback; }

private:
friend class web::http::client::http_client_config;
friend class web::http::oauth2::details::oauth2_handler;
Expand Down Expand Up @@ -514,6 +527,8 @@ class oauth2_config
oauth2_token m_token;

utility::nonce_generator m_state_generator;

web::http::client::CertificateChainFunction m_certificate_chain_callback;
};

} // namespace web::http::oauth2::experimental
Expand Down
21 changes: 21 additions & 0 deletions Release/include/cpprest/ws_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@

#include "pplx/pplxtasks.h"
#include "cpprest/uri.h"
#include "cpprest/certificate_info.h"
#include "cpprest/details/web_utilities.h"
#include "cpprest/http_headers.h"
#include "cpprest/json.h"
#include "cpprest/asyncrt_utils.h"
#include "cpprest/ws_msg.h"

Expand Down Expand Up @@ -73,6 +75,7 @@ class websocket_client_config
/// Creates a websocket client configuration with default settings.
/// </summary>
websocket_client_config() :
m_certificate_chain_callback([](const std::shared_ptr<http::client::certificate_info>&)->bool { return true; }),
m_sni_enabled(true),
m_validate_certificates(true)
{
Expand Down Expand Up @@ -205,13 +208,31 @@ class websocket_client_config
m_validate_certificates = validate_certs;
}

/// Set the certificate chain callback. If set, HTTP client will call this callback in a blocking manner during HTTP connection.
/// </summary>
void set_user_certificate_chain_callback(const http::client::CertificateChainFunction& callback)
{
m_certificate_chain_callback = callback;
}

/// <summary>
/// Invokes the certificate chain callback.
/// </summary>
/// <param name="certificate_info">Pointer to the certificate_info struct that has the certificate information.</param>
/// <returns>True if the consumer code allows the connection, false otherwise. False will terminate the HTTP connection.</returns>
bool invoke_certificate_chain_callback(const std::shared_ptr<http::client::certificate_info>& certificate_Info) const
{
return m_certificate_chain_callback(certificate_Info);
}

private:
web::web_proxy m_proxy;
web::credentials m_credentials;
web::http::http_headers m_headers;
bool m_sni_enabled;
utf8string m_sni_hostname;
bool m_validate_certificates;
http::client::CertificateChainFunction m_certificate_chain_callback;
};

/// <summary>
Expand Down
30 changes: 27 additions & 3 deletions Release/src/http/client/http_client_asio.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

/***
* Copyright (C) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
Expand Down Expand Up @@ -44,6 +45,7 @@
#include "cpprest/base_uri.h"
#include "cpprest/details/x509_cert_utilities.h"
#include "cpprest/details/http_helpers.h"
#include "cpprest/certificate_info.h"
#include <unordered_set>
#include <memory>

Expand Down Expand Up @@ -975,6 +977,9 @@ class asio_context : public request_context, public std::enable_shared_from_this
// finally by the root CA self signed certificate.

const auto &host = utility::conversions::to_utf8string(m_http_client->base_uri().host());

using namespace web::http::client::details;

#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
Expand All @@ -986,12 +991,31 @@ class asio_context : public request_context, public std::enable_shared_from_this
}
if(m_openssl_failed)
{
return verify_cert_chain_platform_specific(verifyCtx, host);
}

if (!is_end_certificate_in_chain(verifyCtx))
{
// Continue until we get the end certificate.
return true;
}

auto chainFunc = [this](const std::shared_ptr<certificate_info>& cert_info) {
return m_http_client->client_config().invoke_certificate_chain_callback(cert_info);
};

return http::client::details::verify_cert_chain_platform_specific(verifyCtx, utility::conversions::to_utf8string(host), chainFunc);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code cosmetics: indentation of closing bracket

#endif

boost::asio::ssl::rfc2818_verification rfc2818(host);
return rfc2818(preverified, verifyCtx);
if(!rfc2818(preverified, verifyCtx))
{
return false;
}

auto info = std::make_shared<certificate_info>(host, get_X509_cert_chain_encoded_data(verifyCtx));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does currently not compile on Linux because get_X509_cert_chain_encoded_data() is not defined (due to some #ifdef). @chogorma Please refer to our PR to your private repository.

info->verified = true;

return m_http_client->client_config().invoke_certificate_chain_callback(info);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Linux this is called several times. I'm not exactly sure why, but I suppose it is due to the way OpenSSL handles certificate chains (cf. comments in this method). Do we need a similar workaround as for macOS and Android above?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you would need to do the same flow on Linux as per the macOS flow above.

The "is_end_certificate_in_chain" check above should allow the connection until we get the end certificate, once we get that, then we get the OS to build the full cert chain, only after that, then we do the cert-pinning.

If you just wait for the end certificate and don't build the full chain from the OS, you will only have the certs from the SSL handshake, and more than likely that will not be the full chain.

}

void handle_write_headers(const boost::system::error_code& ec)
Expand Down
2 changes: 2 additions & 0 deletions Release/src/http/client/http_client_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ class request_context
// Registration for cancellation notification if enabled.
pplx::cancellation_token_registration m_cancellationRegistration;

bool m_certificate_chain_verification_failed{ false };

protected:

request_context(const std::shared_ptr<_http_client_communicator> &client, const http_request &request);
Expand Down
Loading