From a7d9315af2137478217f966cc8fa284997ed641c Mon Sep 17 00:00:00 2001 From: Darnell Andries Date: Thu, 8 Aug 2024 15:22:22 -0700 Subject: [PATCH] Address Web Discovery feedback --- .../webui/welcome_page/welcome_dom_handler.cc | 3 + browser/web_discovery/BUILD.gn | 9 +- .../web_discovery_service_factory.cc | 27 ++-- .../web_discovery_service_factory.h | 6 +- .../web_discovery_service_factory_unittest.cc | 33 ----- components/constants/BUILD.gn | 5 +- components/constants/DEPS | 1 + components/constants/pref_names.h | 3 + components/web_discovery/browser/BUILD.gn | 5 +- .../browser/background_credential_helper.cc | 128 +++++++++++++++++ .../browser/background_credential_helper.h | 59 ++++++++ .../browser/credential_manager.cc | 134 ++---------------- .../browser/credential_manager.h | 42 +----- .../browser/credential_manager_unittest.cc | 4 +- components/web_discovery/browser/patterns.cc | 26 ++-- components/web_discovery/browser/pref_names.h | 2 - components/web_discovery/browser/rsa.cc | 29 +++- components/web_discovery/browser/rsa.h | 4 +- .../browser/server_config_loader.cc | 117 ++++++--------- .../browser/server_config_loader.h | 7 +- .../browser/web_discovery_service.cc | 2 - components/web_discovery/common/BUILD.gn | 3 +- .../credential_keys_and_responses.json | 1 - 23 files changed, 312 insertions(+), 338 deletions(-) delete mode 100644 browser/web_discovery/web_discovery_service_factory_unittest.cc create mode 100644 components/web_discovery/browser/background_credential_helper.cc create mode 100644 components/web_discovery/browser/background_credential_helper.h diff --git a/browser/ui/webui/welcome_page/welcome_dom_handler.cc b/browser/ui/webui/welcome_page/welcome_dom_handler.cc index 0478aaaaab8a..b828846b04e9 100644 --- a/browser/ui/webui/welcome_page/welcome_dom_handler.cc +++ b/browser/ui/webui/welcome_page/welcome_dom_handler.cc @@ -21,6 +21,7 @@ #include "chrome/common/webui_url_constants.h" #include "chrome/grit/branded_strings.h" #include "components/prefs/pref_service.h" +#include "extensions/buildflags/buildflags.h" #include "ui/base/l10n/l10n_util.h" namespace { @@ -170,7 +171,9 @@ void WelcomeDOMHandler::HandleSetMetricsReportingEnabled( void WelcomeDOMHandler::HandleEnableWebDiscovery( const base::Value::List& args) { DCHECK(profile_); +#if BUILDFLAG(ENABLE_EXTENSIONS) profile_->GetPrefs()->SetBoolean(kWebDiscoveryExtensionEnabled, true); +#endif } void WelcomeDOMHandler::SetLocalStateBooleanEnabled( diff --git a/browser/web_discovery/BUILD.gn b/browser/web_discovery/BUILD.gn index 1ec447b9c281..991df6173997 100644 --- a/browser/web_discovery/BUILD.gn +++ b/browser/web_discovery/BUILD.gn @@ -17,6 +17,7 @@ if (enable_web_discovery_native) { "//brave/components/web_discovery/browser", "//brave/components/web_discovery/common", "//chrome/browser:browser_process", + "//chrome/browser/profiles:profile", "//chrome/common:constants", "//components/keyed_service/content", "//components/user_prefs", @@ -44,14 +45,6 @@ source_set("unit_tests") { "//content/test:test_support", "//testing/gtest", ] - - if (enable_web_discovery_native) { - sources += [ "web_discovery_service_factory_unittest.cc" ] - deps += [ - ":web_discovery", - "//brave/components/web_discovery/common", - ] - } } } diff --git a/browser/web_discovery/web_discovery_service_factory.cc b/browser/web_discovery/web_discovery_service_factory.cc index 87522d736a7f..f6d0d29f2315 100644 --- a/browser/web_discovery/web_discovery_service_factory.cc +++ b/browser/web_discovery/web_discovery_service_factory.cc @@ -9,14 +9,25 @@ #include "brave/components/web_discovery/browser/web_discovery_service.h" #include "brave/components/web_discovery/common/features.h" #include "chrome/browser/browser_process.h" +#include "chrome/browser/profiles/profile_selections.h" #include "chrome/common/chrome_paths.h" -#include "components/keyed_service/content/browser_context_dependency_manager.h" #include "components/user_prefs/user_prefs.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/storage_partition.h" namespace web_discovery { +namespace { + +ProfileSelections GetProfileSelections() { + if (!base::FeatureList::IsEnabled(features::kBraveWebDiscoveryNative)) { + return ProfileSelections::BuildNoProfilesSelected(); + } + return ProfileSelections::BuildForRegularProfile(); +} + +} // namespace + WebDiscoveryService* WebDiscoveryServiceFactory::GetForBrowserContext( content::BrowserContext* context) { return static_cast( @@ -29,9 +40,8 @@ WebDiscoveryServiceFactory* WebDiscoveryServiceFactory::GetInstance() { } WebDiscoveryServiceFactory::WebDiscoveryServiceFactory() - : BrowserContextKeyedServiceFactory( - "WebDiscoveryService", - BrowserContextDependencyManager::GetInstance()) {} + : ProfileKeyedServiceFactory("WebDiscoveryService", + GetProfileSelections()) {} WebDiscoveryServiceFactory::~WebDiscoveryServiceFactory() = default; @@ -47,15 +57,6 @@ KeyedService* WebDiscoveryServiceFactory::BuildServiceInstanceFor( user_data_dir, shared_url_loader_factory); } -content::BrowserContext* WebDiscoveryServiceFactory::GetBrowserContextToUse( - content::BrowserContext* context) const { - if (!base::FeatureList::IsEnabled(features::kBraveWebDiscoveryNative)) { - return nullptr; - } - // Prevents creation of service instance for incognito/OTR profiles - return context->IsOffTheRecord() ? nullptr : context; -} - bool WebDiscoveryServiceFactory::ServiceIsCreatedWithBrowserContext() const { return true; } diff --git a/browser/web_discovery/web_discovery_service_factory.h b/browser/web_discovery/web_discovery_service_factory.h index 48687f69b531..8e914b91b039 100644 --- a/browser/web_discovery/web_discovery_service_factory.h +++ b/browser/web_discovery/web_discovery_service_factory.h @@ -7,13 +7,13 @@ #define BRAVE_BROWSER_WEB_DISCOVERY_WEB_DISCOVERY_SERVICE_FACTORY_H_ #include "base/no_destructor.h" -#include "components/keyed_service/content/browser_context_keyed_service_factory.h" +#include "chrome/browser/profiles/profile_keyed_service_factory.h" namespace web_discovery { class WebDiscoveryService; -class WebDiscoveryServiceFactory : public BrowserContextKeyedServiceFactory { +class WebDiscoveryServiceFactory : public ProfileKeyedServiceFactory { public: static WebDiscoveryServiceFactory* GetInstance(); static WebDiscoveryService* GetForBrowserContext( @@ -31,8 +31,6 @@ class WebDiscoveryServiceFactory : public BrowserContextKeyedServiceFactory { KeyedService* BuildServiceInstanceFor( content::BrowserContext* context) const override; - content::BrowserContext* GetBrowserContextToUse( - content::BrowserContext* context) const override; bool ServiceIsCreatedWithBrowserContext() const override; }; diff --git a/browser/web_discovery/web_discovery_service_factory_unittest.cc b/browser/web_discovery/web_discovery_service_factory_unittest.cc deleted file mode 100644 index cbda07dfa63b..000000000000 --- a/browser/web_discovery/web_discovery_service_factory_unittest.cc +++ /dev/null @@ -1,33 +0,0 @@ -/* Copyright (c) 2024 The Brave Authors. All rights reserved. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at https://mozilla.org/MPL/2.0/. */ - -#include "brave/browser/web_discovery/web_discovery_service_factory.h" - -#include "base/test/scoped_feature_list.h" -#include "brave/components/web_discovery/common/features.h" -#include "chrome/test/base/testing_browser_process.h" -#include "chrome/test/base/testing_profile_manager.h" -#include "content/public/test/browser_task_environment.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace web_discovery { - -TEST(WebDiscoveryServiceFactoryTest, PrivateNotCreated) { - base::test::ScopedFeatureList scoped_features( - features::kBraveWebDiscoveryNative); - content::BrowserTaskEnvironment task_environment; - auto* browser_process = TestingBrowserProcess::GetGlobal(); - TestingProfileManager profile_manager(browser_process); - ASSERT_TRUE(profile_manager.SetUp()); - - auto* profile = profile_manager.CreateTestingProfile("test"); - - EXPECT_TRUE(WebDiscoveryServiceFactory::GetForBrowserContext(profile)); - EXPECT_FALSE(WebDiscoveryServiceFactory::GetForBrowserContext( - profile->GetOffTheRecordProfile( - Profile::OTRProfileID::CreateUniqueForTesting(), true))); -} - -} // namespace web_discovery diff --git a/components/constants/BUILD.gn b/components/constants/BUILD.gn index e5aa1ab647ab..0c078e684f71 100644 --- a/components/constants/BUILD.gn +++ b/components/constants/BUILD.gn @@ -29,7 +29,10 @@ source_set("constants") { ] public_deps = [ ":brave_services_key" ] - deps = [ "//base" ] + deps = [ + "//base", + "//extensions/buildflags", + ] } source_set("brave_service_key_helper") { diff --git a/components/constants/DEPS b/components/constants/DEPS index 08b52cb8cbf3..055e4869975b 100644 --- a/components/constants/DEPS +++ b/components/constants/DEPS @@ -1,3 +1,4 @@ include_rules = [ + "+extensions/buildflags", "+extensions/common", ] diff --git a/components/constants/pref_names.h b/components/constants/pref_names.h index c0c832447925..8c459d21b0ed 100644 --- a/components/constants/pref_names.h +++ b/components/constants/pref_names.h @@ -7,6 +7,7 @@ #define BRAVE_COMPONENTS_CONSTANTS_PREF_NAMES_H_ #include "build/build_config.h" +#include "extensions/buildflags/buildflags.h" inline constexpr char kBraveAutofillPrivateWindows[] = "brave.autofill_private_windows"; @@ -85,8 +86,10 @@ inline constexpr char kBraveShieldsSettingsVersion[] = inline constexpr char kDefaultBrowserPromptEnabled[] = "brave.default_browser_prompt_enabled"; +#if BUILDFLAG(ENABLE_EXTENSIONS) inline constexpr char kWebDiscoveryExtensionEnabled[] = "brave.web_discovery_enabled"; +#endif inline constexpr char kWebDiscoveryCTAState[] = "brave.web_discovery.cta_state"; inline constexpr char kDontAskEnableWebDiscovery[] = "brave.dont_ask_enable_web_discovery"; diff --git a/components/web_discovery/browser/BUILD.gn b/components/web_discovery/browser/BUILD.gn index dc19dce58ff2..16e4467c5f15 100644 --- a/components/web_discovery/browser/BUILD.gn +++ b/components/web_discovery/browser/BUILD.gn @@ -7,8 +7,11 @@ import("//brave/components/web_discovery/buildflags/buildflags.gni") assert(enable_web_discovery_native) -static_library("browser") { +component("browser") { + output_name = "web_discovery_browser" sources = [ + "background_credential_helper.cc", + "background_credential_helper.h", "credential_manager.cc", "credential_manager.h", "credential_signer.h", diff --git a/components/web_discovery/browser/background_credential_helper.cc b/components/web_discovery/browser/background_credential_helper.cc new file mode 100644 index 000000000000..8eaf90135f13 --- /dev/null +++ b/components/web_discovery/browser/background_credential_helper.cc @@ -0,0 +1,128 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/web_discovery/browser/background_credential_helper.h" + +#include + +#include "base/base64.h" +#include "base/containers/span_rust.h" +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" +#include "crypto/sha2.h" + +namespace web_discovery { + +BackgroundCredentialHelper::BackgroundCredentialHelper() + : anonymous_credential_manager_( + anonymous_credentials::new_credential_manager()) {} + +BackgroundCredentialHelper::~BackgroundCredentialHelper() = default; + +void BackgroundCredentialHelper::UseFixedSeedForTesting() { + anonymous_credential_manager_ = + anonymous_credentials::new_credential_manager_with_fixed_seed(); +} + +std::unique_ptr BackgroundCredentialHelper::GenerateRSAKey() { + auto key_pair = GenerateRSAKeyPair(); + if (!key_pair) { + return nullptr; + } + rsa_private_key_ = std::move(key_pair->key_pair); + return key_pair; +} + +void BackgroundCredentialHelper::SetRSAKey( + std::unique_ptr rsa_private_key) { + rsa_private_key_ = std::move(rsa_private_key); +} + +std::optional +BackgroundCredentialHelper::GenerateJoinRequest(std::string pre_challenge) { + base::AssertLongCPUWorkAllowed(); + CHECK(rsa_private_key_); + auto challenge = crypto::SHA256Hash(base::as_byte_span(pre_challenge)); + + auto join_request = anonymous_credential_manager_->start_join( + base::SpanToRustSlice(challenge)); + + auto signature = RSASign(rsa_private_key_.get(), join_request.join_request); + + if (!signature) { + VLOG(1) << "RSA signature failed"; + return std::nullopt; + } + + return GenerateJoinRequestResult{.start_join_result = join_request, + .signature = *signature}; +} + +std::optional BackgroundCredentialHelper::FinishJoin( + std::string date, + std::vector group_pub_key, + std::vector gsk, + std::vector join_resp_bytes) { + base::AssertLongCPUWorkAllowed(); + auto pub_key_result = anonymous_credentials::load_group_public_key( + base::SpanToRustSlice(group_pub_key)); + auto gsk_result = + anonymous_credentials::load_credential_big(base::SpanToRustSlice(gsk)); + auto join_resp_result = anonymous_credentials::load_join_response( + base::SpanToRustSlice(join_resp_bytes)); + if (!pub_key_result.error_message.empty() || + !gsk_result.error_message.empty() || + !join_resp_result.error_message.empty()) { + VLOG(1) << "Failed to finish credential join due to deserialization error " + "with group pub key, gsk, or join response: " + << pub_key_result.error_message.c_str() + << gsk_result.error_message.c_str() + << join_resp_result.error_message.c_str(); + return std::nullopt; + } + auto finish_res = anonymous_credential_manager_->finish_join( + *pub_key_result.value, *gsk_result.value, + std::move(join_resp_result.value)); + if (!finish_res.error_message.empty()) { + VLOG(1) << "Failed to finish credential join for " << date << ": " + << finish_res.error_message.c_str(); + return std::nullopt; + } + return base::Base64Encode(finish_res.data); +} + +std::optional> +BackgroundCredentialHelper::PerformSign( + std::vector msg, + std::vector basename, + std::optional> gsk_bytes, + std::optional> credential_bytes) { + base::AssertLongCPUWorkAllowed(); + if (gsk_bytes && credential_bytes) { + auto gsk_result = anonymous_credentials::load_credential_big( + base::SpanToRustSlice(*gsk_bytes)); + auto credential_result = anonymous_credentials::load_user_credentials( + base::SpanToRustSlice(*credential_bytes)); + if (!gsk_result.error_message.empty() || + !credential_result.error_message.empty()) { + VLOG(1) << "Failed to sign due to deserialization error with gsk, or " + "user credential: " + << gsk_result.error_message.c_str() + << credential_result.error_message.c_str(); + return std::nullopt; + } + anonymous_credential_manager_->set_gsk_and_credentials( + std::move(gsk_result.value), std::move(credential_result.value)); + } + auto sig_res = anonymous_credential_manager_->sign( + base::SpanToRustSlice(msg), base::SpanToRustSlice(basename)); + if (!sig_res.error_message.empty()) { + VLOG(1) << "Failed to sign: " << sig_res.error_message.c_str(); + return std::nullopt; + } + return std::vector(sig_res.data.begin(), sig_res.data.end()); +} + +} // namespace web_discovery diff --git a/components/web_discovery/browser/background_credential_helper.h b/components/web_discovery/browser/background_credential_helper.h new file mode 100644 index 000000000000..970efe823191 --- /dev/null +++ b/components/web_discovery/browser/background_credential_helper.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_BACKGROUND_CREDENTIAL_HELPER_H_ +#define BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_BACKGROUND_CREDENTIAL_HELPER_H_ + +#include +#include +#include +#include + +#include "brave/components/web_discovery/browser/anonymous_credentials/rs/cxx/src/lib.rs.h" +#include "brave/components/web_discovery/browser/rsa.h" +#include "crypto/rsa_private_key.h" + +namespace web_discovery { + +struct GenerateJoinRequestResult { + anonymous_credentials::StartJoinResult start_join_result; + std::string signature; +}; + +class BackgroundCredentialHelper { + public: + BackgroundCredentialHelper(); + ~BackgroundCredentialHelper(); + + BackgroundCredentialHelper(const BackgroundCredentialHelper&) = delete; + BackgroundCredentialHelper& operator=(const BackgroundCredentialHelper&) = + delete; + + void UseFixedSeedForTesting(); + + std::unique_ptr GenerateRSAKey(); + void SetRSAKey(std::unique_ptr rsa_private_key); + std::optional GenerateJoinRequest( + std::string pre_challenge); + std::optional FinishJoin( + std::string date, + std::vector group_pub_key, + std::vector gsk, + std::vector join_resp_bytes); + std::optional> PerformSign( + std::vector msg, + std::vector basename, + std::optional> gsk_bytes, + std::optional> credential_bytes); + + private: + rust::Box + anonymous_credential_manager_; + std::unique_ptr rsa_private_key_; +}; + +} // namespace web_discovery + +#endif // BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_BACKGROUND_CREDENTIAL_HELPER_H_ diff --git a/components/web_discovery/browser/credential_manager.cc b/components/web_discovery/browser/credential_manager.cc index 0c5ccca25dc1..495a314e35bf 100644 --- a/components/web_discovery/browser/credential_manager.cc +++ b/components/web_discovery/browser/credential_manager.cc @@ -9,22 +9,16 @@ #include "base/base64.h" #include "base/containers/span.h" -#include "base/containers/span_rust.h" #include "base/functional/bind.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/logging.h" -#include "base/task/sequenced_task_runner.h" #include "base/task/thread_pool.h" -#include "base/threading/thread_restrictions.h" -#include "brave/components/web_discovery/browser/anonymous_credentials/rs/cxx/src/lib.rs.h" #include "brave/components/web_discovery/browser/pref_names.h" #include "brave/components/web_discovery/browser/rsa.h" #include "brave/components/web_discovery/browser/util.h" #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" -#include "crypto/rsa_private_key.h" -#include "crypto/sha2.h" #include "services/network/public/cpp/shared_url_loader_factory.h" #include "services/network/public/cpp/simple_url_loader.h" #include "services/network/public/mojom/url_response_head.mojom.h" @@ -66,116 +60,6 @@ constexpr net::NetworkTrafficAnnotationTag kJoinNetworkTrafficAnnotation = } // namespace -BackgroundCredentialHelper::BackgroundCredentialHelper() - : anonymous_credential_manager_( - anonymous_credentials::new_credential_manager()) {} - -BackgroundCredentialHelper::~BackgroundCredentialHelper() = default; - -void BackgroundCredentialHelper::UseFixedSeedForTesting() { - anonymous_credential_manager_ = - anonymous_credentials::new_credential_manager_with_fixed_seed(); -} - -std::unique_ptr BackgroundCredentialHelper::GenerateRSAKey() { - auto key_pair = GenerateRSAKeyPair(); - if (!key_pair) { - return nullptr; - } - rsa_private_key_ = std::move(key_pair->key_pair); - return key_pair; -} - -void BackgroundCredentialHelper::SetRSAKey( - std::unique_ptr rsa_private_key) { - rsa_private_key_ = std::move(rsa_private_key); -} - -std::optional -BackgroundCredentialHelper::GenerateJoinRequest(std::string pre_challenge) { - base::AssertLongCPUWorkAllowed(); - CHECK(rsa_private_key_); - auto challenge = crypto::SHA256Hash(base::as_byte_span(pre_challenge)); - - auto join_request = anonymous_credential_manager_->start_join( - base::SpanToRustSlice(challenge)); - - auto signature = RSASign(rsa_private_key_.get(), join_request.join_request); - - if (!signature) { - VLOG(1) << "RSA signature failed"; - return std::nullopt; - } - - return GenerateJoinRequestResult{.start_join_result = join_request, - .signature = *signature}; -} - -std::optional BackgroundCredentialHelper::FinishJoin( - std::string date, - std::vector group_pub_key, - std::vector gsk, - std::vector join_resp_bytes) { - base::AssertLongCPUWorkAllowed(); - auto pub_key_result = anonymous_credentials::load_group_public_key( - base::SpanToRustSlice(group_pub_key)); - auto gsk_result = - anonymous_credentials::load_credential_big(base::SpanToRustSlice(gsk)); - auto join_resp_result = anonymous_credentials::load_join_response( - base::SpanToRustSlice(join_resp_bytes)); - if (!pub_key_result.error_message.empty() || - !gsk_result.error_message.empty() || - !join_resp_result.error_message.empty()) { - VLOG(1) << "Failed to finish credential join due to deserialization error " - "with group pub key, gsk, or join response: " - << pub_key_result.error_message.c_str() - << gsk_result.error_message.c_str() - << join_resp_result.error_message.c_str(); - return std::nullopt; - } - auto finish_res = anonymous_credential_manager_->finish_join( - *pub_key_result.value, *gsk_result.value, - std::move(join_resp_result.value)); - if (!finish_res.error_message.empty()) { - VLOG(1) << "Failed to finish credential join for " << date << ": " - << finish_res.error_message.c_str(); - return std::nullopt; - } - return base::Base64Encode(finish_res.data); -} - -std::optional> -BackgroundCredentialHelper::PerformSign( - std::vector msg, - std::vector basename, - std::optional> gsk_bytes, - std::optional> credential_bytes) { - base::AssertLongCPUWorkAllowed(); - if (gsk_bytes && credential_bytes) { - auto gsk_result = anonymous_credentials::load_credential_big( - base::SpanToRustSlice(*gsk_bytes)); - auto credential_result = anonymous_credentials::load_user_credentials( - base::SpanToRustSlice(*credential_bytes)); - if (!gsk_result.error_message.empty() || - !credential_result.error_message.empty()) { - VLOG(1) << "Failed to sign due to deserialization error with gsk, or " - "user credential: " - << gsk_result.error_message.c_str() - << credential_result.error_message.c_str(); - return std::nullopt; - } - anonymous_credential_manager_->set_gsk_and_credentials( - std::move(gsk_result.value), std::move(credential_result.value)); - } - auto sig_res = anonymous_credential_manager_->sign( - base::SpanToRustSlice(msg), base::SpanToRustSlice(basename)); - if (!sig_res.error_message.empty()) { - VLOG(1) << "Failed to sign: " << sig_res.error_message.c_str(); - return std::nullopt; - } - return std::vector(sig_res.data.begin(), sig_res.data.end()); -} - CredentialManager::CredentialManager( PrefService* profile_prefs, network::SharedURLLoaderFactory* shared_url_loader_factory, @@ -193,22 +77,22 @@ CredentialManager::~CredentialManager() = default; bool CredentialManager::LoadRSAKey() { std::string private_key_b64 = profile_prefs_->GetString(kCredentialRSAPrivateKey); - rsa_public_key_b64_ = profile_prefs_->GetString(kCredentialRSAPublicKey); - if (private_key_b64.empty() || rsa_public_key_b64_->empty()) { - rsa_public_key_b64_ = std::nullopt; + if (private_key_b64.empty()) { return true; } - auto key_pair = ImportRSAKeyPair(private_key_b64); - if (!key_pair) { + auto key_info = ImportRSAKeyPair(private_key_b64); + if (!key_info) { VLOG(1) << "Failed to import stored RSA key"; - rsa_public_key_b64_ = std::nullopt; return false; } + + CHECK(key_info->key_pair); + rsa_public_key_b64_ = std::move(key_info->public_key_b64); background_credential_helper_ .AsyncCall(&BackgroundCredentialHelper::SetRSAKey) - .WithArgs(std::move(key_pair)); + .WithArgs(std::move(key_info->key_pair)); return true; } @@ -220,10 +104,10 @@ void CredentialManager::OnNewRSAKey(std::unique_ptr key_info) { } rsa_public_key_b64_ = key_info->public_key_b64; + CHECK(key_info->key_pair && key_info->private_key_b64); profile_prefs_->SetString(kCredentialRSAPrivateKey, - key_info->private_key_b64); - profile_prefs_->SetString(kCredentialRSAPublicKey, *rsa_public_key_b64_); + *key_info->private_key_b64); JoinGroups(); } diff --git a/components/web_discovery/browser/credential_manager.h b/components/web_discovery/browser/credential_manager.h index 18841571de24..1fa9d9eb4706 100644 --- a/components/web_discovery/browser/credential_manager.h +++ b/components/web_discovery/browser/credential_manager.h @@ -11,16 +11,13 @@ #include #include -#include "base/functional/callback.h" #include "base/memory/raw_ptr.h" #include "base/task/sequenced_task_runner.h" #include "base/threading/sequence_bound.h" #include "base/timer/wall_clock_timer.h" -#include "brave/components/web_discovery/browser/anonymous_credentials/rs/cxx/src/lib.rs.h" +#include "brave/components/web_discovery/browser/background_credential_helper.h" #include "brave/components/web_discovery/browser/credential_signer.h" -#include "brave/components/web_discovery/browser/rsa.h" #include "brave/components/web_discovery/browser/server_config_loader.h" -#include "crypto/rsa_private_key.h" #include "net/base/backoff_entry.h" class PrefService; @@ -32,43 +29,6 @@ class SimpleURLLoader; namespace web_discovery { -struct GenerateJoinRequestResult { - anonymous_credentials::StartJoinResult start_join_result; - std::string signature; -}; - -class BackgroundCredentialHelper { - public: - BackgroundCredentialHelper(); - ~BackgroundCredentialHelper(); - - BackgroundCredentialHelper(const BackgroundCredentialHelper&) = delete; - BackgroundCredentialHelper& operator=(const BackgroundCredentialHelper&) = - delete; - - void UseFixedSeedForTesting(); - - std::unique_ptr GenerateRSAKey(); - void SetRSAKey(std::unique_ptr rsa_private_key); - std::optional GenerateJoinRequest( - std::string pre_challenge); - std::optional FinishJoin( - std::string date, - std::vector group_pub_key, - std::vector gsk, - std::vector join_resp_bytes); - std::optional> PerformSign( - std::vector msg, - std::vector basename, - std::optional> gsk_bytes, - std::optional> credential_bytes); - - private: - rust::Box - anonymous_credential_manager_; - std::unique_ptr rsa_private_key_; -}; - // Manages and utilizes anonymous credentials used for communicating // with Web Discovery servers. These Direct Anonymous Attestation credentials // are used to prevent Sybil attacks on the servers. diff --git a/components/web_discovery/browser/credential_manager_unittest.cc b/components/web_discovery/browser/credential_manager_unittest.cc index b7237ced323a..d3efa10710f1 100644 --- a/components/web_discovery/browser/credential_manager_unittest.cc +++ b/components/web_discovery/browser/credential_manager_unittest.cc @@ -56,12 +56,10 @@ class WebDiscoveryCredentialManagerTest : public testing::Test { ASSERT_TRUE(test_data_value); const auto& test_data_dict = test_data_value->GetDict(); const auto* rsa_priv_key = test_data_dict.FindString("rsa_priv_key"); - const auto* rsa_pub_key = test_data_dict.FindString("rsa_pub_key"); const auto* group_pub_key = test_data_dict.FindString("group_pub_key"); const auto* join_responses = test_data_dict.FindDict("join_responses"); - ASSERT_TRUE(rsa_priv_key && rsa_pub_key && group_pub_key && join_responses); + ASSERT_TRUE(rsa_priv_key && group_pub_key && join_responses); - profile_prefs_.SetString(kCredentialRSAPublicKey, *rsa_pub_key); profile_prefs_.SetString(kCredentialRSAPrivateKey, *rsa_priv_key); server_config_loader_ = std::make_unique( diff --git a/components/web_discovery/browser/patterns.cc b/components/web_discovery/browser/patterns.cc index d0e7457dd5cd..dea62ea3b36d 100644 --- a/components/web_discovery/browser/patterns.cc +++ b/components/web_discovery/browser/patterns.cc @@ -67,8 +67,8 @@ bool ParsePayloadRule(const base::Value& rule_value, PayloadRule* rule_out) { VLOG(1) << "Selector or key missing from payload rule"; return false; } - rule_out->selector = *selector; - rule_out->key = *payload_key; + rule_out->selector = std::move(*selector); + rule_out->key = std::move(*payload_key); if (rule_list->size() > 2) { auto* field_action = (*rule_list)[2].GetIfString(); if (field_action && *field_action == kJoinFieldAction) { @@ -103,8 +103,8 @@ std::optional> ParsePayloadRules( VLOG(1) << "Payload rule or result types unknown"; return std::nullopt; } - rule_group_it->action = *action; - rule_group_it->key = key; + rule_group_it->action = std::move(*action); + rule_group_it->key = std::move(key); rule_group_it->result_type = result_type_it->second; rule_group_it->rule_type = rule_type_it->second; if (auto* fields = rule_group_dict->FindList(kFieldsKey)) { @@ -127,16 +127,12 @@ std::optional> ParsePayloadRules( RefineFunctionList ParseFunctionsApplied(const base::Value::List* list) { CHECK(list); RefineFunctionList result; - for (const auto& function_val : *list) { - const auto* function_list = function_val.GetIfList(); + for (auto& function_val : *list) { + auto* function_list = function_val.GetIfList(); if (!function_list || function_list->size() <= 1) { continue; } - base::Value::List function_vec; - for (const auto& element : *function_list) { - function_vec.Append(element.Clone()); - } - result.push_back(std::move(function_vec)); + result.push_back(function_list->Clone()); } return result; } @@ -175,9 +171,9 @@ std::optional> ParseScrapeRules( rule->rule_type = rule_type_it->second; } } - rule->attribute = *attribute; + rule->attribute = std::move(*attribute); if (sub_selector) { - rule->sub_selector = *sub_selector; + rule->sub_selector = std::move(*sub_selector); } if (functions_applied) { rule->functions_applied = ParseFunctionsApplied(functions_applied); @@ -229,7 +225,7 @@ std::optional> ParsePatternsURLDetails( VLOG(1) << "ID or scrape dict missing for pattern"; return std::nullopt; } - details.id = *id; + details.id = std::move(*id); details.is_search_engine = base::Contains(*search_engines_list, i_str); @@ -250,7 +246,7 @@ std::optional> ParsePatternsURLDetails( if (query_template_dict) { auto* prefix = query_template_dict->FindString(kQueryTemplatePrefixKey); if (prefix) { - details.search_template_prefix = *prefix; + details.search_template_prefix = std::move(*prefix); } } } diff --git a/components/web_discovery/browser/pref_names.h b/components/web_discovery/browser/pref_names.h index b2f39a2cb7f6..6f6e1ecb3e7f 100644 --- a/components/web_discovery/browser/pref_names.h +++ b/components/web_discovery/browser/pref_names.h @@ -18,8 +18,6 @@ inline constexpr char kWebDiscoveryNativeEnabled[] = // they do not require secure storage. inline constexpr char kCredentialRSAPrivateKey[] = "brave.web_discovery.rsa_priv_key"; -inline constexpr char kCredentialRSAPublicKey[] = - "brave.web_discovery.rsa_pub_key"; inline constexpr char kAnonymousCredentialsDict[] = "brave.web_discovery.anon_creds"; diff --git a/components/web_discovery/browser/rsa.cc b/components/web_discovery/browser/rsa.cc index 6b2b50cf16bd..1dc2ec67e253 100644 --- a/components/web_discovery/browser/rsa.cc +++ b/components/web_discovery/browser/rsa.cc @@ -26,37 +26,54 @@ RSAKeyInfo::~RSAKeyInfo() = default; std::unique_ptr GenerateRSAKeyPair() { base::AssertLongCPUWorkAllowed(); - auto info = std::make_unique(); auto private_key = crypto::RSAPrivateKey::Create(kRsaKeySize); if (!private_key) { return nullptr; } - info->key_pair = std::move(private_key); + auto key_pair = std::move(private_key); std::vector encoded_public_key; std::vector encoded_private_key; - if (!info->key_pair->ExportPrivateKey(&encoded_private_key) || - !info->key_pair->ExportPublicKey(&encoded_public_key)) { + if (!key_pair->ExportPrivateKey(&encoded_private_key) || + !key_pair->ExportPublicKey(&encoded_public_key)) { return nullptr; } + auto info = std::make_unique(); + + info->key_pair = std::move(key_pair); info->public_key_b64 = base::Base64Encode(encoded_public_key); info->private_key_b64 = base::Base64Encode(encoded_private_key); return info; } -std::unique_ptr ImportRSAKeyPair( +std::unique_ptr ImportRSAKeyPair( const std::string& private_key_b64) { auto decoded_key = base::Base64Decode(private_key_b64); if (!decoded_key) { return nullptr; } - return crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(*decoded_key); + auto key_pair = crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(*decoded_key); + if (!key_pair) { + return nullptr; + } + + std::vector encoded_public_key; + if (!key_pair->ExportPublicKey(&encoded_public_key)) { + return nullptr; + } + + auto info = std::make_unique(); + + info->key_pair = std::move(key_pair); + info->public_key_b64 = base::Base64Encode(encoded_public_key); + + return info; } std::optional RSASign(crypto::RSAPrivateKey* key, diff --git a/components/web_discovery/browser/rsa.h b/components/web_discovery/browser/rsa.h index e940649fcdd1..35861bd8ec61 100644 --- a/components/web_discovery/browser/rsa.h +++ b/components/web_discovery/browser/rsa.h @@ -19,13 +19,13 @@ struct RSAKeyInfo { RSAKeyInfo(); ~RSAKeyInfo(); std::unique_ptr key_pair; - std::string private_key_b64; + std::optional private_key_b64; std::string public_key_b64; }; std::unique_ptr GenerateRSAKeyPair(); -std::unique_ptr ImportRSAKeyPair( +std::unique_ptr ImportRSAKeyPair( const std::string& private_key_b64); std::optional RSASign(crypto::RSAPrivateKey* key, diff --git a/components/web_discovery/browser/server_config_loader.cc b/components/web_discovery/browser/server_config_loader.cc index b9ecd4bbeac2..ae01a5770601 100644 --- a/components/web_discovery/browser/server_config_loader.cc +++ b/components/web_discovery/browser/server_config_loader.cc @@ -88,7 +88,7 @@ KeyMap ParseKeys(const base::Value::Dict& encoded_keys) { if (!decoded_data) { continue; } - map[date] = *decoded_data; + map[date] = std::move(*decoded_data); } return map; } @@ -107,9 +107,9 @@ ParseSourceMapActionConfigs(const base::Value::Dict& configs_dict) { } auto* keys_list = config_dict->FindList(kKeysFieldName); if (keys_list) { - for (const auto& key_val : *keys_list) { + for (auto& key_val : *keys_list) { if (key_val.is_string()) { - action_config->keys.push_back(key_val.GetString()); + action_config->keys.push_back(std::move(key_val.GetString())); } } } @@ -122,26 +122,35 @@ ParseSourceMapActionConfigs(const base::Value::Dict& configs_dict) { return map; } -std::optional GunzipContents(std::string gzipped_contents) { - base::AssertLongCPUWorkAllowed(); - std::string result; - if (!compression::GzipUncompress(gzipped_contents, &result)) { - return std::nullopt; +std::unique_ptr ParseAndWritePatternsFile( + base::FilePath patterns_path, + std::string gzipped_contents) { + std::string uncompressed_contents; + if (!compression::GzipUncompress(gzipped_contents, &uncompressed_contents)) { + VLOG(1) << "Failed to uncompress patterns"; + return nullptr; + } + auto patterns = ParsePatterns(uncompressed_contents); + if (!patterns) { + return nullptr; } - return result; -} -bool WritePatternsFile(base::FilePath patterns_path, std::string contents) { - return base::WriteFile(patterns_path, contents); + if (!base::WriteFile(patterns_path, uncompressed_contents)) { + VLOG(1) << "Failed to write patterns file"; + return nullptr; + } + return patterns; } -std::optional ReadPatternsFile(base::FilePath patterns_path) { +std::unique_ptr ReadAndParsePatternsFile( + base::FilePath patterns_path) { std::string contents; if (!base::ReadFileToStringWithMaxSize(patterns_path, &contents, kPatternsMaxFileSize)) { - return std::nullopt; + VLOG(1) << "Failed to read local patterns file"; + return nullptr; } - return contents; + return ParsePatterns(contents); } std::unique_ptr ProcessConfigResponses( @@ -196,7 +205,7 @@ std::unique_ptr ProcessConfigResponses( const auto* location = quorum_root->FindString(kLocationFieldName); if (location && kAllowedReportLocations.contains(*location)) { - config->location = *location; + config->location = std::move(*location); } else { config->location = kOmittedLocationValue; } @@ -219,7 +228,8 @@ ServerConfig::~ServerConfig() = default; ServerConfigDownloadResult::ServerConfigDownloadResult( bool is_collector_config, std::optional response_body) - : is_collector_config(is_collector_config), response_body(response_body) {} + : is_collector_config(is_collector_config), + response_body(std::move(response_body)) {} ServerConfigDownloadResult::~ServerConfigDownloadResult() = default; ServerConfigDownloadResult::ServerConfigDownloadResult( const ServerConfigDownloadResult&) = default; @@ -288,7 +298,8 @@ void ServerConfigLoader::LoadConfigs() { auto make_download_result = [](bool is_collector_config, std::optional response_body) { - return ServerConfigDownloadResult(is_collector_config, response_body); + return ServerConfigDownloadResult(is_collector_config, + std::move(response_body)); }; auto collector_callback = @@ -307,20 +318,19 @@ void ServerConfigLoader::LoadConfigs() { void ServerConfigLoader::OnConfigResponsesDownloaded( std::vector results) { CHECK_EQ(results.size(), 2u); - const std::optional* collector_response_body = nullptr; - const std::optional* quorum_response_body = nullptr; + std::optional collector_response_body; + std::optional quorum_response_body; for (const auto& result : results) { if (result.is_collector_config) { - collector_response_body = &result.response_body; + collector_response_body = std::move(result.response_body); } else { - quorum_response_body = &result.response_body; + quorum_response_body = std::move(result.response_body); } } - CHECK(collector_response_body && quorum_response_body); auto* collector_response_info = collector_config_url_loader_->ResponseInfo(); auto* quorum_response_info = quorum_config_url_loader_->ResponseInfo(); - if (!*collector_response_body || !*quorum_response_body || + if (!collector_response_body || !quorum_response_body || !collector_response_info || !quorum_response_info || collector_response_info->headers->response_code() != 200 || quorum_response_info->headers->response_code() != 200) { @@ -331,8 +341,9 @@ void ServerConfigLoader::OnConfigResponsesDownloaded( background_task_runner_->PostTaskAndReplyWithResult( FROM_HERE, - base::BindOnce(&ProcessConfigResponses, **collector_response_body, - **quorum_response_body), + base::BindOnce(&ProcessConfigResponses, + std::move(*collector_response_body), + std::move(*quorum_response_body)), base::BindOnce(&ServerConfigLoader::OnConfigResponsesProcessed, weak_ptr_factory_.GetWeakPtr())); } @@ -366,21 +377,7 @@ void ServerConfigLoader::OnConfigResponsesProcessed( void ServerConfigLoader::LoadStoredPatterns() { background_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, base::BindOnce(&ReadPatternsFile, patterns_path_), - base::BindOnce(&ServerConfigLoader::OnPatternsFileLoaded, - weak_ptr_factory_.GetWeakPtr())); -} - -void ServerConfigLoader::OnPatternsFileLoaded( - std::optional patterns_json) { - if (!patterns_json) { - VLOG(1) << "Failed to load local patterns file"; - local_state_->ClearPref(kPatternsRetrievalTime); - SchedulePatternsRequest(); - return; - } - background_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, base::BindOnce(&ParsePatterns, *patterns_json), + FROM_HERE, base::BindOnce(&ReadAndParsePatternsFile, patterns_path_), base::BindOnce(&ServerConfigLoader::OnStoredPatternsParsed, weak_ptr_factory_.GetWeakPtr())); } @@ -447,22 +444,11 @@ void ServerConfigLoader::OnPatternsResponse( return; } background_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, base::BindOnce(&GunzipContents, *response_body), - base::BindOnce(&ServerConfigLoader::OnPatternsGunzip, - weak_ptr_factory_.GetWeakPtr())); -} - -void ServerConfigLoader::OnPatternsGunzip( - std::optional patterns_json) { - if (!patterns_json) { - VLOG(1) << "Failed to decompress patterns file"; - HandlePatternsStatus(false); - return; - } - background_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, base::BindOnce(&ParsePatterns, *patterns_json), + FROM_HERE, + base::BindOnce(&ParseAndWritePatternsFile, patterns_path_, + std::move(*response_body)), base::BindOnce(&ServerConfigLoader::OnNewPatternsParsed, - weak_ptr_factory_.GetWeakPtr(), *patterns_json)); + weak_ptr_factory_.GetWeakPtr())); } void ServerConfigLoader::OnStoredPatternsParsed( @@ -477,31 +463,14 @@ void ServerConfigLoader::OnStoredPatternsParsed( } void ServerConfigLoader::OnNewPatternsParsed( - std::string new_patterns_json, std::unique_ptr parsed_patterns) { if (!parsed_patterns) { HandlePatternsStatus(false); return; } - background_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, - base::BindOnce(&WritePatternsFile, patterns_path_, new_patterns_json), - base::BindOnce(&ServerConfigLoader::OnPatternsWritten, - weak_ptr_factory_.GetWeakPtr(), - std::move(parsed_patterns))); -} - -void ServerConfigLoader::OnPatternsWritten( - std::unique_ptr parsed_group, - bool result) { - if (!result) { - VLOG(1) << "Failed to write patterns file"; - HandlePatternsStatus(false); - return; - } local_state_->SetTime(kPatternsRetrievalTime, base::Time::Now()); HandlePatternsStatus(true); - last_loaded_patterns_ = std::move(parsed_group); + last_loaded_patterns_ = std::move(parsed_patterns); patterns_callback_.Run(); } diff --git a/components/web_discovery/browser/server_config_loader.h b/components/web_discovery/browser/server_config_loader.h index 552735976493..4331592db277 100644 --- a/components/web_discovery/browser/server_config_loader.h +++ b/components/web_discovery/browser/server_config_loader.h @@ -109,16 +109,11 @@ class ServerConfigLoader { void OnConfigResponsesProcessed(std::unique_ptr config); void LoadStoredPatterns(); - void OnPatternsFileLoaded(std::optional patterns_json); void SchedulePatternsRequest(); void RequestPatterns(); void OnPatternsResponse(std::optional response_body); - void OnPatternsGunzip(std::optional patterns_json); void OnStoredPatternsParsed(std::unique_ptr parsed_patterns); - void OnNewPatternsParsed(std::string new_patterns_json, - std::unique_ptr parsed_patterns); - void OnPatternsWritten(std::unique_ptr parsed_group, - bool result); + void OnNewPatternsParsed(std::unique_ptr parsed_patterns); void HandlePatternsStatus(bool result); const raw_ptr local_state_; diff --git a/components/web_discovery/browser/web_discovery_service.cc b/components/web_discovery/browser/web_discovery_service.cc index 848d89ec289b..2be6eee2c7c0 100644 --- a/components/web_discovery/browser/web_discovery_service.cc +++ b/components/web_discovery/browser/web_discovery_service.cc @@ -59,7 +59,6 @@ void WebDiscoveryService::RegisterProfilePrefs(PrefRegistrySimple* registry) { registry->RegisterBooleanPref(kWebDiscoveryNativeEnabled, false); registry->RegisterDictionaryPref(kAnonymousCredentialsDict); registry->RegisterStringPref(kCredentialRSAPrivateKey, {}); - registry->RegisterStringPref(kCredentialRSAPublicKey, {}); } void WebDiscoveryService::SetExtensionPrefIfNativeDisabled( @@ -102,7 +101,6 @@ void WebDiscoveryService::Stop() { void WebDiscoveryService::ClearPrefs() { profile_prefs_->ClearPref(kAnonymousCredentialsDict); profile_prefs_->ClearPref(kCredentialRSAPrivateKey); - profile_prefs_->ClearPref(kCredentialRSAPublicKey); } void WebDiscoveryService::OnEnabledChange() { diff --git a/components/web_discovery/common/BUILD.gn b/components/web_discovery/common/BUILD.gn index 6ce00a413af9..36b3ff34852e 100644 --- a/components/web_discovery/common/BUILD.gn +++ b/components/web_discovery/common/BUILD.gn @@ -7,7 +7,8 @@ import("//brave/components/web_discovery/buildflags/buildflags.gni") assert(enable_web_discovery_native) -static_library("common") { +component("common") { + output_name = "web_discovery_common" sources = [ "features.cc", "features.h", diff --git a/test/data/web_discovery/credential_keys_and_responses.json b/test/data/web_discovery/credential_keys_and_responses.json index 9fbc3cce99e8..46d9d85f1bc4 100644 --- a/test/data/web_discovery/credential_keys_and_responses.json +++ b/test/data/web_discovery/credential_keys_and_responses.json @@ -1,6 +1,5 @@ { "rsa_priv_key": "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCsD7x4pzqvukjWYyPQiH4KTgxV+ep6hsW+3V3z5vo0M86a4fXcvPnVJ2N4doYz3CsC4YJWftZKiLyY1h/e8ZB9ERd6bkaxRD6lXg26Gry/YPO+gFlhszolLPnFxYaBtTTq4Pt34xseD5jQkiTNXSYNSbpxX/ZNiCn/C55IcUWf7LL6Buufr1ckqz4GIFPclV3+W1+qCb82I3ToMjluKIcApHjXhvZmw7EPogAaxFHXWJmixAE0EVIesBR+DGlPZqqDayiOw2BKZl5zlzvnL04AGyFX7/BWUBZthJuNb6whY20WoU0pZTsf92W0AFthafW4CqPqGXK+QQe4fbtG7f+dAgMBAAECggEAHiUbph/WXldKz5TK/4wKWQ/XhXClrhXSq1/pSAQdreuttOEFzEinlLqz6LULSia2umh8B19td92A/V32c37rC55k+KQ9am1EdICH8yUgEH+R9LxT7JQUCdNZZ1b1+9+dh9Em/Zgidh/RbClOnVRGiGl0asyfQHIsuWx1rMd7pUrvBufy/bEFbcEio7r009qM8CxluS6zRmXO4tWNoZFu42JYjJcEnU+a1mRJ06n1GaVJ0dLd1IA5g4pvVF8wy6R3bQb9xRiwLQMBuG0vLhm69YeyJwHShE5S4WqsubtcxkiZrPak7MLlzwsxk1wU52PbTk1T3geqH5JIerwJ5g7CGQKBgQDq17z9e9OedERboUgXU/nZpSWOqZp5Ukw/dx2xWVtMKhGg6wWoIvadDEeke9RB6JbAlSmBcvT5A8D8P0/1OiatK0i2Yyj1MzuxACnJkYaPjp0Xl4wpZJH1t4QkWEpztPx/JdY6O08WYAbdl0GJhNbL5rqCOS3FZhLpj9OH42IRgwKBgQC7kA0nfifnRRoiL2NwVLeAuG2O4b8JfRPehLw63kl/C4VVbZt0P99EqZwUuKv/nvlrvAfBinwhXvtWhX3GvLUnAQm5eGEt1BMd7GJaIylHFpmlt4+qcoyD+jyd0Jbq4vwiNiupZSZctqRmbu9rLdkoEzSGkkvyY6GQbJxc/ZiAXwKBgAJSb8Px3X3LmIFvbs8MPYQxZdWrR6O7dJWMD/cY8xYltFbq+/tVnSqgXHT75HViX1s4HljxUgrERrw3xAqgsJE1xFpJULZb81MktUUQ80uoFVWOYgxmuiq7zcquNM5AE98N+LhKrdWCzY6TWEqLzbPmbCGtfw5cnANDMMw/K1ERAoGAaLrMvYqR2W8aYpA3ZBfJxxQ0CJ5Av5mZqJxRRkWsoEXck5D6RnULxBk4z9E2KSupdeCuLAGZwkB48xzi2D+yny7TMT7odGCAtCqz2ETd3ZXfAUt36uK/V0o44p4ARvOreabpxlJ2kzpgndm/0gbtxJTEtYem5JeBNVWQEdSAfN0CgYABJjSTY6fDJ1Cj18eiTWY4Tq2phUJQedhZr7/m6e/OZCi40MBiBZ8rdpqqIJzwIVqeei3CTvgZGPa8uKBjMxFZ1ikfNe2+/MA629Cq2FfSNEw1k/+w8uMUMet/Zb+mJ2a6HGZdNfck/5STVYkjKVoIx9jagBBaG1gTbkUcA/XA8Q==", - "rsa_pub_key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArA+8eKc6r7pI1mMj0Ih+Ck4MVfnqeobFvt1d8+b6NDPOmuH13Lz51SdjeHaGM9wrAuGCVn7WSoi8mNYf3vGQfREXem5GsUQ+pV4Nuhq8v2DzvoBZYbM6JSz5xcWGgbU06uD7d+MbHg+Y0JIkzV0mDUm6cV/2TYgp/wueSHFFn+yy+gbrn69XJKs+BiBT3JVd/ltfqgm/NiN06DI5biiHAKR414b2ZsOxD6IAGsRR11iZosQBNBFSHrAUfgxpT2aqg2sojsNgSmZec5c75y9OABshV+/wVlAWbYSbjW+sIWNtFqFNKWU7H/dltABbYWn1uAqj6hlyvkEHuH27Ru3/nQIDAQAB", "group_pub_key": "I8s5zyMT4UHmqR4a99qBnZWXcQt8l4sePztiSEudSeUZqfnUZaWvV8MmeI/Wo0QwKpFNoxYabxfoqXr26KgDLyRdJc5/pCH4o6v714iE+QqNWQDItrGff9l2UcSYI0QhDugl3PWLCswyqeQjLYZkCIk/Qo4iE5eOmW9cM5TsxQ8DYk2USWuMtz+h72xmfomQo28Fyb1GtdQU4LWxfXMLUR3ttXPoJ/IYTxeZNfFuDWGUa3MWdlAJLgdWNsWaDyPiBgzntlxiw5q5xdCTJkmNcNzbZlVRHeng+ZX41gxY4scIP0+pMVtPQFj4NFAFAxVjMl4Zlrvzuy6+Iut3LYLBxBQG1TiY2dANuXJlvZbOC8jUsgeZvKZnwCG/ljnaZQ0CGyF0/ydNadCsSLPFfTiecSJkXL1Pi5NLxzacG/4QBPIhy4VjU4Gg17HNzbYuKljCvS0jAbYsuDEHqi7Z4MXTpREafV3WTE9yGdB2zE/RMRklUNukcAC1ayAjb4tIw6yD", "join_responses": { "20240622": "BAh+HPSF0v2NgWIsqbaFlZokWS5+yuzg0WJAcT1AlAZwIz5/e7n2v7P79pa3LzKjklJ4xUM/WZiujbNZd3gfX5QEFS55KwibK5dEdYMTzDiHWMU4OSIV7p/ZWmjs61Apbx8gnckDONbUOrf4FR2Y46SMp4/VxaZju+S6qBVFdbgqdQQG85r4caFZa56KdERRvy19pfnCdYTDvKedWs7V8xc6LhXjMbFIMlRv0ExlONRTtD3NqOynppR9UCIZmVyaQ+FiBA+uiU9RkwWpAvg2oTAWyS0XbgV6xFe2YlY1mvbpX2ZyEPS+aCmbJo/8/X0piHvcd01D6ytLZfUKQ3qFcoh2mZUGRo7N4bow9J5+dHZkSDQN/r94XLA4uJrTM2dxeFeNAyNlEuSYnxzA9YBRF6YyPuRlWRHXxhSlqO0rbspV+fEX",