@@ -150,6 +150,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 \
@@ -204,6 +205,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!"
@@ -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 --for-builtin-sig | tr -d '\n' | xxd -p -r | openssl smime -sign -in /dev/stdin ...
```
By default, it's not required that verity files have a signature.
new file mode 100644
@@ -0,0 +1,146 @@
+// 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,
+ OPT_FOR_BUILTIN_SIG,
+};
+
+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}
+};
+
+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, for_builtin_sig = 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;
+ case OPT_FOR_BUILTIN_SIG:
+ for_builtin_sig = 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();
+
+ for (int i = 0; i < argc; i++) {
+ 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);
+
+ /* The kernel expects more than the digest as the signed payload */
+ if (for_builtin_sig) {
+ 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
+ printf("%s:%s %s\n",
+ libfsverity_get_hash_name(tree_params.hash_algorithm),
+ digest_hex, argv[i]);
+ }
+ 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;
+}
@@ -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[])
@@ -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] [--for-builtin-sig]\n"
+ }, {
.name = "enable",
.func = fsverity_cmd_enable,
.short_desc = "Enable fs-verity on a file",
@@ -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[]);
@@ -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)
@@ -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);