#include <linux/err.h>
#include <linux/ratelimit.h>
#include <linux/key-type.h>
#include <crypto/public_key.h>
#include <crypto/hash_info.h>
#include <keys/asymmetric-type.h>
#include <keys/system_keyring.h>
#include "integrity.h"
static struct key *request_asymmetric_key(struct key *keyring, uint32_t keyid)
{
struct key *key;
char name[12];
sprintf(name, "id:%08x", keyid);
pr_debug("key search: \"%s\"\n", name);
key = get_ima_blacklist_keyring();
if (key) {
key_ref_t kref;
kref = keyring_search(make_key_ref(key, 1),
&key_type_asymmetric, name, true);
if (!IS_ERR(kref)) {
pr_err("Key '%s' is in ima_blacklist_keyring\n", name);
return ERR_PTR(-EKEYREJECTED);
}
}
if (keyring) {
key_ref_t kref;
kref = keyring_search(make_key_ref(keyring, 1),
&key_type_asymmetric, name, true);
if (IS_ERR(kref))
key = ERR_CAST(kref);
else
key = key_ref_to_ptr(kref);
} else {
key = request_key(&key_type_asymmetric, name, NULL);
}
if (IS_ERR(key)) {
if (keyring)
pr_err_ratelimited("Request for unknown key '%s' in '%s' keyring. err %ld\n",
name, keyring->description,
PTR_ERR(key));
else
pr_err_ratelimited("Request for unknown key '%s' err %ld\n",
name, PTR_ERR(key));
switch (PTR_ERR(key)) {
case -EACCES:
case -ENOTDIR:
case -EAGAIN:
return ERR_PTR(-ENOKEY);
default:
return key;
}
}
pr_debug("%s() = 0 [%x]\n", __func__, key_serial(key));
return key;
}
static int asymmetric_verify_common(const struct key *key,
const struct public_key *pk,
const char *sig, int siglen,
const char *data, int datalen)
{
struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig;
struct public_key_signature pks;
int ret;
siglen -= sizeof(*hdr);
if (siglen != be16_to_cpu(hdr->sig_size))
return -EBADMSG;
if (hdr->hash_algo >= HASH_ALGO__LAST)
return -ENOPKG;
memset(&pks, 0, sizeof(pks));
pks.hash_algo = hash_algo_name[hdr->hash_algo];
pks.pkey_algo = pk->pkey_algo;
if (!strcmp(pk->pkey_algo, "rsa")) {
pks.encoding = "pkcs1";
} else if (!strncmp(pk->pkey_algo, "ecdsa-", 6)) {
pks.encoding = "x962";
} else if (!strcmp(pk->pkey_algo, "ecrdsa")) {
pks.encoding = "raw";
} else {
ret = -ENOPKG;
goto out;
}
pks.m = (u8 *)data;
pks.m_size = datalen;
pks.s = hdr->sig;
pks.s_size = siglen;
ret = verify_signature(key, &pks);
out:
pr_debug("%s() = %d\n", __func__, ret);
return ret;
}
int asymmetric_verify(struct key *keyring, const char *sig,
int siglen, const char *data, int datalen)
{
struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig;
const struct public_key *pk;
struct key *key;
int ret;
if (siglen <= sizeof(*hdr))
return -EBADMSG;
key = request_asymmetric_key(keyring, be32_to_cpu(hdr->keyid));
if (IS_ERR(key))
return PTR_ERR(key);
pk = asymmetric_key_public_key(key);
if (!pk) {
ret = -ENOKEY;
goto out;
}
ret = asymmetric_verify_common(key, pk, sig, siglen, data, datalen);
out:
key_put(key);
return ret;
}
static int calc_file_id_hash(enum evm_ima_xattr_type type,
enum hash_algo algo, const u8 *digest,
struct ima_max_digest_data *hash)
{
struct ima_file_id file_id = {.hash_type = type, .hash_algorithm = algo};
size_t digest_size = hash_digest_size[algo];
struct crypto_shash *tfm;
size_t file_id_size;
int rc;
if (type != IMA_VERITY_DIGSIG && type != EVM_IMA_XATTR_DIGSIG &&
type != EVM_XATTR_PORTABLE_DIGSIG)
return -EINVAL;
tfm = crypto_alloc_shash(hash_algo_name[algo], 0, 0);
if (IS_ERR(tfm))
return PTR_ERR(tfm);
memcpy(file_id.hash, digest, digest_size);
file_id_size = sizeof(file_id) - (HASH_MAX_DIGESTSIZE - digest_size);
hash->hdr.algo = algo;
hash->hdr.length = digest_size;
rc = crypto_shash_tfm_digest(tfm, (const u8 *)&file_id, file_id_size,
hash->digest);
crypto_free_shash(tfm);
return rc;
}
static int asymmetric_verify_v3_hashless(struct key *key,
const struct public_key *pk,
const char *encoding,
const char *sig, int siglen,
u8 algo,
const u8 *digest)
{
struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig;
struct ima_file_id file_id = {
.hash_type = hdr->type,
.hash_algorithm = algo,
};
size_t digest_size = hash_digest_size[algo];
struct public_key_signature pks = {
.m = (u8 *)&file_id,
.m_size = sizeof(file_id) - (HASH_MAX_DIGESTSIZE - digest_size),
.s = hdr->sig,
.s_size = siglen - sizeof(*hdr),
.pkey_algo = pk->pkey_algo,
.hash_algo = "none",
.encoding = encoding,
};
int ret;
if (hdr->type != IMA_VERITY_DIGSIG &&
hdr->type != EVM_IMA_XATTR_DIGSIG &&
hdr->type != EVM_XATTR_PORTABLE_DIGSIG)
return -EINVAL;
if (pks.s_size != be16_to_cpu(hdr->sig_size))
return -EBADMSG;
memcpy(file_id.hash, digest, digest_size);
ret = verify_signature(key, &pks);
pr_debug("%s() = %d\n", __func__, ret);
return ret;
}
int asymmetric_verify_v3(struct key *keyring, const char *sig, int siglen,
const char *data, int datalen, u8 algo)
{
struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig;
struct ima_max_digest_data hash;
const struct public_key *pk;
struct key *key;
int rc;
if (algo >= HASH_ALGO__LAST)
return -ENOPKG;
if (siglen <= sizeof(*hdr))
return -EBADMSG;
key = request_asymmetric_key(keyring, be32_to_cpu(hdr->keyid));
if (IS_ERR(key))
return PTR_ERR(key);
pk = asymmetric_key_public_key(key);
if (!pk) {
rc = -ENOKEY;
goto out;
}
if (!strncmp(pk->pkey_algo, "mldsa", 5)) {
rc = asymmetric_verify_v3_hashless(key, pk, "raw",
sig, siglen, algo, data);
} else {
rc = calc_file_id_hash(hdr->type, algo, data, &hash);
if (rc) {
rc = -EINVAL;
goto out;
}
rc = asymmetric_verify_common(key, pk, sig, siglen, hash.digest,
hash.hdr.length);
}
out:
key_put(key);
return rc;
}