You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
340 lines
10 KiB
340 lines
10 KiB
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "verity/hash_tree_builder.h"
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <memory>
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/stringprintf.h>
|
|
#include <android-base/strings.h>
|
|
#include <android-base/unique_fd.h>
|
|
#include <openssl/bn.h>
|
|
|
|
#include "build_verity_tree_utils.h"
|
|
|
|
const EVP_MD* HashTreeBuilder::HashFunction(const std::string& hash_name) {
|
|
if (android::base::EqualsIgnoreCase(hash_name, "sha1")) {
|
|
return EVP_sha1();
|
|
}
|
|
if (android::base::EqualsIgnoreCase(hash_name, "sha256")) {
|
|
return EVP_sha256();
|
|
}
|
|
if (android::base::EqualsIgnoreCase(hash_name, "sha384")) {
|
|
return EVP_sha384();
|
|
}
|
|
if (android::base::EqualsIgnoreCase(hash_name, "sha512")) {
|
|
return EVP_sha512();
|
|
}
|
|
if (android::base::EqualsIgnoreCase(hash_name, "blake2b-256")) {
|
|
return EVP_blake2b256();
|
|
}
|
|
|
|
LOG(ERROR) << "Unsupported hash algorithm " << hash_name;
|
|
return nullptr;
|
|
}
|
|
|
|
HashTreeBuilder::HashTreeBuilder(size_t block_size, const EVP_MD* md)
|
|
: block_size_(block_size), data_size_(0), md_(md) {
|
|
CHECK(md_ != nullptr) << "Failed to initialize md";
|
|
|
|
hash_size_raw_ = EVP_MD_size(md_);
|
|
|
|
// Round up the hash size to the next power of 2.
|
|
hash_size_ = 1;
|
|
while (hash_size_ < hash_size_raw_) {
|
|
hash_size_ = hash_size_ << 1;
|
|
}
|
|
CHECK_LT(hash_size_ * 2, block_size_);
|
|
}
|
|
|
|
std::string HashTreeBuilder::BytesArrayToString(
|
|
const std::vector<unsigned char>& bytes) {
|
|
std::string result;
|
|
for (const auto& c : bytes) {
|
|
result += android::base::StringPrintf("%02x", c);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool HashTreeBuilder::ParseBytesArrayFromString(
|
|
const std::string& hex_string, std::vector<unsigned char>* bytes) {
|
|
if (hex_string.size() % 2 != 0) {
|
|
LOG(ERROR) << "Hex string size must be even number " << hex_string;
|
|
return false;
|
|
}
|
|
|
|
BIGNUM* bn = nullptr;
|
|
if (!BN_hex2bn(&bn, hex_string.c_str())) {
|
|
LOG(ERROR) << "Failed to parse hex in " << hex_string;
|
|
return false;
|
|
}
|
|
std::unique_ptr<BIGNUM, decltype(&BN_free)> guard(bn, BN_free);
|
|
|
|
size_t bytes_size = BN_num_bytes(bn);
|
|
bytes->resize(bytes_size);
|
|
if (BN_bn2bin(bn, bytes->data()) != bytes_size) {
|
|
LOG(ERROR) << "Failed to convert hex to bytes " << hex_string;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
uint64_t HashTreeBuilder::CalculateSize(
|
|
uint64_t input_size, size_t block_size, size_t hash_size) {
|
|
uint64_t verity_blocks = 0;
|
|
size_t level_blocks;
|
|
size_t levels = 0;
|
|
do {
|
|
level_blocks =
|
|
verity_tree_blocks(input_size, block_size, hash_size, levels);
|
|
levels++;
|
|
verity_blocks += level_blocks;
|
|
} while (level_blocks > 1);
|
|
|
|
return verity_blocks * block_size;
|
|
}
|
|
|
|
bool HashTreeBuilder::Initialize(int64_t expected_data_size,
|
|
const std::vector<unsigned char>& salt) {
|
|
data_size_ = expected_data_size;
|
|
salt_ = salt;
|
|
|
|
if (data_size_ % block_size_ != 0) {
|
|
LOG(ERROR) << "file size " << data_size_
|
|
<< " is not a multiple of block size " << block_size_;
|
|
return false;
|
|
}
|
|
|
|
// Reserve enough space for the hash of the input data.
|
|
size_t base_level_blocks =
|
|
verity_tree_blocks(data_size_, block_size_, hash_size_, 0);
|
|
std::vector<unsigned char> base_level;
|
|
base_level.reserve(base_level_blocks * block_size_);
|
|
verity_tree_.emplace_back(std::move(base_level));
|
|
|
|
// Save the hash of the zero block to avoid future recalculation.
|
|
std::vector<unsigned char> zero_block(block_size_, 0);
|
|
zero_block_hash_.resize(hash_size_);
|
|
HashBlock(zero_block.data(), zero_block_hash_.data());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HashTreeBuilder::HashBlock(const unsigned char* block,
|
|
unsigned char* out) {
|
|
unsigned int s;
|
|
int ret = 1;
|
|
|
|
EVP_MD_CTX* mdctx = EVP_MD_CTX_create();
|
|
CHECK(mdctx != nullptr);
|
|
ret &= EVP_DigestInit_ex(mdctx, md_, nullptr);
|
|
ret &= EVP_DigestUpdate(mdctx, salt_.data(), salt_.size());
|
|
ret &= EVP_DigestUpdate(mdctx, block, block_size_);
|
|
ret &= EVP_DigestFinal_ex(mdctx, out, &s);
|
|
EVP_MD_CTX_destroy(mdctx);
|
|
|
|
CHECK_EQ(1, ret);
|
|
CHECK_EQ(hash_size_raw_, s);
|
|
std::fill(out + s, out + hash_size_, 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HashTreeBuilder::HashBlocks(const unsigned char* data, size_t len,
|
|
std::vector<unsigned char>* output_vector) {
|
|
if (len == 0) {
|
|
return true;
|
|
}
|
|
CHECK_EQ(0, len % block_size_);
|
|
|
|
if (data == nullptr) {
|
|
for (size_t i = 0; i < len; i += block_size_) {
|
|
output_vector->insert(output_vector->end(), zero_block_hash_.begin(),
|
|
zero_block_hash_.end());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
for (size_t i = 0; i < len; i += block_size_) {
|
|
unsigned char hash_buffer[hash_size_];
|
|
if (!HashBlock(data + i, hash_buffer)) {
|
|
return false;
|
|
}
|
|
output_vector->insert(output_vector->end(), hash_buffer,
|
|
hash_buffer + hash_size_);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HashTreeBuilder::Update(const unsigned char* data, size_t len) {
|
|
CHECK_GT(data_size_, 0);
|
|
|
|
if (!leftover_.empty()) {
|
|
CHECK_LT(leftover_.size(), block_size_);
|
|
size_t append_len = std::min(len, block_size_ - leftover_.size());
|
|
if (data == nullptr) {
|
|
leftover_.insert(leftover_.end(), append_len, 0);
|
|
} else {
|
|
leftover_.insert(leftover_.end(), data, data + append_len);
|
|
}
|
|
if (leftover_.size() < block_size_) {
|
|
return true;
|
|
}
|
|
if (!HashBlocks(leftover_.data(), leftover_.size(), &verity_tree_[0])) {
|
|
return false;
|
|
}
|
|
leftover_.clear();
|
|
if (data != nullptr) {
|
|
data += append_len;
|
|
}
|
|
len -= append_len;
|
|
}
|
|
if (len % block_size_ != 0) {
|
|
if (data == nullptr) {
|
|
leftover_.assign(len % block_size_, 0);
|
|
} else {
|
|
leftover_.assign(data + len - len % block_size_, data + len);
|
|
}
|
|
len -= len % block_size_;
|
|
}
|
|
return HashBlocks(data, len, &verity_tree_[0]);
|
|
}
|
|
|
|
bool HashTreeBuilder::CalculateRootDigest(const std::vector<unsigned char>& root_verity,
|
|
std::vector<unsigned char>* root_digest) {
|
|
if (root_verity.size() != block_size_) {
|
|
return false;
|
|
}
|
|
return HashBlocks(root_verity.data(), block_size_, root_digest);
|
|
}
|
|
|
|
bool HashTreeBuilder::BuildHashTree() {
|
|
// Expects only the base level in the verity_tree_.
|
|
CHECK_EQ(1, verity_tree_.size());
|
|
|
|
if (!leftover_.empty()) {
|
|
LOG(ERROR) << leftover_.size() << " bytes data left from last Update().";
|
|
return false;
|
|
}
|
|
|
|
// Expects the base level to have the same size as the total hash size of
|
|
// input data.
|
|
AppendPaddings(&verity_tree_.back());
|
|
size_t base_level_blocks =
|
|
verity_tree_blocks(data_size_, block_size_, hash_size_, 0);
|
|
CHECK_EQ(base_level_blocks * block_size_, verity_tree_[0].size());
|
|
|
|
while (verity_tree_.back().size() > block_size_) {
|
|
const auto& current_level = verity_tree_.back();
|
|
// Computes the next level of the verity tree based on the hash of the
|
|
// current level.
|
|
size_t next_level_blocks =
|
|
verity_tree_blocks(current_level.size(), block_size_, hash_size_, 0);
|
|
std::vector<unsigned char> next_level;
|
|
next_level.reserve(next_level_blocks * block_size_);
|
|
|
|
HashBlocks(current_level.data(), current_level.size(), &next_level);
|
|
AppendPaddings(&next_level);
|
|
|
|
// Checks the size of the next level.
|
|
CHECK_EQ(next_level_blocks * block_size_, next_level.size());
|
|
verity_tree_.emplace_back(std::move(next_level));
|
|
}
|
|
|
|
CHECK_EQ(block_size_, verity_tree_.back().size());
|
|
return CalculateRootDigest(verity_tree_.back(), &root_hash_);
|
|
}
|
|
|
|
bool HashTreeBuilder::CheckHashTree(
|
|
const std::vector<unsigned char>& hash_tree) const {
|
|
size_t offset = 0;
|
|
// Reads reversely to output the verity tree top-down.
|
|
for (size_t i = verity_tree_.size(); i > 0; i--) {
|
|
const auto& level_blocks = verity_tree_[i - 1];
|
|
if (offset + level_blocks.size() > hash_tree.size()) {
|
|
LOG(ERROR) << "Hash tree too small: " << hash_tree.size();
|
|
return false;
|
|
}
|
|
auto iter = std::mismatch(level_blocks.begin(), level_blocks.end(),
|
|
hash_tree.begin() + offset)
|
|
.first;
|
|
if (iter != level_blocks.end()) {
|
|
LOG(ERROR) << "Mismatch found at the hash tree level " << i << " offset "
|
|
<< std::distance(level_blocks.begin(), iter);
|
|
return false;
|
|
}
|
|
offset += level_blocks.size();
|
|
}
|
|
if (offset != hash_tree.size()) {
|
|
LOG(ERROR) << "Hash tree size mismatch: " << hash_tree.size()
|
|
<< " != " << offset;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool HashTreeBuilder::WriteHashTreeToFile(const std::string& output) const {
|
|
android::base::unique_fd output_fd(
|
|
open(output.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666));
|
|
if (output_fd == -1) {
|
|
PLOG(ERROR) << "Failed to open output file " << output;
|
|
return false;
|
|
}
|
|
|
|
return WriteHashTreeToFd(output_fd, 0);
|
|
}
|
|
|
|
bool HashTreeBuilder::WriteHashTree(
|
|
std::function<bool(const void*, size_t)> callback) const {
|
|
CHECK(!verity_tree_.empty());
|
|
|
|
// Reads reversely to output the verity tree top-down.
|
|
for (size_t i = verity_tree_.size(); i > 0; i--) {
|
|
const auto& level_blocks = verity_tree_[i - 1];
|
|
if (!callback(level_blocks.data(), level_blocks.size())) {
|
|
PLOG(ERROR) << "Failed to write the hash tree level " << i;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HashTreeBuilder::WriteHashTreeToFd(int fd, uint64_t offset) const {
|
|
CHECK(!verity_tree_.empty());
|
|
|
|
if (lseek(fd, offset, SEEK_SET) != offset) {
|
|
PLOG(ERROR) << "Failed to seek the output fd, offset: " << offset;
|
|
return false;
|
|
}
|
|
|
|
return WriteHashTree([fd](auto data, auto size) {
|
|
return android::base::WriteFully(fd, data, size);
|
|
});
|
|
}
|
|
|
|
void HashTreeBuilder::AppendPaddings(std::vector<unsigned char>* data) {
|
|
size_t remainder = data->size() % block_size_;
|
|
if (remainder != 0) {
|
|
data->resize(data->size() + block_size_ - remainder, 0);
|
|
}
|
|
}
|