blob: 032a0a24fe1013e801f580013caa0b4178086613 [file] [log] [blame]
// Copyright 2020 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/trusted_vault/standalone_trusted_vault_backend.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
#include "components/trusted_vault/features.h"
#include "components/trusted_vault/proto/local_trusted_vault.pb.h"
#include "components/trusted_vault/proto_string_bytes_conversion.h"
#include "components/trusted_vault/securebox.h"
#include "components/trusted_vault/standalone_trusted_vault_storage.h"
#include "components/trusted_vault/test/mock_trusted_vault_connection.h"
#include "components/trusted_vault/trusted_vault_connection.h"
#include "components/trusted_vault/trusted_vault_histograms.h"
#include "components/trusted_vault/trusted_vault_server_constants.h"
#include "google_apis/gaia/gaia_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace trusted_vault {
namespace {
using testing::_;
using testing::ByMove;
using testing::ElementsAre;
using testing::Eq;
using testing::Ge;
using testing::IsEmpty;
using testing::IsNull;
using testing::Mock;
using testing::Ne;
using testing::NotNull;
using testing::Return;
using testing::SaveArg;
MATCHER_P(MatchTrustedVaultKeyAndVersions, expected, "") {
const auto* trusted_vault_keys =
std::get_if<std::vector<TrustedVaultKeyAndVersion>>(&arg);
if (!trusted_vault_keys) {
*result_listener << "does not hold a vector of TrustedVaultKeyAndVersion";
return false;
}
return testing::ExplainMatchResult(*trusted_vault_keys, expected,
result_listener);
}
MATCHER_P(DegradedRecoverabilityStateEq, expected_state, "") {
const trusted_vault_pb::LocalTrustedVaultDegradedRecoverabilityState&
given_state = arg;
return given_state.degraded_recoverability_value() ==
expected_state.degraded_recoverability_value() &&
given_state.last_refresh_time_millis_since_unix_epoch() ==
expected_state.last_refresh_time_millis_since_unix_epoch();
}
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;
}
CoreAccountInfo MakeAccountInfoWithGaiaId(const std::string& gaia_id) {
CoreAccountInfo account_info;
account_info.gaia = GaiaId(gaia_id);
return account_info;
}
class MockDelegate : public StandaloneTrustedVaultBackend::Delegate {
public:
MockDelegate() = default;
~MockDelegate() override = default;
MOCK_METHOD(void, NotifyRecoverabilityDegradedChanged, (), (override));
MOCK_METHOD(void, NotifyStateChanged, (), (override));
};
class FakeFileAccess : public StandaloneTrustedVaultStorage::FileAccess {
public:
FakeFileAccess() = default;
FakeFileAccess(const FakeFileAccess& other) = delete;
FakeFileAccess& operator=(const FakeFileAccess& other) = delete;
~FakeFileAccess() override = default;
trusted_vault_pb::LocalTrustedVault ReadFromDisk() override {
return stored_data_;
}
void WriteToDisk(const trusted_vault_pb::LocalTrustedVault& data) override {
stored_data_ = data;
}
void SetStoredLocalTrustedVault(
const trusted_vault_pb::LocalTrustedVault& local_trusted_vault) {
stored_data_ = local_trusted_vault;
}
trusted_vault_pb::LocalTrustedVault GetStoredLocalTrustedVault() const {
return stored_data_;
}
private:
trusted_vault_pb::LocalTrustedVault stored_data_;
};
class StandaloneTrustedVaultBackendTest : public testing::Test {
public:
StandaloneTrustedVaultBackendTest() {
clock_.SetNow(base::Time::Now());
ResetBackend();
}
~StandaloneTrustedVaultBackendTest() override = default;
void ResetBackend() {
auto connection =
std::make_unique<testing::NiceMock<MockTrustedVaultConnection>>();
// 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, RegisterLocalDeviceWithoutKeys)
.WillByDefault(testing::InvokeWithoutArgs([&]() {
return std::make_unique<TrustedVaultConnection::Request>();
}));
ResetBackend(std::move(connection));
}
void ResetBackend(
std::unique_ptr<testing::NiceMock<MockTrustedVaultConnection>>
connection) {
auto file_access = std::make_unique<FakeFileAccess>();
if (file_access_) {
// We only want to reset the backend, not the underlying faked file.
file_access->SetStoredLocalTrustedVault(
file_access_->GetStoredLocalTrustedVault());
}
file_access_ = file_access.get();
auto delegate = std::make_unique<testing::NiceMock<MockDelegate>>();
connection_ = connection.get();
backend_ = base::MakeRefCounted<StandaloneTrustedVaultBackend>(
security_domain_id(),
StandaloneTrustedVaultStorage::CreateForTesting(std::move(file_access)),
std::move(delegate), std::move(connection));
backend_->SetClockForTesting(&clock_);
backend_->ReadDataFromDisk();
}
FakeFileAccess* file_access() { return file_access_; }
MockTrustedVaultConnection* connection() { return connection_; }
base::SimpleTestClock* clock() { return &clock_; }
StandaloneTrustedVaultBackend* backend() { return backend_.get(); }
SecurityDomainId security_domain_id() const {
return SecurityDomainId::kChromeSync;
}
std::string security_domain_name_for_uma() const {
return GetSecurityDomainNameForUma(security_domain_id());
}
void SetPrimaryAccountWithUnknownAuthError(
std::optional<CoreAccountInfo> primary_account) {
backend_->SetPrimaryAccount(
primary_account,
StandaloneTrustedVaultBackend::RefreshTokenErrorState::kUnknown);
}
// 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),
MatchTrustedVaultKeyAndVersions(GetTrustedVaultKeysWithVersions(
vault_keys, last_vault_key_version)),
_, Eq(AuthenticationFactorType(LocalPhysicalDevice())), _))
.WillOnce(
[&](const CoreAccountInfo&,
const MemberKeysSource& member_keys_source,
const SecureBoxPublicKey& device_public_key,
AuthenticationFactorType,
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.
SetPrimaryAccountWithUnknownAuthError(account_info);
Mock::VerifyAndClearExpectations(connection_);
EXPECT_FALSE(device_registration_callback.is_null());
// Pretend that the registration completed successfully.
std::move(device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kSuccess,
/*key_version=*/last_vault_key_version);
// Reset primary account.
SetPrimaryAccountWithUnknownAuthError(/*primary_account=*/std::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::SimpleTestClock clock_;
scoped_refptr<StandaloneTrustedVaultBackend> backend_;
raw_ptr<FakeFileAccess> file_access_ = nullptr;
raw_ptr<testing::NiceMock<MockTrustedVaultConnection>> connection_ = nullptr;
};
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldWriteDegradedRecoverabilityState) {
SetPrimaryAccountWithUnknownAuthError(MakeAccountInfoWithGaiaId("user"));
trusted_vault_pb::LocalTrustedVaultDegradedRecoverabilityState
degraded_recoverability_state;
degraded_recoverability_state.set_degraded_recoverability_value(
trusted_vault_pb::DegradedRecoverabilityValue::kDegraded);
degraded_recoverability_state.set_last_refresh_time_millis_since_unix_epoch(
123);
backend()->WriteDegradedRecoverabilityState(degraded_recoverability_state);
// Read the file from disk.
trusted_vault_pb::LocalTrustedVault proto =
file_access()->GetStoredLocalTrustedVault();
ASSERT_THAT(proto.user_size(), Eq(1));
EXPECT_THAT(proto.user(0).degraded_recoverability_state(),
DegradedRecoverabilityStateEq(degraded_recoverability_state));
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldInvokeGetIsRecoverabilityDegradedCallbackImmediately) {
// The TaskEnvironment is needed because this test initializes the handler,
// which works with time.
base::test::SingleThreadTaskEnvironment environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
SetPrimaryAccountWithUnknownAuthError(account_info);
EXPECT_CALL(*connection(), DownloadIsRecoverabilityDegraded)
.WillOnce([](const CoreAccountInfo&,
TrustedVaultConnection::IsRecoverabilityDegradedCallback
callback) {
std::move(callback).Run(TrustedVaultRecoverabilityStatus::kDegraded);
return std::make_unique<TrustedVaultConnection::Request>();
});
base::MockCallback<base::OnceCallback<void(bool)>> cb;
// The callback should be invoked because GetIsRecoverabilityDegraded() is
// called with the current primary account.
EXPECT_CALL(cb, Run(true));
backend()->GetIsRecoverabilityDegraded(account_info, cb.Get());
environment.FastForwardBy(base::Milliseconds(1));
}
TEST_F(
StandaloneTrustedVaultBackendTest,
ShouldDeferGetIsRecoverabilityDegradedCallbackUntilSetPrimaryAccountIsInvoked) {
// TODO(crbug.com/40255601): looks like this test verifies scenario not
// possible in prod anymore, remove it together with
// |pending_get_is_recoverability_degraded_| logic.
// The TaskEnvironment is needed because this test initializes the handler,
// which works with time.
base::test::SingleThreadTaskEnvironment environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
SetPrimaryAccountWithUnknownAuthError(MakeAccountInfoWithGaiaId("user1"));
base::MockCallback<base::OnceCallback<void(bool)>> cb;
// The callback should not be invoked because GetIsRecoverabilityDegraded()
// and SetPrimaryAccount() are invoked with different accounts.
EXPECT_CALL(cb, Run(_)).Times(0);
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user2");
// This GetIsRecoverabilityDegraded() is corresponding to a late
// SetPrimaryAccount(), in this case the callback should be deferred and
// invoked when SetPrimaryAccount() is called.
backend()->GetIsRecoverabilityDegraded(account_info, cb.Get());
Mock::VerifyAndClearExpectations(&cb);
ON_CALL(*connection(), DownloadIsRecoverabilityDegraded(Eq(account_info), _))
.WillByDefault([](const CoreAccountInfo&,
TrustedVaultConnection::IsRecoverabilityDegradedCallback
callback) {
std::move(callback).Run(TrustedVaultRecoverabilityStatus::kDegraded);
return std::make_unique<TrustedVaultConnection::Request>();
});
// The callback should be invoked on SetPrimaryAccount() since the last
// GetIsRecoverabilityDegraded() was called with the same account.
EXPECT_CALL(cb, Run(true));
SetPrimaryAccountWithUnknownAuthError(account_info);
environment.FastForwardBy(base::Milliseconds(1));
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldNotInvokeGetIsRecoverabilityDegradedCallback) {
// TODO(crbug.com/40255601): looks like this test verifies scenario not
// possible in prod anymore, remove it together with
// |pending_get_is_recoverability_degraded_| logic.
// The TaskEnvironment is needed because this test initializes the handler,
// which works with time.
base::test::SingleThreadTaskEnvironment environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
EXPECT_CALL(*connection(), DownloadIsRecoverabilityDegraded).Times(0);
base::MockCallback<base::OnceCallback<void(bool)>> cb;
// The callback should not be invoked because GetIsRecoverabilityDegraded()
// and SetPrimaryAccount() are invoked with different accounts.
EXPECT_CALL(cb, Run(_)).Times(0);
backend()->GetIsRecoverabilityDegraded(MakeAccountInfoWithGaiaId("user1"),
cb.Get());
SetPrimaryAccountWithUnknownAuthError(MakeAccountInfoWithGaiaId("user2"));
environment.FastForwardBy(base::Milliseconds(1));
}
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};
trusted_vault_pb::LocalTrustedVault initial_data;
trusted_vault_pb::LocalTrustedVaultPerUser* user_data1 =
initial_data.add_user();
trusted_vault_pb::LocalTrustedVaultPerUser* user_data2 =
initial_data.add_user();
user_data1->set_gaia_id(account_info_1.gaia.ToString());
user_data2->set_gaia_id(account_info_2.gaia.ToString());
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());
file_access()->SetStoredLocalTrustedVault(initial_data);
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};
trusted_vault_pb::LocalTrustedVault initial_data;
trusted_vault_pb::LocalTrustedVaultPerUser* user_data =
initial_data.add_user();
user_data->set_gaia_id(account_info.gaia.ToString());
user_data->add_vault_key()->set_key_material(
GetConstantTrustedVaultKey().data(), GetConstantTrustedVaultKey().size());
user_data->add_vault_key()->set_key_material(kKey.data(), kKey.size());
file_access()->SetStoredLocalTrustedVault(initial_data);
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 GaiaId kGaiaId1("user1");
const GaiaId 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 content from storage.
trusted_vault_pb::LocalTrustedVault proto =
file_access()->GetStoredLocalTrustedVault();
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);
// Reset the backend, which makes it re-read the data stored above.
ResetBackend();
// 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, 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.
SetPrimaryAccountWithUnknownAuthError(account_info_1);
SetPrimaryAccountWithUnknownAuthError(/*primary_account=*/std::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 storage and verify that keys were removed.
trusted_vault_pb::LocalTrustedVault proto =
file_access()->GetStoredLocalTrustedVault();
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);
SetPrimaryAccountWithUnknownAuthError(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.
SetPrimaryAccountWithUnknownAuthError(/*primary_account=*/std::nullopt);
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/IsEmpty()));
backend()->FetchKeys(account_info, fetch_keys_callback.Get());
// Read the file from storage and verify that keys were removed.
trusted_vault_pb::LocalTrustedVault proto =
file_access()->GetStoredLocalTrustedVault();
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);
SetPrimaryAccountWithUnknownAuthError(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. Don't use the default
// connection, otherwise FetchKeys() below would perform a device
// registration.
ResetBackend(/*connection=*/nullptr);
SetPrimaryAccountWithUnknownAuthError(/*primary_account=*/std::nullopt);
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/IsEmpty()));
backend()->SetPrimaryAccount(
account_info,
StandaloneTrustedVaultBackend::RefreshTokenErrorState::kUnknown);
backend()->FetchKeys(account_info, fetch_keys_callback.Get());
// Read the file from storage and verify that keys were removed.
trusted_vault_pb::LocalTrustedVault proto =
file_access()->GetStoredLocalTrustedVault();
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),
MatchTrustedVaultKeyAndVersions(
GetTrustedVaultKeysWithVersions({kVaultKey}, kLastKeyVersion)),
_, Eq(AuthenticationFactorType(LocalPhysicalDevice())), _))
.WillOnce([&](const CoreAccountInfo&, const MemberKeysSource&,
const SecureBoxPublicKey& device_public_key,
AuthenticationFactorType,
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.
base::HistogramTester histogram_tester;
SetPrimaryAccountWithUnknownAuthError(account_info);
ASSERT_FALSE(device_registration_callback.is_null());
histogram_tester.ExpectUniqueSample(
"TrustedVault.DeviceRegistrationState." + security_domain_name_for_uma(),
/*sample=*/
TrustedVaultDeviceRegistrationStateForUMA::
kAttemptingRegistrationWithNewKeyPair,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(
"TrustedVault.DeviceRegistered." + security_domain_name_for_uma(),
/*sample=*/false,
/*expected_bucket_count=*/1);
// Pretend that the registration completed successfully.
std::move(device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kSuccess, kLastKeyVersion);
histogram_tester.ExpectUniqueSample(
/*name=*/"TrustedVault.DeviceRegistrationOutcome." +
security_domain_name_for_uma(),
/*sample=*/TrustedVaultDeviceRegistrationOutcomeForUMA::kSuccess,
/*expected_bucket_count=*/1);
// Now the device should be registered.
trusted_vault_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_byte_span(registration_info.private_key_material()));
EXPECT_THAT(key_pair->public_key().ExportToBytes(),
Eq(serialized_public_device_key));
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldHandleLocalDataObsoleteAndPersistState) {
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;
EXPECT_CALL(
*connection(),
RegisterAuthenticationFactor(
Eq(account_info),
MatchTrustedVaultKeyAndVersions(
GetTrustedVaultKeysWithVersions({kVaultKey}, kLastKeyVersion)),
_, Eq(AuthenticationFactorType(LocalPhysicalDevice())), _))
.WillOnce([&](const CoreAccountInfo&,
const MemberKeysSource& member_keys_source,
const SecureBoxPublicKey& device_public_key,
AuthenticationFactorType,
TrustedVaultConnection::RegisterAuthenticationFactorCallback
callback) {
device_registration_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
// Setting the primary account will trigger device registration.
SetPrimaryAccountWithUnknownAuthError(account_info);
ASSERT_FALSE(device_registration_callback.is_null());
// Pretend that the registration failed with kLocalDataObsolete.
std::move(device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kLocalDataObsolete,
/*key_version=*/0);
// Verify persisted file state.
trusted_vault_pb::LocalTrustedVault proto =
file_access()->GetStoredLocalTrustedVault();
ASSERT_THAT(proto.user_size(), Eq(1));
// Ensure that the failure is remembered, so there are no retries. This is a
// regression test for crbug.com/1358015.
EXPECT_TRUE(proto.user(0)
.local_device_registration_info()
.last_registration_returned_local_data_obsolete());
// Additionally ensure that |local_device_registration_info| has correct
// state.
EXPECT_FALSE(
proto.user(0).local_device_registration_info().device_registered());
EXPECT_TRUE(proto.user(0)
.local_device_registration_info()
.has_private_key_material());
// Keys shouldn't be marked as stale: this is exclusively about upper layers
// invoking MarkLocalKeysAsStale().
EXPECT_FALSE(proto.user(0).keys_marked_as_stale_by_consumer());
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldClearDataAndAttemptDeviceRegistration) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const std::vector<std::vector<uint8_t>> kInitialVaultKeys = {{1, 2, 3}};
const int kInitialLastKeyVersion = 1;
// Mimic device previously registered with some keys.
StoreKeysAndMimicDeviceRegistration(kInitialVaultKeys, kInitialLastKeyVersion,
account_info);
// Set primary account to trigger immediate device registration attempt upon
// reset.
SetPrimaryAccountWithUnknownAuthError(account_info);
// Expect device registration attempt without keys.
TrustedVaultConnection::RegisterAuthenticationFactorCallback
device_registration_callback;
std::vector<uint8_t> serialized_public_device_key;
EXPECT_CALL(*connection(),
RegisterLocalDeviceWithoutKeys(Eq(account_info), _, _))
.WillOnce([&](const CoreAccountInfo& account_info,
const SecureBoxPublicKey& device_public_key,
TrustedVaultConnection::RegisterAuthenticationFactorCallback
callback) {
serialized_public_device_key = device_public_key.ExportToBytes();
device_registration_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
// Clear data for |account_info|, keys should be removed and device
// registration attempt should be triggered.
backend()->ClearLocalDataForAccount(account_info);
// 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());
// Mimic successful device registration and verify the state.
std::move(device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kSuccess,
kInitialLastKeyVersion + 1);
// Now the device should be registered.
trusted_vault_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_byte_span(registration_info.private_key_material()));
EXPECT_THAT(key_pair->public_key().ExportToBytes(),
Eq(serialized_public_device_key));
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldRetryDeviceRegistrationWhenAuthErrorResolved) {
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);
EXPECT_CALL(
*connection(),
RegisterAuthenticationFactor(
Eq(account_info),
MatchTrustedVaultKeyAndVersions(
GetTrustedVaultKeysWithVersions({kVaultKey}, kLastKeyVersion)),
_, Eq(AuthenticationFactorType(LocalPhysicalDevice())), _));
base::HistogramTester histogram_tester;
backend()->SetPrimaryAccount(
account_info, StandaloneTrustedVaultBackend::RefreshTokenErrorState::
kPersistentAuthError);
histogram_tester.ExpectUniqueSample(
"TrustedVault.DeviceRegistrationState." + security_domain_name_for_uma(),
/*sample=*/
TrustedVaultDeviceRegistrationStateForUMA::
kAttemptingRegistrationWithNewKeyPair,
/*expected_bucket_count=*/1);
Mock::VerifyAndClearExpectations(connection());
// When the auth error is resolved, the registration should be retried.
EXPECT_CALL(
*connection(),
RegisterAuthenticationFactor(
Eq(account_info),
MatchTrustedVaultKeyAndVersions(
GetTrustedVaultKeysWithVersions({kVaultKey}, kLastKeyVersion)),
_, Eq(AuthenticationFactorType(LocalPhysicalDevice())), _));
base::HistogramTester histogram_tester2;
backend()->SetPrimaryAccount(
account_info, StandaloneTrustedVaultBackend::RefreshTokenErrorState::
kNoPersistentAuthErrors);
// The second attempt should NOT have logged the histogram, following the
// histogram's definition that it should be logged once.
histogram_tester2.ExpectTotalCount(
"TrustedVault.DeviceRegistrationState." + security_domain_name_for_uma(),
/*expected_count=*/0);
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldTryToRegisterDeviceEvenIfLocalKeysAreStale) {
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);
ASSERT_TRUE(backend()->MarkLocalKeysAsStale(account_info));
EXPECT_CALL(*connection(), RegisterLocalDeviceWithoutKeys).Times(0);
EXPECT_CALL(
*connection(),
RegisterAuthenticationFactor(
Eq(account_info),
MatchTrustedVaultKeyAndVersions(
GetTrustedVaultKeysWithVersions({kVaultKey}, kLastKeyVersion)),
_, Eq(AuthenticationFactorType(LocalPhysicalDevice())), _));
base::HistogramTester histogram_tester;
SetPrimaryAccountWithUnknownAuthError(account_info);
histogram_tester.ExpectUniqueSample(
"TrustedVault.DeviceRegistrationState." + security_domain_name_for_uma(),
/*sample=*/
TrustedVaultDeviceRegistrationStateForUMA::
kAttemptingRegistrationWithNewKeyPair,
/*expected_bucket_count=*/1);
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldNotTryToRegisterDeviceIfPreviousAttemptFailed) {
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);
backend()->SetLastRegistrationReturnedLocalDataObsoleteForTesting(
account_info.gaia);
EXPECT_CALL(*connection(), RegisterAuthenticationFactor).Times(0);
EXPECT_CALL(*connection(), RegisterLocalDeviceWithoutKeys).Times(0);
base::HistogramTester histogram_tester;
SetPrimaryAccountWithUnknownAuthError(account_info);
histogram_tester.ExpectUniqueSample(
"TrustedVault.DeviceRegistrationState." + security_domain_name_for_uma(),
/*sample=*/
TrustedVaultDeviceRegistrationStateForUMA::kLocalKeysAreStale,
/*expected_bucket_count=*/1);
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldRegisterDeviceAlthoughPreviousAttemptFailedUponNewStoredKeys) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const std::vector<uint8_t> kInitialKeys = {1, 2, 3};
const int kInitialKeysVersion = 5;
const std::vector<uint8_t> kNewKeys = {1, 2, 3, 4};
const int kNewKeysVersion = 6;
backend()->StoreKeys(account_info.gaia, {kInitialKeys}, kInitialKeysVersion);
backend()->SetLastRegistrationReturnedLocalDataObsoleteForTesting(
account_info.gaia);
EXPECT_CALL(*connection(), RegisterAuthenticationFactor).Times(0);
EXPECT_CALL(*connection(), RegisterLocalDeviceWithoutKeys).Times(0);
SetPrimaryAccountWithUnknownAuthError(account_info);
Mock::VerifyAndClearExpectations(connection());
ASSERT_FALSE(backend()
->GetDeviceRegistrationInfoForTesting(account_info.gaia)
.device_registered());
// StoreKeys() should trigger a registration nevertheless.
EXPECT_CALL(
*connection(),
RegisterAuthenticationFactor(
Eq(account_info),
MatchTrustedVaultKeyAndVersions(
GetTrustedVaultKeysWithVersions({kNewKeys}, kNewKeysVersion)),
_, Eq(AuthenticationFactorType(LocalPhysicalDevice())), _));
backend()->StoreKeys(account_info.gaia, {kNewKeys}, kNewKeysVersion);
}
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 MemberKeysSource&,
const SecureBoxPublicKey&, AuthenticationFactorType,
TrustedVaultConnection::RegisterAuthenticationFactorCallback
callback) {
device_registration_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
EXPECT_CALL(*connection(), RegisterAuthenticationFactor);
// Setting the primary account will trigger device registration.
SetPrimaryAccountWithUnknownAuthError(account_info);
ASSERT_FALSE(device_registration_callback.is_null());
Mock::VerifyAndClearExpectations(connection());
// Mimic transient failure.
std::move(device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kOtherError, /*key_version=*/0);
// Mimic a restart to trigger device registration attempt, which should remain
// throttled.
base::HistogramTester histogram_tester;
ResetBackend();
EXPECT_CALL(*connection(), RegisterAuthenticationFactor).Times(0);
SetPrimaryAccountWithUnknownAuthError(account_info);
histogram_tester.ExpectUniqueSample(
"TrustedVault.DeviceRegistrationState." + security_domain_name_for_uma(),
/*sample=*/
TrustedVaultDeviceRegistrationStateForUMA::kThrottledClientSide,
/*expected_bucket_count=*/1);
// Mimic a restart after sufficient time has passed, to trigger another device
// registration attempt, which should now be unthrottled.
base::HistogramTester histogram_tester2;
ResetBackend();
EXPECT_CALL(*connection(), RegisterAuthenticationFactor);
clock()->Advance(StandaloneTrustedVaultBackend::kThrottlingDuration);
SetPrimaryAccountWithUnknownAuthError(account_info);
histogram_tester2.ExpectUniqueSample(
"TrustedVault.DeviceRegistrationState." + security_domain_name_for_uma(),
/*sample=*/
TrustedVaultDeviceRegistrationStateForUMA::
kAttemptingRegistrationWithExistingKeyPair,
/*expected_bucket_count=*/1);
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldNotThrottleUponAccessTokenFetchingFailure) {
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 MemberKeysSource&,
const SecureBoxPublicKey&, AuthenticationFactorType,
TrustedVaultConnection::RegisterAuthenticationFactorCallback
callback) {
device_registration_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
EXPECT_CALL(*connection(), RegisterAuthenticationFactor);
// Setting the primary account will trigger device registration.
SetPrimaryAccountWithUnknownAuthError(account_info);
ASSERT_FALSE(device_registration_callback.is_null());
Mock::VerifyAndClearExpectations(connection());
base::HistogramTester histogram_tester;
// Mimic access token fetching failure.
std::move(device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kTransientAccessTokenFetchError,
/*key_version=*/0);
histogram_tester.ExpectUniqueSample(
/*name=*/"TrustedVault.DeviceRegistrationOutcome." +
security_domain_name_for_uma(),
/*sample=*/
TrustedVaultDeviceRegistrationOutcomeForUMA::
kTransientAccessTokenFetchError,
/*expected_bucket_count=*/1);
// Mimic a restart to trigger device registration attempt, which should not be
// throttled.
ResetBackend();
EXPECT_CALL(*connection(), RegisterAuthenticationFactor);
SetPrimaryAccountWithUnknownAuthError(account_info);
}
TEST_F(StandaloneTrustedVaultBackendTest, ShouldNotThrottleUponNetworkError) {
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 MemberKeysSource&,
const SecureBoxPublicKey&, AuthenticationFactorType,
TrustedVaultConnection::RegisterAuthenticationFactorCallback
callback) {
device_registration_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
EXPECT_CALL(*connection(), RegisterAuthenticationFactor);
// Setting the primary account will trigger device registration.
SetPrimaryAccountWithUnknownAuthError(account_info);
ASSERT_FALSE(device_registration_callback.is_null());
Mock::VerifyAndClearExpectations(connection());
// Mimic network error.
std::move(device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kNetworkError, /*key_version=*/0);
// Mimic a restart to trigger device registration attempt, which should not be
// throttled.
ResetBackend();
EXPECT_CALL(*connection(), RegisterAuthenticationFactor);
SetPrimaryAccountWithUnknownAuthError(account_info);
}
// 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 MemberKeysSource&,
const SecureBoxPublicKey&, AuthenticationFactorType,
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.
SetPrimaryAccountWithUnknownAuthError(account_info);
ASSERT_FALSE(device_registration_callback.is_null());
Mock::VerifyAndClearExpectations(connection());
// Mimic transient failure.
std::move(device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kOtherError, /*key_version=*/0);
// Mimic system set to the past.
clock()->Advance(base::Seconds(-1));
device_registration_callback =
TrustedVaultConnection::RegisterAuthenticationFactorCallback();
EXPECT_CALL(*connection(), RegisterAuthenticationFactor);
// Reset and set primary account to trigger device registration attempt.
SetPrimaryAccountWithUnknownAuthError(/*primary_account=*/std::nullopt);
SetPrimaryAccountWithUnknownAuthError(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);
SetPrimaryAccountWithUnknownAuthError(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());
}
// The server may clean up some stale keys eventually, client should clean them
// up as well to ensure that the state doesn't diverge. In particular, this may
// cause problems with registering authentication factors, since the server will
// reject request with stale keys.
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldCleanUpOldKeysWhenDownloadingNew) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const std::vector<uint8_t> kInitialVaultKey = {1, 2, 3};
const int kInitialLastKeyVersion = 1;
StoreKeysAndMimicDeviceRegistration({kInitialVaultKey},
kInitialLastKeyVersion, account_info);
ASSERT_TRUE(backend()->MarkLocalKeysAsStale(account_info));
SetPrimaryAccountWithUnknownAuthError(account_info);
TrustedVaultConnection::DownloadNewKeysCallback download_keys_callback;
ON_CALL(*connection(), DownloadNewKeys)
.WillByDefault(
[&](const CoreAccountInfo&,
const std::optional<TrustedVaultKeyAndVersion>&,
std::unique_ptr<SecureBoxKeyPair> key_pair,
TrustedVaultConnection::DownloadNewKeysCallback callback) {
download_keys_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
EXPECT_CALL(*connection(), DownloadNewKeys);
// 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());
const std::vector<uint8_t> kNewVaultKey = {2, 3, 5};
// Note that |fetch_keys_callback| should not receive kInitialVaultKey.
EXPECT_CALL(fetch_keys_callback, Run(ElementsAre(kNewVaultKey)));
std::move(download_keys_callback)
.Run(TrustedVaultDownloadKeysStatus::kSuccess, {kNewVaultKey},
kInitialLastKeyVersion + 1);
}
// Regression test for crbug.com/1500258: second FetchKeys() is triggered, while
// first is still ongoing (e.g. keys are being downloaded).
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldDownloadKeysAndCompleteConcurrentFetches) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const std::vector<uint8_t> kInitialVaultKey = {1, 2, 3};
const int kInitialLastKeyVersion = 1;
StoreKeysAndMimicDeviceRegistration({kInitialVaultKey},
kInitialLastKeyVersion, account_info);
ASSERT_TRUE(backend()->MarkLocalKeysAsStale(account_info));
SetPrimaryAccountWithUnknownAuthError(account_info);
TrustedVaultConnection::DownloadNewKeysCallback download_keys_callback;
ON_CALL(*connection(), DownloadNewKeys)
.WillByDefault(
[&](const CoreAccountInfo&,
const std::optional<TrustedVaultKeyAndVersion>&,
std::unique_ptr<SecureBoxKeyPair> key_pair,
TrustedVaultConnection::DownloadNewKeysCallback callback) {
download_keys_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
EXPECT_CALL(*connection(), DownloadNewKeys);
// FetchKeys() should trigger keys downloading.
base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback>
fetch_keys_callback1;
backend()->FetchKeys(account_info, fetch_keys_callback1.Get());
ASSERT_FALSE(download_keys_callback.is_null());
// Mimic second FetchKeys(), note that keys are not downloaded yet and first
// fetch is not completed.
base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback>
fetch_keys_callback2;
backend()->FetchKeys(account_info, fetch_keys_callback2.Get());
// Both fetches should be completed once keys are downloaded.
std::vector<uint8_t> kNewVaultKey = {2, 3, 5};
EXPECT_CALL(fetch_keys_callback1,
Run(ElementsAre(kInitialVaultKey, kNewVaultKey)));
EXPECT_CALL(fetch_keys_callback2,
Run(ElementsAre(kInitialVaultKey, kNewVaultKey)));
base::HistogramTester histogram_tester;
std::move(download_keys_callback)
.Run(TrustedVaultDownloadKeysStatus::kSuccess,
{kInitialVaultKey, kNewVaultKey}, kInitialLastKeyVersion + 1);
// Download keys status should be recorded for every fetch.
histogram_tester.ExpectUniqueSample(
"TrustedVault.DownloadKeysStatus." + security_domain_name_for_uma(),
/*sample=*/TrustedVaultDownloadKeysStatusForUMA::kSuccess,
/*expected_bucket_count=*/2);
}
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));
SetPrimaryAccountWithUnknownAuthError(account_info);
TrustedVaultConnection::DownloadNewKeysCallback download_keys_callback;
ON_CALL(*connection(), DownloadNewKeys)
.WillByDefault(
[&](const CoreAccountInfo&,
const std::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.
base::HistogramTester histogram_tester;
std::move(download_keys_callback)
.Run(TrustedVaultDownloadKeysStatus::kOtherError,
/*keys=*/std::vector<std::vector<uint8_t>>(),
/*last_key_version=*/0);
histogram_tester.ExpectUniqueSample(
"TrustedVault.DownloadKeysStatus." + security_domain_name_for_uma(),
/*sample=*/TrustedVaultDownloadKeysStatusForUMA::kOtherError,
/*expected_bucket_count=*/1);
EXPECT_TRUE(backend()->AreConnectionRequestsThrottledForTesting());
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(StandaloneTrustedVaultBackend::kThrottlingDuration);
EXPECT_FALSE(backend()->AreConnectionRequestsThrottledForTesting());
EXPECT_CALL(*connection(), DownloadNewKeys);
backend()->FetchKeys(account_info, /*callback=*/base::DoNothing());
EXPECT_FALSE(download_keys_callback.is_null());
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldThrottleIfDownloadingReturnedNoNewKeys) {
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));
SetPrimaryAccountWithUnknownAuthError(account_info);
TrustedVaultConnection::DownloadNewKeysCallback download_keys_callback;
ON_CALL(*connection(), DownloadNewKeys)
.WillByDefault(
[&](const CoreAccountInfo&,
const std::optional<TrustedVaultKeyAndVersion>&,
std::unique_ptr<SecureBoxKeyPair> key_pair,
TrustedVaultConnection::DownloadNewKeysCallback callback) {
download_keys_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
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 the server having no new keys.
base::HistogramTester histogram_tester;
std::move(download_keys_callback)
.Run(TrustedVaultDownloadKeysStatus::kNoNewKeys,
/*keys=*/std::vector<std::vector<uint8_t>>(),
/*last_key_version=*/0);
histogram_tester.ExpectUniqueSample(
"TrustedVault.DownloadKeysStatus." + security_domain_name_for_uma(),
/*sample=*/TrustedVaultDownloadKeysStatusForUMA::kNoNewKeys,
/*expected_bucket_count=*/1);
EXPECT_TRUE(backend()->AreConnectionRequestsThrottledForTesting());
// Registration should remain intact.
EXPECT_TRUE(backend()
->GetDeviceRegistrationInfoForTesting(account_info.gaia)
.device_registered());
}
// 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::RegisterAuthenticationFactorCallback
device_registration_callback;
EXPECT_CALL(*connection(), RegisterLocalDeviceWithoutKeys(account_info, _, _))
.WillOnce([&](const CoreAccountInfo&, const SecureBoxPublicKey&,
TrustedVaultConnection::RegisterAuthenticationFactorCallback
callback) {
device_registration_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
// Setting the primary account will trigger device registration.
SetPrimaryAccountWithUnknownAuthError(account_info);
ASSERT_FALSE(device_registration_callback.is_null());
// Pretend that the registration completed successfully.
std::move(device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kSuccess, kServerConstantKeyVersion);
// Now the device should be registered.
trusted_vault_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,
ShouldRegisterWithRecentVersionAndNotRedoRegistration) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const std::vector<uint8_t> kVaultKey = {1, 2, 3};
const int kLastKeyVersion = 1;
std::vector<uint8_t> private_device_key = StoreKeysAndMimicDeviceRegistration(
{kVaultKey}, kLastKeyVersion, account_info);
EXPECT_THAT(backend()
->GetDeviceRegistrationInfoForTesting(account_info.gaia)
.device_registered_version(),
Eq(1));
// Mimic restart to be able to test histogram recording.
ResetBackend();
// No registration attempt should be made, since device is already registered
// with version 1.
EXPECT_CALL(*connection(), RegisterAuthenticationFactor).Times(0);
EXPECT_CALL(*connection(), RegisterLocalDeviceWithoutKeys).Times(0);
base::HistogramTester histogram_tester;
SetPrimaryAccountWithUnknownAuthError(account_info);
histogram_tester.ExpectUniqueSample(
"TrustedVault.DeviceRegistrationState." + security_domain_name_for_uma(),
/*sample=*/
TrustedVaultDeviceRegistrationStateForUMA::kAlreadyRegisteredV1,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(
"TrustedVault.DeviceRegistered." + security_domain_name_for_uma(),
/*sample=*/true,
/*expected_bucket_count=*/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;
SetPrimaryAccountWithUnknownAuthError(account_info);
backend()->StoreKeys(account_info.gaia, kVaultKeys, kLastKeyVersion);
TrustedVaultConnection::RegisterAuthenticationFactorCallback
registration_callback;
EXPECT_CALL(
*connection(),
RegisterAuthenticationFactor(
Eq(account_info),
MatchTrustedVaultKeyAndVersions(
GetTrustedVaultKeysWithVersions({kVaultKeys}, kLastKeyVersion)),
PublicKeyWhenExportedEq(kPublicKey),
Eq(AuthenticationFactorType(
UnspecifiedAuthenticationFactorType(kMethodTypeHint))),
_))
.WillOnce([&](const CoreAccountInfo&, const MemberKeysSource&,
const SecureBoxPublicKey& device_public_key,
AuthenticationFactorType,
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, kLastKeyVersion);
}
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());
SetPrimaryAccountWithUnknownAuthError(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);
ASSERT_FALSE(backend()->HasPendingTrustedRecoveryMethodForTesting());
// 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());
EXPECT_TRUE(backend()->HasPendingTrustedRecoveryMethodForTesting());
// 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(
_, _, _, Eq(AuthenticationFactorType(LocalPhysicalDevice())), _));
EXPECT_CALL(
*connection(),
RegisterAuthenticationFactor(
Eq(account_info),
MatchTrustedVaultKeyAndVersions(
GetTrustedVaultKeysWithVersions({kVaultKeys}, kLastKeyVersion)),
PublicKeyWhenExportedEq(kPublicKey),
Eq(AuthenticationFactorType(
UnspecifiedAuthenticationFactorType(kMethodTypeHint))),
_))
.WillOnce([&](const CoreAccountInfo&, const MemberKeysSource&,
const SecureBoxPublicKey& device_public_key,
AuthenticationFactorType,
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>();
});
SetPrimaryAccountWithUnknownAuthError(account_info);
// The operation should be in flight.
EXPECT_FALSE(backend()->HasPendingTrustedRecoveryMethodForTesting());
ASSERT_FALSE(registration_callback.is_null());
// Mimic successful completion of the request.
EXPECT_CALL(completion_callback, Run());
std::move(registration_callback)
.Run(TrustedVaultRegistrationStatus::kSuccess, kLastKeyVersion);
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldDeferTrustedRecoveryMethodUntilPersistentAuthErrorFixed) {
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;
// Mimic device previously registered with some keys.
StoreKeysAndMimicDeviceRegistration(kVaultKeys, kLastKeyVersion,
account_info);
// Mimic entering a persistent auth error.
backend()->SetPrimaryAccount(
account_info, StandaloneTrustedVaultBackend::RefreshTokenErrorState::
kPersistentAuthError);
// No request should be issued while there is a persistent auth error.
base::MockCallback<base::OnceClosure> completion_callback;
EXPECT_CALL(*connection(), RegisterAuthenticationFactor).Times(0);
backend()->AddTrustedRecoveryMethod(account_info.gaia, kPublicKey,
kMethodTypeHint,
completion_callback.Get());
EXPECT_TRUE(backend()->HasPendingTrustedRecoveryMethodForTesting());
// Upon resolving the auth error, the request should be issued.
TrustedVaultConnection::RegisterAuthenticationFactorCallback
registration_callback;
EXPECT_CALL(
*connection(),
RegisterAuthenticationFactor(
Eq(account_info),
MatchTrustedVaultKeyAndVersions(
GetTrustedVaultKeysWithVersions({kVaultKeys}, kLastKeyVersion)),
PublicKeyWhenExportedEq(kPublicKey),
Eq(AuthenticationFactorType(
UnspecifiedAuthenticationFactorType(kMethodTypeHint))),
_))
.WillOnce([&](const CoreAccountInfo&, const MemberKeysSource&,
const SecureBoxPublicKey& device_public_key,
AuthenticationFactorType,
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, StandaloneTrustedVaultBackend::RefreshTokenErrorState::
kNoPersistentAuthErrors);
// The operation should be in flight.
EXPECT_FALSE(backend()->HasPendingTrustedRecoveryMethodForTesting());
ASSERT_FALSE(registration_callback.is_null());
// Mimic successful completion of the request.
EXPECT_CALL(completion_callback, Run());
std::move(registration_callback)
.Run(TrustedVaultRegistrationStatus::kSuccess, kLastKeyVersion);
}
} // namespace
} // namespace trusted_vault