// 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 #include #include #include #include #include #include #include #include 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(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(data); size_t tree_off = (offset_ - (offset_ % kNodeSize)) / kDigestsPerNode; uint8_t* out = static_cast(tree) + tree_off; void* next = static_cast(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(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(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(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(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(data) + offset; // The digests are in the next level up. Digest actual; const uint8_t* expected = static_cast(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 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 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(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(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(root)); return MerkleTree::Verify(data, data_len, tree, tree_len, offset, length, digest); }