root/crypto/asymmetric_keys/public_key.c
// SPDX-License-Identifier: GPL-2.0-or-later
/* In-software asymmetric public-key crypto subtype
 *
 * See Documentation/crypto/asymmetric-keys.rst
 *
 * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved.
 * Written by David Howells (dhowells@redhat.com)
 */

#define pr_fmt(fmt) "PKEY: "fmt
#include <crypto/akcipher.h>
#include <crypto/public_key.h>
#include <crypto/sig.h>
#include <keys/asymmetric-subtype.h>
#include <linux/asn1.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/string.h>

MODULE_DESCRIPTION("In-software asymmetric public-key subtype");
MODULE_AUTHOR("Red Hat, Inc.");
MODULE_LICENSE("GPL");

/*
 * Provide a part of a description of the key for /proc/keys.
 */
static void public_key_describe(const struct key *asymmetric_key,
                                struct seq_file *m)
{
        struct public_key *key = asymmetric_key->payload.data[asym_crypto];

        if (key)
                seq_printf(m, "%s.%s", key->id_type, key->pkey_algo);
}

/*
 * Destroy a public key algorithm key.
 */
void public_key_free(struct public_key *key)
{
        if (key) {
                kfree_sensitive(key->key);
                kfree(key->params);
                kfree(key);
        }
}
EXPORT_SYMBOL_GPL(public_key_free);

/*
 * Destroy a public key algorithm key.
 */
static void public_key_destroy(void *payload0, void *payload3)
{
        public_key_free(payload0);
        public_key_signature_free(payload3);
}

/*
 * Given a public_key, and an encoding and hash_algo to be used for signing
 * and/or verification with that key, determine the name of the corresponding
 * akcipher algorithm.  Also check that encoding and hash_algo are allowed.
 */
static int
software_key_determine_akcipher(const struct public_key *pkey,
                                const char *encoding, const char *hash_algo,
                                char alg_name[CRYPTO_MAX_ALG_NAME], bool *sig,
                                enum kernel_pkey_operation op)
{
        int n;

        *sig = true;

        if (!encoding)
                return -EINVAL;

        if (strcmp(pkey->pkey_algo, "rsa") == 0) {
                /*
                 * RSA signatures usually use EMSA-PKCS1-1_5 [RFC3447 sec 8.2].
                 */
                if (strcmp(encoding, "pkcs1") == 0) {
                        *sig = op == kernel_pkey_sign ||
                               op == kernel_pkey_verify;
                        if (!*sig) {
                                /*
                                 * For encrypt/decrypt, hash_algo is not used
                                 * but allowed to be set for historic reasons.
                                 */
                                n = snprintf(alg_name, CRYPTO_MAX_ALG_NAME,
                                             "pkcs1pad(%s)",
                                             pkey->pkey_algo);
                        } else {
                                if (!hash_algo)
                                        hash_algo = "none";
                                n = snprintf(alg_name, CRYPTO_MAX_ALG_NAME,
                                             "pkcs1(%s,%s)",
                                             pkey->pkey_algo, hash_algo);
                        }
                        return n >= CRYPTO_MAX_ALG_NAME ? -EINVAL : 0;
                }
                if (strcmp(encoding, "raw") != 0)
                        return -EINVAL;
                /*
                 * Raw RSA cannot differentiate between different hash
                 * algorithms.
                 */
                if (hash_algo)
                        return -EINVAL;
                *sig = false;
        } else if (strncmp(pkey->pkey_algo, "ecdsa", 5) == 0) {
                if (strcmp(encoding, "x962") != 0 &&
                    strcmp(encoding, "p1363") != 0)
                        return -EINVAL;
                /*
                 * ECDSA signatures are taken over a raw hash, so they don't
                 * differentiate between different hash algorithms.  That means
                 * that the verifier should hard-code a specific hash algorithm.
                 * Unfortunately, in practice ECDSA is used with multiple SHAs,
                 * so we have to allow all of them and not just one.
                 */
                if (!hash_algo)
                        return -EINVAL;
                if (strcmp(hash_algo, "sha1") != 0 &&
                    strcmp(hash_algo, "sha224") != 0 &&
                    strcmp(hash_algo, "sha256") != 0 &&
                    strcmp(hash_algo, "sha384") != 0 &&
                    strcmp(hash_algo, "sha512") != 0 &&
                    strcmp(hash_algo, "sha3-256") != 0 &&
                    strcmp(hash_algo, "sha3-384") != 0 &&
                    strcmp(hash_algo, "sha3-512") != 0)
                        return -EINVAL;
                n = snprintf(alg_name, CRYPTO_MAX_ALG_NAME, "%s(%s)",
                             encoding, pkey->pkey_algo);
                return n >= CRYPTO_MAX_ALG_NAME ? -EINVAL : 0;
        } else if (strcmp(pkey->pkey_algo, "ecrdsa") == 0) {
                if (strcmp(encoding, "raw") != 0)
                        return -EINVAL;
                if (!hash_algo)
                        return -EINVAL;
                if (strcmp(hash_algo, "streebog256") != 0 &&
                    strcmp(hash_algo, "streebog512") != 0)
                        return -EINVAL;
        } else if (strcmp(pkey->pkey_algo, "mldsa44") == 0 ||
                   strcmp(pkey->pkey_algo, "mldsa65") == 0 ||
                   strcmp(pkey->pkey_algo, "mldsa87") == 0) {
                if (strcmp(encoding, "raw") != 0)
                        return -EINVAL;
                if (!hash_algo)
                        return -EINVAL;
                if (strcmp(hash_algo, "none") != 0 &&
                    strcmp(hash_algo, "sha512") != 0)
                        return -EINVAL;
        } else {
                /* Unknown public key algorithm */
                return -ENOPKG;
        }
        if (strscpy(alg_name, pkey->pkey_algo, CRYPTO_MAX_ALG_NAME) < 0)
                return -EINVAL;
        return 0;
}

static u8 *pkey_pack_u32(u8 *dst, u32 val)
{
        memcpy(dst, &val, sizeof(val));
        return dst + sizeof(val);
}

/*
 * Query information about a key.
 */
static int software_key_query(const struct kernel_pkey_params *params,
                              struct kernel_pkey_query *info)
{
        struct public_key *pkey = params->key->payload.data[asym_crypto];
        char alg_name[CRYPTO_MAX_ALG_NAME];
        u8 *key, *ptr;
        int ret, len;
        bool issig;

        ret = software_key_determine_akcipher(pkey, params->encoding,
                                              params->hash_algo, alg_name,
                                              &issig, kernel_pkey_sign);
        if (ret < 0)
                return ret;

        key = kmalloc(pkey->keylen + sizeof(u32) * 2 + pkey->paramlen,
                      GFP_KERNEL);
        if (!key)
                return -ENOMEM;

        memcpy(key, pkey->key, pkey->keylen);
        ptr = key + pkey->keylen;
        ptr = pkey_pack_u32(ptr, pkey->algo);
        ptr = pkey_pack_u32(ptr, pkey->paramlen);
        memcpy(ptr, pkey->params, pkey->paramlen);

        memset(info, 0, sizeof(*info));

        if (issig) {
                struct crypto_sig *sig;

                sig = crypto_alloc_sig(alg_name, 0, 0);
                if (IS_ERR(sig)) {
                        ret = PTR_ERR(sig);
                        goto error_free_key;
                }

                if (pkey->key_is_private)
                        ret = crypto_sig_set_privkey(sig, key, pkey->keylen);
                else
                        ret = crypto_sig_set_pubkey(sig, key, pkey->keylen);
                if (ret < 0)
                        goto error_free_sig;

                len = crypto_sig_keysize(sig);
                info->key_size = len;
                info->max_sig_size = crypto_sig_maxsize(sig);
                info->max_data_size = crypto_sig_digestsize(sig);

                info->supported_ops = KEYCTL_SUPPORTS_VERIFY;
                if (pkey->key_is_private)
                        info->supported_ops |= KEYCTL_SUPPORTS_SIGN;

                if (strcmp(params->encoding, "pkcs1") == 0) {
                        info->max_enc_size = len / BITS_PER_BYTE;
                        info->max_dec_size = len / BITS_PER_BYTE;

                        info->supported_ops |= KEYCTL_SUPPORTS_ENCRYPT;
                        if (pkey->key_is_private)
                                info->supported_ops |= KEYCTL_SUPPORTS_DECRYPT;
                }

error_free_sig:
                crypto_free_sig(sig);
        } else {
                struct crypto_akcipher *tfm;

                tfm = crypto_alloc_akcipher(alg_name, 0, 0);
                if (IS_ERR(tfm)) {
                        ret = PTR_ERR(tfm);
                        goto error_free_key;
                }

                if (pkey->key_is_private)
                        ret = crypto_akcipher_set_priv_key(tfm, key, pkey->keylen);
                else
                        ret = crypto_akcipher_set_pub_key(tfm, key, pkey->keylen);
                if (ret < 0)
                        goto error_free_akcipher;

                len = crypto_akcipher_maxsize(tfm);
                info->key_size = len * BITS_PER_BYTE;
                info->max_sig_size = len;
                info->max_data_size = len;
                info->max_enc_size = len;
                info->max_dec_size = len;

                info->supported_ops = KEYCTL_SUPPORTS_ENCRYPT;
                if (pkey->key_is_private)
                        info->supported_ops |= KEYCTL_SUPPORTS_DECRYPT;

error_free_akcipher:
                crypto_free_akcipher(tfm);
        }

error_free_key:
        kfree_sensitive(key);
        pr_devel("<==%s() = %d\n", __func__, ret);
        return ret;
}

/*
 * Do encryption, decryption and signing ops.
 */
static int software_key_eds_op(struct kernel_pkey_params *params,
                               const void *in, void *out)
{
        const struct public_key *pkey = params->key->payload.data[asym_crypto];
        char alg_name[CRYPTO_MAX_ALG_NAME];
        struct crypto_akcipher *tfm;
        struct crypto_sig *sig;
        char *key, *ptr;
        bool issig;
        int ret;

        pr_devel("==>%s()\n", __func__);

        ret = software_key_determine_akcipher(pkey, params->encoding,
                                              params->hash_algo, alg_name,
                                              &issig, params->op);
        if (ret < 0)
                return ret;

        key = kmalloc(pkey->keylen + sizeof(u32) * 2 + pkey->paramlen,
                      GFP_KERNEL);
        if (!key)
                return -ENOMEM;

        memcpy(key, pkey->key, pkey->keylen);
        ptr = key + pkey->keylen;
        ptr = pkey_pack_u32(ptr, pkey->algo);
        ptr = pkey_pack_u32(ptr, pkey->paramlen);
        memcpy(ptr, pkey->params, pkey->paramlen);

        if (issig) {
                sig = crypto_alloc_sig(alg_name, 0, 0);
                if (IS_ERR(sig)) {
                        ret = PTR_ERR(sig);
                        goto error_free_key;
                }

                if (pkey->key_is_private)
                        ret = crypto_sig_set_privkey(sig, key, pkey->keylen);
                else
                        ret = crypto_sig_set_pubkey(sig, key, pkey->keylen);
                if (ret)
                        goto error_free_tfm;
        } else {
                tfm = crypto_alloc_akcipher(alg_name, 0, 0);
                if (IS_ERR(tfm)) {
                        ret = PTR_ERR(tfm);
                        goto error_free_key;
                }

                if (pkey->key_is_private)
                        ret = crypto_akcipher_set_priv_key(tfm, key, pkey->keylen);
                else
                        ret = crypto_akcipher_set_pub_key(tfm, key, pkey->keylen);
                if (ret)
                        goto error_free_tfm;
        }

        ret = -EINVAL;

        /* Perform the encryption calculation. */
        switch (params->op) {
        case kernel_pkey_encrypt:
                if (issig)
                        break;
                ret = crypto_akcipher_sync_encrypt(tfm, in, params->in_len,
                                                   out, params->out_len);
                break;
        case kernel_pkey_decrypt:
                if (issig)
                        break;
                ret = crypto_akcipher_sync_decrypt(tfm, in, params->in_len,
                                                   out, params->out_len);
                break;
        case kernel_pkey_sign:
                if (!issig)
                        break;
                ret = crypto_sig_sign(sig, in, params->in_len,
                                      out, params->out_len);
                break;
        default:
                BUG();
        }

        if (!issig && ret == 0)
                ret = crypto_akcipher_maxsize(tfm);

error_free_tfm:
        if (issig)
                crypto_free_sig(sig);
        else
                crypto_free_akcipher(tfm);
error_free_key:
        kfree_sensitive(key);
        pr_devel("<==%s() = %d\n", __func__, ret);
        return ret;
}

/*
 * Verify a signature using a public key.
 */
int public_key_verify_signature(const struct public_key *pkey,
                                const struct public_key_signature *sig)
{
        char alg_name[CRYPTO_MAX_ALG_NAME];
        struct crypto_sig *tfm;
        char *key, *ptr;
        bool issig;
        int ret;

        pr_devel("==>%s()\n", __func__);

        BUG_ON(!pkey);
        BUG_ON(!sig);
        BUG_ON(!sig->s);

        /*
         * If the signature specifies a public key algorithm, it *must* match
         * the key's actual public key algorithm.
         *
         * Small exception: ECDSA signatures don't specify the curve, but ECDSA
         * keys do.  So the strings can mismatch slightly in that case:
         * "ecdsa-nist-*" for the key, but "ecdsa" for the signature.
         */
        if (sig->pkey_algo) {
                if (strcmp(pkey->pkey_algo, sig->pkey_algo) != 0 &&
                    (strncmp(pkey->pkey_algo, "ecdsa-", 6) != 0 ||
                     strcmp(sig->pkey_algo, "ecdsa") != 0))
                        return -EKEYREJECTED;
        }

        ret = software_key_determine_akcipher(pkey, sig->encoding,
                                              sig->hash_algo, alg_name,
                                              &issig, kernel_pkey_verify);
        if (ret < 0)
                return ret;

        tfm = crypto_alloc_sig(alg_name, 0, 0);
        if (IS_ERR(tfm))
                return PTR_ERR(tfm);

        key = kmalloc(pkey->keylen + sizeof(u32) * 2 + pkey->paramlen,
                      GFP_KERNEL);
        if (!key) {
                ret = -ENOMEM;
                goto error_free_tfm;
        }

        memcpy(key, pkey->key, pkey->keylen);
        ptr = key + pkey->keylen;
        ptr = pkey_pack_u32(ptr, pkey->algo);
        ptr = pkey_pack_u32(ptr, pkey->paramlen);
        memcpy(ptr, pkey->params, pkey->paramlen);

        if (pkey->key_is_private)
                ret = crypto_sig_set_privkey(tfm, key, pkey->keylen);
        else
                ret = crypto_sig_set_pubkey(tfm, key, pkey->keylen);
        if (ret)
                goto error_free_key;

        ret = crypto_sig_verify(tfm, sig->s, sig->s_size, sig->m, sig->m_size);

error_free_key:
        kfree_sensitive(key);
error_free_tfm:
        crypto_free_sig(tfm);
        pr_devel("<==%s() = %d\n", __func__, ret);
        if (WARN_ON_ONCE(ret > 0))
                ret = -EINVAL;
        return ret;
}
EXPORT_SYMBOL_GPL(public_key_verify_signature);

static int public_key_verify_signature_2(const struct key *key,
                                         const struct public_key_signature *sig)
{
        const struct public_key *pk = key->payload.data[asym_crypto];
        return public_key_verify_signature(pk, sig);
}

/*
 * Public key algorithm asymmetric key subtype
 */
struct asymmetric_key_subtype public_key_subtype = {
        .owner                  = THIS_MODULE,
        .name                   = "public_key",
        .name_len               = sizeof("public_key") - 1,
        .describe               = public_key_describe,
        .destroy                = public_key_destroy,
        .query                  = software_key_query,
        .eds_op                 = software_key_eds_op,
        .verify_signature       = public_key_verify_signature_2,
};
EXPORT_SYMBOL_GPL(public_key_subtype);