Tuesday, 29 March 2016

HTTPS Support for a Casablanca Client


This post is a continuation of a previous one about SSL and Casablanca C++ REST-API library by Microsoft. There we added SSL support to a Casablanca server. This time we'll do it on the client side. At the first sight it seems to be simple, just write:
  web::http::client::http_client restClient(U("https://xxx.yyy.zzz"));
  auto response = restClient.request(web::http::methods::GET);
and go on! As we saw, the only thing to do is to use a HTTPS URI. Nothing more to do? Not really! The main problem when adding SSL support to the client occurred to be the error handling. More specifically, I wanted to differentiate between an SSL error and a simple no-connection error.

Casablanca uses the new standard std::error_condition/error_category classes for that. The problem is, these classes are yet poorly understood by programmers (at least by me, because it is so new)*, and, which is much worse, that their usage by Casablanca (as of v.2.5.0) isn't consistent between Windows and Linux code!

On Windows, there is a special windows_category defined for system errors, while for Linux the standard std::system_category is used. Worse, the error codes forwarded there come from Boost ASIO, which in its turn just forwards OpenSSL's error codes. Which again depends on the library version :-/.

As to tame this chaos a little, the following little helper class was born:
class CCasablancaClientErrorCode 
{
public:
  CCasablancaClientErrorCode(int errCode) : _errCode(errCode) {};
 
  /**
    Check and translate to POSIX-conforming system error code.

    For HTTP connection problems following codes are used by Casablanca:
        std::errc::host_unreachable, std::errc::timed_out, std::errc::connection_aborted
  */
  bool IsStdSystemError(std::errc& stdErrorCode) const
  {
#ifdef _WINDOWS
    const std::error_condition ec = utility::details::windows_category().default_error_condition(_errCode);
#else  
    const std::error_condition ec = std::system_category().default_error_condition(_errCode);
#endif
    if (ec.category().name() != genericCategoryStrg) 
    {
      return false;
    }
    else
    {
      stdErrorCode = std::errc(ec.value());
      return true;
     }
  }
 
  /**
    Check if the reported error comes from SSL.

    There is only one, generic server SLL certificate error at the moment!
  */ 
  bool IsSslError() const
  {  
#ifdef _WINDOWS
    const std::error_condition ec = utility::details::windows_category().default_error_condition(_errCode);

    if (ec.category() == utility::details::windows_category())
    {
      return _errCode == ERROR_WINHTTP_SECURE_FAILURE;
    }
    else
    {
      return false;
    }
#else
    const std::error_condition ec = std::system_category().default_error_condition(_errCode);

    if (ec.category().name() == genericCategoryStrg)
    {
      return false;
    }
    else
    {
      // ??? OPEN TODO:::: must test, but it will depend on the SSL version !!!
      // - patch Casablanca's ASIO code?
      // return _errCode == 336458004; //== 0x140DF114  // OpenSSL error code  ??OR?? 335544539 == 0x140000DB

      return true; // should be SSL 
    }
#endif
  }

private:
  int _errCode;
};
Note that on Windows, only a single generic SSL error code is supported by Casablanca 2.5. If we wanted to differentiate between SSL error types, we'd have to patch Casablanca code (install a special callback handler to report the context of the HTTP error). I didn't make it, as generic SSL error was good enough for my customer.

Now now we can use this helper class like that:
  CCasablancaClientErrorCode clientErrCode = e.error_code().value();
  std::errc sysErrCode;

  if (clientErrCode.IsStdSystemError(sysErrCode))
  {
    switch (sysErrCode)
    {
    case std::errc::host_unreachable:
      // no connection error!
      break;
    default:
      // other error (std::errc::timed_out, std::errc::connection_aborted)
    }
  }
  else if (clientErrCode.IsSslError())
  {
    // SSL error!
  }
Having done that, the HTTPS support on the Client side was ready.

--
* I won't explain the workings of std::error_category here, for the confused readers this was already done here (parts 1 to 5, I said it's not a piece of cake).

10 comments:

Sean Clarke said...

Very interesting. Is there any appetite in offering HTTPS extensions for your server/listener implementation?

Marek Krj said...

@Sean Clarke: ???, Casablanca server/listener is by Microsoft, HTTPS is already baked in there - at least for Windows (as of 2.5.0)! Linux support was added lately too, AFAIK.

Anonymous said...

Hey,
Just a small question: Made the https server to work ( tested it in firefox and was able to access it). Now on the client side I am doing a http_client and then the restClient.request(GET).get() and it crashes giving me an "Unhandled exception at 0x7581C54F in CasablancaClient.exe: Microsoft C++ exception: web::http::http_exception at memory location 0x0023ECB0." Do you by any chance know the cause.
Thank you.

Marek Krj said...

@Anonymous: no idea, maybe your URL is wrong? task.get() can throw exceptions (AFAIR), so catch them, and look up the reason. Then we may be can explain it.

Anonymous said...

Hi Marek,

As i am trying with client (https://xyz.com:3456), but i got an error "Error in SSL handshake".But, by accessing through browser i am able to get the message from server. To communicate with https server do we need invoke some other functions for client ?. Can you please help me on this ..!

Marek Krj said...

@Anonymous: you don't need anything to be done on Windows, as Casablanca is using WinHttp there. But I assume you are on Linux or OS-X, thus using Asio... I wasn't testing it directly, but our client as compiled for Mac wasn't causing problems (AFAIR). The error message could hint at a problem with negotiating cyphersuites to be used for the SSL connection. Maybe your machine has only some old, deprecated ones configured? Sorry, but this seems to require some debugging.

Anonymous said...

Hi Marek,
I am trying to send from a server to a client a stream of doubles. so the client requests and then on the server side I will generate a double and then send it, then again generate a double and send it, and so on. On the client side I should be able to print the doubles. I do not want to send all the doubles at the same time. I have been able to stream a file and pass it from a server to a client without loading it into memory, but I don't know how to do it for simple stream of doubles.

thanks.

Marek Krj said...

@Anonymous
Casablanca uses std. C++ streams for file transfer, so I guess one solution would be to write a custom fake ios buffer class containing your doubles and plug it into an istream.

Anonymous said...

Hi,

I am interested on this error code:
335544539 == 0x140000DB

May I know where I can find reference for this error code? What is it? I tried download openssl source code and do global search, but found nothing.

Reason I am asking, we have this error appears intermittently in our software but doesn't really know where it came from.

Thanks in advance for any feedback.

Marek Krj said...

@Anonymous: You might like to have a look at this: http://stackoverflow.com/questions/9828066/how-to-decipher-a-boost-asio-ssl-error-code