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