#include <openssl/evp.h>
#include <sparse/sparse.h>
#undef NDEBUG
#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
struct sparse_hash_ctx {
unsigned char *hashes;
const unsigned char *salt;
uint64_t salt_size;
uint64_t hash_size;
uint64_t block_size;
const unsigned char *zero_block_hash;
const EVP_MD *md;
};
#define div_round_up(x,y) (((x) + (y) - 1)/(y))
#define round_up(x,y) (div_round_up(x,y)*(y))
#define FATAL(x...) { \
fprintf(stderr, x); \
exit(1); \
}
size_t verity_tree_blocks(uint64_t data_size, size_t block_size, size_t hash_size,
int level)
{
size_t level_blocks = div_round_up(data_size, block_size);
int hashes_per_block = div_round_up(block_size, hash_size);
do {
level_blocks = div_round_up(level_blocks, hashes_per_block);
} while (level--);
return level_blocks;
}
int hash_block(const EVP_MD *md,
const unsigned char *block, size_t len,
const unsigned char *salt, size_t salt_len,
unsigned char *out, size_t *out_size)
{
EVP_MD_CTX *mdctx;
unsigned int s;
int ret = 1;
mdctx = EVP_MD_CTX_create();
assert(mdctx);
ret &= EVP_DigestInit_ex(mdctx, md, NULL);
ret &= EVP_DigestUpdate(mdctx, salt, salt_len);
ret &= EVP_DigestUpdate(mdctx, block, len);
ret &= EVP_DigestFinal_ex(mdctx, out, &s);
EVP_MD_CTX_destroy(mdctx);
assert(ret == 1);
if (out_size) {
*out_size = s;
}
return 0;
}
int hash_blocks(const EVP_MD *md,
const unsigned char *in, size_t in_size,
unsigned char *out, size_t *out_size,
const unsigned char *salt, size_t salt_size,
size_t block_size)
{
size_t s;
*out_size = 0;
for (size_t i = 0; i < in_size; i += block_size) {
hash_block(md, in + i, block_size, salt, salt_size, out, &s);
out += s;
*out_size += s;
}
return 0;
}
int hash_chunk(void *priv, const void *data, int len)
{
struct sparse_hash_ctx *ctx = (struct sparse_hash_ctx *)priv;
assert(len % ctx->block_size == 0);
if (data) {
size_t s;
hash_blocks(ctx->md, (const unsigned char *)data, len,
ctx->hashes, &s,
ctx->salt, ctx->salt_size, ctx->block_size);
ctx->hashes += s;
} else {
for (size_t i = 0; i < (size_t)len; i += ctx->block_size) {
memcpy(ctx->hashes, ctx->zero_block_hash, ctx->hash_size);
ctx->hashes += ctx->hash_size;
}
}
return 0;
}
void usage(void)
{
printf("usage: build_verity_tree [ <options> ] -s <size> | <data> <verity>\n"
"options:\n"
" -a,--salt-str=<string> set salt to <string>\n"
" -A,--salt-hex=<hex digits> set salt to <hex digits>\n"
" -h show this help\n"
" -s,--verity-size=<data size> print the size of the verity tree\n"
" -S treat <data image> as a sparse file\n"
);
}
int main(int argc, char **argv)
{
char *data_filename;
char *verity_filename;
unsigned char *salt = NULL;
size_t salt_size = 0;
bool sparse = false;
size_t block_size = 4096;
size_t calculate_size = 0;
while (1) {
const static struct option long_options[] = {
{"salt-str", required_argument, 0, 'a'},
{"salt-hex", required_argument, 0, 'A'},
{"help", no_argument, 0, 'h'},
{"sparse", no_argument, 0, 'S'},
{"verity-size", required_argument, 0, 's'},
};
int c = getopt_long(argc, argv, "a:A:hSs:", long_options, NULL);
if (c < 0) {
break;
}
switch (c) {
case 'a':
salt_size = strlen(optarg);
salt = new unsigned char[salt_size]();
if (salt == NULL) {
FATAL("failed to allocate memory for salt\n");
}
memcpy(salt, optarg, salt_size);
break;
case 'A': {
BIGNUM *bn = NULL;
if(!BN_hex2bn(&bn, optarg)) {
FATAL("failed to convert salt from hex\n");
}
salt_size = BN_num_bytes(bn);
salt = new unsigned char[salt_size]();
if (salt == NULL) {
FATAL("failed to allocate memory for salt\n");
}
if((size_t)BN_bn2bin(bn, salt) != salt_size) {
FATAL("failed to convert salt to bytes\n");
}
}
break;
case 'h':
usage();
return 1;
case 'S':
sparse = true;
break;
case 's':
calculate_size = strtoul(optarg, NULL, 0);
break;
case '?':
usage();
return 1;
default:
abort();
}
}
argc -= optind;
argv += optind;
const EVP_MD *md = EVP_sha256();
if (!md) {
FATAL("failed to get digest\n");
}
size_t hash_size = EVP_MD_size(md);
assert(hash_size * 2 < block_size);
if (!salt || !salt_size) {
salt_size = hash_size;
salt = new unsigned char[salt_size];
if (salt == NULL) {
FATAL("failed to allocate memory for salt\n");
}
int random_fd = open("/dev/urandom", O_RDONLY);
if (random_fd < 0) {
FATAL("failed to open /dev/urandom\n");
}
ssize_t ret = read(random_fd, salt, salt_size);
if (ret != (ssize_t)salt_size) {
FATAL("failed to read %zu bytes from /dev/urandom: %zd %d\n", salt_size, ret, errno);
}
close(random_fd);
}
if (calculate_size) {
if (argc != 0) {
usage();
return 1;
}
size_t verity_blocks = 0;
size_t level_blocks;
int levels = 0;
do {
level_blocks = verity_tree_blocks(calculate_size, block_size, hash_size, levels);
levels++;
verity_blocks += level_blocks;
} while (level_blocks > 1);
printf("%zu\n", verity_blocks * block_size);
return 0;
}
if (argc != 2) {
usage();
return 1;
}
data_filename = argv[0];
verity_filename = argv[1];
int fd = open(data_filename, O_RDONLY);
if (fd < 0) {
FATAL("failed to open %s\n", data_filename);
}
struct sparse_file *file;
if (sparse) {
file = sparse_file_import(fd, false, false);
} else {
file = sparse_file_import_auto(fd, false);
}
if (!file) {
FATAL("failed to read file %s\n", data_filename);
}
int64_t len = sparse_file_len(file, false, false);
if (len % block_size != 0) {
FATAL("file size %" PRIu64 " is not a multiple of %zu bytes\n",
len, block_size);
}
int levels = 0;
size_t verity_blocks = 0;
size_t level_blocks;
do {
level_blocks = verity_tree_blocks(len, block_size, hash_size, levels);
levels++;
verity_blocks += level_blocks;
} while (level_blocks > 1);
unsigned char *verity_tree = new unsigned char[verity_blocks * block_size]();
unsigned char **verity_tree_levels = new unsigned char *[levels + 1]();
size_t *verity_tree_level_blocks = new size_t[levels]();
if (verity_tree == NULL || verity_tree_levels == NULL || verity_tree_level_blocks == NULL) {
FATAL("failed to allocate memory for verity tree\n");
}
unsigned char *ptr = verity_tree;
for (int i = levels - 1; i >= 0; i--) {
verity_tree_levels[i] = ptr;
verity_tree_level_blocks[i] = verity_tree_blocks(len, block_size, hash_size, i);
ptr += verity_tree_level_blocks[i] * block_size;
}
assert(ptr == verity_tree + verity_blocks * block_size);
assert(verity_tree_level_blocks[levels - 1] == 1);
unsigned char zero_block_hash[hash_size];
unsigned char zero_block[block_size];
memset(zero_block, 0, block_size);
hash_block(md, zero_block, block_size, salt, salt_size, zero_block_hash, NULL);
unsigned char root_hash[hash_size];
verity_tree_levels[levels] = root_hash;
struct sparse_hash_ctx ctx;
ctx.hashes = verity_tree_levels[0];
ctx.salt = salt;
ctx.salt_size = salt_size;
ctx.hash_size = hash_size;
ctx.block_size = block_size;
ctx.zero_block_hash = zero_block_hash;
ctx.md = md;
sparse_file_callback(file, false, false, hash_chunk, &ctx);
sparse_file_destroy(file);
close(fd);
for (int i = 0; i < levels; i++) {
size_t out_size;
hash_blocks(md,
verity_tree_levels[i], verity_tree_level_blocks[i] * block_size,
verity_tree_levels[i + 1], &out_size,
salt, salt_size, block_size);
if (i < levels - 1) {
assert(div_round_up(out_size, block_size) == verity_tree_level_blocks[i + 1]);
} else {
assert(out_size == hash_size);
}
}
for (size_t i = 0; i < hash_size; i++) {
printf("%02x", root_hash[i]);
}
printf(" ");
for (size_t i = 0; i < salt_size; i++) {
printf("%02x", salt[i]);
}
printf("\n");
fd = open(verity_filename, O_WRONLY|O_CREAT, 0666);
if (fd < 0) {
FATAL("failed to open output file '%s'\n", verity_filename);
}
write(fd, verity_tree, verity_blocks * block_size);
close(fd);
delete[] verity_tree_levels;
delete[] verity_tree_level_blocks;
delete[] verity_tree;
delete[] salt;
}