1
0
mirror of https://git.code.sf.net/p/linux-ima/ima-evm-utils synced 2025-04-28 06:33:36 +02:00

Sign an fs-verity file digest

Sign fs-verity file digests provided in the format as produced by
"fsverity digest".  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 digest 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 digest <pathname> | evmctl sign_hash --veritysig \
 --key <pem encoded private key>

Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
This commit is contained in:
Mimi Zohar 2021-11-24 08:35:20 -05:00
parent acb19d1894
commit fc46af121e
4 changed files with 186 additions and 18 deletions

3
README
View File

@ -34,7 +34,7 @@ COMMANDS
ima_hash file ima_hash file
ima_measurement [--ignore-violations] [--verify-sig [--key "key1, key2, ..."]] [--pcrs [hash-algorithm,]file [--pcrs hash-algorithm,file] ...] file ima_measurement [--ignore-violations] [--verify-sig [--key "key1, key2, ..."]] [--pcrs [hash-algorithm,]file [--pcrs hash-algorithm,file] ...] file
ima_fix [-t fdsxm] path ima_fix [-t fdsxm] path
sign_hash [--key key] [--pass password] sign_hash [--veritysig] [--key key] [--pass password]
hmac [--imahash | --imasig ] file hmac [--imahash | --imasig ] file
@ -43,6 +43,7 @@ OPTIONS
-a, --hashalgo sha1, sha224, sha256, sha384, sha512 -a, --hashalgo sha1, sha224, sha256, sha384, sha512
-s, --imasig make IMA signature -s, --imasig make IMA signature
--veritysig sign an fs-verity file digest hash
-d, --imahash make IMA hash -d, --imahash make IMA hash
-f, --sigfile store IMA signature in .sig file instead of xattr -f, --sigfile store IMA signature in .sig file instead of xattr
--xattr-user store xattrs in user namespace (for testing purposes) --xattr-user store xattrs in user namespace (for testing purposes)

View File

@ -135,6 +135,7 @@ static int msize;
static dev_t fs_dev; static dev_t fs_dev;
static bool evm_immutable; static bool evm_immutable;
static bool evm_portable; static bool evm_portable;
static bool veritysig;
#define HMAC_FLAG_NO_UUID 0x0001 #define HMAC_FLAG_NO_UUID 0x0001
#define HMAC_FLAG_CAPS_SET 0x0002 #define HMAC_FLAG_CAPS_SET 0x0002
@ -731,33 +732,106 @@ static int cmd_sign_ima(struct command *cmd)
return do_cmd(cmd, sign_ima_file); return do_cmd(cmd, sign_ima_file);
} }
/*
* Sign file hash(es) provided in the format as produced by either
* sha*sum or "fsverity digest".
*
* sha*sum format: <hash> <pathname>
* fsverity digest 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) static int cmd_sign_hash(struct command *cmd)
{ {
const char *key; unsigned char sigv3_hash[MAX_DIGEST_SIZE];
char *token, *line = NULL; unsigned char sig[MAX_SIGNATURE_SIZE];
int hashlen = 0;
size_t line_len;
ssize_t len;
unsigned char hash[MAX_DIGEST_SIZE]; unsigned char hash[MAX_DIGEST_SIZE];
unsigned char sig[MAX_SIGNATURE_SIZE] = "\x03"; int siglen, algolen = 0, hashlen = 0;
int siglen; char *line = NULL, *token, *hashp;
size_t line_len = 0;
const char *key;
char algo[7]; /* Current maximum fsverity hash algo name length */
ssize_t len;
int ret;
key = imaevm_params.keyfile ? : "/etc/keys/privkey_evm.pem"; key = imaevm_params.keyfile ? : "/etc/keys/privkey_evm.pem";
/* support reading hash (eg. output of shasum) */
while ((len = getline(&line, &line_len, stdin)) > 0) { while ((len = getline(&line, &line_len, stdin)) > 0) {
/* remove end of line */ /* remove end of line */
if (line[len - 1] == '\n') if (line[len - 1] == '\n')
line[--len] = '\0'; line[--len] = '\0';
/* find the end of the hash */ /*
token = strpbrk(line, ", \t"); * Before either directly or indirectly signing the hash,
hashlen = token ? token - line : strlen(line); * convert the hex-ascii hash representation to binary.
*/
if (veritysig) {
/*
* Split the hash algorithm from the hash
* example format: sha256:51dda1..d7c6 <file pathname>
*/
hashp = strpbrk(line, ":");
if (hashp) /* pointer to the delimiter */
algolen = hashp - line;
if (!hashp || algolen <= 0 ||
algolen >= sizeof(algo)) {
log_err("Missing/invalid fsverity hash algorithm\n");
continue;
}
strncpy(algo, line, algolen);
algo[algolen] = '\0'; /* Nul terminate algorithm */
hashp++;
token = strpbrk(line, " ");
if (!token) {
log_err("Missing fsverity hash\n");
continue;
}
hashlen = token - hashp;
if (hashlen <= 0) {
log_err("Missing fsverity hash\n");
continue;
}
assert(hashlen / 2 <= sizeof(hash)); assert(hashlen / 2 <= sizeof(hash));
hex2bin(hash, line, hashlen / 2); hex2bin(hash, hashp, hashlen / 2);
siglen = sign_hash(imaevm_params.hash_algo, hash, 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); key, NULL, sig + 1);
sig[0] = IMA_VERITY_DIGSIG;
sig[1] = DIGSIG_VERSION_3; /* sigv3 */
} else {
/* Parse the shaXsum output */
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;
}
if (siglen <= 1) if (siglen <= 1)
return siglen; return siglen;
assert(siglen < sizeof(sig)); assert(siglen < sizeof(sig));
@ -2568,7 +2642,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_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_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"}, {"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 digest\" output.\n"},
#ifdef DEBUG #ifdef DEBUG
{"hmac", cmd_hmac_evm, 0, "[--imahash | --imasig ] file", "Sign file metadata with HMAC using symmetric key (for testing purpose).\n"}, {"hmac", cmd_hmac_evm, 0, "[--imahash | --imasig ] file", "Sign file metadata with HMAC using symmetric key (for testing purpose).\n"},
#endif #endif
@ -2608,6 +2682,7 @@ static struct option opts[] = {
{"verify-bank", 2, 0, 143}, {"verify-bank", 2, 0, 143},
{"keyid", 1, 0, 144}, {"keyid", 1, 0, 144},
{"keyid-from-cert", 1, 0, 145}, {"keyid-from-cert", 1, 0, 145},
{"veritysig", 0, 0, 146},
{} {}
}; };
@ -2840,6 +2915,9 @@ int main(int argc, char *argv[])
} }
imaevm_params.keyid = keyid; imaevm_params.keyid = keyid;
break; break;
case 146:
veritysig = 1;
break;
case '?': case '?':
exit(1); exit(1);
break; break;

View File

@ -93,6 +93,7 @@ enum evm_ima_xattr_type {
EVM_IMA_XATTR_DIGSIG, EVM_IMA_XATTR_DIGSIG,
IMA_XATTR_DIGEST_NG, IMA_XATTR_DIGEST_NG,
EVM_XATTR_PORTABLE_DIGSIG, EVM_XATTR_PORTABLE_DIGSIG,
IMA_VERITY_DIGSIG,
}; };
struct h_misc { struct h_misc {
@ -138,7 +139,8 @@ enum digest_algo {
enum digsig_version { enum digsig_version {
DIGSIG_VERSION_1 = 1, DIGSIG_VERSION_1 = 1,
DIGSIG_VERSION_2 DIGSIG_VERSION_2,
DIGSIG_VERSION_3 /* hash of ima_file_id struct (portion used) */
}; };
struct pubkey_hdr { 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); void init_public_keys(const char *keyfiles);
int imaevm_hash_algo_from_sig(unsigned char *sig); int imaevm_hash_algo_from_sig(unsigned char *sig);
const char *imaevm_hash_algo_by_id(int algo); 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 #endif

View File

@ -497,6 +497,92 @@ err:
return ret; return ret;
} }
#define HASH_MAX_DIGESTSIZE 64 /* kernel HASH_MAX_DIGESTSIZE is 64 bytes */
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.
*
* On success, return the hash length, otherwise 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 imaevm_get_hash_algo(const char *algo)
{ {
int i; int i;