From 035ed495d3b9ae90613abc69731e87db974cb947 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 9 Oct 2023 16:58:31 +0100 Subject: [PATCH 1/3] Add encryption support --- CHANGELOG.md | 3 ++- src/cpprealm/app.cpp | 7 ++----- src/cpprealm/internal/bridge/realm.cpp | 7 +++++++ src/cpprealm/internal/bridge/realm.hpp | 1 + tests/experimental/sync/flexible_sync_tests.cpp | 17 +++++++++++++++++ 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ef403a5..46be05da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ X.Y.Z Release notes (YYYY-MM-DD) * Using a property type of vector of enums would cause a compilation error (since 0.1.0). ### Enhancements -* None +* The Sync metadata Realm is now encrypted by default unless the `REALM_DISABLE_METADATA_ENCRYPTION` environment variable is set. +* Add ability to encrypt a Realm. Usage: `realm::config::set_encryption_key(const std::array&)`. ### Breaking Changes * None diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index 20deccf8..96861e6f 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -352,11 +352,8 @@ namespace realm { #endif SyncClientConfig config; bool should_encrypt = !getenv("REALM_DISABLE_METADATA_ENCRYPTION"); -#if REALM_DISABLE_METADATA_ENCRYPTION - config.metadata_mode = SyncManager::MetadataMode::NoEncryption; -#else - config.metadata_mode = SyncManager::MetadataMode::NoEncryption; -#endif + config.metadata_mode = should_encrypt ? SyncManager::MetadataMode::Encryption : SyncManager::MetadataMode::NoEncryption; + #ifdef QT_CORE_LIB auto qt_path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).toStdString(); if (!std::filesystem::exists(qt_path)) { diff --git a/src/cpprealm/internal/bridge/realm.cpp b/src/cpprealm/internal/bridge/realm.cpp index 59d111c1..0ea38c09 100644 --- a/src/cpprealm/internal/bridge/realm.cpp +++ b/src/cpprealm/internal/bridge/realm.cpp @@ -269,6 +269,13 @@ namespace realm::internal::bridge { reinterpret_cast(&m_config)->schema_version = version; } + void realm::config::set_encryption_key(const std::array& encryption_key) { + auto key = std::vector(); + key.resize(64); + key.assign(encryption_key.begin(), encryption_key.end()); + reinterpret_cast(&m_config)->encryption_key = std::move(key); + } + realm::sync_config realm::config::sync_config() const { return reinterpret_cast(&m_config)->sync_config; } diff --git a/src/cpprealm/internal/bridge/realm.hpp b/src/cpprealm/internal/bridge/realm.hpp index 8400b2f3..a09a5ffb 100644 --- a/src/cpprealm/internal/bridge/realm.hpp +++ b/src/cpprealm/internal/bridge/realm.hpp @@ -150,6 +150,7 @@ namespace realm::internal::bridge { void set_sync_config(const std::optional&); void set_custom_http_headers(const std::map& headers); void set_schema_version(uint64_t version); + void set_encryption_key(const std::array&); std::optional get_schema(); private: #ifdef __i386__ diff --git a/tests/experimental/sync/flexible_sync_tests.cpp b/tests/experimental/sync/flexible_sync_tests.cpp index 23cbfaa5..f0bae056 100644 --- a/tests/experimental/sync/flexible_sync_tests.cpp +++ b/tests/experimental/sync/flexible_sync_tests.cpp @@ -75,4 +75,21 @@ TEST_CASE("flexible_sync_beta", "[sync]") { objs = synced_realm.objects(); CHECK(objs.size() == 1); } + + SECTION("encrypted sync realm") { + auto encrypted_app = realm::App(Admin::shared().cached_app_id(), Admin::shared().base_url()); + auto user = encrypted_app.login(realm::App::credentials::anonymous()).get(); + auto flx_sync_config = user.flexible_sync_configuration(); + flx_sync_config.set_encryption_key({0,0,0,0,0,0,0,0, 1,1,0,0,0,0,0,0, 2,2,0,0,0,0,0,0, 3,3,0,0,0,0,0,0, 4,4,0,0,0,0,0,0, 5,5,0,0,0,0,0,0, 6,6,0,0,0,0,0,0, 7,7,0,0,0,0,0,0}); + auto synced_realm = experimental::db(flx_sync_config); + + auto update_success = synced_realm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { + subs.clear(); + }).get(); + CHECK(update_success == true); + CHECK(synced_realm.subscriptions().size() == 0); + // Missing encryption key + auto flx_sync_config2 = user.flexible_sync_configuration(); + REQUIRE_THROWS(experimental::db(flx_sync_config2)); + } } \ No newline at end of file From 6093f59b6808bd3d05cbe37367554cf56df7c8cf Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Tue, 10 Oct 2023 11:03:38 +0100 Subject: [PATCH 2/3] Add encryption support --- CHANGELOG.md | 14 +++++- src/cpprealm/app.cpp | 50 +++++++++++++------ src/cpprealm/app.hpp | 11 ++++ tests/alpha/app_tests.cpp | 2 +- tests/alpha/asymmetric_object_tests.cpp | 2 +- tests/alpha/flx_sync_tests.cpp | 4 +- tests/experimental/db/realm_tests.cpp | 15 ++++++ tests/experimental/sync/app_tests.cpp | 4 +- .../sync/asymmetric_object_tests.cpp | 2 +- .../experimental/sync/flexible_sync_tests.cpp | 11 ++-- 10 files changed, 88 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46be05da..8f66721b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,21 @@ X.Y.Z Release notes (YYYY-MM-DD) * Using a property type of vector of enums would cause a compilation error (since 0.1.0). ### Enhancements -* The Sync metadata Realm is now encrypted by default unless the `REALM_DISABLE_METADATA_ENCRYPTION` environment variable is set. +* The Sync metadata Realm is now encrypted by default on Apple platforms unless the `REALM_DISABLE_METADATA_ENCRYPTION` environment variable is set. + To enable encryption on the metadata Realm on other platforms you must set an encryption key on `realm::App::configuration`. +```cpp +std::array example_key = {...}; +realm::App::configuration app_config; +app_config.app_id = ... +app_config.metadata_encryption_key = example_key; +auto encrypted_app = realm::App(app_config); +``` * Add ability to encrypt a Realm. Usage: `realm::config::set_encryption_key(const std::array&)`. ### Breaking Changes -* None +* `realm::App(const std::string &app_id, const std::optional &base_url, + const std::optional &path, const std::optional> &custom_http_headers)` has been deprecated. + use `realm::App(const realm::App::configuration&);` instead. ### Compatibility * Fileformat: Generates files with format v22. diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index 96861e6f..fae624e0 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -343,16 +343,24 @@ namespace realm { return credentials(app::AppCredentials::function(payload)); } - App::App(const std::string &app_id, - const std::optional &base_url, - const std::optional &path, - const std::optional> &custom_http_headers) { + App::App(const configuration& config) { #if QT_CORE_LIB util::Scheduler::set_default_factory(util::make_qt); #endif - SyncClientConfig config; + SyncClientConfig client_config; + +#if REALM_PLATFORM_APPLE bool should_encrypt = !getenv("REALM_DISABLE_METADATA_ENCRYPTION"); - config.metadata_mode = should_encrypt ? SyncManager::MetadataMode::Encryption : SyncManager::MetadataMode::NoEncryption; +#else + bool should_encrypt = config.metadata_encryption_key && !getenv("REALM_DISABLE_METADATA_ENCRYPTION"); +#endif + client_config.metadata_mode = should_encrypt ? SyncManager::MetadataMode::Encryption : SyncManager::MetadataMode::NoEncryption; + if (config.metadata_encryption_key) { + auto key = std::vector(); + key.resize(64); + key.assign(config.metadata_encryption_key->begin(), config.metadata_encryption_key->end()); + client_config.custom_encryption_key = std::move(key); + } #ifdef QT_CORE_LIB auto qt_path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).toStdString(); @@ -361,19 +369,19 @@ namespace realm { } config.base_file_path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).toStdString(); #else - if (path) { - config.base_file_path = *path; + if (config.path) { + client_config.base_file_path = *config.path; } else { - config.base_file_path = std::filesystem::current_path().make_preferred().generic_string(); + client_config.base_file_path = std::filesystem::current_path().make_preferred().generic_string(); } #endif - config.user_agent_binding_info = "RealmCpp/0.0.1"; - config.user_agent_application_info = app_id; + client_config.user_agent_binding_info = "RealmCpp/0.0.1"; + client_config.user_agent_application_info = config.app_id; auto app_config = app::App::Config(); - app_config.app_id = app_id; - app_config.transport = std::make_shared(custom_http_headers); - app_config.base_url = base_url ? base_url : util::Optional(); + app_config.app_id = config.app_id; + app_config.transport = std::make_shared(config.custom_http_headers); + app_config.base_url = config.base_url ? config.base_url : util::Optional(); auto device_info = app::App::Config::DeviceInfo(); device_info.framework_name = "Realm Cpp", @@ -382,7 +390,19 @@ namespace realm { device_info.sdk = "Realm Cpp"; app_config.device_info = std::move(device_info); - m_app = app::App::get_shared_app(std::move(app_config), config); + m_app = app::App::get_shared_app(std::move(app_config), client_config); + } + + App::App(const std::string &app_id, + const std::optional &base_url, + const std::optional &path, + const std::optional> &custom_http_headers) { + configuration c; + c.app_id = app_id; + c.base_url = base_url; + c.path = path; + c.custom_http_headers = custom_http_headers; + *this = App(std::move(c)); } std::future App::register_user(const std::string &username, const std::string &password) { diff --git a/src/cpprealm/app.hpp b/src/cpprealm/app.hpp index 127f41c0..affd1304 100644 --- a/src/cpprealm/app.hpp +++ b/src/cpprealm/app.hpp @@ -222,11 +222,22 @@ bool operator!=(const user& lhs, const user& rhs); class App { public: + struct configuration { + std::string app_id; + std::optional base_url; + std::optional path; + std::optional> custom_http_headers; + std::optional> metadata_encryption_key; + }; + + [[deprecated("Use App(const configuration&) instead.")]] explicit App(const std::string& app_id, const std::optional& base_url = {}, const std::optional& path = {}, const std::optional>& custom_http_headers = {}); + App(const configuration&); + struct credentials { using auth_code = util::TaggedString; using id_token = util::TaggedString; diff --git a/tests/alpha/app_tests.cpp b/tests/alpha/app_tests.cpp index 7d41d9e2..d16ce990 100644 --- a/tests/alpha/app_tests.cpp +++ b/tests/alpha/app_tests.cpp @@ -68,7 +68,7 @@ static std::string create_jwt(const std::string& appId) } TEST_CASE("app", "[app]") { - auto app = realm::App(Admin::shared().cached_app_id(), Admin::shared().base_url()); + auto app = realm::App(realm::App::configuration({Admin::shared().cached_app_id(), Admin::shared().base_url()})); SECTION("auth_providers_promise") { auto run_login = [&app](realm::App::credentials&& credentials) { diff --git a/tests/alpha/asymmetric_object_tests.cpp b/tests/alpha/asymmetric_object_tests.cpp index 6d7fbd84..c949bc58 100644 --- a/tests/alpha/asymmetric_object_tests.cpp +++ b/tests/alpha/asymmetric_object_tests.cpp @@ -8,7 +8,7 @@ using namespace realm; TEST_CASE("asymmetric object", "[sync]") { SECTION("basic", "[sync]") { auto asymmetric_app_id = Admin::shared().create_app({}, "test", true); - auto app = realm::App(asymmetric_app_id, Admin::shared().base_url()); + auto app = realm::App(realm::App::configuration({asymmetric_app_id, Admin::shared().base_url()})); auto user = app.login(realm::App::credentials::anonymous()).get(); auto p = realm::async_open(user.flexible_sync_configuration()); auto tsr = p.get_future().get(); diff --git a/tests/alpha/flx_sync_tests.cpp b/tests/alpha/flx_sync_tests.cpp index 4bcdabee..68420b7c 100644 --- a/tests/alpha/flx_sync_tests.cpp +++ b/tests/alpha/flx_sync_tests.cpp @@ -6,7 +6,7 @@ using namespace realm; TEST_CASE("flx_sync", "[sync]") { - auto app = realm::App(Admin::shared().cached_app_id(), Admin::shared().base_url()); + auto app = realm::App(realm::App::configuration({Admin::shared().cached_app_id(), Admin::shared().base_url()})); SECTION("all") { app.get_sync_manager().set_log_level(logger::level::off); auto user = app.login(realm::App::credentials::anonymous()).get(); @@ -92,7 +92,7 @@ TEST_CASE("flx_sync", "[sync]") { } TEST_CASE("realm_is_populated_on_async_open", "[sync]") { - auto app = realm::App(Admin::shared().cached_app_id(), Admin::shared().base_url()); + auto app = realm::App(realm::App::configuration({Admin::shared().cached_app_id(), Admin::shared().base_url()})); SECTION("all") { { auto user = app.login(realm::App::credentials::anonymous()).get(); diff --git a/tests/experimental/db/realm_tests.cpp b/tests/experimental/db/realm_tests.cpp index a5df3628..b77b506a 100644 --- a/tests/experimental/db/realm_tests.cpp +++ b/tests/experimental/db/realm_tests.cpp @@ -61,4 +61,19 @@ namespace realm::experimental { t.join(); p.get_future().get(); } + + TEST_CASE("encrypted realm") { + std::array example_key = {0,0,0,0,0,0,0,0, 1,1,0,0,0,0,0,0, 2,2,0,0,0,0,0,0, 3,3,0,0,0,0,0,0, 4,4,0,0,0,0,0,0, 5,5,0,0,0,0,0,0, 6,6,0,0,0,0,0,0, 7,7,0,0,0,0,0,0}; + realm_path path; + + auto config = realm::db_config(); + config.set_encryption_key(example_key); + config.set_path(path); + auto realm = experimental::db(config); + + // Missing encryption key + auto config2 = realm::db_config(); + config2.set_path(path); + REQUIRE_THROWS(experimental::db(config2)); + } } \ No newline at end of file diff --git a/tests/experimental/sync/app_tests.cpp b/tests/experimental/sync/app_tests.cpp index 6484c589..f146237b 100644 --- a/tests/experimental/sync/app_tests.cpp +++ b/tests/experimental/sync/app_tests.cpp @@ -6,7 +6,7 @@ using namespace realm; TEST_CASE("app", "[sync]") { - auto app = realm::App(Admin::shared().cached_app_id(), Admin::shared().base_url()); + auto app = realm::App(realm::App::configuration({Admin::shared().cached_app_id(), Admin::shared().base_url()})); SECTION("get_current_user") { auto user = app.login(realm::App::credentials::anonymous()).get(); @@ -31,7 +31,7 @@ TEST_CASE("app", "[sync]") { SECTION("clear_cached_apps") { auto temp_app_id = Admin::shared().create_app({"str_col", "_id"}); - auto temp_app = realm::App(temp_app_id, Admin::shared().base_url()); + auto temp_app = realm::App(realm::App::configuration({temp_app_id, Admin::shared().base_url()})); auto cached_app = temp_app.get_cached_app(temp_app_id, Admin::shared().base_url()); CHECK(cached_app.has_value()); app.clear_cached_apps(); diff --git a/tests/experimental/sync/asymmetric_object_tests.cpp b/tests/experimental/sync/asymmetric_object_tests.cpp index 8aed3d7d..04a07567 100644 --- a/tests/experimental/sync/asymmetric_object_tests.cpp +++ b/tests/experimental/sync/asymmetric_object_tests.cpp @@ -8,7 +8,7 @@ using namespace realm; TEST_CASE("asymmetric object", "[sync_beta]") { SECTION("basic", "[sync]") { auto asymmetric_app_id = Admin::shared().create_app({}, "test", true); - auto app = realm::App(asymmetric_app_id, Admin::shared().base_url()); + auto app = realm::App(realm::App::configuration({asymmetric_app_id, Admin::shared().base_url()})); auto user = app.login(realm::App::credentials::anonymous()).get(); auto synced_realm = experimental::open(user.flexible_sync_configuration()); diff --git a/tests/experimental/sync/flexible_sync_tests.cpp b/tests/experimental/sync/flexible_sync_tests.cpp index f0bae056..31b835d3 100644 --- a/tests/experimental/sync/flexible_sync_tests.cpp +++ b/tests/experimental/sync/flexible_sync_tests.cpp @@ -6,7 +6,7 @@ using namespace realm; TEST_CASE("flexible_sync_beta", "[sync]") { - auto app = realm::App(Admin::shared().cached_app_id(), Admin::shared().base_url()); + auto app = realm::App(realm::App::configuration({Admin::shared().cached_app_id(), Admin::shared().base_url()})); SECTION("all") { app.get_sync_manager().set_log_level(logger::level::all); auto user = app.login(realm::App::credentials::anonymous()).get(); @@ -77,10 +77,15 @@ TEST_CASE("flexible_sync_beta", "[sync]") { } SECTION("encrypted sync realm") { - auto encrypted_app = realm::App(Admin::shared().cached_app_id(), Admin::shared().base_url()); + std::array example_key = {0,0,0,0,0,0,0,0, 1,1,0,0,0,0,0,0, 2,2,0,0,0,0,0,0, 3,3,0,0,0,0,0,0, 4,4,0,0,0,0,0,0, 5,5,0,0,0,0,0,0, 6,6,0,0,0,0,0,0, 7,7,0,0,0,0,0,0}; + realm::App::configuration app_config; + app_config.app_id = Admin::shared().create_app({"str_col", "_id"}); + app_config.base_url = Admin::shared().base_url(); + app_config.metadata_encryption_key = example_key; + auto encrypted_app = realm::App(app_config); auto user = encrypted_app.login(realm::App::credentials::anonymous()).get(); auto flx_sync_config = user.flexible_sync_configuration(); - flx_sync_config.set_encryption_key({0,0,0,0,0,0,0,0, 1,1,0,0,0,0,0,0, 2,2,0,0,0,0,0,0, 3,3,0,0,0,0,0,0, 4,4,0,0,0,0,0,0, 5,5,0,0,0,0,0,0, 6,6,0,0,0,0,0,0, 7,7,0,0,0,0,0,0}); + flx_sync_config.set_encryption_key(example_key); auto synced_realm = experimental::db(flx_sync_config); auto update_success = synced_realm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { From 9ae926b8e64d4f550b9f0b8fc0cfcc60e610e8a9 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 16 Oct 2023 16:51:04 +0100 Subject: [PATCH 3/3] Omit redundant code --- src/cpprealm/app.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index fae624e0..dd5a49ec 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -381,7 +381,7 @@ namespace realm { auto app_config = app::App::Config(); app_config.app_id = config.app_id; app_config.transport = std::make_shared(config.custom_http_headers); - app_config.base_url = config.base_url ? config.base_url : util::Optional(); + app_config.base_url = config.base_url; auto device_info = app::App::Config::DeviceInfo(); device_info.framework_name = "Realm Cpp",