All of lore.kernel.org
 help / color / mirror / Atom feed
From: luca.boccassi@gmail.com
To: linux-fscrypt@vger.kernel.org
Subject: [fsverity-utils PATCH v5] Add digest sub command
Date: Mon, 26 Oct 2020 19:18:39 +0000	[thread overview]
Message-ID: <20201026191839.3329948-1-luca.boccassi@gmail.com> (raw)
In-Reply-To: <20201026181729.3322756-1-luca.boccassi@gmail.com>

From: Luca Boccassi <luca.boccassi@microsoft.com>

Add a digest sub command that prints a hex-encoded digest of
a file, ready to be signed offline (ie: includes the full
data that is expected by the kernel - magic string, digest
algorithm and size).

Useful in case the integrated signing mechanism with local cert/key
cannot be used.

Signed-off-by: Luca Boccassi <luca.boccassi@microsoft.com>
---
v2: add --for-builtin-sig and default to printing only the digest,
    without the extra values the kernel expects
v3: change output to match the measure command
    support multiple files as input
v4: fix cleanup issue introduced in v3
v5: Adjust comments to mention multi-file support in input
    Do not print the hash algo with --for-builtin-sig
    Close file handle on error

 Makefile              |   3 +
 README.md             |   4 ++
 programs/cmd_digest.c | 151 ++++++++++++++++++++++++++++++++++++++++++
 programs/cmd_sign.c   |   8 ---
 programs/fsverity.c   |   8 +++
 programs/fsverity.h   |   4 ++
 programs/utils.c      |   8 +++
 programs/utils.h      |   1 +
 8 files changed, 179 insertions(+), 8 deletions(-)
 create mode 100644 programs/cmd_digest.c

diff --git a/Makefile b/Makefile
index b3d5041..6c6c8c9 100644
--- a/Makefile
+++ b/Makefile
@@ -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!"
 
diff --git a/README.md b/README.md
index 669a243..36a52e9 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 --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.
diff --git a/programs/cmd_digest.c b/programs/cmd_digest.c
new file mode 100644
index 0000000..88843d1
--- /dev/null
+++ b/programs/cmd_digest.c
@@ -0,0 +1,151 @@
+// 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 the fs-verity measurement of the given file(s), for offline signing. */
+int fsverity_cmd_digest(const struct fsverity_command *cmd,
+		      int argc, char *argv[])
+{
+	u8 *salt = NULL;
+	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:
+			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++) {
+		struct fsverity_signed_digest *d = NULL;
+		struct libfsverity_digest *digest = NULL;
+		char digest_hex[FS_VERITY_MAX_DIGEST_SIZE * 2 + sizeof(struct fsverity_signed_digest) * 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);
+
+		/* 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 if (for_builtin_sig)
+			printf("%s %s\n", digest_hex, argv[i]);
+		else
+			printf("%s:%s %s\n",
+				libfsverity_get_hash_name(tree_params.hash_algorithm),
+				digest_hex, argv[i]);
+
+		filedes_close(&file);
+		free(digest);
+		free(d);
+	}
+	status = 0;
+out:
+	free(salt);
+	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_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..0f9da42 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 the fs-verity measurement 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"
+	}, {
 		.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);
-- 
2.20.1


  parent reply	other threads:[~2020-10-26 19:18 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-10-22 17:21 [fsverity-utils PATCH] Add digest sub command luca.boccassi
2020-10-24  4:23 ` Eric Biggers
2020-10-26 11:49   ` Luca Boccassi
2020-10-26 11:40 ` [fsverity-utils PATCH v2] " luca.boccassi
2020-10-26 17:48   ` Eric Biggers
2020-10-26 18:12     ` Luca Boccassi
2020-10-26 18:11   ` [fsverity-utils PATCH v3] " luca.boccassi
2020-10-26 18:17     ` [fsverity-utils PATCH v4] " luca.boccassi
2020-10-26 18:58       ` Eric Biggers
2020-10-26 19:21         ` Luca Boccassi
2020-10-26 19:18       ` luca.boccassi [this message]
2020-10-26 20:36         ` [fsverity-utils PATCH v5] " Eric Biggers

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20201026191839.3329948-1-luca.boccassi@gmail.com \
    --to=luca.boccassi@gmail.com \
    --cc=linux-fscrypt@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.