Recommended CryptoInitializer.cpp changes

Post here your questions about the C++ API for SFS2X

Moderators: Lapo, Bax, MBagnati

dhuang11
Posts: 38
Joined: 02 May 2014, 08:21

Recommended CryptoInitializer.cpp changes

Postby dhuang11 » 30 Nov 2015, 07:31

Trying to turn on encryption for the C++ client. It works fine, but we ran into some blocks that we've resolved internally by making some changes to the way CryptoInitializer.cpp works. These are the issues we ran into:
1. It assumes the config-Host() is an IP address and doesn't account that it may be a domain name. We rely heavily on DNS for locating our SFS2X server. Connecting to SFS2X allows usage of a hostname, but the CryptoInitializer is not consistent. We made a minor change to use the bitSwarm->ConnectedIP() field instead of assuming that config->Host() returns an IP address
2. We also noticed that whenever there is an error in retrieving the crypto key, it would make one-off calls to OnHttpError("") before returning the expected bool value for boost::future. This makes it easy to run into a locked situation If during the CRYPTO_INIT event, we attempt to disconnect/dispose SFS2X for error situations. In that scenario, we end up deadlocking between boost::future waiting for a bool return value and lockDispose.lock() in BitSwarmClient.cpp. Instead, we changed it such that CryptoInitializer also holds a private instance variable called "errorMessage". If there is an error/exception when retrieving the SFS2X encryption key, we set errorMessage with an error rather than calling OnHttpError(message) directly. This allows the expected bool future value to return. Then in RunHelper(), we wait for the future value to return. If it returns true, we call OnHttpResponse() with the key the same as before. If it returns false or the errorMessage instance variable is populated, we call OnHttpError() with the errorMessage. What we've done is made it exactly so that boost::future will get it's expected return/future value regardless of any errors that occur during key retrieval.

Anyway, please check out these changes and let us know your thoughts and if they will be included in the next SFS2X C++ API patch. I can't seem to upload attachments, so I've copied & pasted the code changes here:

CryptoInitializer.cpp:

Code: Select all

// ===================================================================
//
// Description      
//      Helper class for crypting
//
// Revision history
//      Date         Description
//      01-May-2015      First version
//
// ===================================================================
#define BOOST_THREAD_USES_MOVE
#define BOOST_THREAD_PROVIDES_FUTURE

#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/insert_linebreaks.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/archive/iterators/remove_whitespace.hpp>
#include <boost/asio/ssl.hpp>
#include "CryptoInitializer.h"
#include "CryptoKey.h"
#include "../Core/Sockets/TCPClientSSL.h"            // SSL TCP client handler

using namespace boost::archive::iterators;

namespace Sfs2X {
namespace Util {

boost::shared_ptr<string> CryptoInitializer::KEY_SESSION_TOKEN (new string("SessToken"));
boost::shared_ptr<string> CryptoInitializer::TARGET_SERVLET (new string("BlueBox/CryptoManager"));

// -------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------
CryptoInitializer::CryptoInitializer(boost::shared_ptr<SmartFox> sfs)
{
   this->useHttps = true;
   this->key = boost::shared_ptr<string>();
    this->errorMessage = nullptr;

   // These are not events because they should fail at test time
   if (!sfs->IsConnected())
      boost::throw_exception(boost::enable_error_info (std::runtime_error("Cryptography cannot be initialized before connecting to SmartFoxServer!")));
         
   if (sfs->GetSocketEngine()->CryptoKey() != NULL)
      boost::throw_exception(boost::enable_error_info (std::runtime_error("Cryptography is already initialized!")));
         
   this->sfs = sfs;
}

// -------------------------------------------------------------------
// Run
// -------------------------------------------------------------------
void CryptoInitializer::Run()
{
   RunHelper();
}

// -------------------------------------------------------------------
// RunHelper
// -------------------------------------------------------------------
void CryptoInitializer::RunHelper()
{
   // To grant linux compatibility, below statements have been replaced with RunHelperSSLAsync/RunHelperAsync methods that return a bool instead of the key session string
   // The new implementation of RunHelperSSLAsync/RunHelperAsync store key session in a local member (see key member)
   /*
   boost::future<boost::shared_ptr<string> > f = boost::async(boost::bind((useHttps ? &CryptoInitializer::RunHelperSSLAsync : &CryptoInitializer::RunHelperAsync), this));

   // Wait for asynch compution of RunHelperAsync, then process returned value as http response
   OnHttpResponse(f.get());
   */

    boost::future<bool> f = boost::async(boost::launch::async, boost::bind((useHttps ? &CryptoInitializer::RunHelperSSLAsync : &CryptoInitializer::RunHelperAsync), this));
   
    bool result = f.get();
   
    if (result && !errorMessage)
    {
        // Wait for asynch compution of RunHelperAsync, then process returned value as http response
        OnHttpResponse(this->key);
    }
    else
    {
        if(errorMessage) {
            OnHttpError(errorMessage);
        } else {
            OnHttpError(boost::shared_ptr<string>(new string("Error retrieving crypto key from SFS2X")));
        }
    }
}

// -------------------------------------------------------------------
// RunHelperAsync
// -------------------------------------------------------------------
bool CryptoInitializer::RunHelperAsync()
{
   bool returned = false;
   boost::shared_ptr<TCPClient> client = boost::shared_ptr<TCPClient>();

   try
   {
      client = boost::shared_ptr<TCPClient>(new TCPClient(*(sfs->GetBoostService())));
       
      boost::shared_ptr<IPAddress> address (new IPAddress(IPAddress::IPADDRESSTYPE_IPV4, *sfs->GetIPAddress()));

      client->SynchConnect(address, (useHttps ? sfs->Config()->HttpsPort() : sfs->Config()->HttpPort()));
   }
   catch (exception e)
   {
      boost::shared_ptr<string> messageException (new string(e.what()));
      boost::shared_ptr<string> message (new string("Http error creating http connection: " + (*messageException)));

        this->errorMessage = message;
       
      try
      {
         client->Shutdown();
      }
      catch (exception e)
      {
         boost::shared_ptr<string> messageException (new string(e.what()));
         boost::shared_ptr<string> message (new string("Error during http scocket shutdown: " + (*messageException)));

            this->errorMessage = message;
      }

      return returned;
   }

   try
   {
      string data = (*KEY_SESSION_TOKEN) + "=" + (*sfs->SessionToken());

      boost::shared_ptr<wstring> dataAsNative (new wstring());
      dataAsNative->assign(data.begin(), data.end());

      boost::shared_ptr<string> dataAsUtf8 (new string());
      WStrToUtf8(dataAsNative, dataAsUtf8);

      string sb;
      sb.append("POST /" + (*CryptoInitializer::TARGET_SERVLET) + " HTTP/1.0\r\n");
      sb.append("Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n");

      boost::shared_ptr<string> valueContentLength (new string());
      boost::shared_ptr<string> format (new string ("Content-Length: %ld\r\n"));
      StringFormatter<long int> (valueContentLength, format, dataAsUtf8->size());

      sb.append(*valueContentLength);
      sb.append("\r\n");
      sb.append(data);
      
      boost::shared_ptr<vector<unsigned char> > outputData (new vector<unsigned char>(sb.begin(), sb.end()));
      outputData->push_back('\0');

      client->SynchWrite(outputData);

      string responseFromServer;
      do
      {
         boost::shared_ptr<vector<unsigned char> > receive = client->SynchRead();
         if (receive->size() == 0)
         {
            break;
         }

         boost::shared_ptr<string> src (new string(receive->begin(), receive->end()));
         boost::shared_ptr<wstring> dest (new wstring());
         Utf8toWStr(src, dest);

         responseFromServer.append(dest->begin(), dest->end());

      } while (true);

        // Remove http header and trim whitespaces at the end
      std::vector<string> parts;

      const char* chunkCurrent = responseFromServer.c_str();
      const char* chunkNext = chunkCurrent;

      while ((chunkNext = strstr(chunkCurrent, "\r\n\r\n")) != NULL)
      {
         string tokenizedValue (chunkCurrent, chunkNext - chunkCurrent);
         parts.push_back(tokenizedValue);
         chunkCurrent = chunkNext + 4;
      }

      string tokenizedValueLast (chunkCurrent);
      if (tokenizedValueLast.length() > 0)
      {
         parts.push_back(tokenizedValueLast);
      }

      if ((responseFromServer.size() > 4) && (responseFromServer.compare(responseFromServer.size() - 4, 4, "\r\n\r\n") == 0))
      {
         parts.push_back("");
      }

      if (parts.size() < 2) {
            this->errorMessage = boost::shared_ptr<string>(new string ("Error during http response: connection closed by remote side"));
         return returned;
      }

      string charsToTrim = " ";
      string dataPayload = parts[1].substr(0, parts[1].find_last_not_of(charsToTrim) + 1);
      
      this->key = boost::shared_ptr<string>(new string(dataPayload));
      returned = true;
   }
   catch (exception e)
   {
      boost::shared_ptr<string> messageException (new string(e.what()));
      boost::shared_ptr<string> message (new string("Error during http request: " + (*messageException)));

        this->errorMessage = message;
   }

   try
   {
      client->Shutdown();
   }
   catch (exception e)
   {
      boost::shared_ptr<string> messageException (new string(e.what()));
      boost::shared_ptr<string> message (new string("Error during http scocket shutdown: " + (*messageException)));

        this->errorMessage = message;
   }

   return returned;
}

// -------------------------------------------------------------------
// RunHelperSSLAsync
// -------------------------------------------------------------------
bool CryptoInitializer::RunHelperSSLAsync()
{
   bool returned = false;
   boost::shared_ptr<TCPClientSSL> client = boost::shared_ptr<TCPClientSSL>();

   try
   {
      // Create a context that uses the default paths for
      // finding CA certificates.
      boost::asio::ssl::context contextSSL(boost::asio::ssl::context::sslv23);
      contextSSL.set_default_verify_paths();
      //contextSSL.load_verify_file("ca.pem");

      client = boost::shared_ptr<TCPClientSSL>(new TCPClientSSL(*(sfs->GetBoostService()), contextSSL));

      boost::shared_ptr<IPAddress> address (new IPAddress(IPAddress::IPADDRESSTYPE_IPV4, *sfs->GetIPAddress()));

      client->SynchConnect(address, (useHttps ? sfs->Config()->HttpsPort() : sfs->Config()->HttpPort()));
   }
   catch (exception e)
   {
      boost::shared_ptr<string> messageException (new string(e.what()));
      boost::shared_ptr<string> message (new string("Http error creating http connection: " + (*messageException)));

        this->errorMessage = message;

      try
      {
         client->Shutdown();
      }
      catch (exception e)
      {
         boost::shared_ptr<string> messageException (new string(e.what()));
         boost::shared_ptr<string> message (new string("Error during http scocket shutdown: " + (*messageException)));

            this->errorMessage = message;
        }

      return returned;
   }

   try
   {
      string data = (*KEY_SESSION_TOKEN) + "=" + (*sfs->SessionToken());

      boost::shared_ptr<wstring> dataAsNative (new wstring());
      dataAsNative->assign(data.begin(), data.end());

      boost::shared_ptr<string> dataAsUtf8 (new string());
      WStrToUtf8(dataAsNative, dataAsUtf8);

      string sb;
      sb.append("POST /" + (*CryptoInitializer::TARGET_SERVLET) + " HTTP/1.0\r\n");
      sb.append("Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n");

      boost::shared_ptr<string> valueContentLength (new string());
      boost::shared_ptr<string> format (new string ("Content-Length: %ld\r\n"));
      StringFormatter<long int> (valueContentLength, format, dataAsUtf8->size());

      sb.append(*valueContentLength);
      sb.append("\r\n");
      sb.append(data);
      
      boost::shared_ptr<vector<unsigned char> > outputData (new vector<unsigned char>(sb.begin(), sb.end()));
      outputData->push_back('\0');

      client->SynchWrite(outputData);

      string responseFromServer;
      do
      {
         boost::shared_ptr<vector<unsigned char> > receive = client->SynchRead();
         if (receive->size() == 0)
         {
            break;
         }

         boost::shared_ptr<string> src (new string(receive->begin(), receive->end()));
         boost::shared_ptr<wstring> dest (new wstring());
         Utf8toWStr(src, dest);

         responseFromServer.append(dest->begin(), dest->end());

      } while (true);

        // Remove http header and trim whitespaces at the end
      std::vector<string> parts;

      const char* chunkCurrent = responseFromServer.c_str();
      const char* chunkNext = chunkCurrent;

      while ((chunkNext = strstr(chunkCurrent, "\r\n\r\n")) != NULL)
      {
         string tokenizedValue (chunkCurrent, chunkNext - chunkCurrent);
         parts.push_back(tokenizedValue);
         chunkCurrent = chunkNext + 4;
      }

      string tokenizedValueLast (chunkCurrent);
      if (tokenizedValueLast.length() > 0)
      {
         parts.push_back(tokenizedValueLast);
      }

      if ((responseFromServer.size() > 4) && (responseFromServer.compare(responseFromServer.size() - 4, 4, "\r\n\r\n") == 0))
      {
         parts.push_back("");
      }

      if (parts.size() < 2) {
            this->errorMessage = boost::shared_ptr<string>(new string ("Error during http response: connection closed by remote side"));
         return returned;
      }

      string charsToTrim = " ";
      string dataPayload = parts[1].substr(0, parts[1].find_last_not_of(charsToTrim) + 1);
      
      this->key = boost::shared_ptr<string>(new string(dataPayload));
      returned = true;
   }
   catch (exception e)
   {
      boost::shared_ptr<string> messageException (new string(e.what()));
      boost::shared_ptr<string> message (new string("Error during http request: " + (*messageException)));

        this->errorMessage = message;
   }

   try
   {
      client->Shutdown();
   }
   catch (exception e)
   {
      boost::shared_ptr<string> messageException (new string(e.what()));
      boost::shared_ptr<string> message (new string("Error during http scocket shutdown: " + (*messageException)));

        this->errorMessage = message;
   }

   return returned;
}

// -------------------------------------------------------------------
// OnHttpResponse
// -------------------------------------------------------------------
void CryptoInitializer::OnHttpResponse(boost::shared_ptr<string> rawData)
{
   boost::shared_ptr<ByteArray> byteData = boost::shared_ptr<ByteArray>();
   byteData = DecodeResponse(rawData);

   boost::shared_ptr<ByteArray> key (new ByteArray());
   boost::shared_ptr<ByteArray> iv (new ByteArray());

   // Extract the key and init vector and pass them to the BitSwarmClient
   boost::shared_ptr<vector<unsigned char> > keyValues (new vector<unsigned char>());
   byteData->Position(0);
   byteData->ReadBytes(0, 16, *keyValues.get());
   key->WriteBytes(keyValues);

   boost::shared_ptr<vector<unsigned char> > ivValues (new vector<unsigned char>());
   byteData->Position(0);
   byteData->ReadBytes(16, 16, *ivValues.get());
   iv->WriteBytes(ivValues);

   boost::shared_ptr<CryptoKey> cryptoKey (new CryptoKey(iv, key));
   sfs->GetSocketEngine()->CryptoKey(cryptoKey);

   boost::shared_ptr<map<string, boost::shared_ptr<void> > > evtParams (new map<string, boost::shared_ptr<void> >());

   boost::shared_ptr<bool> value (new bool());
   *value = true;
   evtParams->insert(pair<string, boost::shared_ptr<void> >("success", value));

   boost::shared_ptr<SFSEvent> evt (new SFSEvent(SFSEvent::CRYPTO_INIT, evtParams));
   sfs->DispatchEvent(evt);
}

// -------------------------------------------------------------------
// OnHttpError
// -------------------------------------------------------------------
void CryptoInitializer::OnHttpError(boost::shared_ptr<string> errorMsg)
{
   boost::shared_ptr<map<string, boost::shared_ptr<void> > > evtParams (new map<string, boost::shared_ptr<void> >());

   boost::shared_ptr<bool> value (new bool());
   *value = false;
   evtParams->insert(pair<string, boost::shared_ptr<void> >("success", value));

   evtParams->insert(pair<string, boost::shared_ptr<void> >("errorMessage", errorMsg));

   boost::shared_ptr<SFSEvent> evt (new SFSEvent(SFSEvent::CRYPTO_INIT, evtParams));
   sfs->DispatchEvent(evt);
}

// -------------------------------------------------------------------
// DecodeResponse
// -------------------------------------------------------------------
boost::shared_ptr<ByteArray> CryptoInitializer::DecodeResponse (boost::shared_ptr<string> rawData)
{
   
   typedef transform_width<binary_from_base64<remove_whitespace<string::const_iterator> >, 8, 6 > base64_decoder;
   
   unsigned int paddChars = (unsigned int)count(rawData->begin(), rawData->end(), '=');
   std::replace(rawData->begin(), rawData->end(), '=', 'A');                     // replace '=' by base64 encoding of '\0'
   string result(base64_decoder(rawData->begin()), base64_decoder(rawData->end()));   // decode
   result.erase(result.end() - paddChars, result.end());                        // erase padding '\0' characters

   boost::shared_ptr<vector<unsigned char> > data (new vector<unsigned char>(result.begin(), result.end()));

   return boost::shared_ptr<ByteArray>(new ByteArray(data));
}

}   // namespace Util
}   // namespace Sfs2X


CryptoInitializer.h:

Code: Select all

// ===================================================================
//
// Description      
//      Helper class for crypting
//
// Revision history
//      Date         Description
//      01-May-2015      First version
//
// ===================================================================
#ifndef __CryptoInitializer__
#define __CryptoInitializer__

#include "./Common.h"
#include "../SmartFox.h"

#include <boost/shared_ptr.hpp>         // Boost Asio shared pointer

#if defined(_MSC_VER)
#pragma warning(disable:4786)         // STL library: disable warning 4786; this warning is generated due to a Microsoft bug
#endif
#include <string>                  // STL library: string object
using namespace std;               // STL library: declare the STL namespace

namespace Sfs2X {
namespace Util {
   
   /// <summary>
   /// Initializer for encryption
   /// </summary>
   class CryptoInitializer
   {
   public:

      // -------------------------------------------------------------------
      // Public methods
      // -------------------------------------------------------------------

      CryptoInitializer(boost::shared_ptr<SmartFox> sfs);

      /// <summary>
      /// Initialize encryption
      /// </summary>
      void Run();

      // -------------------------------------------------------------------
      // Public members
      // -------------------------------------------------------------------
      static boost::shared_ptr<string> KEY_SESSION_TOKEN;
      static boost::shared_ptr<string> TARGET_SERVLET;
      
   protected:

      // -------------------------------------------------------------------
      // Protected methods
      // -------------------------------------------------------------------

      // -------------------------------------------------------------------
      // Protected members
      // -------------------------------------------------------------------

   private:

      // -------------------------------------------------------------------
      // Private methods
      // -------------------------------------------------------------------
      void RunHelper();
      bool RunHelperAsync();
      bool RunHelperSSLAsync();
      void OnHttpResponse(boost::shared_ptr<string> rawData);
      void OnHttpError(boost::shared_ptr<string> errorMsg);

      boost::shared_ptr<ByteArray> DecodeResponse (boost::shared_ptr<string> rawData);

      // -------------------------------------------------------------------
      // Private members
      // -------------------------------------------------------------------
      boost::shared_ptr<SmartFox> sfs;
      boost::shared_ptr<string> key;      // This is the session key retrieved by RunHelperAsync/RunHelperSSLAsync that for linux compatibility is stored as local variabile instead of returned from methods
        boost::shared_ptr<string> errorMessage; // This is the error string if it exists
      bool useHttps;
   };

}   // namespace Util
}   // namespace Sfs2X

#endif
dhuang11
Posts: 38
Joined: 02 May 2014, 08:21

Re: Recommended CryptoInitializer.cpp changes

Postby dhuang11 » 30 Nov 2015, 07:53

Also - another bug in the C++ code

BitSwarmClient::BitSwarmClient() should initialize cryptoKey = NULL. Otherwise, clients that aren't using encryption could get incorrect values and falsely determine that encryption is enabled. We've seen a couple crashes in this code.
MBagnati
Posts: 126
Joined: 12 Feb 2013, 10:57

Re: Recommended CryptoInitializer.cpp changes

Postby MBagnati » 30 Nov 2015, 17:14

Hi,
Thanks for the useful tips.
I have incorporated the suggested changes into official API sources.
They will be released with the next API version

Thanks for your help

Return to “SFS2X C++ API”

Who is online

Users browsing this forum: No registered users and 13 guests