All of lore.kernel.org
 help / color / mirror / Atom feed
* [fsverity-utils PATCH] Add digest sub command
@ 2020-10-22 17:21 luca.boccassi
  2020-10-24  4:23 ` Eric Biggers
  2020-10-26 11:40 ` [fsverity-utils PATCH v2] " luca.boccassi
  0 siblings, 2 replies; 12+ messages in thread
From: luca.boccassi @ 2020-10-22 17:21 UTC (permalink / raw)
  To: linux-fscrypt

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>
---
 Makefile              |   3 +
 README.md             |   4 ++
 programs/cmd_digest.c | 137 ++++++++++++++++++++++++++++++++++++++++++
 programs/cmd_sign.c   |   8 ---
 programs/fsverity.c   |   8 +++
 programs/fsverity.h   |   4 ++
 programs/utils.c      |   8 +++
 programs/utils.h      |   1 +
 8 files changed, 165 insertions(+), 8 deletions(-)
 create mode 100644 programs/cmd_digest.c

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);
-- 
2.20.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [fsverity-utils PATCH] Add digest sub command
  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
  1 sibling, 1 reply; 12+ messages in thread
From: Eric Biggers @ 2020-10-24  4:23 UTC (permalink / raw)
  To: luca.boccassi; +Cc: linux-fscrypt

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

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [fsverity-utils PATCH v2] Add digest sub command
  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:40 ` luca.boccassi
  2020-10-26 17:48   ` Eric Biggers
  2020-10-26 18:11   ` [fsverity-utils PATCH v3] " luca.boccassi
  1 sibling, 2 replies; 12+ messages in thread
From: luca.boccassi @ 2020-10-26 11:40 UTC (permalink / raw)
  To: linux-fscrypt

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

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

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..f219cf7 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 | 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..b9a1945
--- /dev/null
+++ b/programs/cmd_digest.c
@@ -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();
+
+	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);
+
+	/* The kernel expects more than the digest as the signed payload */
+	if (for_builtin_sig) {
+		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);
+	} else
+		bin2hex(digest->digest, 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..7afd569 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] [--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


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [fsverity-utils PATCH] Add digest sub command
  2020-10-24  4:23 ` Eric Biggers
@ 2020-10-26 11:49   ` Luca Boccassi
  0 siblings, 0 replies; 12+ messages in thread
From: Luca Boccassi @ 2020-10-26 11:49 UTC (permalink / raw)
  To: Eric Biggers; +Cc: linux-fscrypt

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.

-- 
Kind regards,
Luca Boccassi


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [fsverity-utils PATCH v2] Add digest sub command
  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
  1 sibling, 1 reply; 12+ messages in thread
From: Eric Biggers @ 2020-10-26 17:48 UTC (permalink / raw)
  To: luca.boccassi; +Cc: linux-fscrypt

On Mon, Oct 26, 2020 at 11:40:07AM +0000, 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, 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;

I think this should allow specifying multiple files, like 'fsverity measure'
does.  'fsverity measure' is intended to behave like the sha256sum program.

> +	/* The kernel expects more than the digest as the signed payload */
> +	if (for_builtin_sig) {
> +		d = xzalloc(sizeof(*d) + digest->digest_size);
> +		if (!d)
> +			goto out_err;

No need to check the return value of xzalloc(), since it exits on error.

> +	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);

Please make the output in the !compact case match 'fsverity measure':

	printf("%s:%s %s\n",
	       libfsverity_get_hash_name(tree_params.hash_algorithm),
	       digest_hex, argv[i]);

- Eric

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [fsverity-utils PATCH v3] Add digest sub command
  2020-10-26 11:40 ` [fsverity-utils PATCH v2] " luca.boccassi
  2020-10-26 17:48   ` Eric Biggers
@ 2020-10-26 18:11   ` luca.boccassi
  2020-10-26 18:17     ` [fsverity-utils PATCH v4] " luca.boccassi
  1 sibling, 1 reply; 12+ messages in thread
From: luca.boccassi @ 2020-10-26 18:11 UTC (permalink / raw)
  To: linux-fscrypt

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

 Makefile              |   3 +
 README.md             |   4 ++
 programs/cmd_digest.c | 146 ++++++++++++++++++++++++++++++++++++++++++
 programs/cmd_sign.c   |   8 ---
 programs/fsverity.c   |   8 +++
 programs/fsverity.h   |   4 ++
 programs/utils.c      |   8 +++
 programs/utils.h      |   1 +
 8 files changed, 174 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..cd21206
--- /dev/null
+++ b/programs/cmd_digest.c
@@ -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;
+}
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..c7c4f75 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] [--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


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [fsverity-utils PATCH v2] Add digest sub command
  2020-10-26 17:48   ` Eric Biggers
@ 2020-10-26 18:12     ` Luca Boccassi
  0 siblings, 0 replies; 12+ messages in thread
From: Luca Boccassi @ 2020-10-26 18:12 UTC (permalink / raw)
  To: Eric Biggers; +Cc: linux-fscrypt

On Mon, 2020-10-26 at 10:48 -0700, Eric Biggers wrote:
> On Mon, Oct 26, 2020 at 11:40:07AM +0000, 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, 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;
> 
> I think this should allow specifying multiple files, like 'fsverity measure'
> does.  'fsverity measure' is intended to behave like the sha256sum program.

Added in v3.

> > +	/* The kernel expects more than the digest as the signed payload */
> > +	if (for_builtin_sig) {
> > +		d = xzalloc(sizeof(*d) + digest->digest_size);
> > +		if (!d)
> > +			goto out_err;
> 
> No need to check the return value of xzalloc(), since it exits on error.

Removed in v3.

> > +	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);
> 
> Please make the output in the !compact case match 'fsverity measure':
> 
> 	printf("%s:%s %s\n",
> 	       libfsverity_get_hash_name(tree_params.hash_algorithm),
> 	       digest_hex, argv[i]);
> 
> - Eric

Done with v3, sorry got confused with the 'sign' output.

-- 
Kind regards,
Luca Boccassi


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [fsverity-utils PATCH v4] Add digest sub command
  2020-10-26 18:11   ` [fsverity-utils PATCH v3] " luca.boccassi
@ 2020-10-26 18:17     ` luca.boccassi
  2020-10-26 18:58       ` Eric Biggers
  2020-10-26 19:18       ` [fsverity-utils PATCH v5] " luca.boccassi
  0 siblings, 2 replies; 12+ messages in thread
From: luca.boccassi @ 2020-10-26 18:17 UTC (permalink / raw)
  To: linux-fscrypt

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

 Makefile              |   3 +
 README.md             |   4 ++
 programs/cmd_digest.c | 148 ++++++++++++++++++++++++++++++++++++++++++
 programs/cmd_sign.c   |   8 ---
 programs/fsverity.c   |   8 +++
 programs/fsverity.h   |   4 ++
 programs/utils.c      |   8 +++
 programs/utils.h      |   1 +
 8 files changed, 176 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..d06e0d9
--- /dev/null
+++ b/programs/cmd_digest.c
@@ -0,0 +1,148 @@
+// 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[])
+{
+	u8 *salt = NULL;
+	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 filedes file = { .fd = -1 };
+		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
+			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:
+	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..c7c4f75 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] [--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


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [fsverity-utils PATCH v4] Add digest sub command
  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       ` [fsverity-utils PATCH v5] " luca.boccassi
  1 sibling, 1 reply; 12+ messages in thread
From: Eric Biggers @ 2020-10-26 18:58 UTC (permalink / raw)
  To: luca.boccassi; +Cc: linux-fscrypt

On Mon, Oct 26, 2020 at 06:17:29PM +0000, 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[])

Since it can be more than one file now:

/* Compute the fs-verity measurement of the given file(s), for offline signing */

> +	for (int i = 0; i < argc; i++) {
> +		struct filedes file = { .fd = -1 };
> +		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;

'file' doesn't get closed on error.  Making it back to the outer scope would fix
that.

> +		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]);

I don't think the hash algorithm should be printed in the
'!compact && for_builtin_sig' case, since it's already included in the struct
that gets hex-encoded.  I.e.

		else if (for_builtin_sig)
			printf("%s %s\n", digest_hex, argv[i]);

> diff --git a/programs/fsverity.c b/programs/fsverity.c
> index 95f6964..c7c4f75 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",

Likewise, since this can now accept multiple files:

"Compute the fs-verity measurement of the given file(s), for offline signing"

(I don't think that "printed as hex" needs to be explicitly mentioned here.)

- Eric

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [fsverity-utils PATCH v5] Add digest sub command
  2020-10-26 18:17     ` [fsverity-utils PATCH v4] " luca.boccassi
  2020-10-26 18:58       ` Eric Biggers
@ 2020-10-26 19:18       ` luca.boccassi
  2020-10-26 20:36         ` Eric Biggers
  1 sibling, 1 reply; 12+ messages in thread
From: luca.boccassi @ 2020-10-26 19:18 UTC (permalink / raw)
  To: linux-fscrypt

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


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [fsverity-utils PATCH v4] Add digest sub command
  2020-10-26 18:58       ` Eric Biggers
@ 2020-10-26 19:21         ` Luca Boccassi
  0 siblings, 0 replies; 12+ messages in thread
From: Luca Boccassi @ 2020-10-26 19:21 UTC (permalink / raw)
  To: Eric Biggers; +Cc: linux-fscrypt

On Mon, 2020-10-26 at 11:58 -0700, Eric Biggers wrote:
> On Mon, Oct 26, 2020 at 06:17:29PM +0000, 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[])
> 
> Since it can be more than one file now:
> 
> /* Compute the fs-verity measurement of the given file(s), for offline signing */

Applied in v5.

> > +	for (int i = 0; i < argc; i++) {
> > +		struct filedes file = { .fd = -1 };
> > +		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;
> 
> 'file' doesn't get closed on error.  Making it back to the outer scope would fix
> that.

Added to out_err in v5 (I had ignored it as cmd_sign does currently).

> > +		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]);
> 
> I don't think the hash algorithm should be printed in the
> '!compact && for_builtin_sig' case, since it's already included in the struct
> that gets hex-encoded.  I.e.
> 
> 		else if (for_builtin_sig)
> 			printf("%s %s\n", digest_hex, argv[i]);

Fixed in v5.

> > diff --git a/programs/fsverity.c b/programs/fsverity.c
> > index 95f6964..c7c4f75 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",
> 
> Likewise, since this can now accept multiple files:
> 
> "Compute the fs-verity measurement of the given file(s), for offline signing"
> 
> (I don't think that "printed as hex" needs to be explicitly mentioned here.)

Done in v5.

-- 
Kind regards,
Luca Boccassi


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [fsverity-utils PATCH v5] Add digest sub command
  2020-10-26 19:18       ` [fsverity-utils PATCH v5] " luca.boccassi
@ 2020-10-26 20:36         ` Eric Biggers
  0 siblings, 0 replies; 12+ messages in thread
From: Eric Biggers @ 2020-10-26 20:36 UTC (permalink / raw)
  To: luca.boccassi; +Cc: linux-fscrypt

On Mon, Oct 26, 2020 at 07:18:39PM +0000, luca.boccassi@gmail.com wrote:
> 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>

Applied with a few tweaks.  Thanks!

- Eric

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2020-10-26 20:36 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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       ` [fsverity-utils PATCH v5] " luca.boccassi
2020-10-26 20:36         ` Eric Biggers

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.