| // 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/os_crypt/async/browser/os_crypt_async.h" |
| |
| #include <optional> |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "base/test/gtest_util.h" |
| #include "base/test/task_environment.h" |
| #include "components/os_crypt/async/browser/key_provider.h" |
| #include "components/os_crypt/async/browser/test_utils.h" |
| #include "components/os_crypt/async/common/algorithm.mojom.h" |
| #include "components/os_crypt/async/common/encryptor.h" |
| #include "components/os_crypt/sync/os_crypt.h" |
| #include "components/os_crypt/sync/os_crypt_mocker.h" |
| #include "crypto/hkdf.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace os_crypt_async { |
| |
| class OSCryptAsyncTest : public ::testing::Test { |
| protected: |
| using ProviderList = |
| std::vector<std::pair<size_t, std::unique_ptr<KeyProvider>>>; |
| |
| Encryptor GetInstanceSync(OSCryptAsync& factory) { |
| base::RunLoop run_loop; |
| std::optional<Encryptor> encryptor; |
| auto sub = factory.GetInstance(base::BindLambdaForTesting( |
| [&](Encryptor encryptor_param, bool success) { |
| EXPECT_TRUE(success); |
| encryptor.emplace(std::move(encryptor_param)); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| return std::move(*encryptor); |
| } |
| |
| base::test::TaskEnvironment task_environment_; |
| }; |
| |
| class TestKeyProvider : public KeyProvider { |
| public: |
| explicit TestKeyProvider(const std::string& name = "TEST", |
| bool use_for_encryption = true) |
| : name_(name), use_for_encryption_(use_for_encryption) {} |
| |
| protected: |
| Encryptor::Key GenerateKey() { |
| // Make the key derive from the name to ensure different providers have |
| // different keys. |
| std::string key = crypto::HkdfSha256(name_, "salt", "info", |
| Encryptor::Key::kAES256GCMKeySize); |
| return Encryptor::Key(std::vector<uint8_t>(key.begin(), key.end()), |
| mojom::Algorithm::kAES256GCM); |
| } |
| |
| const std::string name_; |
| |
| private: |
| void GetKey(KeyCallback callback) override { |
| std::move(callback).Run(name_, GenerateKey()); |
| } |
| |
| bool UseForEncryption() override { return use_for_encryption_; } |
| |
| const bool use_for_encryption_; |
| }; |
| |
| TEST_F(OSCryptAsyncTest, EncryptHeader) { |
| const std::string kTestProviderName("TEST"); |
| ProviderList providers; |
| providers.emplace_back(std::make_pair( |
| 10u, std::make_unique<TestKeyProvider>(kTestProviderName))); |
| OSCryptAsync factory(std::move(providers)); |
| Encryptor encryptor = GetInstanceSync(factory); |
| |
| auto ciphertext = encryptor.EncryptString("secrets"); |
| ASSERT_TRUE(std::equal(kTestProviderName.cbegin(), kTestProviderName.cend(), |
| ciphertext->cbegin())); |
| } |
| |
| TEST_F(OSCryptAsyncTest, TwoProvidersBothEnabled) { |
| std::optional<std::vector<uint8_t>> ciphertext; |
| { |
| const std::string kFooProviderName("FOO"); |
| ProviderList providers; |
| providers.emplace_back( |
| /*precedence=*/10u, std::make_unique<TestKeyProvider>( |
| kFooProviderName, /*use_for_encryption=*/true)); |
| providers.emplace_back( |
| /*precedence=*/5u, |
| std::make_unique<TestKeyProvider>("BAR", /*use_for_encryption=*/true)); |
| OSCryptAsync factory(std::move(providers)); |
| Encryptor encryptor = GetInstanceSync(factory); |
| |
| ciphertext = encryptor.EncryptString("secrets"); |
| ASSERT_TRUE(ciphertext); |
| // The higher of the two providers should have been picked for data |
| // encryption. |
| EXPECT_TRUE(std::equal(kFooProviderName.cbegin(), kFooProviderName.cend(), |
| ciphertext->cbegin())); |
| } |
| // Check that provider precedence does not matter for decrypt. |
| { |
| ProviderList providers; |
| providers.emplace_back( |
| /*precedence=*/5u, |
| std::make_unique<TestKeyProvider>("FOO", /*use_for_encryption=*/true)); |
| // BAR is the preferred provider since it has the higher precedence. |
| providers.emplace_back( |
| /*precedence=*/10u, |
| std::make_unique<TestKeyProvider>("BAR", /*use_for_encryption=*/true)); |
| OSCryptAsync factory(std::move(providers)); |
| Encryptor encryptor = GetInstanceSync(factory); |
| |
| auto plaintext = encryptor.DecryptData(*ciphertext); |
| // The correct provider based on the encrypted data header should have been |
| // picked for data decryption. |
| ASSERT_TRUE(plaintext); |
| EXPECT_EQ("secrets", *plaintext); |
| } |
| } |
| |
| TEST_F(OSCryptAsyncTest, TwoProvidersOneEnabled) { |
| std::optional<std::vector<uint8_t>> ciphertext; |
| { |
| const std::string kBarProviderName("BAR"); |
| ProviderList providers; |
| providers.emplace_back( |
| /*precedence=*/10u, |
| std::make_unique<TestKeyProvider>("FOO", /*use_for_encryption=*/false)); |
| providers.emplace_back( |
| /*precedence=*/5u, std::make_unique<TestKeyProvider>( |
| kBarProviderName, /*use_for_encryption=*/true)); |
| OSCryptAsync factory(std::move(providers)); |
| Encryptor encryptor = GetInstanceSync(factory); |
| |
| ciphertext = encryptor.EncryptString("secrets"); |
| ASSERT_TRUE(ciphertext); |
| // Despite FOO being higher than BAR, BAR is chosen for encryption because |
| // FOO is not enabled for encryption. |
| EXPECT_TRUE(std::equal(kBarProviderName.cbegin(), kBarProviderName.cend(), |
| ciphertext->cbegin())); |
| } |
| // Check that even with no enabled providers, data can still be decrypted by |
| // any registered provider. |
| { |
| ProviderList providers; |
| // Neither is enabled for encrypt. |
| providers.emplace_back( |
| /*precedence=*/5u, |
| std::make_unique<TestKeyProvider>("FOO", /*use_for_encryption=*/false)); |
| providers.emplace_back( |
| /*precedence=*/10u, |
| std::make_unique<TestKeyProvider>("BAR", /*use_for_encryption=*/false)); |
| OSCryptAsync factory(std::move(providers)); |
| Encryptor encryptor = GetInstanceSync(factory); |
| |
| auto plaintext = encryptor.DecryptData(*ciphertext); |
| // The correct provider based on the encrypted data header should have been |
| // picked for data decryption. |
| ASSERT_TRUE(plaintext); |
| EXPECT_EQ("secrets", *plaintext); |
| } |
| } |
| |
| class SlowTestKeyProvider : public TestKeyProvider { |
| public: |
| explicit SlowTestKeyProvider(base::TimeDelta sleep_time) |
| : sleep_time_(sleep_time) {} |
| |
| private: |
| void GetKey(KeyCallback callback) override { |
| base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](KeyCallback callback, Encryptor::Key key, |
| const std::string& name) { |
| std::move(callback).Run(name, std::move(key)); |
| }, |
| std::move(callback), GenerateKey(), name_), |
| sleep_time_); |
| } |
| |
| const base::TimeDelta sleep_time_; |
| }; |
| |
| // This test verifies that GetInstanceAsync can correctly handle multiple queued |
| // requests for an instance for a slow init. |
| TEST_F(OSCryptAsyncTest, MultipleCalls) { |
| ProviderList providers; |
| providers.emplace_back( |
| /*precedence=*/10u, |
| std::make_unique<SlowTestKeyProvider>(base::Seconds(1))); |
| OSCryptAsync factory(std::move(providers)); |
| |
| size_t calls = 0; |
| const size_t kExpectedCalls = 10; |
| base::RunLoop run_loop; |
| std::list<base::CallbackListSubscription> subs; |
| for (size_t call = 0; call < kExpectedCalls; call++) { |
| subs.push_back(factory.GetInstance(base::BindLambdaForTesting( |
| [&calls, &run_loop](Encryptor encryptor, bool success) { |
| calls++; |
| if (calls == kExpectedCalls) { |
| run_loop.Quit(); |
| } |
| }))); |
| } |
| run_loop.Run(); |
| EXPECT_EQ(calls, kExpectedCalls); |
| } |
| |
| // This test verifies that if the subscription from CallbackList moves out of |
| // scope, then the callback never occurs. |
| TEST_F(OSCryptAsyncTest, SubscriptionCancelled) { |
| ProviderList providers; |
| providers.emplace_back( |
| /*precedence=*/10u, |
| std::make_unique<SlowTestKeyProvider>(base::Seconds(1))); |
| OSCryptAsync factory(std::move(providers)); |
| |
| { |
| auto sub = factory.GetInstance( |
| base::BindOnce([](Encryptor encryptor, bool success) { |
| // This should not be called, as the subscription went out of scope. |
| NOTREACHED(); |
| })); |
| } |
| |
| // Complete the init on a nested RunLoop. |
| base::RunLoop run_loop; |
| auto sub = factory.GetInstance( |
| base::BindLambdaForTesting([&](Encryptor encryptor_param, bool success) { |
| EXPECT_TRUE(success); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| TEST_F(OSCryptAsyncTest, TestOSCryptAsyncInterface) { |
| auto os_crypt = GetTestOSCryptAsyncForTesting(); |
| auto encryptor = GetInstanceSync(*os_crypt); |
| auto ciphertext = encryptor.EncryptString("testsecrets"); |
| ASSERT_TRUE(ciphertext); |
| { |
| auto decrypted = encryptor.DecryptData(*ciphertext); |
| ASSERT_TRUE(decrypted); |
| EXPECT_EQ(*decrypted, "testsecrets"); |
| } |
| { |
| // Verify that all encryptors returned by the test OSCryptAsync instance use |
| // the same keys. |
| auto second_encryptor = GetInstanceSync(*os_crypt); |
| auto decrypted = second_encryptor.DecryptData(*ciphertext); |
| ASSERT_TRUE(decrypted); |
| EXPECT_EQ(*decrypted, "testsecrets"); |
| } |
| } |
| |
| TEST_F(OSCryptAsyncTest, TestEncryptorInterface) { |
| auto encryptor = GetTestEncryptorForTesting(); |
| auto ciphertext = encryptor.EncryptString("testsecrets"); |
| ASSERT_TRUE(ciphertext); |
| auto decrypted = encryptor.DecryptData(*ciphertext); |
| ASSERT_TRUE(decrypted); |
| EXPECT_EQ(*decrypted, "testsecrets"); |
| } |
| |
| class FailingKeyProvider : public TestKeyProvider { |
| private: |
| void GetKey(KeyCallback callback) override { |
| std::move(callback).Run("", std::nullopt); |
| } |
| }; |
| |
| // Some tests require a working OSCrypt. |
| class OSCryptAsyncTestWithOSCrypt : public OSCryptAsyncTest { |
| protected: |
| void SetUp() override { OSCryptMocker::SetUp(); } |
| |
| void TearDown() override { |
| OSCryptMocker::TearDown(); |
| #if BUILDFLAG(IS_WIN) |
| OSCrypt::ResetStateForTesting(); |
| #endif // BUILDFLAG(IS_WIN) |
| } |
| }; |
| |
| // This test merely verifies that OSCryptAsync can operate with no key providers |
| // and return a valid Encryptor with no keys, and that it can interop with |
| // OSCrypt. The rest of the encryption tests for this mode are located in |
| // encryptor_unittest.cc. |
| TEST_F(OSCryptAsyncTestWithOSCrypt, Empty) { |
| ProviderList providers; |
| OSCryptAsync factory(std::move(providers)); |
| Encryptor encryptor = GetInstanceSync(factory); |
| std::string ciphertext; |
| EXPECT_TRUE(OSCrypt::EncryptString("secrets", &ciphertext)); |
| std::string plaintext; |
| EXPECT_TRUE(encryptor.DecryptString(ciphertext, &plaintext)); |
| EXPECT_EQ("secrets", plaintext); |
| } |
| |
| TEST_F(OSCryptAsyncTestWithOSCrypt, FailingKeyProvider) { |
| ProviderList providers; |
| providers.emplace_back(/*precedence=*/10u, |
| std::make_unique<FailingKeyProvider>()); |
| OSCryptAsync factory(std::move(providers)); |
| // TODO: Work out how best to handle provider failures. |
| Encryptor encryptor = GetInstanceSync(factory); |
| |
| { |
| // Encryption should still work, because an empty Encryptor is made which |
| // falls back to OSCrypt. |
| auto ciphertext = encryptor.EncryptString("secrets"); |
| EXPECT_TRUE(ciphertext); |
| std::string plaintext; |
| EXPECT_TRUE(OSCrypt::DecryptString( |
| std::string(ciphertext->cbegin(), ciphertext->cend()), &plaintext)); |
| EXPECT_EQ("secrets", plaintext); |
| } |
| { |
| std::string ciphertext; |
| EXPECT_TRUE(OSCrypt::EncryptString("secrets", &ciphertext)); |
| std::string plaintext; |
| // Decryption falls back to OSCrypt if there are no matching providers. In |
| // this case, there are no providers at all. |
| EXPECT_TRUE(encryptor.DecryptString(ciphertext, &plaintext)); |
| EXPECT_EQ("secrets", plaintext); |
| } |
| } |
| |
| using OSCryptAsyncDeathTest = OSCryptAsyncTest; |
| |
| TEST_F(OSCryptAsyncDeathTest, SamePrecedence) { |
| ProviderList providers; |
| providers.emplace_back( |
| /*precedence=*/5u, |
| std::make_unique<TestKeyProvider>("A", /*use_for_encryption=*/true)); |
| providers.emplace_back( |
| /*precedence=*/20u, |
| std::make_unique<TestKeyProvider>("B", /*use_for_encryption=*/true)); |
| providers.emplace_back( |
| /*precedence=*/5u, |
| std::make_unique<TestKeyProvider>("C", /*use_for_encryption=*/true)); |
| providers.emplace_back( |
| /*precedence=*/10u, |
| std::make_unique<TestKeyProvider>("D", /*use_for_encryption=*/true)); |
| EXPECT_DCHECK_DEATH_WITH({ OSCryptAsync factory(std::move(providers)); }, |
| "Cannot have two providers with same precedence."); |
| } |
| |
| TEST_F(OSCryptAsyncDeathTest, SameName) { |
| ProviderList providers; |
| providers.emplace_back( |
| /*precedence=*/5u, |
| std::make_unique<TestKeyProvider>("TEST", /*use_for_encryption=*/true)); |
| providers.emplace_back( |
| /*precedence=*/10u, |
| std::make_unique<TestKeyProvider>("TEST", /*use_for_encryption=*/true)); |
| EXPECT_DCHECK_DEATH_WITH( |
| { |
| OSCryptAsync factory(std::move(providers)); |
| std::ignore = GetInstanceSync(factory); |
| }, |
| "Tags must not overlap."); |
| } |
| |
| TEST_F(OSCryptAsyncDeathTest, OverlappingNames) { |
| ProviderList providers; |
| providers.emplace_back( |
| /*precedence=*/5u, |
| std::make_unique<TestKeyProvider>("TEST", /*use_for_encryption=*/true)); |
| providers.emplace_back( |
| /*precedence=*/10u, |
| std::make_unique<TestKeyProvider>("TEST2", /*use_for_encryption=*/true)); |
| EXPECT_DCHECK_DEATH_WITH( |
| { |
| OSCryptAsync factory(std::move(providers)); |
| std::ignore = GetInstanceSync(factory); |
| }, |
| "Tags must not overlap."); |
| } |
| |
| TEST_F(OSCryptAsyncDeathTest, OverlappingNamesBackwards) { |
| ProviderList providers; |
| providers.emplace_back( |
| /*precedence=*/5u, |
| std::make_unique<TestKeyProvider>("TEST2", /*use_for_encryption=*/true)); |
| providers.emplace_back( |
| /*precedence=*/10u, |
| std::make_unique<TestKeyProvider>("TEST", /*use_for_encryption=*/true)); |
| EXPECT_DCHECK_DEATH_WITH( |
| { |
| OSCryptAsync factory(std::move(providers)); |
| std::ignore = GetInstanceSync(factory); |
| }, |
| "Tags must not overlap."); |
| } |
| |
| } // namespace os_crypt_async |