| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extensions/browser/api/feedback_private/feedback_service.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/barrier_closure.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/logging.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/statistics_recorder.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/task/thread_pool.h" |
| #include "base/time/time.h" |
| #include "build/chromeos_buildflags.h" |
| #include "components/embedder_support/user_agent_utils.h" |
| #include "components/feedback/feedback_data.h" |
| #include "components/feedback/feedback_report.h" |
| #include "components/feedback/redaction_tool/redaction_tool.h" |
| #include "components/feedback/system_logs/system_logs_fetcher.h" |
| #include "components/feedback/system_logs/system_logs_source.h" |
| #include "components/variations/net/variations_command_line.h" |
| #include "content/public/browser/browser_context.h" |
| #include "extensions/browser/api/extensions_api_client.h" |
| #include "extensions/browser/api/feedback_private/feedback_private_delegate.h" |
| #include "extensions/browser/blob_reader.h" |
| #include "extensions/browser/extensions_browser_client.h" |
| #include "net/base/network_change_notifier.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "ash/public/cpp/assistant/controller/assistant_controller.h" |
| #include "base/base64.h" |
| #include "chromeos/ash/components/cryptohome/cryptohome_parameters.h" |
| #include "chromeos/ash/services/assistant/public/cpp/assistant_service.h" |
| #include "components/account_id/account_id.h" |
| #include "components/user_manager/user_manager.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "extensions/browser/api/feedback_private/proto/hpke.pb.h" |
| #include "net/http/http_status_code.h" |
| #include "services/network/public/cpp/resource_request.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/cpp/simple_url_loader.h" |
| #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" |
| #include "services/network/public/mojom/url_response_head.mojom.h" |
| #include "third_party/boringssl/src/include/openssl/hpke.h" |
| #include "third_party/cros_system_api/dbus/debugd/dbus-constants.h" |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| #if !BUILDFLAG(IS_CHROMEOS) |
| #include "base/base64.h" |
| #include "base/feature_list.h" |
| #endif |
| |
| namespace extensions { |
| |
| using system_logs::SysLogsFetcherCallback; |
| using system_logs::SystemLogsFetcher; |
| using system_logs::SystemLogsResponse; |
| |
| namespace { |
| |
| #if !BUILDFLAG(IS_CHROMEOS) |
| constexpr char kVariationsStateAttachmentName[] = "variations_state.bin"; |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| // The paths are relative to "/var/log/" by default, which can be overwritten |
| // for testing purpose. |
| constexpr base::FilePath::CharType kBluetoothLogsFilePath[] = |
| FILE_PATH_LITERAL("bluetooth/log.bz2"); |
| constexpr base::FilePath::CharType kBluetoothLogsFilePathOld[] = |
| FILE_PATH_LITERAL("bluetooth/log.bz2.old"); |
| constexpr base::FilePath::CharType kBluetoothQualityReportFilePath[] = |
| FILE_PATH_LITERAL("bluetooth/bluetooth_quality_report"); |
| |
| constexpr char kBluetoothLogsAttachmentName[] = "bluetooth_logs.bz2"; |
| constexpr char kBluetoothLogsAttachmentNameOld[] = "bluetooth_logs.old.bz2"; |
| constexpr char kBluetoothQualityReportAttachmentName[] = |
| "bluetooth_quality_report"; |
| |
| constexpr char kVariationsAttachmentName[] = "variations.binary"; |
| constexpr char kVariationsFetchHpkeKey[] = |
| "https://www.gstatic.com/chromeos-feedback-variations-encryption-key/" |
| "public_keyset.json"; |
| constexpr int kVariationsMaxDownloadBytes = 512; |
| |
| void AddAttachment(scoped_refptr<feedback::FeedbackData> feedback_data, |
| const base::FilePath& root_path, |
| const std::string& file_path, |
| const std::string& attachment_name) { |
| std::string temp_log_content; |
| if (base::ReadFileToString(root_path.Append(file_path), &temp_log_content)) { |
| feedback_data->AddFile(attachment_name, std::move(temp_log_content)); |
| } else { |
| LOG(WARNING) << "failed to add attachment " << attachment_name |
| << ": could not read file: " << file_path << " in " |
| << root_path.value(); |
| } |
| } |
| |
| void AttachBluetoothLogs(scoped_refptr<feedback::FeedbackData> feedback_data, |
| const base::FilePath& root_path) { |
| AddAttachment(feedback_data, root_path, kBluetoothLogsFilePath, |
| kBluetoothLogsAttachmentName); |
| AddAttachment(feedback_data, root_path, kBluetoothLogsFilePathOld, |
| kBluetoothLogsAttachmentNameOld); |
| AddAttachment(feedback_data, root_path, kBluetoothQualityReportFilePath, |
| kBluetoothQualityReportAttachmentName); |
| } |
| |
| // A new case must be added for every new log type. Otherwise the code should |
| // not compile. |
| std::string_view GetAttachmentName(debugd::FeedbackBinaryLogType log_type) { |
| switch (log_type) { |
| case debugd::WIFI_FIRMWARE_DUMP: |
| return "wifi_firmware_dumps.tar.zst"; |
| case debugd::BLUETOOTH_FIRMWARE_DUMP: |
| return "bluetooth_firmware_dumps.tar.zst"; |
| } |
| } |
| #endif |
| |
| #if !BUILDFLAG(IS_CHROMEOS) |
| void IncludeVariations(scoped_refptr<feedback::FeedbackData> feedback_data) { |
| std::vector<uint8_t> ciphertext; |
| auto status = |
| variations::VariationsCommandLine::GetForCurrentProcess().EncryptToString( |
| &ciphertext); |
| base::UmaHistogramEnumeration("Variations.VariationsStateEncryptionStatus", |
| status); |
| // Variations is at best effort. |
| if (status == variations::VariationsStateEncryptionStatus::kSuccess) { |
| // This is a binary file. |
| feedback_data->AddFile(kVariationsStateAttachmentName, |
| std::string(ciphertext.begin(), ciphertext.end())); |
| } |
| } |
| #endif |
| |
| void RedactFeedbackData(scoped_refptr<feedback::FeedbackData> feedback_data) { |
| redaction::RedactionTool redactor(nullptr); |
| redactor.EnableCreditCardRedaction(true); |
| feedback_data->RedactDescription(redactor); |
| } |
| |
| } // namespace |
| |
| FeedbackService::FeedbackService(content::BrowserContext* browser_context) |
| : FeedbackService( |
| browser_context, |
| ExtensionsAPIClient::Get()->GetFeedbackPrivateDelegate()) {} |
| |
| FeedbackService::FeedbackService(content::BrowserContext* browser_context, |
| FeedbackPrivateDelegate* delegate) |
| : browser_context_(browser_context), delegate_(delegate) {} |
| |
| FeedbackService::~FeedbackService() = default; |
| |
| void FeedbackService::RedactThenSendFeedback( |
| const FeedbackParams& params, |
| scoped_refptr<feedback::FeedbackData> feedback_data, |
| SendFeedbackCallback callback) { |
| base::ThreadPool::PostTaskAndReply( |
| FROM_HERE, {base::TaskPriority::BEST_EFFORT}, |
| base::BindOnce(&RedactFeedbackData, feedback_data), |
| base::BindOnce(&FeedbackService::SendFeedback, this, params, |
| feedback_data, std::move(callback))); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| void FeedbackService::SetLogFilesRootPathForTesting( |
| const base::FilePath& log_file_root) { |
| log_file_root_ = log_file_root; |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| // After the attached file and screenshot if available are fetched, the callback |
| // will be invoked. Other further processing will be done in background. The |
| // report will be sent out once all data are in place. |
| void FeedbackService::SendFeedback( |
| const FeedbackParams& params, |
| scoped_refptr<feedback::FeedbackData> feedback_data, |
| SendFeedbackCallback callback) { |
| auto* browser_client = ExtensionsBrowserClient::Get(); |
| feedback_data->set_locale(browser_client->GetApplicationLocale()); |
| feedback_data->set_user_agent(embedder_support::GetUserAgent()); |
| |
| FetchAttachedFileAndScreenshot( |
| feedback_data, |
| base::BindOnce(&FeedbackService::OnAttachedFileAndScreenshotFetched, this, |
| params, feedback_data, std::move(callback))); |
| } |
| |
| void FeedbackService::FetchAttachedFileAndScreenshot( |
| scoped_refptr<feedback::FeedbackData> feedback_data, |
| base::OnceClosure callback) { |
| const bool must_attach_file = !feedback_data->attached_file_uuid().empty(); |
| const bool must_attach_screenshot = !feedback_data->screenshot_uuid().empty(); |
| auto barrier_closure = base::BarrierClosure( |
| (must_attach_file ? 1 : 0) + (must_attach_screenshot ? 1 : 0), |
| std::move(callback)); |
| |
| if (must_attach_file) { |
| auto populate_attached_file = base::BindOnce( |
| [](scoped_refptr<feedback::FeedbackData> feedback_data, |
| std::string data, int64_t /*length*/) { |
| feedback_data->set_attached_file_uuid(std::string()); |
| feedback_data->AttachAndCompressFileData(std::move(data)); |
| }, |
| feedback_data); |
| |
| BlobReader::Read( |
| browser_context_->GetBlobRemote(feedback_data->attached_file_uuid()), |
| std::move(populate_attached_file).Then(barrier_closure)); |
| } |
| |
| if (must_attach_screenshot) { |
| auto populate_screenshot = base::BindOnce( |
| [](scoped_refptr<feedback::FeedbackData> feedback_data, |
| std::string data, int64_t /*length*/) { |
| feedback_data->set_screenshot_uuid(std::string()); |
| feedback_data->set_image(std::move(data)); |
| }, |
| feedback_data); |
| BlobReader::Read( |
| browser_context_->GetBlobRemote(feedback_data->screenshot_uuid()), |
| std::move(populate_screenshot).Then(barrier_closure)); |
| } |
| } |
| |
| void FeedbackService::OnAttachedFileAndScreenshotFetched( |
| const FeedbackParams& params, |
| scoped_refptr<feedback::FeedbackData> feedback_data, |
| SendFeedbackCallback callback) { |
| if (params.load_system_info) { |
| // The user has chosen to send system logs. They (and on ash more logs) |
| // will be loaded in the background without blocking the client. |
| FetchSystemInformation(params, feedback_data); |
| } else { |
| #if BUILDFLAG(IS_CHROMEOS) |
| if (feedback_data->sys_info()->size() > 0) { |
| // The user has chosen to send system logs which has been loaded from the |
| // client side. On ash, extra logs need to be fetched. |
| FetchExtraLogs(params, feedback_data); |
| } else { |
| // The user has chosen not to send system logs. |
| OnAllLogsFetched(params, feedback_data); |
| } |
| #else |
| OnAllLogsFetched(params, feedback_data); |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| } |
| |
| base::UmaHistogramMediumTimes( |
| "Feedback.Duration.FormSubmitToConfirmation", |
| base::TimeTicks::Now() - params.form_submit_time); |
| |
| // True means report will be sent shortly. |
| // False means report will be sent once the device is online. |
| const bool status = !net::NetworkChangeNotifier::IsOffline(); |
| |
| UMA_HISTOGRAM_BOOLEAN("Feedback.ReportSending.Online", status); |
| |
| // Notify client that data submitted has been received successfully. The |
| // report will be sent out once further processing is done. |
| std::move(callback).Run(status); |
| } |
| |
| void FeedbackService::FetchSystemInformation( |
| const FeedbackParams& params, |
| scoped_refptr<feedback::FeedbackData> feedback_data) { |
| base::TimeTicks fetch_start_time = base::TimeTicks::Now(); |
| delegate_->FetchSystemInformation( |
| browser_context_, |
| base::BindOnce(&FeedbackService::OnSystemInformationFetched, this, |
| fetch_start_time, params, feedback_data)); |
| } |
| |
| void FeedbackService::OnSystemInformationFetched( |
| base::TimeTicks fetch_start_time, |
| const FeedbackParams& params, |
| scoped_refptr<feedback::FeedbackData> feedback_data, |
| std::unique_ptr<system_logs::SystemLogsResponse> sys_info) { |
| // Fetching is currently slow and could take up to 2 minutes on Chrome OS. |
| base::UmaHistogramMediumTimes("Feedback.Duration.FetchSystemInformation", |
| base::TimeTicks::Now() - fetch_start_time); |
| if (sys_info) { |
| for (auto& itr : *sys_info) { |
| if (FeedbackCommon::IncludeInSystemLogs(itr.first, |
| params.is_internal_email)) |
| feedback_data->AddLog(std::move(itr.first), std::move(itr.second)); |
| } |
| } |
| #if !BUILDFLAG(IS_CHROMEOS) |
| if (base::FeatureList::IsEnabled(variations::kFeedbackIncludeVariations)) { |
| IncludeVariations(feedback_data); |
| } |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| FetchExtraLogs(params, feedback_data); |
| #else |
| OnAllLogsFetched(params, feedback_data); |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| void FeedbackService::FetchExtraLogs( |
| const FeedbackParams& params, |
| scoped_refptr<feedback::FeedbackData> feedback_data) { |
| delegate_->FetchExtraLogs( |
| feedback_data, |
| base::BindOnce(&FeedbackService::OnExtraLogsFetched, this, params)); |
| } |
| |
| void FeedbackService::OnExtraLogsFetched( |
| const FeedbackParams& params, |
| scoped_refptr<feedback::FeedbackData> feedback_data) { |
| auto barrier_closure = |
| base::BarrierClosure((params.send_bluetooth_logs ? 2 : 0) + |
| (params.send_wifi_debug_logs ? 1 : 0) + 1, |
| base::BindOnce(&FeedbackService::OnAllLogsFetched, |
| this, params, feedback_data)); |
| |
| EncryptVariations(feedback_data, barrier_closure); |
| |
| const user_manager::User* user = |
| user_manager::UserManager::Get()->GetActiveUser(); |
| const auto account_identifier = |
| cryptohome::CreateAccountIdentifierFromAccountId( |
| user ? user->GetAccountId() : EmptyAccountId()); |
| |
| // If bluetooth logs are requested, invoke AttachBluetoothLogs to add |
| // them in a separate thread to avoid blocking the UI thread. |
| if (params.send_bluetooth_logs) { |
| base::ThreadPool::PostTaskAndReply( |
| FROM_HERE, {base::MayBlock()}, |
| base::BindOnce(&AttachBluetoothLogs, feedback_data, log_file_root_), |
| barrier_closure); |
| |
| binary_log_files_reader_.GetFeedbackBinaryLogs( |
| account_identifier, |
| debugd::FeedbackBinaryLogType::BLUETOOTH_FIRMWARE_DUMP, |
| base::BindOnce(&FeedbackService::OnBinaryLogFilesFetched, this, params, |
| feedback_data, barrier_closure)); |
| } |
| |
| if (params.send_wifi_debug_logs) { |
| binary_log_files_reader_.GetFeedbackBinaryLogs( |
| account_identifier, debugd::FeedbackBinaryLogType::WIFI_FIRMWARE_DUMP, |
| base::BindOnce(&FeedbackService::OnBinaryLogFilesFetched, this, params, |
| feedback_data, barrier_closure)); |
| } |
| } |
| |
| void FeedbackService::OnBinaryLogFilesFetched( |
| const FeedbackParams& params, |
| scoped_refptr<feedback::FeedbackData> feedback_data, |
| base::RepeatingClosure barrier_closure_callback, |
| feedback::BinaryLogFilesReader::BinaryLogsResponse binary_logs_response) { |
| if (binary_logs_response) { |
| for (auto& item : *binary_logs_response) { |
| feedback_data->AddFile(GetAttachmentName(item.first).data(), |
| std::move(item.second)); |
| } |
| } |
| std::move(barrier_closure_callback).Run(); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| void FeedbackService::OnAllLogsFetched( |
| const FeedbackParams& params, |
| scoped_refptr<feedback::FeedbackData> feedback_data) { |
| if (!params.send_tab_titles) { |
| feedback_data->RemoveLog( |
| feedback::FeedbackReport::kMemUsageWithTabTitlesKey); |
| } |
| feedback_data->CompressSystemInfo(); |
| |
| if (params.send_histograms) { |
| std::string histograms = |
| base::StatisticsRecorder::ToJSON(base::JSON_VERBOSITY_LEVEL_FULL); |
| feedback_data->SetAndCompressHistograms(std::move(histograms)); |
| } |
| |
| if (params.send_autofill_metadata) { |
| feedback_data->CompressAutofillMetadata(); |
| } |
| |
| DCHECK(feedback_data->attached_file_uuid().empty()); |
| DCHECK(feedback_data->screenshot_uuid().empty()); |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| // Send feedback to Assistant server if triggered from Google Assistant. |
| if (feedback_data->from_assistant()) { |
| ash::AssistantController::Get()->SendAssistantFeedback( |
| feedback_data->assistant_debug_info_allowed(), |
| feedback_data->description(), feedback_data->image()); |
| } |
| #endif |
| |
| // Signal the feedback object that the data from the feedback page has been |
| // filled - the object will manage sending of the actual report. |
| feedback_data->OnFeedbackPageDataComplete(); |
| base::UmaHistogramTimes("Feedback.Duration.FormSubmitToSendQueue", |
| base::TimeTicks::Now() - params.form_submit_time); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| void FeedbackService::EncryptVariations( |
| scoped_refptr<feedback::FeedbackData> feedback_data, |
| base::RepeatingClosure barrier_closure) { |
| net::NetworkTrafficAnnotationTag traffic_annotation = |
| net::DefineNetworkTrafficAnnotation( |
| "chromeos_feedback_report_hpke_public_key_fetch_for_variations", R"( |
| semantics { |
| sender: "ChromeOS Feedback Report App" |
| description: |
| "Users can press Alt+Shift+i to report a bug or a feedback in " |
| "general. Here we fetch a Hpke public key to encrypt " |
| "the current running variations. This is ChromeOS-only." |
| trigger: |
| "When user chooses to send feedback to Google." |
| data: |
| "Fetches a HpKe public key. This key is used to encrypt the " |
| "variations that are running at the time the feedback report was " |
| "generated, and used by incident management engineering to triage " |
| "issues potentially caused by experiments. " |
| "If the user unchecks 'Send system information', this will " |
| "not be fetched and variations will not be included in the " |
| "feedback report." |
| destination: GOOGLE_OWNED_SERVICE |
| internal { |
| contacts { |
| email: "cros-feedback-app@google.com" |
| } |
| } |
| user_data { |
| type: NONE |
| } |
| last_reviewed: "2024-11-06" |
| } |
| policy { |
| cookies_allowed: NO |
| setting: |
| "This feature cannot be disabled by settings and is only activated " |
| "by direct user request." |
| chrome_policy { |
| UserFeedbackAllowed { |
| UserFeedbackAllowed: false |
| } |
| } |
| })"); |
| |
| auto resource_request = std::make_unique<network::ResourceRequest>(); |
| resource_request->url = GURL(kVariationsFetchHpkeKey); |
| resource_request->method = "GET"; |
| resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit; |
| |
| auto loader = network::SimpleURLLoader::Create(std::move(resource_request), |
| traffic_annotation); |
| |
| if (!url_loader_factory_) { |
| url_loader_factory_ = browser_context_->GetDefaultStoragePartition() |
| ->GetURLLoaderFactoryForBrowserProcess(); |
| } |
| |
| // Loader will be owned by the callback, so we need a temporary reference to |
| // avoid use after move. |
| network::SimpleURLLoader* loader_ptr = loader.get(); |
| loader_ptr->DownloadToString( |
| url_loader_factory_.get(), |
| base::BindOnce(&FeedbackService::OnVariationsFetchHpkeURL, this, |
| std::move(loader), feedback_data, barrier_closure), |
| kVariationsMaxDownloadBytes); |
| } |
| |
| void FeedbackService::OnVariationsFetchHpkeURL( |
| std::unique_ptr<network::SimpleURLLoader> loader, |
| scoped_refptr<feedback::FeedbackData> feedback_data, |
| base::RepeatingClosure barrier_closure, |
| std::unique_ptr<std::string> hpke_public_key) { |
| if (!loader) { |
| LOG(ERROR) << "invalid loader"; |
| return VariationsFinished(false, barrier_closure); |
| } |
| |
| auto net_error = loader->NetError(); |
| int http_error = 0; |
| if (loader->ResponseInfo() && loader->ResponseInfo()->headers) { |
| http_error = loader->ResponseInfo()->headers->response_code(); |
| } |
| if (!hpke_public_key || http_error != net::HTTP_OK) { |
| LOG(ERROR) << "Unable to fetch hpke_public_key. http code: " << http_error |
| << ", net error: " << net_error; |
| return VariationsFinished(false, barrier_closure); |
| } |
| // Send the JSON string to a dedicated service for safe parsing. |
| data_decoder_.ParseJson( |
| *hpke_public_key, |
| base::BindOnce(&FeedbackService::VariationsExtractHpkePublicKey, this, |
| feedback_data, barrier_closure)); |
| } |
| |
| // Sample JSON string: |
| // { |
| // "primaryKeyId": 123, |
| // "key": [ |
| // { |
| // "keyData": { |
| // "typeUrl": "type.googleapis.com/google.crypto.tink.HpkePublicKey", |
| // "value": "Base64Encoded HPKE Proto", |
| // "keyMaterialType": "ASYMMETRIC_PUBLIC" |
| // }, |
| // "status": "ENABLED", |
| // "keyId": 123, |
| // "outputPrefixType": "RAW" |
| // } |
| // ] |
| // } |
| void FeedbackService::VariationsExtractHpkePublicKey( |
| scoped_refptr<feedback::FeedbackData> feedback_data, |
| base::RepeatingClosure barrier_closure, |
| data_decoder::DataDecoder::ValueOrError result) { |
| if (!result.has_value() || !result->is_dict()) { |
| LOG(ERROR) << "Failed to parse JSON or it's not a dictionary."; |
| return VariationsFinished(false, barrier_closure); |
| } |
| |
| const base::Value::Dict& json_dict = result->GetDict(); |
| const base::Value::List* key_list = json_dict.FindList("key"); |
| |
| if (!key_list || key_list->empty()) { |
| LOG(ERROR) << "Key list not found or empty."; |
| return VariationsFinished(false, barrier_closure); |
| } |
| |
| // Get the first item in the "key" list |
| const base::Value& key_item = (*key_list)[0]; |
| const base::Value::Dict* key_dict = key_item.GetIfDict(); |
| |
| if (!key_dict) { |
| LOG(ERROR) << "Unexpected format in 'key' item."; |
| return VariationsFinished(false, barrier_closure); |
| } |
| |
| // Extract "keyData" dictionary |
| const base::Value::Dict* key_data_dict = key_dict->FindDict("keyData"); |
| if (!key_data_dict) { |
| LOG(ERROR) << "Failed to find 'keyData' dictionary."; |
| return VariationsFinished(false, barrier_closure); |
| } |
| |
| // Extract "value" from "keyData" |
| const std::string* base64_serialized_proto_hpke = |
| key_data_dict->FindString("value"); |
| if (!base64_serialized_proto_hpke) { |
| LOG(ERROR) << "Failed to extract 'value' from 'keyData'."; |
| return VariationsFinished(false, barrier_closure); |
| } |
| |
| // std::string base64_proto_key = *base64_proto_keyp; |
| std::string serialized_proto_hpke; |
| |
| if (!base::Base64Decode(*base64_serialized_proto_hpke, |
| &serialized_proto_hpke)) { |
| LOG(ERROR) << "base64 decode of hpke proto failed"; |
| return VariationsFinished(false, barrier_closure); |
| } |
| userfeedback::HpkePublicKey key_proto; |
| if (!key_proto.ParseFromString(serialized_proto_hpke)) { |
| LOG(ERROR) << "Failed to parse HpkePublicKey."; |
| return VariationsFinished(false, barrier_closure); |
| } |
| |
| std::string hpke_public_key_string = key_proto.public_key(); |
| std::vector<uint8_t> hpke_public_key; |
| hpke_public_key.assign(hpke_public_key_string.begin(), |
| hpke_public_key_string.end()); |
| VLOG(1) << "HPKE public KEY:" << base::HexEncode(hpke_public_key); |
| return VariationsEncryptWithHpkeKey(hpke_public_key, feedback_data, |
| barrier_closure); |
| ; |
| } |
| |
| void FeedbackService::VariationsEncryptWithHpkeKey( |
| const std::vector<uint8_t>& hpke_public_key, |
| scoped_refptr<feedback::FeedbackData> feedback_data, |
| base::RepeatingClosure barrier_closure) { |
| std::string variations_string = |
| variations::VariationsCommandLine::GetForCurrentProcess().ToString(); |
| if (variations_string.empty()) { |
| LOG(ERROR) << "Unable to get valid variations."; |
| return VariationsFinished(false, barrier_closure); |
| } |
| std::vector<uint8_t> variations(variations_string.begin(), |
| variations_string.end()); |
| bssl::ScopedEVP_HPKE_CTX sender_context; |
| |
| // This vector will hold the encapsulated shared secret "enc" followed by the |
| // symmetrically encrypted ciphertext "ct". Start with a size big enough for |
| // the shared secret. |
| std::vector<uint8_t> encrypted_variations(EVP_HPKE_MAX_ENC_LENGTH); |
| size_t encapsulated_shared_secret_len; |
| |
| if (!EVP_HPKE_CTX_setup_sender( |
| /*ctx=*/sender_context.get(), |
| /*out_enc=*/encrypted_variations.data(), |
| /*out_enc_len=*/&encapsulated_shared_secret_len, |
| /*max_enc=*/encrypted_variations.size(), |
| /*kem=*/EVP_hpke_x25519_hkdf_sha256(), |
| /*kdf=*/EVP_hpke_hkdf_sha256(), |
| /*aead=*/EVP_hpke_aes_256_gcm(), |
| /*peer_public_key=*/hpke_public_key.data(), |
| /*peer_public_key_len=*/hpke_public_key.size(), |
| /*info=*/nullptr, |
| /*info_len=*/0)) { |
| LOG(ERROR) << "hpke setup failed"; |
| return VariationsFinished(false, barrier_closure); |
| } |
| encrypted_variations.resize(encapsulated_shared_secret_len + |
| variations.size() + |
| EVP_HPKE_CTX_max_overhead(sender_context.get())); |
| base::span<uint8_t> ciphertext = |
| base::span(encrypted_variations).subspan(encapsulated_shared_secret_len); |
| size_t ciphertext_len; |
| |
| if (!EVP_HPKE_CTX_seal( |
| /*ctx=*/sender_context.get(), |
| /*out=*/ciphertext.data(), |
| /*out_len=*/&ciphertext_len, |
| /*max_out_len=*/ciphertext.size(), |
| /*in=*/variations.data(), |
| /*in_len*/ variations.size(), |
| /*ad=*/nullptr, |
| /*ad_len=*/0)) { |
| LOG(ERROR) << "hpke seal failed"; |
| return VariationsFinished(false, barrier_closure); |
| } |
| encrypted_variations.resize(encapsulated_shared_secret_len + ciphertext_len); |
| feedback_data->AddFile( |
| kVariationsAttachmentName, |
| std::string(encrypted_variations.begin(), encrypted_variations.end())); |
| return VariationsFinished(true, barrier_closure); |
| } |
| |
| void FeedbackService::VariationsFinished( |
| bool variations_attached, |
| base::RepeatingClosure barrier_closure) { |
| if (variations_attached) { |
| VLOG(1) << "variations attached to feedback report"; |
| } else { |
| VLOG(1) << "variations not attached to feedback report"; |
| } |
| std::move(barrier_closure).Run(); |
| } |
| |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| } // namespace extensions |