Commit 17d90e66 authored by Mikel Astiz's avatar Mikel Astiz Committed by Commit Bot

Implement file-based TrustedVaultClient

This adds basic support for persisting encryption keys on disk using a
dedicated file (intentionally outside sync's directory), containing an
encrypted protocol buffer as introduced in this very patch.

Bug: 1012660
Change-Id: Ie9fedae86799f412703914dbfec53e3d0b85a2c4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1865324
Commit-Queue: Mikel Astiz <mastiz@chromium.org>
Reviewed-by: default avatarMarc Treib <treib@chromium.org>
Cr-Commit-Position: refs/heads/master@{#707986}
parent 7777033e
......@@ -68,6 +68,7 @@
#include "components/sync/base/pref_names.h"
#include "components/sync/base/report_unrecoverable_error.h"
#include "components/sync/base/sync_base_switches.h"
#include "components/sync/driver/file_based_trusted_vault_client.h"
#include "components/sync/driver/model_type_controller.h"
#include "components/sync/driver/sync_api_component_factory.h"
#include "components/sync/driver/sync_driver_switches.h"
......@@ -137,6 +138,11 @@ namespace browser_sync {
namespace {
#if !defined(OS_ANDROID)
const base::FilePath::CharType kTrustedVaultFilename[] =
FILE_PATH_LITERAL("Trusted Vault");
#endif // !defined(OS_ANDROID)
#if defined(OS_WIN)
const base::FilePath::CharType kLoopbackServerBackendFilename[] =
FILE_PATH_LITERAL("profile.pb");
......@@ -196,6 +202,12 @@ ChromeSyncClient::ChromeSyncClient(Profile* profile) : profile_(profile) {
account_web_data_service_, profile_password_store_,
account_password_store_,
BookmarkSyncServiceFactory::GetForProfile(profile_));
// TODO(crbug.com/1012659): Instantiate an Android-specific client.
#if !defined(OS_ANDROID)
trusted_vault_client_ = std::make_unique<syncer::FileBasedTrustedVaultClient>(
profile_->GetPath().Append(kTrustedVaultFilename));
#endif // !defined(OS_ANDROID)
}
ChromeSyncClient::~ChromeSyncClient() {}
......@@ -417,14 +429,7 @@ BookmarkUndoService* ChromeSyncClient::GetBookmarkUndoService() {
}
syncer::TrustedVaultClient* ChromeSyncClient::GetTrustedVaultClient() {
#if defined(OS_ANDROID)
// TODO(crbug.com/1012659): Instantiate a client for Android.
NOTIMPLEMENTED();
#else
// TODO(crbug.com/1012660): Instantiate a generic client for other platforms.
NOTIMPLEMENTED();
#endif // defined(OS_ANDROID)
return nullptr;
return trusted_vault_client_.get();
}
invalidation::InvalidationService* ChromeSyncClient::GetInvalidationService() {
......
......@@ -73,6 +73,8 @@ class ChromeSyncClient : public browser_sync::BrowserSyncClient {
std::unique_ptr<browser_sync::ProfileSyncComponentsFactoryImpl>
component_factory_;
std::unique_ptr<syncer::TrustedVaultClient> trusted_vault_client_;
// Members that must be fetched on the UI thread but accessed on their
// respective backend threads.
scoped_refptr<autofill::AutofillWebDataService> profile_web_data_service_;
......
......@@ -365,7 +365,7 @@ class SingleClientNigoriWithWebApiTest : public SyncTest {
};
IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiTest,
ShouldAcceptEncryptionKeysFromTheWeb) {
ShouldAcceptEncryptionKeysFromTheWebWhileSignedIn) {
const std::string kTestEncryptionKey = "testpassphrase1";
const GURL retrieval_url =
......@@ -398,6 +398,46 @@ IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiTest,
.Wait());
}
IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiTest,
PRE_ShouldAcceptEncryptionKeysFromTheWebBeforeSignIn) {
const std::string kTestEncryptionKey = "testpassphrase1";
const GURL retrieval_url =
GetTrustedVaultRetrievalURL(*embedded_test_server(), kTestEncryptionKey);
ASSERT_TRUE(SetupClients());
// Mimic opening a web page where the user can interact with the retrieval
// flow, while the user is signed out.
ui_test_utils::NavigateToURLWithDisposition(
GetBrowser(0), retrieval_url, WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_NONE);
// Wait until the title changes to "OK" via Javascript, which indicates
// completion.
PageTitleChecker title_checker(
/*expected_title=*/"OK",
GetBrowser(0)->tab_strip_model()->GetActiveWebContents());
EXPECT_TRUE(title_checker.Wait());
}
IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiTest,
ShouldAcceptEncryptionKeysFromTheWebBeforeSignIn) {
const std::string kTestEncryptionKey = "testpassphrase1";
// Mimic the account being already using a trusted vault passphrase.
encryption_helper::SetNigoriInFakeServer(
GetFakeServer(), BuildTrustedVaultNigoriSpecifics({kTestEncryptionKey}));
// Sign in and start sync.
EXPECT_TRUE(SetupSync());
ASSERT_EQ(syncer::PassphraseType::kTrustedVaultPassphrase,
GetSyncService(0)->GetUserSettings()->GetPassphraseType());
EXPECT_FALSE(GetSyncService(0)
->GetUserSettings()
->IsTrustedVaultKeyRequiredForPreferredDataTypes());
}
// Same as SingleClientNigoriWithWebApiTest but does NOT override
// switches::kGaiaUrl, which means the embedded test server gets treated as
// untrusted origin.
......
......@@ -584,6 +584,7 @@ source_set("unit_tests") {
"driver/async_directory_type_controller_unittest.cc",
"driver/backend_migrator_unittest.cc",
"driver/data_type_manager_impl_unittest.cc",
"driver/file_based_trusted_vault_client_unittest.cc",
"driver/generic_change_processor_unittest.cc",
"driver/glue/sync_engine_impl_unittest.cc",
"driver/model_association_manager_unittest.cc",
......@@ -688,6 +689,7 @@ source_set("unit_tests") {
"//base",
"//base/test:test_support",
"//components/invalidation/impl",
"//components/os_crypt",
"//components/os_crypt:test_support",
"//components/prefs:test_support",
"//components/signin/public/base:test_support",
......
......@@ -33,6 +33,8 @@ jumbo_static_library("driver") {
"data_type_status_table.h",
"directory_data_type_controller.cc",
"directory_data_type_controller.h",
"file_based_trusted_vault_client.cc",
"file_based_trusted_vault_client.h",
"generic_change_processor.cc",
"generic_change_processor.h",
"generic_change_processor_factory.cc",
......@@ -108,6 +110,7 @@ jumbo_static_library("driver") {
"//components/data_use_measurement/core",
"//components/invalidation/impl:feature_list",
"//components/keyed_service/core",
"//components/os_crypt",
"//components/prefs",
"//components/signin/public/identity_manager",
"//components/version_info",
......
// 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/file_based_trusted_vault_client.h"
#include <utility>
#include "base/bind_helpers.h"
#include "base/files/file_util.h"
#include "base/files/important_file_writer.h"
#include "base/memory/ref_counted.h"
#include "base/sequenced_task_runner.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/task_runner_util.h"
#include "components/os_crypt/os_crypt.h"
#include "components/sync/protocol/local_trusted_vault.pb.h"
namespace syncer {
namespace {
constexpr base::TaskTraits kTaskTraits = {
base::ThreadPool(), base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN};
sync_pb::LocalTrustedVault ReadEncryptedFile(const base::FilePath& file_path) {
sync_pb::LocalTrustedVault proto;
std::string ciphertext;
std::string decrypted_content;
if (base::ReadFileToString(file_path, &ciphertext) &&
OSCrypt::DecryptString(ciphertext, &decrypted_content)) {
proto.ParseFromString(decrypted_content);
}
return proto;
}
void WriteToDisk(const sync_pb::LocalTrustedVault& data,
const base::FilePath& file_path) {
std::string encrypted_data;
if (!OSCrypt::EncryptString(data.SerializeAsString(), &encrypted_data)) {
DLOG(ERROR) << "Failed to encrypt trusted vault file.";
return;
}
if (!base::ImportantFileWriter::WriteFileAtomically(file_path,
encrypted_data)) {
DLOG(ERROR) << "Failed to write trusted vault file.";
}
}
} // namespace
//
class FileBasedTrustedVaultClient::Backend
: public base::RefCountedThreadSafe<Backend> {
public:
explicit Backend(const base::FilePath& file_path) : file_path_(file_path) {}
void ReadDataFromDisk() { data_ = ReadEncryptedFile(file_path_); }
std::vector<std::string> FetchKeys(const std::string& gaia_id) {
const sync_pb::LocalTrustedVaultPerUser* per_user_vault =
FindUserVault(gaia_id);
std::vector<std::string> keys;
if (per_user_vault) {
for (const sync_pb::LocalTrustedVaultKey& key : per_user_vault->key()) {
keys.push_back(key.key_material());
}
}
return keys;
}
void StoreKeys(const std::string& gaia_id,
const std::vector<std::string>& keys) {
// Find or create user for |gaid_id|.
sync_pb::LocalTrustedVaultPerUser* per_user_vault = FindUserVault(gaia_id);
if (!per_user_vault) {
per_user_vault = data_.add_user();
per_user_vault->set_gaia_id(gaia_id);
}
// Replace all keys.
per_user_vault->clear_key();
for (const std::string& key : keys) {
per_user_vault->add_key()->set_key_material(key);
}
WriteToDisk(data_, file_path_);
}
private:
friend class base::RefCountedThreadSafe<Backend>;
~Backend() = default;
// Finds the per-user vault in |data_| for |gaia_id|. Returns null if not
// found.
sync_pb::LocalTrustedVaultPerUser* FindUserVault(const std::string& gaia_id) {
for (int i = 0; i < data_.user_size(); ++i) {
if (data_.user(i).gaia_id() == gaia_id) {
return data_.mutable_user(i);
}
}
return nullptr;
}
const base::FilePath file_path_;
sync_pb::LocalTrustedVault data_;
DISALLOW_COPY_AND_ASSIGN(Backend);
};
FileBasedTrustedVaultClient::FileBasedTrustedVaultClient(
const base::FilePath& file_path)
: file_path_(file_path),
backend_task_runner_(base::CreateSequencedTaskRunner(kTaskTraits)) {}
FileBasedTrustedVaultClient::~FileBasedTrustedVaultClient() = default;
void FileBasedTrustedVaultClient::FetchKeys(
const std::string& gaia_id,
base::OnceCallback<void(const std::vector<std::string>&)> cb) {
TriggerLazyInitializationIfNeeded();
base::PostTaskAndReplyWithResult(
backend_task_runner_.get(), FROM_HERE,
base::BindOnce(&Backend::FetchKeys, backend_, gaia_id), std::move(cb));
}
void FileBasedTrustedVaultClient::StoreKeys(
const std::string& gaia_id,
const std::vector<std::string>& keys) {
TriggerLazyInitializationIfNeeded();
backend_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Backend::StoreKeys, backend_, gaia_id, keys));
}
void FileBasedTrustedVaultClient::WaitForFlushForTesting(
base::OnceClosure cb) const {
backend_task_runner_->PostTaskAndReply(FROM_HERE, base::DoNothing(),
std::move(cb));
}
void FileBasedTrustedVaultClient::TriggerLazyInitializationIfNeeded() {
if (backend_) {
return;
}
backend_ = base::MakeRefCounted<Backend>(file_path_);
backend_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Backend::ReadDataFromDisk, backend_));
}
bool FileBasedTrustedVaultClient::IsInitializationTriggeredForTesting() const {
return backend_ != nullptr;
}
} // namespace syncer
// 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.
#ifndef COMPONENTS_SYNC_DRIVER_FILE_BASED_TRUSTED_VAULT_CLIENT_H_
#define COMPONENTS_SYNC_DRIVER_FILE_BASED_TRUSTED_VAULT_CLIENT_H_
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequenced_task_runner.h"
#include "components/sync/driver/trusted_vault_client.h"
namespace syncer {
// Standalone, file-based implementation of TrustedVaultClient that stores the
// keys in a local file, containing a serialized protocol buffer encrypted with
// platform-dependent crypto mechanisms (OSCrypt).
//
// Reading of the file is done lazily.
class FileBasedTrustedVaultClient : public TrustedVaultClient {
public:
explicit FileBasedTrustedVaultClient(const base::FilePath& file_path);
~FileBasedTrustedVaultClient() override;
// TrustedVaultClient implementation.
void FetchKeys(
const std::string& gaia_id,
base::OnceCallback<void(const std::vector<std::string>&)> cb) override;
void StoreKeys(const std::string& gaia_id,
const std::vector<std::string>& keys) override;
// Runs |cb| when all requests have completed.
void WaitForFlushForTesting(base::OnceClosure cb) const;
bool IsInitializationTriggeredForTesting() const;
private:
void TriggerLazyInitializationIfNeeded();
const base::FilePath file_path_;
const scoped_refptr<base::SequencedTaskRunner> backend_task_runner_;
// Backend constructed lazily in the UI thread, used in |backend_task_runner_|
// and destroyed (refcounted) on any thread.
class Backend;
scoped_refptr<Backend> backend_;
DISALLOW_COPY_AND_ASSIGN(FileBasedTrustedVaultClient);
};
} // namespace syncer
#endif // COMPONENTS_SYNC_DRIVER_FILE_BASED_TRUSTED_VAULT_CLIENT_H_
// 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/file_based_trusted_vault_client.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/run_loop.h"
#include "base/test/bind_test_util.h"
#include "base/test/task_environment.h"
#include "components/os_crypt/os_crypt.h"
#include "components/os_crypt/os_crypt_mocker.h"
#include "components/sync/protocol/local_trusted_vault.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
namespace {
using testing::ElementsAre;
using testing::Eq;
using testing::IsEmpty;
using testing::Ne;
MATCHER_P(KeyMaterialEq, expected, "") {
return arg.key_material() == expected;
}
base::FilePath CreateUniqueTempDir(base::ScopedTempDir* temp_dir) {
EXPECT_TRUE(temp_dir->CreateUniqueTempDir());
return temp_dir->GetPath();
}
// Issues a call to FetchKeys() for client |client| and waits until the callback
// completes. |client| must not be null.
std::vector<std::string> FetchKeysAndWaitForClient(
const std::string& gaia_id,
FileBasedTrustedVaultClient* client) {
DCHECK(client);
base::RunLoop loop;
std::vector<std::string> fetched_keys;
client->FetchKeys(gaia_id, base::BindLambdaForTesting(
[&](const std::vector<std::string>& keys) {
fetched_keys = keys;
loop.Quit();
}));
loop.Run();
return fetched_keys;
}
class FileBasedTrustedVaultClientTest : public testing::Test {
protected:
FileBasedTrustedVaultClientTest()
: file_path_(CreateUniqueTempDir(&temp_dir_)
.Append(base::FilePath(FILE_PATH_LITERAL("some_file")))),
client_(file_path_) {}
~FileBasedTrustedVaultClientTest() override = default;
void SetUp() override { OSCryptMocker::SetUp(); }
void TearDown() override { OSCryptMocker::TearDown(); }
std::vector<std::string> FetchKeysAndWait(const std::string& gaia_id) {
return FetchKeysAndWaitForClient(gaia_id, &client_);
}
void WaitForFlush() {
base::RunLoop loop;
client_.WaitForFlushForTesting(loop.QuitClosure());
loop.Run();
}
base::test::TaskEnvironment task_environment_;
base::ScopedTempDir temp_dir_;
const base::FilePath file_path_;
FileBasedTrustedVaultClient client_;
};
TEST_F(FileBasedTrustedVaultClientTest, ShouldNotAutoTriggerInitialization) {
EXPECT_FALSE(client_.IsInitializationTriggeredForTesting());
}
TEST_F(FileBasedTrustedVaultClientTest, ShouldFetchEmptyKeys) {
const std::string kGaiaId = "user1";
ASSERT_FALSE(client_.IsInitializationTriggeredForTesting());
EXPECT_THAT(FetchKeysAndWait(kGaiaId), IsEmpty());
EXPECT_TRUE(client_.IsInitializationTriggeredForTesting());
}
TEST_F(FileBasedTrustedVaultClientTest, ShouldFetchNonEmptyKeys) {
const std::string kGaiaId1 = "user1";
const std::string kGaiaId2 = "user2";
const std::string kKey1 = "key1";
const std::string kKey2 = "key2";
const std::string kKey3 = "key3";
sync_pb::LocalTrustedVault initial_data;
sync_pb::LocalTrustedVaultPerUser* user_data1 = initial_data.add_user();
sync_pb::LocalTrustedVaultPerUser* user_data2 = initial_data.add_user();
user_data1->set_gaia_id(kGaiaId1);
user_data2->set_gaia_id(kGaiaId2);
user_data1->add_key()->set_key_material(kKey1);
user_data2->add_key()->set_key_material(kKey2);
user_data2->add_key()->set_key_material(kKey3);
std::string encrypted_data;
ASSERT_TRUE(OSCrypt::EncryptString(initial_data.SerializeAsString(),
&encrypted_data));
ASSERT_NE(-1, base::WriteFile(file_path_, encrypted_data.c_str(),
encrypted_data.size()));
ASSERT_FALSE(client_.IsInitializationTriggeredForTesting());
EXPECT_THAT(FetchKeysAndWait(kGaiaId1), ElementsAre(kKey1));
EXPECT_THAT(FetchKeysAndWait(kGaiaId2), ElementsAre(kKey2, kKey3));
}
TEST_F(FileBasedTrustedVaultClientTest, ShouldStoreKeys) {
const std::string kGaiaId1 = "user1";
const std::string kGaiaId2 = "user2";
const std::string kKey1 = "key1";
const std::string kKey2 = "key2";
const std::string kKey3 = "key3";
const std::string kKey4 = "key4";
client_.StoreKeys(kGaiaId1, {kKey1});
client_.StoreKeys(kGaiaId2, {kKey2});
// Keys for |kGaiaId2| overriden, so |kKey2| should be lost.
client_.StoreKeys(kGaiaId2, {kKey3, kKey4});
// Wait until the last write completes.
WaitForFlush();
// Read the file from disk.
std::string ciphertext;
std::string decrypted_content;
sync_pb::LocalTrustedVault proto;
EXPECT_TRUE(base::ReadFileToString(file_path_, &ciphertext));
EXPECT_THAT(ciphertext, Ne(""));
EXPECT_TRUE(OSCrypt::DecryptString(ciphertext, &decrypted_content));
EXPECT_TRUE(proto.ParseFromString(decrypted_content));
ASSERT_THAT(proto.user_size(), Eq(2));
EXPECT_THAT(proto.user(0).key(), ElementsAre(KeyMaterialEq(kKey1)));
EXPECT_THAT(proto.user(1).key(),
ElementsAre(KeyMaterialEq(kKey3), KeyMaterialEq(kKey4)));
}
TEST_F(FileBasedTrustedVaultClientTest, ShouldFetchPreviouslyStoredKeys) {
const std::string kGaiaId1 = "user1";
const std::string kGaiaId2 = "user2";
const std::string kKey1 = "key1";
const std::string kKey2 = "key2";
const std::string kKey3 = "key3";
client_.StoreKeys(kGaiaId1, {kKey1});
client_.StoreKeys(kGaiaId2, {kKey2, kKey3});
// Wait until the last write completes.
WaitForFlush();
// Instantiate a second client to read the file.
FileBasedTrustedVaultClient other_client(file_path_);
EXPECT_THAT(FetchKeysAndWaitForClient(kGaiaId1, &other_client),
ElementsAre(kKey1));
EXPECT_THAT(FetchKeysAndWaitForClient(kGaiaId2, &other_client),
ElementsAre(kKey2, kKey3));
}
} // namespace
} // namespace syncer
// 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.
syntax = "proto2";
option optimize_for = LITE_RUNTIME;
package sync_pb;
message LocalTrustedVaultKey {
// The actual key.
optional bytes key_material = 1;
}
message LocalTrustedVaultPerUser {
// User identifier.
optional bytes gaia_id = 1;
// All keys known for a user.
repeated LocalTrustedVaultKey key = 2;
}
message LocalTrustedVault {
repeated LocalTrustedVaultPerUser user = 1;
}
\ No newline at end of file
......@@ -28,6 +28,7 @@ sync_protocol_bases = [
"get_updates_caller_info",
"history_delete_directive_specifics",
"history_status",
"local_trusted_vault",
"loopback_server",
"managed_user_setting_specifics",
"managed_user_shared_setting_specifics",
......
......@@ -70,6 +70,8 @@ class IOSChromeSyncClient : public browser_sync::BrowserSyncClient {
std::unique_ptr<browser_sync::ProfileSyncComponentsFactoryImpl>
component_factory_;
std::unique_ptr<syncer::TrustedVaultClient> trusted_vault_client_;
// Members that must be fetched on the UI thread but accessed on their
// respective backend threads.
scoped_refptr<autofill::AutofillWebDataService> profile_web_data_service_;
......
......@@ -32,6 +32,7 @@
#include "components/reading_list/core/reading_list_model.h"
#include "components/sync/base/report_unrecoverable_error.h"
#include "components/sync/base/sync_base_switches.h"
#include "components/sync/driver/file_based_trusted_vault_client.h"
#include "components/sync/driver/sync_api_component_factory.h"
#include "components/sync/driver/sync_service.h"
#include "components/sync/driver/sync_util.h"
......@@ -70,6 +71,9 @@
namespace {
const base::FilePath::CharType kTrustedVaultFilename[] =
FILE_PATH_LITERAL("Trusted Vault");
syncer::ModelTypeSet GetDisabledTypesFromCommandLine() {
std::string disabled_types_str =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
......@@ -110,6 +114,10 @@ IOSChromeSyncClient::IOSChromeSyncClient(ios::ChromeBrowserState* browser_state)
profile_web_data_service_, account_web_data_service_, password_store_,
/*account_password_store=*/nullptr,
ios::BookmarkSyncServiceFactory::GetForBrowserState(browser_state_));
// TODO(crbug.com/1012660): Instantiate a specific client for ios.
trusted_vault_client_ = std::make_unique<syncer::FileBasedTrustedVaultClient>(
browser_state_->GetStatePath().Append(kTrustedVaultFilename));
}
IOSChromeSyncClient::~IOSChromeSyncClient() {}
......@@ -191,9 +199,7 @@ IOSChromeSyncClient::GetInvalidationService() {
}
syncer::TrustedVaultClient* IOSChromeSyncClient::GetTrustedVaultClient() {
// TODO(crbug.com/1012660): Instantiate a generic client for ios.
NOTIMPLEMENTED();
return nullptr;
return trusted_vault_client_.get();
}
scoped_refptr<syncer::ExtensionsActivity>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment