root/security/integrity/digsig_asymmetric.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2013 Intel Corporation
 *
 * Author:
 * Dmitry Kasatkin <dmitry.kasatkin@intel.com>
 */

#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"

/*
 * Request an asymmetric key.
 */
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) {
                /* search in specific 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)) {
                        /* Hide some search errors */
                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;
}

/**
 * asymmetric_verify_common -- sigv2 and sigv3 common verify function
 * @key: The key to use for signature verification; caller must free it
 * @pk: The associated public key; must not be NULL
 * @sig: The xattr signature
 * @siglen: The length of the xattr signature; must be at least
 *          sizeof(struct signature_v2_hdr)
 * @data: The data to verify the signature on
 * @datalen: Length of @data
 */
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)) {
                /* edcsa-nist-p192 etc. */
                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;
}

/*
 * calc_file_id_hash - calculate the hash of the ima_file_id struct data
 * @type: xattr type [enum evm_ima_xattr_type]
 * @algo: hash algorithm [enum hash_algo]; caller must ensure valid value
 * @digest: pointer to the digest to be hashed
 * @hash: (out) pointer to the hash
 *
 * IMA signature version 3 disambiguates the data that is signed by
 * indirectly signing the hash of the ima_file_id structure data.
 *
 * Return 0 on success, error code otherwise.
 */
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);

        /* Calculate the ima_file_id struct hash on the portion used. */
        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;
}

/**
 * asymmetric_verify_v3_hashless - Use hashless signature verification on sigv3
 * @key: The key to use for signature verification; caller must free it
 * @pk: The associated public key; must not be NULL
 * @encoding: The encoding the key type uses
 * @sig: The xattr signature
 * @siglen: The length of the xattr signature; must be at least
 *          sizeof(struct signature_v2_hdr)
 * @algo: hash algorithm [enum hash_algo]; caller must ensure valid value
 * @digest: The file digest
 *
 * Create an ima_file_id structure and use it for signature verification
 * directly. This can be used for ML-DSA in pure mode for example.
 */
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;
}