blob: c0fa51bf834d95e9a56fa4ab09f208f892d1afab [file] [log] [blame]
// Copyright 2018 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 "oobe_config/oobe_config.h"
#include <map>
#include <utility>
#include <base/files/file_enumerator.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <policy/resilient_policy_util.h>
#include <libtpmcrypto/tpm_crypto_impl.h>
#include "oobe_config/rollback_constants.h"
#include "oobe_config/rollback_data.pb.h"
namespace oobe_config {
OobeConfig::OobeConfig() {
crypto_ = std::make_unique<tpmcrypto::TpmCryptoImpl>();
}
OobeConfig::OobeConfig(std::unique_ptr<tpmcrypto::TpmCrypto> crypto)
: crypto_(std::move(crypto)) {}
OobeConfig::~OobeConfig() = default;
base::FilePath OobeConfig::GetPrefixedFilePath(
const base::FilePath& file_path) const {
if (prefix_path_for_testing_.empty())
return file_path;
DCHECK(!file_path.value().empty());
DCHECK_EQ('/', file_path.value()[0]);
return prefix_path_for_testing_.Append(file_path.value().substr(1));
}
bool OobeConfig::ReadFileWithoutPrefix(const base::FilePath& file_path,
std::string* out_content) const {
bool result = base::ReadFileToString(file_path, out_content);
if (result) {
LOG(INFO) << "Loaded " << file_path.value();
} else {
LOG(ERROR) << "Couldn't read " << file_path.value();
*out_content = "";
}
return result;
}
bool OobeConfig::ReadFile(const base::FilePath& file_path,
std::string* out_content) const {
return ReadFileWithoutPrefix(GetPrefixedFilePath(file_path), out_content);
}
bool OobeConfig::FileExists(const base::FilePath& file_path) const {
return base::PathExists(GetPrefixedFilePath(file_path));
}
bool OobeConfig::WriteFileWithoutPrefix(const base::FilePath& file_path,
const std::string& data) const {
if (!base::CreateDirectory(file_path.DirName())) {
PLOG(ERROR) << "Couldn't create directory for " << file_path.value();
return false;
}
int bytes_written = base::WriteFile(file_path, data.c_str(), data.size());
if (bytes_written != data.size()) {
PLOG(ERROR) << "Couldn't write " << file_path.value()
<< " bytes=" << bytes_written;
return false;
}
LOG(INFO) << "Wrote " << file_path.value();
return true;
}
bool OobeConfig::WriteFile(const base::FilePath& file_path,
const std::string& data) const {
return WriteFileWithoutPrefix(GetPrefixedFilePath(file_path), data);
}
bool OobeConfig::GetRollbackData(RollbackData* rollback_data) const {
std::string file_content;
ReadFile(kSaveTempPath.Append(kInstallAttributesFileName), &file_content);
rollback_data->set_install_attributes(file_content);
ReadFile(kSaveTempPath.Append(kOwnerKeyFileName), &file_content);
rollback_data->set_owner_key(file_content);
ReadFile(kSaveTempPath.Append(kShillDefaultProfileFileName), &file_content);
rollback_data->set_shill_default_profile(file_content);
if (base::PathExists(
GetPrefixedFilePath(kSaveTempPath.Append(kOobeCompletedFileName)))) {
// If OOBE has been completed already, we know the EULA has been accepted.
rollback_data->set_eula_auto_accept(true);
}
if (base::PathExists(GetPrefixedFilePath(
kSaveTempPath.Append(kMetricsReportingEnabledFileName)))) {
// If |kMetricsReportingEnabledFile| exists, metrics are enabled.
rollback_data->set_eula_send_statistics(true);
}
PolicyData* policy_data = rollback_data->mutable_device_policy();
std::map<int, base::FilePath> policy_paths =
policy::GetSortedResilientPolicyFilePaths(
GetPrefixedFilePath(kSaveTempPath.Append(kPolicyFileName)));
for (auto entry : policy_paths) {
policy_data->add_policy_index(entry.first);
ReadFileWithoutPrefix(entry.second, &file_content);
policy_data->add_policy_file(file_content);
}
return true;
}
bool OobeConfig::GetSerializedRollbackData(
std::string* serialized_rollback_data) const {
RollbackData rollback_data;
if (!GetRollbackData(&rollback_data)) {
return false;
}
if (!rollback_data.SerializeToString(serialized_rollback_data)) {
LOG(ERROR) << "Couldn't serialize proto.";
return false;
}
return true;
}
bool OobeConfig::RestoreRollbackData(const RollbackData& rollback_data) const {
WriteFile(kRestoreTempPath.Append(kInstallAttributesFileName),
rollback_data.install_attributes());
WriteFile(kRestoreTempPath.Append(kOwnerKeyFileName),
rollback_data.owner_key());
WriteFile(kRestoreTempPath.Append(kShillDefaultProfileFileName),
rollback_data.shill_default_profile());
if (rollback_data.device_policy().policy_file_size() !=
rollback_data.device_policy().policy_index_size()) {
LOG(ERROR) << "Invalid rollback_data.";
return false;
}
for (int i = 0; i < rollback_data.device_policy().policy_file_size(); ++i) {
base::FilePath policy_path = policy::GetResilientPolicyFilePathForIndex(
GetPrefixedFilePath(kRestoreTempPath.Append(kPolicyFileName)),
rollback_data.device_policy().policy_index(i));
WriteFileWithoutPrefix(policy_path,
rollback_data.device_policy().policy_file(i));
}
return true;
}
bool OobeConfig::UnencryptedRollbackSave() const {
std::string serialized_rollback_data;
if (!GetSerializedRollbackData(&serialized_rollback_data)) {
return false;
}
if (!WriteFile(kUnencryptedStatefulRollbackDataPath,
serialized_rollback_data)) {
return false;
}
return true;
}
bool OobeConfig::EncryptedRollbackSave() const {
std::string serialized_rollback_data;
if (!GetSerializedRollbackData(&serialized_rollback_data)) {
return false;
}
LOG(INFO) << "Encrypting rollback data size="
<< serialized_rollback_data.size();
std::string encrypted;
brillo::SecureBlob blob(serialized_rollback_data);
if (!crypto_->Encrypt(blob, &encrypted)) {
LOG(ERROR) << "Failed to encrypt rollback data";
return false;
}
LOG(INFO) << "Writing encrypted rollback data";
if (!WriteFile(kUnencryptedStatefulRollbackDataPath, encrypted)) {
return false;
}
return true;
}
bool OobeConfig::UnencryptedRollbackRestore() const {
std::string rollback_data_str;
if (!ReadFile(kUnencryptedStatefulRollbackDataPath, &rollback_data_str)) {
return false;
}
// Write the unencrypted data immediately to
// kEncryptedStatefulRollbackDataPath.
if (!WriteFile(kEncryptedStatefulRollbackDataPath, rollback_data_str)) {
return false;
}
RollbackData rollback_data;
if (!rollback_data.ParseFromString(rollback_data_str)) {
LOG(ERROR) << "Couldn't parse proto.";
return false;
}
LOG(INFO) << "Parsed " << kUnencryptedStatefulRollbackDataPath.value();
// Data is already unencrypted, restore it.
return RestoreRollbackData(rollback_data);
}
bool OobeConfig::EncryptedRollbackRestore() const {
std::string encrypted_data;
if (!ReadFile(kUnencryptedStatefulRollbackDataPath, &encrypted_data)) {
return false;
}
// Decrypt the data.
LOG(INFO) << "Decrypting rollback data size=" << encrypted_data.size();
brillo::SecureBlob serialized_rollback_data;
crypto_->Decrypt(encrypted_data, &serialized_rollback_data);
std::string rollback_data_str = serialized_rollback_data.to_string();
// Write the unencrypted data immediately to
// kEncryptedStatefulRollbackDataPath.
if (!WriteFile(kEncryptedStatefulRollbackDataPath, rollback_data_str)) {
return false;
}
RollbackData rollback_data;
if (!rollback_data.ParseFromString(rollback_data_str)) {
LOG(ERROR) << "Couldn't parse proto.";
return false;
}
LOG(INFO) << "Parsed " << kUnencryptedStatefulRollbackDataPath.value();
// Data is already unencrypted, restore it.
return RestoreRollbackData(rollback_data);
}
void OobeConfig::CleanupEncryptedStatefulDirectory() const {
base::FileEnumerator iter(
GetPrefixedFilePath(kEncryptedStatefulRollbackDataPath), false,
base::FileEnumerator::FILES);
for (auto file = iter.Next(); !file.empty(); file = iter.Next()) {
if (!base::DeleteFile(file, false)) {
LOG(ERROR) << "Couldn't delete " << file.value();
}
}
}
bool OobeConfig::CheckFirstStage() const {
// Check whether we're in the first stage.
if (!FileExists(kUnencryptedStatefulRollbackDataPath)) {
LOG(INFO) << "CheckFirstStage: Rollback data "
<< kUnencryptedStatefulRollbackDataPath.value()
<< " does not exist.";
return false;
}
if (FileExists(kFirstStageCompletedFile)) {
LOG(INFO) << "CheckFirstStage: First stage already completed.";
return false;
}
// At this point, we should be in the first stage. We verify, that the other
// files are in a consistent state.
if (FileExists(kSecondStageCompletedFile)) {
LOG(ERROR)
<< "CheckFirstStage: Second stage is completed but first stage is not.";
return false;
}
if (FileExists(kEncryptedStatefulRollbackDataPath)) {
LOG(ERROR) << "CheckFirstStage: Both encrypted and unencrypted rollback "
"data path exists.";
return false;
}
LOG(INFO) << "CheckFirstStage: OK.";
return true;
}
bool OobeConfig::CheckSecondStage() const {
// Check whether we're in the second stage.
if (!FileExists(kFirstStageCompletedFile)) {
LOG(INFO) << "CheckSecondStage: First stage not yet completed.";
return false;
}
if (FileExists(kSecondStageCompletedFile)) {
LOG(INFO) << "CheckSecondStage: Second stage already completed.";
return false;
}
// At this point, we should be in the second stage. We verify, that the other
// files are in a consistent state.
if (!FileExists(kUnencryptedStatefulRollbackDataPath)) {
LOG(ERROR) << "CheckSecondStage: Rollback data "
<< kUnencryptedStatefulRollbackDataPath.value()
<< " should exist in second stage.";
return false;
}
if (!FileExists(kEncryptedStatefulRollbackDataPath)) {
LOG(ERROR) << "CheckSecondStage: Rollback data "
<< kEncryptedStatefulRollbackDataPath.value()
<< " should exist in second stage.";
return false;
}
LOG(INFO) << "CheckSecondStage: OK.";
return true;
}
bool OobeConfig::CheckThirdStage() const {
if (!FileExists(kSecondStageCompletedFile)) {
LOG(INFO) << "CheckThirdStage: Second stage not yet completed.";
return false;
}
// At this point, we should be in the third stage. We verify, that the other
// files are in a consistent state.
if (!FileExists(kFirstStageCompletedFile)) {
LOG(ERROR) << "CheckThirdStage: First stage should be already completed.";
return false;
}
if (FileExists(kUnencryptedStatefulRollbackDataPath)) {
LOG(ERROR) << "CheckThirdStage: Rollback data "
<< kUnencryptedStatefulRollbackDataPath.value()
<< " should not exist in third stage.";
return false;
}
if (!FileExists(kEncryptedStatefulRollbackDataPath)) {
LOG(ERROR) << "CheckThirdStage: Rollback data "
<< kEncryptedStatefulRollbackDataPath.value()
<< " should exist in third stage.";
return false;
}
LOG(INFO) << "CheckThirdStage: OK.";
return true;
}
bool OobeConfig::ShouldSaveRollbackData() const {
return FileExists(kRollbackSaveMarkerFile);
}
bool OobeConfig::DeleteRollbackSaveFlagFile() const {
return base::DeleteFile(
GetPrefixedFilePath(kRollbackSaveMarkerFile), false);
}
} // namespace oobe_config