910e62b5创建于 1月15日历史提交
// 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/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 "base/base64.h"
#include "chromeos/ash/components/cryptohome/cryptohome_parameters.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());

  // 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