Message ID | 20201022172155.2994326-1-luca.boccassi@gmail.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | [fsverity-utils] Add digest sub command | expand |
On Thu, Oct 22, 2020 at 06:21:55PM +0100, luca.boccassi@gmail.com wrote: > +/* Compute a file's fs-verity measurement, then print it in hex format. */ > +int fsverity_cmd_digest(const struct fsverity_command *cmd, > + int argc, char *argv[]) > +{ > + struct filedes file = { .fd = -1 }; > + u8 *salt = NULL; > + struct libfsverity_merkle_tree_params tree_params = { .version = 1 }; > + struct libfsverity_digest *digest = NULL; > + struct fsverity_signed_digest *d = NULL; > + char digest_hex[FS_VERITY_MAX_DIGEST_SIZE * 2 + sizeof(struct fsverity_signed_digest) * 2 + 1]; > + bool compact = false; > + int status; > + int c; > + > + while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) { > + switch (c) { > + case OPT_HASH_ALG: > + if (!parse_hash_alg_option(optarg, > + &tree_params.hash_algorithm)) > + goto out_usage; > + break; > + case OPT_BLOCK_SIZE: > + if (!parse_block_size_option(optarg, > + &tree_params.block_size)) > + goto out_usage; > + break; > + case OPT_SALT: > + if (!parse_salt_option(optarg, &salt, > + &tree_params.salt_size)) > + goto out_usage; > + tree_params.salt = salt; > + break; > + case OPT_COMPACT: > + compact = true; > + break; > + default: > + goto out_usage; > + } > + } > + > + argv += optind; > + argc -= optind; > + > + if (argc != 1) > + goto out_usage; > + > + if (tree_params.hash_algorithm == 0) > + tree_params.hash_algorithm = FS_VERITY_HASH_ALG_DEFAULT; > + > + if (tree_params.block_size == 0) > + tree_params.block_size = get_default_block_size(); > + > + 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; > + } > + > + ASSERT(digest->digest_size <= FS_VERITY_MAX_DIGEST_SIZE); > + > + d = xzalloc(sizeof(*d) + digest->digest_size); > + if (!d) > + goto out_err; > + 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); > + > + if (compact) > + printf("%s", digest_hex); > + else > + printf("File '%s' (%s:%s)\n", argv[0], > + libfsverity_get_hash_name(tree_params.hash_algorithm), > + digest_hex); Can you make this command format its output in the same way as 'fsverity measure' by default, and put the 'struct fsverity_signed_digest' formatted output behind an option, like --for-builtin-sig? The 'struct fsverity_signed_digest' is specific to the builtin (in-kernel) signature support, which isn't the only way to use fs-verity. The signature verification can also be done in userspace, which is more flexible. (And you should consider doing it that way, if you haven't already. I'm not sure exactly what your use case is.) So when possible, I'd like to have the default be the basic fs-verity feature. If someone then specifically wants to use the builtin signature support on top of that, as opposed to using fs-verity in another way, then they can provide the option they need to do that. Separately, it would also be nice to share more code with cmd_sign.c, as they both have to parse a lot of the same options. Maybe it doesn't work out, though. - Eric
On Fri, 2020-10-23 at 21:23 -0700, Eric Biggers wrote: > On Thu, Oct 22, 2020 at 06:21:55PM +0100, luca.boccassi@gmail.com wrote: > > +/* Compute a file's fs-verity measurement, then print it in hex format. */ > > +int fsverity_cmd_digest(const struct fsverity_command *cmd, > > + int argc, char *argv[]) > > +{ > > + struct filedes file = { .fd = -1 }; > > + u8 *salt = NULL; > > + struct libfsverity_merkle_tree_params tree_params = { .version = 1 }; > > + struct libfsverity_digest *digest = NULL; > > + struct fsverity_signed_digest *d = NULL; > > + char digest_hex[FS_VERITY_MAX_DIGEST_SIZE * 2 + sizeof(struct fsverity_signed_digest) * 2 + 1]; > > + bool compact = false; > > + int status; > > + int c; > > + > > + while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) { > > + switch (c) { > > + case OPT_HASH_ALG: > > + if (!parse_hash_alg_option(optarg, > > + &tree_params.hash_algorithm)) > > + goto out_usage; > > + break; > > + case OPT_BLOCK_SIZE: > > + if (!parse_block_size_option(optarg, > > + &tree_params.block_size)) > > + goto out_usage; > > + break; > > + case OPT_SALT: > > + if (!parse_salt_option(optarg, &salt, > > + &tree_params.salt_size)) > > + goto out_usage; > > + tree_params.salt = salt; > > + break; > > + case OPT_COMPACT: > > + compact = true; > > + break; > > + default: > > + goto out_usage; > > + } > > + } > > + > > + argv += optind; > > + argc -= optind; > > + > > + if (argc != 1) > > + goto out_usage; > > + > > + if (tree_params.hash_algorithm == 0) > > + tree_params.hash_algorithm = FS_VERITY_HASH_ALG_DEFAULT; > > + > > + if (tree_params.block_size == 0) > > + tree_params.block_size = get_default_block_size(); > > + > > + 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; > > + } > > + > > + ASSERT(digest->digest_size <= FS_VERITY_MAX_DIGEST_SIZE); > > + > > + d = xzalloc(sizeof(*d) + digest->digest_size); > > + if (!d) > > + goto out_err; > > + 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); > > + > > + if (compact) > > + printf("%s", digest_hex); > > + else > > + printf("File '%s' (%s:%s)\n", argv[0], > > + libfsverity_get_hash_name(tree_params.hash_algorithm), > > + digest_hex); > > Can you make this command format its output in the same way as > 'fsverity measure' by default, and put the 'struct fsverity_signed_digest' > formatted output behind an option, like --for-builtin-sig? Sure, done in v2. > The 'struct fsverity_signed_digest' is specific to the builtin (in-kernel) > signature support, which isn't the only way to use fs-verity. The signature > verification can also be done in userspace, which is more flexible. (And you > should consider doing it that way, if you haven't already. I'm not sure exactly > what your use case is.) Our use case is to ultimately add kernel-side policy enforcement of integrity via the IPE LSM: https://microsoft.github.io/ipe/ so we can't do without kernel verification. I need the new command as I've got some teams building and signing on Windows, using whatever native API is available there, so can't use the 'fsverity sign' command. > So when possible, I'd like to have the default be the basic fs-verity feature. > If someone then specifically wants to use the builtin signature support on top > of that, as opposed to using fs-verity in another way, then they can provide the > option they need to do that. > > Separately, it would also be nice to share more code with cmd_sign.c, as they > both have to parse a lot of the same options. Maybe it doesn't work out, > though. I did have a look at this at the beginning, but I was not happy with how it looked like - both are using APIs from the public library and are compact enough, so it felt a bit awkward to add yet another shim layer, and opted to avoid it. If you feel strongly about it let me know and I can revise.
diff --git a/Makefile b/Makefile index 2a2e067..3fc1bec 100644 --- a/Makefile +++ b/Makefile @@ -127,6 +127,7 @@ ALL_PROG_HEADERS := $(wildcard programs/*.h) $(COMMON_HEADERS) PROG_COMMON_SRC := programs/utils.c PROG_COMMON_OBJ := $(PROG_COMMON_SRC:.c=.o) FSVERITY_PROG_OBJ := $(PROG_COMMON_OBJ) \ + programs/cmd_digest.o \ programs/cmd_enable.o \ programs/cmd_measure.o \ programs/cmd_sign.o \ @@ -181,6 +182,8 @@ check:fsverity test_programs $(RUN_FSVERITY) sign fsverity fsverity.sig --hash=sha512 \ --block-size=512 --salt=12345678 \ --key=testdata/key.pem --cert=testdata/cert.pem > /dev/null + $(RUN_FSVERITY) digest fsverity --hash=sha512 \ + --block-size=512 --salt=12345678 > /dev/null rm -f fsverity.sig @echo "All tests passed!" diff --git a/README.md b/README.md index 669a243..997b699 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,10 @@ the set of X.509 certificates that have been loaded into the fsverity enable file --signature=file.sig rm -f file.sig sha256sum file + + # The digest to be signed can also be printed separately, hex + # encoded, in case the integrated signing cannot be used: + fsverity digest file --compact | xxd -p -r | openssl smime -sign -in /dev/stdin ... ``` By default, it's not required that verity files have a signature. diff --git a/programs/cmd_digest.c b/programs/cmd_digest.c new file mode 100644 index 0000000..314cda7 --- /dev/null +++ b/programs/cmd_digest.c @@ -0,0 +1,137 @@ +// 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> + +enum { + OPT_HASH_ALG, + OPT_BLOCK_SIZE, + OPT_SALT, + OPT_COMPACT, +}; + +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}, + {NULL, 0, NULL, 0} +}; + +struct fsverity_signed_digest { + char magic[8]; /* must be "FSVerity" */ + __le16 digest_algorithm; + __le16 digest_size; + __u8 digest[]; +}; + +/* Compute a file's fs-verity measurement, then print it in hex format. */ +int fsverity_cmd_digest(const struct fsverity_command *cmd, + int argc, char *argv[]) +{ + struct filedes file = { .fd = -1 }; + u8 *salt = NULL; + struct libfsverity_merkle_tree_params tree_params = { .version = 1 }; + struct libfsverity_digest *digest = NULL; + struct fsverity_signed_digest *d = NULL; + char digest_hex[FS_VERITY_MAX_DIGEST_SIZE * 2 + sizeof(struct fsverity_signed_digest) * 2 + 1]; + bool compact = false; + int status; + int c; + + while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) { + switch (c) { + case OPT_HASH_ALG: + if (!parse_hash_alg_option(optarg, + &tree_params.hash_algorithm)) + goto out_usage; + break; + case OPT_BLOCK_SIZE: + if (!parse_block_size_option(optarg, + &tree_params.block_size)) + goto out_usage; + break; + case OPT_SALT: + if (!parse_salt_option(optarg, &salt, + &tree_params.salt_size)) + goto out_usage; + tree_params.salt = salt; + break; + case OPT_COMPACT: + compact = true; + break; + default: + goto out_usage; + } + } + + argv += optind; + argc -= optind; + + if (argc != 1) + goto out_usage; + + if (tree_params.hash_algorithm == 0) + tree_params.hash_algorithm = FS_VERITY_HASH_ALG_DEFAULT; + + if (tree_params.block_size == 0) + tree_params.block_size = get_default_block_size(); + + 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; + } + + ASSERT(digest->digest_size <= FS_VERITY_MAX_DIGEST_SIZE); + + d = xzalloc(sizeof(*d) + digest->digest_size); + if (!d) + goto out_err; + 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); + + if (compact) + printf("%s", digest_hex); + else + printf("File '%s' (%s:%s)\n", argv[0], + libfsverity_get_hash_name(tree_params.hash_algorithm), + digest_hex); + status = 0; +out: + filedes_close(&file); + free(salt); + free(digest); + 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 index e1bbfd6..580e4df 100644 --- a/programs/cmd_sign.c +++ b/programs/cmd_sign.c @@ -43,14 +43,6 @@ static const struct option longopts[] = { {NULL, 0, NULL, 0} }; -static int read_callback(void *file, void *buf, size_t count) -{ - errno = 0; - if (!full_read(file, buf, count)) - return errno ? -errno : -EIO; - return 0; -} - /* Sign a file for fs-verity by computing its measurement, then signing it. */ int fsverity_cmd_sign(const struct fsverity_command *cmd, int argc, char *argv[]) diff --git a/programs/fsverity.c b/programs/fsverity.c index 95f6964..3e089b8 100644 --- a/programs/fsverity.c +++ b/programs/fsverity.c @@ -21,6 +21,14 @@ static const struct fsverity_command { const char *usage_str; } fsverity_commands[] = { { + .name = "digest", + .func = fsverity_cmd_digest, + .short_desc = "Compute and print hex-encoded fs-verity digest of a file, for offline signing", + .usage_str = +" fsverity digest FILE\n" +" [--hash-alg=HASH_ALG] [--block-size=BLOCK_SIZE] [--salt=SALT]\n" +" [--compact]\n" + }, { .name = "enable", .func = fsverity_cmd_enable, .short_desc = "Enable fs-verity on a file", diff --git a/programs/fsverity.h b/programs/fsverity.h index fd9bc4a..669fef2 100644 --- a/programs/fsverity.h +++ b/programs/fsverity.h @@ -25,6 +25,10 @@ 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[]); diff --git a/programs/utils.c b/programs/utils.c index 0aca98d..facccda 100644 --- a/programs/utils.c +++ b/programs/utils.c @@ -175,6 +175,14 @@ bool filedes_close(struct filedes *file) 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) diff --git a/programs/utils.h b/programs/utils.h index 6968708..ab5005f 100644 --- a/programs/utils.h +++ b/programs/utils.h @@ -43,6 +43,7 @@ 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);