Added support for SSL and HTTPS requests using OpenSSL.

This commit is contained in:
Martín Lucas Golini
2014-06-01 17:01:10 -03:00
parent b25bf76330
commit 513ba89d96
22 changed files with 1025 additions and 38 deletions

View File

@@ -87,6 +87,7 @@
// Network
#include <eepp/network.hpp>
using namespace EE::Network;
using namespace EE::Network::SSL;
// UI
#include <eepp/ui.hpp>

View File

@@ -11,5 +11,6 @@
#include <eepp/network/ctcplistener.hpp>
#include <eepp/network/ctcpsocket.hpp>
#include <eepp/network/cudpsocket.hpp>
#include <eepp/network/ssl/csslsocket.hpp>
#endif

View File

@@ -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<std::string, std::string> 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<cAsyncRequest*> mThreads;
cMutex mThreadsMutex;
bool mIsSSL;
void RemoveOldThreads();
};

View File

@@ -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)

View File

@@ -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<char> Data; ///< Data of the packet
};

View File

@@ -0,0 +1,49 @@
#ifndef EE_NETWORKCSSLSOCKET_HPP
#define EE_NETWORKCSSLSOCKET_HPP
#include <eepp/network/ctcpsocket.hpp>
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

View File

@@ -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

View File

@@ -11,3 +11,5 @@
#define EE_GL3_ENABLED 1
#define EE_SHADERS_SUPPORTED
#define EE_GLEW_AVAILABLE
#define EE_SSL_SUPPORT
#define EE_OPENSSL

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 3.0.0, 2014-04-12T15:44:53. -->
<!-- Written by QtCreator 3.0.0, 2014-06-01T17:00:18. -->
<qtcreator>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
@@ -56,13 +56,13 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{388e5431-b31b-42b3-b9ad-9002d279d75d}</value>
<value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">11</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">../../make/linux</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Arguments">--with-static-backend gmake</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Arguments">--with-static-backend --with-ssl gmake</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Command">premake4</value>
<value type="QString" key="ProjectExplorer.ProcessStep.WorkingDirectory">%{buildDir}../../../</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Custom Process Step</value>
@@ -151,7 +151,7 @@
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Arguments">--with-static-backend gmake</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Arguments">--with-static-backend --with-ssl gmake</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Command">premake4</value>
<value type="QString" key="ProjectExplorer.ProcessStep.WorkingDirectory">%{buildDir}../../../</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Custom Process Step</value>
@@ -200,7 +200,7 @@
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Arguments">--with-static-backend gmake</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Arguments">--with-static-backend --with-ssl gmake</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Command">premake4</value>
<value type="QString" key="ProjectExplorer.ProcessStep.WorkingDirectory">%{buildDir}../../../</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Custom Process Step</value>
@@ -609,7 +609,7 @@
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Arguments">--with-static-backend gmake</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Arguments">--with-static-backend --with-ssl gmake</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Command">premake4</value>
<value type="QString" key="ProjectExplorer.ProcessStep.WorkingDirectory">%{buildDir}../../../</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Custom Process Step</value>
@@ -778,7 +778,7 @@
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Arguments">--with-static-backend gmake</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Arguments">--with-static-backend --with-ssl gmake</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Command">premake4</value>
<value type="QString" key="ProjectExplorer.ProcessStep.WorkingDirectory">%{buildDir}../../../</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Custom Process Step</value>

View File

@@ -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

View File

@@ -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 $@

View File

@@ -1,10 +1,13 @@
#include <eepp/network/chttp.hpp>
#include <eepp/network/ssl/csslsocket.hpp>
#include <cctype>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <limits>
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;
}

View File

@@ -15,7 +15,6 @@ cSocket::~cSocket() {
Close();
}
void cSocket::SetBlocking(bool blocking) {
// Apply if the socket is already created
if (mSocket != Private::cSocketImpl::InvalidSocket())

View File

@@ -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();

View File

@@ -0,0 +1,391 @@
#include <eepp/network/ssl/backend/openssl/copensslsocket.hpp>
/** This implementation is based on the Godo Game Engine implementation ( https://github.com/okamstudio/godot ), MIT licensed. */
#ifdef EE_OPENSSL
#include <eepp/network/cpacket.hpp>
#include <eepp/network/ssl/backend/openssl/curl_hostcheck.h>
#include <eepp/system/filesystem.hpp>
namespace EE { namespace Network { namespace SSL {
static std::vector<X509*> 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

View File

@@ -0,0 +1,68 @@
#ifndef EE_NETWORKCOPENLSSLSOCKET_HPP
#define EE_NETWORKCOPENLSSLSOCKET_HPP
#include <eepp/network/ssl/csslsocketimpl.hpp>
#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 <openssl/bio.h> // BIO objects for I/O
#include <openssl/ssl.h> // SSL and SSL_CTX for SSL connections
#include <openssl/err.h> // Error reporting
#include <openssl/x509v3.h>
}
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

View File

@@ -0,0 +1,221 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, 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, <daniel@haxx.se>.
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 <string.h>
/* 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

View File

@@ -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, <daniel@haxx.se>, 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 */

View File

@@ -0,0 +1,105 @@
#include <eepp/network/ssl/csslsocket.hpp>
#include <eepp/network/ssl/csslsocketimpl.hpp>
#include <eepp/network/platform/platformimpl.hpp>
#ifdef EE_OPENSSL
#include <eepp/network/ssl/backend/openssl/copensslsocket.hpp>
#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 );
}
}}}

View File

@@ -0,0 +1,29 @@
#ifndef EE_NETWORKCSSLSOCKETIMPL_HPP
#define EE_NETWORKCSSLSOCKETIMPL_HPP
#include <eepp/network/ssl/csslsocket.hpp>
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

View File

@@ -13,7 +13,7 @@
#include <eepp/graphics/renderer/cgl.hpp>
#include <eepp/helper/haikuttf/hkfontmanager.hpp>
#include <eepp/physics/cphysicsmanager.hpp>
#include <eepp/network/ssl/csslsocket.hpp>
#include <eepp/window/cbackend.hpp>
#include <eepp/window/backend/SDL/cbackendsdl.hpp>
#include <eepp/window/backend/SDL2/cbackendsdl2.hpp>
@@ -75,6 +75,10 @@ cEngine::~cEngine() {
HaikuTTF::hkFontManager::DestroySingleton();
#ifdef EE_SSL_SUPPORT
Network::SSL::cSSLSocket::End();
#endif
Destroy();
eeSAFE_DELETE( mBackend );

View File

@@ -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");