diff options
Diffstat (limited to 'programs')
-rw-r--r-- | programs/cmd_digest.c | 129 | ||||
-rw-r--r-- | programs/cmd_enable.c | 123 | ||||
-rw-r--r-- | programs/cmd_measure.c | 69 | ||||
-rw-r--r-- | programs/cmd_sign.c | 133 | ||||
-rw-r--r-- | programs/fsverity.c | 241 | ||||
-rw-r--r-- | programs/fsverity.h | 58 | ||||
-rw-r--r-- | programs/test_compute_digest.c | 310 | ||||
-rw-r--r-- | programs/test_hash_algs.c | 45 | ||||
-rw-r--r-- | programs/test_sign_digest.c | 57 | ||||
-rw-r--r-- | programs/utils.c | 235 | ||||
-rw-r--r-- | programs/utils.h | 51 |
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, ¶ms->hash_algorithm); + case OPT_BLOCK_SIZE: + return parse_block_size_option(arg, ¶ms->block_size); + case OPT_SALT: + return parse_salt_option(arg, (u8 **)¶ms->salt, + ¶ms->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, ¶ms, &d) == -EINVAL); + params.version = 1000; + ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); + + /* bad hash_algorithm */ + params = good_params; + params.hash_algorithm = 1000; + ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); + + /* bad block_size */ + params = good_params; + params.block_size = 1; + ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); + params.block_size = 4097; + ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); + + /* bad salt_size */ + params = good_params; + params.salt_size = 1000; + ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); + params.salt = (u8 *)""; + ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); + + /* bad reserved fields */ + params = good_params; + params.reserved1[0] = 1; + ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); + params = good_params; + params.reserved1[ARRAY_SIZE(params.reserved1) - 1] = 1; + ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); + params = good_params; + params.reserved2[0] = 1; + ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); + params = good_params; + params.reserved2[ARRAY_SIZE(params.reserved2) - 1] = 1; + ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &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(¶ms, 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, ¶ms, &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, ¶ms, &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 */ |