From 2c9cae69f9f6446477c613b8bd1585a60015f0a0 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 15 Jul 2024 09:58:01 +0100 Subject: [PATCH] Cleanup, add SSL verify callback --- include/cpprealm/app.hpp | 16 -- .../networking/platform_networking.hpp | 17 ++ src/cpprealm/app.cpp | 3 +- .../internal/curl/network_transport.cpp | 176 +++++++++++------- .../internal/network/network_transport.cpp | 8 +- 5 files changed, 129 insertions(+), 91 deletions(-) diff --git a/include/cpprealm/app.hpp b/include/cpprealm/app.hpp index 2b5856f2..d5740771 100644 --- a/include/cpprealm/app.hpp +++ b/include/cpprealm/app.hpp @@ -240,28 +240,12 @@ class App { std::optional base_url; /// Custom location for Realm files. std::optional 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> custom_http_headers; /// Custom encryption key for the metadata Realm. std::optional> 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 proxy_configuration; /** diff --git a/include/cpprealm/networking/platform_networking.hpp b/include/cpprealm/networking/platform_networking.hpp index a472850f..3e140bba 100644 --- a/include/cpprealm/networking/platform_networking.hpp +++ b/include/cpprealm/networking/platform_networking.hpp @@ -229,14 +229,31 @@ namespace realm::networking { void set_http_client_factory(std::function()>&&); 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> 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 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 ssl_verify_callback; }; diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index 8cbdadec..1325f4d4 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -1,7 +1,7 @@ #include #include -#include #include +#include #ifndef REALMCXX_VERSION_MAJOR #include @@ -13,7 +13,6 @@ #include #include #include -#include #include diff --git a/src/cpprealm/internal/curl/network_transport.cpp b/src/cpprealm/internal/curl/network_transport.cpp index cd9ec953..d3dfd549 100644 --- a/src/cpprealm/internal/curl/network_transport.cpp +++ b/src/cpprealm/internal/curl/network_transport.cpp @@ -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& 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; - 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); - /* 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&& 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(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&& 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(http_code), + 0, // binding_response_code + std::move(response_headers), + std::move(response), + std::nullopt + }); } } // namespace realm::networking diff --git a/src/cpprealm/internal/network/network_transport.cpp b/src/cpprealm/internal/network/network_transport.cpp index f3572182..e2f6b608 100644 --- a/src/cpprealm/internal/network/network_transport.cpp +++ b/src/cpprealm/internal/network/network_transport.cpp @@ -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()); }