[BSC] Add UnexportableKeyLoader class
This CL adds a helper UnexportableKeyLoader class that allows scheduling
tasks on UnexportableKeyService before a key is loaded.
Bug: b/263249728
Change-Id: Ib2ca96e9bd13b9cea95be7343b3dc0f705159357
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4454999
Commit-Queue: Alex Ilin <alexilin@chromium.org>
Reviewed-by: Kristian Monsen <kristianm@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1143982}
diff --git a/components/unexportable_keys/unexportable_key_loader_unittest.cc b/components/unexportable_keys/unexportable_key_loader_unittest.cc
new file mode 100644
index 0000000..b5fd3a1
--- /dev/null
+++ b/components/unexportable_keys/unexportable_key_loader_unittest.cc
@@ -0,0 +1,195 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/unexportable_keys/unexportable_key_loader.h"
+
+#include "base/check.h"
+#include "base/test/bind.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_future.h"
+#include "components/unexportable_keys/background_task_priority.h"
+#include "components/unexportable_keys/service_error.h"
+#include "components/unexportable_keys/unexportable_key_service_impl.h"
+#include "components/unexportable_keys/unexportable_key_task_manager.h"
+#include "crypto/scoped_mock_unexportable_key_provider.h"
+#include "crypto/signature_verifier.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace unexportable_keys {
+
+namespace {
+
+constexpr crypto::SignatureVerifier::SignatureAlgorithm
+ kAcceptableAlgorithms[] = {crypto::SignatureVerifier::ECDSA_SHA256};
+constexpr BackgroundTaskPriority kTaskPriority =
+ BackgroundTaskPriority::kUserVisible;
+
+} // namespace
+
+class UnexportableKeyLoaderTest : public testing::Test {
+ public:
+ UnexportableKeyLoaderTest()
+ : task_manager_(std::make_unique<UnexportableKeyTaskManager>()),
+ service_(std::make_unique<UnexportableKeyServiceImpl>(*task_manager_)) {
+ }
+
+ UnexportableKeyServiceImpl& service() { return *service_; }
+
+ void RunBackgroundTasks() { task_environment_.RunUntilIdle(); }
+
+ void ResetService() {
+ task_manager_ = std::make_unique<UnexportableKeyTaskManager>();
+ service_ = std::make_unique<UnexportableKeyServiceImpl>(*task_manager_);
+ }
+
+ void DisableKeyProvider() {
+ // Using `emplace()` to destroy the existing scoped object before
+ // constructing a new one.
+ scoped_key_provider_.emplace<crypto::ScopedNullUnexportableKeyProvider>();
+ }
+
+ std::vector<uint8_t> GenerateNewKeyAndReturnWrappedKey() {
+ base::test::TestFuture<ServiceErrorOr<UnexportableKeyId>> generate_future;
+ service().GenerateSigningKeySlowlyAsync(
+ kAcceptableAlgorithms, kTaskPriority, generate_future.GetCallback());
+ RunBackgroundTasks();
+ ServiceErrorOr<UnexportableKeyId> key_id = generate_future.Get();
+ CHECK(key_id.has_value());
+
+ ServiceErrorOr<std::vector<uint8_t>> wrapped_key =
+ service().GetWrappedKey(*key_id);
+ CHECK(wrapped_key.has_value());
+ return *wrapped_key;
+ }
+
+ private:
+ base::test::TaskEnvironment task_environment_{
+ base::test::TaskEnvironment::ThreadPoolExecutionMode::
+ QUEUED}; // QUEUED - tasks don't run until `RunUntilIdle()` is
+ // called.
+ // Provides a mock key provider by default.
+ absl::variant<crypto::ScopedMockUnexportableKeyProvider,
+ crypto::ScopedNullUnexportableKeyProvider>
+ scoped_key_provider_;
+ std::unique_ptr<UnexportableKeyTaskManager> task_manager_;
+ std::unique_ptr<UnexportableKeyServiceImpl> service_;
+};
+
+TEST_F(UnexportableKeyLoaderTest, CreateFromWrappedKeySync) {
+ std::vector<uint8_t> wrapped_key = GenerateNewKeyAndReturnWrappedKey();
+
+ // `wrapped_key` is already registered in the service. The loader should
+ // return a key immediately.
+ auto key_loader = UnexportableKeyLoader::CreateFromWrappedKey(
+ service(), wrapped_key, kTaskPriority);
+ EXPECT_EQ(key_loader->GetStateForTesting(),
+ UnexportableKeyLoader::State::kReady);
+ EXPECT_TRUE(key_loader->GetKeyIdOrErrorForTesting().has_value());
+
+ base::test::TestFuture<ServiceErrorOr<UnexportableKeyId>> on_load_future;
+ key_loader->InvokeCallbackAfterKeyLoaded(on_load_future.GetCallback());
+ EXPECT_TRUE(on_load_future.IsReady());
+ EXPECT_EQ(key_loader->GetKeyIdOrErrorForTesting(), on_load_future.Get());
+}
+
+TEST_F(UnexportableKeyLoaderTest, CreateFromWrappedKeyAsync) {
+ std::vector<uint8_t> wrapped_key = GenerateNewKeyAndReturnWrappedKey();
+ // A new key is still registered inside the service. Reset the service to
+ // remove the key.
+ ResetService();
+
+ auto key_loader = UnexportableKeyLoader::CreateFromWrappedKey(
+ service(), wrapped_key, kTaskPriority);
+ EXPECT_EQ(key_loader->GetStateForTesting(),
+ UnexportableKeyLoader::State::kLoading);
+ EXPECT_EQ(key_loader->GetKeyIdOrErrorForTesting(),
+ base::unexpected(ServiceError::kKeyNotReady));
+
+ base::test::TestFuture<ServiceErrorOr<UnexportableKeyId>> on_load_future;
+ key_loader->InvokeCallbackAfterKeyLoaded(on_load_future.GetCallback());
+ EXPECT_FALSE(on_load_future.IsReady());
+
+ RunBackgroundTasks();
+ EXPECT_EQ(key_loader->GetStateForTesting(),
+ UnexportableKeyLoader::State::kReady);
+ EXPECT_TRUE(key_loader->GetKeyIdOrErrorForTesting().has_value());
+ EXPECT_TRUE(on_load_future.IsReady());
+ EXPECT_EQ(key_loader->GetKeyIdOrErrorForTesting(), on_load_future.Get());
+}
+
+TEST_F(UnexportableKeyLoaderTest, CreateFromWrappedKeyMultipleCallbacks) {
+ std::vector<uint8_t> wrapped_key = GenerateNewKeyAndReturnWrappedKey();
+ // A new key is still registered inside the service. Reset the service to
+ // remove the key.
+ ResetService();
+
+ auto key_loader = UnexportableKeyLoader::CreateFromWrappedKey(
+ service(), wrapped_key, kTaskPriority);
+
+ std::array<base::test::TestFuture<ServiceErrorOr<UnexportableKeyId>>, 5>
+ on_load_futures;
+ for (auto& future : on_load_futures) {
+ key_loader->InvokeCallbackAfterKeyLoaded(future.GetCallback());
+ EXPECT_FALSE(future.IsReady());
+ }
+
+ RunBackgroundTasks();
+ EXPECT_EQ(key_loader->GetStateForTesting(),
+ UnexportableKeyLoader::State::kReady);
+ EXPECT_TRUE(key_loader->GetKeyIdOrErrorForTesting().has_value());
+ for (auto& future : on_load_futures) {
+ EXPECT_TRUE(future.IsReady());
+ EXPECT_EQ(key_loader->GetKeyIdOrErrorForTesting(), future.Get());
+ }
+}
+
+TEST_F(UnexportableKeyLoaderTest, CreateWithNewKey) {
+ auto key_loader = UnexportableKeyLoader::CreateWithNewKey(
+ service(), kAcceptableAlgorithms, kTaskPriority);
+ EXPECT_EQ(key_loader->GetStateForTesting(),
+ UnexportableKeyLoader::State::kLoading);
+ EXPECT_EQ(key_loader->GetKeyIdOrErrorForTesting(),
+ base::unexpected(ServiceError::kKeyNotReady));
+
+ base::test::TestFuture<ServiceErrorOr<UnexportableKeyId>> on_load_future;
+ key_loader->InvokeCallbackAfterKeyLoaded(on_load_future.GetCallback());
+ EXPECT_FALSE(on_load_future.IsReady());
+
+ RunBackgroundTasks();
+ EXPECT_EQ(key_loader->GetStateForTesting(),
+ UnexportableKeyLoader::State::kReady);
+ EXPECT_TRUE(key_loader->GetKeyIdOrErrorForTesting().has_value());
+ EXPECT_TRUE(on_load_future.IsReady());
+ EXPECT_EQ(key_loader->GetKeyIdOrErrorForTesting(), on_load_future.Get());
+}
+
+TEST_F(UnexportableKeyLoaderTest, CreateWithNewKeyFailure) {
+ DisableKeyProvider();
+ auto key_loader = UnexportableKeyLoader::CreateWithNewKey(
+ service(), kAcceptableAlgorithms, kTaskPriority);
+ EXPECT_EQ(key_loader->GetStateForTesting(),
+ UnexportableKeyLoader::State::kReady);
+ EXPECT_EQ(key_loader->GetKeyIdOrErrorForTesting(),
+ base::unexpected(ServiceError::kNoKeyProvider));
+}
+
+TEST_F(UnexportableKeyLoaderTest, SignDataAfterLoading) {
+ auto key_loader = UnexportableKeyLoader::CreateWithNewKey(
+ service(), kAcceptableAlgorithms, kTaskPriority);
+
+ base::test::TestFuture<ServiceErrorOr<std::vector<uint8_t>>> sign_future;
+ key_loader->InvokeCallbackAfterKeyLoaded(base::BindLambdaForTesting(
+ [&](ServiceErrorOr<UnexportableKeyId> key_id_or_error) {
+ ASSERT_TRUE(key_id_or_error.has_value());
+ service().SignSlowlyAsync(*key_id_or_error,
+ std::vector<uint8_t>({1, 2, 3}),
+ kTaskPriority, sign_future.GetCallback());
+ }));
+ EXPECT_FALSE(sign_future.IsReady());
+ RunBackgroundTasks();
+ EXPECT_TRUE(sign_future.IsReady());
+ EXPECT_TRUE(sign_future.Get().has_value());
+}
+
+} // namespace unexportable_keys