blob: 62fb344d824360d10f3c17add908be890219a452 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync/driver/sync_service_crypto.h"
#include <list>
#include <map>
#include <utility>
#include "base/callback_helpers.h"
#include "base/observer_list.h"
#include "base/run_loop.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/sync/base/sync_prefs.h"
#include "components/sync/driver/sync_driver_switches.h"
#include "components/sync/driver/trusted_vault_client.h"
#include "components/sync/engine/nigori/key_derivation_params.h"
#include "components/sync/nigori/nigori.h"
#include "components/sync/test/engine/mock_sync_engine.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 syncer {
namespace {
using testing::_;
using testing::Eq;
using testing::Ne;
sync_pb::EncryptedData MakeEncryptedData(
const std::string& passphrase,
const KeyDerivationParams& derivation_params) {
std::unique_ptr<Nigori> nigori =
Nigori::CreateByDerivation(derivation_params, passphrase);
std::string nigori_name;
EXPECT_TRUE(
nigori->Permute(Nigori::Type::Password, kNigoriKeyName, &nigori_name));
const std::string unencrypted = "test";
sync_pb::EncryptedData encrypted;
encrypted.set_key_name(nigori_name);
EXPECT_TRUE(nigori->Encrypt(unencrypted, encrypted.mutable_blob()));
return encrypted;
}
CoreAccountInfo MakeAccountInfoWithGaia(const std::string& gaia) {
CoreAccountInfo result;
result.gaia = gaia;
return result;
}
class MockDelegate : public SyncServiceCrypto::Delegate {
public:
MockDelegate() = default;
~MockDelegate() override = default;
MOCK_METHOD(void, CryptoStateChanged, (), (override));
MOCK_METHOD(void, CryptoRequiredUserActionChanged, (), (override));
MOCK_METHOD(void, ReconfigureDataTypesDueToCrypto, (), (override));
MOCK_METHOD(void,
EncryptionBootstrapTokenChanged,
(const std::string&),
(override));
};
// Object representing a server that contains the authoritative trusted vault
// keys, and TestTrustedVaultClient reads from.
class TestTrustedVaultServer {
public:
TestTrustedVaultServer() = default;
~TestTrustedVaultServer() = default;
void StoreKeysOnServer(const std::string& gaia_id,
const std::vector<std::vector<uint8_t>>& keys) {
gaia_id_to_keys_[gaia_id] = keys;
}
// Mimics a user going through a key-retrieval flow (e.g. reauth) such that
// keys are fetched from the server and cached in |client|.
void MimicKeyRetrievalByUser(const std::string& gaia_id,
TrustedVaultClient* client) {
DCHECK(client);
DCHECK_NE(0U, gaia_id_to_keys_.count(gaia_id))
<< "StoreKeysOnServer() should have been called for " << gaia_id;
client->StoreKeys(gaia_id, gaia_id_to_keys_[gaia_id],
/*last_key_version=*/
static_cast<int>(gaia_id_to_keys_[gaia_id].size()) - 1);
}
// Mimics the server RPC endpoint that allows key rotation.
std::vector<std::vector<uint8_t>> RequestRotatedKeysFromServer(
const std::string& gaia_id,
const std::vector<uint8_t>& key_known_by_client) const {
auto it = gaia_id_to_keys_.find(gaia_id);
if (it == gaia_id_to_keys_.end()) {
return {};
}
const std::vector<std::vector<uint8_t>>& latest_keys = it->second;
if (std::find(latest_keys.begin(), latest_keys.end(),
key_known_by_client) == latest_keys.end()) {
// |key_known_by_client| is invalid or too old: cannot be used to follow
// key rotation.
return {};
}
return latest_keys;
}
private:
std::map<std::string, std::vector<std::vector<uint8_t>>> gaia_id_to_keys_;
};
// Simple in-memory implementation of TrustedVaultClient.
class TestTrustedVaultClient : public TrustedVaultClient {
public:
explicit TestTrustedVaultClient(const TestTrustedVaultServer* server)
: server_(server) {}
~TestTrustedVaultClient() override = default;
// Exposes the total number of calls to FetchKeys().
int fetch_count() const { return fetch_count_; }
// Exposes the total number of calls to MarkKeysAsStale().
bool keys_marked_as_stale_count() const {
return keys_marked_as_stale_count_;
}
// Exposes the total number of calls to the server's RequestKeysFromServer().
int server_request_count() const { return server_request_count_; }
// Exposes the total number of calls to GetIsRecoverabilityDegraded().
int get_is_recoverablity_degraded_call_count() const {
return get_is_recoverablity_degraded_call_count_;
}
// Mimics the completion of the next (FIFO) FetchKeys() request.
bool CompleteFetchKeysRequest() {
if (pending_responses_.empty()) {
return false;
}
base::OnceClosure cb = std::move(pending_responses_.front());
pending_responses_.pop_front();
std::move(cb).Run();
return true;
}
void SetIsRecoverabilityDegraded(bool is_recoverability_degraded) {
is_recoverability_degraded_ = is_recoverability_degraded;
for (Observer& observer : observer_list_) {
observer.OnTrustedVaultRecoverabilityChanged();
}
}
// TrustedVaultClient implementation.
void AddObserver(Observer* observer) override {
observer_list_.AddObserver(observer);
}
void RemoveObserver(Observer* observer) override {
observer_list_.RemoveObserver(observer);
}
void FetchKeys(
const CoreAccountInfo& account_info,
base::OnceCallback<void(const std::vector<std::vector<uint8_t>>&)> cb)
override {
const std::string& gaia_id = account_info.gaia;
++fetch_count_;
CachedKeysPerUser& cached_keys = gaia_id_to_cached_keys_[gaia_id];
// If there are no keys cached, the only way to bootstrap the client is by
// going through a retrieval flow, see MimicKeyRetrievalByUser().
if (cached_keys.keys.empty()) {
pending_responses_.push_back(
base::BindOnce(std::move(cb), std::vector<std::vector<uint8_t>>()));
return;
}
// If the locally cached keys are not marked as stale, return them directly.
if (!cached_keys.marked_as_stale) {
pending_responses_.push_back(
base::BindOnce(std::move(cb), cached_keys.keys));
return;
}
// Fetch keys from the server and cache them.
cached_keys.keys =
server_->RequestRotatedKeysFromServer(gaia_id, cached_keys.keys.back());
cached_keys.marked_as_stale = false;
// Return the newly-cached keys.
pending_responses_.push_back(
base::BindOnce(std::move(cb), cached_keys.keys));
}
// Store keys in the client-side cache, usually retrieved from the server as
// part of the key retrieval process, see MimicKeyRetrievalByUser().
void StoreKeys(const std::string& gaia_id,
const std::vector<std::vector<uint8_t>>& keys,
int last_key_version) override {
CachedKeysPerUser& cached_keys = gaia_id_to_cached_keys_[gaia_id];
cached_keys.keys = keys;
cached_keys.marked_as_stale = false;
for (Observer& observer : observer_list_) {
observer.OnTrustedVaultKeysChanged();
}
}
void MarkKeysAsStale(const CoreAccountInfo& account_info,
base::OnceCallback<void(bool)> cb) override {
const std::string& gaia_id = account_info.gaia;
++keys_marked_as_stale_count_;
CachedKeysPerUser& cached_keys = gaia_id_to_cached_keys_[gaia_id];
if (cached_keys.keys.empty() || cached_keys.marked_as_stale) {
// Nothing changed so report |false|.
std::move(cb).Run(false);
return;
}
// The cache is stale and should be invalidated. Following calls to
// FetchKeys() will read from the server.
cached_keys.marked_as_stale = true;
std::move(cb).Run(true);
}
void GetIsRecoverabilityDegraded(const CoreAccountInfo& account_info,
base::OnceCallback<void(bool)> cb) override {
++get_is_recoverablity_degraded_call_count_;
std::move(cb).Run(is_recoverability_degraded_);
}
void AddTrustedRecoveryMethod(const std::string& gaia_id,
const std::vector<uint8_t>& public_key,
int method_type_hint,
base::OnceClosure cb) override {
// Not relevant in these tests.
std::move(cb).Run();
}
private:
struct CachedKeysPerUser {
bool marked_as_stale = false;
std::vector<std::vector<uint8_t>> keys;
};
const TestTrustedVaultServer* const server_;
std::map<std::string, CachedKeysPerUser> gaia_id_to_cached_keys_;
base::ObserverList<Observer> observer_list_;
int fetch_count_ = 0;
int keys_marked_as_stale_count_ = 0;
int get_is_recoverablity_degraded_call_count_ = 0;
int server_request_count_ = 0;
std::list<base::OnceClosure> pending_responses_;
bool is_recoverability_degraded_ = false;
};
class SyncServiceCryptoTest : public testing::Test {
protected:
// Account used in most tests.
const CoreAccountInfo kSyncingAccount =
MakeAccountInfoWithGaia("syncingaccount");
// Initial trusted vault keys stored on the server |TestTrustedVaultServer|
// for |kSyncingAccount|.
const std::vector<std::vector<uint8_t>> kInitialTrustedVaultKeys = {
{0, 1, 2, 3, 4}};
SyncServiceCryptoTest()
: trusted_vault_client_(&trusted_vault_server_),
crypto_(&delegate_, &trusted_vault_client_) {
trusted_vault_server_.StoreKeysOnServer(kSyncingAccount.gaia,
kInitialTrustedVaultKeys);
}
~SyncServiceCryptoTest() override = default;
bool VerifyAndClearExpectations() {
return testing::Mock::VerifyAndClearExpectations(&delegate_) &&
testing::Mock::VerifyAndClearExpectations(&trusted_vault_client_) &&
testing::Mock::VerifyAndClearExpectations(&engine_);
}
void MimicKeyRetrievalByUser() {
trusted_vault_server_.MimicKeyRetrievalByUser(kSyncingAccount.gaia,
&trusted_vault_client_);
}
testing::NiceMock<MockDelegate> delegate_;
TestTrustedVaultServer trusted_vault_server_;
TestTrustedVaultClient trusted_vault_client_;
testing::NiceMock<MockSyncEngine> engine_;
SyncServiceCrypto crypto_;
};
// Happy case where no user action is required upon startup.
TEST_F(SyncServiceCryptoTest, ShouldRequireNoUserAction) {
crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
EXPECT_FALSE(crypto_.IsPassphraseRequired());
EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
EXPECT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
EXPECT_FALSE(crypto_.IsTrustedVaultRecoverabilityDegraded());
}
TEST_F(SyncServiceCryptoTest, ShouldSetUpNewCustomPassphrase) {
const std::string kTestPassphrase = "somepassphrase";
crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
ASSERT_FALSE(crypto_.IsPassphraseRequired());
ASSERT_FALSE(crypto_.IsUsingExplicitPassphrase());
ASSERT_FALSE(crypto_.IsEncryptEverythingEnabled());
ASSERT_THAT(crypto_.GetPassphraseType(),
Ne(PassphraseType::kCustomPassphrase));
EXPECT_CALL(engine_, SetEncryptionPassphrase(kTestPassphrase));
crypto_.SetEncryptionPassphrase(kTestPassphrase);
// Mimic completion of the procedure in the sync engine.
EXPECT_CALL(delegate_, CryptoStateChanged());
crypto_.OnPassphraseTypeChanged(PassphraseType::kCustomPassphrase,
base::Time::Now());
// The current implementation notifies observers again upon
// crypto_.OnEncryptedTypesChanged(). This may change in the future.
EXPECT_CALL(delegate_, CryptoStateChanged());
crypto_.OnEncryptedTypesChanged(syncer::EncryptableUserTypes(),
/*encrypt_everything=*/true);
EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
crypto_.OnPassphraseAccepted();
EXPECT_FALSE(crypto_.IsPassphraseRequired());
EXPECT_TRUE(crypto_.IsEncryptEverythingEnabled());
ASSERT_THAT(crypto_.GetPassphraseType(),
Eq(PassphraseType::kCustomPassphrase));
EXPECT_TRUE(crypto_.IsUsingExplicitPassphrase());
}
TEST_F(SyncServiceCryptoTest, ShouldExposePassphraseRequired) {
const std::string kTestPassphrase = "somepassphrase";
crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
ASSERT_FALSE(crypto_.IsPassphraseRequired());
ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(0));
// Mimic the engine determining that a passphrase is required.
EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
crypto_.OnPassphraseRequired(
KeyDerivationParams::CreateForPbkdf2(),
MakeEncryptedData(kTestPassphrase,
KeyDerivationParams::CreateForPbkdf2()));
EXPECT_TRUE(crypto_.IsPassphraseRequired());
VerifyAndClearExpectations();
// Entering the wrong passphrase should be rejected.
EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto()).Times(0);
EXPECT_CALL(engine_, SetDecryptionPassphrase).Times(0);
EXPECT_FALSE(crypto_.SetDecryptionPassphrase("wrongpassphrase"));
EXPECT_TRUE(crypto_.IsPassphraseRequired());
// Entering the correct passphrase should be accepted.
EXPECT_CALL(engine_, SetDecryptionPassphrase(kTestPassphrase))
.WillOnce([&](const std::string&) { crypto_.OnPassphraseAccepted(); });
// The current implementation issues two reconfigurations: one immediately
// after checking the passphrase in the UI thread and a second time later when
// the engine confirms with OnPassphraseAccepted().
EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto()).Times(2);
EXPECT_TRUE(crypto_.SetDecryptionPassphrase(kTestPassphrase));
EXPECT_FALSE(crypto_.IsPassphraseRequired());
}
TEST_F(SyncServiceCryptoTest,
ShouldReadValidTrustedVaultKeysFromClientBeforeInitialization) {
// Cache |kInitialTrustedVaultKeys| into |trusted_vault_client_| prior to
// engine initialization.
MimicKeyRetrievalByUser();
EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto()).Times(0);
ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
// OnTrustedVaultKeyRequired() called during initialization of the sync
// engine (i.e. before SetSyncEngine()).
crypto_.OnTrustedVaultKeyRequired();
// Trusted vault keys should be fetched only after the engine initialization
// is completed.
ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(0));
crypto_.SetSyncEngine(kSyncingAccount, &engine_);
// While there is an ongoing fetch, there should be no user action required.
EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
base::OnceClosure add_keys_cb;
EXPECT_CALL(engine_,
AddTrustedVaultDecryptionKeys(kInitialTrustedVaultKeys, _))
.WillOnce(
[&](const std::vector<std::vector<uint8_t>>& keys,
base::OnceClosure done_cb) { add_keys_cb = std::move(done_cb); });
// Mimic completion of the fetch.
ASSERT_TRUE(trusted_vault_client_.CompleteFetchKeysRequest());
ASSERT_TRUE(add_keys_cb);
EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
// Mimic completion of the engine.
EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
crypto_.OnTrustedVaultKeyAccepted();
std::move(add_keys_cb).Run();
EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
EXPECT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(0));
EXPECT_THAT(trusted_vault_client_.server_request_count(), Eq(0));
}
TEST_F(SyncServiceCryptoTest,
ShouldReadValidTrustedVaultKeysFromClientAfterInitialization) {
// Cache |kInitialTrustedVaultKeys| into |trusted_vault_client_| prior to
// engine initialization.
MimicKeyRetrievalByUser();
EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto()).Times(0);
ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
// Mimic the initialization of the sync engine, without trusted vault keys
// being required.
crypto_.SetSyncEngine(kSyncingAccount, &engine_);
ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(0));
// Later on, mimic trusted vault keys being required (e.g. remote Nigori
// update), which should trigger a fetch.
crypto_.OnTrustedVaultKeyRequired();
EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
// While there is an ongoing fetch, there should be no user action required.
EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
base::OnceClosure add_keys_cb;
EXPECT_CALL(engine_,
AddTrustedVaultDecryptionKeys(kInitialTrustedVaultKeys, _))
.WillOnce(
[&](const std::vector<std::vector<uint8_t>>& keys,
base::OnceClosure done_cb) { add_keys_cb = std::move(done_cb); });
// Mimic completion of the fetch.
ASSERT_TRUE(trusted_vault_client_.CompleteFetchKeysRequest());
ASSERT_TRUE(add_keys_cb);
EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
// Mimic completion of the engine.
EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
crypto_.OnTrustedVaultKeyAccepted();
std::move(add_keys_cb).Run();
EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
EXPECT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(0));
EXPECT_THAT(trusted_vault_client_.server_request_count(), Eq(0));
}
TEST_F(SyncServiceCryptoTest,
ShouldReadNoTrustedVaultKeysFromClientAfterInitialization) {
EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto()).Times(0);
EXPECT_CALL(engine_, AddTrustedVaultDecryptionKeys).Times(0);
ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
// Mimic the initialization of the sync engine, without trusted vault keys
// being required.
crypto_.SetSyncEngine(kSyncingAccount, &engine_);
ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(0));
ASSERT_THAT(trusted_vault_client_.server_request_count(), Eq(0));
// Later on, mimic trusted vault keys being required (e.g. remote Nigori
// update), which should trigger a fetch.
crypto_.OnTrustedVaultKeyRequired();
EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
// While there is an ongoing fetch, there should be no user action required.
ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
// Mimic completion of the fetch, which should lead to a reconfiguration.
EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
ASSERT_TRUE(trusted_vault_client_.CompleteFetchKeysRequest());
EXPECT_TRUE(crypto_.IsTrustedVaultKeyRequired());
EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
EXPECT_THAT(trusted_vault_client_.server_request_count(), Eq(0));
EXPECT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(0));
}
TEST_F(SyncServiceCryptoTest, ShouldReadInvalidTrustedVaultKeysFromClient) {
// Cache |kInitialTrustedVaultKeys| into |trusted_vault_client_| prior to
// engine initialization. In this test, |kInitialTrustedVaultKeys| does not
// match the Nigori keys (i.e. the engine continues to think trusted vault
// keys are required).
MimicKeyRetrievalByUser();
base::OnceClosure add_keys_cb;
ON_CALL(engine_, AddTrustedVaultDecryptionKeys)
.WillByDefault(
[&](const std::vector<std::vector<uint8_t>>& keys,
base::OnceClosure done_cb) { add_keys_cb = std::move(done_cb); });
ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
// Mimic the initialization of the sync engine, without trusted vault keys
// being required.
crypto_.SetSyncEngine(kSyncingAccount, &engine_);
ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(0));
ASSERT_THAT(trusted_vault_client_.server_request_count(), Eq(0));
// Later on, mimic trusted vault keys being required (e.g. remote Nigori
// update), which should trigger a fetch.
crypto_.OnTrustedVaultKeyRequired();
EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
// While there is an ongoing fetch, there should be no user action required.
EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
// Mimic completion of the client.
EXPECT_CALL(engine_,
AddTrustedVaultDecryptionKeys(kInitialTrustedVaultKeys, _));
ASSERT_TRUE(trusted_vault_client_.CompleteFetchKeysRequest());
ASSERT_TRUE(add_keys_cb);
EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
// Mimic completion of the engine, without OnTrustedVaultKeyAccepted().
std::move(add_keys_cb).Run();
// The keys should be marked as stale, and a second fetch attempt started.
EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
EXPECT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(1));
EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(2));
// Mimic completion of the client for the second pass.
EXPECT_CALL(engine_,
AddTrustedVaultDecryptionKeys(kInitialTrustedVaultKeys, _));
ASSERT_TRUE(trusted_vault_client_.CompleteFetchKeysRequest());
ASSERT_TRUE(add_keys_cb);
// Mimic completion of the engine, without OnTrustedVaultKeyAccepted(), for
// the second pass.
EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
std::move(add_keys_cb).Run();
EXPECT_TRUE(crypto_.IsTrustedVaultKeyRequired());
EXPECT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(1));
EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(2));
}
// Similar to ShouldReadInvalidTrustedVaultKeysFromClient but in this case the
// client is able to follow a key rotation as part of the second fetch attempt.
TEST_F(SyncServiceCryptoTest, ShouldFollowKeyRotationDueToSecondFetch) {
const std::vector<std::vector<uint8_t>> kRotatedKeys = {
kInitialTrustedVaultKeys[0], {2, 3, 4, 5}};
// Cache |kInitialTrustedVaultKeys| into |trusted_vault_client_| prior to
// engine initialization. In this test, |kInitialTrustedVaultKeys| does not
// match the Nigori keys (i.e. the engine continues to think trusted vault
// keys are required until |kRotatedKeys| are provided).
MimicKeyRetrievalByUser();
// Mimic server-side key rotation which the keys, in a way that the rotated
// keys are a continuation of kInitialTrustedVaultKeys, such that
// TestTrustedVaultServer will allow the client to silently follow key
// rotation.
trusted_vault_server_.StoreKeysOnServer(kSyncingAccount.gaia, kRotatedKeys);
// The engine replies with OnTrustedVaultKeyAccepted() only if |kRotatedKeys|
// are provided.
ON_CALL(engine_, AddTrustedVaultDecryptionKeys)
.WillByDefault([&](const std::vector<std::vector<uint8_t>>& keys,
base::OnceClosure done_cb) {
if (keys == kRotatedKeys) {
crypto_.OnTrustedVaultKeyAccepted();
}
std::move(done_cb).Run();
});
// Mimic initialization of the engine where trusted vault keys are needed and
// |kInitialTrustedVaultKeys| are fetched as part of the first fetch.
crypto_.SetSyncEngine(kSyncingAccount, &engine_);
crypto_.OnTrustedVaultKeyRequired();
ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
// While there is an ongoing fetch (first attempt), there should be no user
// action required.
ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
// The keys fetched in the first attempt (|kInitialTrustedVaultKeys|) are
// insufficient and should be marked as stale. In addition, a second fetch
// should be triggered.
ASSERT_TRUE(trusted_vault_client_.CompleteFetchKeysRequest());
ASSERT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(1));
ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(2));
// While there is an ongoing fetch (second attempt), there should be no user
// action required.
ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
// Because of |kRotatedKeys| is a continuation of |kInitialTrustedVaultKeys|,
// TrustedVaultServer should successfully deliver the new keys |kRotatedKeys|
// to the client.
EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
ASSERT_TRUE(trusted_vault_client_.CompleteFetchKeysRequest());
EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
ASSERT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(1));
}
// Similar to ShouldReadInvalidTrustedVaultKeysFromClient: the vault
// initially has no valid keys, leading to IsTrustedVaultKeyRequired().
// Later, the vault gets populated with the keys, which should trigger
// a fetch and eventually resolve the encryption issue.
TEST_F(SyncServiceCryptoTest, ShouldRefetchTrustedVaultKeysWhenChangeObserved) {
const std::vector<std::vector<uint8_t>> kNewKeys = {{2, 3, 4, 5}};
// Cache |kInitialTrustedVaultKeys| into |trusted_vault_client_| prior to
// engine initialization. In this test, |kInitialTrustedVaultKeys| does not
// match the Nigori keys (i.e. the engine continues to think trusted vault
// keys are required until |kNewKeys| are provided).
MimicKeyRetrievalByUser();
// The engine replies with OnTrustedVaultKeyAccepted() only if |kNewKeys| are
// provided.
ON_CALL(engine_, AddTrustedVaultDecryptionKeys)
.WillByDefault([&](const std::vector<std::vector<uint8_t>>& keys,
base::OnceClosure done_cb) {
if (keys == kNewKeys) {
crypto_.OnTrustedVaultKeyAccepted();
}
std::move(done_cb).Run();
});
// Mimic initialization of the engine where trusted vault keys are needed and
// |kInitialTrustedVaultKeys| are fetched, which are insufficient, and hence
// IsTrustedVaultKeyRequired() is exposed.
crypto_.SetSyncEngine(kSyncingAccount, &engine_);
crypto_.OnTrustedVaultKeyRequired();
ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
ASSERT_TRUE(trusted_vault_client_.CompleteFetchKeysRequest());
// Note that this initial attempt involves two fetches, where both return
// |kInitialTrustedVaultKeys|.
ASSERT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(1));
ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(2));
ASSERT_TRUE(trusted_vault_client_.CompleteFetchKeysRequest());
ASSERT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(1));
ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequired());
// Mimic server-side key reset and a new retrieval.
trusted_vault_server_.StoreKeysOnServer(kSyncingAccount.gaia, kNewKeys);
MimicKeyRetrievalByUser();
// Key retrieval should have initiated a third fetch.
EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(3));
EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
EXPECT_TRUE(trusted_vault_client_.CompleteFetchKeysRequest());
EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
EXPECT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(1));
}
// Same as above but the new keys become available during an ongoing FetchKeys()
// request.
TEST_F(SyncServiceCryptoTest,
ShouldDeferTrustedVaultKeyFetchingWhenChangeObservedWhileOngoingFetch) {
const std::vector<std::vector<uint8_t>> kNewKeys = {{2, 3, 4, 5}};
// Cache |kInitialTrustedVaultKeys| into |trusted_vault_client_| prior to
// engine initialization. In this test, |kInitialTrustedVaultKeys| does not
// match the Nigori keys (i.e. the engine continues to think trusted vault
// keys are required until |kNewKeys| are provided).
MimicKeyRetrievalByUser();
// The engine replies with OnTrustedVaultKeyAccepted() only if |kNewKeys| are
// provided.
ON_CALL(engine_, AddTrustedVaultDecryptionKeys)
.WillByDefault([&](const std::vector<std::vector<uint8_t>>& keys,
base::OnceClosure done_cb) {
if (keys == kNewKeys) {
crypto_.OnTrustedVaultKeyAccepted();
}
std::move(done_cb).Run();
});
// Mimic initialization of the engine where trusted vault keys are needed and
// |kInitialTrustedVaultKeys| are in the process of being fetched.
crypto_.SetSyncEngine(kSyncingAccount, &engine_);
crypto_.OnTrustedVaultKeyRequired();
ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
// While there is an ongoing fetch, mimic server-side key reset and a new
// retrieval.
trusted_vault_server_.StoreKeysOnServer(kSyncingAccount.gaia, kNewKeys);
MimicKeyRetrievalByUser();
// Because there's already an ongoing fetch, a second one should not have been
// triggered yet and should be deferred instead.
EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
// As soon as the first fetch completes, the second one (deferred) should be
// started.
EXPECT_TRUE(trusted_vault_client_.CompleteFetchKeysRequest());
EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(2));
EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
// The completion of the second fetch should resolve the encryption issue.
EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
EXPECT_TRUE(trusted_vault_client_.CompleteFetchKeysRequest());
EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(2));
EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
}
// The engine gets initialized and the vault initially has insufficient keys,
// leading to IsTrustedVaultKeyRequired(). Later, keys are added to the vault
// *twice*, where the later event should be handled as a deferred fetch.
TEST_F(
SyncServiceCryptoTest,
ShouldDeferTrustedVaultKeyFetchingWhenChangeObservedWhileOngoingRefetch) {
const std::vector<std::vector<uint8_t>> kLatestKeys = {{2, 2, 2, 2, 2}};
// The engine replies with OnTrustedVaultKeyAccepted() only if |kLatestKeys|
// are provided.
ON_CALL(engine_, AddTrustedVaultDecryptionKeys)
.WillByDefault([&](const std::vector<std::vector<uint8_t>>& keys,
base::OnceClosure done_cb) {
if (keys == kLatestKeys) {
crypto_.OnTrustedVaultKeyAccepted();
}
std::move(done_cb).Run();
});
// Mimic initialization of the engine where trusted vault keys are needed and
// no keys are fetched from the client, hence IsTrustedVaultKeyRequired() is
// exposed.
crypto_.SetSyncEngine(kSyncingAccount, &engine_);
crypto_.OnTrustedVaultKeyRequired();
ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
ASSERT_TRUE(trusted_vault_client_.CompleteFetchKeysRequest());
ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
ASSERT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(0));
ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequired());
// Mimic retrieval of keys, leading to a second fetch that returns
// |kInitialTrustedVaultKeys|, which are insufficient and should be marked as
// stale as soon as the fetch completes (later below).
MimicKeyRetrievalByUser();
EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(2));
// While the second fetch is ongoing, mimic additional keys being retrieved.
// Because there's already an ongoing fetch, a third one should not have been
// triggered yet and should be deferred instead.
trusted_vault_server_.StoreKeysOnServer(kSyncingAccount.gaia, kLatestKeys);
MimicKeyRetrievalByUser();
EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(2));
// As soon as the second fetch completes, the keys should be marked as stale
// and a third fetch attempt triggered.
EXPECT_TRUE(trusted_vault_client_.CompleteFetchKeysRequest());
EXPECT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(1));
EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(3));
// As soon as the third fetch completes, the fourth one (deferred) should be
// started.
EXPECT_TRUE(trusted_vault_client_.CompleteFetchKeysRequest());
EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(3));
}
TEST_F(SyncServiceCryptoTest, ShouldNotGetRecoverabilityIfFeatureDisabled) {
trusted_vault_client_.SetIsRecoverabilityDegraded(true);
crypto_.OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase,
base::Time::Now());
crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
ASSERT_THAT(crypto_.GetPassphraseType(),
Eq(PassphraseType::kTrustedVaultPassphrase));
ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
EXPECT_THAT(trusted_vault_client_.get_is_recoverablity_degraded_call_count(),
Eq(0));
EXPECT_FALSE(crypto_.IsTrustedVaultRecoverabilityDegraded());
}
TEST_F(SyncServiceCryptoTest,
ShouldNotReportDegradedRecoverabilityUponInitialization) {
base::test::ScopedFeatureList override_features;
override_features.InitAndEnableFeature(
switches::kSyncTrustedVaultPassphraseRecovery);
trusted_vault_client_.SetIsRecoverabilityDegraded(false);
crypto_.OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase,
base::Time::Now());
crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
ASSERT_THAT(crypto_.GetPassphraseType(),
Eq(PassphraseType::kTrustedVaultPassphrase));
ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
EXPECT_FALSE(crypto_.IsTrustedVaultRecoverabilityDegraded());
}
TEST_F(SyncServiceCryptoTest,
ShouldReportDegradedRecoverabilityUponInitialization) {
base::test::ScopedFeatureList override_features;
override_features.InitAndEnableFeature(
switches::kSyncTrustedVaultPassphraseRecovery);
trusted_vault_client_.SetIsRecoverabilityDegraded(true);
crypto_.OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase,
base::Time::Now());
crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
ASSERT_THAT(crypto_.GetPassphraseType(),
Eq(PassphraseType::kTrustedVaultPassphrase));
ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
EXPECT_TRUE(crypto_.IsTrustedVaultRecoverabilityDegraded());
}
TEST_F(SyncServiceCryptoTest, ShouldReportDegradedRecoverabilityUponChange) {
base::test::ScopedFeatureList override_features;
override_features.InitAndEnableFeature(
switches::kSyncTrustedVaultPassphraseRecovery);
trusted_vault_client_.SetIsRecoverabilityDegraded(false);
crypto_.OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase,
base::Time::Now());
crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
ASSERT_THAT(crypto_.GetPassphraseType(),
Eq(PassphraseType::kTrustedVaultPassphrase));
ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
ASSERT_FALSE(crypto_.IsTrustedVaultRecoverabilityDegraded());
// Changing the state notifies observers and should lead to a change in
// IsTrustedVaultRecoverabilityDegraded().
EXPECT_CALL(delegate_, CryptoStateChanged());
trusted_vault_client_.SetIsRecoverabilityDegraded(true);
EXPECT_TRUE(crypto_.IsTrustedVaultRecoverabilityDegraded());
}
TEST_F(SyncServiceCryptoTest,
ShouldStopReportingDegradedRecoverabilityUponChange) {
base::test::ScopedFeatureList override_features;
override_features.InitAndEnableFeature(
switches::kSyncTrustedVaultPassphraseRecovery);
trusted_vault_client_.SetIsRecoverabilityDegraded(true);
crypto_.OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase,
base::Time::Now());
crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
ASSERT_THAT(crypto_.GetPassphraseType(),
Eq(PassphraseType::kTrustedVaultPassphrase));
ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
ASSERT_TRUE(crypto_.IsTrustedVaultRecoverabilityDegraded());
// Changing the state notifies observers and should lead to a change in
// IsTrustedVaultRecoverabilityDegraded().
EXPECT_CALL(delegate_, CryptoStateChanged());
trusted_vault_client_.SetIsRecoverabilityDegraded(false);
EXPECT_FALSE(crypto_.IsTrustedVaultRecoverabilityDegraded());
}
TEST_F(SyncServiceCryptoTest, ShouldReportDegradedRecoverabilityUponRetrieval) {
base::test::ScopedFeatureList override_features;
override_features.InitAndEnableFeature(
switches::kSyncTrustedVaultPassphraseRecovery);
trusted_vault_client_.SetIsRecoverabilityDegraded(true);
// Mimic startup with trusted vault keys being required.
crypto_.OnTrustedVaultKeyRequired();
crypto_.OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase,
base::Time::Now());
crypto_.SetSyncEngine(kSyncingAccount, &engine_);
ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
ASSERT_FALSE(crypto_.IsTrustedVaultRecoverabilityDegraded());
// Complete the fetching of initial keys (no keys) from the client.
ASSERT_TRUE(trusted_vault_client_.CompleteFetchKeysRequest());
ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequired());
ASSERT_FALSE(crypto_.IsTrustedVaultRecoverabilityDegraded());
// Mimic a successful key retrieval.
ON_CALL(engine_, AddTrustedVaultDecryptionKeys)
.WillByDefault([&](const std::vector<std::vector<uint8_t>>& keys,
base::OnceClosure done_cb) {
crypto_.OnTrustedVaultKeyAccepted();
std::move(done_cb).Run();
});
MimicKeyRetrievalByUser();
ASSERT_TRUE(trusted_vault_client_.CompleteFetchKeysRequest());
ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
// The recoverability state should be exposed.
EXPECT_TRUE(crypto_.IsTrustedVaultRecoverabilityDegraded());
}
TEST_F(SyncServiceCryptoTest,
ShouldClearDegradedRecoverabilityIfCustomPassphraseIsSet) {
const std::string kTestPassphrase = "somepassphrase";
base::test::ScopedFeatureList override_features;
override_features.InitAndEnableFeature(
switches::kSyncTrustedVaultPassphraseRecovery);
// Mimic a browser startup in |kTrustedVaultPassphrase| with no additional
// keys required and degraded recoverability state.
trusted_vault_client_.SetIsRecoverabilityDegraded(true);
crypto_.OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase,
base::Time::Now());
crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
ASSERT_THAT(crypto_.GetPassphraseType(),
Eq(PassphraseType::kTrustedVaultPassphrase));
ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
ASSERT_FALSE(crypto_.IsPassphraseRequired());
ASSERT_TRUE(crypto_.IsTrustedVaultRecoverabilityDegraded());
// Mimic the user setting up a new custom passphrase.
crypto_.SetEncryptionPassphrase(kTestPassphrase);
// Mimic completion of the procedure in the sync engine.
EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
EXPECT_CALL(delegate_, CryptoStateChanged());
crypto_.OnPassphraseTypeChanged(PassphraseType::kCustomPassphrase,
base::Time::Now());
crypto_.OnPassphraseAccepted();
ASSERT_THAT(crypto_.GetPassphraseType(),
Eq(PassphraseType::kCustomPassphrase));
// Recoverability should no longer be considered degraded.
EXPECT_FALSE(crypto_.IsTrustedVaultRecoverabilityDegraded());
}
} // namespace
} // namespace syncer