aboutsummaryrefslogtreecommitdiff
path: root/programs
diff options
context:
space:
mode:
Diffstat (limited to 'programs')
-rw-r--r--programs/cmd_digest.c129
-rw-r--r--programs/cmd_enable.c123
-rw-r--r--programs/cmd_measure.c69
-rw-r--r--programs/cmd_sign.c133
-rw-r--r--programs/fsverity.c241
-rw-r--r--programs/fsverity.h58
-rw-r--r--programs/test_compute_digest.c310
-rw-r--r--programs/test_hash_algs.c45
-rw-r--r--programs/test_sign_digest.c57
-rw-r--r--programs/utils.c235
-rw-r--r--programs/utils.h51
11 files changed, 1451 insertions, 0 deletions
diff --git a/programs/cmd_digest.c b/programs/cmd_digest.c
new file mode 100644
index 0000000..1a3c769
--- /dev/null
+++ b/programs/cmd_digest.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: MIT
+/*
+ * The 'fsverity digest' command
+ *
+ * Copyright 2020 Microsoft
+ *
+ * Use of this source code is governed by an MIT-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/MIT.
+ */
+
+#include "fsverity.h"
+
+#include <fcntl.h>
+#include <getopt.h>
+
+static const struct option longopts[] = {
+ {"hash-alg", required_argument, NULL, OPT_HASH_ALG},
+ {"block-size", required_argument, NULL, OPT_BLOCK_SIZE},
+ {"salt", required_argument, NULL, OPT_SALT},
+ {"compact", no_argument, NULL, OPT_COMPACT},
+ {"for-builtin-sig", no_argument, NULL, OPT_FOR_BUILTIN_SIG},
+ {NULL, 0, NULL, 0}
+};
+
+/*
+ * Compute the fs-verity digest of the given file(s), for offline signing.
+ */
+int fsverity_cmd_digest(const struct fsverity_command *cmd,
+ int argc, char *argv[])
+{
+ struct filedes file = { .fd = -1 };
+ struct libfsverity_merkle_tree_params tree_params = { .version = 1 };
+ bool compact = false, for_builtin_sig = false;
+ int status;
+ int c;
+
+ while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) {
+ switch (c) {
+ case OPT_HASH_ALG:
+ case OPT_BLOCK_SIZE:
+ case OPT_SALT:
+ if (!parse_tree_param(c, optarg, &tree_params))
+ goto out_usage;
+ break;
+ case OPT_COMPACT:
+ compact = true;
+ break;
+ case OPT_FOR_BUILTIN_SIG:
+ for_builtin_sig = true;
+ break;
+ default:
+ goto out_usage;
+ }
+ }
+
+ argv += optind;
+ argc -= optind;
+
+ if (argc < 1)
+ goto out_usage;
+
+ for (int i = 0; i < argc; i++) {
+ struct fsverity_formatted_digest *d = NULL;
+ struct libfsverity_digest *digest = NULL;
+ char digest_hex[FS_VERITY_MAX_DIGEST_SIZE * 2 +
+ sizeof(*d) * 2 + 1];
+
+ if (!open_file(&file, argv[i], O_RDONLY, 0))
+ goto out_err;
+
+ if (!get_file_size(&file, &tree_params.file_size))
+ goto out_err;
+
+ if (libfsverity_compute_digest(&file, read_callback,
+ &tree_params, &digest) != 0) {
+ error_msg("failed to compute digest");
+ goto out_err;
+ }
+
+ ASSERT(digest->digest_size <= FS_VERITY_MAX_DIGEST_SIZE);
+
+ if (for_builtin_sig) {
+ /*
+ * Format the digest for use with the built-in signature
+ * support.
+ */
+ d = xzalloc(sizeof(*d) + digest->digest_size);
+ memcpy(d->magic, "FSVerity", 8);
+ d->digest_algorithm =
+ cpu_to_le16(digest->digest_algorithm);
+ d->digest_size = cpu_to_le16(digest->digest_size);
+ memcpy(d->digest, digest->digest, digest->digest_size);
+
+ bin2hex((const u8 *)d, sizeof(*d) + digest->digest_size,
+ digest_hex);
+ } else {
+ bin2hex(digest->digest, digest->digest_size,
+ digest_hex);
+ }
+
+ if (compact)
+ printf("%s\n", digest_hex);
+ else if (for_builtin_sig)
+ printf("%s %s\n", digest_hex, argv[i]);
+ else
+ printf("%s:%s %s\n",
+ libfsverity_get_hash_name(digest->digest_algorithm),
+ digest_hex, argv[i]);
+
+ filedes_close(&file);
+ free(digest);
+ free(d);
+ }
+ status = 0;
+out:
+ destroy_tree_params(&tree_params);
+ return status;
+
+out_err:
+ filedes_close(&file);
+ status = 1;
+ goto out;
+
+out_usage:
+ usage(cmd, stderr);
+ status = 2;
+ goto out;
+}
diff --git a/programs/cmd_enable.c b/programs/cmd_enable.c
new file mode 100644
index 0000000..14c3c17
--- /dev/null
+++ b/programs/cmd_enable.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: MIT
+/*
+ * The 'fsverity enable' command
+ *
+ * Copyright 2018 Google LLC
+ *
+ * Use of this source code is governed by an MIT-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/MIT.
+ */
+
+#include "fsverity.h"
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+
+static bool read_signature(const char *filename, u8 **sig_ret,
+ u32 *sig_size_ret)
+{
+ struct filedes file = { .fd = -1 };
+ u64 file_size;
+ u8 *sig = NULL;
+ bool ok = false;
+
+ if (!open_file(&file, filename, O_RDONLY, 0))
+ goto out;
+ if (!get_file_size(&file, &file_size))
+ goto out;
+ if (file_size <= 0) {
+ error_msg("signature file '%s' is empty", filename);
+ goto out;
+ }
+ if (file_size > 1000000) {
+ error_msg("signature file '%s' is too large", filename);
+ goto out;
+ }
+ sig = xmalloc(file_size);
+ if (!full_read(&file, sig, file_size))
+ goto out;
+ *sig_ret = sig;
+ *sig_size_ret = file_size;
+ sig = NULL;
+ ok = true;
+out:
+ filedes_close(&file);
+ free(sig);
+ return ok;
+}
+
+static const struct option longopts[] = {
+ {"hash-alg", required_argument, NULL, OPT_HASH_ALG},
+ {"block-size", required_argument, NULL, OPT_BLOCK_SIZE},
+ {"salt", required_argument, NULL, OPT_SALT},
+ {"signature", required_argument, NULL, OPT_SIGNATURE},
+ {NULL, 0, NULL, 0}
+};
+
+/* Enable fs-verity on a file. */
+int fsverity_cmd_enable(const struct fsverity_command *cmd,
+ int argc, char *argv[])
+{
+ struct libfsverity_merkle_tree_params tree_params = { .version = 1 };
+ u8 *sig = NULL;
+ u32 sig_size = 0;
+ struct filedes file;
+ int status;
+ int c;
+
+ while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) {
+ switch (c) {
+ case OPT_HASH_ALG:
+ case OPT_BLOCK_SIZE:
+ case OPT_SALT:
+ if (!parse_tree_param(c, optarg, &tree_params))
+ goto out_usage;
+ break;
+ case OPT_SIGNATURE:
+ if (sig != NULL) {
+ error_msg("--signature can only be specified once");
+ goto out_usage;
+ }
+ if (!read_signature(optarg, &sig, &sig_size))
+ goto out_err;
+ break;
+ default:
+ goto out_usage;
+ }
+ }
+
+ argv += optind;
+ argc -= optind;
+
+ if (argc != 1)
+ goto out_usage;
+
+ if (!open_file(&file, argv[0], O_RDONLY, 0))
+ goto out_err;
+
+ if (libfsverity_enable_with_sig(file.fd, &tree_params, sig, sig_size)) {
+ error_msg_errno("FS_IOC_ENABLE_VERITY failed on '%s'",
+ file.name);
+ filedes_close(&file);
+ goto out_err;
+ }
+ if (!filedes_close(&file))
+ goto out_err;
+
+ status = 0;
+out:
+ destroy_tree_params(&tree_params);
+ free(sig);
+ return status;
+
+out_err:
+ status = 1;
+ goto out;
+
+out_usage:
+ usage(cmd, stderr);
+ status = 2;
+ goto out;
+}
diff --git a/programs/cmd_measure.c b/programs/cmd_measure.c
new file mode 100644
index 0000000..d78969c
--- /dev/null
+++ b/programs/cmd_measure.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: MIT
+/*
+ * The 'fsverity measure' command
+ *
+ * Copyright 2018 Google LLC
+ *
+ * Use of this source code is governed by an MIT-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/MIT.
+ */
+
+#include "fsverity.h"
+
+#include <fcntl.h>
+#include <sys/ioctl.h>
+
+/* Display the fs-verity digest of the given verity file(s). */
+int fsverity_cmd_measure(const struct fsverity_command *cmd,
+ int argc, char *argv[])
+{
+ struct fsverity_digest *d = NULL;
+ struct filedes file;
+ char digest_hex[FS_VERITY_MAX_DIGEST_SIZE * 2 + 1];
+ char _hash_alg_name[32];
+ const char *hash_alg_name;
+ int status;
+ int i;
+
+ if (argc < 2)
+ goto out_usage;
+
+ d = xzalloc(sizeof(*d) + FS_VERITY_MAX_DIGEST_SIZE);
+
+ for (i = 1; i < argc; i++) {
+ d->digest_size = FS_VERITY_MAX_DIGEST_SIZE;
+
+ if (!open_file(&file, argv[i], O_RDONLY, 0))
+ goto out_err;
+ if (ioctl(file.fd, FS_IOC_MEASURE_VERITY, d) != 0) {
+ error_msg_errno("FS_IOC_MEASURE_VERITY failed on '%s'",
+ file.name);
+ filedes_close(&file);
+ goto out_err;
+ }
+ filedes_close(&file);
+
+ ASSERT(d->digest_size <= FS_VERITY_MAX_DIGEST_SIZE);
+ bin2hex(d->digest, d->digest_size, digest_hex);
+ hash_alg_name = libfsverity_get_hash_name(d->digest_algorithm);
+ if (!hash_alg_name) {
+ sprintf(_hash_alg_name, "ALG_%u", d->digest_algorithm);
+ hash_alg_name = _hash_alg_name;
+ }
+ printf("%s:%s %s\n", hash_alg_name, digest_hex, argv[i]);
+ }
+ status = 0;
+out:
+ free(d);
+ return status;
+
+out_err:
+ status = 1;
+ goto out;
+
+out_usage:
+ usage(cmd, stderr);
+ status = 2;
+ goto out;
+}
diff --git a/programs/cmd_sign.c b/programs/cmd_sign.c
new file mode 100644
index 0000000..47ba6a2
--- /dev/null
+++ b/programs/cmd_sign.c
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: MIT
+/*
+ * The 'fsverity sign' command
+ *
+ * Copyright 2018 Google LLC
+ *
+ * Use of this source code is governed by an MIT-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/MIT.
+ */
+
+#include "fsverity.h"
+
+#include <fcntl.h>
+#include <getopt.h>
+
+static bool write_signature(const char *filename, const u8 *sig, u32 sig_size)
+{
+ struct filedes file;
+ bool ok;
+
+ if (!open_file(&file, filename, O_WRONLY|O_CREAT|O_TRUNC, 0644))
+ return false;
+ ok = full_write(&file, sig, sig_size);
+ ok &= filedes_close(&file);
+ return ok;
+}
+
+static const struct option longopts[] = {
+ {"hash-alg", required_argument, NULL, OPT_HASH_ALG},
+ {"block-size", required_argument, NULL, OPT_BLOCK_SIZE},
+ {"salt", required_argument, NULL, OPT_SALT},
+ {"key", required_argument, NULL, OPT_KEY},
+ {"cert", required_argument, NULL, OPT_CERT},
+ {NULL, 0, NULL, 0}
+};
+
+/* Sign a file for fs-verity by computing its digest, then signing it. */
+int fsverity_cmd_sign(const struct fsverity_command *cmd,
+ int argc, char *argv[])
+{
+ struct filedes file = { .fd = -1 };
+ struct libfsverity_merkle_tree_params tree_params = { .version = 1 };
+ struct libfsverity_signature_params sig_params = {};
+ struct libfsverity_digest *digest = NULL;
+ char digest_hex[FS_VERITY_MAX_DIGEST_SIZE * 2 + 1];
+ u8 *sig = NULL;
+ size_t sig_size;
+ int status;
+ int c;
+
+ while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) {
+ switch (c) {
+ case OPT_HASH_ALG:
+ case OPT_BLOCK_SIZE:
+ case OPT_SALT:
+ if (!parse_tree_param(c, optarg, &tree_params))
+ goto out_usage;
+ break;
+ case OPT_KEY:
+ if (sig_params.keyfile != NULL) {
+ error_msg("--key can only be specified once");
+ goto out_usage;
+ }
+ sig_params.keyfile = optarg;
+ break;
+ case OPT_CERT:
+ if (sig_params.certfile != NULL) {
+ error_msg("--cert can only be specified once");
+ goto out_usage;
+ }
+ sig_params.certfile = optarg;
+ break;
+ default:
+ goto out_usage;
+ }
+ }
+
+ argv += optind;
+ argc -= optind;
+
+ if (argc != 2)
+ goto out_usage;
+
+ if (sig_params.keyfile == NULL) {
+ error_msg("Missing --key argument");
+ goto out_usage;
+ }
+ if (sig_params.certfile == NULL)
+ sig_params.certfile = sig_params.keyfile;
+
+ if (!open_file(&file, argv[0], O_RDONLY, 0))
+ goto out_err;
+
+ if (!get_file_size(&file, &tree_params.file_size))
+ goto out_err;
+
+ if (libfsverity_compute_digest(&file, read_callback,
+ &tree_params, &digest) != 0) {
+ error_msg("failed to compute digest");
+ goto out_err;
+ }
+
+ if (libfsverity_sign_digest(digest, &sig_params,
+ &sig, &sig_size) != 0) {
+ error_msg("failed to sign digest");
+ goto out_err;
+ }
+
+ if (!write_signature(argv[1], sig, sig_size))
+ goto out_err;
+
+ ASSERT(digest->digest_size <= FS_VERITY_MAX_DIGEST_SIZE);
+ bin2hex(digest->digest, digest->digest_size, digest_hex);
+ printf("Signed file '%s' (%s:%s)\n", argv[0],
+ libfsverity_get_hash_name(digest->digest_algorithm), digest_hex);
+ status = 0;
+out:
+ filedes_close(&file);
+ destroy_tree_params(&tree_params);
+ free(digest);
+ free(sig);
+ return status;
+
+out_err:
+ status = 1;
+ goto out;
+
+out_usage:
+ usage(cmd, stderr);
+ status = 2;
+ goto out;
+}
diff --git a/programs/fsverity.c b/programs/fsverity.c
new file mode 100644
index 0000000..b911b2e
--- /dev/null
+++ b/programs/fsverity.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: MIT
+/*
+ * fs-verity userspace tool
+ *
+ * Copyright 2018 Google LLC
+ *
+ * Use of this source code is governed by an MIT-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/MIT.
+ */
+
+#include "fsverity.h"
+
+#include <limits.h>
+
+static const struct fsverity_command {
+ const char *name;
+ int (*func)(const struct fsverity_command *cmd, int argc, char *argv[]);
+ const char *short_desc;
+ const char *usage_str;
+} fsverity_commands[] = {
+ {
+ .name = "digest",
+ .func = fsverity_cmd_digest,
+ .short_desc =
+"Compute the fs-verity digest of the given file(s), for offline signing",
+ .usage_str =
+" fsverity digest FILE...\n"
+" [--hash-alg=HASH_ALG] [--block-size=BLOCK_SIZE] [--salt=SALT]\n"
+" [--compact] [--for-builtin-sig]\n"
+#ifndef _WIN32
+ }, {
+ .name = "enable",
+ .func = fsverity_cmd_enable,
+ .short_desc = "Enable fs-verity on a file",
+ .usage_str =
+" fsverity enable FILE\n"
+" [--hash-alg=HASH_ALG] [--block-size=BLOCK_SIZE] [--salt=SALT]\n"
+" [--signature=SIGFILE]\n"
+ }, {
+ .name = "measure",
+ .func = fsverity_cmd_measure,
+ .short_desc =
+"Display the fs-verity digest of the given verity file(s)",
+ .usage_str =
+" fsverity measure FILE...\n"
+#endif /* !_WIN32 */
+ }, {
+ .name = "sign",
+ .func = fsverity_cmd_sign,
+ .short_desc = "Sign a file for fs-verity",
+ .usage_str =
+" fsverity sign FILE OUT_SIGFILE --key=KEYFILE\n"
+" [--hash-alg=HASH_ALG] [--block-size=BLOCK_SIZE] [--salt=SALT]\n"
+" [--cert=CERTFILE]\n"
+ }
+};
+
+static void show_all_hash_algs(FILE *fp)
+{
+ u32 alg_num = 1;
+ const char *name;
+
+ fprintf(fp, "Available hash algorithms:");
+ while ((name = libfsverity_get_hash_name(alg_num++)) != NULL)
+ fprintf(fp, " %s", name);
+ putc('\n', fp);
+}
+
+static void usage_all(FILE *fp)
+{
+ int i;
+
+ fputs("Usage:\n", fp);
+ for (i = 0; i < ARRAY_SIZE(fsverity_commands); i++)
+ fprintf(fp, " %s:\n%s\n", fsverity_commands[i].short_desc,
+ fsverity_commands[i].usage_str);
+ fputs(
+" Standard options:\n"
+" fsverity --help\n"
+" fsverity --version\n"
+"\n", fp);
+ show_all_hash_algs(fp);
+}
+
+static void usage_cmd(const struct fsverity_command *cmd, FILE *fp)
+{
+ fprintf(fp, "Usage:\n%s", cmd->usage_str);
+}
+
+void usage(const struct fsverity_command *cmd, FILE *fp)
+{
+ if (cmd)
+ usage_cmd(cmd, fp);
+ else
+ usage_all(fp);
+}
+
+static void show_version(void)
+{
+ printf("fsverity v%d.%d\n", FSVERITY_UTILS_MAJOR_VERSION,
+ FSVERITY_UTILS_MINOR_VERSION);
+}
+
+static void handle_common_options(int argc, char *argv[],
+ const struct fsverity_command *cmd)
+{
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (*arg++ != '-')
+ continue;
+ if (*arg++ != '-')
+ continue;
+ if (!strcmp(arg, "help")) {
+ usage(cmd, stdout);
+ exit(0);
+ } else if (!strcmp(arg, "version")) {
+ show_version();
+ exit(0);
+ } else if (!*arg) /* reached "--", no more options */
+ return;
+ }
+}
+
+static const struct fsverity_command *find_command(const char *name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fsverity_commands); i++)
+ if (!strcmp(name, fsverity_commands[i].name))
+ return &fsverity_commands[i];
+ return NULL;
+}
+
+static bool parse_hash_alg_option(const char *arg, u32 *alg_ptr)
+{
+ char *end;
+ unsigned long n = strtoul(arg, &end, 10);
+
+ if (*alg_ptr != 0) {
+ error_msg("--hash-alg can only be specified once");
+ return false;
+ }
+
+ /* Specified by number? */
+ if (n > 0 && n < INT32_MAX && *end == '\0') {
+ *alg_ptr = n;
+ return true;
+ }
+
+ /* Specified by name? */
+ *alg_ptr = libfsverity_find_hash_alg_by_name(arg);
+ if (*alg_ptr)
+ return true;
+ error_msg("unknown hash algorithm: '%s'", arg);
+ show_all_hash_algs(stderr);
+ return false;
+}
+
+static bool parse_block_size_option(const char *arg, u32 *size_ptr)
+{
+ char *end;
+ unsigned long n = strtoul(arg, &end, 10);
+
+ if (*size_ptr != 0) {
+ error_msg("--block-size can only be specified once");
+ return false;
+ }
+
+ if (n <= 0 || n >= INT_MAX || !is_power_of_2(n) || *end != '\0') {
+ error_msg("Invalid block size: %s. Must be power of 2", arg);
+ return false;
+ }
+ *size_ptr = n;
+ return true;
+}
+
+static bool parse_salt_option(const char *arg, u8 **salt_ptr,
+ u32 *salt_size_ptr)
+{
+ if (*salt_ptr != NULL) {
+ error_msg("--salt can only be specified once");
+ return false;
+ }
+ *salt_size_ptr = strlen(arg) / 2;
+ *salt_ptr = xmalloc(*salt_size_ptr);
+ if (!hex2bin(arg, *salt_ptr, *salt_size_ptr)) {
+ error_msg("salt is not a valid hex string");
+ return false;
+ }
+ return true;
+}
+
+bool parse_tree_param(int opt_char, const char *arg,
+ struct libfsverity_merkle_tree_params *params)
+{
+ switch (opt_char) {
+ case OPT_HASH_ALG:
+ return parse_hash_alg_option(arg, &params->hash_algorithm);
+ case OPT_BLOCK_SIZE:
+ return parse_block_size_option(arg, &params->block_size);
+ case OPT_SALT:
+ return parse_salt_option(arg, (u8 **)&params->salt,
+ &params->salt_size);
+ default:
+ ASSERT(0);
+ }
+}
+
+void destroy_tree_params(struct libfsverity_merkle_tree_params *params)
+{
+ free((u8 *)params->salt);
+ memset(params, 0, sizeof(*params));
+}
+
+int main(int argc, char *argv[])
+{
+ const struct fsverity_command *cmd;
+
+ install_libfsverity_error_handler();
+
+ if (argc < 2) {
+ error_msg("no command specified");
+ usage_all(stderr);
+ return 2;
+ }
+
+ cmd = find_command(argv[1]);
+
+ handle_common_options(argc, argv, cmd);
+
+ if (!cmd) {
+ error_msg("unrecognized command: '%s'", argv[1]);
+ usage_all(stderr);
+ return 2;
+ }
+ return cmd->func(cmd, argc - 1, argv + 1);
+}
diff --git a/programs/fsverity.h b/programs/fsverity.h
new file mode 100644
index 0000000..45c4fe1
--- /dev/null
+++ b/programs/fsverity.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Private header for the 'fsverity' program
+ *
+ * Copyright 2018 Google LLC
+ *
+ * Use of this source code is governed by an MIT-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/MIT.
+ */
+#ifndef PROGRAMS_FSVERITY_H
+#define PROGRAMS_FSVERITY_H
+
+#include "utils.h"
+#include "../common/fsverity_uapi.h"
+
+/*
+ * Largest digest size among all hash algorithms supported by fs-verity.
+ * This can be increased if needed.
+ */
+#define FS_VERITY_MAX_DIGEST_SIZE 64
+
+enum {
+ OPT_BLOCK_SIZE,
+ OPT_CERT,
+ OPT_COMPACT,
+ OPT_FOR_BUILTIN_SIG,
+ OPT_HASH_ALG,
+ OPT_KEY,
+ OPT_SALT,
+ OPT_SIGNATURE,
+};
+
+struct fsverity_command;
+
+/* cmd_digest.c */
+int fsverity_cmd_digest(const struct fsverity_command *cmd,
+ int argc, char *argv[]);
+
+/* cmd_enable.c */
+int fsverity_cmd_enable(const struct fsverity_command *cmd,
+ int argc, char *argv[]);
+
+/* cmd_measure.c */
+int fsverity_cmd_measure(const struct fsverity_command *cmd,
+ int argc, char *argv[]);
+
+/* cmd_sign.c */
+int fsverity_cmd_sign(const struct fsverity_command *cmd,
+ int argc, char *argv[]);
+
+/* fsverity.c */
+void usage(const struct fsverity_command *cmd, FILE *fp);
+bool parse_tree_param(int opt_char, const char *arg,
+ struct libfsverity_merkle_tree_params *params);
+void destroy_tree_params(struct libfsverity_merkle_tree_params *params);
+
+#endif /* PROGRAMS_FSVERITY_H */
diff --git a/programs/test_compute_digest.c b/programs/test_compute_digest.c
new file mode 100644
index 0000000..e7f2645
--- /dev/null
+++ b/programs/test_compute_digest.c
@@ -0,0 +1,310 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Test libfsverity_compute_digest().
+ *
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by an MIT-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/MIT.
+ */
+
+#include "utils.h"
+
+#include <ctype.h>
+#include <inttypes.h>
+
+struct mem_file {
+ u8 *data;
+ size_t size;
+ size_t offset;
+};
+
+static int read_fn(void *fd, void *buf, size_t count)
+{
+ struct mem_file *f = fd;
+
+ ASSERT(count <= f->size - f->offset);
+ memcpy(buf, &f->data[f->offset], count);
+ f->offset += count;
+ return 0;
+}
+
+static int error_read_fn(void *fd __attribute__((unused)),
+ void *buf __attribute__((unused)),
+ size_t count __attribute__((unused)))
+{
+ return -EIO;
+}
+
+static const struct test_case {
+ u32 hash_algorithm;
+ u32 block_size;
+ const char *salt;
+ u64 file_size;
+ const char *digest;
+} test_cases[] = {
+ { /* large file */
+ .hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
+ .file_size = 1000000,
+ .block_size = 4096,
+ .digest = "\x48\xdf\x0c\x46\x23\x29\xcd\x87"
+ "\x96\x61\xbd\x05\xb3\x9a\xa8\x1b"
+ "\x05\xcc\x16\xaf\xd2\x7a\x71\x96"
+ "\xa5\x59\xda\x83\x53\x1d\x39\xd9",
+ }, { /* small file */
+ .hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
+ .file_size = 100000,
+ .block_size = 4096,
+ .digest = "\xf2\x09\x6a\x36\xc5\xcd\xca\x4f"
+ "\xa3\x3e\xe8\x85\x28\x33\x15\x0b"
+ "\xb3\x24\x99\x2e\x54\x17\xa9\xd5"
+ "\x71\xf1\xbf\xff\xf7\x3b\x9e\xfc",
+ }, { /* single-block file */
+ .hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
+ .file_size = 4096,
+ .block_size = 4096,
+ .digest = "\x6a\xc3\x99\x79\x01\x6e\x3d\xdf"
+ "\x3d\x39\xff\xf6\xcb\x98\x4f\x7c"
+ "\x11\x8a\xcd\xf1\x85\x29\x19\xf5"
+ "\xc1\x00\xc4\xb1\x42\xc1\x81\x8e",
+ }, { /* tiny file */
+ .hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
+ .file_size = 1,
+ .block_size = 4096,
+ .digest = "\xb8\x03\x42\x95\x03\xd9\x59\x15"
+ "\x82\x9b\x29\xfd\xbc\x8b\xba\xd1"
+ "\x42\xf3\xab\xfd\x11\xb1\xca\xdf"
+ "\x55\x26\x58\x2e\x68\x5c\x05\x51",
+ }, { /* empty file */
+ .hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
+ .file_size = 0,
+ .block_size = 4096,
+ .digest = "\x3d\x24\x8c\xa5\x42\xa2\x4f\xc6"
+ "\x2d\x1c\x43\xb9\x16\xea\xe5\x01"
+ "\x68\x78\xe2\x53\x3c\x88\x23\x84"
+ "\x80\xb2\x61\x28\xa1\xf1\xaf\x95",
+ }, { /* salt */
+ .hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
+ .file_size = 1000000,
+ .block_size = 4096,
+ .salt = "abcd",
+ .digest = "\x91\x79\x00\xb0\xd2\x99\x45\x4a"
+ "\xa3\x04\xd5\xde\xbc\x6f\x39\xe4"
+ "\xaf\x7b\x5a\xbe\x33\xbd\xbc\x56"
+ "\x8d\x5d\x8f\x1e\x5c\x4d\x86\x52",
+ }, { /* max length salt (32 bytes) */
+ .hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
+ .file_size = 1000000,
+ .block_size = 4096,
+ .salt = "0123456789:;<=>?@ABCDEFGHIJKLMNO",
+ .digest = "\xbc\x2d\x70\x32\x4c\x04\x8c\x22"
+ "\x0a\x2c\xb1\x90\x83\x21\x40\x86"
+ "\x3e\xb2\x68\xe6\x80\x42\x79\x39"
+ "\xe5\xd4\x67\xbe\xa5\xec\x5a\xd9",
+ }, { /* 1K block size */
+ .hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
+ .file_size = 1000000,
+ .block_size = 1024,
+ .digest = "\xe9\xdf\x92\x7c\x14\xfc\xb9\x61"
+ "\xd5\xf5\x1c\x66\x6d\x8a\xe4\xc1"
+ "\x4f\xe4\xff\x98\xa3\x74\xc7\x33"
+ "\xe8\x98\xd0\x0c\x9e\x74\xa8\xe3",
+ }, { /* 512-byte block size */
+ .hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
+ .file_size = 1000000,
+ .block_size = 512,
+ .digest = "\x03\x93\xee\x3d\xfd\x4a\x28\x96"
+ "\x6e\x2a\xf4\xe0\x7c\xfa\x5b\x03"
+ "\x2c\x30\xda\x5b\xb8\xe8\xef\x63"
+ "\xb9\xa5\x5b\xf9\x63\x26\x23\x34",
+ }, { /* 64K block size */
+ .hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
+ .file_size = 1000000,
+ .block_size = 65536,
+ .digest = "\xf3\xb6\x41\x8f\x26\xd4\xd0\xe7"
+ "\x47\x28\x19\x3b\xae\x76\xf1\x5c"
+ "\xb4\xbb\x2c\xe9\x77\x74\x48\xd7"
+ "\x6b\xd8\x13\x8b\x69\xec\x61\xc2",
+ }, { /* SHA-512 */
+ .hash_algorithm = FS_VERITY_HASH_ALG_SHA512,
+ .file_size = 1000000,
+ .block_size = 4096,
+ .salt = "abcd",
+ .digest = "\x84\x25\xc6\xd0\xc9\x4f\x84\xed"
+ "\x90\x4c\x12\x93\x68\x45\xfb\xb7"
+ "\xaf\x99\x53\x75\x37\x89\x71\x2d"
+ "\xcc\x3b\xe1\x42\xdb\x3d\x4b\x6b"
+ "\x47\xa3\x99\xad\x52\xaa\x60\x92"
+ "\x56\xce\x29\xa9\x60\xbf\x4b\xb0"
+ "\xe5\x95\xec\x38\x6c\xa5\x8c\x06"
+ "\x51\x9d\x54\x6d\xc5\xb1\x97\xbb",
+ }, { /* default hash algorithm (SHA-256) and block size (4096) */
+ .file_size = 100000,
+ .digest = "\xf2\x09\x6a\x36\xc5\xcd\xca\x4f"
+ "\xa3\x3e\xe8\x85\x28\x33\x15\x0b"
+ "\xb3\x24\x99\x2e\x54\x17\xa9\xd5"
+ "\x71\xf1\xbf\xff\xf7\x3b\x9e\xfc",
+ },
+};
+
+static void fix_digest_and_print(const struct test_case *t,
+ const struct libfsverity_digest *d)
+{
+ char alg_name[32] = {};
+ size_t i;
+
+ strncpy(alg_name, libfsverity_get_hash_name(t->hash_algorithm),
+ sizeof(alg_name) - 1);
+ for (i = 0; i < sizeof(alg_name) - 1; i++)
+ alg_name[i] = toupper((u8)alg_name[i]);
+
+ printf("\t}, {\n"
+ "\t\t.hash_algorithm = FS_VERITY_HASH_ALG_%s,\n"
+ "\t\t.file_size = %" PRIu64 ",\n"
+ "\t\t.block_size = %u,\n",
+ alg_name, t->file_size, t->block_size);
+ if (t->salt != NULL)
+ printf("\t\t.salt = \"%s\",\n", t->salt);
+ for (i = 0; i < d->digest_size; i++) {
+ if (i == 0)
+ printf("\t\t.digest = \"");
+ else if (i % 8 == 0)
+ printf("\t\t\t \"");
+ printf("\\x%02x", d->digest[i]);
+ if (i + 1 == d->digest_size)
+ printf("\",\n");
+ else if (i % 8 == 7)
+ printf("\"\n");
+ }
+}
+
+static void test_invalid_params(void)
+{
+ struct mem_file f = { .data = (u8 *)"abcd", .size = 4 };
+ struct libfsverity_merkle_tree_params good_params = {
+ .version = 1,
+ .hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
+ .file_size = 4,
+ .block_size = 4096,
+ };
+ struct libfsverity_merkle_tree_params params;
+ struct libfsverity_digest *d = NULL;
+
+ libfsverity_set_error_callback(NULL);
+
+ ASSERT(libfsverity_compute_digest(&f, read_fn, &good_params, &d) == 0);
+ f.offset = 0;
+ free(d);
+ d = NULL;
+
+ /* missing required arguments */
+ ASSERT(libfsverity_compute_digest(&f, NULL, &good_params, &d) == -EINVAL);
+ ASSERT(libfsverity_compute_digest(&f, read_fn, NULL, &d) == -EINVAL);
+ ASSERT(libfsverity_compute_digest(&f, read_fn, &good_params, NULL) == -EINVAL);
+
+ /* bad version */
+ params = good_params;
+ params.version = 0;
+ ASSERT(libfsverity_compute_digest(&f, read_fn, &params, &d) == -EINVAL);
+ params.version = 1000;
+ ASSERT(libfsverity_compute_digest(&f, read_fn, &params, &d) == -EINVAL);
+
+ /* bad hash_algorithm */
+ params = good_params;
+ params.hash_algorithm = 1000;
+ ASSERT(libfsverity_compute_digest(&f, read_fn, &params, &d) == -EINVAL);
+
+ /* bad block_size */
+ params = good_params;
+ params.block_size = 1;
+ ASSERT(libfsverity_compute_digest(&f, read_fn, &params, &d) == -EINVAL);
+ params.block_size = 4097;
+ ASSERT(libfsverity_compute_digest(&f, read_fn, &params, &d) == -EINVAL);
+
+ /* bad salt_size */
+ params = good_params;
+ params.salt_size = 1000;
+ ASSERT(libfsverity_compute_digest(&f, read_fn, &params, &d) == -EINVAL);
+ params.salt = (u8 *)"";
+ ASSERT(libfsverity_compute_digest(&f, read_fn, &params, &d) == -EINVAL);
+
+ /* bad reserved fields */
+ params = good_params;
+ params.reserved1[0] = 1;
+ ASSERT(libfsverity_compute_digest(&f, read_fn, &params, &d) == -EINVAL);
+ params = good_params;
+ params.reserved1[ARRAY_SIZE(params.reserved1) - 1] = 1;
+ ASSERT(libfsverity_compute_digest(&f, read_fn, &params, &d) == -EINVAL);
+ params = good_params;
+ params.reserved2[0] = 1;
+ ASSERT(libfsverity_compute_digest(&f, read_fn, &params, &d) == -EINVAL);
+ params = good_params;
+ params.reserved2[ARRAY_SIZE(params.reserved2) - 1] = 1;
+ ASSERT(libfsverity_compute_digest(&f, read_fn, &params, &d) == -EINVAL);
+
+ /* error reading file */
+ ASSERT(libfsverity_compute_digest(&f, error_read_fn, &good_params, &d) == -EIO);
+
+ ASSERT(d == NULL);
+}
+
+int main(int argc, char *argv[])
+{
+ const bool update = (argc == 2 && !strcmp(argv[1], "--update"));
+ size_t i;
+ struct mem_file f = {};
+ struct libfsverity_merkle_tree_params params;
+ struct libfsverity_digest *d;
+ int err;
+
+ install_libfsverity_error_handler();
+
+ for (i = 0; i < ARRAY_SIZE(test_cases); i++)
+ f.size = max(f.size, test_cases[i].file_size);
+
+ f.data = xmalloc(f.size);
+ for (i = 0; i < f.size; i++)
+ f.data[i] = (i % 11) + (i % 439) + (i % 1103);
+
+ for (i = 0; i < ARRAY_SIZE(test_cases); i++) {
+ u32 expected_alg = test_cases[i].hash_algorithm ?:
+ FS_VERITY_HASH_ALG_SHA256;
+
+ memset(&params, 0, sizeof(params));
+ params.version = 1;
+ params.hash_algorithm = test_cases[i].hash_algorithm;
+ params.file_size = test_cases[i].file_size;
+ params.block_size = test_cases[i].block_size;
+ if (test_cases[i].salt) {
+ params.salt = (const u8 *)test_cases[i].salt;
+ params.salt_size = strlen(test_cases[i].salt);
+ }
+
+ f.size = test_cases[i].file_size;
+ f.offset = 0;
+
+ err = libfsverity_compute_digest(&f, read_fn, &params, &d);
+ ASSERT(err == 0);
+
+ ASSERT(d->digest_algorithm == expected_alg);
+ ASSERT(d->digest_size ==
+ libfsverity_get_digest_size(expected_alg));
+ if (update)
+ fix_digest_and_print(&test_cases[i], d);
+ else
+ ASSERT(!memcmp(d->digest, test_cases[i].digest,
+ d->digest_size));
+ free(d);
+ d = NULL;
+ }
+ free(f.data);
+ if (update) {
+ printf("\t}\n");
+ return 1;
+ }
+
+ test_invalid_params();
+ printf("test_compute_digest passed\n");
+ return 0;
+}
diff --git a/programs/test_hash_algs.c b/programs/test_hash_algs.c
new file mode 100644
index 0000000..ede9f05
--- /dev/null
+++ b/programs/test_hash_algs.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Test the hash algorithm-related libfsverity APIs.
+ *
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by an MIT-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/MIT.
+ */
+
+#include "utils.h"
+
+#define SHA256_DIGEST_SIZE 32
+#define SHA512_DIGEST_SIZE 64
+
+int main(void)
+{
+ install_libfsverity_error_handler();
+
+ ASSERT(libfsverity_get_digest_size(0) == -1);
+ ASSERT(libfsverity_get_hash_name(0) == NULL);
+ ASSERT(libfsverity_find_hash_alg_by_name("bad") == 0);
+ ASSERT(libfsverity_find_hash_alg_by_name(NULL) == 0);
+
+ ASSERT(libfsverity_get_digest_size(100) == -1);
+ ASSERT(libfsverity_get_hash_name(100) == NULL);
+
+ ASSERT(libfsverity_get_digest_size(FS_VERITY_HASH_ALG_SHA256) ==
+ SHA256_DIGEST_SIZE);
+ ASSERT(!strcmp("sha256",
+ libfsverity_get_hash_name(FS_VERITY_HASH_ALG_SHA256)));
+ ASSERT(libfsverity_find_hash_alg_by_name("sha256") ==
+ FS_VERITY_HASH_ALG_SHA256);
+
+ ASSERT(libfsverity_get_digest_size(FS_VERITY_HASH_ALG_SHA512) ==
+ SHA512_DIGEST_SIZE);
+ ASSERT(!strcmp("sha512",
+ libfsverity_get_hash_name(FS_VERITY_HASH_ALG_SHA512)));
+ ASSERT(libfsverity_find_hash_alg_by_name("sha512") ==
+ FS_VERITY_HASH_ALG_SHA512);
+
+ printf("test_hash_algs passed\n");
+ return 0;
+}
diff --git a/programs/test_sign_digest.c b/programs/test_sign_digest.c
new file mode 100644
index 0000000..412c6cb
--- /dev/null
+++ b/programs/test_sign_digest.c
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Test libfsverity_sign_digest().
+ *
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by an MIT-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/MIT.
+ */
+
+#include "utils.h"
+
+#include <fcntl.h>
+
+#define SHA256_DIGEST_SIZE 32
+
+int main(void)
+{
+ struct libfsverity_digest *d = xzalloc(sizeof(*d) + SHA256_DIGEST_SIZE);
+ const struct libfsverity_signature_params params = {
+ .keyfile = "testdata/key.pem",
+ .certfile = "testdata/cert.pem",
+ };
+ u8 *sig;
+ size_t sig_size;
+ struct filedes file;
+ u8 *expected_sig;
+ u64 expected_sig_size;
+ int err;
+
+ install_libfsverity_error_handler();
+
+ d->digest_algorithm = FS_VERITY_HASH_ALG_SHA256;
+ d->digest_size = SHA256_DIGEST_SIZE;
+ memcpy(d->digest,
+ "\x91\x79\x00\xb0\xd2\x99\x45\x4a\xa3\x04\xd5\xde\xbc\x6f\x39"
+ "\xe4\xaf\x7b\x5a\xbe\x33\xbd\xbc\x56\x8d\x5d\x8f\x1e\x5c\x4d"
+ "\x86\x52", SHA256_DIGEST_SIZE);
+
+ err = libfsverity_sign_digest(d, &params, &sig, &sig_size);
+ ASSERT(err == 0);
+
+ ASSERT(open_file(&file, "testdata/file.sig", O_RDONLY, 0));
+ ASSERT(get_file_size(&file, &expected_sig_size));
+ ASSERT(sig_size == expected_sig_size);
+ expected_sig = xmalloc(sig_size);
+ ASSERT(full_read(&file, expected_sig, sig_size));
+ ASSERT(!memcmp(sig, expected_sig, sig_size));
+
+ free(d);
+ free(sig);
+ free(expected_sig);
+ filedes_close(&file);
+ printf("test_sign_digest passed\n");
+ return 0;
+}
diff --git a/programs/utils.c b/programs/utils.c
new file mode 100644
index 0000000..ce19b57
--- /dev/null
+++ b/programs/utils.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Utility functions for the 'fsverity' program
+ *
+ * Copyright 2018 Google LLC
+ *
+ * Use of this source code is governed by an MIT-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/MIT.
+ */
+
+#include "utils.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/* ========== Memory allocation ========== */
+
+void *xmalloc(size_t size)
+{
+ void *p = malloc(size);
+
+ if (!p)
+ fatal_error("out of memory");
+ return p;
+}
+
+void *xzalloc(size_t size)
+{
+ return memset(xmalloc(size), 0, size);
+}
+
+void *xmemdup(const void *mem, size_t size)
+{
+ return memcpy(xmalloc(size), mem, size);
+}
+
+char *xstrdup(const char *s)
+{
+ return xmemdup(s, strlen(s) + 1);
+}
+
+/* ========== Error messages and assertions ========== */
+
+static void do_error_msg(const char *format, va_list va, int err)
+{
+ fputs("ERROR: ", stderr);
+ vfprintf(stderr, format, va);
+ if (err)
+ fprintf(stderr, ": %s", strerror(err));
+ putc('\n', stderr);
+}
+
+void error_msg(const char *format, ...)
+{
+ va_list va;
+
+ va_start(va, format);
+ do_error_msg(format, va, 0);
+ va_end(va);
+}
+
+void error_msg_errno(const char *format, ...)
+{
+ va_list va;
+
+ va_start(va, format);
+ do_error_msg(format, va, errno);
+ va_end(va);
+}
+
+__noreturn void fatal_error(const char *format, ...)
+{
+ va_list va;
+
+ va_start(va, format);
+ do_error_msg(format, va, 0);
+ va_end(va);
+ abort();
+}
+
+__noreturn void assertion_failed(const char *expr, const char *file, int line)
+{
+ fatal_error("Assertion failed: %s at %s:%d", expr, file, line);
+}
+
+static void print_libfsverity_error(const char *msg)
+{
+ error_msg("%s", msg);
+}
+
+void install_libfsverity_error_handler(void)
+{
+ libfsverity_set_error_callback(print_libfsverity_error);
+}
+
+/* ========== File utilities ========== */
+
+bool open_file(struct filedes *file, const char *filename, int flags, int mode)
+{
+ file->fd = open(filename, flags | O_BINARY, mode);
+ if (file->fd < 0) {
+ error_msg_errno("can't open '%s' for %s", filename,
+ (flags & O_ACCMODE) == O_RDONLY ? "reading" :
+ (flags & O_ACCMODE) == O_WRONLY ? "writing" :
+ "reading and writing");
+ return false;
+ }
+ file->name = xstrdup(filename);
+ return true;
+}
+
+bool get_file_size(struct filedes *file, u64 *size_ret)
+{
+ struct stat stbuf;
+
+ if (fstat(file->fd, &stbuf) != 0) {
+ error_msg_errno("can't stat file '%s'", file->name);
+ return false;
+ }
+ *size_ret = stbuf.st_size;
+ return true;
+}
+
+bool full_read(struct filedes *file, void *buf, size_t count)
+{
+ while (count) {
+ int n = read(file->fd, buf, min(count, INT_MAX));
+
+ if (n < 0) {
+ error_msg_errno("reading from '%s'", file->name);
+ return false;
+ }
+ if (n == 0) {
+ error_msg("unexpected end-of-file on '%s'", file->name);
+ return false;
+ }
+ buf += n;
+ count -= n;
+ }
+ return true;
+}
+
+bool full_write(struct filedes *file, const void *buf, size_t count)
+{
+ while (count) {
+ int n = write(file->fd, buf, min(count, INT_MAX));
+
+ if (n < 0) {
+ error_msg_errno("writing to '%s'", file->name);
+ return false;
+ }
+ buf += n;
+ count -= n;
+ }
+ return true;
+}
+
+bool filedes_close(struct filedes *file)
+{
+ int res;
+
+ if (file->fd < 0)
+ return true;
+ res = close(file->fd);
+ if (res != 0)
+ error_msg_errno("closing '%s'", file->name);
+ file->fd = -1;
+ free(file->name);
+ file->name = NULL;
+ return res == 0;
+}
+
+int read_callback(void *file, void *buf, size_t count)
+{
+ errno = 0;
+ if (!full_read(file, buf, count))
+ return errno ? -errno : -EIO;
+ return 0;
+}
+
+/* ========== String utilities ========== */
+
+static int hex2bin_char(char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'a' && c <= 'f')
+ return 10 + (c - 'a');
+ if (c >= 'A' && c <= 'F')
+ return 10 + (c - 'A');
+ return -1;
+}
+
+bool hex2bin(const char *hex, u8 *bin, size_t bin_len)
+{
+ size_t i;
+
+ if (strlen(hex) != 2 * bin_len)
+ return false;
+
+ for (i = 0; i < bin_len; i++) {
+ int hi = hex2bin_char(*hex++);
+ int lo = hex2bin_char(*hex++);
+
+ if (hi < 0 || lo < 0)
+ return false;
+ bin[i] = (hi << 4) | lo;
+ }
+ return true;
+}
+
+static char bin2hex_char(u8 nibble)
+{
+ ASSERT(nibble <= 0xf);
+
+ if (nibble < 10)
+ return '0' + nibble;
+ return 'a' + (nibble - 10);
+}
+
+void bin2hex(const u8 *bin, size_t bin_len, char *hex)
+{
+ size_t i;
+
+ for (i = 0; i < bin_len; i++) {
+ *hex++ = bin2hex_char(bin[i] >> 4);
+ *hex++ = bin2hex_char(bin[i] & 0xf);
+ }
+ *hex = '\0';
+}
diff --git a/programs/utils.h b/programs/utils.h
new file mode 100644
index 0000000..ab5005f
--- /dev/null
+++ b/programs/utils.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Utility functions for programs
+ *
+ * Copyright 2018 Google LLC
+ *
+ * Use of this source code is governed by an MIT-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/MIT.
+ */
+#ifndef PROGRAMS_UTILS_H
+#define PROGRAMS_UTILS_H
+
+#include "libfsverity.h"
+#include "../common/common_defs.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+void *xmalloc(size_t size);
+void *xzalloc(size_t size);
+void *xmemdup(const void *mem, size_t size);
+char *xstrdup(const char *s);
+
+__printf(1, 2) __cold void error_msg(const char *format, ...);
+__printf(1, 2) __cold void error_msg_errno(const char *format, ...);
+__printf(1, 2) __cold __noreturn void fatal_error(const char *format, ...);
+__cold __noreturn void assertion_failed(const char *expr,
+ const char *file, int line);
+
+#define ASSERT(e) ({ if (!(e)) assertion_failed(#e, __FILE__, __LINE__); })
+
+void install_libfsverity_error_handler(void);
+
+struct filedes {
+ int fd;
+ char *name; /* filename, for logging or error messages */
+};
+
+bool open_file(struct filedes *file, const char *filename, int flags, int mode);
+bool get_file_size(struct filedes *file, u64 *size_ret);
+bool full_read(struct filedes *file, void *buf, size_t count);
+bool full_write(struct filedes *file, const void *buf, size_t count);
+bool filedes_close(struct filedes *file);
+int read_callback(void *file, void *buf, size_t count);
+
+bool hex2bin(const char *hex, u8 *bin, size_t bin_len);
+void bin2hex(const u8 *bin, size_t bin_len, char *hex);
+
+#endif /* PROGRAMS_UTILS_H */