Skip to content

Commit

Permalink
Cleanup, add SSL verify callback
Browse files Browse the repository at this point in the history
  • Loading branch information
leemaguire committed Jul 15, 2024
1 parent e4e1365 commit 2c9cae6
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 91 deletions.
16 changes: 0 additions & 16 deletions include/cpprealm/app.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,28 +240,12 @@ class App {
std::optional<std::string> base_url;
/// Custom location for Realm files.
std::optional<std::string> path;
/**
* Extra HTTP headers to be set on each request to Atlas Device Sync when using the internal HTTP client.
*
* Note: This has been deprecated and custom network options must now be supplied by either:
*
* - Your own subclass of `realm::networking::http_transport_client` / `realm::networking::sync_socket_provider`
* - Use default_http_transport and pass custom headers & proxy via the constructor. Set via `configuration.http_transport_client`.
*/
[[deprecated("Network options must be supplied via custom network implementations.")]]
std::optional<std::map<std::string, std::string>> custom_http_headers;
/// Custom encryption key for the metadata Realm.
std::optional<std::array<char, 64>> metadata_encryption_key;
/// Caches an App and its configuration for a given App ID. On by default.
bool enable_caching = true;
/**
* Network proxy configuration to be set on each HTTP and WebSocket request.
*
* Note: This has been deprecated and custom network options must now be supplied by either:
*
* - Your own subclass of `realm::networking::http_transport_client` / `realm::networking::sync_socket_provider`
* - Use default_http_transport and pass custom headers & proxy via the constructor. Set via `configuration.http_transport_client`.
*/
[[deprecated("Network options must be supplied via custom network implementations.")]]
std::optional<sync_config::proxy_config> proxy_configuration;
/**
Expand Down
17 changes: 17 additions & 0 deletions include/cpprealm/networking/platform_networking.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,14 +229,31 @@ namespace realm::networking {
void set_http_client_factory(std::function<std::shared_ptr<http_transport_client>()>&&);

struct default_transport_configuration {
/**
* Extra HTTP headers to be set on each request to Atlas Device Sync when using the internal HTTP client.
*/
std::optional<std::map<std::string, std::string>> custom_http_headers;
/**
* Network proxy configuration to be set on each request.
*/
std::optional<::realm::internal::bridge::realm::sync_config::proxy_config> proxy_config;

using SSLVerifyCallback = bool(const std::string& server_address,
internal::bridge::realm::sync_config::proxy_config::port_type server_port,
const char* pem_data, size_t pem_size, int preverify_ok, int depth);
/**
* If set to false, no validation will take place and the client will accept any certificate.
*/
bool client_validate_ssl = true;
/**
* Used for providing your own root certificate.
*/
std::optional<std::string> ssl_trust_certificate_path;
/**
* `ssl_verify_callback` is used to implement custom SSL certificate
* verification. It is only used if the protocol is SSL & `ssl_trust_certificate_path`
* is not set.
*/
std::function<SSLVerifyCallback> ssl_verify_callback;
};

Expand Down
3 changes: 1 addition & 2 deletions src/cpprealm/app.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include <cpprealm/app.hpp>
#include <cpprealm/internal/bridge/status.hpp>
#include <cpprealm/networking/platform_networking.hpp>
#include <cpprealm/internal/networking/shims.hpp>
#include <cpprealm/networking/platform_networking.hpp>

#ifndef REALMCXX_VERSION_MAJOR
#include <cpprealm/version_numbers.hpp>
Expand All @@ -13,7 +13,6 @@
#include <realm/object-store/sync/sync_user.hpp>
#include <realm/sync/config.hpp>
#include <realm/util/bson/bson.hpp>
#include <realm/util/platform_info.hpp>

#include <utility>

Expand Down
176 changes: 104 additions & 72 deletions src/cpprealm/internal/curl/network_transport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,91 +86,123 @@ namespace realm::networking {
}
return nitems * size;
}
} // namespace

static ::realm::networking::response do_http_request(const ::realm::networking::request& request,
const std::optional<internal::bridge::realm::sync_config::proxy_config>& proxy_config = std::nullopt)
{
CurlGlobalGuard curl_global_guard;
auto curl = curl_easy_init();
if (!curl) {
return ::realm::networking::response{500, -1, {}, "", std::nullopt};
}
using SSLVerifyCallback = std::function<bool(const std::string& server_address,
int server_port,
const char* pem_data, size_t pem_size,
int preverify_ok, int depth)>;

struct curl_slist* list = nullptr;
CURLcode ssl_ctx_callback(CURL *curl, void */*sslctx*/, SSLVerifyCallback *parm) {
auto verify_callback = (SSLVerifyCallback)(*parm);

std::string response;
::realm::networking::http_headers response_headers;
char *url;
curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);
std::string server_address(url);

curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str());
long port;
curl_easy_getinfo(curl, CURLINFO_PRIMARY_PORT, &port);

if (proxy_config) {
curl_easy_setopt(curl, CURLOPT_PROXY, util::format("%1:%2", proxy_config->address, proxy_config->port).c_str());
curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L);
if (proxy_config->username_password) {
curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, util::format("%1:%2", proxy_config->username_password->first, proxy_config->username_password->second).c_str());
}
}
const char *pem_data = "-----BEGIN CERTIFICATE-----\n...certificate data...\n-----END CERTIFICATE-----\n";
size_t pem_size = strlen(pem_data);

Check failure on line 107 in src/cpprealm/internal/curl/network_transport.cpp

View workflow job for this annotation

GitHub Actions / Linux Release (clang 15)

use of undeclared identifier 'strlen'

Check failure on line 107 in src/cpprealm/internal/curl/network_transport.cpp

View workflow job for this annotation

GitHub Actions / Linux Debug (gcc 9.1)

'strlen' was not declared in this scope

Check failure on line 107 in src/cpprealm/internal/curl/network_transport.cpp

View workflow job for this annotation

GitHub Actions / Linux Debug (clang 15)

use of undeclared identifier 'strlen'

Check failure on line 107 in src/cpprealm/internal/curl/network_transport.cpp

View workflow job for this annotation

GitHub Actions / Linux Debug (gcc 12.1)

'strlen' was not declared in this scope

Check failure on line 107 in src/cpprealm/internal/curl/network_transport.cpp

View workflow job for this annotation

GitHub Actions / Linux Release (gcc 8.3)

'strlen' was not declared in this scope

Check failure on line 107 in src/cpprealm/internal/curl/network_transport.cpp

View workflow job for this annotation

GitHub Actions / Linux Debug (gcc 11.1)

'strlen' was not declared in this scope

Check failure on line 107 in src/cpprealm/internal/curl/network_transport.cpp

View workflow job for this annotation

GitHub Actions / Linux Debug (gcc 10.1)

'strlen' was not declared in this scope

Check failure on line 107 in src/cpprealm/internal/curl/network_transport.cpp

View workflow job for this annotation

GitHub Actions / Linux Release (gcc 9.1)

'strlen' was not declared in this scope

Check failure on line 107 in src/cpprealm/internal/curl/network_transport.cpp

View workflow job for this annotation

GitHub Actions / Linux Release (gcc 11.1)

'strlen' was not declared in this scope

Check failure on line 107 in src/cpprealm/internal/curl/network_transport.cpp

View workflow job for this annotation

GitHub Actions / Linux Debug (gcc 8.3)

'strlen' was not declared in this scope

Check failure on line 107 in src/cpprealm/internal/curl/network_transport.cpp

View workflow job for this annotation

GitHub Actions / Linux Release (gcc 10.1)

'strlen' was not declared in this scope

Check failure on line 107 in src/cpprealm/internal/curl/network_transport.cpp

View workflow job for this annotation

GitHub Actions / Linux Release (gcc 12.1)

'strlen' was not declared in this scope

/* Now specify the POST data */
if (request.method == ::realm::networking::http_method::post) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str());
}
else if (request.method == ::realm::networking::http_method::put) {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str());
}
else if (request.method == ::realm::networking::http_method::patch) {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str());
}
else if (request.method == ::realm::networking::http_method::del) {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str());
}
else if (request.method == ::realm::networking::http_method::patch) {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str());
int preverify_ok = 1;
int depth = 0;

bool result = verify_callback(server_address, port, pem_data, pem_size, preverify_ok, depth);
return result ? CURLE_OK : CURLE_SSL_CERTPROBLEM;
}

void default_http_transport::send_request_to_server(const ::realm::networking::request& request,
std::function<void(const ::realm::networking::response&)>&& completion_block) {
CurlGlobalGuard curl_global_guard;
auto curl = curl_easy_init();
if (!curl) {
completion_block(::realm::networking::response{500, -1, {}, "", std::nullopt});
return;
}

struct curl_slist* list = nullptr;

std::string response;
::realm::networking::http_headers response_headers;

curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str());


curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, m_configuration.client_validate_ssl);

if (m_configuration.ssl_verify_callback) {
curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_callback);
curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA, &m_configuration.ssl_verify_callback);
}

if (m_configuration.ssl_trust_certificate_path) {
curl_easy_setopt(curl, CURLOPT_CAINFO, m_configuration.ssl_trust_certificate_path->c_str());
}

if (m_configuration.proxy_config) {
curl_easy_setopt(curl, CURLOPT_PROXY, util::format("%1:%2", m_configuration.proxy_config->address, m_configuration.proxy_config->port).c_str());
curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L);
if (m_configuration.proxy_config->username_password) {
curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, util::format("%1:%2", m_configuration.proxy_config->username_password->first, m_configuration.proxy_config->username_password->second).c_str());
}
}

curl_easy_setopt(curl, CURLOPT_TIMEOUT, request.timeout_ms);
/* Now specify the POST data */
if (request.method == ::realm::networking::http_method::post) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str());
}
else if (request.method == ::realm::networking::http_method::put) {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str());
}
else if (request.method == ::realm::networking::http_method::patch) {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str());
}
else if (request.method == ::realm::networking::http_method::del) {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str());
}
else if (request.method == ::realm::networking::http_method::patch) {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str());
}

for (auto header : request.headers) {
curl_easy_setopt(curl, CURLOPT_TIMEOUT, request.timeout_ms);

for (auto header : request.headers) {
auto header_str = util::format("%1: %2", header.first, header.second);
list = curl_slist_append(list, header_str.c_str());
}
if (m_configuration.custom_http_headers) {
for (auto header : *m_configuration.custom_http_headers) {
auto header_str = util::format("%1: %2", header.first, header.second);
list = curl_slist_append(list, header_str.c_str());
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_cb);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response_headers);

auto response_code = curl_easy_perform(curl);
if (response_code != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed when sending request to '%s' with body '%s': %s\n",
request.url.c_str(), request.body.c_str(), curl_easy_strerror(response_code));
}
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(curl);
curl_slist_free_all(list);
return {
static_cast<int>(http_code),
0, // binding_response_code
std::move(response_headers),
std::move(response),
std::nullopt
};
}
} // namespace

void default_http_transport::send_request_to_server(const ::realm::networking::request& request,
std::function<void(const ::realm::networking::response&)>&& completion_block) {
if (m_configuration.custom_http_headers) {
auto req_copy = request;
req_copy.headers.insert(m_configuration.custom_http_headers->begin(), m_configuration.custom_http_headers->end());
completion_block(do_http_request(req_copy, m_configuration.proxy_config));
return;
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_cb);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response_headers);

auto response_code = curl_easy_perform(curl);
if (response_code != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed when sending request to '%s' with body '%s': %s\n",
request.url.c_str(), request.body.c_str(), curl_easy_strerror(response_code));
}
completion_block(do_http_request(request, m_configuration.proxy_config));
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(curl);
curl_slist_free_all(list);
completion_block({
static_cast<int>(http_code),
0, // binding_response_code
std::move(response_headers),
std::move(response),
std::nullopt
});
}
} // namespace realm::networking
8 changes: 7 additions & 1 deletion src/cpprealm/internal/network/network_transport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,17 @@ namespace realm::networking {
m_ssl_context.use_default_verify();
}

if (m_configuration.ssl_verify_callback) {
socket.ssl_stream->use_verify_callback(std::move(m_configuration.ssl_verify_callback));
}

if (url_scheme == URLScheme::HTTPS) {
socket.ssl_stream.emplace(socket, m_ssl_context, Stream::client);
socket.ssl_stream->set_host_name(host); // Throws

socket.ssl_stream->set_verify_mode(VerifyMode::peer);
if (m_configuration.client_validate_ssl) {
socket.ssl_stream->set_verify_mode(VerifyMode::peer);
}
socket.ssl_stream->set_logger(logger.get());
}

Expand Down

0 comments on commit 2c9cae6

Please sign in to comment.