blob: ac059d97c21f493751a754c0fe8470c4d9d42902 [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/file_utils.h"
#include <algorithm>
#include <memory>
#include <utility>
#if USE_SELINUX
#include <selinux/selinux.h>
#endif
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <utime.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/threading/scoped_blocking_call.h>
#include <brillo/data_encoding.h>
#include <crypto/rsa_private_key.h>
#include <crypto/signature_creator.h>
#include <crypto/signature_verifier.h>
#include "crypto/sha2.h"
#include <openssl/sha.h>
#include <base/command_line.h>
#include <base/process/launch.h>
#include <brillo/process/process.h>
namespace arc {
namespace data_snapshotd {
namespace {
constexpr char kHashFile[] = "hash";
constexpr char kPublicKeyFile[] = "public_key_info";
constexpr char kUserhashFile[] = "userhash";
} // namespace
bool ReadSnapshotDirectory(const base::FilePath& dir,
SnapshotDirectory* snapshot_directory,
bool inode_verification_enabled) {
if (!snapshot_directory) {
LOG(ERROR) << "snapshot_directory is nullptr";
return false;
}
base::FileEnumerator dir_enumerator(
dir, true /* recursive */,
base::FileEnumerator::FileType::DIRECTORIES |
base::FileEnumerator::FileType::FILES |
base::FileEnumerator::FileType::SHOW_SYM_LINKS);
std::vector<SnapshotFile> snapshot_files;
for (auto file = dir_enumerator.Next(); !file.empty();
file = dir_enumerator.Next()) {
base::FilePath relative_path;
if (!dir.IsParent(file) || !dir.AppendRelativePath(file, &relative_path)) {
LOG(ERROR) << dir.value() << " is not a parent of " << file.value();
return false;
}
SnapshotFile snapshot_file;
snapshot_file.set_name(relative_path.value());
std::string contents;
if (!dir_enumerator.GetInfo().IsDirectory() && !base::IsLink(file) &&
dir_enumerator.GetInfo().GetSize() != 0 &&
!base::ReadFileToString(file, &contents)) {
LOG(ERROR) << "Failed to read file " << file.value();
return false;
}
{
std::vector<uint8_t> digest;
digest.resize(SHA256_DIGEST_LENGTH);
if (!SHA256((const unsigned char*)contents.data(), contents.size(),
digest.data())) {
LOG(ERROR) << "Failed to calculate digest of file contents.";
return false;
}
snapshot_file.set_content_hash(digest.data(), digest.size());
}
#if USE_SELINUX
char* con = nullptr;
if (lgetfilecon(file.value().c_str(), &con) < 0) {
PLOG(ERROR) << "Failed to getfilecon of file " << file.value();
return false;
}
snapshot_file.set_selinux_context(con, strlen(con));
if (con != nullptr) {
free(con);
}
#endif // USE_SELINUX
struct stat stat_buf;
if (lstat(file.value().c_str(), &stat_buf)) {
PLOG(ERROR) << "Failed to get stat of file " << file.value();
return false;
}
Stat* stat_value = snapshot_file.mutable_stat();
if (inode_verification_enabled)
stat_value->set_ino(stat_buf.st_ino);
stat_value->set_mode(stat_buf.st_mode);
stat_value->set_uid(stat_buf.st_uid);
stat_value->set_gid(stat_buf.st_gid);
stat_value->set_size(stat_buf.st_size);
stat_value->set_modification_time(stat_buf.st_mtime);
snapshot_files.emplace_back(snapshot_file);
}
std::sort(snapshot_files.begin(), snapshot_files.end(),
[](const SnapshotFile& a, const SnapshotFile& b) {
// Sort lexicographically by name.
return a.name() < b.name();
});
for (auto file : snapshot_files) {
if (file.name() == kHashFile)
continue;
if (file.name() == kPublicKeyFile)
continue;
*snapshot_directory->mutable_files()->Add() = file;
}
return true;
}
std::vector<uint8_t> CalculateDirectoryCryptographicHash(
const SnapshotDirectory& dir) {
std::string serialized;
if (!dir.SerializeToString(&serialized)) {
LOG(ERROR) << "Failed to serialize to string snapshot directory info.";
return {};
}
std::string hash = crypto::SHA256HashString(serialized);
return std::vector<uint8_t>(hash.begin(), hash.end());
}
bool StorePublicKey(const base::FilePath& dir,
const std::string& encoded_public_key) {
if (encoded_public_key.empty()) {
LOG(ERROR) << "Empty public key info";
return false;
}
if (!base::DirectoryExists(dir)) {
LOG(ERROR) << "Directory " << dir.value() << " does not exist.";
return false;
}
auto public_key_file = dir.Append(kPublicKeyFile);
if (!base::WriteFile(public_key_file, encoded_public_key.data(),
encoded_public_key.length())) {
LOG(ERROR) << "Failed to write public key info to file "
<< public_key_file.value();
return false;
}
return true;
}
bool StoreUserhash(const base::FilePath& dir, const std::string& userhash) {
if (userhash.empty()) {
LOG(ERROR) << "Empty user hash";
return false;
}
if (!base::DirectoryExists(dir)) {
LOG(ERROR) << "Directory " << dir.value() << " does not exist.";
return false;
}
if (!base::WriteFile(dir.Append(kUserhashFile), userhash.c_str(),
userhash.size())) {
LOG(ERROR) << "Failed to write userhash to file "
<< dir.Append(kUserhashFile);
return false;
}
return true;
}
bool SignAndStoreHash(const base::FilePath& dir,
crypto::RSAPrivateKey* private_key,
bool inode_verification_enabled) {
if (!private_key) {
LOG(ERROR) << "nullptr private key";
return false;
}
if (!base::DirectoryExists(dir)) {
LOG(ERROR) << "Directory " << dir.value() << " does not exist.";
return false;
}
SnapshotDirectory snapshot_dir;
if (!ReadSnapshotDirectory(dir, &snapshot_dir, inode_verification_enabled)) {
return false;
}
std::vector<uint8_t> hash = CalculateDirectoryCryptographicHash(snapshot_dir);
if (hash.empty()) {
return false;
}
std::vector<uint8_t> signature;
std::unique_ptr<crypto::SignatureCreator> signer(
crypto::SignatureCreator::Create(private_key,
crypto::SignatureCreator::SHA256));
if (!signer->Update(hash.data(), hash.size())) {
LOG(ERROR) << "Failed to update signing data of directory contents: "
<< dir.value();
return false;
}
if (!signer->Final(&signature)) {
LOG(ERROR) << "Failed to sign directory contents: " << dir.value();
return false;
}
std::string encoded_signature =
brillo::data_encoding::Base64Encode(signature.data(), signature.size());
if (!base::WriteFile(dir.Append(kHashFile), encoded_signature.data(),
encoded_signature.length())) {
LOG(ERROR) << "Failed to write a signature to file "
<< dir.Append(kHashFile);
return false;
}
return true;
}
bool VerifyHash(const base::FilePath& dir,
const std::string& expected_userhash,
const std::string& expected_public_key_digest,
bool inode_verification_enabled) {
if (!base::DirectoryExists(dir)) {
LOG(ERROR) << "Directory " << dir.value() << " does not exist.";
return false;
}
if (expected_public_key_digest.empty()) {
LOG(ERROR) << "Public key digest is empty.";
return false;
}
std::string userhash;
if (!base::ReadFileToString(dir.Append(kUserhashFile), &userhash)) {
LOG(ERROR) << "Failed to read userhash for file "
<< dir.Append(kUserhashFile);
return false;
}
if (userhash != expected_userhash) {
LOG(ERROR) << "Requested to load snapshot for unsupported account.";
return false;
}
std::string encoded_public_key;
if (!base::ReadFileToString(dir.Append(kPublicKeyFile),
&encoded_public_key)) {
LOG(ERROR) << "Failed to read public key info from file "
<< dir.Append(kPublicKeyFile);
return false;
}
std::vector<uint8_t> public_key;
if (!brillo::data_encoding::Base64Decode(encoded_public_key, &public_key)) {
LOG(ERROR) << "Failed to decode public key.";
return false;
}
std::string encoded_public_key_digest =
CalculateEncodedSha256Digest(public_key);
if (encoded_public_key_digest.empty()) {
LOG(ERROR) << "Calculated encoded sha256 digest failed";
return false;
}
if (encoded_public_key_digest.compare(expected_public_key_digest)) {
LOG(ERROR) << "Public key has been modified.";
return false;
}
std::string contents;
if (!base::ReadFileToString(dir.Append(kHashFile), &contents)) {
LOG(ERROR) << "Failed to read signed hash from file "
<< dir.Append(kHashFile);
return false;
}
std::vector<uint8_t> signature;
if (!brillo::data_encoding::Base64Decode(contents, &signature)) {
LOG(ERROR) << "Failed to decode signature.";
return false;
}
crypto::SignatureVerifier verifier;
if (!verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA256,
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size(),
reinterpret_cast<const uint8_t*>(public_key.data()),
public_key.size())) {
LOG(ERROR) << "Failed to initilize signature verifier.";
return false;
}
SnapshotDirectory snapshot_dir;
if (!ReadSnapshotDirectory(dir, &snapshot_dir, inode_verification_enabled)) {
LOG(ERROR) << "Read snapshot directory failed";
return false;
}
std::vector<uint8_t> hash = CalculateDirectoryCryptographicHash(snapshot_dir);
verifier.VerifyUpdate(hash.data(), hash.size());
return verifier.VerifyFinal();
}
std::string CalculateEncodedSha256Digest(const std::vector<uint8_t>& value) {
// Store a new public key digest.
std::vector<uint8_t> digest;
digest.resize(SHA256_DIGEST_LENGTH);
if (!SHA256((const unsigned char*)value.data(), value.size(),
digest.data()) ||
digest.empty()) {
LOG(ERROR) << "Failed to calculate digest of public key.";
return "";
}
return brillo::data_encoding::Base64Encode(digest.data(), digest.size());
}
bool CopySnapshotDirectory(const base::FilePath& from,
const base::FilePath& to) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
base::CommandLine cmd{base::FilePath("/bin/cp")};
cmd.AppendArg("-r");
cmd.AppendArg("--preserve=all");
cmd.AppendArg(from.value().c_str());
cmd.AppendArg(to.value().c_str());
int exit_code;
auto p = base::LaunchProcess(cmd, base::LaunchOptions());
if (!p.WaitForExitWithTimeout(base::TimeDelta::FromSeconds(30), &exit_code) ||
exit_code != EXIT_SUCCESS) {
LOG(ERROR) << "Copy snapshot directory failed: from " << from.value()
<< " to " << to.value();
return false;
}
return true;
}
} // namespace data_snapshotd
} // namespace arc