microsoft / cpprestsdk

The C++ REST SDK is a Microsoft project for cloud-based client-server communication in native code using a modern asynchronous C++ API design. This project aims to help C++ developers connect to and interact with services.
Other
7.91k stars 1.64k forks source link

HTTPS client server in unix #1730

Closed hros closed 1 year ago

hros commented 1 year ago

Although other issues state that https on Linux is supported (link), I am having difficulties getting it to work

I created certificates and keys by following the instructions, creating the files: rootca.key, rootca.crt, rootca.srl, user.key, user.crt, user,csr, dh2048.pem.

I am running on Ubuntu 20.04 (in WSL-2). I created the server:

url = "https://" + url;
http_listener_config conf;
conf.set_ssl_context_callback([&cert, &privkey](ssl::context &ctx) {
  try {
    ctx.set_options(ssl::context::default_workarounds);
    ctx.use_certificate_chain_file(cert);
    ctx.use_private_key_file(privkey, ssl::context::pem);
  } catch (exception const &e) {
    clog << "ERROR:" << e.what() << "\n";
  }
});
listener = http_listener(utility::conversions::to_string_t(url), conf);

where cert and privkey are string variables set to rootca.crt, rootca.key respectively

When I run the server, I am able to use the browser to test the server and get the json reply (after ignoring the browser's warning of NET::ERR_CERT_AUTHORITY_INVALID and continuing to browse the "unsafe" uri)

My problem is that I am not able to get the client to communicate with the server. I would like the client and server to use TLS and have the client be verified by the certificate that is signed by the server (user.crt?) My code is:

http_client_config conf;
url = "https://" + url;
try {
  conf.set_ssl_context_callback([&cert](boost::asio::ssl::context &ctx) {
    ctx.load_verify_file(cert);
  });
} catch (std::exception const &e) {
  clog << "ERROR: " << e.what() << endl;
}
http_client client(url, conf);

but when I make_request I get an Error in SSL handshake when I pass the cert file either user.crt or rootca.crt. note that the client code works when using unencrypted http

I tried accessing the server with curl, and I found two options that worl:

  1. curl --cacert rootca.crt https://url
  2. curl --cacert rootca.crt --key user.key https://url It seems that the server is not requiring a user key

What am I missing?

barcharcraz commented 1 year ago

The HTTP server component of cpprestsdk has never been supported on any platform, and exists basically only to test the client component

hros commented 1 year ago

The question is how to setup the client for https communication

That is supposedly suported!

I am asking for code example for the client, using certificates to communicate using tls

hros commented 1 year ago

I have solved the issue Here are the steps taken:

  1. create public/private key pair and certificate for the REST Server:

    openssl ecparam -genkey -name prime256v1 -noout -out private-key.pem
    openssl ec -in private-key.pem -pubout -out public-key.pem
    openssl req -new -x509 -sha256 -key private-key.pem -subj /CN=localhost -out certificate.pem

    replacing the file names private-key.pem, public-key.pem and certificate.pem with whatever names you prefer, and changing localhost to the ip address of the server

  2. The https server code:

    
    #include <cpprest/http_listener.h>
    #include <cpprest/json.h>

using namespace web; using namespace web::http; using namespace web::http::experimental::listener; namespace net = boost::asio; namespace ssl = net::ssl;

string url = "https://" + host + ":" + to_string(port); http_listener_config conf; conf.set_ssl_context_callback([&cert, &privkey](ssl::context &ctx) { try { ctx.set_options(ssl::context::default_workarounds | ssl::context::no_sslv2 | ssl::context::no_tlsv1 | ssl::context::no_tlsv1_1 | ssl::context::single_dh_use); ctx.use_certificate_chain_file(cert); ctx.use_private_key_file(privkey, ssl::context::pem); } catch (exception const &e) { clog << "ERROR: " << e.what() << endl; } }); listener = http_listener(utility::conversions::to_string_t(url), conf); listener.support(methods::GET, get_handler); listener.open().wait(); cout << "Listening for requests at: " << host << ":" << port << endl;

where `host` and `port` are the ip address and port of the server; `cert` and `privkey` are the filenames of the certificate and private key that were generated in the previous step
`get handler` is a function of type: `http_response get_handler(http_request request)`, which parses the request and returns the response (similar code for other methods: `method::POST`, ...)

3. **the client code:**
```cpp
#define _TURN_OFF_PLATFORM_STRING
#include <cpprest/http_client.h>
#include <cpprest/json.h>

using namespace web;
using namespace web::http;
using namespace web::http::client;
namespace net = boost::asio;
namespace ssl = net::ssl;

string url = "https://" + host + ":" + (to_string(port));
http_client_config conf;
try {
  conf.set_ssl_context_callback([](boost::asio::ssl::context &ctx) {
    ctx.load_verify_file(cert);
  });
} catch (std::exception const &e) {
  clog << "ERROR: " << e.what() << endl;
}
http_client client(uri_builder(url).to_uri(), conf);

client.request(...);

using the server's certificate (cert is the file name of the certificate) proceed with issuing a request as usual (there are numerous examples Note that defining _TURN_OFF_PLATFORM_STRING disables the U macro used to cast string to string_t which can cause conflicts. Use utility::string_t instead of the U macro