blob: 64df7cafdf52f5b7bd262984ddf232677bd6dd84 [file] [log] [blame]
// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by the GPL v2 license that can
// be found in the LICENSE file.
//
// Implementation of FileHasher
#define __STDC_LIMIT_MACROS 1
#define __STDC_FORMAT_MACROS 1
#include <errno.h>
#include <inttypes.h>
#include <linux/fs.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <string>
#include <vector>
#include <base/bits.h>
#include <base/check.h>
#include <base/files/file.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include "verity/file_hasher.h"
namespace verity {
namespace {
// |base::File| doesn't have a good way of getting block device's size. So we
// have to do linux trickery here.
int64_t GetFileSize(base::File* file) {
struct stat statbuf;
int rc = fstat(file->GetPlatformFile(), &statbuf);
PLOG_IF(FATAL, rc != 0) << "Failed to get file status";
if (S_ISBLK(statbuf.st_mode)) {
int64_t size = 0;
rc = ioctl(file->GetPlatformFile(), BLKGETSIZE64, &size);
PLOG_IF(FATAL, rc != 0) << "Failed to get block size of input device";
return size;
}
return file->GetLength();
}
} // namespace
FileHasher::~FileHasher() {
if (initialized_)
dm_bht_destroy(&tree_);
}
bool FileHasher::Initialize() {
CHECK(!initialized_);
if (!alg_ || !source_ || !destination_) {
LOG(ERROR) << "Invalid arguments supplied to ctor.";
LOG(INFO) << "s: " << source_ << " d: " << destination_;
return false;
}
int64_t source_size = GetFileSize(source_.get());
if (source_size < 0) {
PLOG(ERROR) << "Failed to get the file size";
return false;
}
if (block_limit_ > source_size / PAGE_SIZE) {
LOG(ERROR) << block_limit_ << " blocks exceeds image size of "
<< source_size;
return false;
} else if (block_limit_ == 0) {
block_limit_ = source_size / PAGE_SIZE;
if (source_size % PAGE_SIZE) {
LOG(ERROR) << "The source file size must be divisible by the block size, "
<< "Size: " << source_size;
LOG(INFO) << "Suggested size: "
<< base::bits::Align(source_size, PAGE_SIZE);
return false;
}
}
// Now we initialize the tree
if (dm_bht_create(&tree_, block_limit_, alg_)) {
LOG(ERROR) << "Could not create the BH tree";
return false;
}
sectors_ = dm_bht_sectors(&tree_);
hash_data_.resize(verity_to_bytes(sectors_));
// No reading is needed.
dm_bht_set_read_cb(&tree_, dm_bht_zeroread_callback);
dm_bht_set_buffer(&tree_, hash_data_.data());
initialized_ = true;
return true;
}
bool FileHasher::Store() {
return destination_->WriteAtCurrentPos(hash_data_.data(),
hash_data_.size()) >= 0;
}
bool FileHasher::Hash() {
// TODO(wad) abstract size when dm-bht needs to do break from PAGE_SIZE
uint8_t block_data[PAGE_SIZE];
uint32_t block = 0;
while (block < block_limit_) {
if (source_->ReadAtCurrentPos(reinterpret_cast<char*>(block_data),
PAGE_SIZE) < 0) {
PLOG(ERROR) << "Failed to read for block: " << block;
return false;
}
if (dm_bht_store_block(&tree_, block, block_data)) {
LOG(ERROR) << "Failed to store block " << block;
return false;
}
++block;
}
return !dm_bht_compute(&tree_);
}
void FileHasher::set_salt(const char* salt) {
if (!strcmp(salt, "random"))
salt = RandomSalt();
dm_bht_set_salt(&tree_, salt);
salt_ = salt;
}
const char* FileHasher::RandomSalt() {
char buf[DM_BHT_SALT_SIZE];
base::FilePath urandom_path("/dev/urandom");
base::File source(urandom_path,
base::File::FLAG_OPEN | base::File::FLAG_READ);
LOG_IF(FATAL, !source.IsValid())
<< "Failed to open the random source: " << urandom_path;
PLOG_IF(FATAL, source.ReadAtCurrentPos(buf, sizeof(buf)) < 0)
<< "Failed to read the random source";
for (size_t i = 0; i < sizeof(buf); ++i) {
// NOLINTNEXTLINE(runtime/printf)
sprintf(&random_salt_[i * 2], "%02x", buf[i]);
}
random_salt_[sizeof(random_salt_) - 1] = '\0';
return random_salt_;
}
std::string FileHasher::GetTable(bool colocated) {
// Grab the digest (up to 1kbit supported)
uint8_t digest[128];
char hexsalt[DM_BHT_SALT_SIZE * 2 + 1];
bool have_salt;
dm_bht_root_hexdigest(&tree_, digest, sizeof(digest));
have_salt = dm_bht_salt(&tree_, hexsalt) == 0;
// TODO(wad) later support sizes that need 64-bit sectors.
unsigned int hash_start = 0;
unsigned int root_end = to_sector(block_limit_ << PAGE_SHIFT);
if (colocated)
hash_start = root_end;
std::vector<std::string> parts = {
"0",
base::NumberToString(root_end),
"verity",
"payload=ROOT_DEV",
"hashtree=HASH_DEV",
"hashstart=" + base::NumberToString(hash_start),
"alg=" + std::string(alg_),
"root_hexdigest=" + std::string(reinterpret_cast<char*>(digest)),
};
if (have_salt)
parts.push_back("salt=" + std::string(hexsalt));
return base::JoinString(parts, " ");
}
void FileHasher::PrintTable(bool colocated) {
printf("%s\n", GetTable(colocated).c_str());
}
} // namespace verity