diff --git a/README b/README index 5b5ecb5..ffe46ad 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 72ce7f7..bfc8cb4 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 @@ -731,33 +732,106 @@ 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 digest". + * + * sha*sum format: + * fsverity digest format: : + * + * 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) { - const char *key; - char *token, *line = NULL; - int hashlen = 0; - size_t line_len; - ssize_t len; + unsigned char sigv3_hash[MAX_DIGEST_SIZE]; + unsigned char sig[MAX_SIGNATURE_SIZE]; unsigned char hash[MAX_DIGEST_SIZE]; - unsigned char sig[MAX_SIGNATURE_SIZE] = "\x03"; - int siglen; + int siglen, algolen = 0, hashlen = 0; + 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"; - /* 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 hash algorithm from the hash + * example format: sha256:51dda1..d7c6 + */ + 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)); + 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 { + /* 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; + } - 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)); @@ -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_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 digest\" 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 @@ -2608,6 +2682,7 @@ static struct option opts[] = { {"verify-bank", 2, 0, 143}, {"keyid", 1, 0, 144}, {"keyid-from-cert", 1, 0, 145}, + {"veritysig", 0, 0, 146}, {} }; @@ -2840,6 +2915,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 0d53a02..afcf1e0 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 a4f2ec4..8923506 100644 --- a/src/libimaevm.c +++ b/src/libimaevm.c @@ -497,6 +497,92 @@ err: 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 i;