blob: a2c3c0afe8f050a64cfb51d5e94945e917c4b5de [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 <digest/merkle-tree.h>
#include <stdint.h>
#include <string.h>
#include <digest/digest.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/unique_ptr.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
namespace digest {
// Size of a node in bytes. Defined in tree.h.
constexpr size_t MerkleTree::kNodeSize;
// The number of digests that fit in a node. Importantly, if L is a
// node-aligned length in one level of the Merkle tree, |L / kDigestsPerNode| is
// the corresponding digest-aligned length in the next level up.
const size_t kDigestsPerNode = MerkleTree::kNodeSize / Digest::kLength;
namespace {
// Digest wrapper functions. These functions implement how a node in the Merkle
// tree is hashed:
// digest = Hash((offset | level) + length + node_data + padding)
// where:
// * offset is from the start of the VMO.
// * level is the height of the node in the tree (data nodes have level == 0).
// * length is the node size, e.g kNodeSize except possibly for the last node.
// * node_data is the actual bytes from the node.
// * padding is |kNodeSize - length| zeros.
// Wrapper for Digest::Init. This primes the working |digest| initializing it
// and hashing the |locality| and |length|.
zx_status_t DigestInit(Digest* digest, uint64_t locality, size_t length) {
zx_status_t rc;
ZX_DEBUG_ASSERT(digest);
ZX_DEBUG_ASSERT(length < UINT32_MAX);
if ((rc = digest->Init()) != ZX_OK) {
return rc;
}
digest->Update(&locality, sizeof(locality));
uint32_t len32 = static_cast<uint32_t>(fbl::min(length, MerkleTree::kNodeSize));
digest->Update(&len32, sizeof(len32));
return ZX_OK;
}
// Wrapper for Digest::Update. This will hash data from |in|, either |length|
// bytes or up to the next node boundary, as determined from |offset|. Returns
// the number of bytes hashed.
size_t DigestUpdate(Digest* digest, const uint8_t* in, size_t offset, size_t length) {
ZX_DEBUG_ASSERT(digest);
// Check if length crosses a node boundary
length = fbl::min(length, MerkleTree::kNodeSize - (offset % MerkleTree::kNodeSize));
digest->Update(in, length);
return length;
}
// Wrapper for Digest::Final. This pads the hashed data with zeros up to a
// node boundary before finalizing the digest.
void DigestFinal(Digest* digest, size_t offset) {
offset = offset % MerkleTree::kNodeSize;
if (offset != 0) {
size_t pad_len = MerkleTree::kNodeSize - offset;
uint8_t pad[pad_len];
memset(pad, 0, pad_len);
digest->Update(pad, pad_len);
}
digest->Final();
}
////////
// Helper functions for working between levels of the tree.
// Helper function to transform a length in the current level to a length in the
// next level up.
size_t NextLength(size_t length) {
if (length > MerkleTree::kNodeSize) {
return fbl::round_up(length, MerkleTree::kNodeSize) / kDigestsPerNode;
} else {
return 0;
}
}
// Helper function to transform a length in the current level to a node-aligned
// length in the next level up.
size_t NextAligned(size_t length) {
return fbl::round_up(NextLength(length), MerkleTree::kNodeSize);
}
} // namespace
////////
// Creation methods
size_t MerkleTree::GetTreeLength(size_t data_len) {
size_t next_len = NextAligned(data_len);
return (next_len == 0 ? 0 : next_len + GetTreeLength(next_len));
}
zx_status_t MerkleTree::Create(const void* data, size_t data_len, void* tree, size_t tree_len,
Digest* digest) {
zx_status_t rc;
MerkleTree mt;
if ((rc = mt.CreateInit(data_len, tree_len)) != ZX_OK ||
(rc = mt.CreateUpdate(data, data_len, tree)) != ZX_OK ||
(rc = mt.CreateFinal(tree, digest)) != ZX_OK) {
return rc;
}
return ZX_OK;
}
MerkleTree::MerkleTree() : initialized_(false), next_(nullptr), level_(0), offset_(0), length_(0) {}
MerkleTree::~MerkleTree() {}
zx_status_t MerkleTree::CreateInit(size_t data_len, size_t tree_len) {
initialized_ = true;
offset_ = 0;
length_ = data_len;
// Data fits in a single node, making this the top level of the tree.
if (data_len <= kNodeSize) {
return ZX_OK;
}
fbl::AllocChecker ac;
next_.reset(new (&ac) MerkleTree());
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
next_->level_ = level_ + 1;
// Ascend the tree.
data_len = NextAligned(data_len);
if (tree_len < data_len) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
tree_len -= data_len;
return next_->CreateInit(data_len, tree_len);
}
zx_status_t MerkleTree::CreateUpdate(const void* data, size_t length, void* tree) {
ZX_DEBUG_ASSERT(offset_ + length >= offset_);
// Must call CreateInit first.
if (!initialized_) {
return ZX_ERR_BAD_STATE;
}
// Early exit if no work to do.
if (length == 0) {
return ZX_OK;
}
// Must not overrun expected length.
if (offset_ + length > length_) {
return ZX_ERR_OUT_OF_RANGE;
}
// Must have data to read and a tree to fill if expecting more than one
// digest.
if (!data || (!tree && length_ > kNodeSize)) {
return ZX_ERR_INVALID_ARGS;
}
// Save pointers to the data, digest, and the next level tree.
const uint8_t* in = static_cast<const uint8_t*>(data);
size_t tree_off = (offset_ - (offset_ % kNodeSize)) / kDigestsPerNode;
uint8_t* out = static_cast<uint8_t*>(tree) + tree_off;
void* next = static_cast<uint8_t*>(tree) + NextAligned(length_);
// Consume the data.
zx_status_t rc = ZX_OK;
while (length > 0 && rc == ZX_OK) {
// Check if this is the start of a node.
if (offset_ % kNodeSize == 0 &&
(rc = DigestInit(&digest_, offset_ | level_, length_ - offset_)) != ZX_OK) {
break;
}
// Hash the node data.
size_t chunk = DigestUpdate(&digest_, in, offset_, length);
in += chunk;
offset_ += chunk;
length -= chunk;
// Done if not at the end of a node.
if (offset_ % kNodeSize != 0 && offset_ != length_) {
break;
}
DigestFinal(&digest_, offset_);
// Done if at the top of the tree.
if (length_ <= kNodeSize) {
break;
}
// If this is the first digest in a new node, first initialize it.
if (tree_off % kNodeSize == 0) {
memset(out, 0, kNodeSize);
}
// Add the digest and ascend the tree.
digest_.CopyTo(out, Digest::kLength);
rc = next_->CreateUpdate(out, Digest::kLength, next);
out += Digest::kLength;
tree_off += Digest::kLength;
}
return rc;
}
zx_status_t MerkleTree::CreateFinal(void* tree, Digest* root) {
return CreateFinalInternal(nullptr, tree, root);
}
zx_status_t MerkleTree::CreateFinalInternal(const void* data, void* tree, Digest* root) {
zx_status_t rc;
// Must call CreateInit first. Must call CreateUpdate with all data first.
if (!initialized_ || (level_ == 0 && offset_ != length_)) {
return ZX_ERR_BAD_STATE;
}
// Must have root to write and a tree to fill if expecting more than one
// digest.
if (!root || (!tree && length_ > kNodeSize)) {
return ZX_ERR_INVALID_ARGS;
}
// Special case: the level is empty.
if (length_ == 0) {
if ((rc = DigestInit(&digest_, 0, 0)) != ZX_OK) {
return rc;
}
DigestFinal(&digest_, 0);
}
// Consume padding if needed.
const uint8_t* tail = static_cast<const uint8_t*>(data) + offset_;
if ((rc = CreateUpdate(tail, length_ - offset_, tree)) != ZX_OK) {
return rc;
}
initialized_ = false;
// If the top, save the digest as the Merkle tree root and return.
if (length_ <= kNodeSize) {
*root = digest_.AcquireBytes();
digest_.ReleaseBytes();
return ZX_OK;
}
// Finalize the next level up.
uint8_t* next = static_cast<uint8_t*>(tree) + NextAligned(length_);
return next_->CreateFinalInternal(tree, next, root);
}
////////
// Verification methods
zx_status_t MerkleTree::Verify(const void* data, size_t data_len, const void* tree, size_t tree_len,
size_t offset, size_t length, const Digest& root) {
uint64_t level = 0;
size_t root_len = data_len;
while (data_len > kNodeSize) {
zx_status_t rc;
// Verify the data in this level.
if ((rc = VerifyLevel(data, data_len, tree, offset, length, level)) != ZX_OK) {
return rc;
}
// Ascend to the next level up.
data = tree;
root_len = NextLength(data_len);
data_len = NextAligned(data_len);
tree = static_cast<const uint8_t*>(tree) + data_len;
if (tree_len < data_len) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
tree_len -= data_len;
offset /= kDigestsPerNode;
length /= kDigestsPerNode;
++level;
}
return VerifyRoot(data, root_len, level, root);
}
zx_status_t MerkleTree::VerifyRoot(const void* data, size_t root_len, uint64_t level,
const Digest& expected) {
zx_status_t rc;
// Must have data if length isn't 0. Must have either zero or one node.
if ((!data && root_len != 0) || root_len > kNodeSize) {
return ZX_ERR_INVALID_ARGS;
}
const uint8_t* in = static_cast<const uint8_t*>(data);
Digest actual;
// We have up to one node if at tree bottom, exactly one node otherwise.
if ((rc = DigestInit(&actual, level, (level == 0 ? root_len : kNodeSize))) != ZX_OK) {
return rc;
}
DigestUpdate(&actual, in, 0, root_len);
DigestFinal(&actual, root_len);
return (actual == expected ? ZX_OK : ZX_ERR_IO_DATA_INTEGRITY);
}
zx_status_t MerkleTree::VerifyLevel(const void* data, size_t data_len, const void* tree,
size_t offset, size_t length, uint64_t level) {
zx_status_t rc;
ZX_DEBUG_ASSERT(offset + length >= offset);
// Must have more than one node of data and digests to check against.
if (!data || data_len <= kNodeSize || !tree) {
return ZX_ERR_INVALID_ARGS;
}
// Must not overrun expected length.
if (offset + length > data_len) {
return ZX_ERR_OUT_OF_RANGE;
}
// Align parameters to node boundaries, but don't exceed data_len
offset -= offset % kNodeSize;
size_t finish = fbl::round_up(offset + length, kNodeSize);
length = fbl::min(finish, data_len) - offset;
const uint8_t* in = static_cast<const uint8_t*>(data) + offset;
// The digests are in the next level up.
Digest actual;
const uint8_t* expected = static_cast<const uint8_t*>(tree) + (offset / kDigestsPerNode);
// Check the data of this level against the digests.
while (length > 0) {
if ((rc = DigestInit(&actual, offset | level, data_len - offset)) != ZX_OK) {
return rc;
}
size_t chunk = DigestUpdate(&actual, in, offset, length);
in += chunk;
offset += chunk;
length -= chunk;
DigestFinal(&actual, offset);
if (actual != expected) {
return ZX_ERR_IO_DATA_INTEGRITY;
}
expected += Digest::kLength;
}
return ZX_OK;
}
} // namespace digest
////////
// C-style wrapper functions
using digest::Digest;
using digest::MerkleTree;
struct merkle_tree_t {
MerkleTree obj;
};
size_t merkle_tree_get_tree_length(size_t data_len) {
return MerkleTree::GetTreeLength(data_len);
}
zx_status_t merkle_tree_create_init(size_t data_len, size_t tree_len, merkle_tree_t** out) {
zx_status_t rc;
// Must have some where to store the wrapper.
if (!out) {
return ZX_ERR_INVALID_ARGS;
}
// Allocate the wrapper object using a unique_ptr. That way, if we hit an
// error we'll clean up automatically.
fbl::AllocChecker ac;
fbl::unique_ptr<merkle_tree_t> mt_uniq(new (&ac) merkle_tree_t());
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
// Call the C++ function.
if ((rc = mt_uniq->obj.CreateInit(data_len, tree_len)) != ZX_OK) {
return rc;
}
// Release the wrapper object.
*out = mt_uniq.release();
return ZX_OK;
}
zx_status_t merkle_tree_create_update(merkle_tree_t* mt, const void* data, size_t length,
void* tree) {
// Must have a wrapper object.
if (!mt) {
return ZX_ERR_INVALID_ARGS;
}
// Call the C++ function.
zx_status_t rc;
if ((rc = mt->obj.CreateUpdate(data, length, tree)) != ZX_OK) {
return rc;
}
return ZX_OK;
}
zx_status_t merkle_tree_create_final(merkle_tree_t* mt, void* tree, void* out, size_t out_len) {
// Must have a wrapper object.
if (!mt) {
return ZX_ERR_INVALID_ARGS;
}
// Take possession of the wrapper object. That way, we'll clean up
// automatically.
fbl::unique_ptr<merkle_tree_t> mt_uniq(mt);
// Call the C++ function.
zx_status_t rc;
Digest digest;
if ((rc = mt_uniq->obj.CreateFinal(tree, &digest)) != ZX_OK) {
return rc;
}
return digest.CopyTo(static_cast<uint8_t*>(out), out_len);
}
zx_status_t merkle_tree_create(const void* data, size_t data_len, void* tree, size_t tree_len,
void* out, size_t out_len) {
zx_status_t rc;
Digest digest;
if ((rc = MerkleTree::Create(data, data_len, tree, tree_len, &digest)) != ZX_OK) {
return rc;
}
return digest.CopyTo(static_cast<uint8_t*>(out), out_len);
}
zx_status_t merkle_tree_verify(const void* data, size_t data_len, void* tree, size_t tree_len,
size_t offset, size_t length, const void* root, size_t root_len) {
// Must have a complete root digest.
if (root_len < Digest::kLength) {
return ZX_ERR_INVALID_ARGS;
}
Digest digest(static_cast<const uint8_t*>(root));
return MerkleTree::Verify(data, data_len, tree, tree_len, offset, length, digest);
}