blob: 7cc2df3f85a4ecb6477b21fe38b0ed1bac18d7ff [file] [log] [blame]
// Copyright 2020 The Chromium OS 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 <string>
#include <utility>
#include <vector>
#include <base/bind.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/run_loop.h>
#include <base/test/task_environment.h>
#include <base/threading/thread_task_runner_handle.h>
#include <brillo/cryptohome.h>
#include <brillo/data_encoding.h>
#include <brillo/secure_blob.h>
#include <brillo/dbus/mock_dbus_method_response.h>
#include <dbus/bus.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "arc/data-snapshotd/block_ui_controller.h"
#include "arc/data-snapshotd/dbus_adaptor.h"
#include "arc/data-snapshotd/fake_process_launcher.h"
#include "arc/data-snapshotd/file_utils.h"
#include "arc/data-snapshotd/mock_esc_key_watcher.h"
#include "arc/data-snapshotd/worker_bridge.h"
#include "bootlockbox-client/bootlockbox/boot_lockbox_client.h"
// Note that boot_lockbox_rpc.pb.h have to be included before
// dbus_adaptors/org.chromium.BootLockboxInterface.h because it is used in
// there.
#include "bootlockbox/proto_bindings/boot_lockbox_rpc.pb.h"
#include "bootlockbox-client/bootlockbox/dbus-proxies.h"
using testing::_;
using testing::Eq;
using testing::Invoke;
using testing::Return;
namespace arc {
namespace data_snapshotd {
namespace {
constexpr char kRandomDir[] = "data";
constexpr char kRandomFile[] = "random file";
constexpr char kContent[] = "content";
constexpr char kFakeLastSnapshotPublicKey[] = "fake_public_key";
constexpr char kFakeAccountID[] = "fake_account_id";
MATCHER_P(nEq, expected, "") {
return expected != arg;
}
} // namespace
class MockBootLockboxClient : public cryptohome::BootLockboxClient {
public:
explicit MockBootLockboxClient(scoped_refptr<dbus::Bus> bus)
: BootLockboxClient(
std::make_unique<org::chromium::BootLockboxInterfaceProxy>(bus),
bus) {}
~MockBootLockboxClient() override = default;
MOCK_METHOD(bool,
Store,
(const std::string&, const std::string&),
(override));
MOCK_METHOD(bool, Read, (const std::string&, std::string*), (override));
MOCK_METHOD(bool, Finalize, (), (override));
};
class FakeWorkerBridge : public WorkerBridge {
public:
FakeWorkerBridge() = default;
// WorkerBridge overrides:
void Init(const std::string& account_id,
base::OnceCallback<void(bool)> on_initialized) override {
EXPECT_EQ(account_id, account_id_);
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(on_initialized), init_result_));
}
void TakeSnapshot(
const std::string& account_id,
const std::string& private_key,
const std::string& public_key,
std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<bool>> response)
override {
EXPECT_EQ(account_id, account_id);
EXPECT_FALSE(private_key.empty());
EXPECT_FALSE(public_key.empty());
response->Return(result_);
}
void LoadSnapshot(
const std::string& account_id,
std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<bool, bool>>
response) override {
EXPECT_EQ(account_id, account_id);
response->Return(result_, last_);
}
bool is_available_for_testing() const override { return init_result_; }
void set_init_result(bool init_result) { init_result_ = init_result; }
void set_result(bool result) { result_ = result; }
void set_account_id(const std::string& account_id) {
account_id_ = account_id;
}
void set_last(bool last) { last_ = last; }
private:
bool init_result_ = false;
bool result_ = false;
bool last_ = false;
std::string account_id_;
};
class DBusAdaptorTest : public testing::Test {
public:
DBusAdaptorTest() : bus_(new dbus::Bus{dbus::Bus::Options{}}) {}
void SetUp() override {
EXPECT_TRUE(root_tempdir_.CreateUniqueTempDir());
auto boot_lockbox_client =
std::make_unique<testing::StrictMock<MockBootLockboxClient>>(bus_);
boot_lockbox_client_ = boot_lockbox_client.get();
process_launcher_ = std::make_unique<FakeProcessLauncher>();
dbus_adaptor_ = DBusAdaptor::CreateForTesting(
root_tempdir_.GetPath(), std::move(boot_lockbox_client),
BlockUiController::CreateForTesting(
std::make_unique<FakeEscKeyWatcher>(&delegate_),
root_tempdir_.GetPath(),
process_launcher_->GetLaunchProcessCallback()));
}
void TearDown() override {
dbus_adaptor_.reset();
EXPECT_TRUE(base::DeletePathRecursively(root_tempdir_.GetPath()));
process_launcher_.reset();
}
FakeWorkerBridge* CreateWorkerBridge() {
auto worker_bridge = std::make_unique<FakeWorkerBridge>();
auto* worker_bridge_ptr = worker_bridge.get();
WorkerBridge::SetFakeInstanceForTesting(std::move(worker_bridge));
return worker_bridge_ptr;
}
DBusAdaptor* dbus_adaptor() { return dbus_adaptor_.get(); }
const base::FilePath& last_snapshot_dir() const {
return dbus_adaptor_->get_last_snapshot_directory();
}
const base::FilePath& previous_snapshot_dir() const {
return dbus_adaptor_->get_previous_snapshot_directory();
}
base::FilePath random_dir() const {
return root_tempdir_.GetPath().Append(kRandomDir);
}
// Creates |dir| and fills in with random content.
void CreateDir(const base::FilePath& dir) {
EXPECT_TRUE(base::CreateDirectory(dir));
EXPECT_TRUE(base::CreateDirectory(dir.Append(kRandomDir)));
EXPECT_TRUE(
base::WriteFile(dir.Append(kRandomFile), kContent, strlen(kContent)));
}
void ExpectUiScreenShown(bool shown = true) {
process_launcher_->ExpectUiScreenShown(root_tempdir_.GetPath(), shown);
}
void ExpectProgressUpdated(int percent, bool result = true) {
process_launcher_->ExpectProgressUpdated(percent, result);
}
void GenerateKeyPairBasic() {
EXPECT_CALL(*boot_lockbox_client(), Store(Eq(kLastSnapshotPublicKey), _))
.WillOnce(Return(true));
ExpectUiScreenShown();
EXPECT_TRUE(dbus_adaptor()->GenerateKeyPair());
}
void TakeSnapshotBasic(bool init_result, bool result, bool expected_result) {
auto* worker_bridge = CreateWorkerBridge();
std::unique_ptr<brillo::dbus_utils::MockDBusMethodResponse<bool>> response(
new brillo::dbus_utils::MockDBusMethodResponse<bool>(nullptr));
response->set_return_callback(base::Bind(
[](bool expected_result, const bool& success) {
EXPECT_EQ(expected_result, success);
},
expected_result));
worker_bridge->set_init_result(init_result);
worker_bridge->set_result(result);
worker_bridge->set_account_id(kFakeAccountID);
dbus_adaptor()->TakeSnapshot(std::move(response), kFakeAccountID);
}
void LoadSnapshotBasic(bool init_result, bool result, bool last) {
auto* worker_bridge = CreateWorkerBridge();
std::unique_ptr<brillo::dbus_utils::MockDBusMethodResponse<bool, bool>>
response(new brillo::dbus_utils::MockDBusMethodResponse<bool, bool>(
nullptr));
response->set_return_callback(base::Bind(
[](bool expected_result, bool expected_last, const bool& success,
const bool& last) {
EXPECT_EQ(expected_result, success);
EXPECT_EQ(expected_last, last);
},
result, last));
worker_bridge->set_init_result(init_result);
worker_bridge->set_result(result);
worker_bridge->set_last(last);
worker_bridge->set_account_id(kFakeAccountID);
dbus_adaptor()->LoadSnapshot(std::move(response), kFakeAccountID);
}
MockBootLockboxClient* boot_lockbox_client() { return boot_lockbox_client_; }
private:
base::test::TaskEnvironment task_environment_;
MockEscKeyWatcherDelegate delegate_;
scoped_refptr<dbus::Bus> bus_;
MockBootLockboxClient* boot_lockbox_client_;
std::unique_ptr<FakeProcessLauncher> process_launcher_;
std::unique_ptr<DBusAdaptor> dbus_adaptor_;
base::ScopedTempDir root_tempdir_;
};
TEST_F(DBusAdaptorTest, ClearSnapshotBasic) {
CreateDir(last_snapshot_dir());
EXPECT_TRUE(base::DirectoryExists(last_snapshot_dir()));
CreateDir(previous_snapshot_dir());
EXPECT_TRUE(base::DirectoryExists(previous_snapshot_dir()));
EXPECT_TRUE(dbus_adaptor()->ClearSnapshot(false /* last */));
EXPECT_FALSE(base::DirectoryExists(previous_snapshot_dir()));
EXPECT_TRUE(dbus_adaptor()->ClearSnapshot(false /* last */));
EXPECT_TRUE(dbus_adaptor()->ClearSnapshot(true /* last */));
EXPECT_FALSE(base::DirectoryExists(last_snapshot_dir()));
EXPECT_TRUE(dbus_adaptor()->ClearSnapshot(true /* last */));
}
// Test successful basic flow with no pre-existing snapshots.
TEST_F(DBusAdaptorTest, GenerateKeyPairBasic) {
GenerateKeyPairBasic();
}
// Test successful basic flow with pre-existing snapshots.
TEST_F(DBusAdaptorTest, GenerateKeyPairExisting) {
CreateDir(last_snapshot_dir());
CreateDir(previous_snapshot_dir());
SnapshotDirectory last_dir;
EXPECT_TRUE(ReadSnapshotDirectory(last_snapshot_dir(), &last_dir));
std::vector<uint8_t> last_hash =
CalculateDirectoryCryptographicHash(last_dir);
EXPECT_FALSE(last_hash.empty());
{
SnapshotDirectory previous_dir;
EXPECT_TRUE(ReadSnapshotDirectory(previous_snapshot_dir(), &previous_dir));
std::vector<uint8_t> previous_hash =
CalculateDirectoryCryptographicHash(previous_dir);
EXPECT_FALSE(previous_hash.empty());
EXPECT_NE(last_hash, previous_hash);
}
// Last snapshot dir => previous snapshot dir.
EXPECT_CALL(*boot_lockbox_client(), Read(Eq(kLastSnapshotPublicKey), _))
.WillOnce(Invoke([](const std::string& key, std::string* value) {
*value = kFakeLastSnapshotPublicKey;
return true;
}));
EXPECT_CALL(*boot_lockbox_client(), Store(Eq(kPreviousSnapshotPublicKey),
Eq(kFakeLastSnapshotPublicKey)))
.WillOnce(Return(true));
EXPECT_CALL(*boot_lockbox_client(), Store(Eq(kLastSnapshotPublicKey), Eq("")))
.WillOnce(Return(true));
EXPECT_CALL(*boot_lockbox_client(),
Store(Eq(kLastSnapshotPublicKey), nEq("")))
.WillOnce(Return(true));
ExpectUiScreenShown();
EXPECT_TRUE(dbus_adaptor()->GenerateKeyPair());
{
SnapshotDirectory previous_dir;
EXPECT_TRUE(ReadSnapshotDirectory(previous_snapshot_dir(), &previous_dir));
std::vector<uint8_t> previous_hash =
CalculateDirectoryCryptographicHash(previous_dir);
EXPECT_FALSE(previous_hash.empty());
// Check that the last snapshot has been moved to previous snapshot dir.
EXPECT_EQ(previous_hash, last_hash);
}
}
// Test successful flow with last snapshot key reading failure.
TEST_F(DBusAdaptorTest, GenerateKeyPairReadFailure) {
CreateDir(last_snapshot_dir());
// Attempt failure: last snapshot dir => previous snapshot dir.
EXPECT_CALL(*boot_lockbox_client(), Read(Eq(kLastSnapshotPublicKey), _))
.WillOnce(Return(false));
EXPECT_CALL(*boot_lockbox_client(),
Store(Eq(kLastSnapshotPublicKey), nEq("")))
.WillOnce(Return(true));
ExpectUiScreenShown();
// Generating key pair should be still successful.
EXPECT_TRUE(dbus_adaptor()->GenerateKeyPair());
}
// Test successful flow with pre-existing last snapshot empty key.
TEST_F(DBusAdaptorTest, GenerateKeyPairReadEmpty) {
CreateDir(last_snapshot_dir());
// Attempt failure: last snapshot dir => previous snapshot dir.
EXPECT_CALL(*boot_lockbox_client(), Read(Eq(kLastSnapshotPublicKey), _))
.WillOnce(Invoke([](const std::string& key, std::string* value) {
*value = "";
return true;
}));
EXPECT_CALL(*boot_lockbox_client(),
Store(Eq(kLastSnapshotPublicKey), nEq("")))
.WillOnce(Return(true));
ExpectUiScreenShown();
// Generating key pair should be still successful.
EXPECT_TRUE(dbus_adaptor()->GenerateKeyPair());
}
// Test success flow with pre-existing snapshots and moving error.
TEST_F(DBusAdaptorTest, GenerateKeyPairMoveError) {
CreateDir(last_snapshot_dir());
// Last snapshot dir => previous snapshot dir.
EXPECT_CALL(*boot_lockbox_client(), Read(Eq(kLastSnapshotPublicKey), _))
.WillOnce(Invoke([](const std::string& key, std::string* value) {
*value = kFakeLastSnapshotPublicKey;
return true;
}));
// Fail to move last snapshot public key to previous.
EXPECT_CALL(*boot_lockbox_client(), Store(Eq(kPreviousSnapshotPublicKey),
Eq(kFakeLastSnapshotPublicKey)))
.WillOnce(Return(false));
EXPECT_CALL(*boot_lockbox_client(),
Store(Eq(kLastSnapshotPublicKey), nEq("")))
.WillOnce(Return(true));
ExpectUiScreenShown();
// Generating key pair should be still successful, because the last snapshot
// will be re-generated anyway.
EXPECT_TRUE(dbus_adaptor()->GenerateKeyPair());
}
// Test failure flow when showing a UI screen is failed.
TEST_F(DBusAdaptorTest, GenerateKeyPairUiFailure) {
EXPECT_CALL(*boot_lockbox_client(), Store(Eq(kLastSnapshotPublicKey), _))
.WillOnce(Return(true));
ExpectUiScreenShown(false /* shown */);
// Since the screen is not shown, do not dismiss it.
EXPECT_FALSE(dbus_adaptor()->GenerateKeyPair());
}
// Test failure flow when storing freshly generated public key is failed.
TEST_F(DBusAdaptorTest, GenerateKeyPairStoreFailure) {
// Fail once freshly generated public key storage is attempted.
EXPECT_CALL(*boot_lockbox_client(),
Store(Eq(kLastSnapshotPublicKey), nEq("")))
.WillOnce(Return(false));
EXPECT_FALSE(dbus_adaptor()->GenerateKeyPair());
}
// Test failure flow when the keys were not generated.
TEST_F(DBusAdaptorTest, TakeSnapshotNoPrivateKeyFailure) {
TakeSnapshotBasic(true /* init_result */, true /* result */,
false /* expected_result */);
}
// Test failure flow when worker's initialization failed.
TEST_F(DBusAdaptorTest, TakeSnapshotInitializationFailed) {
GenerateKeyPairBasic();
TakeSnapshotBasic(false /* init_result */, false /* result */,
false /* expected_result */);
}
// Test failure flow when snapshot taking failed.
TEST_F(DBusAdaptorTest, TakeSnapshotFailure) {
GenerateKeyPairBasic();
TakeSnapshotBasic(true /* init_result */, false /* result */,
false /* expected_result */);
}
// Test basic TakeSnapshot success flow.
TEST_F(DBusAdaptorTest, TakeSnapshotSuccess) {
GenerateKeyPairBasic();
TakeSnapshotBasic(true /* init_result */, true /* result */,
false /* expected_result */);
}
// Test failure flow if TakeSnapshot is invoked twice.
TEST_F(DBusAdaptorTest, TakeSnapshotDouble) {
GenerateKeyPairBasic();
TakeSnapshotBasic(true /* init_result */, true /* result */,
true /* expected_result */);
TakeSnapshotBasic(true /* init_result */, true /* result */,
false /* expected_result */);
}
// Test failure flow when worker's initialization failed..
TEST_F(DBusAdaptorTest, LoadSnapshotInitializationFailed) {
LoadSnapshotBasic(false /* init_result */, false /* result */,
false /* last */);
}
// Test failure when snapshot loading failed.
TEST_F(DBusAdaptorTest, LoadSnapshotFailure) {
LoadSnapshotBasic(true /* init_result */, false /* result */,
false /* last */);
}
// Test basic success flow.
TEST_F(DBusAdaptorTest, LoadSnapshotSuccess) {
LoadSnapshotBasic(true /* init_result */, true /* result */, true /* last */);
}
// Test failure flow when UI screen is not shown.
TEST_F(DBusAdaptorTest, UpdateNoUIScreen) {
ExpectUiScreenShown(false /* result */);
EXPECT_FALSE(dbus_adaptor()->Update(0 /* percent */));
}
// Test failure flow with invalid percent number.
TEST_F(DBusAdaptorTest, UpdateInvalidPercent) {
EXPECT_CALL(*boot_lockbox_client(), Store(Eq(kLastSnapshotPublicKey), _))
.WillOnce(Return(true));
ExpectUiScreenShown();
EXPECT_TRUE(dbus_adaptor()->GenerateKeyPair());
EXPECT_FALSE(dbus_adaptor()->Update(-1 /* percent */));
EXPECT_FALSE(dbus_adaptor()->Update(101 /* percent */));
}
// Test failure flow with the progress update command failure.
TEST_F(DBusAdaptorTest, UpdateFailure) {
EXPECT_CALL(*boot_lockbox_client(), Store(Eq(kLastSnapshotPublicKey), _))
.WillOnce(Return(true));
ExpectUiScreenShown();
EXPECT_TRUE(dbus_adaptor()->GenerateKeyPair());
ExpectProgressUpdated(0 /* percent */, false /* result */);
EXPECT_FALSE(dbus_adaptor()->Update(0 /* percent */));
}
// Test success basic UI update flow.
TEST_F(DBusAdaptorTest, UpdateSuccess) {
EXPECT_CALL(*boot_lockbox_client(), Store(Eq(kLastSnapshotPublicKey), _))
.WillOnce(Return(true));
ExpectUiScreenShown();
EXPECT_TRUE(dbus_adaptor()->GenerateKeyPair());
ExpectProgressUpdated(0 /* percent */);
EXPECT_TRUE(dbus_adaptor()->Update(0 /* percent */));
ExpectProgressUpdated(10 /* percent */);
EXPECT_TRUE(dbus_adaptor()->Update(10 /* percent */));
ExpectProgressUpdated(100 /* percent */);
EXPECT_TRUE(dbus_adaptor()->Update(100 /* percent */));
}
} // namespace data_snapshotd
} // namespace arc