Add metrics for measuring the speed and reliability of app-based crypto.

This CL adds metrics to call into the elevation service to perform
app-based encryption and decryption operations similar to those
that would be performed to support app-based encryption in Chrome.

A task is posted at startup to measure a call to Encrypt for a new
local state, with the encrypted data persisted to local state.

On subsequent startups, the encrypted data is decrypted and the time
taken to decrypt is measured

This will give a good proxy for the performance of the code in
src/chrome if it is to be used in production, and help direct
further development on the threading model that will be used
for initializing the os_crypt key in future iterations.

A test was added that requires running as Admin to work correctly
as it installs the service, and verifies that it can be
successfully communicated with.

BUG=1333461

Change-Id: I9d735242a24bc8a29cfc3b8a14634578d6cf1c3e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3697028
Reviewed-by: Greg Thompson <[email protected]>
Reviewed-by: Scott Violet <[email protected]>
Commit-Queue: Will Harris <[email protected]>
Reviewed-by: Robert Kaplow <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1016956}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 44b91da..b0214ae 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -5704,6 +5704,10 @@
       "notifications/win/notification_template_builder.cc",
       "notifications/win/notification_template_builder.h",
       "obsolete_system/obsolete_system_win.cc",
+      "os_crypt/app_bound_encryption_metrics_win.cc",
+      "os_crypt/app_bound_encryption_metrics_win.h",
+      "os_crypt/app_bound_encryption_win.cc",
+      "os_crypt/app_bound_encryption_win.h",
       "password_manager/password_manager_util_win.cc",
       "password_manager/password_manager_util_win.h",
       "performance_manager/mechanisms/working_set_trimmer_win.cc",
@@ -5806,7 +5810,7 @@
       "//chrome/chrome_elf:third_party_shared_defines",
       "//chrome/common:version_header",
       "//chrome/credential_provider/common:common_constants",
-      "//chrome/elevation_service:elevation_service_idl",
+      "//chrome/elevation_service:public_headers",
       "//chrome/install_static:install_static_util",
       "//chrome/installer/util:with_no_strings",
       "//chrome/notification_helper:constants",
diff --git a/chrome/browser/browser_features.cc b/chrome/browser/browser_features.cc
index 77d5a8a8..9803fd1 100644
--- a/chrome/browser/browser_features.cc
+++ b/chrome/browser/browser_features.cc
@@ -27,7 +27,7 @@
 
 // Destroy profiles when their last browser window is closed, instead of when
 // the browser exits.
-const base::Feature kDestroyProfileOnBrowserClose{
+const base::Feature kDestroyProfileOnBrowserClose {
   "DestroyProfileOnBrowserClose",
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
       base::FEATURE_ENABLED_BY_DEFAULT
@@ -164,4 +164,10 @@
     "RestartNetworkServiceUnsandboxedForFailedLaunch",
     base::FEATURE_ENABLED_BY_DEFAULT};
 
+#if BUILDFLAG(IS_WIN)
+// When this feature is enabled, metrics are gathered regarding the performance
+// and reliability of app-bound encryption primitives on a background thread.
+const base::Feature kAppBoundEncryptionMetrics{
+    "AppBoundEncryptionMetrics", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
 }  // namespace features
diff --git a/chrome/browser/browser_features.h b/chrome/browser/browser_features.h
index 4f70115..f860c19 100644
--- a/chrome/browser/browser_features.h
+++ b/chrome/browser/browser_features.h
@@ -68,6 +68,10 @@
 
 extern const base::Feature kKeyPinningComponentUpdater;
 
+#if BUILDFLAG(IS_WIN)
+extern const base::Feature kAppBoundEncryptionMetrics;
+#endif
+
 }  // namespace features
 
 #endif  // CHROME_BROWSER_BROWSER_FEATURES_H_
diff --git a/chrome/browser/chrome_browser_main_win.cc b/chrome/browser/chrome_browser_main_win.cc
index 9f15dccc..d924bb5 100644
--- a/chrome/browser/chrome_browser_main_win.cc
+++ b/chrome/browser/chrome_browser_main_win.cc
@@ -47,10 +47,12 @@
 #include "build/branding_buildflags.h"
 #include "build/build_config.h"
 #include "chrome/browser/about_flags.h"
+#include "chrome/browser/browser_features.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/enterprise/browser_management/management_service_factory.h"
 #include "chrome/browser/enterprise/util/critical_policy_section_metrics_win.h"
 #include "chrome/browser/first_run/first_run.h"
+#include "chrome/browser/os_crypt/app_bound_encryption_metrics_win.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/profiles/profile_shortcut_manager.h"
 #include "chrome/browser/safe_browsing/chrome_cleaner/settings_resetter_win.h"
@@ -651,6 +653,12 @@
   content::GetUIThreadTaskRunner({})->PostDelayedTask(
       FROM_HERE, base::BindOnce(&DetectFaultTolerantHeap), base::Minutes(1));
 
+  // Query feature first, to include full population in field trial.
+  if (base::FeatureList::IsEnabled(features::kAppBoundEncryptionMetrics) &&
+      install_static::IsSystemInstall()) {
+    os_crypt::MeasureAppBoundEncryptionStatus(g_browser_process->local_state());
+  }
+
   // Record Processor Metrics. This is very low priority, hence posting as
   // BEST_EFFORT to start after Chrome startup has completed. This metric is
   // only available starting Windows 10.
diff --git a/chrome/browser/os_crypt/DEPS b/chrome/browser/os_crypt/DEPS
new file mode 100644
index 0000000..1a0e207
--- /dev/null
+++ b/chrome/browser/os_crypt/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  '+chrome/elevation_service/elevation_service_idl.h',
+  '+chrome/elevation_service/elevator.h',
+]
diff --git a/chrome/browser/os_crypt/OWNERS b/chrome/browser/os_crypt/OWNERS
new file mode 100644
index 0000000..971354f1
--- /dev/null
+++ b/chrome/browser/os_crypt/OWNERS
@@ -0,0 +1,5 @@
+# Default sending any reviews to these owners:
[email protected]
+
+# As a backup, a review can also be obtained from a reviewer in this file.
+file://chrome/elevation_service/OWNERS
diff --git a/chrome/browser/os_crypt/README.md b/chrome/browser/os_crypt/README.md
new file mode 100644
index 0000000..803cb68b4
--- /dev/null
+++ b/chrome/browser/os_crypt/README.md
@@ -0,0 +1,27 @@
+This directory contains the interface to the application-bound encryption
+primitives that are implemented by the elevation service in
+[src/chrome/elevation_service].
+
+`EncryptAppBoundString` and `DecryptAppBoundString` act like
+`OSCrypt::EncryptString` and `OSCrypt::DecryptString` implemented by
+[src/components/os_crypt] except that, unlike `OSCrypt`, which binds encrypted
+data to the current user using DPAPI, this API will bind the encrypted data
+with a `ProtectionLevel` specified by the caller.
+
+`ProtectionLevels` are defined by chrome/elevation_service and are currently:
+
+ - `ProtectionLevel::NONE`
+
+   This acts identically to DPAPI in that the protection level is user-bound.
+   Only a `DecryptAppBoundString` call that comes from the same user principle
+   as the original `EncryptAppBoundString` call with succeed.
+
+ - `ProtectionLevel::PATH_VALIDATION`
+
+   This adds an additional protection that the path of the calling application
+   will be validated. Only a `DecryptAppBoundString` call that comes from the
+   same user principle, calling from the same Application (with the same file
+   path) as the original `EncryptAppBoundString` call with succeed. It is only
+   safe to call this from an application that is installed into a 'Trusted
+   Path' such as `C:\Program Files`, otherwise protection can be trivially
+   bypassed by renaming/placing a file into the required location.
diff --git a/chrome/browser/os_crypt/app_bound_encryption_metrics_win.cc b/chrome/browser/os_crypt/app_bound_encryption_metrics_win.cc
new file mode 100644
index 0000000..b5d29b5
--- /dev/null
+++ b/chrome/browser/os_crypt/app_bound_encryption_metrics_win.cc
@@ -0,0 +1,134 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/os_crypt/app_bound_encryption_metrics_win.h"
+
+#include <string>
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_util.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/win/com_init_util.h"
+#include "base/win/windows_types.h"
+#include "chrome/browser/os_crypt/app_bound_encryption_win.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace os_crypt {
+
+namespace prefs {
+
+const char kOsCryptAppBoundFixedDataPrefName[] =
+    "os_crypt.app_bound_fixed_data";
+
+}  // namespace prefs
+
+namespace {
+
+// Rather than generate a random key here, use fixed data here for the purposes
+// of measuring the performance, as the content itself does not matter.
+const char kFixedData[] = "Fixed data used for metrics";
+
+void DecryptAndRecordMetricsOnCOMThread(const std::string& encrypted_data) {
+  base::win::AssertComInitialized();
+
+  std::string decrypted_data;
+  DWORD last_error;
+  HRESULT hr;
+  {
+    SCOPED_UMA_HISTOGRAM_TIMER("OSCrypt.AppBoundEncryption.Decrypt.Time");
+    hr = DecryptAppBoundString(encrypted_data, decrypted_data, last_error);
+  }
+
+  if (FAILED(hr)) {
+    base::UmaHistogramSparse(
+        "OSCrypt.AppBoundEncryption.Decrypt.ResultLastError", last_error);
+  } else {
+    // Check if it returned success but the data was invalid. This should never
+    // happen. If it does, log a unique HRESULT to track it.
+    if (decrypted_data != kFixedData) {
+      const HRESULT kErrorWrongData =
+          MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0xA101);
+      hr = kErrorWrongData;
+    }
+  }
+
+  base::UmaHistogramSparse("OSCrypt.AppBoundEncryption.Decrypt.ResultCode", hr);
+}
+
+std::string EncryptAndRecordMetricsOnCOMThread() {
+  base::win::AssertComInitialized();
+
+  std::string encrypted_data;
+  DWORD last_error;
+  HRESULT hr;
+  {
+    SCOPED_UMA_HISTOGRAM_TIMER("OSCrypt.AppBoundEncryption.Encrypt.Time");
+    hr = EncryptAppBoundString(ProtectionLevel::PATH_VALIDATION, kFixedData,
+                               encrypted_data, last_error);
+  }
+
+  base::UmaHistogramSparse("OSCrypt.AppBoundEncryption.Encrypt.ResultCode", hr);
+
+  if (FAILED(hr)) {
+    base::UmaHistogramSparse(
+        "OSCrypt.AppBoundEncryption.Encrypt.ResultLastError", last_error);
+  }
+
+  return encrypted_data;
+}
+
+void StorePrefOnUiThread(PrefService* local_state,
+                         const std::string& encrypted_data) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  if (encrypted_data.empty())
+    return;
+  std::string base64_data;
+  base::Base64Encode(encrypted_data, &base64_data);
+
+  local_state->SetString(prefs::kOsCryptAppBoundFixedDataPrefName, base64_data);
+}
+
+}  // namespace
+
+void RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
+  registry->RegisterStringPref(prefs::kOsCryptAppBoundFixedDataPrefName, {});
+}
+
+bool MeasureAppBoundEncryptionStatus(PrefService* local_state) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  auto com_runner = base::ThreadPool::CreateCOMSTATaskRunner(
+      {base::MayBlock(), base::TaskPriority::USER_BLOCKING,
+       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+      base::SingleThreadTaskRunnerThreadMode::SHARED);
+
+  if (local_state->HasPrefPath(prefs::kOsCryptAppBoundFixedDataPrefName)) {
+    const std::string base64_encrypted_data =
+        local_state->GetString(prefs::kOsCryptAppBoundFixedDataPrefName);
+
+    std::string encrypted_data;
+    // If this fails it will be caught later when trying to decrypt and logged
+    // above..
+    std::ignore = base::Base64Decode(base64_encrypted_data, &encrypted_data);
+
+    // Gather metrics for decrypt.
+    return com_runner->PostTask(
+        FROM_HERE,
+        base::BindOnce(&DecryptAndRecordMetricsOnCOMThread, encrypted_data));
+  }
+
+  return com_runner->PostTaskAndReplyWithResult(
+      FROM_HERE, base::BindOnce(&EncryptAndRecordMetricsOnCOMThread),
+      base::BindOnce(StorePrefOnUiThread, local_state));
+}
+
+}  // namespace os_crypt
diff --git a/chrome/browser/os_crypt/app_bound_encryption_metrics_win.h b/chrome/browser/os_crypt/app_bound_encryption_metrics_win.h
new file mode 100644
index 0000000..66c6507
--- /dev/null
+++ b/chrome/browser/os_crypt/app_bound_encryption_metrics_win.h
@@ -0,0 +1,22 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_OS_CRYPT_APP_BOUND_ENCRYPTION_METRICS_WIN_H_
+#define CHROME_BROWSER_OS_CRYPT_APP_BOUND_ENCRYPTION_METRICS_WIN_H_
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace os_crypt {
+
+// Register local state prefs required by the app bound encryption metrics.
+void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
+
+// Posts background tasks to measure the app bound encryption metrics. This
+// should be called on the UI thread.
+bool MeasureAppBoundEncryptionStatus(PrefService* local_state);
+
+}  // namespace os_crypt
+
+#endif  // CHROME_BROWSER_OS_CRYPT_APP_BOUND_ENCRYPTION_METRICS_WIN_H_
diff --git a/chrome/browser/os_crypt/app_bound_encryption_win.cc b/chrome/browser/os_crypt/app_bound_encryption_win.cc
new file mode 100644
index 0000000..79a680af
--- /dev/null
+++ b/chrome/browser/os_crypt/app_bound_encryption_win.cc
@@ -0,0 +1,98 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/os_crypt/app_bound_encryption_win.h"
+
+#include <objbase.h>
+#include <string.h>
+#include <windows.h>
+#include <wrl/client.h>
+
+#include "base/logging.h"
+#include "base/win/com_init_util.h"
+#include "base/win/scoped_bstr.h"
+#include "chrome/elevation_service/elevation_service_idl.h"
+#include "chrome/install_static/install_util.h"
+
+namespace os_crypt {
+
+HRESULT EncryptAppBoundString(ProtectionLevel protection_level,
+                              const std::string& plaintext,
+                              std::string& ciphertext,
+                              DWORD& last_error) {
+  base::win::AssertComInitialized();
+  Microsoft::WRL::ComPtr<IElevator> elevator;
+  last_error = ERROR_GEN_FAILURE;
+  HRESULT hr = ::CoCreateInstance(
+      install_static::GetElevatorClsid(), nullptr, CLSCTX_LOCAL_SERVER,
+      install_static::GetElevatorIid(), IID_PPV_ARGS_Helper(&elevator));
+
+  if (FAILED(hr))
+    return hr;
+
+  hr = ::CoSetProxyBlanket(
+      elevator.Get(), RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT,
+      COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
+      RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_DYNAMIC_CLOAKING);
+  if (FAILED(hr))
+    return hr;
+
+  base::win::ScopedBstr plaintext_data;
+  ::memcpy(plaintext_data.AllocateBytes(plaintext.length()), plaintext.data(),
+           plaintext.length());
+
+  base::win::ScopedBstr encrypted_data;
+  hr = elevator->EncryptData(protection_level, plaintext_data.Get(),
+                             encrypted_data.Receive(), &last_error);
+  if (FAILED(hr))
+    return hr;
+
+  ciphertext.assign(
+      reinterpret_cast<std::string::value_type*>(encrypted_data.Get()),
+      encrypted_data.ByteLength());
+
+  last_error = ERROR_SUCCESS;
+  return S_OK;
+}
+
+HRESULT DecryptAppBoundString(const std::string& ciphertext,
+                              std::string& plaintext,
+                              DWORD& last_error) {
+  DCHECK(!ciphertext.empty());
+  base::win::AssertComInitialized();
+  Microsoft::WRL::ComPtr<IElevator> elevator;
+  last_error = ERROR_GEN_FAILURE;
+  HRESULT hr = ::CoCreateInstance(
+      install_static::GetElevatorClsid(), nullptr, CLSCTX_LOCAL_SERVER,
+      install_static::GetElevatorIid(), IID_PPV_ARGS_Helper(&elevator));
+
+  if (FAILED(hr))
+    return hr;
+
+  hr = ::CoSetProxyBlanket(
+      elevator.Get(), RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT,
+      COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
+      RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_DYNAMIC_CLOAKING);
+  if (FAILED(hr))
+    return hr;
+
+  base::win::ScopedBstr ciphertext_data;
+  ::memcpy(ciphertext_data.AllocateBytes(ciphertext.length()),
+           ciphertext.data(), ciphertext.length());
+
+  base::win::ScopedBstr plaintext_data;
+  hr = elevator->DecryptData(ciphertext_data.Get(), plaintext_data.Receive(),
+                             &last_error);
+  if (FAILED(hr))
+    return hr;
+
+  plaintext.assign(
+      reinterpret_cast<std::string::value_type*>(plaintext_data.Get()),
+      plaintext_data.ByteLength());
+
+  last_error = ERROR_SUCCESS;
+  return S_OK;
+}
+
+}  // namespace os_crypt
diff --git a/chrome/browser/os_crypt/app_bound_encryption_win.h b/chrome/browser/os_crypt/app_bound_encryption_win.h
new file mode 100644
index 0000000..e8f1372
--- /dev/null
+++ b/chrome/browser/os_crypt/app_bound_encryption_win.h
@@ -0,0 +1,44 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_OS_CRYPT_APP_BOUND_ENCRYPTION_WIN_H_
+#define CHROME_BROWSER_OS_CRYPT_APP_BOUND_ENCRYPTION_WIN_H_
+
+#include <string>
+
+#include "base/win/windows_types.h"
+#include "chrome/elevation_service/elevation_service_idl.h"
+
+namespace os_crypt {
+
+// Encrypts a string with a Protection level of `level`. See
+// `src/chrome/elevation_service/elevation-service_idl.idl` for the definition
+// of available protection levels.
+//
+// This returns an HRESULT as defined by src/chrome/elevation_service/elevator.h
+// or S_OK for success. If the call fails then `last_error` will be set to the
+// value returned from the most recent failing Windows API call or
+// ERROR_GEN_FAILURE.
+//
+// This should be called on a COM-enabled thread.
+HRESULT EncryptAppBoundString(ProtectionLevel level,
+                              const std::string& plaintext,
+                              std::string& ciphertext,
+                              DWORD& last_error);
+
+// Decrypts a string previously encrypted by a call to EncryptAppBoundString.
+//
+// This returns an HRESULT as defined by src/chrome/elevation_service/elevator.h
+// or S_OK for success. If the call fails then `last_error` will be set to the
+// value returned from the most recent failing Windows API call or
+// ERROR_GEN_FAILURE.
+//
+// This should be called on a COM-enabled thread.
+HRESULT DecryptAppBoundString(const std::string& ciphertext,
+                              std::string& plaintext,
+                              DWORD& last_error);
+
+}  // namespace os_crypt
+
+#endif  // CHROME_BROWSER_OS_CRYPT_APP_BOUND_ENCRYPTION_WIN_H_
diff --git a/chrome/browser/os_crypt/app_bound_encryption_win_browsertest.cc b/chrome/browser/os_crypt/app_bound_encryption_win_browsertest.cc
new file mode 100644
index 0000000..c21144c
--- /dev/null
+++ b/chrome/browser/os_crypt/app_bound_encryption_win_browsertest.cc
@@ -0,0 +1,221 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/os_crypt/app_bound_encryption_win.h"
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/path_service.h"
+#include "base/process/process_info.h"
+#include "base/test/bind.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/browser_features.h"
+#include "chrome/elevation_service/elevator.h"
+#include "chrome/install_static/buildflags.h"
+#include "chrome/install_static/install_constants.h"
+#include "chrome/install_static/install_details.h"
+#include "chrome/install_static/install_util.h"
+#include "chrome/install_static/test/scoped_install_details.h"
+#include "chrome/installer/util/install_service_work_item.h"
+#include "chrome/installer/util/util_constants.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/version_info/version_info_values.h"
+#include "content/public/test/browser_test.h"
+
+namespace {
+
+void WaitForHistogram(const std::string& histogram_name) {
+  // Continue if histogram was already recorded.
+  if (base::StatisticsRecorder::FindHistogram(histogram_name))
+    return;
+
+  // Else, wait until the histogram is recorded.
+  base::RunLoop run_loop;
+  auto histogram_observer =
+      std::make_unique<base::StatisticsRecorder::ScopedHistogramSampleObserver>(
+          histogram_name,
+          base::BindLambdaForTesting(
+              [&](const char* histogram_name, uint64_t name_hash,
+                  base::HistogramBase::Sample sample) { run_loop.Quit(); }));
+  run_loop.Run();
+}
+
+}  // namespace
+
+// This class allows system-level tests to be carried out that do not interfere
+// with an existing system-level install.
+class FakeInstallDetails : public install_static::PrimaryInstallDetails {
+ public:
+  // Copy template from first mode from install modes. Some of the values will
+  // then be overridden.
+  FakeInstallDetails() : constants_(install_static::kInstallModes[0]) {
+    // AppGuid determines registry locations, so use a test one.
+#if BUILDFLAG(USE_GOOGLE_UPDATE_INTEGRATION)
+    constants_.app_guid = L"testguid";
+#endif
+
+    // This is the CLSID of the test interface, used if
+    // kElevatorClsIdForTestingSwitch is supplied on the command line of the
+    // elevation service.
+    constants_.elevator_clsid = {elevation_service::kTestElevatorClsid};
+
+    // This is the IID of the non-channel specific IElevator Interface. See
+    // chrome/elevation_service/elevation_service_idl.idl.
+    constants_.elevator_iid = {
+        0xA949CB4E,
+        0xC4F9,
+        0x44C4,
+        {0xB2, 0x13, 0x6B, 0xF8, 0xAA, 0x9A, 0xC6,
+         0x9C}};  // IElevator IID and TypeLib
+                  // {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}
+
+    // These are used to generate the name of the service, so keep them
+    // different from any real installs.
+    constants_.base_app_name = L"testapp";
+    constants_.base_app_id = L"testapp";
+
+    // This is needed for shell_integration::GetDefaultBrowser which runs on
+    // startup.
+    constants_.prog_id_prefix = L"TestHTM";
+
+    set_mode(&constants_);
+    set_system_level(true);
+  }
+
+  FakeInstallDetails(const FakeInstallDetails&) = delete;
+  FakeInstallDetails& operator=(const FakeInstallDetails&) = delete;
+
+ private:
+  install_static::InstallConstants constants_;
+};
+
+class AppBoundEncryptionWinTest : public InProcessBrowserTest {
+ public:
+  AppBoundEncryptionWinTest()
+      : scoped_install_details_(std::make_unique<FakeInstallDetails>()) {}
+
+ protected:
+  void SetUp() override {
+    if (base::GetCurrentProcessIntegrityLevel() != base::HIGH_INTEGRITY)
+      GTEST_SKIP() << "Elevation is required for this test.";
+    enable_metrics_feature_.InitAndEnableFeature(
+        features::kAppBoundEncryptionMetrics);
+    InstallService();
+    InProcessBrowserTest::SetUp();
+  }
+
+  void TearDown() override {
+    if (base::GetCurrentProcessIntegrityLevel() != base::HIGH_INTEGRITY)
+      return;
+    InProcessBrowserTest::TearDown();
+    UnInstallService();
+  }
+
+  base::HistogramTester histogram_tester_;
+  base::test::ScopedFeatureList enable_metrics_feature_;
+
+ private:
+  static bool InstallService() {
+    base::FilePath exe_dir;
+    base::PathService::Get(base::DIR_EXE, &exe_dir);
+    base::CommandLine service_cmd(
+        exe_dir.Append(installer::kElevationServiceExe));
+    service_cmd.AppendSwitch(
+        elevation_service::switches::kElevatorClsIdForTestingSwitch);
+    installer::InstallServiceWorkItem install_service_work_item(
+        install_static::GetElevationServiceName(),
+        install_static::GetElevationServiceDisplayName(), SERVICE_DEMAND_START,
+        service_cmd, base::CommandLine(base::CommandLine::NO_PROGRAM),
+        install_static::GetClientStateKeyPath(),
+        {install_static::GetElevatorClsid()},
+        {install_static::GetElevatorIid()});
+    install_service_work_item.set_best_effort(true);
+    install_service_work_item.set_rollback_enabled(false);
+    return install_service_work_item.Do();
+  }
+
+  static bool UnInstallService() {
+    return installer::InstallServiceWorkItem::DeleteService(
+        install_static::GetElevationServiceName(),
+        install_static::GetClientStateKeyPath(),
+        {install_static::GetElevatorClsid()},
+        {install_static::GetElevatorIid()});
+  }
+
+  install_static::ScopedInstallDetails scoped_install_details_;
+};
+
+// Test the basic interface to Encrypt and Decrypt data.
+IN_PROC_BROWSER_TEST_F(AppBoundEncryptionWinTest, EncryptDecrypt) {
+  ASSERT_TRUE(install_static::IsSystemInstall());
+  const std::string plaintext("plaintext");
+  std::string ciphertext;
+  DWORD last_error;
+
+  HRESULT hr = os_crypt::EncryptAppBoundString(
+      ProtectionLevel::PATH_VALIDATION, plaintext, ciphertext, last_error);
+
+  ASSERT_HRESULT_SUCCEEDED(hr);
+
+  std::string returned_plaintext;
+  hr = os_crypt::DecryptAppBoundString(ciphertext, returned_plaintext,
+                                       last_error);
+
+  ASSERT_HRESULT_SUCCEEDED(hr);
+  EXPECT_EQ(plaintext, returned_plaintext);
+}
+
+// These tests verify that the metrics are recorded correctly. The first load of
+// browser in the PRE_ test stores the "Test Key" with app-bound encryption and
+// the second stage of the test verifies it can be retrieved successfully.
+IN_PROC_BROWSER_TEST_F(AppBoundEncryptionWinTest, PRE_MetricsTest) {
+  ASSERT_TRUE(install_static::IsSystemInstall());
+  // These histograms are recorded on a background worker thread, so the test
+  // needs to wait until this task completes and the histograms are recorded.
+  WaitForHistogram("OSCrypt.AppBoundEncryption.Encrypt.ResultCode");
+  histogram_tester_.ExpectBucketCount(
+      "OSCrypt.AppBoundEncryption.Encrypt.ResultCode", S_OK, 1);
+
+  WaitForHistogram("OSCrypt.AppBoundEncryption.Encrypt.Time");
+}
+
+IN_PROC_BROWSER_TEST_F(AppBoundEncryptionWinTest, MetricsTest) {
+  ASSERT_TRUE(install_static::IsSystemInstall());
+  // These histograms are recorded on a background worker thread, so the test
+  // needs to wait until this task completes and the histograms are recorded.
+  WaitForHistogram("OSCrypt.AppBoundEncryption.Decrypt.ResultCode");
+  histogram_tester_.ExpectBucketCount(
+      "OSCrypt.AppBoundEncryption.Decrypt.ResultCode", S_OK, 1);
+
+  WaitForHistogram("OSCrypt.AppBoundEncryption.Decrypt.Time");
+}
+
+// Run this test manually to force uninstall the service.
+IN_PROC_BROWSER_TEST_F(AppBoundEncryptionWinTest, MANUAL_Uninstall) {}
+
+class AppBoundEncryptionWinTestNoService : public InProcessBrowserTest {
+ public:
+  AppBoundEncryptionWinTestNoService()
+      : scoped_install_details_(std::make_unique<FakeInstallDetails>()) {}
+
+ private:
+  install_static::ScopedInstallDetails scoped_install_details_;
+};
+
+IN_PROC_BROWSER_TEST_F(AppBoundEncryptionWinTestNoService, NoService) {
+  const std::string plaintext("plaintext");
+  std::string ciphertext;
+  DWORD last_error;
+
+  HRESULT hr = os_crypt::EncryptAppBoundString(
+      ProtectionLevel::PATH_VALIDATION, plaintext, ciphertext, last_error);
+
+  EXPECT_EQ(REGDB_E_CLASSNOTREG, hr);
+  EXPECT_EQ(DWORD{ERROR_GEN_FAILURE}, last_error);
+}
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 8bfc3599..badad2b 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -423,6 +423,7 @@
 #include "chrome/browser/font_prewarmer_tab_helper.h"
 #include "chrome/browser/media/cdm_pref_service_helper.h"
 #include "chrome/browser/media/media_foundation_service_monitor.h"
+#include "chrome/browser/os_crypt/app_bound_encryption_metrics_win.h"
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
 #include "chrome/browser/win/conflicts/incompatible_applications_updater.h"
 #include "chrome/browser/win/conflicts/module_database.h"
@@ -1181,6 +1182,7 @@
   component_updater::RegisterPrefsForSwReporter(registry);
   safe_browsing::RegisterChromeCleanerScanCompletionTimePref(registry);
   MediaFoundationServiceMonitor::RegisterPrefs(registry);
+  os_crypt::RegisterLocalStatePrefs(registry);
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   IncompatibleApplicationsUpdater::RegisterLocalStatePrefs(registry);
   ModuleDatabase::RegisterLocalStatePrefs(registry);