root/drivers/s390/crypto/pkey_cca.c
// SPDX-License-Identifier: GPL-2.0
/*
 *  pkey cca specific code
 *
 *  Copyright IBM Corp. 2024
 */

#define pr_fmt(fmt) "pkey: " fmt

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cpufeature.h>

#include "zcrypt_ccamisc.h"
#include "pkey_base.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("IBM Corporation");
MODULE_DESCRIPTION("s390 protected key CCA handler");

#if IS_MODULE(CONFIG_PKEY_CCA)
static struct ap_device_id pkey_cca_card_ids[] = {
        { .dev_type = AP_DEVICE_TYPE_CEX4 },
        { .dev_type = AP_DEVICE_TYPE_CEX5 },
        { .dev_type = AP_DEVICE_TYPE_CEX6 },
        { .dev_type = AP_DEVICE_TYPE_CEX7 },
        { .dev_type = AP_DEVICE_TYPE_CEX8 },
        { /* end of list */ },
};
MODULE_DEVICE_TABLE(ap, pkey_cca_card_ids);
#endif

/*
 * Check key blob for known and supported CCA key.
 */
static bool is_cca_key(const u8 *key, u32 keylen)
{
        struct keytoken_header *hdr = (struct keytoken_header *)key;

        if (keylen < sizeof(*hdr))
                return false;

        switch (hdr->type) {
        case TOKTYPE_CCA_INTERNAL:
                switch (hdr->version) {
                case TOKVER_CCA_AES:
                case TOKVER_CCA_VLSC:
                        return true;
                default:
                        return false;
                }
        case TOKTYPE_CCA_INTERNAL_PKA:
                return true;
        default:
                return false;
        }
}

static bool is_cca_keytype(enum pkey_key_type key_type)
{
        switch (key_type) {
        case PKEY_TYPE_CCA_DATA:
        case PKEY_TYPE_CCA_CIPHER:
        case PKEY_TYPE_CCA_ECC:
                return true;
        default:
                return false;
        }
}

static int cca_apqns4key(const u8 *key, u32 keylen, u32 flags,
                         struct pkey_apqn *apqns, size_t *nr_apqns, u32 pflags)
{
        struct keytoken_header *hdr = (struct keytoken_header *)key;
        u32 _apqns[MAXAPQNSINLIST], _nr_apqns = ARRAY_SIZE(_apqns);
        u32 xflags;
        int rc;

        xflags = pflags & PKEY_XFLAG_NOMEMALLOC ? ZCRYPT_XFLAG_NOMEMALLOC : 0;

        if (!flags)
                flags = PKEY_FLAGS_MATCH_CUR_MKVP | PKEY_FLAGS_MATCH_ALT_MKVP;

        if (keylen < sizeof(struct keytoken_header))
                return -EINVAL;

        zcrypt_wait_api_operational();

        if (hdr->type == TOKTYPE_CCA_INTERNAL) {
                u64 cur_mkvp = 0, old_mkvp = 0;
                int minhwtype = ZCRYPT_CEX3C;

                if (hdr->version == TOKVER_CCA_AES) {
                        struct secaeskeytoken *t = (struct secaeskeytoken *)key;

                        if (flags & PKEY_FLAGS_MATCH_CUR_MKVP)
                                cur_mkvp = t->mkvp;
                        if (flags & PKEY_FLAGS_MATCH_ALT_MKVP)
                                old_mkvp = t->mkvp;
                } else if (hdr->version == TOKVER_CCA_VLSC) {
                        struct cipherkeytoken *t = (struct cipherkeytoken *)key;

                        minhwtype = ZCRYPT_CEX6;
                        if (flags & PKEY_FLAGS_MATCH_CUR_MKVP)
                                cur_mkvp = t->mkvp0;
                        if (flags & PKEY_FLAGS_MATCH_ALT_MKVP)
                                old_mkvp = t->mkvp0;
                } else {
                        /* unknown CCA internal token type */
                        return -EINVAL;
                }
                rc = cca_findcard2(_apqns, &_nr_apqns, 0xFFFF, 0xFFFF,
                                   minhwtype, AES_MK_SET,
                                   cur_mkvp, old_mkvp, xflags);
                if (rc)
                        goto out;

        } else if (hdr->type == TOKTYPE_CCA_INTERNAL_PKA) {
                struct eccprivkeytoken *t = (struct eccprivkeytoken *)key;
                u64 cur_mkvp = 0, old_mkvp = 0;

                if (t->secid == 0x20) {
                        if (flags & PKEY_FLAGS_MATCH_CUR_MKVP)
                                cur_mkvp = t->mkvp;
                        if (flags & PKEY_FLAGS_MATCH_ALT_MKVP)
                                old_mkvp = t->mkvp;
                } else {
                        /* unknown CCA internal 2 token type */
                        return -EINVAL;
                }
                rc = cca_findcard2(_apqns, &_nr_apqns, 0xFFFF, 0xFFFF,
                                   ZCRYPT_CEX7, APKA_MK_SET,
                                   cur_mkvp, old_mkvp, xflags);
                if (rc)
                        goto out;

        } else {
                PKEY_DBF_ERR("%s unknown/unsupported blob type %d version %d\n",
                             __func__, hdr->type, hdr->version);
                return -EINVAL;
        }

        if (apqns) {
                if (*nr_apqns < _nr_apqns)
                        rc = -ENOSPC;
                else
                        memcpy(apqns, _apqns, _nr_apqns * sizeof(u32));
        }
        *nr_apqns = _nr_apqns;

out:
        pr_debug("rc=%d\n", rc);
        return rc;
}

static int cca_apqns4type(enum pkey_key_type ktype,
                          u8 cur_mkvp[32], u8 alt_mkvp[32], u32 flags,
                          struct pkey_apqn *apqns, size_t *nr_apqns,
                          u32 pflags)
{
        u32 _apqns[MAXAPQNSINLIST], _nr_apqns = ARRAY_SIZE(_apqns);
        u32 xflags;
        int rc;

        xflags = pflags & PKEY_XFLAG_NOMEMALLOC ? ZCRYPT_XFLAG_NOMEMALLOC : 0;

        zcrypt_wait_api_operational();

        if (ktype == PKEY_TYPE_CCA_DATA || ktype == PKEY_TYPE_CCA_CIPHER) {
                u64 cur_mkvp = 0, old_mkvp = 0;
                int minhwtype = ZCRYPT_CEX3C;

                if (flags & PKEY_FLAGS_MATCH_CUR_MKVP)
                        cur_mkvp = *((u64 *)cur_mkvp);
                if (flags & PKEY_FLAGS_MATCH_ALT_MKVP)
                        old_mkvp = *((u64 *)alt_mkvp);
                if (ktype == PKEY_TYPE_CCA_CIPHER)
                        minhwtype = ZCRYPT_CEX6;
                rc = cca_findcard2(_apqns, &_nr_apqns, 0xFFFF, 0xFFFF,
                                   minhwtype, AES_MK_SET,
                                   cur_mkvp, old_mkvp, xflags);
                if (rc)
                        goto out;

        } else if (ktype == PKEY_TYPE_CCA_ECC) {
                u64 cur_mkvp = 0, old_mkvp = 0;

                if (flags & PKEY_FLAGS_MATCH_CUR_MKVP)
                        cur_mkvp = *((u64 *)cur_mkvp);
                if (flags & PKEY_FLAGS_MATCH_ALT_MKVP)
                        old_mkvp = *((u64 *)alt_mkvp);
                rc = cca_findcard2(_apqns, &_nr_apqns, 0xFFFF, 0xFFFF,
                                   ZCRYPT_CEX7, APKA_MK_SET,
                                   cur_mkvp, old_mkvp, xflags);
                if (rc)
                        goto out;

        } else {
                PKEY_DBF_ERR("%s unknown/unsupported key type %d",
                             __func__, (int)ktype);
                return -EINVAL;
        }

        if (apqns) {
                if (*nr_apqns < _nr_apqns)
                        rc = -ENOSPC;
                else
                        memcpy(apqns, _apqns, _nr_apqns * sizeof(u32));
        }
        *nr_apqns = _nr_apqns;

out:
        pr_debug("rc=%d\n", rc);
        return rc;
}

static int cca_key2protkey(const struct pkey_apqn *apqns, size_t nr_apqns,
                           const u8 *key, u32 keylen,
                           u8 *protkey, u32 *protkeylen, u32 *protkeytype,
                           u32 pflags)
{
        struct keytoken_header *hdr = (struct keytoken_header *)key;
        struct pkey_apqn _apqns[MAXAPQNSINLIST];
        u32 xflags;
        int i, rc;

        xflags = pflags & PKEY_XFLAG_NOMEMALLOC ? ZCRYPT_XFLAG_NOMEMALLOC : 0;

        if (keylen < sizeof(*hdr))
                return -EINVAL;

        if (hdr->type == TOKTYPE_CCA_INTERNAL &&
            hdr->version == TOKVER_CCA_AES) {
                /* CCA AES data key */
                if (keylen < sizeof(struct secaeskeytoken))
                        return -EINVAL;
                if (cca_check_secaeskeytoken(pkey_dbf_info, 3, key, 0))
                        return -EINVAL;
        } else if (hdr->type == TOKTYPE_CCA_INTERNAL &&
                   hdr->version == TOKVER_CCA_VLSC) {
                /* CCA AES cipher key */
                if (keylen < hdr->len)
                        return -EINVAL;
                if (cca_check_secaescipherkey(pkey_dbf_info,
                                              3, key, 0, 1))
                        return -EINVAL;
        } else if (hdr->type == TOKTYPE_CCA_INTERNAL_PKA) {
                /* CCA ECC (private) key */
                if (keylen < sizeof(struct eccprivkeytoken))
                        return -EINVAL;
                if (cca_check_sececckeytoken(pkey_dbf_info, 3, key, keylen, 1))
                        return -EINVAL;
        } else {
                PKEY_DBF_ERR("%s unknown/unsupported blob type %d version %d\n",
                             __func__, hdr->type, hdr->version);
                return -EINVAL;
        }

        zcrypt_wait_api_operational();

        if (!apqns || (nr_apqns == 1 &&
                       apqns[0].card == 0xFFFF && apqns[0].domain == 0xFFFF)) {
                nr_apqns = MAXAPQNSINLIST;
                rc = cca_apqns4key(key, keylen, 0, _apqns, &nr_apqns, pflags);
                if (rc)
                        goto out;
                apqns = _apqns;
        }

        for (rc = -ENODEV, i = 0; rc && i < nr_apqns; i++) {
                if (hdr->type == TOKTYPE_CCA_INTERNAL &&
                    hdr->version == TOKVER_CCA_AES) {
                        rc = cca_sec2protkey(apqns[i].card, apqns[i].domain,
                                             key, protkey,
                                             protkeylen, protkeytype, xflags);
                } else if (hdr->type == TOKTYPE_CCA_INTERNAL &&
                           hdr->version == TOKVER_CCA_VLSC) {
                        rc = cca_cipher2protkey(apqns[i].card, apqns[i].domain,
                                                key, protkey,
                                                protkeylen, protkeytype, xflags);
                } else if (hdr->type == TOKTYPE_CCA_INTERNAL_PKA) {
                        rc = cca_ecc2protkey(apqns[i].card, apqns[i].domain,
                                             key, protkey,
                                             protkeylen, protkeytype, xflags);
                } else {
                        rc = -EINVAL;
                        break;
                }
        }

out:
        pr_debug("rc=%d\n", rc);
        return rc;
}

/*
 * Generate CCA secure key.
 * As of now only CCA AES Data or Cipher secure keys are
 * supported.
 * keytype is one of the PKEY_KEYTYPE_* constants,
 * subtype may be 0 or PKEY_TYPE_CCA_DATA or PKEY_TYPE_CCA_CIPHER,
 * keybitsize is the bit size of the key (may be 0 for
 * keytype PKEY_KEYTYPE_AES_*).
 */
static int cca_gen_key(const struct pkey_apqn *apqns, size_t nr_apqns,
                       u32 keytype, u32 subtype,
                       u32 keybitsize, u32 flags,
                       u8 *keybuf, u32 *keybuflen, u32 *_keyinfo, u32 pflags)
{
        struct pkey_apqn _apqns[MAXAPQNSINLIST];
        int i, len, rc;
        u32 xflags;

        xflags = pflags & PKEY_XFLAG_NOMEMALLOC ? ZCRYPT_XFLAG_NOMEMALLOC : 0;

        /* check keytype, subtype, keybitsize */
        switch (keytype) {
        case PKEY_KEYTYPE_AES_128:
        case PKEY_KEYTYPE_AES_192:
        case PKEY_KEYTYPE_AES_256:
                len = pkey_keytype_aes_to_size(keytype);
                if (keybitsize && keybitsize != 8 * len) {
                        PKEY_DBF_ERR("%s unknown/unsupported keybitsize %d\n",
                                     __func__, keybitsize);
                        return -EINVAL;
                }
                keybitsize = 8 * len;
                switch (subtype) {
                case PKEY_TYPE_CCA_DATA:
                case PKEY_TYPE_CCA_CIPHER:
                        break;
                default:
                        PKEY_DBF_ERR("%s unknown/unsupported subtype %d\n",
                                     __func__, subtype);
                        return -EINVAL;
                }
                break;
        default:
                PKEY_DBF_ERR("%s unknown/unsupported keytype %d\n",
                             __func__, keytype);
                return -EINVAL;
        }

        zcrypt_wait_api_operational();

        if (!apqns || (nr_apqns == 1 &&
                       apqns[0].card == 0xFFFF && apqns[0].domain == 0xFFFF)) {
                nr_apqns = MAXAPQNSINLIST;
                rc = cca_apqns4type(subtype, NULL, NULL, 0,
                                    _apqns, &nr_apqns, pflags);
                if (rc)
                        goto out;
                apqns = _apqns;
        }

        for (rc = -ENODEV, i = 0; rc && i < nr_apqns; i++) {
                if (subtype == PKEY_TYPE_CCA_CIPHER) {
                        rc = cca_gencipherkey(apqns[i].card, apqns[i].domain,
                                              keybitsize, flags,
                                              keybuf, keybuflen, xflags);
                } else {
                        /* PKEY_TYPE_CCA_DATA */
                        rc = cca_genseckey(apqns[i].card, apqns[i].domain,
                                           keybitsize, keybuf, xflags);
                        *keybuflen = (rc ? 0 : SECKEYBLOBSIZE);
                }
        }

out:
        pr_debug("rc=%d\n", rc);
        return rc;
}

/*
 * Generate CCA secure key with given clear key value.
 * As of now only CCA AES Data or Cipher secure keys are
 * supported.
 * keytype is one of the PKEY_KEYTYPE_* constants,
 * subtype may be 0 or PKEY_TYPE_CCA_DATA or PKEY_TYPE_CCA_CIPHER,
 * keybitsize is the bit size of the key (may be 0 for
 * keytype PKEY_KEYTYPE_AES_*).
 */
static int cca_clr2key(const struct pkey_apqn *apqns, size_t nr_apqns,
                       u32 keytype, u32 subtype,
                       u32 keybitsize, u32 flags,
                       const u8 *clrkey, u32 clrkeylen,
                       u8 *keybuf, u32 *keybuflen, u32 *_keyinfo, u32 pflags)
{
        struct pkey_apqn _apqns[MAXAPQNSINLIST];
        int i, len, rc;
        u32 xflags;

        if (pflags & PKEY_XFLAG_NOCLEARKEY) {
                PKEY_DBF_ERR("%s clear key but xflag NOCLEARKEY\n", __func__);
                return -EINVAL;
        }

        xflags = pflags & PKEY_XFLAG_NOMEMALLOC ? ZCRYPT_XFLAG_NOMEMALLOC : 0;

        /* check keytype, subtype, clrkeylen, keybitsize */
        switch (keytype) {
        case PKEY_KEYTYPE_AES_128:
        case PKEY_KEYTYPE_AES_192:
        case PKEY_KEYTYPE_AES_256:
                len = pkey_keytype_aes_to_size(keytype);
                if (keybitsize && keybitsize != 8 * len) {
                        PKEY_DBF_ERR("%s unknown/unsupported keybitsize %d\n",
                                     __func__, keybitsize);
                        return -EINVAL;
                }
                keybitsize = 8 * len;
                if (clrkeylen != len) {
                        PKEY_DBF_ERR("%s invalid clear key len %d != %d\n",
                                     __func__, clrkeylen, len);
                        return -EINVAL;
                }
                switch (subtype) {
                case PKEY_TYPE_CCA_DATA:
                case PKEY_TYPE_CCA_CIPHER:
                        break;
                default:
                        PKEY_DBF_ERR("%s unknown/unsupported subtype %d\n",
                                     __func__, subtype);
                        return -EINVAL;
                }
                break;
        default:
                PKEY_DBF_ERR("%s unknown/unsupported keytype %d\n",
                             __func__, keytype);
                return -EINVAL;
        }

        zcrypt_wait_api_operational();

        if (!apqns || (nr_apqns == 1 &&
                       apqns[0].card == 0xFFFF && apqns[0].domain == 0xFFFF)) {
                nr_apqns = MAXAPQNSINLIST;
                rc = cca_apqns4type(subtype, NULL, NULL, 0,
                                    _apqns, &nr_apqns, pflags);
                if (rc)
                        goto out;
                apqns = _apqns;
        }

        for (rc = -ENODEV, i = 0; rc && i < nr_apqns; i++) {
                if (subtype == PKEY_TYPE_CCA_CIPHER) {
                        rc = cca_clr2cipherkey(apqns[i].card, apqns[i].domain,
                                               keybitsize, flags, clrkey,
                                               keybuf, keybuflen, xflags);
                } else {
                        /* PKEY_TYPE_CCA_DATA */
                        rc = cca_clr2seckey(apqns[i].card, apqns[i].domain,
                                            keybitsize, clrkey, keybuf, xflags);
                        *keybuflen = (rc ? 0 : SECKEYBLOBSIZE);
                }
        }

out:
        pr_debug("rc=%d\n", rc);
        return rc;
}

static int cca_verifykey(const u8 *key, u32 keylen,
                         u16 *card, u16 *dom,
                         u32 *keytype, u32 *keybitsize, u32 *flags, u32 pflags)
{
        struct keytoken_header *hdr = (struct keytoken_header *)key;
        u32 apqns[MAXAPQNSINLIST], nr_apqns = ARRAY_SIZE(apqns);
        u32 xflags;
        int rc;

        xflags = pflags & PKEY_XFLAG_NOMEMALLOC ? ZCRYPT_XFLAG_NOMEMALLOC : 0;

        if (keylen < sizeof(*hdr))
                return -EINVAL;

        zcrypt_wait_api_operational();

        if (hdr->type == TOKTYPE_CCA_INTERNAL &&
            hdr->version == TOKVER_CCA_AES) {
                struct secaeskeytoken *t = (struct secaeskeytoken *)key;

                rc = cca_check_secaeskeytoken(pkey_dbf_info, 3, key, 0);
                if (rc)
                        goto out;
                *keytype = PKEY_TYPE_CCA_DATA;
                *keybitsize = t->bitsize;
                rc = cca_findcard2(apqns, &nr_apqns, *card, *dom,
                                   ZCRYPT_CEX3C, AES_MK_SET,
                                   t->mkvp, 0, xflags);
                if (!rc)
                        *flags = PKEY_FLAGS_MATCH_CUR_MKVP;
                if (rc == -ENODEV) {
                        nr_apqns = ARRAY_SIZE(apqns);
                        rc = cca_findcard2(apqns, &nr_apqns, *card, *dom,
                                           ZCRYPT_CEX3C, AES_MK_SET,
                                           0, t->mkvp, xflags);
                        if (!rc)
                                *flags = PKEY_FLAGS_MATCH_ALT_MKVP;
                }
                if (rc)
                        goto out;

                *card = ((struct pkey_apqn *)apqns)->card;
                *dom = ((struct pkey_apqn *)apqns)->domain;

        } else if (hdr->type == TOKTYPE_CCA_INTERNAL &&
                   hdr->version == TOKVER_CCA_VLSC) {
                struct cipherkeytoken *t = (struct cipherkeytoken *)key;

                rc = cca_check_secaescipherkey(pkey_dbf_info, 3, key, 0, 1);
                if (rc)
                        goto out;
                *keytype = PKEY_TYPE_CCA_CIPHER;
                *keybitsize = PKEY_SIZE_UNKNOWN;
                if (!t->plfver && t->wpllen == 512)
                        *keybitsize = PKEY_SIZE_AES_128;
                else if (!t->plfver && t->wpllen == 576)
                        *keybitsize = PKEY_SIZE_AES_192;
                else if (!t->plfver && t->wpllen == 640)
                        *keybitsize = PKEY_SIZE_AES_256;
                rc = cca_findcard2(apqns, &nr_apqns, *card, *dom,
                                   ZCRYPT_CEX6, AES_MK_SET,
                                   t->mkvp0, 0, xflags);
                if (!rc)
                        *flags = PKEY_FLAGS_MATCH_CUR_MKVP;
                if (rc == -ENODEV) {
                        nr_apqns = ARRAY_SIZE(apqns);
                        rc = cca_findcard2(apqns, &nr_apqns, *card, *dom,
                                           ZCRYPT_CEX6, AES_MK_SET,
                                           0, t->mkvp0, xflags);
                        if (!rc)
                                *flags = PKEY_FLAGS_MATCH_ALT_MKVP;
                }
                if (rc)
                        goto out;

                *card = ((struct pkey_apqn *)apqns)->card;
                *dom = ((struct pkey_apqn *)apqns)->domain;

        } else {
                /* unknown/unsupported key blob */
                rc = -EINVAL;
        }

out:
        pr_debug("rc=%d\n", rc);
        return rc;
}

/*
 * This function provides an alternate but usually slow way
 * to convert a 'clear key token' with AES key material into
 * a protected key. This is done via an intermediate step
 * which creates a CCA AES DATA secure key first and then
 * derives the protected key from this secure key.
 */
static int cca_slowpath_key2protkey(const struct pkey_apqn *apqns,
                                    size_t nr_apqns,
                                    const u8 *key, u32 keylen,
                                    u8 *protkey, u32 *protkeylen,
                                    u32 *protkeytype, u32 pflags)
{
        const struct keytoken_header *hdr = (const struct keytoken_header *)key;
        const struct clearkeytoken *t = (const struct clearkeytoken *)key;
        u8 tmpbuf[SECKEYBLOBSIZE]; /* 64 bytes */
        u32 tmplen, keysize = 0;
        int i, rc;

        if (keylen < sizeof(*hdr))
                return -EINVAL;

        if (hdr->type == TOKTYPE_NON_CCA &&
            hdr->version == TOKVER_CLEAR_KEY)
                keysize = pkey_keytype_aes_to_size(t->keytype);
        if (!keysize || t->len != keysize)
                return -EINVAL;

        /* try two times in case of failure */
        for (i = 0, rc = -ENODEV; i < 2 && rc; i++) {
                tmplen = SECKEYBLOBSIZE;
                rc = cca_clr2key(NULL, 0, t->keytype, PKEY_TYPE_CCA_DATA,
                                 8 * keysize, 0, t->clearkey, t->len,
                                 tmpbuf, &tmplen, NULL, pflags);
                pr_debug("cca_clr2key()=%d\n", rc);
                if (rc)
                        continue;
                rc = cca_key2protkey(NULL, 0, tmpbuf, tmplen,
                                     protkey, protkeylen, protkeytype, pflags);
                pr_debug("cca_key2protkey()=%d\n", rc);
        }

        pr_debug("rc=%d\n", rc);
        return rc;
}

static struct pkey_handler cca_handler = {
        .module                  = THIS_MODULE,
        .name                    = "PKEY CCA handler",
        .is_supported_key        = is_cca_key,
        .is_supported_keytype    = is_cca_keytype,
        .key_to_protkey          = cca_key2protkey,
        .slowpath_key_to_protkey = cca_slowpath_key2protkey,
        .gen_key                 = cca_gen_key,
        .clr_to_key              = cca_clr2key,
        .verify_key              = cca_verifykey,
        .apqns_for_key           = cca_apqns4key,
        .apqns_for_keytype       = cca_apqns4type,
};

/*
 * Module init
 */
static int __init pkey_cca_init(void)
{
        /* register this module as pkey handler for all the cca stuff */
        return pkey_handler_register(&cca_handler);
}

/*
 * Module exit
 */
static void __exit pkey_cca_exit(void)
{
        /* unregister this module as pkey handler */
        pkey_handler_unregister(&cca_handler);
}

module_init(pkey_cca_init);
module_exit(pkey_cca_exit);