blob: 9be94625b5fd93a0ed9ab97c8679ff6c46394850 [file] [log] [blame]
// Copyright 2020 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 "components/sync/trusted_vault/standalone_trusted_vault_backend.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "components/os_crypt/os_crypt.h"
#include "components/os_crypt/os_crypt_mocker.h"
#include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
#include "components/sync/driver/sync_driver_switches.h"
#include "components/sync/trusted_vault/securebox.h"
#include "components/sync/trusted_vault/trusted_vault_connection.h"
#include "components/sync/trusted_vault/trusted_vault_server_constants.h"
#include "components/sync/trusted_vault/trusted_vault_switches.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
namespace {
using testing::_;
using testing::ElementsAre;
using testing::Eq;
using testing::IsEmpty;
using testing::IsNull;
using testing::Mock;
using testing::Ne;
using testing::NotNull;
using testing::SaveArg;
MATCHER_P(KeyMaterialEq, expected, "") {
const std::string& key_material = arg.key_material();
const std::vector<uint8_t> key_material_as_bytes(key_material.begin(),
key_material.end());
return key_material_as_bytes == expected;
}
MATCHER_P2(TrustedVaultKeyAndVersionEq, expected_key, expected_version, "") {
const TrustedVaultKeyAndVersion& key_and_version = arg;
return key_and_version.key == expected_key &&
key_and_version.version == expected_version;
}
MATCHER_P(PublicKeyWhenExportedEq, expected, "") {
const SecureBoxPublicKey& actual_public_key = arg;
return actual_public_key.ExportToBytes() == expected;
}
base::FilePath CreateUniqueTempDir(base::ScopedTempDir* temp_dir) {
EXPECT_TRUE(temp_dir->CreateUniqueTempDir());
return temp_dir->GetPath();
}
CoreAccountInfo MakeAccountInfoWithGaiaId(const std::string& gaia_id) {
CoreAccountInfo account_info;
account_info.gaia = gaia_id;
return account_info;
}
sync_pb::LocalTrustedVault ReadLocalTrustedVaultFile(
const base::FilePath& path) {
std::string ciphertext;
base::ReadFileToString(path, &ciphertext);
std::string decrypted_content;
OSCrypt::DecryptString(ciphertext, &decrypted_content);
sync_pb::LocalTrustedVault proto;
proto.ParseFromString(decrypted_content);
return proto;
}
class MockDelegate : public StandaloneTrustedVaultBackend::Delegate {
public:
MockDelegate() = default;
~MockDelegate() override = default;
MOCK_METHOD(void, NotifyRecoverabilityDegradedChanged, (), (override));
};
class MockTrustedVaultConnection : public TrustedVaultConnection {
public:
MockTrustedVaultConnection() = default;
~MockTrustedVaultConnection() override = default;
MOCK_METHOD(std::unique_ptr<Request>,
RegisterAuthenticationFactor,
(const CoreAccountInfo& account_info,
const std::vector<std::vector<uint8_t>>& trusted_vault_keys,
int last_trusted_vault_key_version,
const SecureBoxPublicKey& authentication_factor_public_key,
AuthenticationFactorType authentication_factor_type,
absl::optional<int> authentication_factor_type_hint,
RegisterAuthenticationFactorCallback callback),
(override));
MOCK_METHOD(std::unique_ptr<Request>,
RegisterDeviceWithoutKeys,
(const CoreAccountInfo& account_info,
const SecureBoxPublicKey& device_public_key,
RegisterDeviceWithoutKeysCallback callback),
(override));
MOCK_METHOD(
std::unique_ptr<Request>,
DownloadNewKeys,
(const CoreAccountInfo& account_info,
const TrustedVaultKeyAndVersion& last_trusted_vault_key_and_version,
std::unique_ptr<SecureBoxKeyPair> device_key_pair,
DownloadNewKeysCallback callback),
(override));
MOCK_METHOD(std::unique_ptr<Request>,
RetrieveIsRecoverabilityDegraded,
(const CoreAccountInfo& account_info,
IsRecoverabilityDegradedCallback),
(override));
};
class StandaloneTrustedVaultBackendTest : public testing::Test {
public:
StandaloneTrustedVaultBackendTest()
: file_path_(
CreateUniqueTempDir(&temp_dir_)
.Append(base::FilePath(FILE_PATH_LITERAL("some_file")))) {
auto delegate = std::make_unique<testing::NiceMock<MockDelegate>>();
delegate_ = delegate.get();
auto connection =
std::make_unique<testing::NiceMock<MockTrustedVaultConnection>>();
connection_ = connection.get();
backend_ = base::MakeRefCounted<StandaloneTrustedVaultBackend>(
file_path_, std::move(delegate), std::move(connection));
backend_->SetClockForTesting(&clock_);
clock_.SetNow(base::Time::Now());
// To avoid DCHECK failures in tests that exercise SetPrimaryAccount(),
// return non-null for RegisterAuthenticationFactor(). This registration
// operation will never complete, though.
ON_CALL(*connection_, RegisterAuthenticationFactor(_, _, _, _, _, _, _))
.WillByDefault(testing::InvokeWithoutArgs([&]() {
return std::make_unique<TrustedVaultConnection::Request>();
}));
ON_CALL(*connection_, RegisterDeviceWithoutKeys(_, _, _))
.WillByDefault(testing::InvokeWithoutArgs([&]() {
return std::make_unique<TrustedVaultConnection::Request>();
}));
}
~StandaloneTrustedVaultBackendTest() override = default;
void SetUp() override { OSCryptMocker::SetUp(); }
void TearDown() override { OSCryptMocker::TearDown(); }
MockTrustedVaultConnection* connection() { return connection_; }
base::SimpleTestClock* clock() { return &clock_; }
StandaloneTrustedVaultBackend* backend() { return backend_.get(); }
const base::FilePath& file_path() { return file_path_; }
// Stores |vault_keys| and mimics successful device registration, returns
// private device key material.
std::vector<uint8_t> StoreKeysAndMimicDeviceRegistration(
const std::vector<std::vector<uint8_t>>& vault_keys,
int last_vault_key_version,
CoreAccountInfo account_info) {
DCHECK(!vault_keys.empty());
backend_->StoreKeys(account_info.gaia, vault_keys, last_vault_key_version);
TrustedVaultConnection::RegisterAuthenticationFactorCallback
device_registration_callback;
EXPECT_CALL(*connection_,
RegisterAuthenticationFactor(
Eq(account_info), Eq(vault_keys), last_vault_key_version, _,
AuthenticationFactorType::kPhysicalDevice,
/*authentication_factor_type_hint=*/Eq(absl::nullopt), _))
.WillOnce(
[&](const CoreAccountInfo&,
const std::vector<std::vector<uint8_t>>& vault_keys,
int last_vault_key_version,
const SecureBoxPublicKey& device_public_key,
AuthenticationFactorType, absl::optional<int>,
TrustedVaultConnection::RegisterAuthenticationFactorCallback
callback) {
device_registration_callback = std::move(callback);
// Note: TrustedVaultConnection::Request doesn't support
// cancellation, so these tests don't cover the contract that
// caller should store Request object until it's completed or need
// to be cancelled.
return std::make_unique<TrustedVaultConnection::Request>();
});
// Setting the primary account will trigger device registration.
backend()->SetPrimaryAccount(account_info);
EXPECT_FALSE(device_registration_callback.is_null());
// Pretend that the registration completed successfully.
std::move(device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kSuccess);
// Reset primary account.
backend()->SetPrimaryAccount(absl::nullopt);
std::string device_private_key_material =
backend_->GetDeviceRegistrationInfoForTesting(account_info.gaia)
.private_key_material();
return std::vector<uint8_t>(device_private_key_material.begin(),
device_private_key_material.end());
}
private:
base::ScopedTempDir temp_dir_;
const base::FilePath file_path_;
testing::NiceMock<MockDelegate>* delegate_;
testing::NiceMock<MockTrustedVaultConnection>* connection_;
base::SimpleTestClock clock_;
scoped_refptr<StandaloneTrustedVaultBackend> backend_;
};
TEST_F(StandaloneTrustedVaultBackendTest, ShouldFetchEmptyKeys) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
// Callback should be called immediately.
base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback>
fetch_keys_callback;
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/IsEmpty()));
backend()->FetchKeys(account_info, fetch_keys_callback.Get());
}
TEST_F(StandaloneTrustedVaultBackendTest, ShouldReadAndFetchNonEmptyKeys) {
const CoreAccountInfo account_info_1 = MakeAccountInfoWithGaiaId("user1");
const CoreAccountInfo account_info_2 = MakeAccountInfoWithGaiaId("user2");
const std::vector<uint8_t> kKey1 = {0, 1, 2, 3, 4};
const std::vector<uint8_t> kKey2 = {1, 2, 3, 4};
const std::vector<uint8_t> kKey3 = {2, 3, 4};
sync_pb::LocalTrustedVault initial_data;
sync_pb::LocalTrustedVaultPerUser* user_data1 = initial_data.add_user();
sync_pb::LocalTrustedVaultPerUser* user_data2 = initial_data.add_user();
user_data1->set_gaia_id(account_info_1.gaia);
user_data2->set_gaia_id(account_info_2.gaia);
user_data1->add_vault_key()->set_key_material(kKey1.data(), kKey1.size());
user_data2->add_vault_key()->set_key_material(kKey2.data(), kKey2.size());
user_data2->add_vault_key()->set_key_material(kKey3.data(), kKey3.size());
std::string encrypted_data;
ASSERT_TRUE(OSCrypt::EncryptString(initial_data.SerializeAsString(),
&encrypted_data));
ASSERT_NE(-1, base::WriteFile(file_path(), encrypted_data.c_str(),
encrypted_data.size()));
backend()->ReadDataFromDisk();
// Keys should be fetched immediately for both accounts.
base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback>
fetch_keys_callback;
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/ElementsAre(kKey1)));
backend()->FetchKeys(account_info_1, fetch_keys_callback.Get());
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/ElementsAre(kKey2, kKey3)));
backend()->FetchKeys(account_info_2, fetch_keys_callback.Get());
}
TEST_F(StandaloneTrustedVaultBackendTest, ShouldFilterOutConstantKey) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user1");
const std::vector<uint8_t> kKey = {1, 2, 3, 4};
sync_pb::LocalTrustedVault initial_data;
sync_pb::LocalTrustedVaultPerUser* user_data = initial_data.add_user();
user_data->set_gaia_id(account_info.gaia);
user_data->add_vault_key()->set_key_material(
GetConstantTrustedVaultKey().data(), GetConstantTrustedVaultKey().size());
user_data->add_vault_key()->set_key_material(kKey.data(), kKey.size());
std::string encrypted_data;
ASSERT_TRUE(OSCrypt::EncryptString(initial_data.SerializeAsString(),
&encrypted_data));
ASSERT_NE(-1, base::WriteFile(file_path(), encrypted_data.c_str(),
encrypted_data.size()));
backend()->ReadDataFromDisk();
// Keys should be fetched immediately, constant key must be filtered out.
base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback>
fetch_keys_callback;
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/ElementsAre(kKey)));
backend()->FetchKeys(account_info, fetch_keys_callback.Get());
}
TEST_F(StandaloneTrustedVaultBackendTest, ShouldStoreKeys) {
const std::string kGaiaId1 = "user1";
const std::string kGaiaId2 = "user2";
const std::vector<uint8_t> kKey1 = {0, 1, 2, 3, 4};
const std::vector<uint8_t> kKey2 = {1, 2, 3, 4};
const std::vector<uint8_t> kKey3 = {2, 3, 4};
const std::vector<uint8_t> kKey4 = {3, 4};
backend()->StoreKeys(kGaiaId1, {kKey1}, /*last_key_version=*/7);
backend()->StoreKeys(kGaiaId2, {kKey2}, /*last_key_version=*/8);
// Keys for |kGaiaId2| overridden, so |kKey2| should be lost.
backend()->StoreKeys(kGaiaId2, {kKey3, kKey4}, /*last_key_version=*/9);
// Read the file from disk.
std::string ciphertext;
std::string decrypted_content;
sync_pb::LocalTrustedVault proto;
EXPECT_TRUE(base::ReadFileToString(file_path(), &ciphertext));
EXPECT_THAT(ciphertext, Ne(""));
EXPECT_TRUE(OSCrypt::DecryptString(ciphertext, &decrypted_content));
EXPECT_TRUE(proto.ParseFromString(decrypted_content));
ASSERT_THAT(proto.user_size(), Eq(2));
EXPECT_THAT(proto.user(0).vault_key(), ElementsAre(KeyMaterialEq(kKey1)));
EXPECT_THAT(proto.user(0).last_vault_key_version(), Eq(7));
EXPECT_THAT(proto.user(1).vault_key(),
ElementsAre(KeyMaterialEq(kKey3), KeyMaterialEq(kKey4)));
EXPECT_THAT(proto.user(1).last_vault_key_version(), Eq(9));
}
TEST_F(StandaloneTrustedVaultBackendTest, ShouldFetchPreviouslyStoredKeys) {
const CoreAccountInfo account_info_1 = MakeAccountInfoWithGaiaId("user1");
const CoreAccountInfo account_info_2 = MakeAccountInfoWithGaiaId("user2");
const std::vector<uint8_t> kKey1 = {0, 1, 2, 3, 4};
const std::vector<uint8_t> kKey2 = {1, 2, 3, 4};
const std::vector<uint8_t> kKey3 = {2, 3, 4};
backend()->StoreKeys(account_info_1.gaia, {kKey1}, /*last_key_version=*/0);
backend()->StoreKeys(account_info_2.gaia, {kKey2, kKey3},
/*last_key_version=*/1);
// Instantiate a second backend to read the file.
auto other_backend = base::MakeRefCounted<StandaloneTrustedVaultBackend>(
file_path(), std::make_unique<testing::NiceMock<MockDelegate>>(),
std::make_unique<testing::NiceMock<MockTrustedVaultConnection>>());
other_backend->ReadDataFromDisk();
// Keys should be fetched immediately for both accounts.
base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback>
fetch_keys_callback;
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/ElementsAre(kKey1)));
other_backend->FetchKeys(account_info_1, fetch_keys_callback.Get());
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/ElementsAre(kKey2, kKey3)));
other_backend->FetchKeys(account_info_2, fetch_keys_callback.Get());
}
TEST_F(StandaloneTrustedVaultBackendTest, ShouldDeleteNonPrimaryAccountKeys) {
const CoreAccountInfo account_info_1 = MakeAccountInfoWithGaiaId("user1");
const CoreAccountInfo account_info_2 = MakeAccountInfoWithGaiaId("user2");
const std::vector<uint8_t> kKey1 = {0, 1, 2, 3, 4};
const std::vector<uint8_t> kKey2 = {1, 2, 3, 4};
const std::vector<uint8_t> kKey3 = {2, 3, 4};
backend()->StoreKeys(account_info_1.gaia, {kKey1}, /*last_key_version=*/0);
backend()->StoreKeys(account_info_2.gaia, {kKey2, kKey3},
/*last_key_version=*/1);
// Make sure that backend handles primary account changes prior
// UpdateAccountsInCookieJarInfo() call.
backend()->SetPrimaryAccount(account_info_1);
backend()->SetPrimaryAccount(absl::nullopt);
// Keys should be removed immediately if account is not primary and not in
// cookie jar.
backend()->UpdateAccountsInCookieJarInfo(signin::AccountsInCookieJarInfo());
// Keys should be removed from both in-memory and disk storages.
base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback>
fetch_keys_callback;
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/IsEmpty()));
backend()->FetchKeys(account_info_1, fetch_keys_callback.Get());
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/IsEmpty()));
backend()->FetchKeys(account_info_2, fetch_keys_callback.Get());
// Read the file from disk and verify that keys were removed from disk
// storage.
sync_pb::LocalTrustedVault proto = ReadLocalTrustedVaultFile(file_path());
EXPECT_THAT(proto.user_size(), Eq(0));
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldDeferPrimaryAccountKeysDeletion) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user1");
const std::vector<uint8_t> kKey = {0, 1, 2, 3, 4};
backend()->StoreKeys(account_info.gaia, {kKey}, /*last_key_version=*/0);
backend()->SetPrimaryAccount(account_info);
// Keys should not be removed immediately.
backend()->UpdateAccountsInCookieJarInfo(signin::AccountsInCookieJarInfo());
base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback>
fetch_keys_callback;
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/ElementsAre(kKey)));
backend()->FetchKeys(account_info, fetch_keys_callback.Get());
// Reset primary account, keys should be deleted from both in-memory and disk
// storage.
backend()->SetPrimaryAccount(absl::nullopt);
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/IsEmpty()));
backend()->FetchKeys(account_info, fetch_keys_callback.Get());
// Read the file from disk and verify that keys were removed from disk
// storage.
sync_pb::LocalTrustedVault proto = ReadLocalTrustedVaultFile(file_path());
EXPECT_THAT(proto.user_size(), Eq(0));
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldCompletePrimaryAccountKeysDeletionAfterRestart) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user1");
const std::vector<uint8_t> kKey = {0, 1, 2, 3, 4};
backend()->StoreKeys(account_info.gaia, {kKey}, /*last_key_version=*/0);
backend()->SetPrimaryAccount(account_info);
// Keys should not be removed immediately.
backend()->UpdateAccountsInCookieJarInfo(signin::AccountsInCookieJarInfo());
base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback>
fetch_keys_callback;
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/ElementsAre(kKey)));
backend()->FetchKeys(account_info, fetch_keys_callback.Get());
// Mimic browser restart and reset primary account.
auto new_backend = base::MakeRefCounted<StandaloneTrustedVaultBackend>(
file_path(),
/*delegate=*/std::make_unique<testing::NiceMock<MockDelegate>>(),
/*connection=*/nullptr);
new_backend->ReadDataFromDisk();
new_backend->SetPrimaryAccount(absl::nullopt);
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/IsEmpty()));
new_backend->FetchKeys(account_info, fetch_keys_callback.Get());
// Read the file from disk and verify that keys were removed from disk
// storage.
sync_pb::LocalTrustedVault proto = ReadLocalTrustedVaultFile(file_path());
EXPECT_THAT(proto.user_size(), Eq(0));
}
TEST_F(StandaloneTrustedVaultBackendTest, ShouldRegisterDevice) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const std::vector<uint8_t> kVaultKey = {1, 2, 3};
const int kLastKeyVersion = 1;
backend()->StoreKeys(account_info.gaia, {kVaultKey}, kLastKeyVersion);
TrustedVaultConnection::RegisterAuthenticationFactorCallback
device_registration_callback;
std::vector<uint8_t> serialized_public_device_key;
EXPECT_CALL(*connection(),
RegisterAuthenticationFactor(
Eq(account_info), ElementsAre(kVaultKey), kLastKeyVersion, _,
AuthenticationFactorType::kPhysicalDevice,
/*authentication_factor_type_hint=*/Eq(absl::nullopt), _))
.WillOnce([&](const CoreAccountInfo&,
const std::vector<std::vector<uint8_t>>&, int,
const SecureBoxPublicKey& device_public_key,
AuthenticationFactorType, absl::optional<int>,
TrustedVaultConnection::RegisterAuthenticationFactorCallback
callback) {
serialized_public_device_key = device_public_key.ExportToBytes();
device_registration_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
// Setting the primary account will trigger device registration.
backend()->SetPrimaryAccount(account_info);
ASSERT_FALSE(device_registration_callback.is_null());
// Pretend that the registration completed successfully.
std::move(device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kSuccess);
// Now the device should be registered.
sync_pb::LocalDeviceRegistrationInfo registration_info =
backend()->GetDeviceRegistrationInfoForTesting(account_info.gaia);
EXPECT_TRUE(registration_info.device_registered());
EXPECT_TRUE(registration_info.has_private_key_material());
std::unique_ptr<SecureBoxKeyPair> key_pair =
SecureBoxKeyPair::CreateByPrivateKeyImport(base::as_bytes(
base::make_span(registration_info.private_key_material())));
EXPECT_THAT(key_pair->public_key().ExportToBytes(),
Eq(serialized_public_device_key));
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldThrottleAndUnthrottleDeviceRegistration) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const std::vector<uint8_t> kVaultKey = {1, 2, 3};
const int kLastKeyVersion = 1;
backend()->StoreKeys(account_info.gaia, {kVaultKey}, kLastKeyVersion);
TrustedVaultConnection::RegisterAuthenticationFactorCallback
device_registration_callback;
ON_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _, _, _, _))
.WillByDefault(
[&](const CoreAccountInfo&, const std::vector<std::vector<uint8_t>>&,
int, const SecureBoxPublicKey&, AuthenticationFactorType,
absl::optional<int>,
TrustedVaultConnection::RegisterAuthenticationFactorCallback
callback) {
device_registration_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
clock()->SetNow(base::Time::Now());
EXPECT_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _, _, _, _));
// Setting the primary account will trigger device registration.
backend()->SetPrimaryAccount(account_info);
ASSERT_FALSE(device_registration_callback.is_null());
Mock::VerifyAndClearExpectations(connection());
// Mimic transient failure.
std::move(device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kOtherError);
// Following request should be throttled.
device_registration_callback =
TrustedVaultConnection::RegisterAuthenticationFactorCallback();
EXPECT_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _, _, _, _))
.Times(0);
// Reset and set primary account to trigger device registration attempt.
backend()->SetPrimaryAccount(absl::nullopt);
backend()->SetPrimaryAccount(account_info);
EXPECT_TRUE(device_registration_callback.is_null());
Mock::VerifyAndClearExpectations(connection());
// Advance time to pass the throttling duration and trigger another attempt.
clock()->Advance(switches::kTrustedVaultServiceThrottlingDuration.Get());
EXPECT_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _, _, _, _));
// Reset and set primary account to trigger device registration attempt.
backend()->SetPrimaryAccount(absl::nullopt);
backend()->SetPrimaryAccount(account_info);
EXPECT_FALSE(device_registration_callback.is_null());
}
// System time can be changed to the past and if this situation not handled,
// requests could be throttled for unreasonable amount of time.
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldUnthrottleDeviceRegistrationWhenTimeSetToPast) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const std::vector<uint8_t> kVaultKey = {1, 2, 3};
const int kLastKeyVersion = 1;
backend()->StoreKeys(account_info.gaia, {kVaultKey}, kLastKeyVersion);
TrustedVaultConnection::RegisterAuthenticationFactorCallback
device_registration_callback;
ON_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _, _, _, _))
.WillByDefault(
[&](const CoreAccountInfo&, const std::vector<std::vector<uint8_t>>&,
int, const SecureBoxPublicKey&, AuthenticationFactorType,
absl::optional<int>,
TrustedVaultConnection::RegisterAuthenticationFactorCallback
callback) {
device_registration_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
clock()->SetNow(base::Time::Now());
EXPECT_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _, _, _, _));
// Setting the primary account will trigger device registration.
backend()->SetPrimaryAccount(account_info);
ASSERT_FALSE(device_registration_callback.is_null());
Mock::VerifyAndClearExpectations(connection());
// Mimic transient failure.
std::move(device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kOtherError);
// Mimic system set to the past.
clock()->Advance(base::TimeDelta::FromSeconds(-1));
device_registration_callback =
TrustedVaultConnection::RegisterAuthenticationFactorCallback();
EXPECT_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _, _, _, _));
// Reset and set primary account to trigger device registration attempt.
backend()->SetPrimaryAccount(absl::nullopt);
backend()->SetPrimaryAccount(account_info);
EXPECT_FALSE(device_registration_callback.is_null());
}
// Unless keys marked as stale, FetchKeys() should be completed immediately,
// without keys download attempt.
TEST_F(StandaloneTrustedVaultBackendTest, ShouldFetchKeysImmediately) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const std::vector<std::vector<uint8_t>> kVaultKeys = {{1, 2, 3}};
const int kLastKeyVersion = 1;
// Make keys downloading theoretically possible.
StoreKeysAndMimicDeviceRegistration(kVaultKeys, kLastKeyVersion,
account_info);
backend()->SetPrimaryAccount(account_info);
EXPECT_CALL(*connection(), DownloadNewKeys).Times(0);
std::vector<std::vector<uint8_t>> fetched_keys;
// Callback should be called immediately.
base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback>
fetch_keys_callback;
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/Eq(kVaultKeys)));
backend()->FetchKeys(account_info, fetch_keys_callback.Get());
}
TEST_F(StandaloneTrustedVaultBackendTest, ShouldDownloadNewKeys) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const std::vector<uint8_t> kInitialVaultKey = {1, 2, 3};
const int kInitialLastKeyVersion = 1;
std::vector<uint8_t> private_device_key_material =
StoreKeysAndMimicDeviceRegistration({kInitialVaultKey},
kInitialLastKeyVersion, account_info);
EXPECT_TRUE(backend()->MarkLocalKeysAsStale(account_info));
backend()->SetPrimaryAccount(account_info);
const std::vector<std::vector<uint8_t>> kNewVaultKeys = {kInitialVaultKey,
{1, 3, 2}};
const int kNewLastKeyVersion = 2;
std::unique_ptr<SecureBoxKeyPair> device_key_pair;
TrustedVaultConnection::DownloadNewKeysCallback download_keys_callback;
EXPECT_CALL(*connection(),
DownloadNewKeys(Eq(account_info),
TrustedVaultKeyAndVersionEq(
kInitialVaultKey, kInitialLastKeyVersion),
_, _))
.WillOnce([&](const CoreAccountInfo&, const TrustedVaultKeyAndVersion&,
std::unique_ptr<SecureBoxKeyPair> key_pair,
TrustedVaultConnection::DownloadNewKeysCallback callback) {
device_key_pair = std::move(key_pair);
download_keys_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
// FetchKeys() should trigger keys downloading.
base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback>
fetch_keys_callback;
backend()->FetchKeys(account_info, fetch_keys_callback.Get());
ASSERT_FALSE(download_keys_callback.is_null());
// Ensure that the right device key was passed into DownloadNewKeys().
ASSERT_THAT(device_key_pair, NotNull());
EXPECT_THAT(device_key_pair->private_key().ExportToBytes(),
Eq(private_device_key_material));
// Mimic successful key downloading, it should make fetch keys attempt
// completed.
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/Eq(kNewVaultKeys)));
std::move(download_keys_callback)
.Run(TrustedVaultDownloadKeysStatus::kSuccess, kNewVaultKeys,
kNewLastKeyVersion);
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldThrottleAndUntrottleKeysDownloading) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const std::vector<uint8_t> kInitialVaultKey = {1, 2, 3};
const int kInitialLastKeyVersion = 1;
std::vector<uint8_t> private_device_key_material =
StoreKeysAndMimicDeviceRegistration({kInitialVaultKey},
kInitialLastKeyVersion, account_info);
EXPECT_TRUE(backend()->MarkLocalKeysAsStale(account_info));
backend()->SetPrimaryAccount(account_info);
TrustedVaultConnection::DownloadNewKeysCallback download_keys_callback;
ON_CALL(*connection(), DownloadNewKeys(_, _, _, _))
.WillByDefault(
[&](const CoreAccountInfo&,
const absl::optional<TrustedVaultKeyAndVersion>&,
std::unique_ptr<SecureBoxKeyPair> key_pair,
TrustedVaultConnection::DownloadNewKeysCallback callback) {
download_keys_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
clock()->SetNow(base::Time::Now());
EXPECT_CALL(*connection(), DownloadNewKeys(_, _, _, _));
// FetchKeys() should trigger keys downloading.
backend()->FetchKeys(account_info, /*callback=*/base::DoNothing());
ASSERT_FALSE(download_keys_callback.is_null());
Mock::VerifyAndClearExpectations(connection());
// Mimic transient failure.
std::move(download_keys_callback)
.Run(TrustedVaultDownloadKeysStatus::kOtherError,
/*keys=*/std::vector<std::vector<uint8_t>>(),
/*last_key_version=*/0);
download_keys_callback = TrustedVaultConnection::DownloadNewKeysCallback();
EXPECT_CALL(*connection(), DownloadNewKeys(_, _, _, _)).Times(0);
// Following request should be throttled.
backend()->FetchKeys(account_info, /*callback=*/base::DoNothing());
EXPECT_TRUE(download_keys_callback.is_null());
Mock::VerifyAndClearExpectations(connection());
// Advance time to pass the throttling duration and trigger another attempt.
clock()->Advance(switches::kTrustedVaultServiceThrottlingDuration.Get());
EXPECT_CALL(*connection(), DownloadNewKeys(_, _, _, _));
backend()->FetchKeys(account_info, /*callback=*/base::DoNothing());
EXPECT_FALSE(download_keys_callback.is_null());
}
// Tests silent device registration (when no vault keys available yet). After
// successful registration, the client should be able to download keys.
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldSilentlyRegisterDeviceAndDownloadNewKeys) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const int kServerConstantKeyVersion = 100;
TrustedVaultConnection::RegisterDeviceWithoutKeysCallback
device_registration_callback;
EXPECT_CALL(*connection(), RegisterDeviceWithoutKeys(account_info, _, _))
.WillOnce([&](const CoreAccountInfo&, const SecureBoxPublicKey&,
TrustedVaultConnection::RegisterDeviceWithoutKeysCallback
callback) {
device_registration_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
// Setting the primary account will trigger device registration.
backend()->SetPrimaryAccount(account_info);
ASSERT_FALSE(device_registration_callback.is_null());
// Pretend that the registration completed successfully.
std::move(device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kSuccess,
TrustedVaultKeyAndVersion{GetConstantTrustedVaultKey(),
kServerConstantKeyVersion});
// Now the device should be registered.
sync_pb::LocalDeviceRegistrationInfo registration_info =
backend()->GetDeviceRegistrationInfoForTesting(account_info.gaia);
EXPECT_TRUE(registration_info.device_registered());
EXPECT_TRUE(registration_info.has_private_key_material());
TrustedVaultConnection::DownloadNewKeysCallback download_keys_callback;
ON_CALL(*connection(), DownloadNewKeys(account_info,
TrustedVaultKeyAndVersionEq(
GetConstantTrustedVaultKey(),
kServerConstantKeyVersion),
_, _))
.WillByDefault(
[&](const CoreAccountInfo&, const TrustedVaultKeyAndVersion&,
std::unique_ptr<SecureBoxKeyPair> key_pair,
TrustedVaultConnection::DownloadNewKeysCallback callback) {
download_keys_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
// FetchKeys() should trigger keys downloading. Note: unlike tests with
// following regular key rotation, in this case MarkLocalKeysAsStale() isn't
// called intentionally.
base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback>
fetch_keys_callback;
backend()->FetchKeys(account_info, fetch_keys_callback.Get());
ASSERT_FALSE(download_keys_callback.is_null());
// Mimic successful key downloading, it should make fetch keys attempt
// completed.
const std::vector<std::vector<uint8_t>> kNewVaultKeys = {{1, 2, 3}};
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/kNewVaultKeys));
std::move(download_keys_callback)
.Run(TrustedVaultDownloadKeysStatus::kSuccess, kNewVaultKeys,
/*last_key_version=*/kServerConstantKeyVersion + 1);
}
TEST_F(StandaloneTrustedVaultBackendTest, ShouldAddTrustedRecoveryMethod) {
const std::vector<std::vector<uint8_t>> kVaultKeys = {{1, 2}, {1, 2, 3}};
const int kLastKeyVersion = 1;
const std::vector<uint8_t> kPublicKey =
SecureBoxKeyPair::GenerateRandom()->public_key().ExportToBytes();
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const int kMethodTypeHint = 7;
backend()->SetPrimaryAccount(account_info);
backend()->StoreKeys(account_info.gaia, kVaultKeys, kLastKeyVersion);
TrustedVaultConnection::RegisterAuthenticationFactorCallback
registration_callback;
EXPECT_CALL(
*connection(),
RegisterAuthenticationFactor(
Eq(account_info), Eq(kVaultKeys), kLastKeyVersion,
PublicKeyWhenExportedEq(kPublicKey),
AuthenticationFactorType::kUnspecified, Eq(kMethodTypeHint), _))
.WillOnce([&](const CoreAccountInfo&,
const std::vector<std::vector<uint8_t>>&, int,
const SecureBoxPublicKey& device_public_key,
AuthenticationFactorType, absl::optional<int>,
TrustedVaultConnection::RegisterAuthenticationFactorCallback
callback) {
registration_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
base::MockCallback<base::OnceClosure> completion_callback;
backend()->AddTrustedRecoveryMethod(account_info.gaia, kPublicKey,
kMethodTypeHint,
completion_callback.Get());
// The operation should be in flight.
ASSERT_FALSE(registration_callback.is_null());
// Mimic successful completion of the request.
EXPECT_CALL(completion_callback, Run());
std::move(registration_callback)
.Run(TrustedVaultRegistrationStatus::kSuccess);
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldIgnoreTrustedRecoveryMethodWithInvalidPublicKey) {
const std::vector<std::vector<uint8_t>> kVaultKeys = {{1, 2, 3}};
const int kLastKeyVersion = 0;
const std::vector<uint8_t> kInvalidPublicKey = {1, 2, 3, 4};
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const int kMethodTypeHint = 7;
ASSERT_THAT(SecureBoxPublicKey::CreateByImport(kInvalidPublicKey), IsNull());
backend()->SetPrimaryAccount(account_info);
backend()->StoreKeys(account_info.gaia, kVaultKeys, kLastKeyVersion);
EXPECT_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _, _, _, _))
.Times(0);
base::MockCallback<base::OnceClosure> completion_callback;
EXPECT_CALL(completion_callback, Run());
backend()->AddTrustedRecoveryMethod(account_info.gaia, kInvalidPublicKey,
kMethodTypeHint,
completion_callback.Get());
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldDeferTrustedRecoveryMethodUntilPrimaryAccount) {
const std::vector<std::vector<uint8_t>> kVaultKeys = {{1, 2, 3}};
const int kLastKeyVersion = 1;
const std::vector<uint8_t> kPublicKey =
SecureBoxKeyPair::GenerateRandom()->public_key().ExportToBytes();
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const int kMethodTypeHint = 7;
backend()->StoreKeys(account_info.gaia, kVaultKeys, kLastKeyVersion);
// No request should be issued while there is no primary account.
base::MockCallback<base::OnceClosure> completion_callback;
EXPECT_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _, _, _, _))
.Times(0);
backend()->AddTrustedRecoveryMethod(account_info.gaia, kPublicKey,
kMethodTypeHint,
completion_callback.Get());
// Upon setting a primary account, RegisterAuthenticationFactor() should be
// invoked. It should in fact be called twice: one for device registration,
// and one for the AddTrustedRecoveryMethod() call being tested here.
TrustedVaultConnection::RegisterAuthenticationFactorCallback
registration_callback;
EXPECT_CALL(*connection(),
RegisterAuthenticationFactor(
_, _, _, _, AuthenticationFactorType::kPhysicalDevice, _, _));
EXPECT_CALL(
*connection(),
RegisterAuthenticationFactor(
Eq(account_info), Eq(kVaultKeys), kLastKeyVersion,
PublicKeyWhenExportedEq(kPublicKey),
AuthenticationFactorType::kUnspecified, Eq(kMethodTypeHint), _))
.WillOnce([&](const CoreAccountInfo&,
const std::vector<std::vector<uint8_t>>&, int,
const SecureBoxPublicKey& device_public_key,
AuthenticationFactorType, absl::optional<int>,
TrustedVaultConnection::RegisterAuthenticationFactorCallback
callback) {
registration_callback = std::move(callback);
// Note: TrustedVaultConnection::Request doesn't support
// cancellation, so these tests don't cover the contract that
// caller should store Request object until it's completed or need
// to be cancelled.
return std::make_unique<TrustedVaultConnection::Request>();
});
backend()->SetPrimaryAccount(account_info);
// The operation should be in flight.
ASSERT_FALSE(registration_callback.is_null());
// Mimic successful completion of the request.
EXPECT_CALL(completion_callback, Run());
std::move(registration_callback)
.Run(TrustedVaultRegistrationStatus::kSuccess);
}
} // namespace
} // namespace syncer