blob: 0997f9ae965f3f3ef33c2c7b519588ac2744d8d6 [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 <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback_helpers.h"
#include "base/hash/md5.h"
#include "base/memory/raw_ptr.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 "components/os_crypt/sync/os_crypt.h"
#include "components/os_crypt/sync/os_crypt_mocker.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
#include "components/sync/base/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/trusted_vault_connection.h"
#include "components/trusted_vault/trusted_vault_histograms.h"
#include "components/trusted_vault/trusted_vault_server_constants.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace trusted_vault {
namespace {
using testing::_;
using testing::ByMove;
using testing::ElementsAre;
using testing::Eq;
using testing::IsEmpty;
using testing::IsNull;
using testing::Mock;
using testing::Ne;
using testing::NotNull;
using testing::Return;
using testing::SaveArg;
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;
}
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;
}
bool WriteLocalTrustedVaultFile(
const trusted_vault_pb::LocalTrustedVault& proto,
const base::FilePath& path) {
trusted_vault_pb::LocalTrustedVaultFileContent file_proto;
file_proto.set_serialized_local_trusted_vault(proto.SerializeAsString());
file_proto.set_md5_digest_hex_string(
base::MD5String(file_proto.serialized_local_trusted_vault()));
return base::WriteFile(path, file_proto.SerializeAsString());
}
bool WriteLocalEncryptedTrustedVaultFile(
const trusted_vault_pb::LocalTrustedVault& proto,
const base::FilePath& path) {
std::string encrypted_content;
if (!OSCrypt::EncryptString(proto.SerializeAsString(), &encrypted_content)) {
return false;
}
return base::WriteFile(path, encrypted_content);
}
trusted_vault_pb::LocalTrustedVault ReadLocalTrustedVaultFile(
const base::FilePath& path) {
std::string file_content;
trusted_vault_pb::LocalTrustedVault data_proto;
if (!base::ReadFileToString(path, &file_content)) {
return data_proto;
}
trusted_vault_pb::LocalTrustedVaultFileContent file_proto;
if (!file_proto.ParseFromString(file_content)) {
return data_proto;
}
if (base::MD5String(file_proto.serialized_local_trusted_vault()) !=
file_proto.md5_digest_hex_string()) {
return data_proto;
}
data_proto.ParseFromString(file_proto.serialized_local_trusted_vault());
return data_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>,
DownloadIsRecoverabilityDegraded,
(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")))),
deprecated_file_path_(temp_dir_.GetPath().Append(
base::FilePath(FILE_PATH_LITERAL("deprecated_file")))) {
clock_.SetNow(base::Time::Now());
ResetBackend();
}
~StandaloneTrustedVaultBackendTest() override = default;
void SetUp() override { OSCryptMocker::SetUp(); }
void TearDown() override { OSCryptMocker::TearDown(); }
void ResetBackend() {
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_, deprecated_file_path_, std::move(delegate),
std::move(connection));
backend_->SetClockForTesting(&clock_);
backend_->ReadDataFromDisk();
// 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>();
}));
}
MockTrustedVaultConnection* connection() { return connection_; }
base::SimpleTestClock* clock() { return &clock_; }
StandaloneTrustedVaultBackend* backend() { return backend_.get(); }
const base::FilePath& file_path() { return file_path_; }
const base::FilePath& deprecated_file_path() { return deprecated_file_path_; }
void SetPrimaryAccountWithUnknownAuthError(
absl::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), 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.
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);
// Reset primary account.
SetPrimaryAccountWithUnknownAuthError(/*primary_account=*/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_;
const base::FilePath deprecated_file_path_;
raw_ptr<testing::NiceMock<MockDelegate>> delegate_;
raw_ptr<testing::NiceMock<MockTrustedVaultConnection>> connection_;
base::SimpleTestClock clock_;
scoped_refptr<StandaloneTrustedVaultBackend> backend_;
};
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 =
ReadLocalTrustedVaultFile(file_path());
ASSERT_THAT(proto.user_size(), Eq(1));
EXPECT_THAT(proto.user(0).degraded_recoverability_state(),
DegradedRecoverabilityStateEq(degraded_recoverability_state));
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldInvokeGetIsRecoverabilityDegradedCallbackImmediately) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
syncer::kSyncTrustedVaultPeriodicDegradedRecoverabilityPolling);
// 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/1413179): looks like this test verifies scenario not
// possible in prod anymore, remove it together with
// |pending_get_is_recoverability_degraded_| logic.
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
syncer::kSyncTrustedVaultPeriodicDegradedRecoverabilityPolling);
// 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/1413179): looks like this test verifies scenario not
// possible in prod anymore, remove it together with
// |pending_get_is_recoverability_degraded_| logic.
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
syncer::kSyncTrustedVaultPeriodicDegradedRecoverabilityPolling);
// 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, ShouldRecordNotFoundWhenReadingFile) {
base::HistogramTester histogram_tester;
backend()->ReadDataFromDisk();
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultFileReadStatus",
/*sample=*/TrustedVaultFileReadStatusForUMA::kNotFound,
/*expected_bucket_count=*/1);
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldRecordMD5DigestMismatchWhenReadingFile) {
trusted_vault_pb::LocalTrustedVaultFileContent file_proto;
file_proto.set_md5_digest_hex_string("corrupted_md5_digest");
ASSERT_TRUE(base::WriteFile(file_path(), file_proto.SerializeAsString()));
base::HistogramTester histogram_tester;
backend()->ReadDataFromDisk();
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultFileReadStatus",
/*sample=*/TrustedVaultFileReadStatusForUMA::kMD5DigestMismatch,
/*expected_bucket_count=*/1);
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldRecordFileProtoDeserializationFailedWhenReadingFile) {
ASSERT_TRUE(base::WriteFile(file_path(), "corrupted_proto"));
base::HistogramTester histogram_tester;
backend()->ReadDataFromDisk();
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultFileReadStatus",
/*sample=*/
TrustedVaultFileReadStatusForUMA::kFileProtoDeserializationFailed,
/*expected_bucket_count=*/1);
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldRecordDataProtoDeserializationFailedWhenReadingFile) {
const std::string kCorruptedSerializedDataProto = "corrupted_proto";
trusted_vault_pb::LocalTrustedVaultFileContent file_proto;
file_proto.set_serialized_local_trusted_vault(kCorruptedSerializedDataProto);
file_proto.set_md5_digest_hex_string(
base::MD5String(kCorruptedSerializedDataProto));
ASSERT_TRUE(base::WriteFile(file_path(), file_proto.SerializeAsString()));
base::HistogramTester histogram_tester;
backend()->ReadDataFromDisk();
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultFileReadStatus",
/*sample=*/
TrustedVaultFileReadStatusForUMA::kDataProtoDeserializationFailed,
/*expected_bucket_count=*/1);
}
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);
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());
ASSERT_TRUE(WriteLocalTrustedVaultFile(initial_data, file_path()));
base::HistogramTester histogram_tester;
backend()->ReadDataFromDisk();
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultFileReadStatus",
/*sample=*/TrustedVaultFileReadStatusForUMA::kSuccess,
/*expected_bucket_count=*/1);
// 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, ShouldMigrateDataFromDeprecatedFile) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user1");
const std::vector<uint8_t> kKey = {0, 1, 2, 3, 4};
const int kLastKeyVersion = 1;
trusted_vault_pb::LocalTrustedVault initial_data;
// Migration from version 0 to version 1 makes test more complex, bypass it.
initial_data.set_data_version(1);
trusted_vault_pb::LocalTrustedVaultPerUser* user_data =
initial_data.add_user();
user_data->set_gaia_id(account_info.gaia);
user_data->add_vault_key()->set_key_material(kKey.data(), kKey.size());
user_data->set_last_vault_key_version(kLastKeyVersion);
ASSERT_TRUE(WriteLocalEncryptedTrustedVaultFile(initial_data,
deprecated_file_path()));
backend()->ReadDataFromDisk();
// Ensure that backend is able to use data from deprecated file.
base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback>
fetch_keys_callback;
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/ElementsAre(kKey)));
backend()->FetchKeys(account_info, fetch_keys_callback.Get());
// Ensure that backend completed file migration.
EXPECT_FALSE(base::PathExists(deprecated_file_path()));
trusted_vault_pb::LocalTrustedVault proto =
ReadLocalTrustedVaultFile(file_path());
ASSERT_THAT(proto.user_size(), Eq(1));
EXPECT_THAT(proto.user(0).vault_key(), ElementsAre(KeyMaterialEq(kKey)));
EXPECT_THAT(proto.user(0).last_vault_key_version(), Eq(kLastKeyVersion));
}
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);
user_data->add_vault_key()->set_key_material(
GetConstantTrustedVaultKey().data(), GetConstantTrustedVaultKey().size());
user_data->add_vault_key()->set_key_material(kKey.data(), kKey.size());
ASSERT_TRUE(WriteLocalTrustedVaultFile(initial_data, file_path()));
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};
base::HistogramTester histogram_tester;
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);
histogram_tester.ExpectUniqueSample("Sync.TrustedVaultFileWriteSuccess",
/*sample=*/true,
/*expected_bucket_count=*/3);
// Read the file from disk.
trusted_vault_pb::LocalTrustedVault proto =
ReadLocalTrustedVaultFile(file_path());
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,
ShouldUpgradeToVersion1AndFixMissingConstantKey) {
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};
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);
user_data2->set_gaia_id(account_info_2.gaia);
// Mimic |user_data1| to be affected by crbug.com/1267391 and |user_data2| to
// be not affected.
AssignBytesToProtoString(kKey1,
user_data1->add_vault_key()->mutable_key_material());
AssignBytesToProtoString(GetConstantTrustedVaultKey(),
user_data2->add_vault_key()->mutable_key_material());
AssignBytesToProtoString(kKey2,
user_data2->add_vault_key()->mutable_key_material());
ASSERT_TRUE(WriteLocalTrustedVaultFile(initial_data, file_path()));
// Backend should fix corrupted data and write new state.
backend()->ReadDataFromDisk();
// Read the file from disk.
trusted_vault_pb::LocalTrustedVault proto =
ReadLocalTrustedVaultFile(file_path());
ASSERT_THAT(proto.user_size(), Eq(2));
// Constant key should be added for the first user.
EXPECT_THAT(proto.user(0).vault_key(),
ElementsAre(KeyMaterialEq(GetConstantTrustedVaultKey()),
KeyMaterialEq(kKey1)));
// Sanity check that state for the second user isn't changed.
EXPECT_THAT(proto.user(1).vault_key(),
ElementsAre(KeyMaterialEq(GetConstantTrustedVaultKey()),
KeyMaterialEq(kKey2)));
EXPECT_THAT(proto.data_version(), testing::Ge(1));
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldUpgradeAllUsersDataToVersion2AndResetKeysAreStale) {
const CoreAccountInfo account_info_1 = MakeAccountInfoWithGaiaId("user1");
const CoreAccountInfo account_info_2 = MakeAccountInfoWithGaiaId("user2");
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);
user_data1->set_keys_marked_as_stale_by_consumer(true);
user_data2->set_gaia_id(account_info_2.gaia);
user_data2->set_keys_marked_as_stale_by_consumer(true);
ASSERT_TRUE(WriteLocalTrustedVaultFile(initial_data, file_path()));
// Backend should reset |keys_marked_as_stale_by_consumer| for both accounts
// and write new state.
backend()->ReadDataFromDisk();
trusted_vault_pb::LocalTrustedVault new_data =
ReadLocalTrustedVaultFile(file_path());
ASSERT_THAT(new_data.user_size(), Eq(2));
EXPECT_FALSE(new_data.user(0).keys_marked_as_stale_by_consumer());
EXPECT_FALSE(new_data.user(1).keys_marked_as_stale_by_consumer());
EXPECT_THAT(new_data.data_version(), Eq(2));
}
// This test ensures that migration logic in ReadDataFromDisk() doesn't create
// new file if there wasn't any.
TEST_F(StandaloneTrustedVaultBackendTest, ShouldNotWriteEmptyData) {
backend()->ReadDataFromDisk();
EXPECT_FALSE(base::PathExists(file_path()));
}
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(), deprecated_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.
SetPrimaryAccountWithUnknownAuthError(account_info_1);
SetPrimaryAccountWithUnknownAuthError(/*primary_account=*/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.
trusted_vault_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);
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=*/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.
trusted_vault_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);
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.
auto new_backend = base::MakeRefCounted<StandaloneTrustedVaultBackend>(
file_path(), deprecated_file_path(),
/*delegate=*/std::make_unique<testing::NiceMock<MockDelegate>>(),
/*connection=*/nullptr);
new_backend->ReadDataFromDisk();
new_backend->SetPrimaryAccount(
/*primary_account=*/absl::nullopt,
StandaloneTrustedVaultBackend::RefreshTokenErrorState::kUnknown);
SetPrimaryAccountWithUnknownAuthError(/*primary_account=*/absl::nullopt);
EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/IsEmpty()));
new_backend->SetPrimaryAccount(
account_info,
StandaloneTrustedVaultBackend::RefreshTokenErrorState::kUnknown);
new_backend->FetchKeys(account_info, fetch_keys_callback.Get());
// Read the file from disk and verify that keys were removed from disk
// storage.
trusted_vault_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.
base::HistogramTester histogram_tester;
SetPrimaryAccountWithUnknownAuthError(account_info);
ASSERT_FALSE(device_registration_callback.is_null());
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultDeviceRegistrationState",
/*sample=*/
TrustedVaultDeviceRegistrationStateForUMA::
kAttemptingRegistrationWithNewKeyPair,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample("Sync.TrustedVaultDeviceRegistered",
/*sample=*/false,
/*expected_bucket_count=*/1);
// Pretend that the registration completed successfully.
std::move(device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kSuccess);
histogram_tester.ExpectUniqueSample(
/*name=*/"Sync.TrustedVaultDeviceRegistrationOutcome",
/*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_bytes(
base::make_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), 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) {
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);
// Verify persisted file state.
trusted_vault_pb::LocalTrustedVault proto =
ReadLocalTrustedVaultFile(file_path());
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::RegisterDeviceWithoutKeysCallback
device_registration_callback;
std::vector<uint8_t> serialized_public_device_key;
EXPECT_CALL(*connection(), RegisterDeviceWithoutKeys(Eq(account_info), _, _))
.WillOnce([&](const CoreAccountInfo& account_info,
const SecureBoxPublicKey& device_public_key,
TrustedVaultConnection::RegisterDeviceWithoutKeysCallback
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,
TrustedVaultKeyAndVersion(GetConstantTrustedVaultKey(),
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_bytes(
base::make_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), ElementsAre(kVaultKey), kLastKeyVersion, _,
AuthenticationFactorType::kPhysicalDevice,
/*authentication_factor_type_hint=*/Eq(absl::nullopt), _));
base::HistogramTester histogram_tester;
backend()->SetPrimaryAccount(
account_info, StandaloneTrustedVaultBackend::RefreshTokenErrorState::
kPersistentAuthError);
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultDeviceRegistrationState",
/*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), ElementsAre(kVaultKey), kLastKeyVersion, _,
AuthenticationFactorType::kPhysicalDevice,
/*authentication_factor_type_hint=*/Eq(absl::nullopt), _));
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("Sync.TrustedVaultDeviceRegistrationState",
/*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(), RegisterDeviceWithoutKeys).Times(0);
EXPECT_CALL(*connection(),
RegisterAuthenticationFactor(
Eq(account_info), ElementsAre(kVaultKey), kLastKeyVersion, _,
AuthenticationFactorType::kPhysicalDevice,
/*authentication_factor_type_hint=*/Eq(absl::nullopt), _));
base::HistogramTester histogram_tester;
SetPrimaryAccountWithUnknownAuthError(account_info);
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultDeviceRegistrationState",
/*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(), RegisterDeviceWithoutKeys).Times(0);
base::HistogramTester histogram_tester;
SetPrimaryAccountWithUnknownAuthError(account_info);
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultDeviceRegistrationState",
/*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(), RegisterDeviceWithoutKeys).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), ElementsAre(kNewKeys), kNewKeysVersion, _,
AuthenticationFactorType::kPhysicalDevice,
/*authentication_factor_type_hint=*/Eq(absl::nullopt), _));
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 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>();
});
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);
// 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(
"Sync.TrustedVaultDeviceRegistrationState",
/*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(
"Sync.TrustedVaultDeviceRegistrationState",
/*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 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>();
});
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);
histogram_tester.ExpectUniqueSample(
/*name=*/"Sync.TrustedVaultDeviceRegistrationOutcome",
/*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 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>();
});
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);
// 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 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.
SetPrimaryAccountWithUnknownAuthError(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::Seconds(-1));
device_registration_callback =
TrustedVaultConnection::RegisterAuthenticationFactorCallback();
EXPECT_CALL(*connection(), RegisterAuthenticationFactor);
// Reset and set primary account to trigger device registration attempt.
SetPrimaryAccountWithUnknownAuthError(/*primary_account=*/absl::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());
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldDownloadNewKeysWithV1Registration) {
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);
ASSERT_THAT(backend()
->GetDeviceRegistrationInfoForTesting(account_info.gaia)
.device_registered_version(),
Eq(1));
const std::vector<uint8_t> kNewVaultKey = {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. Note that the client should keep old key as well.
base::HistogramTester histogram_tester;
EXPECT_CALL(fetch_keys_callback,
Run(/*keys=*/ElementsAre(kInitialVaultKey, kNewVaultKey)));
std::move(download_keys_callback)
.Run(TrustedVaultDownloadKeysStatus::kSuccess, {kNewVaultKey},
kNewLastKeyVersion);
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultDownloadKeysStatus",
/*sample=*/TrustedVaultDownloadKeysStatusForUMA::kSuccess,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultDownloadKeysStatusV1",
/*sample=*/TrustedVaultDownloadKeysStatusForUMA::kSuccess,
/*expected_bucket_count=*/1);
}
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 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.
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(
"Sync.TrustedVaultDownloadKeysStatus",
/*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 absl::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(
"Sync.TrustedVaultDownloadKeysStatus",
/*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::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.
SetPrimaryAccountWithUnknownAuthError(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.
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, ShouldRedoDeviceRegistration) {
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(
{GetConstantTrustedVaultKey(), kVaultKey}, kLastKeyVersion, account_info);
// Mimic that device was registered before "redo registration" logic was
// introduced.
backend()->SetDeviceRegisteredVersionForTesting(account_info.gaia,
/*version=*/0);
// Mimic restart to be able to test histogram recording.
ResetBackend();
// Another device registration request should be issued upon setting the
// primary account.
TrustedVaultConnection::RegisterAuthenticationFactorCallback
device_registration_callback;
std::vector<uint8_t> serialized_public_device_key;
EXPECT_CALL(*connection(),
RegisterAuthenticationFactor(
Eq(account_info),
ElementsAre(GetConstantTrustedVaultKey(), 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>();
});
{
base::HistogramTester histogram_tester;
SetPrimaryAccountWithUnknownAuthError(account_info);
ASSERT_FALSE(device_registration_callback.is_null());
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultDeviceRegistrationState",
/*sample=*/
TrustedVaultDeviceRegistrationStateForUMA::
kAttemptingRegistrationWithExistingKeyPair,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample("Sync.TrustedVaultDeviceRegistered",
/*sample=*/true,
/*expected_bucket_count=*/1);
// Pretend that the registration completed successfully.
std::move(device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kSuccess);
// Now the device reregistration should be completed.
trusted_vault_pb::LocalDeviceRegistrationInfo registration_info =
backend()->GetDeviceRegistrationInfoForTesting(account_info.gaia);
EXPECT_TRUE(registration_info.device_registered());
EXPECT_THAT(registration_info.device_registered_version(), Eq(1));
EXPECT_TRUE(registration_info.has_private_key_material());
// Ensure device key was reused.
EXPECT_THAT(ProtoStringToBytes(registration_info.private_key_material()),
Eq(private_device_key));
EXPECT_THAT(
serialized_public_device_key,
Eq(SecureBoxKeyPair::CreateByPrivateKeyImport(private_device_key)
->public_key()
.ExportToBytes()));
}
{
// Mimic the restart and verify that kAlreadyRegisteredV1 is recorded.
ResetBackend();
base::HistogramTester histogram_tester;
SetPrimaryAccountWithUnknownAuthError(account_info);
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultDeviceRegistrationState",
/*sample=*/
TrustedVaultDeviceRegistrationStateForUMA::kAlreadyRegisteredV1,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample("Sync.TrustedVaultDeviceRegistered",
/*sample=*/true,
/*expected_bucket_count=*/1);
}
}
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldRedoDeviceRegistrationWithConstantKey) {
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const int kInitialServerConstantKeyVersion = 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.
SetPrimaryAccountWithUnknownAuthError(account_info);
// Pretend that the registration completed successfully.
ASSERT_FALSE(device_registration_callback.is_null());
std::move(device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kSuccess,
TrustedVaultKeyAndVersion{GetConstantTrustedVaultKey(),
kInitialServerConstantKeyVersion});
// Mimic that device was registered before "redo registration" logic was
// introduced.
backend()->SetDeviceRegisteredVersionForTesting(account_info.gaia,
/*version=*/0);
// Mimic restart to be able to test histogram recording.
ResetBackend();
// Another device registration request should be issued upon setting the
// primary account and it should ignore presence of
// kInitialServerConstantKeyVersion, e.g. RegisterDeviceWithoutKeys() again.
TrustedVaultConnection::RegisterDeviceWithoutKeysCallback
device_redo_registration_callback;
EXPECT_CALL(*connection(), RegisterDeviceWithoutKeys(account_info, _, _))
.WillOnce([&](const CoreAccountInfo&,
const SecureBoxPublicKey& device_public_key,
TrustedVaultConnection::RegisterDeviceWithoutKeysCallback
callback) {
device_redo_registration_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
{
const int kNewServerConstantKeyVersion = 101;
base::HistogramTester histogram_tester;
SetPrimaryAccountWithUnknownAuthError(account_info);
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultDeviceRegistrationState",
/*sample=*/
TrustedVaultDeviceRegistrationStateForUMA::
kAttemptingRegistrationWithExistingKeyPair,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample("Sync.TrustedVaultDeviceRegistered",
/*sample=*/true,
/*expected_bucket_count=*/1);
// Pretend that the registration completed successfully and that constant
// key version has changed between device registration requests.
ASSERT_FALSE(device_redo_registration_callback.is_null());
std::move(device_redo_registration_callback)
.Run(TrustedVaultRegistrationStatus::kSuccess,
TrustedVaultKeyAndVersion{GetConstantTrustedVaultKey(),
kNewServerConstantKeyVersion});
// Now the device reregistration should be completed.
trusted_vault_pb::LocalDeviceRegistrationInfo registration_info =
backend()->GetDeviceRegistrationInfoForTesting(account_info.gaia);
EXPECT_TRUE(registration_info.device_registered());
EXPECT_THAT(registration_info.device_registered_version(), Eq(1));
EXPECT_TRUE(registration_info.has_private_key_material());
// Read the file from disk and verify that kNewServerConstantKeyVersion is
// stored.
trusted_vault_pb::LocalTrustedVault proto =
ReadLocalTrustedVaultFile(file_path());
ASSERT_THAT(proto.user_size(), Eq(1));
EXPECT_THAT(proto.user(0).last_vault_key_version(),
Eq(kNewServerConstantKeyVersion));
}
{
// Mimic the restart and verify that kAlreadyRegisteredV1 is recorded.
ResetBackend();
base::HistogramTester histogram_tester;
SetPrimaryAccountWithUnknownAuthError(account_info);
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultDeviceRegistrationState",
/*sample=*/
TrustedVaultDeviceRegistrationStateForUMA::kAlreadyRegisteredV1,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample("Sync.TrustedVaultDeviceRegistered",
/*sample=*/true,
/*expected_bucket_count=*/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(), RegisterDeviceWithoutKeys).Times(0);
base::HistogramTester histogram_tester;
SetPrimaryAccountWithUnknownAuthError(account_info);
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultDeviceRegistrationState",
/*sample=*/
TrustedVaultDeviceRegistrationStateForUMA::kAlreadyRegisteredV1,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample("Sync.TrustedVaultDeviceRegistered",
/*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), 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());
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(
_, _, _, _, 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>();
});
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);
}
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), 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, 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);
}
// Verifies that Backend can process device registration and keys downloading
// concurrently, when device registration is going to succeed and triggered
// first (to ensure that keys downloading doesn't cancel device registration).
// This is not a likely scenario (keys downloading attempt is an indicator that
// device registration will fail), but Backend shouldn't work under this
// assumption as already reflected on the data level:
// |keys_marked_as_stale_by_consumer| doesn't imply
// |last_registration_returned_local_data_obsolete|.
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldRegisterDeviceWhileConcurrentlyDownloadingKeys) {
// Prepare state where both requests are meaningful:
// 1. This is "redo device registration" attempt (otherwise FetchKeys() will
// fail early).
// 2. Local keys are marked as stale (otherwise FetchKeys() will succeed
// early).
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const std::vector<std::vector<uint8_t>> kTrustedVaultKeys = {{1, 2, 3}};
const int kLastKeyVersion = 1;
StoreKeysAndMimicDeviceRegistration(kTrustedVaultKeys, kLastKeyVersion,
account_info);
// Mimic that device was registered before "redo registration" logic was
// introduced.
backend()->SetDeviceRegisteredVersionForTesting(account_info.gaia,
/*version=*/0);
backend()->MarkLocalKeysAsStale(account_info);
TrustedVaultConnection::RegisterAuthenticationFactorCallback
redo_device_registration_callback;
EXPECT_CALL(*connection(),
RegisterAuthenticationFactor(
Eq(account_info), kTrustedVaultKeys, 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) {
redo_device_registration_callback = std::move(callback);
return std::make_unique<TrustedVaultConnection::Request>();
});
// Trigger "redo device registration".
SetPrimaryAccountWithUnknownAuthError(account_info);
// Trigger keys downloading, ensure that FetchKeys() actually starts
// downloading attempt (e.g. keys are not fetched immediately).
EXPECT_CALL(*connection(),
DownloadNewKeys(Eq(account_info),
TrustedVaultKeyAndVersionEq(kTrustedVaultKeys[0],
kLastKeyVersion),
/*device_key_pair=*/NotNull(), _))
.WillOnce(
Return(ByMove(std::make_unique<TrustedVaultConnection::Request>())));
backend()->FetchKeys(account_info, base::DoNothing());
{
trusted_vault_pb::LocalDeviceRegistrationInfo registration_info =
backend()->GetDeviceRegistrationInfoForTesting(account_info.gaia);
ASSERT_THAT(registration_info.device_registered_version(), Ne(1));
}
// Complete "redo device registration" and verify it succeeds.
ASSERT_FALSE(redo_device_registration_callback.is_null());
std::move(redo_device_registration_callback)
.Run(TrustedVaultRegistrationStatus::kSuccess);
trusted_vault_pb::LocalDeviceRegistrationInfo registration_info =
backend()->GetDeviceRegistrationInfoForTesting(account_info.gaia);
EXPECT_THAT(registration_info.device_registered_version(), Eq(1));
}
// Verifies that Backend can process device registration and keys downloading
// concurrently, when keys downloading is going to succeed and triggered first
// (to ensure that device registration doesn't cancel keys downloading).
TEST_F(StandaloneTrustedVaultBackendTest,
ShouldDownloadKeysWhileConcurrentlyRegisteringDevice) {
// Prepare state where both requests are meaningful:
// 1. This is "redo device registration" attempt (otherwise FetchKeys() will
// fail early).
// 2. Local keys are marked as stale (otherwise FetchKeys() will succeed
// early).
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const std::vector<uint8_t> kInitialTrustedVaultKey = {1, 2, 3};
const int kInitialLastKeyVersion = 1;
StoreKeysAndMimicDeviceRegistration({kInitialTrustedVaultKey},
kInitialLastKeyVersion, account_info);
// Note: SetPrimaryAccount() doesn't trigger device registration yet (not
// needed), the test exploits |has_persistent_auth_error| to trigger it by
// another SetPrimaryAccount() later.
backend()->SetPrimaryAccount(
account_info, StandaloneTrustedVaultBackend::RefreshTokenErrorState::
kPersistentAuthError);
// Mimic that device was registered before "redo registration" logic was
// introduced.
backend()->SetDeviceRegisteredVersionForTesting(account_info.gaia,
/*version=*/0);
backend()->MarkLocalKeysAsStale(account_info);
// Trigger keys downloading, ensure that FetchKeys() actually starts
// downloading attempt (e.g. keys are not fetched immediately).
TrustedVaultConnection::DownloadNewKeysCallback download_keys_callback;
base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback>
fetch_keys_callback;
EXPECT_CALL(*connection(), DownloadNewKeys(Eq(account_info),
TrustedVaultKeyAndVersionEq(
kInitialTrustedVaultKey,
kInitialLastKeyVersion),
/*device_key_pair=*/NotNull(), _))
.WillOnce([&](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>();
});
backend()->FetchKeys(account_info, fetch_keys_callback.Get());
// Note: RegisterAuthenticationFactor() will be actually called two times,
// once upon SetPrimaryAccount() with stale keys and once upon keys
// downloading with new keys.
EXPECT_CALL(
*connection(),
RegisterAuthenticationFactor(
Eq(account_info), _, _, _, AuthenticationFactorType::kPhysicalDevice,
/*authentication_factor_type_hint=*/Eq(absl::nullopt), _))
.WillRepeatedly([&]() {
return std::make_unique<TrustedVaultConnection::Request>();
});
// Trigger "redo device registration".
backend()->SetPrimaryAccount(
account_info, StandaloneTrustedVaultBackend::RefreshTokenErrorState::
kNoPersistentAuthErrors);
// Mimic successful key downloading, it should make fetch keys attempt
// completed.
const std::vector<uint8_t> kNewTrustedVaultKey = {2, 3, 4};
EXPECT_CALL(
fetch_keys_callback,
Run(/*keys*/ ElementsAre(kInitialTrustedVaultKey, kNewTrustedVaultKey)));
ASSERT_FALSE(download_keys_callback.is_null());
std::move(download_keys_callback)
.Run(TrustedVaultDownloadKeysStatus::kSuccess, {kNewTrustedVaultKey},
kInitialLastKeyVersion + 1);
}
TEST_F(StandaloneTrustedVaultBackendTest, ShouldVerifyRegistration) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
syncer::kSyncTrustedVaultVerifyDeviceRegistration);
base::test::SingleThreadTaskEnvironment environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
const std::vector<uint8_t> kVaultKey = {1, 2, 3};
const int kLastKeyVersion = 1;
StoreKeysAndMimicDeviceRegistration({kVaultKey}, kLastKeyVersion,
account_info);
SetPrimaryAccountWithUnknownAuthError(account_info);
// Now the device should be registered.
ASSERT_TRUE(backend()
->GetDeviceRegistrationInfoForTesting(account_info.gaia)
.device_registered());
// Mimic a restart. The device should remain registered.
ResetBackend();
ASSERT_TRUE(backend()
->GetDeviceRegistrationInfoForTesting(account_info.gaia)
.device_registered());
// The device should not register again.
EXPECT_CALL(*connection(), RegisterAuthenticationFactor).Times(0);
EXPECT_CALL(*connection(), RegisterDeviceWithoutKeys).Times(0);
SetPrimaryAccountWithUnknownAuthError(account_info);
TrustedVaultConnection::DownloadNewKeysCallback download_keys_callback;
EXPECT_CALL(*connection(), DownloadNewKeys(Eq(account_info),
TrustedVaultKeyAndVersionEq(
kVaultKey, kLastKeyVersion),
_, _))
.WillOnce([&](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>();
});
// Advance exactly `kVerifyDeviceRegistrationDelay` so the download procedure
// kicks in. Due to the mock time and the synchronous behavior above of
// DownloadNewKeys(), there is no need for epsilons or additional waiting.
environment.FastForwardBy(base::Seconds(10));
ASSERT_FALSE(download_keys_callback.is_null());
// Mimic a successful request that returns no new keys.
base::HistogramTester histogram_tester;
std::move(download_keys_callback)
.Run(TrustedVaultDownloadKeysStatus::kNoNewKeys, {}, 0);
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultVerifyDeviceRegistrationState",
/*sample=*/TrustedVaultDownloadKeysStatusForUMA::kNoNewKeys,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(
"Sync.TrustedVaultVerifyDeviceRegistrationStateV1",
/*sample=*/TrustedVaultDownloadKeysStatusForUMA::kNoNewKeys,
/*expected_bucket_count=*/1);
}
} // namespace
} // namespace trusted_vault