blob: 7c40ffddf94d21d04217c9312015d445b7d9f6aa [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 "arc/data-snapshotd/dbus_adaptor.h"
#include <utility>
#include <vector>
#include <base/bind.h>
#include <base/callback_helpers.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/macros.h>
#include <base/memory/ptr_util.h>
#include <brillo/data_encoding.h>
#include <brillo/cryptohome.h>
#include <crypto/scoped_openssl_types.h>
#include <crypto/rsa_private_key.h>
#include "arc/data-snapshotd/file_utils.h"
#include "bootlockbox-client/bootlockbox/boot_lockbox_client.h"
namespace arc {
namespace data_snapshotd {
namespace {
// Snapshot paths:
constexpr char kCommonSnapshotPath[] =
"/mnt/stateful_partition/unencrypted/arc-data-snapshot/";
constexpr char kLastSnapshotPath[] = "last";
constexpr char kPreviousSnapshotPath[] = "previous";
constexpr char kHomeRootDirectory[] = "/home/root";
} // namespace
// BootLockbox snapshot keys:
const char kLastSnapshotPublicKey[] = "snapshot_public_key_last";
const char kPreviousSnapshotPublicKey[] = "snapshot_public_key_previous";
// Android data directory name:
const char kAndroidDataDirectory[] = "android-data";
DBusAdaptor::DBusAdaptor()
: DBusAdaptor(base::FilePath(kCommonSnapshotPath),
base::FilePath(kHomeRootDirectory),
cryptohome::BootLockboxClient::CreateBootLockboxClient()) {}
DBusAdaptor::~DBusAdaptor() = default;
// static
std::unique_ptr<DBusAdaptor> DBusAdaptor::CreateForTesting(
const base::FilePath& snapshot_directory,
const base::FilePath& home_root_directory,
std::unique_ptr<cryptohome::BootLockboxClient> boot_lockbox_client) {
return base::WrapUnique(new DBusAdaptor(
snapshot_directory, home_root_directory, std::move(boot_lockbox_client)));
}
void DBusAdaptor::RegisterAsync(
const scoped_refptr<dbus::Bus>& bus,
brillo::dbus_utils::AsyncEventSequencer* sequencer) {
dbus_object_ = std::make_unique<brillo::dbus_utils::DBusObject>(
nullptr /* object_manager */, bus, GetObjectPath()),
RegisterWithDBusObject(dbus_object_.get());
dbus_object_->RegisterAsync(sequencer->GetHandler(
"Failed to register D-Bus object" /* descriptive_message */,
true /* failure_is_fatal */));
}
bool DBusAdaptor::GenerateKeyPair() {
// TODO(b/160387490): Implement showing a spinner screen.
std::string last_public_key_digest;
// Try to move last snapshot to previous for consistency.
if (base::PathExists(last_snapshot_directory_) &&
boot_lockbox_client_->Read(kLastSnapshotPublicKey,
&last_public_key_digest) &&
!last_public_key_digest.empty()) {
if (boot_lockbox_client_->Store(kPreviousSnapshotPublicKey,
last_public_key_digest) &&
ClearSnapshot(false /* last */) &&
base::Move(last_snapshot_directory_, previous_snapshot_directory_)) {
boot_lockbox_client_->Store(kLastSnapshotPublicKey, "");
} else {
LOG(ERROR) << "Failed to move last to previous snapshot.";
}
}
// Clear last snapshot - a new one will be created soon.
ClearSnapshot(true /* last */);
// Generate a key pair.
public_key_info_.clear();
std::unique_ptr<crypto::RSAPrivateKey> generated_private_key(
crypto::RSAPrivateKey::Create(1024));
if (!generated_private_key) {
LOG(ERROR) << "Failed to generate a key pair.";
return false;
}
if (!generated_private_key->ExportPublicKey(&public_key_info_)) {
LOG(ERROR) << "Failed to export public key";
return false;
}
// Store a new public key digest.
std::string encoded_digest = CalculateEncodedSha256Digest(public_key_info_);
if (!boot_lockbox_client_->Store(kLastSnapshotPublicKey, encoded_digest)) {
LOG(ERROR) << "Failed to store a public key in BootLockbox.";
return false;
}
// Save private key for later usage.
private_key_ = std::move(generated_private_key);
return true;
}
bool DBusAdaptor::TakeSnapshot(const std::string& account_id) {
if (!private_key_ || public_key_info_.empty()) {
LOG(ERROR) << "Private or public key does not exist.";
return false;
}
if (base::DirectoryExists(last_snapshot_directory_)) {
LOG(ERROR) << "Snapshot directory already exists. Should be cleared first.";
return false;
}
std::string userhash = brillo::cryptohome::home::SanitizeUserName(account_id);
base::FilePath android_data_dir =
home_root_directory_.Append(userhash).Append(kAndroidDataDirectory);
if (!base::DirectoryExists(android_data_dir)) {
LOG(ERROR) << "Android data directory does not exist for user "
<< account_id;
return false;
}
if (base::IsLink(android_data_dir)) {
LOG(ERROR) << android_data_dir.value()
<< " is a symbolic link, not snapshotting.";
return false;
}
if (!CopySnapshotDirectory(android_data_dir, last_snapshot_directory_)) {
LOG(ERROR) << "Failed to copy snapshot directory from "
<< android_data_dir.value() << " to "
<< last_snapshot_directory_.value();
return false;
}
// This callback will be executed or released before the end of this function.
base::ScopedClosureRunner snapshot_clearer(
base::BindOnce(base::IgnoreResult(&DBusAdaptor::ClearSnapshot),
base::Unretained(this), true /* last */));
if (!StorePublicKey(last_snapshot_directory_, public_key_info_))
return false;
if (!StoreUserhash(last_snapshot_directory_, userhash))
return false;
if (!SignAndStoreHash(last_snapshot_directory_, private_key_.get(),
inode_verification_enabled_)) {
return false;
}
// Snapshot saved correctly, release closure without running it.
ignore_result(snapshot_clearer.Release());
// Dispose keys.
private_key_.reset();
public_key_info_.clear();
return true;
}
bool DBusAdaptor::ClearSnapshot(bool last) {
base::FilePath dir(last ? last_snapshot_directory_
: previous_snapshot_directory_);
if (!base::DirectoryExists(dir)) {
LOG(WARNING) << "Snapshot directory is already empty: " << dir.value();
return true;
}
if (!base::DeletePathRecursively(dir)) {
LOG(ERROR) << "Failed to delete snapshot directory: " << dir.value();
return false;
}
return true;
}
void DBusAdaptor::LoadSnapshot(const std::string& account_id,
bool* last,
bool* success) {
std::string userhash = brillo::cryptohome::home::SanitizeUserName(account_id);
if (!base::DirectoryExists(home_root_directory_.Append(userhash))) {
LOG(ERROR) << "User directory does not exist for user " << account_id;
*success = false;
return;
}
base::FilePath android_data_dir =
home_root_directory_.Append(userhash).Append(kAndroidDataDirectory);
if (TryToLoadSnapshot(userhash, last_snapshot_directory_, android_data_dir,
kLastSnapshotPublicKey)) {
*last = true;
*success = true;
return;
}
if (TryToLoadSnapshot(userhash, previous_snapshot_directory_,
android_data_dir, kPreviousSnapshotPublicKey)) {
*last = false;
*success = true;
return;
}
*success = false;
}
bool DBusAdaptor::TryToLoadSnapshot(const std::string& userhash,
const base::FilePath& snapshot_dir,
const base::FilePath& android_data_dir,
const std::string& boot_lockbox_key) {
if (!base::DirectoryExists(snapshot_dir)) {
LOG(ERROR) << "Snapshot directory " << snapshot_dir.value()
<< " does not exist.";
return false;
}
std::string expected_public_key_digest;
if (!boot_lockbox_client_->Read(boot_lockbox_key,
&expected_public_key_digest) ||
expected_public_key_digest.empty()) {
LOG(ERROR) << "Failed to read a public key digest " << boot_lockbox_key
<< " from BootLockbox.";
return false;
}
if (!VerifyHash(snapshot_dir, userhash, expected_public_key_digest,
inode_verification_enabled_)) {
return false;
}
if (!CopySnapshotDirectory(snapshot_dir, android_data_dir)) {
LOG(ERROR) << "Failed to copy a snapshot directory.";
return false;
}
return true;
}
DBusAdaptor::DBusAdaptor(
const base::FilePath& snapshot_directory,
const base::FilePath& home_root_directory,
std::unique_ptr<cryptohome::BootLockboxClient> boot_lockbox_client)
: org::chromium::ArcDataSnapshotdAdaptor(this),
last_snapshot_directory_(snapshot_directory.Append(kLastSnapshotPath)),
previous_snapshot_directory_(
snapshot_directory.Append(kPreviousSnapshotPath)),
home_root_directory_(home_root_directory),
boot_lockbox_client_(std::move(boot_lockbox_client)) {
DCHECK(boot_lockbox_client_);
}
} // namespace data_snapshotd
} // namespace arc