// Copyright 2024 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/dpapi_key_provider.h"
#include <optional>
#include <utility>
#include <vector>
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "components/os_crypt/async/browser/os_crypt_async.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/prefs/testing_pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace os_crypt_async {
// This class tests that DPAPIKeyProvider is forwards and backwards
// compatible with OSCrypt.
class DPAPIKeyProviderTestBase : public ::testing::Test {
protected:
void TearDown() override {
histograms_.ExpectBucketCount("OSCrypt.DPAPIProvider.Status",
expected_histogram_, 1);
}
Encryptor GetInstanceSync(
OSCryptAsync& factory,
Encryptor::Option option = Encryptor::Option::kNone) {
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();
}),
option);
run_loop.Run();
return std::move(*encryptor);
}
Encryptor GetInstanceWithDPAPI() {
std::vector<std::pair<size_t, std::unique_ptr<KeyProvider>>> providers;
providers.emplace_back(std::make_pair(
/*precedence=*/10u, std::make_unique<DPAPIKeyProvider>(&prefs_)));
OSCryptAsync factory(std::move(providers));
return GetInstanceSync(factory);
}
TestingPrefServiceSimple prefs_;
DPAPIKeyProvider::KeyStatus expected_histogram_ =
DPAPIKeyProvider::KeyStatus::kSuccess;
private:
base::HistogramTester histograms_;
base::test::TaskEnvironment task_environment_;
};
class DPAPIKeyProviderTest : public DPAPIKeyProviderTestBase {
protected:
void SetUp() override {
OSCrypt::RegisterLocalPrefs(prefs_.registry());
OSCrypt::Init(&prefs_);
}
void TearDown() override {
OSCrypt::ResetStateForTesting();
DPAPIKeyProviderTestBase::TearDown();
}
};
TEST_F(DPAPIKeyProviderTest, Basic) {
Encryptor encryptor = GetInstanceWithDPAPI();
ASSERT_TRUE(encryptor.IsEncryptionAvailable());
ASSERT_TRUE(encryptor.IsDecryptionAvailable());
std::string plaintext = "secrets";
std::string ciphertext;
ASSERT_TRUE(encryptor.EncryptString(plaintext, &ciphertext));
std::string decrypted;
EXPECT_TRUE(encryptor.DecryptString(ciphertext, &decrypted));
EXPECT_EQ(plaintext, decrypted);
}
TEST_F(DPAPIKeyProviderTest, DecryptOld) {
Encryptor encryptor = GetInstanceWithDPAPI();
std::string plaintext = "secrets";
std::string ciphertext;
ASSERT_TRUE(OSCrypt::EncryptString(plaintext, &ciphertext));
std::string decrypted;
EXPECT_TRUE(encryptor.DecryptString(ciphertext, &decrypted));
EXPECT_EQ(plaintext, decrypted);
}
TEST_F(DPAPIKeyProviderTest, EncryptForOld) {
Encryptor encryptor = GetInstanceWithDPAPI();
std::string plaintext = "secrets";
std::string ciphertext;
ASSERT_TRUE(encryptor.EncryptString(plaintext, &ciphertext));
std::string decrypted;
EXPECT_TRUE(OSCrypt::DecryptString(ciphertext, &decrypted));
EXPECT_EQ(plaintext, decrypted);
}
// Very small Key Provider that provides a random key.
class RandomKeyProvider : public KeyProvider {
private:
void GetKey(KeyCallback callback) final {
std::vector<uint8_t> key(Encryptor::Key::kAES256GCMKeySize);
base::RandBytes(key);
std::move(callback).Run("_",
Encryptor::Key(key, mojom::Algorithm::kAES256GCM));
}
bool UseForEncryption() final { return true; }
bool IsCompatibleWithOsCryptSync() final { return false; }
};
TEST_F(DPAPIKeyProviderTest, EncryptWithOptions) {
std::vector<std::pair<size_t, std::unique_ptr<KeyProvider>>> providers;
providers.emplace_back(std::make_pair(
/*precedence=*/10u, std::make_unique<DPAPIKeyProvider>(&prefs_)));
// Random Key Provider will take precedence here.
providers.emplace_back(std::make_pair(/*precedence=*/15u,
std::make_unique<RandomKeyProvider>()));
OSCryptAsync factory(std::move(providers));
Encryptor encryptor = GetInstanceSync(factory);
std::optional<std::vector<uint8_t>> ciphertext;
{
// This should use RandomKeyProvider.
ciphertext = encryptor.EncryptString("secrets");
ASSERT_TRUE(ciphertext);
EXPECT_EQ(ciphertext->at(0), '_');
std::string plaintext;
// Fail, as it's encrypted with the '_' key provider.
EXPECT_FALSE(OSCrypt::DecryptString(
std::string(ciphertext->begin(), ciphertext->end()), &plaintext));
// Encryptor should be able to decrypt.
const auto decrypted = encryptor.DecryptData(*ciphertext);
EXPECT_TRUE(decrypted);
EXPECT_EQ(*decrypted, "secrets");
}
{
// Now, obtain a second encryptor but with the kEncryptSyncCompat option.
Encryptor encryptor_with_option =
GetInstanceSync(factory, Encryptor::Option::kEncryptSyncCompat);
// This should now encrypt with DPAPI key provider, compatible with OSCrypt
// sync, but still contain both keys.
const auto second_ciphertext =
encryptor_with_option.EncryptString("moresecrets");
ASSERT_TRUE(second_ciphertext);
std::string plaintext;
// First, test a decrypt using OSCrypt sync works.
ASSERT_TRUE(OSCrypt::DecryptString(
std::string(second_ciphertext->begin(), second_ciphertext->end()),
&plaintext));
EXPECT_EQ(plaintext, "moresecrets");
// Now test both encryptors can decrypt both sets of ciphertext, regardless
// of the option.
{
// First Encryptor with first ciphertext.
const auto decrypted = encryptor.DecryptData(*ciphertext);
ASSERT_TRUE(decrypted);
EXPECT_EQ(*decrypted, "secrets");
}
{
// First Encryptor with second ciphertext.
const auto decrypted = encryptor.DecryptData(*second_ciphertext);
ASSERT_TRUE(decrypted);
EXPECT_EQ(*decrypted, "moresecrets");
}
{
// Second encryptor (with option) with first ciphertext.
const auto decrypted = encryptor_with_option.DecryptData(*ciphertext);
ASSERT_TRUE(decrypted);
EXPECT_EQ(*decrypted, "secrets");
}
{
// Second encryptor (with option) with second ciphertext.
const auto decrypted =
encryptor_with_option.DecryptData(*second_ciphertext);
ASSERT_TRUE(decrypted);
EXPECT_EQ(*decrypted, "moresecrets");
}
}
}
// Only test a few scenarios here, just to verify the error histogram is always
// logged.
TEST_F(DPAPIKeyProviderTest, OSCryptNotInit) {
prefs_.ClearPref("os_crypt.encrypted_key");
Encryptor encryptor = GetInstanceWithDPAPI();
// Encryption is available because OSCrypt sync already initialized before the
// test fixture invalidated the key for the DPAPI Key Provider, and so while
// the encryptor has no keyring, it delegates successfully to OSCrypt sync.
EXPECT_TRUE(encryptor.IsEncryptionAvailable());
EXPECT_TRUE(encryptor.IsDecryptionAvailable());
expected_histogram_ = DPAPIKeyProvider::KeyStatus::kKeyNotFound;
}
TEST_F(DPAPIKeyProviderTest, OSCryptBadKeyHeader) {
prefs_.SetString("os_crypt.encrypted_key", "badkeybadkey");
Encryptor encryptor = GetInstanceWithDPAPI();
expected_histogram_ = DPAPIKeyProvider::KeyStatus::kInvalidKeyHeader;
}
TEST_F(DPAPIKeyProviderTestBase, NoOSCrypt) {
Encryptor encryptor = GetInstanceWithDPAPI();
// Compare with DPAPIKeyProviderTest.OSCryptNotInit above: Encryption is not
// available for this test because OSCrypt was never initialized and so the
// encryptor has no key, and OSCrypt::IsEncryptionAvailable is also returning
// false.
EXPECT_FALSE(encryptor.IsEncryptionAvailable());
EXPECT_FALSE(encryptor.IsDecryptionAvailable());
expected_histogram_ = DPAPIKeyProvider::KeyStatus::kKeyNotFound;
}
} // namespace os_crypt_async