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.

Update: As mutual authentication wasn't required it this project I didn't spend much thought on that feature, assuming everything would be OK. Unfortunately, prompted by a question of a reader, I checked up my local and also with newest Casablanca code in Github but unfortunately I couldn't find any trace of support for mutual authentication. As it seems it's not yet implemented (05/08/2017).

--
* 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).

15 comments:

Sean 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

Azdoom said...

This is the first blog I've ever followed... so sorry if I'm breaking some "rules".
I'm in a vague state where customer states I need to use "mutual SSL authentication", and I'm the client (app on dedicated server). Correct me if I'm wrong but... I install a cert locally (Root?). Then I need to point my http_client to it (via http_client_config)? Do you have an example of this? Or perhaps point me in the right direction. Seems there should be a http_client_config.SetCert("myCert.ptk") or w/e the ssl cert file name is. In your post about server, you said tie it to port.. this doesn't hold here correct?

Thank you ahead of time, and you give me hope for humanity...

Adam "floundering" Krist

Marek Krj said...

Hi Azdoom! I suppose "mutual SSL authentication" does mean you will need both server authentication (as described in the blogpost) on the client side and client authentication on the server side. In the post we install an certificate on the client side so the browser can check if the cerificate of the server can be trusted. For the other direction the same has to be done in reverse direction. This means you will need to generate a certificate for your client.

However... I'm not sure if Casablanca supports mutual certificates - I had to look it up in code, just give me a couple of days. Sorry cannot do it right now, too much work :-/.

If not, someone (you?) should write a patch for it. Maybe you could report it as a bug to the maintainers. In the past they reacted rather swiftly. But I'll look it up in next couple of days, I promise :).

Marek Krj said...

@Azdoom
I checked with the last Casablanca sources on Github, and it doesn't seem to support mutual certificates. If it would, it would use ssl::verify_peer in ASIO case and on Windows it would use the WINHTTP_OPTION_CLIENT_CERT_CONTEXT, but it doesn't!

So an opportunity for a nice contribution opens itself, innit? :-)

Anonymous said...

Hi Marek,
I'm trying to implement certificate pinning ,I need to configure the client so it will verify server certificate against a given certificate(thumprint) .
In cpprestsdk there is a set_ssl_context_callback API but it does not work for windows , is there any way to do this on windows?
Thanks.

Marek Krj said...

@Anonymous
Citing from MS docs: "When using the WinHTTP application programming interface (API), you can retrieve a server certificate by calling WinHttpQueryOption and specifying the WINHTTP_OPTION_SECURITY_CERTIFICATE_STRUCT flag."