diff --git a/include/eepp/ee.hpp b/include/eepp/ee.hpp index 84dd4fc88..1d849a5da 100755 --- a/include/eepp/ee.hpp +++ b/include/eepp/ee.hpp @@ -87,6 +87,7 @@ // Network #include using namespace EE::Network; + using namespace EE::Network::SSL; // UI #include diff --git a/include/eepp/network.hpp b/include/eepp/network.hpp index 99becf109..e70e79350 100644 --- a/include/eepp/network.hpp +++ b/include/eepp/network.hpp @@ -11,5 +11,6 @@ #include #include #include +#include #endif diff --git a/include/eepp/network/chttp.hpp b/include/eepp/network/chttp.hpp index d7d081fad..0ea18c417 100644 --- a/include/eepp/network/chttp.hpp +++ b/include/eepp/network/chttp.hpp @@ -39,8 +39,10 @@ class EE_API cHttp : NonCopyable { ** URI ("/") and an empty body. ** @param uri Target URI ** @param method Method to use for the request - ** @param body Content of the request's body */ - Request(const std::string& uri = "/", Method method = Get, const std::string& body = ""); + ** @param body Content of the request's body + ** @param validateCertificate Enables certificate validation for https request + ** @param validateHostname Enables hostname validation for https request */ + Request(const std::string& uri = "/", Method method = Get, const std::string& body = "", bool validateCertificate = false, bool validateHostname = false ); /** @brief Set the value of a field ** The field is created if it doesn't exist. The name of @@ -48,7 +50,6 @@ class EE_API cHttp : NonCopyable { ** By default, a request doesn't contain any field (but the ** mandatory fields are added later by the HTTP client when ** sending the request). - /// ** @param field Name of the field to set ** @param value Value of the field */ void SetField(const std::string& field, const std::string& value); @@ -82,6 +83,18 @@ class EE_API cHttp : NonCopyable { /** @return The request Uri */ const std::string& GetUri() const; + + /** @return If SSL certificate validation is enabled */ + const bool& ValidateCertificate() const; + + /** Enable/disable SSL certificate validation */ + void ValidateCertificate( bool enable ); + + /** @return If SSL hostname validation is enabled */ + const bool& ValidateHostname() const; + + /** Enable/disable SSL hostname validation */ + void ValidateHostname( bool enable ); private: friend class cHttp; @@ -101,12 +114,14 @@ class EE_API cHttp : NonCopyable { typedef std::map FieldTable; // Member data - FieldTable mFields; ///< Fields of the header associated to their value - Method mMethod; ///< Method to use for the request - std::string mUri; ///< Target URI of the request - unsigned int mMajorVersion; ///< Major HTTP version - unsigned int mMinorVersion; ///< Minor HTTP version - std::string mBody; ///< Body of the request + FieldTable mFields; ///< Fields of the header associated to their value + Method mMethod; ///< Method to use for the request + std::string mUri; ///< Target URI of the request + unsigned int mMajorVersion; ///< Major HTTP version + unsigned int mMinorVersion; ///< Minor HTTP version + std::string mBody; ///< Body of the request + bool mValidateCertificate; ///< Validates the SSL certificate in case of an HTTPS request + bool mValidateHostname; ///< Validates the hostname in case of an HTTPS request }; /** @brief Define a HTTP response */ @@ -292,6 +307,7 @@ class EE_API cHttp : NonCopyable { unsigned short mPort; ///< Port used for connection with host std::list mThreads; cMutex mThreadsMutex; + bool mIsSSL; void RemoveOldThreads(); }; diff --git a/include/eepp/network/csocket.hpp b/include/eepp/network/csocket.hpp index 7ab19f5d2..71a9546d3 100644 --- a/include/eepp/network/csocket.hpp +++ b/include/eepp/network/csocket.hpp @@ -79,7 +79,7 @@ protected : /** @brief Close the socket gracefully ** This function can only be accessed by derived classes. */ void Close(); -private : +protected : friend class cSocketSelector; // Member data Type mType; ///< Type of the socket (TCP or UDP) diff --git a/include/eepp/network/ctcpsocket.hpp b/include/eepp/network/ctcpsocket.hpp index 67c3aa351..a702ff251 100644 --- a/include/eepp/network/ctcpsocket.hpp +++ b/include/eepp/network/ctcpsocket.hpp @@ -48,13 +48,13 @@ class EE_API cTcpSocket : public cSocket { ** @param timeout Optional maximum time to wait ** @return Status code ** @see Disconnect */ - Status Connect(const cIpAddress& remoteAddress, unsigned short remotePort, cTime timeout = cTime::Zero); + virtual Status Connect(const cIpAddress& remoteAddress, unsigned short remotePort, cTime timeout = cTime::Zero); /** @brief Disconnect the socket from its remote peer ** This function gracefully closes the connection. If the ** socket is not connected, this function has no effect. ** @see Connect */ - void Disconnect(); + virtual void Disconnect(); /** @brief Send raw data to the remote peer ** This function will fail if the socket is not connected. @@ -62,7 +62,7 @@ class EE_API cTcpSocket : public cSocket { ** @param size Number of bytes to send ** @return Status code ** @see Receive */ - Status Send(const void* data, std::size_t size); + virtual Status Send(const void* data, std::size_t size); /** @brief Receive raw data from the remote peer ** In blocking mode, this function will wait until some @@ -73,14 +73,14 @@ class EE_API cTcpSocket : public cSocket { ** @param received This variable is filled with the actual number of bytes received ** @return Status code ** @see Send */ - Status Receive(void* data, std::size_t size, std::size_t& received); + virtual Status Receive(void* data, std::size_t size, std::size_t& received); /** @brief Send a formatted packet of data to the remote peer ** This function will fail if the socket is not connected. ** @param packet cPacket to send ** @return Status code ** @see Receive */ - Status Send(cPacket& packet); + virtual Status Send(cPacket& packet); /** @brief Receive a formatted packet of data from the remote peer ** In blocking mode, this function will wait until the whole packet @@ -89,7 +89,7 @@ class EE_API cTcpSocket : public cSocket { ** @param packet cPacket to fill with the received data ** @return Status code ** @see Send */ - Status Receive(cPacket& packet); + virtual Status Receive(cPacket& packet); private: @@ -100,7 +100,7 @@ class EE_API cTcpSocket : public cSocket { PendingPacket(); Uint32 Size; ///< Data of packet size - std::size_t SizeReceived; ///< Number of size bytes received so far + std::size_t SizeReceived; ///< Number of size bytes received so far std::vector Data; ///< Data of the packet }; diff --git a/include/eepp/network/ssl/csslsocket.hpp b/include/eepp/network/ssl/csslsocket.hpp new file mode 100644 index 000000000..ad2272772 --- /dev/null +++ b/include/eepp/network/ssl/csslsocket.hpp @@ -0,0 +1,49 @@ +#ifndef EE_NETWORKCSSLSOCKET_HPP +#define EE_NETWORKCSSLSOCKET_HPP + +#include + +namespace EE { namespace Network { namespace SSL { + +class cSSLSocketImpl; + +class EE_API cSSLSocket : public cTcpSocket { + public: + static std::string CertificatesPath; + + static bool Init(); + + static bool End(); + + cSSLSocket( std::string hostname, bool validateCertificate, bool validateHostname ); + + virtual ~cSSLSocket(); + + Status Connect(const cIpAddress& remoteAddress, unsigned short remotePort, cTime timeout = cTime::Zero); + + void Disconnect(); + + Status Send(const void* data, std::size_t size); + + Status Receive(void* data, std::size_t size, std::size_t& received); + + Status Send(cPacket& packet); + + Status Receive(cPacket& packet); + protected: + friend class cSSLSocketImpl; + friend class cOpenSSLSocket; + + cSSLSocketImpl * mImpl; + std::string mHostName; + bool mValidateCertificate; + bool mValidateHostname; + + Status TcpSend(const void* data, std::size_t size); + + Status TcpReceive(void* data, std::size_t size, std::size_t& received); +}; + +}}} + +#endif diff --git a/premake4.lua b/premake4.lua index a66bcc437..c3374248a 100644 --- a/premake4.lua +++ b/premake4.lua @@ -53,6 +53,17 @@ newplatform { } } +newplatform { + name = "clang-static-analyze", + description = "Clang static analysis build", + gcc = { + cc = "clang --analyze", + cxx = "clang++ --analyze", + ar = "ar", + cppflags = "-MMD" + } +} + newplatform { name = "emscripten", description = "Emscripten", @@ -126,6 +137,7 @@ if _OPTIONS.platform then premake.gcc.platforms['Native'] = premake.gcc.platforms[_OPTIONS.platform] end +newoption { trigger = "with-ssl", description = "Enables SSL support for the Network module ( requires OpenSSL )." } newoption { trigger = "with-libsndfile", description = "Build with libsndfile support." } newoption { trigger = "with-static-freetype", description = "Build freetype as a static library." } newoption { trigger = "with-static-eepp", description = "Force to build the demos and tests with eepp compiled statically" } @@ -617,6 +629,22 @@ function select_backend() end end +function check_ssl_support() + if _OPTIONS["with-ssl"] then + if os.is("windows") then + table.insert( link_list, get_backend_link_name( "libssl" ) ) + table.insert( link_list, get_backend_link_name( "libcrypto" ) ) + else + table.insert( link_list, get_backend_link_name( "ssl" ) ) + table.insert( link_list, get_backend_link_name( "crypto" ) ) + end + + files { "src/eepp/network/ssl/backend/openssl/*.cpp" } + + defines { "EE_SSL_SUPPORT", "EE_OPENSSL" } + end +end + function build_eepp( build_name ) includedirs { "include", "src", "src/eepp/helper/freetype2/include", "src/eepp/helper/zlib" } @@ -645,6 +673,7 @@ function build_eepp( build_name ) "src/eepp/window/*.cpp", "src/eepp/window/platform/null/*.cpp", "src/eepp/network/*.cpp", + "src/eepp/network/ssl/*.cpp", "src/eepp/ui/*.cpp", "src/eepp/ui/tools/*.cpp", "src/eepp/physics/*.cpp", @@ -653,6 +682,8 @@ function build_eepp( build_name ) "src/eepp/gaming/mapeditor/*.cpp" } + check_ssl_support() + select_backend() if not _OPTIONS["with-static-freetype"] and os_findlib("freetype") then diff --git a/projects/linux/ee.config b/projects/linux/ee.config index c500c5574..e065aa72b 100644 --- a/projects/linux/ee.config +++ b/projects/linux/ee.config @@ -11,3 +11,5 @@ #define EE_GL3_ENABLED 1 #define EE_SHADERS_SUPPORTED #define EE_GLEW_AVAILABLE +#define EE_SSL_SUPPORT +#define EE_OPENSSL diff --git a/projects/linux/ee.creator.user b/projects/linux/ee.creator.user index 676c3424d..86d908796 100644 --- a/projects/linux/ee.creator.user +++ b/projects/linux/ee.creator.user @@ -1,6 +1,6 @@ - + ProjectExplorer.Project.ActiveTarget @@ -56,13 +56,13 @@ {388e5431-b31b-42b3-b9ad-9002d279d75d} 0 0 - 0 + 11 ../../make/linux true - --with-static-backend gmake + --with-static-backend --with-ssl gmake premake4 %{buildDir}../../../ Custom Process Step @@ -151,7 +151,7 @@ true - --with-static-backend gmake + --with-static-backend --with-ssl gmake premake4 %{buildDir}../../../ Custom Process Step @@ -200,7 +200,7 @@ true - --with-static-backend gmake + --with-static-backend --with-ssl gmake premake4 %{buildDir}../../../ Custom Process Step @@ -609,7 +609,7 @@ true - --with-static-backend gmake + --with-static-backend --with-ssl gmake premake4 %{buildDir}../../../ Custom Process Step @@ -778,7 +778,7 @@ true - --with-static-backend gmake + --with-static-backend --with-ssl gmake premake4 %{buildDir}../../../ Custom Process Step diff --git a/projects/linux/ee.files b/projects/linux/ee.files index 8d0eff5b1..d3d83ffef 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -667,3 +667,10 @@ ../../src/eepp/graphics/renderer/shaders/basegl3cp.frag ../../src/eepp/window/backend/SDL/cbackendsdl.cpp ../../bin/assets/ee.ini +../../src/eepp/network/ssl/csslsocket.cpp +../../src/eepp/network/ssl/csslsocketimpl.hpp +../../src/eepp/network/ssl/backend/openssl/copensslsocket.hpp +../../src/eepp/network/ssl/backend/openssl/copensslsocket.cpp +../../include/eepp/network/ssl/csslsocket.hpp +../../src/eepp/network/ssl/backend/openssl/curl_hostcheck.cpp +../../src/eepp/network/ssl/backend/openssl/curl_hostcheck.h diff --git a/projects/mingw32/make.sh b/projects/mingw32/make.sh index bb41419d7..620123acb 100755 --- a/projects/mingw32/make.sh +++ b/projects/mingw32/make.sh @@ -1,6 +1,6 @@ #!/bin/sh cd $(dirname "$0") -premake4 --file=../../premake4.lua --os=windows --platform=mingw32 --with-static-freetype gmake +premake4 --file=../../premake4.lua --os=windows --platform=mingw32 --with-static-freetype --with-ssl gmake cd ../../make/mingw32/ make $@ diff --git a/src/eepp/network/chttp.cpp b/src/eepp/network/chttp.cpp index 183ce7909..263a5a228 100644 --- a/src/eepp/network/chttp.cpp +++ b/src/eepp/network/chttp.cpp @@ -1,10 +1,13 @@ #include +#include #include #include #include #include #include +using namespace EE::Network::SSL; + namespace { // Convert a string to lower case std::string toLower(std::string str) { @@ -16,7 +19,10 @@ namespace { namespace EE { namespace Network { -cHttp::Request::Request(const std::string& uri, Method method, const std::string& body) { +cHttp::Request::Request(const std::string& uri, Method method, const std::string& body, bool validateCertificate, bool validateHostname ) : + mValidateCertificate( validateCertificate ), + mValidateHostname( validateHostname ) +{ SetMethod(method); SetUri(uri); SetHttpVersion(1, 0); @@ -52,6 +58,22 @@ const std::string &cHttp::Request::GetUri() const { return mUri; } +const bool& cHttp::Request::ValidateCertificate() const { + return mValidateCertificate; +} + +void cHttp::Request::ValidateCertificate(bool enable) { + mValidateCertificate = enable; +} + +const bool &cHttp::Request::ValidateHostname() const { + return mValidateHostname; +} + +void cHttp::Request::ValidateHostname(bool enable) { + mValidateHostname = enable; +} + std::string cHttp::Request::Prepare() const { std::ostringstream out; @@ -211,12 +233,14 @@ void cHttp::Response::ParseFields(std::istream &in) { cHttp::cHttp() : mConnection( NULL ), mHost(), - mPort(0) + mPort(0), + mIsSSL( false ) { } cHttp::cHttp(const std::string& host, unsigned short port) : - mConnection( NULL ) + mConnection( NULL ), + mIsSSL( false ) { SetHost(host, port); } @@ -246,10 +270,15 @@ void cHttp::SetHost(const std::string& host, unsigned short port) { mHostName = host.substr(7); mPort = (port != 0 ? port : 80); } else if (toLower(host.substr(0, 8)) == "https://") { - // HTTPS protocol -- unsupported (requires encryption and certificates and stuff...) - eePRINTL( "HTTPS protocol is not supported by cHttp" ); - mHostName = ""; - mPort = 0; + // HTTPS protocol + #ifdef EE_SSL_SUPPORT + mIsSSL = true; + mHostName = host.substr(8); + mPort = (port != 0 ? port : 443); + #else + mHostName = ""; + mPort = 0; + #endif } else { // Undefined protocol - use HTTP mHostName = host; @@ -265,7 +294,7 @@ void cHttp::SetHost(const std::string& host, unsigned short port) { cHttp::Response cHttp::SendRequest(const cHttp::Request& request, cTime timeout) { if ( NULL == mConnection ) { - cTcpSocket * Conn = eeNew( cTcpSocket, () ); + cTcpSocket * Conn = mIsSSL ? eeNew( cSSLSocket, ( mHostName, request.ValidateCertificate(), request.ValidateHostname() ) ) : eeNew( cTcpSocket, () ); mConnection = Conn; } diff --git a/src/eepp/network/csocket.cpp b/src/eepp/network/csocket.cpp index e55b20246..5a5952673 100644 --- a/src/eepp/network/csocket.cpp +++ b/src/eepp/network/csocket.cpp @@ -15,7 +15,6 @@ cSocket::~cSocket() { Close(); } - void cSocket::SetBlocking(bool blocking) { // Apply if the socket is already created if (mSocket != Private::cSocketImpl::InvalidSocket()) diff --git a/src/eepp/network/ctcpsocket.cpp b/src/eepp/network/ctcpsocket.cpp index fac953ee5..68cadbc02 100644 --- a/src/eepp/network/ctcpsocket.cpp +++ b/src/eepp/network/ctcpsocket.cpp @@ -227,8 +227,7 @@ cSocket::Status cTcpSocket::Send(cPacket& packet) { return Send(&blockToSend[0], blockToSend.size()); } -cSocket::Status cTcpSocket::Receive(cPacket& packet) -{ +cSocket::Status cTcpSocket::Receive(cPacket& packet) { // First clear the variables to fill packet.Clear(); diff --git a/src/eepp/network/ssl/backend/openssl/copensslsocket.cpp b/src/eepp/network/ssl/backend/openssl/copensslsocket.cpp new file mode 100644 index 000000000..f26fed45b --- /dev/null +++ b/src/eepp/network/ssl/backend/openssl/copensslsocket.cpp @@ -0,0 +1,391 @@ +#include + +/** This implementation is based on the Godo Game Engine implementation ( https://github.com/okamstudio/godot ), MIT licensed. */ + +#ifdef EE_OPENSSL + +#include +#include +#include + +namespace EE { namespace Network { namespace SSL { + +static std::vector sCerts; + +bool cOpenSSLSocket::MatchHostname( const char * name, const char * hostname ) { + return Tool_Curl_cert_hostcheck( name, hostname )==CURL_HOST_MATCH; +} + +bool cOpenSSLSocket::MatchCommonName( const char * hostname, const X509 * server_cert ) { + int common_name_loc = -1; + X509_NAME_ENTRY *common_name_entry = NULL; + ASN1_STRING *common_name_asn1 = NULL; + char *common_name_str = NULL; + + // Find the position of the CN field in the Subject field of the certificate + common_name_loc = X509_NAME_get_index_by_NID( X509_get_subject_name( (X509 *) server_cert ), NID_commonName, -1 ); + + // Extract the CN field + common_name_entry = X509_NAME_get_entry( X509_get_subject_name( (X509 *) server_cert ), common_name_loc ); + + // Convert the CN field to a C string + common_name_asn1 = X509_NAME_ENTRY_get_data( common_name_entry ); + + common_name_str = (char *) ASN1_STRING_data( common_name_asn1 ); + + // Make sure there isn't an embedded NUL character in the CN + bool malformed_certificate = (size_t)ASN1_STRING_length( common_name_asn1 ) != strlen( common_name_str ); + + if ( malformed_certificate ) { + return false; + } + + // Compare expected hostname with the CN + return MatchHostname(common_name_str,hostname); +} + +/** Tries to find a match for hostname in the certificate's Subject Alternative Name extension. */ +bool cOpenSSLSocket::MatchSubjectAlternativeName( const char * hostname, const X509 * server_cert ) { + bool result = false; + int i; + int san_names_nb = -1; + STACK_OF(GENERAL_NAME) *san_names = NULL; + + // Try to extract the names within the SAN extension from the certificate + san_names = ( STACK_OF(GENERAL_NAME) *)X509_get_ext_d2i( (X509 *) server_cert, NID_subject_alt_name, NULL, NULL ); + + if ( san_names == NULL ) { + return false; + } + + san_names_nb = sk_GENERAL_NAME_num( san_names ); + + // Check each name within the extension + for ( i=0; i < san_names_nb; i++ ) { + const GENERAL_NAME * current_name = sk_GENERAL_NAME_value( san_names, i ); + + if ( current_name->type == GEN_DNS ) { + // Current name is a DNS name, let's check it + char * dns_name = (char *) ASN1_STRING_data( current_name->d.dNSName ); + + // Make sure there isn't an embedded NUL character in the DNS name + if ( (size_t)ASN1_STRING_length( current_name->d.dNSName ) != strlen( dns_name ) ) { + result = false; + break; + } + else { // Compare expected hostname with the DNS name + if ( MatchHostname( dns_name, hostname ) ) { + result = true; + break; + } + } + } + } + + sk_GENERAL_NAME_pop_free( san_names, GENERAL_NAME_free ); + + return result; +} + +int cOpenSSLSocket::CertVerifyCb( X509_STORE_CTX * x509_ctx, void * arg ) { + /* This is the function that OpenSSL would call if we hadn't called + * SSL_CTX_set_cert_verify_callback(). Therefore, we are "wrapping" + * the default functionality, rather than replacing it. */ + bool base_cert_valid = X509_verify_cert( x509_ctx ); + + if ( !base_cert_valid ) { + eePRINTL( "Cause: %s", ( X509_verify_cert_error_string( X509_STORE_CTX_get_error( x509_ctx ) ) ) ); + ERR_print_errors_fp( stdout ); + } + + X509 * server_cert = X509_STORE_CTX_get_current_cert( x509_ctx ); + + char cert_str[256]; + X509_NAME_oneline( X509_get_subject_name ( server_cert ), cert_str, sizeof ( cert_str ) ); + + eePRINTL( "CERT STR: %s", ( cert_str ) ); + eePRINTL( "VALID: %s", String::ToStr( base_cert_valid ).c_str() ); + + if ( !base_cert_valid ) { + return 0; + } + + cOpenSSLSocket * ssl = (cOpenSSLSocket *)arg; + + if ( ssl->mSSLSocket->mValidateHostname ) { + bool err = !MatchSubjectAlternativeName( ssl->mSSLSocket->mHostName.c_str(), server_cert ); + + if ( err ) { + err = !MatchCommonName( ssl->mSSLSocket->mHostName.c_str(), server_cert ); + } + + if ( err ) { + ssl->mStatus = cSocket::Error; + return 0; + } + } + + return 1; +} + +bool cOpenSSLSocket::Init() { + CRYPTO_malloc_init(); // Initialize malloc, free, etc for OpenSSL's use + + SSL_library_init(); // Initialize OpenSSL's SSL libraries + + SSL_load_error_strings(); // Load SSL error strings + + ERR_load_BIO_strings(); // Load BIO error strings + + OpenSSL_add_all_algorithms(); // Load all available encryption algorithms + + //! Load the certificates and config + if ( FileSystem::FileExists( cSSLSocket::CertificatesPath ) ) { + SafeDataPointer data; + FileSystem::FileGet( cSSLSocket::CertificatesPath, data ); + + if ( data.DataSize > 0 ) { + BIO* mem = BIO_new(BIO_s_mem()); + + BIO_puts( mem, (const char*) data.Data ); + + while( true ) { + X509 * cert = PEM_read_bio_X509(mem, NULL, 0, NULL); + + if (!cert) + break; + + sCerts.push_back(cert); + } + + BIO_free(mem); + } + + eePRINTL( "Loaded certs from '%s': %d", cSSLSocket::CertificatesPath.c_str(), (int)sCerts.size() ); + } + + return true; +} + +bool cOpenSSLSocket::End() { + if ( !sCerts.empty() ) { + for( size_t i = 0; i < sCerts.size(); i++ ) { + X509_free(sCerts[i]); + } + + sCerts.clear(); + } + + return true; +} + +cOpenSSLSocket::cOpenSSLSocket( cSSLSocket * socket ) : + cSSLSocketImpl( socket ), + mCTX( NULL ), + mSSL( NULL ), + mBIO( NULL ), + mConnected( false ), + mStatus( cSocket::Disconnected ), + mMaxCertChainDepth( 9 ) +{ + mSSLSocket = socket; +} + +cOpenSSLSocket::~cOpenSSLSocket() { + Disconnect(); +} + +cSocket::Status cOpenSSLSocket::Connect( const cIpAddress& remoteAddress, unsigned short remotePort, cTime timeout ) { + if ( mConnected ) { + Disconnect(); + } + + // Set up a SSL_CTX object, which will tell our BIO object how to do its work + mCTX = SSL_CTX_new( SSLv23_client_method() ); + + if ( mSSLSocket->mValidateCertificate ) { + if (!sCerts.empty()) { + //yay for undocumented OpenSSL functions + X509_STORE * store = SSL_CTX_get_cert_store( mCTX ); + + for( size_t i = 0; i < sCerts.size(); i++ ) { + X509_STORE_add_cert( store, sCerts[i] ); + } + } + + /* Ask OpenSSL to verify the server certificate. Note that this + * does NOT include verifying that the hostname is correct. + * So, by itself, this means anyone with any legitimate + * CA-issued certificate for any website, can impersonate any + * other website in the world. This is not good. See "The + * Most Dangerous Code in the World" article at + * https://crypto.stanford.edu/~dabo/pubs/abstracts/ssl-client-bugs.html + */ + SSL_CTX_set_verify( mCTX, SSL_VERIFY_PEER, NULL ); + + /* This is how we solve the problem mentioned in the previous + * comment. We "wrap" OpenSSL's validation routine in our + * own routine, which also validates the hostname by calling + * the code provided by iSECPartners. Note that even though + * the "Everything You've Always Wanted to Know About + * Certificate Validation With OpenSSL (But Were Afraid to + * Ask)" paper from iSECPartners says very explicitly not to + * call SSL_CTX_set_cert_verify_callback (at the bottom of + * page 2), what we're doing here is safe because our + * cert_verify_callback() calls X509_verify_cert(), which is + * OpenSSL's built-in routine which would have been called if + * we hadn't set the callback. Therefore, we're just + * "wrapping" OpenSSL's routine, not replacing it. */ + SSL_CTX_set_cert_verify_callback ( mCTX, CertVerifyCb, this ); + + //Let the verify_callback catch the verify_depth error so that we get an appropriate error in the logfile. (??) + SSL_CTX_set_verify_depth( mCTX, mMaxCertChainDepth + 1 ); + } + + mSSL = SSL_new( mCTX ); + /** + mBIO = BIO_new( &_bio_method ); + mBIO->ptr = this; + + SSL_set_bio( mSSL, mBIO, mBIO ); + */ + + SSL_set_fd( mSSL, (int)mSSLSocket->mSocket ); + + // Set the SSL to automatically retry on failure. + SSL_set_mode( mSSL , SSL_MODE_AUTO_RETRY ); + + mStatus = cSocket::Done; + + // Same as before, try to connect. + int result = SSL_connect( mSSL ); + + eePRINTL( "CONNECTION RESULT: %s", String::ToStr(result).c_str() ); + + if ( result < 1 ) { + ERR_print_errors_fp(stdout); + + _print_error(result); + + mStatus = cSocket::Error; + + return mStatus; + } + + X509 * peer = SSL_get_peer_certificate( mSSL ); + + if ( peer ) { + bool cert_ok = SSL_get_verify_result(mSSL) == X509_V_OK; + + eePRINTL( "cert_ok: %s", String::ToStr(cert_ok).c_str() ); + + mStatus = cSocket::Done; + } else if ( mSSLSocket->mValidateCertificate ) { + mStatus = cSocket::Error; + } + + if ( mStatus == cSocket::Done ) { + mConnected = true; + } + + return mStatus; +} + +void cOpenSSLSocket::Disconnect() { + if (!mConnected) + return; + + SSL_shutdown( mSSL ); + SSL_free( mSSL ); + SSL_CTX_free( mCTX ); + + mSSL = NULL; + mCTX = NULL; + + mConnected = false; + mStatus = cSocket::Disconnected; +} + +void cOpenSSLSocket::_print_error(int err) { + err = SSL_get_error(mSSL,err); + + switch(err) { + case SSL_ERROR_NONE: eePRINTL("NO ERROR: The TLS/SSL I/O operation completed"); break; + case SSL_ERROR_ZERO_RETURN: eePRINTL("The TLS/SSL connection has been closed."); + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + eePRINTL("The operation did not complete."); break; + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + eePRINTL("The connect/accept operation did not complete"); break; + case SSL_ERROR_WANT_X509_LOOKUP: + eePRINTL("The operation did not complete because an application callback set by SSL_CTX_set_client_cert_cb() has asked to be called again."); break; + case SSL_ERROR_SYSCALL: + eePRINTL("Some I/O error occurred. The OpenSSL error queue may contain more information on the error."); break; + case SSL_ERROR_SSL: + eePRINTL("A failure in the SSL library occurred, usually a protocol error."); break; + } +} + +cSocket::Status cOpenSSLSocket::Send( const void * data, std::size_t size ) { + Uint8 * buf = (Uint8*)data; + + while( size > 0 ) { + int ret = SSL_write( mSSL, buf, size ); + + if ( ret <= 0 ) { + _print_error(ret); + + Disconnect(); + + return cSocket::Disconnected; + } + + buf+=ret; + size-=ret; + } + + return cSocket::Done; +} + +cSocket::Status cOpenSSLSocket::Receive( void * data, std::size_t size, std::size_t& received ) { + if ( size==0 ) { + received = 0; + return cSocket::Done; + } + + size_t iniSize = size; + + Uint8 * buf = (Uint8*)data; + + while( size > 0 ) { + int ret = SSL_read( mSSL, buf, size ); + + if ( ret < 0 ) { + _print_error(ret); + + Disconnect(); + + return cSocket::Disconnected; + } else if ( 0 == ret ) { + if ( size == iniSize ) { + return cSocket::Disconnected; + } + + received = iniSize - size; + + return cSocket::Done; + } + + buf+=ret; + size-=ret; + } + + received = iniSize; + + return cSocket::Done; +} + +}}} + +#endif diff --git a/src/eepp/network/ssl/backend/openssl/copensslsocket.hpp b/src/eepp/network/ssl/backend/openssl/copensslsocket.hpp new file mode 100644 index 000000000..f7edff827 --- /dev/null +++ b/src/eepp/network/ssl/backend/openssl/copensslsocket.hpp @@ -0,0 +1,68 @@ +#ifndef EE_NETWORKCOPENLSSLSOCKET_HPP +#define EE_NETWORKCOPENLSSLSOCKET_HPP + +#include + +#ifdef EE_OPENSSL + +extern "C" { +#if EE_PLATFORM == EE_PLATFORM_WIN +#undef X509_NAME +#undef X509_EXTENSIONS +#undef X509_CERT_PAIR +#undef PKCS7_ISSUER_AND_SERIAL +#undef OCSP_REQUEST +#undef OCSP_RESPONSE +#define NOCRYPT +#endif +#include // BIO objects for I/O +#include // SSL and SSL_CTX for SSL connections +#include // Error reporting +#include +} + +namespace EE { namespace Network { namespace SSL { + +class cOpenSSLSocket : public cSSLSocketImpl { + public: + static bool Init(); + + static bool End(); + + cOpenSSLSocket( cSSLSocket * socket ); + + ~cOpenSSLSocket(); + + cSocket::Status Connect(const cIpAddress& remoteAddress, unsigned short remotePort, cTime timeout = cTime::Zero); + + void Disconnect(); + + cSocket::Status Send(const void* data, std::size_t size); + + cSocket::Status Receive(void* data, std::size_t size, std::size_t& received); + protected: + SSL_CTX * mCTX; + ::SSL * mSSL; + BIO * mBIO; + cSSLSocket * mSSLSocket; + bool mConnected; + cSocket::Status mStatus; + int mMaxCertChainDepth; + + private: + static int CertVerifyCb(X509_STORE_CTX *x509_ctx, void *arg); + + static bool MatchHostname(const char *name, const char *hostname); + + static bool MatchCommonName(const char *hostname, const X509 *server_cert); + + static bool MatchSubjectAlternativeName(const char *hostname, const X509 *server_cert); + + void _print_error(int err); +}; + +}}} + +#endif + +#endif diff --git a/src/eepp/network/ssl/backend/openssl/curl_hostcheck.cpp b/src/eepp/network/ssl/backend/openssl/curl_hostcheck.cpp new file mode 100644 index 000000000..8f3884623 --- /dev/null +++ b/src/eepp/network/ssl/backend/openssl/curl_hostcheck.cpp @@ -0,0 +1,221 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +/* This file is an amalgamation of hostcheck.c and most of rawstr.c + from cURL. The contents of the COPYING file mentioned above are: + +COPYRIGHT AND PERMISSION NOTICE + +Copyright (c) 1996 - 2013, Daniel Stenberg, . + +All rights reserved. + +Permission to use, copy, modify, and distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright +notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall not +be used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization of the copyright holder. +*/ + +#ifdef EE_OPENSSL + +#include "curl_hostcheck.h" +#include + +/* Portable, consistent toupper (remember EBCDIC). Do not use toupper() because + its behavior is altered by the current locale. */ +static char Curl_raw_toupper(char in) +{ + switch (in) { + case 'a': + return 'A'; + case 'b': + return 'B'; + case 'c': + return 'C'; + case 'd': + return 'D'; + case 'e': + return 'E'; + case 'f': + return 'F'; + case 'g': + return 'G'; + case 'h': + return 'H'; + case 'i': + return 'I'; + case 'j': + return 'J'; + case 'k': + return 'K'; + case 'l': + return 'L'; + case 'm': + return 'M'; + case 'n': + return 'N'; + case 'o': + return 'O'; + case 'p': + return 'P'; + case 'q': + return 'Q'; + case 'r': + return 'R'; + case 's': + return 'S'; + case 't': + return 'T'; + case 'u': + return 'U'; + case 'v': + return 'V'; + case 'w': + return 'W'; + case 'x': + return 'X'; + case 'y': + return 'Y'; + case 'z': + return 'Z'; + } + return in; +} + +/* + * Curl_raw_equal() is for doing "raw" case insensitive strings. This is meant + * to be locale independent and only compare strings we know are safe for + * this. See http://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for + * some further explanation to why this function is necessary. + * + * The function is capable of comparing a-z case insensitively even for + * non-ascii. + */ + +static int Curl_raw_equal(const char *first, const char *second) +{ + while(*first && *second) { + if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) + /* get out of the loop as soon as they don't match */ + break; + first++; + second++; + } + /* we do the comparison here (possibly again), just to make sure that if the + loop above is skipped because one of the strings reached zero, we must not + return this as a successful match */ + return (Curl_raw_toupper(*first) == Curl_raw_toupper(*second)); +} + +static int Curl_raw_nequal(const char *first, const char *second, size_t max) +{ + while(*first && *second && max) { + if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) { + break; + } + max--; + first++; + second++; + } + if(0 == max) + return 1; /* they are equal this far */ + + return Curl_raw_toupper(*first) == Curl_raw_toupper(*second); +} + +/* + * Match a hostname against a wildcard pattern. + * E.g. + * "foo.host.com" matches "*.host.com". + * + * We use the matching rule described in RFC6125, section 6.4.3. + * http://tools.ietf.org/html/rfc6125#section-6.4.3 + */ + +static int hostmatch(const char *hostname, const char *pattern) +{ + const char *pattern_label_end, *pattern_wildcard, *hostname_label_end; + int wildcard_enabled; + size_t prefixlen, suffixlen; + pattern_wildcard = strchr(pattern, '*'); + if(pattern_wildcard == NULL) + return Curl_raw_equal(pattern, hostname) ? + CURL_HOST_MATCH : CURL_HOST_NOMATCH; + + /* We require at least 2 dots in pattern to avoid too wide wildcard + match. */ + wildcard_enabled = 1; + pattern_label_end = strchr(pattern, '.'); + if(pattern_label_end == NULL || strchr(pattern_label_end+1, '.') == NULL || + pattern_wildcard > pattern_label_end || + Curl_raw_nequal(pattern, "xn--", 4)) { + wildcard_enabled = 0; + } + if(!wildcard_enabled) + return Curl_raw_equal(pattern, hostname) ? + CURL_HOST_MATCH : CURL_HOST_NOMATCH; + + hostname_label_end = strchr(hostname, '.'); + if(hostname_label_end == NULL || + !Curl_raw_equal(pattern_label_end, hostname_label_end)) + return CURL_HOST_NOMATCH; + + /* The wildcard must match at least one character, so the left-most + label of the hostname is at least as large as the left-most label + of the pattern. */ + if(hostname_label_end - hostname < pattern_label_end - pattern) + return CURL_HOST_NOMATCH; + + prefixlen = pattern_wildcard - pattern; + suffixlen = pattern_label_end - (pattern_wildcard+1); + return Curl_raw_nequal(pattern, hostname, prefixlen) && + Curl_raw_nequal(pattern_wildcard+1, hostname_label_end - suffixlen, + suffixlen) ? + CURL_HOST_MATCH : CURL_HOST_NOMATCH; +} + +int Tool_Curl_cert_hostcheck(const char *match_pattern, const char *hostname) +{ + if(!match_pattern || !*match_pattern || + !hostname || !*hostname) /* sanity check */ + return 0; + + if(Curl_raw_equal(hostname, match_pattern)) /* trivial case */ + return 1; + + if(hostmatch(hostname,match_pattern) == CURL_HOST_MATCH) + return 1; + return 0; +} + +#endif diff --git a/src/eepp/network/ssl/backend/openssl/curl_hostcheck.h b/src/eepp/network/ssl/backend/openssl/curl_hostcheck.h new file mode 100644 index 000000000..dbecebe8e --- /dev/null +++ b/src/eepp/network/ssl/backend/openssl/curl_hostcheck.h @@ -0,0 +1,35 @@ +#ifndef HEADER_TOOL_CURL_HOSTCHECK_H +#define HEADER_TOOL_CURL_HOSTCHECK_H + +#ifdef EE_OPENSSL + +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#define CURL_HOST_NOMATCH 0 +#define CURL_HOST_MATCH 1 +int Tool_Curl_cert_hostcheck(const char *match_pattern, const char *hostname); + +#endif + +#endif /* HEADER_CURL_HOSTCHECK_H */ + diff --git a/src/eepp/network/ssl/csslsocket.cpp b/src/eepp/network/ssl/csslsocket.cpp new file mode 100644 index 000000000..f83f9fa65 --- /dev/null +++ b/src/eepp/network/ssl/csslsocket.cpp @@ -0,0 +1,105 @@ +#include +#include +#include + +#ifdef EE_OPENSSL +#include +#endif + +namespace EE { namespace Network { namespace SSL { + +static bool ssl_initialized = false; + +std::string cSSLSocket::CertificatesPath = ""; + +bool cSSLSocket::Init() { + bool ret = false; + + if ( !ssl_initialized ) { + if ( CertificatesPath.empty() ) { + #if EE_PLATFORM == EE_PLATFORM_LINUX + CertificatesPath = "/etc/ssl/ca-bundle.pem"; + #endif + } + + #ifdef EE_OPENSSL + ret = cOpenSSLSocket::Init(); + #endif + + ssl_initialized = true; + } + + return ret; +} + +bool cSSLSocket::End() { + bool ret = false; + + if ( ssl_initialized ) { + #ifdef EE_OPENSSL + ret = cOpenSSLSocket::End(); + #endif + + ssl_initialized = false; + } + + return ret; +} + +cSSLSocket::cSSLSocket( std::string hostname , bool validateCertificate, bool validateHostname ) : +#ifdef EE_OPENSSL + mImpl( eeNew( cOpenSSLSocket, ( this ) ) ), +#else + mImpl( NULL ), +#endif + mHostName( hostname ), + mValidateCertificate( validateCertificate ), + mValidateHostname( validateHostname ) +{ + Init(); +} + +cSSLSocket::~cSSLSocket() { + eeSAFE_DELETE( mImpl ); +} + +cSocket::Status cSSLSocket::Connect( const cIpAddress& remoteAddress, unsigned short remotePort, cTime timeout ) { + Status status = cSocket::Disconnected; + + if ( ( status = cTcpSocket::Connect( remoteAddress, remotePort, timeout ) ) == cSocket::Done ) { + status = mImpl->Connect( remoteAddress, remotePort, timeout ); + } + + return status; +} + +void cSSLSocket::Disconnect() { + mImpl->Disconnect(); + cTcpSocket::Disconnect(); +} + +cSocket::Status cSSLSocket::Send(const void* data, std::size_t size) { + return mImpl->Send( data, size ); +} + +cSocket::Status cSSLSocket::Receive(void* data, std::size_t size, std::size_t& received) { + return mImpl->Receive( data, size, received ); +} + +cSocket::Status cSSLSocket::Send(cPacket& packet) { + return cTcpSocket::Send( packet ); +} + +cSocket::Status cSSLSocket::Receive(cPacket& packet) { + return cTcpSocket::Receive( packet ); +} + +cSocket::Status cSSLSocket::TcpSend(const void* data, std::size_t size) { + return cTcpSocket::Send( data, size ); +} + +cSocket::Status cSSLSocket::TcpReceive(void* data, std::size_t size, std::size_t& received) { + return cTcpSocket::Receive( data, size, received ); +} + +}}} diff --git a/src/eepp/network/ssl/csslsocketimpl.hpp b/src/eepp/network/ssl/csslsocketimpl.hpp new file mode 100644 index 000000000..33a80ae88 --- /dev/null +++ b/src/eepp/network/ssl/csslsocketimpl.hpp @@ -0,0 +1,29 @@ +#ifndef EE_NETWORKCSSLSOCKETIMPL_HPP +#define EE_NETWORKCSSLSOCKETIMPL_HPP + +#include + +namespace EE { namespace Network { namespace SSL { + +class cSSLSocketImpl { + public: + cSSLSocketImpl( cSSLSocket * socket ) : + mSSLSocket( socket ) + {} + + virtual ~cSSLSocketImpl() {} + + virtual cSocket::Status Connect(const cIpAddress& remoteAddress, unsigned short remotePort, cTime timeout = cTime::Zero) = 0; + + virtual void Disconnect() = 0; + + virtual cSocket::Status Send(const void* data, std::size_t size) = 0; + + virtual cSocket::Status Receive(void* data, std::size_t size, std::size_t& received) = 0; + protected: + cSSLSocket * mSSLSocket; +}; + +}}} + +#endif diff --git a/src/eepp/window/cengine.cpp b/src/eepp/window/cengine.cpp index 704b9eaae..ee2faf586 100755 --- a/src/eepp/window/cengine.cpp +++ b/src/eepp/window/cengine.cpp @@ -13,7 +13,7 @@ #include #include #include - +#include #include #include #include @@ -75,6 +75,10 @@ cEngine::~cEngine() { HaikuTTF::hkFontManager::DestroySingleton(); + #ifdef EE_SSL_SUPPORT + Network::SSL::cSSLSocket::End(); + #endif + Destroy(); eeSAFE_DELETE( mBackend ); diff --git a/src/examples/http_request/http_request.cpp b/src/examples/http_request/http_request.cpp index a52e89490..9b8bad807 100644 --- a/src/examples/http_request/http_request.cpp +++ b/src/examples/http_request/http_request.cpp @@ -16,7 +16,7 @@ EE_MAIN_FUNC int main (int argc, char * argv []) { cHttp http; // We'll work on http://en.wikipedia.org - http.SetHost("http://en.wikipedia.org"); + http.SetHost("https://en.wikipedia.org"); // Prepare a request to get the wikipedia main page cHttp::Request request("/wiki/Main_Page");