diff mbox series

[v3,ima-evm-utils,3/3] Verify an fs-verity file digest based signature

Message ID 20220520161103.373554-4-zohar@linux.ibm.com (mailing list archive)
State New, archived
Headers show
Series fs-verity file signature support | expand

Commit Message

Mimi Zohar May 20, 2022, 4:11 p.m. UTC
ima-evm-utils does not attempt to calculate or even read the fs-verity
file hash, but can verify the fs-verity signature based on the fsverity
file hash, both contained in the measurement list record.

Example:
evmctl ima_measurement --key <DER encoded public key> \
 --verify-sig /sys/kernel/security/ima/binary_runtime_measurements

Modify 'sig' argument of verify_hash() to be the full xattr in order to
differentiate signatures types.

Note:
Kernel commit b1aaab22e263 ("ima: pass full xattr with the signature")
added the 'type' to signature_v2_hdr struct, which hasn't been reflected
here. (todo)

Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
---
 src/evmctl.c    | 11 +++++--
 src/libimaevm.c | 77 ++++++++++++++++++++++++++++++++++++++++++-------
 2 files changed, 75 insertions(+), 13 deletions(-)

Comments

Stefan Berger May 23, 2022, 12:39 a.m. UTC | #1
On 5/20/22 12:11, Mimi Zohar wrote:
> ima-evm-utils does not attempt to calculate or even read the fs-verity
> file hash, but can verify the fs-verity signature based on the fsverity
> file hash, both contained in the measurement list record.
> 
> Example:
> evmctl ima_measurement --key <DER encoded public key> \
>   --verify-sig /sys/kernel/security/ima/binary_runtime_measurements
> 
> Modify 'sig' argument of verify_hash() to be the full xattr in order to
> differentiate signatures types.
> 
> Note:
> Kernel commit b1aaab22e263 ("ima: pass full xattr with the signature")
> added the 'type' to signature_v2_hdr struct, which hasn't been reflected
> here. (todo)
> 
> Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>

Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>

> ---
>   src/evmctl.c    | 11 +++++--
>   src/libimaevm.c | 77 ++++++++++++++++++++++++++++++++++++++++++-------
>   2 files changed, 75 insertions(+), 13 deletions(-)
> 
> diff --git a/src/evmctl.c b/src/evmctl.c
> index c7c8c8cf6e89..6af72255bf72 100644
> --- a/src/evmctl.c
> +++ b/src/evmctl.c
> @@ -909,7 +909,7 @@ static int verify_evm(const char *file)
>   		return mdlen;
>   	assert(mdlen <= sizeof(hash));
>   
> -	return verify_hash(file, hash, mdlen, sig + 1, len - 1);
> +	return verify_hash(file, hash, mdlen, sig, len);
>   }
>   
>   static int cmd_verify_evm(struct command *cmd)
> @@ -1574,7 +1574,8 @@ void ima_ng_show(struct template_entry *entry)
>   	fieldp += field_len;
>   	total_len -= field_len;
>   
> -	if (!strcmp(entry->name, "ima-sig")) {
> +	if (!strcmp(entry->name, "ima-sig") ||
> +	    !strcmp(entry->name, "ima-sigv2")) {
>   		/* get signature */
>   		field_len = *(uint32_t *)fieldp;
>   		fieldp += sizeof(field_len);
> @@ -1620,11 +1621,17 @@ void ima_ng_show(struct template_entry *entry)
>   			log_info(" ");
>   			log_dump(sig, sig_len);
>   		}
> +
> +		/*
> +		 * Either verify the signature against the hash contained in
> +		 * the measurement list or calculate the hash.
> +		 */
>   		if (verify_list_sig)
>   			err = ima_verify_signature(path, sig, sig_len,
>   						   digest, digest_len);
>   		else
>   			err = ima_verify_signature(path, sig, sig_len, NULL, 0);
> +
>   		if (!err && imaevm_params.verbose > LOG_INFO)
>   			log_info("%s: verification is OK\n", path);
>   	} else {
> diff --git a/src/libimaevm.c b/src/libimaevm.c
> index af145bd62ac9..739ace5459e5 100644
> --- a/src/libimaevm.c
> +++ b/src/libimaevm.c
> @@ -424,10 +424,21 @@ void init_public_keys(const char *keyfiles)
>   }
>   
>   /*
> + * Verify a signature, prefixed with the signature_v2_hdr, either based
> + * directly or indirectly on the file data hash.
> + *
> + * version 2: directly based on the file data hash (e.g. sha*sum)
> + * version 3: indirectly based on the hash of the struct ima_file_id, which
> + *	      contains the xattr type (enum evm_ima_xattr_type), the hash
> + *	      algorithm (enum hash_algo), and the file data hash
> + *	      (e.g. fsverity digest).
> + *
>    * Return: 0 verification good, 1 verification bad, -1 error.
> + *
> + * (Note: signature_v2_hdr struct does not contain the 'type'.)
>    */
> -static int verify_hash_v2(const char *file, const unsigned char *hash, int size,
> -			  unsigned char *sig, int siglen)
> +static int verify_hash_common(const char *file, const unsigned char *hash,
> +			      int size, unsigned char *sig, int siglen)
>   {
>   	int ret = -1;
>   	EVP_PKEY *pkey, *pkey_free = NULL;
> @@ -497,6 +508,39 @@ err:
>   	return ret;
>   }
>   
> +/*
> + * Verify a signature, prefixed with the signature_v2_hdr, directly based
> + * on the file data hash.
> + *
> + * Return: 0 verification good, 1 verification bad, -1 error.
> + */
> +static int verify_hash_v2(const char *file, const unsigned char *hash,
> +			  int size, unsigned char *sig, int siglen)
> +{
> +	/* note: signature_v2_hdr does not contain 'type', use sig + 1 */
> +	return verify_hash_common(file, hash, size, sig + 1, siglen - 1);
> +}
> +
> +/*
> + * Verify a signature, prefixed with the signature_v2_hdr, indirectly based
> + * on the file data hash.
> + *
> + * Return: 0 verification good, 1 verification bad, -1 error.
> + */
> +static int verify_hash_v3(const char *file, const unsigned char *hash,
> +			  int size, unsigned char *sig, int siglen)
> +{
> +	unsigned char sigv3_hash[MAX_DIGEST_SIZE];
> +	int ret;
> +
> +	ret = calc_hash_sigv3(sig[0], NULL, hash, sigv3_hash);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* note: signature_v2_hdr does not contain 'type', use sig + 1 */
> +	return verify_hash_common(file, sigv3_hash, size, sig + 1, siglen - 1);
> +}
> +
>   #define HASH_MAX_DIGESTSIZE 512
>   
>   struct ima_file_id {
> @@ -536,6 +580,9 @@ int calc_hash_sigv3(enum evm_ima_xattr_type type, const char *algo,
>   		return -EINVAL;
>   	}
>   
> +	if (!algo)
> +		algo = imaevm_params.hash_algo;
> +
>   	if ((hash_algo = imaevm_get_hash_algo(algo)) < 0) {
>   		log_err("Hash algorithm %s not supported\n", algo);
>   		return -EINVAL;
> @@ -624,7 +671,7 @@ int imaevm_hash_algo_from_sig(unsigned char *sig)
>   		default:
>   			return -1;
>   		}
> -	} else if (sig[0] == DIGSIG_VERSION_2) {
> +	} else if (sig[0] == DIGSIG_VERSION_2 || sig[0] == DIGSIG_VERSION_3) {
>   		hashalgo = ((struct signature_v2_hdr *)sig)->hash_algo;
>   		if (hashalgo >= PKEY_HASH__LAST)
>   			return -1;
> @@ -633,11 +680,11 @@ int imaevm_hash_algo_from_sig(unsigned char *sig)
>   		return -1;
>   }
>   
> -int verify_hash(const char *file, const unsigned char *hash, int size, unsigned char *sig,
> -		int siglen)
> +int verify_hash(const char *file, const unsigned char *hash, int size,
> +		unsigned char *sig, int siglen)
>   {
>   	/* Get signature type from sig header */
> -	if (sig[0] == DIGSIG_VERSION_1) {
> +	if (sig[1] == DIGSIG_VERSION_1) {
>   		const char *key = NULL;
>   
>   		/* Read pubkey from RSA key */
> @@ -645,9 +692,12 @@ int verify_hash(const char *file, const unsigned char *hash, int size, unsigned
>   			key = "/etc/keys/pubkey_evm.pem";
>   		else
>   			key = imaevm_params.keyfile;
> -		return verify_hash_v1(file, hash, size, sig, siglen, key);
> -	} else if (sig[0] == DIGSIG_VERSION_2) {
> +		return verify_hash_v1(file, hash, size, sig + 1, siglen - 1,
> +					 key);
> +	} else if (sig[1] == DIGSIG_VERSION_2) {
>   		return verify_hash_v2(file, hash, size, sig, siglen);
> +	} else if (sig[1] == DIGSIG_VERSION_3) {
> +		return verify_hash_v3(file, hash, size, sig, siglen);
>   	} else
>   		return -1;
>   }
> @@ -658,11 +708,16 @@ int ima_verify_signature(const char *file, unsigned char *sig, int siglen,
>   	unsigned char hash[MAX_DIGEST_SIZE];
>   	int hashlen, sig_hash_algo;
>   
> -	if (sig[0] != EVM_IMA_XATTR_DIGSIG) {
> +	if (sig[0] != EVM_IMA_XATTR_DIGSIG && sig[0] != IMA_VERITY_DIGSIG) {
>   		log_err("%s: xattr ima has no signature\n", file);
>   		return -1;
>   	}
>   
> +	if (!digest && sig[0] == IMA_VERITY_DIGSIG) {
> +		log_err("%s: calculating the fs-verity digest is not supported\n", file);
> +		return -1;
> +	}
> +
>   	sig_hash_algo = imaevm_hash_algo_from_sig(sig + 1);
>   	if (sig_hash_algo < 0) {
>   		log_err("%s: Invalid signature\n", file);
> @@ -676,14 +731,14 @@ int ima_verify_signature(const char *file, unsigned char *sig, int siglen,
>   	 * measurement list, not by calculating the local file digest.
>   	 */
>   	if (digestlen > 0)
> -	    return verify_hash(file, digest, digestlen, sig + 1, siglen - 1);
> +		return verify_hash(file, digest, digestlen, sig, siglen);
>   
>   	hashlen = ima_calc_hash(file, hash);
>   	if (hashlen <= 1)
>   		return hashlen;
>   	assert(hashlen <= sizeof(hash));
>   
> -	return verify_hash(file, hash, hashlen, sig + 1, siglen - 1);
> +	return verify_hash(file, hash, hashlen, sig, siglen);
>   }
>   
>   /*
diff mbox series

Patch

diff --git a/src/evmctl.c b/src/evmctl.c
index c7c8c8cf6e89..6af72255bf72 100644
--- a/src/evmctl.c
+++ b/src/evmctl.c
@@ -909,7 +909,7 @@  static int verify_evm(const char *file)
 		return mdlen;
 	assert(mdlen <= sizeof(hash));
 
-	return verify_hash(file, hash, mdlen, sig + 1, len - 1);
+	return verify_hash(file, hash, mdlen, sig, len);
 }
 
 static int cmd_verify_evm(struct command *cmd)
@@ -1574,7 +1574,8 @@  void ima_ng_show(struct template_entry *entry)
 	fieldp += field_len;
 	total_len -= field_len;
 
-	if (!strcmp(entry->name, "ima-sig")) {
+	if (!strcmp(entry->name, "ima-sig") ||
+	    !strcmp(entry->name, "ima-sigv2")) {
 		/* get signature */
 		field_len = *(uint32_t *)fieldp;
 		fieldp += sizeof(field_len);
@@ -1620,11 +1621,17 @@  void ima_ng_show(struct template_entry *entry)
 			log_info(" ");
 			log_dump(sig, sig_len);
 		}
+
+		/*
+		 * Either verify the signature against the hash contained in
+		 * the measurement list or calculate the hash.
+		 */
 		if (verify_list_sig)
 			err = ima_verify_signature(path, sig, sig_len,
 						   digest, digest_len);
 		else
 			err = ima_verify_signature(path, sig, sig_len, NULL, 0);
+
 		if (!err && imaevm_params.verbose > LOG_INFO)
 			log_info("%s: verification is OK\n", path);
 	} else {
diff --git a/src/libimaevm.c b/src/libimaevm.c
index af145bd62ac9..739ace5459e5 100644
--- a/src/libimaevm.c
+++ b/src/libimaevm.c
@@ -424,10 +424,21 @@  void init_public_keys(const char *keyfiles)
 }
 
 /*
+ * Verify a signature, prefixed with the signature_v2_hdr, either based
+ * directly or indirectly on the file data hash.
+ *
+ * version 2: directly based on the file data hash (e.g. sha*sum)
+ * version 3: indirectly based on the hash of the struct ima_file_id, which
+ *	      contains the xattr type (enum evm_ima_xattr_type), the hash
+ *	      algorithm (enum hash_algo), and the file data hash
+ *	      (e.g. fsverity digest).
+ *
  * Return: 0 verification good, 1 verification bad, -1 error.
+ *
+ * (Note: signature_v2_hdr struct does not contain the 'type'.)
  */
-static int verify_hash_v2(const char *file, const unsigned char *hash, int size,
-			  unsigned char *sig, int siglen)
+static int verify_hash_common(const char *file, const unsigned char *hash,
+			      int size, unsigned char *sig, int siglen)
 {
 	int ret = -1;
 	EVP_PKEY *pkey, *pkey_free = NULL;
@@ -497,6 +508,39 @@  err:
 	return ret;
 }
 
+/*
+ * Verify a signature, prefixed with the signature_v2_hdr, directly based
+ * on the file data hash.
+ *
+ * Return: 0 verification good, 1 verification bad, -1 error.
+ */
+static int verify_hash_v2(const char *file, const unsigned char *hash,
+			  int size, unsigned char *sig, int siglen)
+{
+	/* note: signature_v2_hdr does not contain 'type', use sig + 1 */
+	return verify_hash_common(file, hash, size, sig + 1, siglen - 1);
+}
+
+/*
+ * Verify a signature, prefixed with the signature_v2_hdr, indirectly based
+ * on the file data hash.
+ *
+ * Return: 0 verification good, 1 verification bad, -1 error.
+ */
+static int verify_hash_v3(const char *file, const unsigned char *hash,
+			  int size, unsigned char *sig, int siglen)
+{
+	unsigned char sigv3_hash[MAX_DIGEST_SIZE];
+	int ret;
+
+	ret = calc_hash_sigv3(sig[0], NULL, hash, sigv3_hash);
+	if (ret < 0)
+		return ret;
+
+	/* note: signature_v2_hdr does not contain 'type', use sig + 1 */
+	return verify_hash_common(file, sigv3_hash, size, sig + 1, siglen - 1);
+}
+
 #define HASH_MAX_DIGESTSIZE 512
 
 struct ima_file_id {
@@ -536,6 +580,9 @@  int calc_hash_sigv3(enum evm_ima_xattr_type type, const char *algo,
 		return -EINVAL;
 	}
 
+	if (!algo)
+		algo = imaevm_params.hash_algo;
+
 	if ((hash_algo = imaevm_get_hash_algo(algo)) < 0) {
 		log_err("Hash algorithm %s not supported\n", algo);
 		return -EINVAL;
@@ -624,7 +671,7 @@  int imaevm_hash_algo_from_sig(unsigned char *sig)
 		default:
 			return -1;
 		}
-	} else if (sig[0] == DIGSIG_VERSION_2) {
+	} else if (sig[0] == DIGSIG_VERSION_2 || sig[0] == DIGSIG_VERSION_3) {
 		hashalgo = ((struct signature_v2_hdr *)sig)->hash_algo;
 		if (hashalgo >= PKEY_HASH__LAST)
 			return -1;
@@ -633,11 +680,11 @@  int imaevm_hash_algo_from_sig(unsigned char *sig)
 		return -1;
 }
 
-int verify_hash(const char *file, const unsigned char *hash, int size, unsigned char *sig,
-		int siglen)
+int verify_hash(const char *file, const unsigned char *hash, int size,
+		unsigned char *sig, int siglen)
 {
 	/* Get signature type from sig header */
-	if (sig[0] == DIGSIG_VERSION_1) {
+	if (sig[1] == DIGSIG_VERSION_1) {
 		const char *key = NULL;
 
 		/* Read pubkey from RSA key */
@@ -645,9 +692,12 @@  int verify_hash(const char *file, const unsigned char *hash, int size, unsigned
 			key = "/etc/keys/pubkey_evm.pem";
 		else
 			key = imaevm_params.keyfile;
-		return verify_hash_v1(file, hash, size, sig, siglen, key);
-	} else if (sig[0] == DIGSIG_VERSION_2) {
+		return verify_hash_v1(file, hash, size, sig + 1, siglen - 1,
+					 key);
+	} else if (sig[1] == DIGSIG_VERSION_2) {
 		return verify_hash_v2(file, hash, size, sig, siglen);
+	} else if (sig[1] == DIGSIG_VERSION_3) {
+		return verify_hash_v3(file, hash, size, sig, siglen);
 	} else
 		return -1;
 }
@@ -658,11 +708,16 @@  int ima_verify_signature(const char *file, unsigned char *sig, int siglen,
 	unsigned char hash[MAX_DIGEST_SIZE];
 	int hashlen, sig_hash_algo;
 
-	if (sig[0] != EVM_IMA_XATTR_DIGSIG) {
+	if (sig[0] != EVM_IMA_XATTR_DIGSIG && sig[0] != IMA_VERITY_DIGSIG) {
 		log_err("%s: xattr ima has no signature\n", file);
 		return -1;
 	}
 
+	if (!digest && sig[0] == IMA_VERITY_DIGSIG) {
+		log_err("%s: calculating the fs-verity digest is not supported\n", file);
+		return -1;
+	}
+
 	sig_hash_algo = imaevm_hash_algo_from_sig(sig + 1);
 	if (sig_hash_algo < 0) {
 		log_err("%s: Invalid signature\n", file);
@@ -676,14 +731,14 @@  int ima_verify_signature(const char *file, unsigned char *sig, int siglen,
 	 * measurement list, not by calculating the local file digest.
 	 */
 	if (digestlen > 0)
-	    return verify_hash(file, digest, digestlen, sig + 1, siglen - 1);
+		return verify_hash(file, digest, digestlen, sig, siglen);
 
 	hashlen = ima_calc_hash(file, hash);
 	if (hashlen <= 1)
 		return hashlen;
 	assert(hashlen <= sizeof(hash));
 
-	return verify_hash(file, hash, hashlen, sig + 1, siglen - 1);
+	return verify_hash(file, hash, hashlen, sig, siglen);
 }
 
 /*