From da15baa05df83f38e9faa03e1e54355028b72615 Mon Sep 17 00:00:00 2001 From: Christof Arnosti Date: Tue, 19 Sep 2017 16:27:27 +0200 Subject: [PATCH 1/3] - Enable Client Side SSL Authentication --- example/https_client/source/client_auth.cpp | 81 +++++++++++++++++++ source/corvusoft/restbed/detail/http_impl.cpp | 65 ++++++++++++--- .../corvusoft/restbed/detail/service_impl.cpp | 5 ++ .../restbed/detail/ssl_settings_impl.hpp | 6 ++ source/corvusoft/restbed/ssl_settings.cpp | 30 +++++++ source/corvusoft/restbed/ssl_settings.hpp | 12 +++ 6 files changed, 189 insertions(+), 10 deletions(-) create mode 100644 example/https_client/source/client_auth.cpp diff --git a/example/https_client/source/client_auth.cpp b/example/https_client/source/client_auth.cpp new file mode 100644 index 00000000..2579b8a1 --- /dev/null +++ b/example/https_client/source/client_auth.cpp @@ -0,0 +1,81 @@ +/* + * Example illustrating a HTTPS client authentication + * + * 0. Generate a CA + * mkdir -p /tmp/ssl/{client,server,CA} + * openssl genrsa -out /tmp/ssl/CA/ca.key 4096 + * openssl req -x509 -new -nodes -key /tmp/ssl/CA/ca.key -sha256 -days 1024 -out /tmp/ssl/CA/ca.pem -subj '/C=US/ST=Oregon/L=Portland/CN=FAKE ROOT' + * ln -s /tmp/ssl/CA/ca.pem /tmp/ssl/CA/`openssl x509 -hash -in /tmp/ssl/CA/ca.pem -noout`.0 + * + * 1. genereate key and certificate: + * + * openssl req -nodes -new -newkey rsa:2048 -keyout /tmp/ssl/client/key.key -out /tmp/ssl/client/certificate.csr -subj '/C=US/ST=Oregon/L=Portland/CN=Client' + * + * openssl req -nodes -new -newkey rsa:2048 -keyout /tmp/ssl/server/key.key -out /tmp/ssl/server/certificate.csr -subj '/C=US/ST=Oregon/L=Portland/CN=localhost' + * + * openssl x509 -req -in /tmp/ssl/client/certificate.csr -CA /tmp/ssl/CA/ca.pem -CAkey /tmp/ssl/CA/ca.key -CAcreateserial -days 500 -sha256 -out /tmp/ssl/client/certificate.crt + * + * openssl x509 -req -in /tmp/ssl/server/certificate.csr -CA /tmp/ssl/CA/ca.pem -CAkey /tmp/ssl/CA/ca.key -CAcreateserial -days 500 -sha256 -out /tmp/ssl/server/certificate.crt + * + * openssl dhparam 2048 -out /tml/ssl/client/dhparam.pem -outform PEM + * + * + * 2. start openssl debug server + * openssl s_server -accept 8080 -www -cert /tmp/ssl/server/certificate.crt -key /tmp/ssl/server/key.key -CApath /tmp/ssl/CA/ -tls1 -verify 2 + * + * Usage: + * ./distribution/example/https_client_authentication + */ + +#include +#include +#include +#include + +using namespace std; +using namespace restbed; + +int main( const int, const char** ) +{ + auto request = make_shared< Request >( Uri( "https://localhost:8080" ) ); + request->set_header( "Accept", "*/*" ); + request->set_header( "Host", "localhost" ); + + auto ssl_settings = make_shared< SSLSettings >( ); + ssl_settings->set_client_authentication_enabled( true ); + ssl_settings->set_private_key( Uri( "file:///tmp/ssl/client/key.key" ) ); + ssl_settings->set_certificate( Uri( "file:///tmp/ssl/client/certificate.crt" ) ); + ssl_settings->set_certificate_authority_pool( Uri( "file:///tmp/ssl/CA/" ) ); + + auto settings = make_shared< Settings >( ); + settings->set_ssl_settings( ssl_settings ); + + auto response = Http::sync( request, settings ); + + fprintf( stderr, "*** Response ***\n" ); + fprintf( stderr, "Status Code: %i\n", response->get_status_code( ) ); + fprintf( stderr, "Status Message: %s\n", response->get_status_message( ).data( ) ); + fprintf( stderr, "HTTP Version: %.1f\n", response->get_version( ) ); + fprintf( stderr, "HTTP Protocol: %s\n", response->get_protocol( ).data( ) ); + + for ( const auto header : response->get_headers( ) ) + { + fprintf( stderr, "Header '%s' > '%s'\n", header.first.data( ), header.second.data( ) ); + } + + if ( response->has_header( "Transfer-Encoding" ) ) + { + Http::fetch( "\r\n", response ); + } + else + { + auto length = 0; + response->get_header( "Content-Length", length ); + + Http::fetch( length, response ); + } + + fprintf( stderr, "Body: %.*s...\n", 3, response->get_body( ).data( ) ); + + return EXIT_SUCCESS; +} diff --git a/source/corvusoft/restbed/detail/http_impl.cpp b/source/corvusoft/restbed/detail/http_impl.cpp index 6091327a..c1e27a3b 100644 --- a/source/corvusoft/restbed/detail/http_impl.cpp +++ b/source/corvusoft/restbed/detail/http_impl.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017, Corvusoft Ltd, All Rights Reserved. + * Copyright 2013-2016, Corvusoft Ltd, All Rights Reserved. */ //System Includes @@ -9,6 +9,7 @@ #include #include #include +#include //Project Includes #include "corvusoft/restbed/uri.hpp" @@ -171,27 +172,71 @@ namespace restbed if ( settings not_eq nullptr ) { - const auto pool = settings->get_certificate_authority_pool( ); - - if ( pool.empty( ) ) + if ( settings->has_enabled_client_authentication( ) ) + { + auto filename = settings->get_certificate_chain( ); + + if ( not filename.empty( ) ) + { + context.use_certificate_chain_file( filename ); + } + + filename = settings->get_certificate( ); + + if ( not filename.empty( ) ) + { + context.use_certificate_file( filename, asio::ssl::context::pem ); + } + + filename = settings->get_private_key( ); + + if ( not filename.empty( ) ) + { + context.use_private_key_file( filename, asio::ssl::context::pem ); + } + + filename = settings->get_private_rsa_key( ); + + if ( not filename.empty( ) ) + { + context.use_rsa_private_key_file( filename, asio::ssl::context::pem ); + } + } + + if ( settings->has_enabled_server_authentication( ) ) { - context.set_default_verify_paths( ); + const auto pool = settings->get_certificate_authority_pool( ); + + if ( pool.empty( ) ) + { + context.set_default_verify_paths( ); + } + else + { + context.add_verify_path( pool ); + } + socket = make_shared< asio::ssl::stream< asio::ip::tcp::socket > >( *request->m_pimpl->m_io_service, context ); + socket->set_verify_mode( asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert ); } else { - context.add_verify_path( settings->get_certificate_authority_pool( ) ); + socket = make_shared< asio::ssl::stream< asio::ip::tcp::socket > >( *request->m_pimpl->m_io_service, context ); + socket->set_verify_mode( asio::ssl::verify_none ); } - - socket = make_shared< asio::ssl::stream< asio::ip::tcp::socket > >( *request->m_pimpl->m_io_service, context ); - socket->set_verify_mode( asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert ); } else { socket = make_shared< asio::ssl::stream< asio::ip::tcp::socket > >( *request->m_pimpl->m_io_service, context ); socket->set_verify_mode( asio::ssl::verify_none ); } - + SSL_set_renegotiate_mode( socket->native_handle(), ssl_renegotiate_once ); + + if( ! settings->get_cipher_suites().empty() ) + { + SSL_set_cipher_list( socket->native_handle(), settings->get_cipher_suites().c_str() ); + } socket->set_verify_callback( asio::ssl::rfc2818_verification( request->get_host( ) ) ); + // TODO implement ssl pinning request->m_pimpl->m_socket = make_shared< SocketImpl >( socket ); } #endif diff --git a/source/corvusoft/restbed/detail/service_impl.cpp b/source/corvusoft/restbed/detail/service_impl.cpp index d04bf689..ad0d3afe 100644 --- a/source/corvusoft/restbed/detail/service_impl.cpp +++ b/source/corvusoft/restbed/detail/service_impl.cpp @@ -239,6 +239,11 @@ namespace restbed { m_ssl_context->use_rsa_private_key_file( filename, asio::ssl::context::pem ); } + + if ( m_ssl_settings->has_enabled_client_authentication( ) ) + { + m_ssl_context->set_verify_mode ( asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert ); + } asio::ssl::context::options options = 0; options = ( m_ssl_settings->has_enabled_tlsv1( ) ) ? options : options | asio::ssl::context::no_tlsv1; diff --git a/source/corvusoft/restbed/detail/ssl_settings_impl.hpp b/source/corvusoft/restbed/detail/ssl_settings_impl.hpp index b36eef8e..ef39df6b 100644 --- a/source/corvusoft/restbed/detail/ssl_settings_impl.hpp +++ b/source/corvusoft/restbed/detail/ssl_settings_impl.hpp @@ -50,6 +50,10 @@ namespace restbed bool m_single_diffie_hellman_use_enabled = true; + bool m_client_authentication_enabled = false; + + bool m_server_authentication_enabled = true; + std::string m_bind_address = ""; std::string m_passphrase = ""; @@ -65,6 +69,8 @@ namespace restbed std::string m_certificate_authority_pool = ""; std::string m_temporary_diffie_hellman = ""; + + std::string m_cipher_suites = ""; }; } } diff --git a/source/corvusoft/restbed/ssl_settings.cpp b/source/corvusoft/restbed/ssl_settings.cpp index 226d9423..7779e890 100644 --- a/source/corvusoft/restbed/ssl_settings.cpp +++ b/source/corvusoft/restbed/ssl_settings.cpp @@ -77,6 +77,16 @@ namespace restbed { return m_pimpl->m_single_diffie_hellman_use_enabled; } + + bool SSLSettings::has_enabled_server_authentication ( void ) const + { + return m_pimpl->m_server_authentication_enabled; + } + + bool SSLSettings::has_enabled_client_authentication( void ) const + { + return m_pimpl->m_client_authentication_enabled; + } uint16_t SSLSettings::get_port( void ) const { @@ -123,6 +133,11 @@ namespace restbed return m_pimpl->m_certificate_authority_pool; } + string SSLSettings::get_cipher_suites( void ) const + { + return m_pimpl->m_cipher_suites; + } + void SSLSettings::set_port( const uint16_t value ) { m_pimpl->m_port = value; @@ -192,6 +207,16 @@ namespace restbed { m_pimpl->m_certificate_authority_pool = String::remove( "file://", value.to_string( ), String::CASE_INSENSITIVE ); } + + void SSLSettings::set_client_authentication_enabled( const bool value ) + { + m_pimpl->m_client_authentication_enabled = value; + } + + void SSLSettings::set_server_authentication_enabled( const bool value ) + { + m_pimpl->m_server_authentication_enabled = value; + } void SSLSettings::set_passphrase( const string& value ) { @@ -212,4 +237,9 @@ namespace restbed { m_pimpl->m_temporary_diffie_hellman = String::remove( "file://", value.to_string( ), String::CASE_INSENSITIVE ); } + + void SSLSettings::set_cipher_suites( const std::string ciphersuites ) + { + m_pimpl->m_cipher_suites = ciphersuites; + } } diff --git a/source/corvusoft/restbed/ssl_settings.hpp b/source/corvusoft/restbed/ssl_settings.hpp index 996e5b7e..6fa11fb3 100644 --- a/source/corvusoft/restbed/ssl_settings.hpp +++ b/source/corvusoft/restbed/ssl_settings.hpp @@ -60,6 +60,10 @@ namespace restbed bool has_enabled_default_workarounds( void ) const; bool has_enabled_single_diffie_hellman_use( void ) const; + + bool has_enabled_client_authentication( void ) const; + + bool has_enabled_server_authentication( void ) const; //Getters uint16_t get_port( void ) const; @@ -79,6 +83,8 @@ namespace restbed std::string get_temporary_diffie_hellman( void ) const; std::string get_certificate_authority_pool( void ) const; + + std::string get_cipher_suites( void ) const; //Setters void set_port( const uint16_t value ); @@ -108,6 +114,10 @@ namespace restbed void set_certificate_chain( const Uri& value ); void set_certificate_authority_pool( const Uri& value ); + + void set_client_authentication_enabled( const bool value ); + + void set_server_authentication_enabled( const bool value ); void set_passphrase( const std::string& value ); @@ -116,6 +126,8 @@ namespace restbed void set_private_rsa_key( const Uri& value ); void set_temporary_diffie_hellman( const Uri& value ); + + void set_cipher_suites( const std::string ciphersuites ); //Operators From 252f02b171efe13cd886400ba40c345b300a620d Mon Sep 17 00:00:00 2001 From: Christof Arnosti Date: Tue, 19 Sep 2017 16:31:49 +0200 Subject: [PATCH 2/3] Cleanup and guarding --- source/corvusoft/restbed/detail/http_impl.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/source/corvusoft/restbed/detail/http_impl.cpp b/source/corvusoft/restbed/detail/http_impl.cpp index c1e27a3b..2293a71a 100644 --- a/source/corvusoft/restbed/detail/http_impl.cpp +++ b/source/corvusoft/restbed/detail/http_impl.cpp @@ -9,7 +9,6 @@ #include #include #include -#include //Project Includes #include "corvusoft/restbed/uri.hpp" @@ -30,6 +29,7 @@ #ifdef BUILD_SSL #include + #include #endif //System Namespaces @@ -229,14 +229,15 @@ namespace restbed socket = make_shared< asio::ssl::stream< asio::ip::tcp::socket > >( *request->m_pimpl->m_io_service, context ); socket->set_verify_mode( asio::ssl::verify_none ); } - SSL_set_renegotiate_mode( socket->native_handle(), ssl_renegotiate_once ); - +#ifdef OPENSSL_IS_BORINGSSL + SSL_set_renegotiate_mode( socket->native_handle(), ssl_renegotiate_once ); // In BoringSSL, renegotiation is disabled by default. With this line we enable it for one time to allow client side authentication +#endif if( ! settings->get_cipher_suites().empty() ) { SSL_set_cipher_list( socket->native_handle(), settings->get_cipher_suites().c_str() ); } socket->set_verify_callback( asio::ssl::rfc2818_verification( request->get_host( ) ) ); - // TODO implement ssl pinning + request->m_pimpl->m_socket = make_shared< SocketImpl >( socket ); } #endif From 289c18a923810e5a06aa4f28b0603be6aefb27d6 Mon Sep 17 00:00:00 2001 From: Christof Arnosti Date: Mon, 13 May 2019 16:46:41 +0200 Subject: [PATCH 3/3] Update CMakeLists.txt --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index df8b5367..2eb9a8d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,14 +29,14 @@ message( " ${Blue}Copyright 2013-2017, Corvusoft Ltd, All Rights Reserved.${Re set( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/modules" ) find_package( asio REQUIRED ) -include_directories( SYSTEM ${asio_INCLUDE} ) +include_directories( ${asio_INCLUDE} ) find_package( kashmir REQUIRED ) -include_directories( SYSTEM ${kashmir_INCLUDE} ) +include_directories( ${kashmir_INCLUDE} ) if ( BUILD_SSL ) find_package( openssl REQUIRED ) - include_directories( SYSTEM ${ssl_INCLUDE} ) + include_directories( ${ssl_INCLUDE} ) endif ( ) #