Created
May 23, 2022 09:02
-
-
Save alexlarsson/765c7f2344c03d80514f0c20ee08560d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <glib.h> | |
#include <gio/gio.h> | |
#include <gio/gunixinputstream.h> | |
#include <sys/stat.h> | |
#include <fcntl.h> | |
#include <unistd.h> | |
#include <errno.h> | |
#include <stdio.h> | |
struct fsverity_descriptor { | |
guint8 version; /* must be 1 */ | |
guint8 hash_algorithm; /* Merkle tree hash algorithm */ | |
guint8 log_blocksize; /* log2 of size of data and tree blocks */ | |
guint8 salt_size; /* size of salt in bytes; 0 if none */ | |
guint32 reserved1; /* must be 0 */ | |
guint64 data_size_be; /* size of file the Merkle tree is built over */ | |
guint8 root_hash[64]; /* Merkle tree root hash */ | |
guint8 salt[32]; /* salt prepended to each hashed block */ | |
guint8 reserved2[144]; /* must be 0's */ | |
}; | |
typedef struct { | |
GInputStream *in; | |
goffset current_pos; | |
goffset file_size; | |
goffset block_size; | |
GChecksum *checksum; | |
gsize digest_len; | |
size_t n_blocks_at_level[8]; | |
} FsVerityData; | |
static gboolean | |
compute_block_digest (FsVerityData *data, | |
goffset block_nr, | |
guint8 *digest_out, | |
GError **error) | |
{ | |
guint8 buf[data->block_size]; | |
gsize bytes_read; | |
gsize digest_len; | |
/* We don't really use current_pos other that to internally verify | |
that we're reading the right place from the stream */ | |
g_assert (data->current_pos == block_nr * data->block_size); | |
if (!g_input_stream_read_all (data->in, buf, sizeof(buf), | |
&bytes_read, NULL, error)) | |
return FALSE; | |
data->current_pos += bytes_read; | |
/* Clear rest */ | |
if (bytes_read < sizeof(buf)) | |
memset (buf + bytes_read, 0, sizeof(buf) - bytes_read); | |
g_checksum_reset (data->checksum); | |
g_checksum_update (data->checksum, buf, sizeof(buf)); | |
digest_len = data->digest_len; | |
g_checksum_get_digest (data->checksum, | |
digest_out, | |
&digest_len); | |
g_assert (digest_len == data->digest_len); | |
return TRUE; | |
} | |
static gboolean | |
compute_tree_digest (FsVerityData *data, | |
int level, | |
goffset block_nr, | |
guint8 *digest_out, | |
GError **error) | |
{ | |
guint8 buf[data->block_size]; | |
gsize digests_per_block = data->block_size / data->digest_len; | |
gsize i, sub_block_start, sub_block_end, n_sub_blocks; | |
gsize digest_len; | |
if (level == 0) | |
return compute_block_digest (data, block_nr, digest_out, error); | |
sub_block_start = block_nr * digests_per_block; | |
sub_block_end = MIN (sub_block_start + digests_per_block, data->n_blocks_at_level[level-1]); | |
n_sub_blocks = sub_block_end - sub_block_start; | |
for (i = 0; i < n_sub_blocks; i++) | |
{ | |
if (!compute_tree_digest (data, level - 1, sub_block_start + i, &buf[i * data->digest_len], error)) | |
return FALSE; | |
} | |
/* Zero remainder of buf */ | |
memset (&buf[n_sub_blocks * data->digest_len], 0, (digests_per_block - n_sub_blocks) * data->digest_len); | |
g_checksum_reset (data->checksum); | |
g_checksum_update (data->checksum, buf, sizeof(buf)); | |
digest_len = data->digest_len; | |
g_checksum_get_digest (data->checksum, | |
digest_out, | |
&digest_len); | |
g_assert (digest_len == data->digest_len); | |
return TRUE; | |
} | |
static GChecksum * | |
fs_verity_compute (GInputStream *in, | |
goffset file_size, | |
goffset block_size, | |
GChecksumType checksum_type, | |
GError **error) | |
{ | |
FsVerityData data; | |
gsize digests_per_block; | |
gsize max_level; | |
gsize digest_len; | |
struct fsverity_descriptor descriptor; | |
guint32 hash_alg; | |
switch (checksum_type) | |
{ | |
case G_CHECKSUM_SHA256: | |
hash_alg = 1; | |
break; | |
case G_CHECKSUM_SHA512: | |
hash_alg = 2; | |
break; | |
default: | |
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Unsupported checksum type"); | |
return NULL; | |
} | |
digest_len = g_checksum_type_get_length (checksum_type); | |
/* Gotta be an even number of digests per block */ | |
if (block_size % digest_len != 0) | |
{ | |
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Unsupported block size"); | |
return NULL; | |
} | |
data.in = in; | |
data.current_pos = 0; | |
data.file_size = file_size; | |
data.block_size = block_size; | |
data.checksum = g_checksum_new (checksum_type); | |
data.digest_len = digest_len; | |
data.n_blocks_at_level[0] = (data.file_size + data.block_size - 1) / data.block_size; | |
digests_per_block = data.block_size / data.digest_len; | |
for (max_level = 0; data.n_blocks_at_level[max_level] > 1; max_level++) | |
{ | |
g_assert (max_level + 1 < G_N_ELEMENTS(data.n_blocks_at_level)); | |
data.n_blocks_at_level[max_level+1] = (data.n_blocks_at_level[max_level] + digests_per_block - 1) / digests_per_block; | |
} | |
memset(&descriptor, 0, sizeof(descriptor)); | |
descriptor.version = 1; | |
descriptor.hash_algorithm = hash_alg; | |
descriptor.log_blocksize = g_bit_storage (data.block_size-1); /* log2(block_size) */ | |
descriptor.salt_size = 0; | |
descriptor.data_size_be = GUINT64_TO_LE(data.file_size); | |
if (!compute_tree_digest (&data, max_level, 0, descriptor.root_hash, error)) | |
return FALSE; | |
g_checksum_reset (data.checksum); | |
g_checksum_update (data.checksum, (guint8 *)&descriptor, sizeof(descriptor)); | |
return data.checksum; | |
} | |
int | |
main(int argc, const char *argv[]) | |
{ | |
const char *path; | |
int fd; | |
GChecksum *checksum; | |
GInputStream *input; | |
struct stat statbuf; | |
if (argc != 2) | |
{ | |
g_print ("No file specified\n"); | |
return 1; | |
} | |
path = argv[1]; | |
fd = open(path, O_RDONLY); | |
if (fd < 0) | |
{ | |
perror("Can't open file"); | |
return 1; | |
} | |
if (fstat(fd, &statbuf) != 0) | |
{ | |
perror("Can't stat file"); | |
return 1; | |
} | |
input = g_unix_input_stream_new (fd, TRUE); | |
checksum = fs_verity_compute(input, statbuf.st_size, 4096, G_CHECKSUM_SHA256, NULL); | |
g_print ("fs-verify digest: %s\n", g_checksum_get_string (checksum)); | |
g_checksum_free (checksum); | |
close(fd); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment