Add private metric report encryption

Implement functionality to fetch public key and encrypt private metrics
report for DWA.

from third_party protocol buffer dependencies.
NO_IFTTT=dwa_egtest.mm will add support for PrivateMetricsFeature in a
separate CL.

Bug: 411369489
Change-Id: Ib4a1baae22064b4c7e8d9897ae8de979f2aef6f7
Binary-Size: Size increase is unavoidable because increase is largely
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6913363
Reviewed-by: Alexei Svitkine <asvitkine@chromium.org>
Reviewed-by: Olivier Robin <olivierrobin@chromium.org>
Commit-Queue: Jay Zhou <zhouzj@google.com>
Cr-Commit-Position: refs/heads/main@{#1515492}
NOKEYCHECK=True
GitOrigin-RevId: cbc6209a5a8eb373d40b1b8f3cd461b8be9a621b
diff --git a/dwa/DEPS b/dwa/DEPS
index b3a9001..115faf5 100644
--- a/dwa/DEPS
+++ b/dwa/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
   "+mojo/public/cpp/bindings",
   "+net/base/registry_controlled_domains",
+  "+third_party/federated_compute",
 ]
diff --git a/dwa/dwa_service.cc b/dwa/dwa_service.cc
index 3f3fb52..cd6b8aa 100644
--- a/dwa/dwa_service.cc
+++ b/dwa/dwa_service.cc
@@ -23,26 +23,10 @@
 #include "components/metrics/metrics_pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/version_info/version_info.h"
+#include "third_party/federated_compute/src/fcp/confidentialcompute/crypto.h"
 
 namespace metrics::dwa {
 
-namespace {
-
-// TODO(crbug.com/411369489): Encrypt private metric report. Current
-// implementation only serializes the report and moves the serialized report
-// into the encrypted field without actually encrypting it.
-::private_metrics::EncryptedPrivateMetricReport EncryptPrivateMetricReport(
-    const ::private_metrics::PrivateMetricReport& report) {
-  std::string serialized_log;
-  report.SerializeToString(&serialized_log);
-
-  ::private_metrics::EncryptedPrivateMetricReport encrypted_report;
-  *encrypted_report.mutable_encrypted_report() = std::move(serialized_log);
-  return encrypted_report;
-}
-
-}  // namespace
-
 // Set of countries in the European Economic Area. Used by
 // RecordCoarseSystemInformation to set geo_designation fields in
 // CoarseSystemInfo. These will need to be manually updated using
@@ -89,12 +73,24 @@
 const size_t kMinLogQueueSizeBytes = 300 * 1024;  // 300 KiB
 const size_t kMaxLogSizeBytes = 1024 * 1024;      // 1 MiB
 
-DwaService::DwaService(MetricsServiceClient* client, PrefService* local_state)
+DwaService::DwaService(
+    MetricsServiceClient* client,
+    PrefService* local_state,
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
     : recorder_(DwaRecorder::Get()),
       client_(client),
       pref_service_(local_state),
       reporting_service_(client, local_state, GetLogStoreLimits()) {
   reporting_service_.Initialize();
+
+  // Set up the downloader to refresh the encryption public key.
+  data_upload_config_downloader_ =
+      std::make_unique<private_metrics::DataUploadConfigDownloader>(
+          url_loader_factory);
+
+  encryption_public_key_verifier_ =
+      base::BindRepeating(&ValidateEncryptionPublicKey);
+
   // Set up the scheduler for DwaService.
   auto rotate_callback = base::BindRepeating(&DwaService::RotateLog,
                                              self_ptr_factory_.GetWeakPtr());
@@ -154,6 +150,66 @@
   reporting_service_.unsent_log_store()->Purge();
 }
 
+void DwaService::RefreshEncryptionPublicKey() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Multiple calls to FetchDataUploadConfig() is acceptable because
+  // FetchDataUploadConfig() checks for an existing fetch and discards any
+  // additional attempts if one is already in progress.
+  data_upload_config_downloader_->FetchDataUploadConfig(
+      base::BindOnce(&DwaService::HandleEncryptionPublicKeyRefresh,
+                     self_ptr_factory_.GetWeakPtr()));
+}
+
+void DwaService::SetEncryptionPublicKeyForTesting(
+    std::string_view test_encryption_public_key) {
+  encryption_public_key_ = test_encryption_public_key;
+}
+
+void DwaService::SetEncryptionPublicKeyVerifierForTesting(
+    const base::RepeatingCallback<
+        bool(const fcp::confidential_compute::OkpCwt&)>&
+        test_encryption_public_key_verifier) {
+  encryption_public_key_verifier_ = test_encryption_public_key_verifier;
+}
+
+void DwaService::HandleEncryptionPublicKeyRefresh(
+    std::optional<fcp::confidentialcompute::DataUploadConfig>
+        maybe_data_upload_config) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // `maybe_data_upload_config` may be empty if the http
+  // request to fetch `fcp::confidentialcompute::DataUploadConfig` results in an
+  // empty response body or if the HTTP status is non-200.
+  // `maybe_data_upload_config` may also be empty if de-serialization of
+  // DataUploadConfig into a protocol buffer object fails.
+  // If HandleEncryptionPublicKeyRefresh() fails to update or refresh the public
+  // key in one of the described error scenarios, the method will maintain the
+  // previous public key value.
+  if (maybe_data_upload_config.has_value() &&
+      // The field may be empty in the unexpected case that the protocol buffer
+      // is malformed.
+      maybe_data_upload_config.value().has_confidential_encryption_config()) {
+    auto encryption_public_key = maybe_data_upload_config.value()
+                                     .confidential_encryption_config()
+                                     .public_key();
+
+    // Validate the public key is a valid cwt before setting the in-memory
+    // field, `encryption_public_key_`. This function call is okay to use in
+    // main thread. The performance of decoding of CBOR web tokens (CWT) is
+    // generally considered to be more efficient than the decoding of JSON Web
+    // Token (JWT) decoding due to CBOR's binary nature.
+    auto cwt = fcp::confidential_compute::OkpCwt::Decode(encryption_public_key);
+    if (!cwt.ok() || !cwt->algorithm.has_value() ||
+        !cwt->public_key.has_value() ||
+        !encryption_public_key_verifier_.Run(*cwt)) {
+      return;
+    }
+
+    encryption_public_key_ = encryption_public_key;
+  }
+}
+
 // static
 UnsentLogStore::UnsentLogStoreLimits DwaService::GetLogStoreLimits() {
   return UnsentLogStore::UnsentLogStoreLimits{
@@ -315,6 +371,59 @@
   return k_anonymity_buckets;
 }
 
+// static
+std::optional<::private_metrics::EncryptedPrivateMetricReport>
+DwaService::EncryptPrivateMetricReport(
+    const ::private_metrics::PrivateMetricReport& report,
+    std::string_view public_key,
+    const fcp::confidential_compute::OkpCwt& decoded_public_key) {
+  std::string serialized_log;
+  report.SerializeToString(&serialized_log);
+
+  ::private_metrics::PrivateMetricReportHeader report_header;
+  report_header.set_key_id(decoded_public_key.public_key.value().key_id);
+  report_header.set_epoch_id(report.epoch_id());
+
+  std::string serialized_report_header;
+  report_header.SerializeToString(&serialized_report_header);
+
+  // The messages are encrypted with AEAD using a per-message generated
+  // symmetric key and then the symmetric key is encrypted using HPKE with the
+  // public key. This function call is considered fast for use in main thread.
+  // TODO(crbug.com/444678679): Add UMA histogram to measure performance on
+  // Encrypt() for detecting any potential regressions or edge cases.
+  fcp::confidential_compute::MessageEncryptor message_encryptor;
+  auto result = message_encryptor.Encrypt(serialized_log, public_key,
+                                          serialized_report_header);
+  if (!result.ok()) {
+    return std::nullopt;
+  }
+
+  ::private_metrics::EncryptedPrivateMetricReport encrypted_report;
+  *encrypted_report.mutable_encrypted_report() =
+      std::move(result.value().ciphertext);
+  encrypted_report.set_serialized_report_header(serialized_report_header);
+  *encrypted_report.mutable_report_header() = std::move(report_header);
+  encrypted_report.set_report_type(
+      ::private_metrics::EncryptedPrivateMetricReport::DWA);
+  return encrypted_report;
+}
+
+// static
+bool DwaService::ValidateEncryptionPublicKey(
+    const fcp::confidential_compute::OkpCwt& cwt) {
+  // In the unexpected case where the `cwt` is malformed and does not contain an
+  // expiration time, the key should not be used.
+  if (!cwt.expiration_time.has_value()) {
+    return false;
+  }
+
+  // If the encryption key is close to expiration (12 hour buffer), the key
+  // should not be used.
+  auto public_key_expiration = cwt.expiration_time.value() - absl::Hours(12);
+  return public_key_expiration >= absl::Now();
+}
+
 void DwaService::RotateLog() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!reporting_service_.unsent_log_store()->has_unsent_logs()) {
@@ -366,9 +475,31 @@
     return;
   }
 
+  // If the encryption public key is empty, no new logs should be created.
+  if (encryption_public_key_.empty()) {
+    RefreshEncryptionPublicKey();
+    return;
+  }
+
+  auto cwt = fcp::confidential_compute::OkpCwt::Decode(encryption_public_key_);
+  if (!cwt.ok() || !cwt->algorithm.has_value() ||
+      !cwt->public_key.has_value()) {
+    RefreshEncryptionPublicKey();
+    return;
+  }
+
+  if (!encryption_public_key_verifier_.Run(*cwt)) {
+    RefreshEncryptionPublicKey();
+    return;
+  }
+
+  // Build the private metric report.
   ::private_metrics::PrivateMetricReport report;
   report.set_ephemeral_id(GetEphemeralClientId(*pref_service_));
 
+  uint64_t epoch_id = (base::Time::Now() - base::Time::UnixEpoch()).InDays();
+  report.set_epoch_id(epoch_id);
+
   std::vector<::dwa::DeidentifiedWebAnalyticsEvent> dwa_events =
       recorder_->TakeDwaEvents();
 
@@ -378,7 +509,7 @@
 
     auto k_anonymity_buckets = BuildKAnonymityBuckets(dwa_event);
     // Since there are no k-anonymity buckets, the k-anonymity filter cannot be
-    // enforced. As such, the bucket should be dropped.
+    // enforced. As such, the event should be dropped.
     // TODO(crbug.com/432764678): Add UMA metric when dwa_event is dropped due
     // to empty k-anonymity buckets.
     if (k_anonymity_buckets.empty()) {
@@ -392,11 +523,24 @@
     *event->mutable_dwa_event() = std::move(dwa_event);
   }
 
-  ::private_metrics::EncryptedPrivateMetricReport encrypted_report =
-      EncryptPrivateMetricReport(report);
+  auto encrypted_report =
+      EncryptPrivateMetricReport(report, encryption_public_key_, cwt.value());
+  if (!encrypted_report.has_value()) {
+    // The encrypted report should be dropped in the unexpected event that
+    // private metrics report encryption fails.
+    // TODO(crbug.com/444681539): Add UMA histogram to check when private
+    // metrics encryption fails.
+    return;
+  }
 
   std::string serialized_log;
-  encrypted_report.SerializeToString(&serialized_log);
+  if (!encrypted_report.value().SerializeToString(&serialized_log)) {
+    // The encrypted report should be dropped in the unexpected event that
+    // private metrics report serialization fails.
+    // TODO(crbug.com/444681539): Add UMA histogram to check when private
+    // metrics serialization fails.
+    return;
+  }
 
   LogMetadata metadata;
   reporting_service_.unsent_log_store()->StoreLog(serialized_log, metadata,
diff --git a/dwa/dwa_service.h b/dwa/dwa_service.h
index 092333d..fef3832 100644
--- a/dwa/dwa_service.h
+++ b/dwa/dwa_service.h
@@ -8,6 +8,7 @@
 #include <cstdint>
 #include <memory>
 #include <optional>
+#include <string>
 #include <vector>
 
 #include "base/memory/weak_ptr.h"
@@ -15,10 +16,13 @@
 #include "components/metrics/dwa/dwa_recorder.h"
 #include "components/metrics/metrics_rotation_scheduler.h"
 #include "components/metrics/metrics_service_client.h"
+#include "components/metrics/private_metrics/data_upload_config_downloader.h"
 #include "components/metrics/private_metrics/private_metrics_reporting_service.h"
 #include "components/metrics/unsent_log_store.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
+#include "third_party/federated_compute/src/fcp/confidentialcompute/cose.h"
+#include "third_party/federated_compute/src/fcp/protos/confidentialcompute/data_upload_config.pb.h"
 #include "third_party/metrics_proto/dwa/deidentified_web_analytics.pb.h"
 #include "third_party/metrics_proto/private_metrics/private_metrics.pb.h"
 
@@ -28,7 +32,9 @@
 // analytics events.
 class DwaService {
  public:
-  DwaService(MetricsServiceClient* client, PrefService* pref_service);
+  DwaService(MetricsServiceClient* client,
+             PrefService* pref_service,
+             scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
   DwaService(const DwaService&) = delete;
   DwaService& operator=(const DwaService&) = delete;
 
@@ -43,6 +49,25 @@
   // Clears all event and log data.
   void Purge();
 
+  // Refresh the public key used to encrypt private metric reports.
+  void RefreshEncryptionPublicKey();
+
+  // Sets encryption public key used to encrypt private metrics reports as
+  // `test_encryption_public_key`.
+  void SetEncryptionPublicKeyForTesting(
+      std::string_view test_encryption_public_key);
+
+  // Sets the value of encryption public key verifier,
+  // `encryption_public_key_verifier_`, to
+  // `test_encryption_public_key_verifier`.
+  // TODO(crbug.com/444679397): Remove this function and instead make
+  // ValidateEncryptionPublicKey() a virtual function that can be overridden in
+  // unit tests.
+  void SetEncryptionPublicKeyVerifierForTesting(
+      const base::RepeatingCallback<
+          bool(const fcp::confidential_compute::OkpCwt&)>&
+          test_encryption_public_key_verifier);
+
   // Records coarse system profile into CoarseSystemInfo of the deidentified web
   // analytics report proto.
   static void RecordCoarseSystemInformation(
@@ -77,7 +102,7 @@
   // k-anonymity buckets for `dwa_event` as there would be no way to enforce the
   // k-anonymity filter without the k-anonymity buckets. For `dwa_event`, the
   // combination of `dwa_event.coarse_system_info`, `dwa_event.event_hash`, and
-  // `dwa_event.field_trials` builds the first k-anbonymity bucket because the
+  // `dwa_event.field_trials` builds the first k-anonymity bucket because the
   // combination describes an user invoking an action. We want to verify there
   // is a sufficient number of users who perform this action before allowing the
   // `dwa_event` past the k-anonymity filter. Similarly,
@@ -90,6 +115,27 @@
   static std::vector<uint64_t> BuildKAnonymityBuckets(
       const ::dwa::DeidentifiedWebAnalyticsEvent& dwa_event);
 
+  // Encrypts `report` using the public encryption key, `public_key`, which is
+  // accepted by the HPKE encryption method in //third_party/federated_compute.
+  // The encrypted private metrics report can only be decrypted in a trusted
+  // execution environment (TEE) because decryption keys are released
+  // execlusively to the TEE. This method accepts the field `decoded_public_key`
+  // as an optimization. The field `decoded_public_key` is the OkpCwt
+  // representation of `public_key`, which is parsed and validated before being
+  // passed to this method. This prevents the need to re-parse the public key to
+  // extract the key id, which is required when building the encrypted private
+  // metrics report.
+  static std::optional<::private_metrics::EncryptedPrivateMetricReport>
+  EncryptPrivateMetricReport(
+      const ::private_metrics::PrivateMetricReport& report,
+      std::string_view public_key,
+      const fcp::confidential_compute::OkpCwt& decoded_public_key);
+
+  // Returns false if the public key `cwt` is expired or should not be used.
+  // Otherwise, returns true.
+  static bool ValidateEncryptionPublicKey(
+      const fcp::confidential_compute::OkpCwt& cwt);
+
   // Register prefs from `dwa_pref_names.h`.
   static void RegisterPrefs(PrefRegistrySimple* registry);
 
@@ -114,6 +160,12 @@
   // Retrieves the storage parameters to control the reporting service.
   static UnsentLogStore::UnsentLogStoreLimits GetLogStoreLimits();
 
+  // Handles updating the public key, `encryption_public_key_`, during a public
+  // key refresh.
+  void HandleEncryptionPublicKeyRefresh(
+      std::optional<fcp::confidentialcompute::DataUploadConfig>
+          maybe_data_upload_config);
+
   // Manages on-device recording of events.
   raw_ptr<DwaRecorder> recorder_;
 
@@ -129,6 +181,23 @@
   // The scheduler for determining when uploads should happen.
   std::unique_ptr<MetricsRotationScheduler> scheduler_;
 
+  // The DataUploadConfig protocol buffer contains the public key required to
+  // encrypt private metric reports and the signed endorsements of the keys
+  // required to perform attestation verification.
+  std::unique_ptr<private_metrics::DataUploadConfigDownloader>
+      data_upload_config_downloader_;
+
+  // The public key used to encrypt PrivateMetricReport protocol buffer. The
+  // public key is fetched on startup and refreshed once every 24 hours.
+  std::string encryption_public_key_;
+
+  // A repeating callback used to verify the `encryption_public_key_`. The
+  // callback should return false if the public key is invalid, expired, or
+  // should not be used.
+  // Otherwise, the callback should return true.
+  base::RepeatingCallback<bool(const fcp::confidential_compute::OkpCwt&)>
+      encryption_public_key_verifier_;
+
   // Weak pointers factory used to post task on different threads. All weak
   // pointers managed by this factory have the same lifetime as DwaService.
   base::WeakPtrFactory<DwaService> self_ptr_factory_{this};
diff --git a/dwa/dwa_service_unittest.cc b/dwa/dwa_service_unittest.cc
index 8d853e7..2728a07 100644
--- a/dwa/dwa_service_unittest.cc
+++ b/dwa/dwa_service_unittest.cc
@@ -17,6 +17,8 @@
 #include "components/prefs/pref_service.h"
 #include "components/prefs/testing_pref_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/federated_compute/src/fcp/confidentialcompute/cose.h"
+#include "third_party/federated_compute/src/fcp/confidentialcompute/crypto.h"
 
 namespace metrics::dwa {
 
@@ -30,7 +32,8 @@
     MetricsStateManager::RegisterPrefs(prefs_.registry());
     DwaService::RegisterPrefs(prefs_.registry());
 
-    scoped_feature_list_.InitAndEnableFeature(kDwaFeature);
+    scoped_feature_list_.InitWithFeatures(
+        {dwa::kDwaFeature, dwa::kPrivateMetricsFeature}, {});
   }
 
   DwaServiceTest(const DwaServiceTest&) = delete;
@@ -40,6 +43,15 @@
 
   void TearDown() override { DwaRecorder::Get()->Purge(); }
 
+  void SetEncryptionPublicKeyForTesting(DwaService* dwa_service) {
+    fcp::confidential_compute::MessageDecryptor decryptor;
+    auto recipient_public_key =
+        decryptor.GetPublicKey([](absl::string_view) { return ""; }, 0);
+    dwa_service->SetEncryptionPublicKeyForTesting(recipient_public_key.value());
+    dwa_service->SetEncryptionPublicKeyVerifierForTesting(base::BindRepeating(
+        [](const fcp::confidential_compute::OkpCwt&) -> bool { return true; }));
+  }
+
   int GetPersistedLogCount() {
     if (base::FeatureList::IsEnabled(kPrivateMetricsFeature)) {
       return prefs_.GetList(private_metrics::prefs::kUnsentLogStoreName).size();
@@ -291,8 +303,62 @@
   ASSERT_EQ(previous_bucket_value, k_anonymity_buckets.at(0));
 }
 
+TEST_F(DwaServiceTest, ValidateEncryptionPublicKey) {
+  fcp::confidential_compute::MessageDecryptor decryptor;
+  auto recipient_public_key =
+      decryptor.GetPublicKey([](absl::string_view) { return ""; }, 0);
+  ASSERT_TRUE(recipient_public_key.ok());
+
+  auto cwt = fcp::confidential_compute::OkpCwt::Decode(*recipient_public_key);
+  ASSERT_TRUE(cwt.ok());
+
+  // Validate DwaService::ValidateEncryptionPublicKey() returns false when key
+  // is already expired.
+  auto now = absl::Now();
+  cwt->expiration_time = std::make_optional(now - absl::Hours(8));
+  EXPECT_FALSE(DwaService::ValidateEncryptionPublicKey(*cwt));
+
+  // Validate DwaService::ValidateEncryptionPublicKey() returns false when key
+  // is close to expiring (less than 12 hours).
+  cwt->expiration_time = std::make_optional(now + absl::Hours(11));
+  EXPECT_FALSE(DwaService::ValidateEncryptionPublicKey(*cwt));
+
+  // Validate DwaService::ValidateEncryptionPublicKey() returns true when key
+  // valid and far from expiring (more than 12 hours).
+  cwt->expiration_time = std::make_optional(now + absl::Hours(13));
+  EXPECT_TRUE(DwaService::ValidateEncryptionPublicKey(*cwt));
+}
+
+TEST_F(DwaServiceTest, EncryptPrivateMetricReport) {
+  TestingPrefServiceSimple pref_service;
+  DwaService::RegisterPrefs(pref_service.registry());
+
+  fcp::confidential_compute::MessageDecryptor decryptor;
+  auto recipient_public_key =
+      decryptor.GetPublicKey([](absl::string_view) { return ""; }, 0);
+  auto decoded_public_key =
+      fcp::confidential_compute::OkpCwt::Decode(*recipient_public_key);
+  decoded_public_key->public_key.value().key_id = "key-id";
+
+  ::private_metrics::PrivateMetricReport report;
+  report.set_ephemeral_id(DwaService::GetEphemeralClientId(pref_service));
+  auto epoch_id = (base::Time::Now() - base::Time::UnixEpoch()).InDays();
+  report.set_epoch_id(epoch_id);
+
+  auto encrypted_report = DwaService::EncryptPrivateMetricReport(
+      report, *recipient_public_key, *decoded_public_key);
+  ASSERT_TRUE(encrypted_report.has_value());
+
+  EXPECT_TRUE(encrypted_report->has_encrypted_report());
+  EXPECT_TRUE(encrypted_report->has_serialized_report_header());
+  EXPECT_TRUE(encrypted_report->has_report_header());
+  EXPECT_TRUE(encrypted_report->has_report_type());
+}
+
 TEST_F(DwaServiceEnvironmentTest, Flush) {
-  DwaService service(&client_, &prefs_);
+  DwaService service(&client_, &prefs_, nullptr);
+  SetEncryptionPublicKeyForTesting(&service);
+
   histogram_tester_.ExpectTotalCount(kDwaInitSequenceHistogramName,
                                      /*expected_count=*/1);
   DwaRecorder::Get()->EnableRecording();
@@ -307,7 +373,9 @@
 }
 
 TEST_F(DwaServiceEnvironmentTest, Purge) {
-  DwaService service(&client_, &prefs_);
+  DwaService service(&client_, &prefs_, nullptr);
+  SetEncryptionPublicKeyForTesting(&service);
+
   histogram_tester_.ExpectTotalCount(kDwaInitSequenceHistogramName,
                                      /*expected_count=*/1);
   DwaRecorder::Get()->EnableRecording();
@@ -332,7 +400,9 @@
 }
 
 TEST_F(DwaServiceEnvironmentTest, EnableDisableRecordingAndReporting) {
-  DwaService service(&client_, &prefs_);
+  DwaService service(&client_, &prefs_, nullptr);
+  SetEncryptionPublicKeyForTesting(&service);
+
   histogram_tester_.ExpectTotalCount(kDwaInitSequenceHistogramName,
                                      /*expected_count=*/1);
   EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 0u);
@@ -390,7 +460,9 @@
 }
 
 TEST_F(DwaServiceEnvironmentTest, LogsRotatedPeriodically) {
-  DwaService service(&client_, &prefs_);
+  DwaService service(&client_, &prefs_, nullptr);
+  SetEncryptionPublicKeyForTesting(&service);
+
   histogram_tester_.ExpectTotalCount(kDwaInitSequenceHistogramName,
                                      /*expected_count=*/1);
   DwaRecorder::Get()->EnableRecording();
diff --git a/private_metrics/data_upload_config_downloader.cc b/private_metrics/data_upload_config_downloader.cc
index 49e187e..914c51b 100644
--- a/private_metrics/data_upload_config_downloader.cc
+++ b/private_metrics/data_upload_config_downloader.cc
@@ -76,7 +76,7 @@
         })");
 
 inline constexpr char kDataUploadConfigGstaticUrl[] =
-    "https://www.gstatic.com/chrome/private-metrics/data-upload-config.pbtxt";
+    "https://www.gstatic.com/chrome/private-metrics/data-upload-config.pb";
 }  // namespace
 
 std::unique_ptr<network::SimpleURLLoader> CreateSimpleURLLoader(
diff --git a/private_metrics/data_upload_config_downloader_unittest.cc b/private_metrics/data_upload_config_downloader_unittest.cc
index 0642fb8..afe9c23 100644
--- a/private_metrics/data_upload_config_downloader_unittest.cc
+++ b/private_metrics/data_upload_config_downloader_unittest.cc
@@ -19,7 +19,7 @@
 namespace {
 
 inline constexpr char kDataUploadConfigGstaticUrl[] =
-    "https://www.gstatic.com/chrome/private-metrics/data-upload-config.pbtxt";
+    "https://www.gstatic.com/chrome/private-metrics/data-upload-config.pb";
 
 void AddValidDataUploadConfigResponse(
     network::TestURLLoaderFactory& test_url_loader_factory) {