/* * 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 #include #include #include #include #include #include #include #include #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& 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* 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 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& 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 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 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* 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& root_verity, std::vector* 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 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& 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 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* data) { size_t remainder = data->size() % block_size_; if (remainder != 0) { data->resize(data->size() + block_size_ - remainder, 0); } }