diff mbox series

[v2,ima-evm-utils,2/3] Sign an fs-verity file digest

Message ID 20220512183056.307597-2-zohar@linux.ibm.com (mailing list archive)
State New
Headers show
Series [v2,ima-evm-utils,1/3] initialize errno in cmd_sign_hash() | expand

Commit Message

Mimi Zohar May 12, 2022, 6:30 p.m. UTC
Sign fs-verity file digests provided in the format as produced by
"fsverity measure".  The output is of the same format as the input,
but with the file signature appended.  Use setfattr to write the
signature as security.ima xattr.

fsverity measure format: <algo>:<hash> <pathname>
output format: <algo>:<hash> <pathname> <signature>

Instead of directly signing the fsverity hash, to disambiguate the
original IMA signatures from the fs-verity signatures stored in the
security.ima xattr a new signature format version 3 (sigv3) was
defined as the hash of the xattr type (enum evm_ima_xattr_type),
the hash algorithm (enum hash_algo), and the hash.

Example:
fsverity measure <pathname> | evmctl sign_hash --veritysig \
 --key <pem encoded private key>

Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
---
 README          |   3 +-
 src/evmctl.c    | 103 +++++++++++++++++++++++++++++++++++++++++-------
 src/imaevm.h    |   5 ++-
 src/libimaevm.c |  85 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 179 insertions(+), 17 deletions(-)

Comments

Stefan Berger May 12, 2022, 9:48 p.m. UTC | #1
On 5/12/22 14:30, Mimi Zohar wrote:
> Sign fs-verity file digests provided in the format as produced by
> "fsverity measure".  The output is of the same format as the input,
> but with the file signature appended.  Use setfattr to write the
> signature as security.ima xattr.
> 
> fsverity measure format: <algo>:<hash> <pathname>
> output format: <algo>:<hash> <pathname> <signature>
> 
> Instead of directly signing the fsverity hash, to disambiguate the
> original IMA signatures from the fs-verity signatures stored in the
> security.ima xattr a new signature format version 3 (sigv3) was
> defined as the hash of the xattr type (enum evm_ima_xattr_type),
> the hash algorithm (enum hash_algo), and the hash.
> 
> Example:
> fsverity measure <pathname> | evmctl sign_hash --veritysig \
>   --key <pem encoded private key>
> 
> Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
> ---
>   README          |   3 +-
>   src/evmctl.c    | 103 +++++++++++++++++++++++++++++++++++++++++-------
>   src/imaevm.h    |   5 ++-
>   src/libimaevm.c |  85 +++++++++++++++++++++++++++++++++++++++
>   4 files changed, 179 insertions(+), 17 deletions(-)
> 
> diff --git a/README b/README
> index 5b5ecb52a74b..ffe46ad75728 100644
> --- a/README
> +++ b/README
> @@ -34,7 +34,7 @@ COMMANDS
>    ima_hash file
>    ima_measurement [--ignore-violations] [--verify-sig [--key "key1, key2, ..."]]  [--pcrs [hash-algorithm,]file [--pcrs hash-algorithm,file] ...] file
>    ima_fix [-t fdsxm] path
> - sign_hash [--key key] [--pass password]
> + sign_hash [--veritysig] [--key key] [--pass password]
>    hmac [--imahash | --imasig ] file
>   
>   
> @@ -43,6 +43,7 @@ OPTIONS
>   
>     -a, --hashalgo     sha1, sha224, sha256, sha384, sha512
>     -s, --imasig       make IMA signature
> +      --veritysig    sign an fs-verity file digest hash
>     -d, --imahash      make IMA hash
>     -f, --sigfile      store IMA signature in .sig file instead of xattr
>         --xattr-user   store xattrs in user namespace (for testing purposes)
> diff --git a/src/evmctl.c b/src/evmctl.c
> index ca9449498321..9152b0a5c7c2 100644
> --- a/src/evmctl.c
> +++ b/src/evmctl.c
> @@ -135,6 +135,7 @@ static int msize;
>   static dev_t fs_dev;
>   static bool evm_immutable;
>   static bool evm_portable;
> +static bool veritysig;
>   
>   #define HMAC_FLAG_NO_UUID	0x0001
>   #define HMAC_FLAG_CAPS_SET	0x0002
> @@ -726,34 +727,102 @@ static int cmd_sign_ima(struct command *cmd)
>   	return do_cmd(cmd, sign_ima_file);
>   }
>   
> +/*
> + * Sign file hash(es) provided in the format as produced by either
> + * sha*sum or "fsverity measure".
> + *
> + * sha*sum format: <hash> <pathname>
> + * fsverity measure format: <algo>:<hash> <pathname>
> + *
> + * To disambiguate the resulting file signatures, a new signature format
> + * version 3 (sigv3) was defined as the hash of the xattr type (enum
> + * evm_ima_xattr_type), the hash algorithm (enum hash_algo), and the hash.
> + *
> + * Either directly sign the sha*sum hash or indirectly sign the fsverity
> + * hash (sigv3).
> + *
> + * The output is the same format as the input with the resulting file
> + * signature appended.
> + */
>   static int cmd_sign_hash(struct command *cmd)
>   {
> +	unsigned char sigv3_hash[MAX_DIGEST_SIZE];
> +	unsigned char sig[MAX_SIGNATURE_SIZE];
> +	unsigned char hash[MAX_DIGEST_SIZE];
> +	int siglen, algolen, hashlen = 0;
> +	char *line = NULL, *token, *hashp;
> +	size_t line_len = 0;
>   	const char *key;
> -	char *token, *line = NULL;
> -	int hashlen = 0;
> -	size_t line_len;
> +	char algo[12];
>   	ssize_t len;
> -	unsigned char hash[MAX_DIGEST_SIZE];
> -	unsigned char sig[MAX_SIGNATURE_SIZE] = "\x03";
> -	int siglen;
> +	int ret;
>   
>   	errno = 0;
>   	key = imaevm_params.keyfile ? : "/etc/keys/privkey_evm.pem";
>   
> -	/* support reading hash (eg. output of shasum) */
>   	while ((len = getline(&line, &line_len, stdin)) > 0) {
>   		/* remove end of line */
>   		if (line[len - 1] == '\n')
>   			line[--len] = '\0';
>   
> -		/* find the end of the hash */
> -		token = strpbrk(line, ", \t");
> -		hashlen = token ? token - line : strlen(line);
> +		/*
> +		 * Before either directly or indirectly signing the hash,
> +		 * convert the hex-ascii hash representation to binary.
> +		 */
> +		if (veritysig) {
> +
> +			/* split the algorithm from the hash */
> +			hashp = strpbrk(line, ":");
> +			if (!hashp) {	/* pointer to the delimiter */
> +				log_err("Missing fsverity hash algorithm\n");
> +				continue;
> +			}
> +
> +			algolen = hashp - line;
> +			if (algolen > sizeof(algo))
> +				algolen = sizeof(algo);

I think a hash name exceeding the max buffer size and needing to be 
truncating it should be an error since the name to index conversion will 
fail  later on.


> +			if (algolen <= 0) {
> +				log_err("Missing fsverity hash algorithm\n");
> +				continue;
> +			}
> +
> +			strncpy(algo, line, algolen);
> +			algo[algolen] = 0;	/* Nul terminate algorithm */

per above: if algolen == sizeof(algo) then you have an out-of-bounds 
access here...
Mimi Zohar May 13, 2022, 12:12 p.m. UTC | #2
On Thu, 2022-05-12 at 17:48 -0400, Stefan Berger wrote:
> > -             /* find the end of the hash */
> > -             token = strpbrk(line, ", \t");
> > -             hashlen = token ? token - line : strlen(line);
> > +             /*
> > +              * Before either directly or indirectly signing the hash,
> > +              * convert the hex-ascii hash representation to binary.
> > +              */
> > +             if (veritysig) {
> > +
> > +                     /* split the algorithm from the hash */
> > +                     hashp = strpbrk(line, ":");
> > +                     if (!hashp) {   /* pointer to the delimiter */
> > +                             log_err("Missing fsverity hash algorithm\n");
> > +                             continue;
> > +                     }
> > +
> > +                     algolen = hashp - line;
> > +                     if (algolen > sizeof(algo))
> > +                             algolen = sizeof(algo);
> 
> I think a hash name exceeding the max buffer size and needing to be 
> truncating it should be an error since the name to index conversion will 
> fail  later on.

Sure.  In fact, the hash algorithm name size should be limited to the
maximum size of those algorithms that fs-verity actually supports.

thanks,

Mimi
Eric Biggers May 17, 2022, 11:13 p.m. UTC | #3
On Thu, May 12, 2022 at 02:30:55PM -0400, Mimi Zohar wrote:
> Sign fs-verity file digests provided in the format as produced by
> "fsverity measure".

This is correct, but it probably would be more useful to give "fsverity digest"
as the example, since "fsverity digest" doesn't depend on the file already
having verity enabled.

- Eric
diff mbox series

Patch

diff --git a/README b/README
index 5b5ecb52a74b..ffe46ad75728 100644
--- a/README
+++ b/README
@@ -34,7 +34,7 @@  COMMANDS
  ima_hash file
  ima_measurement [--ignore-violations] [--verify-sig [--key "key1, key2, ..."]]  [--pcrs [hash-algorithm,]file [--pcrs hash-algorithm,file] ...] file
  ima_fix [-t fdsxm] path
- sign_hash [--key key] [--pass password]
+ sign_hash [--veritysig] [--key key] [--pass password]
  hmac [--imahash | --imasig ] file
 
 
@@ -43,6 +43,7 @@  OPTIONS
 
   -a, --hashalgo     sha1, sha224, sha256, sha384, sha512
   -s, --imasig       make IMA signature
+      --veritysig    sign an fs-verity file digest hash
   -d, --imahash      make IMA hash
   -f, --sigfile      store IMA signature in .sig file instead of xattr
       --xattr-user   store xattrs in user namespace (for testing purposes)
diff --git a/src/evmctl.c b/src/evmctl.c
index ca9449498321..9152b0a5c7c2 100644
--- a/src/evmctl.c
+++ b/src/evmctl.c
@@ -135,6 +135,7 @@  static int msize;
 static dev_t fs_dev;
 static bool evm_immutable;
 static bool evm_portable;
+static bool veritysig;
 
 #define HMAC_FLAG_NO_UUID	0x0001
 #define HMAC_FLAG_CAPS_SET	0x0002
@@ -726,34 +727,102 @@  static int cmd_sign_ima(struct command *cmd)
 	return do_cmd(cmd, sign_ima_file);
 }
 
+/*
+ * Sign file hash(es) provided in the format as produced by either
+ * sha*sum or "fsverity measure".
+ *
+ * sha*sum format: <hash> <pathname>
+ * fsverity measure format: <algo>:<hash> <pathname>
+ *
+ * To disambiguate the resulting file signatures, a new signature format
+ * version 3 (sigv3) was defined as the hash of the xattr type (enum
+ * evm_ima_xattr_type), the hash algorithm (enum hash_algo), and the hash.
+ *
+ * Either directly sign the sha*sum hash or indirectly sign the fsverity
+ * hash (sigv3).
+ *
+ * The output is the same format as the input with the resulting file
+ * signature appended.
+ */
 static int cmd_sign_hash(struct command *cmd)
 {
+	unsigned char sigv3_hash[MAX_DIGEST_SIZE];
+	unsigned char sig[MAX_SIGNATURE_SIZE];
+	unsigned char hash[MAX_DIGEST_SIZE];
+	int siglen, algolen, hashlen = 0;
+	char *line = NULL, *token, *hashp;
+	size_t line_len = 0;
 	const char *key;
-	char *token, *line = NULL;
-	int hashlen = 0;
-	size_t line_len;
+	char algo[12];
 	ssize_t len;
-	unsigned char hash[MAX_DIGEST_SIZE];
-	unsigned char sig[MAX_SIGNATURE_SIZE] = "\x03";
-	int siglen;
+	int ret;
 
 	errno = 0;
 	key = imaevm_params.keyfile ? : "/etc/keys/privkey_evm.pem";
 
-	/* support reading hash (eg. output of shasum) */
 	while ((len = getline(&line, &line_len, stdin)) > 0) {
 		/* remove end of line */
 		if (line[len - 1] == '\n')
 			line[--len] = '\0';
 
-		/* find the end of the hash */
-		token = strpbrk(line, ", \t");
-		hashlen = token ? token - line : strlen(line);
+		/*
+		 * Before either directly or indirectly signing the hash,
+		 * convert the hex-ascii hash representation to binary.
+		 */
+		if (veritysig) {
+
+			/* split the algorithm from the hash */
+			hashp = strpbrk(line, ":");
+			if (!hashp) {	/* pointer to the delimiter */
+				log_err("Missing fsverity hash algorithm\n");
+				continue;
+			}
+
+			algolen = hashp - line;
+			if (algolen > sizeof(algo))
+				algolen = sizeof(algo);
+			if (algolen <= 0) {
+				log_err("Missing fsverity hash algorithm\n");
+				continue;
+			}
+
+			strncpy(algo, line, algolen);
+			algo[algolen] = 0;	/* Nul terminate algorithm */
+
+			hashp++;
+			token = strpbrk(line, ", \t");
+			hashlen = token - hashp;
+			if (hashlen <= 0) {
+				log_err("Missing fsverity hash\n");
+				continue;
+			}
+
+			assert(hashlen / 2 <= sizeof(hash));
+			hex2bin(hash, hashp, hashlen / 2);
+
+			ret = calc_hash_sigv3(IMA_VERITY_DIGSIG, algo, hash,
+					      sigv3_hash);
+			if (ret < 0 || ret == 1) {
+				log_info("Failure to calculate fs-verity hash\n");
+				continue;
+			}
+
+			siglen = sign_hash(algo, sigv3_hash, hashlen / 2,
+					   key, NULL, sig + 1);
+
+			sig[0] = IMA_VERITY_DIGSIG;
+			sig[1] = DIGSIG_VERSION_3;	/* sigv3 */
+		} else {
+			token = strpbrk(line, ", \t");
+			hashlen = token ? token - line : strlen(line);
+			assert(hashlen / 2 <= sizeof(hash));
+			hex2bin(hash, line, hashlen / 2);
+
+			siglen = sign_hash(imaevm_params.hash_algo, hash,
+					   hashlen / 2, key, NULL, sig + 1);
+			sig[0] = EVM_IMA_XATTR_DIGSIG;
+		}
 
-		assert(hashlen / 2 <= sizeof(hash));
-		hex2bin(hash, line, hashlen / 2);
-		siglen = sign_hash(imaevm_params.hash_algo, hash, hashlen / 2,
-				 key, NULL, sig + 1);
 		if (siglen <= 1)
 			return siglen;
 		assert(siglen < sizeof(sig));
@@ -2557,7 +2626,7 @@  struct command cmds[] = {
 	{"ima_boot_aggregate", cmd_ima_bootaggr, 0, "[--pcrs hash-algorithm,file] [TPM 1.2 BIOS event log]", "Calculate per TPM bank boot_aggregate digests\n"},
 	{"ima_fix", cmd_ima_fix, 0, "[-t fdsxm] path", "Recursively fix IMA/EVM xattrs in fix mode.\n"},
 	{"ima_clear", cmd_ima_clear, 0, "[-t fdsxm] path", "Recursively remove IMA/EVM xattrs.\n"},
-	{"sign_hash", cmd_sign_hash, 0, "[--key key] [--pass [password]", "Sign hashes from shaXsum output.\n"},
+	{"sign_hash", cmd_sign_hash, 0, "[--veritysig] [--key key] [--pass [password]", "Sign hashes from either shaXsum or \"fsverity measure\" output.\n"},
 #ifdef DEBUG
 	{"hmac", cmd_hmac_evm, 0, "[--imahash | --imasig ] file", "Sign file metadata with HMAC using symmetric key (for testing purpose).\n"},
 #endif
@@ -2597,6 +2666,7 @@  static struct option opts[] = {
 	{"verify-bank", 2, 0, 143},
 	{"keyid", 1, 0, 144},
 	{"keyid-from-cert", 1, 0, 145},
+	{"veritysig", 0, 0, 146},
 	{}
 
 };
@@ -2827,6 +2897,9 @@  int main(int argc, char *argv[])
 			}
 			imaevm_params.keyid = keyid;
 			break;
+		case 146:
+			veritysig = 1;
+			break;
 		case '?':
 			exit(1);
 			break;
diff --git a/src/imaevm.h b/src/imaevm.h
index 0d53a0232ae4..afcf1e042014 100644
--- a/src/imaevm.h
+++ b/src/imaevm.h
@@ -93,6 +93,7 @@  enum evm_ima_xattr_type {
 	EVM_IMA_XATTR_DIGSIG,
 	IMA_XATTR_DIGEST_NG,
 	EVM_XATTR_PORTABLE_DIGSIG,
+	IMA_VERITY_DIGSIG,
 };
 
 struct h_misc {
@@ -138,7 +139,8 @@  enum digest_algo {
 
 enum digsig_version {
 	DIGSIG_VERSION_1 = 1,
-	DIGSIG_VERSION_2
+	DIGSIG_VERSION_2,
+	DIGSIG_VERSION_3	/* hash of ima_file_id struct (portion used) */
 };
 
 struct pubkey_hdr {
@@ -233,5 +235,6 @@  int ima_verify_signature(const char *file, unsigned char *sig, int siglen, unsig
 void init_public_keys(const char *keyfiles);
 int imaevm_hash_algo_from_sig(unsigned char *sig);
 const char *imaevm_hash_algo_by_id(int algo);
+int calc_hash_sigv3(enum evm_ima_xattr_type type, const char *algo, const unsigned char *in_hash, unsigned char *out_hash);
 
 #endif
diff --git a/src/libimaevm.c b/src/libimaevm.c
index 388b726f1e3a..7c2ed5fb0556 100644
--- a/src/libimaevm.c
+++ b/src/libimaevm.c
@@ -495,6 +495,91 @@  err:
 	return ret;
 }
 
+#define HASH_MAX_DIGESTSIZE 512
+
+struct ima_file_id {
+	__u8 hash_type;		/* xattr type [enum evm_ima_xattr_type] */
+	__u8 hash_algorithm;	/* Digest algorithm [enum hash_algo] */
+	__u8 hash[HASH_MAX_DIGESTSIZE];
+} __packed;
+
+/*
+ * Calculate the signature format version 3 hash based on the portion
+ * of the ima_file_id structure used, not the entire structure.
+ *
+ * For openssl errors return 1, other errors return -EINVAL.
+ */
+int calc_hash_sigv3(enum evm_ima_xattr_type type, const char *algo,
+		    const unsigned char *in_hash, unsigned char *out_hash)
+{
+	struct ima_file_id file_id = { .hash_type = IMA_VERITY_DIGSIG };
+	uint8_t *data = (uint8_t *) &file_id;
+
+	const EVP_MD *md;
+	EVP_MD_CTX *pctx;
+	unsigned int mdlen;
+	int err;
+#if OPENSSL_VERSION_NUMBER < 0x10100000
+	EVP_MD_CTX ctx;
+	pctx = &ctx;
+#else
+	pctx = EVP_MD_CTX_new();
+#endif
+	int hash_algo;
+	int hash_size;
+	unsigned int unused;
+
+	if (type != IMA_VERITY_DIGSIG) {
+		log_err("Only fsverity supports signature format v3 (sigv3)\n");
+		return -EINVAL;
+	}
+
+	if ((hash_algo = imaevm_get_hash_algo(algo)) < 0) {
+		log_err("Hash algorithm %s not supported\n", algo);
+		return -EINVAL;
+	}
+	file_id.hash_algorithm = hash_algo;
+
+	md = EVP_get_digestbyname(algo);
+	if (!md) {
+		log_err("EVP_get_digestbyname(%s) failed\n", algo);
+		err = 1;
+		goto err;
+	}
+
+	hash_size = EVP_MD_size(md);
+	memcpy(file_id.hash, in_hash, hash_size);
+
+	err = EVP_DigestInit(pctx, md);
+	if (!err) {
+		log_err("EVP_DigestInit() failed\n");
+		err = 1;
+		goto err;
+	}
+
+	unused = HASH_MAX_DIGESTSIZE - hash_size;
+	if (!EVP_DigestUpdate(pctx, data, sizeof(file_id) - unused)) {
+		log_err("EVP_DigestUpdate() failed\n");
+		err = 1;
+		goto err;
+	}
+
+	err = EVP_DigestFinal(pctx, out_hash, &mdlen);
+	if (!err) {
+		log_err("EVP_DigestFinal() failed\n");
+		err = 1;
+		goto err;
+	}
+	err = mdlen;
+err:
+	if (err == 1)
+		output_openssl_errors();
+#if OPENSSL_VERSION_NUMBER >= 0x10100000
+	EVP_MD_CTX_free(pctx);
+#endif
+	return err;
+}
+
 int imaevm_get_hash_algo(const char *algo)
 {
 	int i;