root/drivers/platform/cznic/turris-omnia-mcu-keyctl.c
// SPDX-License-Identifier: GPL-2.0
/*
 * CZ.NIC's Turris Omnia MCU ECDSA message signing via keyctl
 *
 * 2025 by Marek BehĂșn <kabel@kernel.org>
 */

#include <crypto/sha2.h>
#include <linux/cleanup.h>
#include <linux/completion.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/key.h>
#include <linux/mutex.h>
#include <linux/string.h>
#include <linux/types.h>

#include <linux/turris-omnia-mcu-interface.h>
#include <linux/turris-signing-key.h>
#include "turris-omnia-mcu.h"

static irqreturn_t omnia_msg_signed_irq_handler(int irq, void *dev_id)
{
        u8 reply[1 + OMNIA_MCU_CRYPTO_SIGNATURE_LEN];
        struct omnia_mcu *mcu = dev_id;
        int err;

        err = omnia_cmd_read(mcu->client, OMNIA_CMD_CRYPTO_COLLECT_SIGNATURE,
                             reply, sizeof(reply));
        if (!err && reply[0] != OMNIA_MCU_CRYPTO_SIGNATURE_LEN)
                err = -EIO;

        guard(mutex)(&mcu->sign_lock);

        if (mcu->sign_requested) {
                mcu->sign_err = err;
                if (!err)
                        memcpy(mcu->signature, &reply[1],
                               OMNIA_MCU_CRYPTO_SIGNATURE_LEN);
                mcu->sign_requested = false;
                complete(&mcu->msg_signed);
        }

        return IRQ_HANDLED;
}

static int omnia_mcu_sign(const struct key *key, const void *msg,
                          void *signature)
{
        struct omnia_mcu *mcu = dev_get_drvdata(turris_signing_key_get_dev(key));
        u8 cmd[1 + SHA256_DIGEST_SIZE], reply;
        int err;

        scoped_guard(mutex, &mcu->sign_lock) {
                if (mcu->sign_requested)
                        return -EBUSY;

                cmd[0] = OMNIA_CMD_CRYPTO_SIGN_MESSAGE;
                memcpy(&cmd[1], msg, SHA256_DIGEST_SIZE);

                err = omnia_cmd_write_read(mcu->client, cmd, sizeof(cmd),
                                           &reply, 1);
                if (err)
                        return err;

                if (!reply)
                        return -EBUSY;

                mcu->sign_requested = true;
        }

        if (wait_for_completion_interruptible(&mcu->msg_signed))
                return -EINTR;

        guard(mutex)(&mcu->sign_lock);

        if (mcu->sign_err)
                return mcu->sign_err;

        memcpy(signature, mcu->signature, OMNIA_MCU_CRYPTO_SIGNATURE_LEN);

        /* forget the signature, for security */
        memzero_explicit(mcu->signature, sizeof(mcu->signature));

        return OMNIA_MCU_CRYPTO_SIGNATURE_LEN;
}

static const void *omnia_mcu_get_public_key(const struct key *key)
{
        struct omnia_mcu *mcu = dev_get_drvdata(turris_signing_key_get_dev(key));

        return mcu->board_public_key;
}

static const struct turris_signing_key_subtype omnia_signing_key_subtype = {
        .key_size               = 256,
        .data_size              = SHA256_DIGEST_SIZE,
        .sig_size               = OMNIA_MCU_CRYPTO_SIGNATURE_LEN,
        .public_key_size        = OMNIA_MCU_CRYPTO_PUBLIC_KEY_LEN,
        .hash_algo              = "sha256",
        .get_public_key         = omnia_mcu_get_public_key,
        .sign                   = omnia_mcu_sign,
};

static int omnia_mcu_read_public_key(struct omnia_mcu *mcu)
{
        u8 reply[1 + OMNIA_MCU_CRYPTO_PUBLIC_KEY_LEN];
        int err;

        err = omnia_cmd_read(mcu->client, OMNIA_CMD_CRYPTO_GET_PUBLIC_KEY,
                             reply, sizeof(reply));
        if (err)
                return err;

        if (reply[0] != OMNIA_MCU_CRYPTO_PUBLIC_KEY_LEN)
                return -EIO;

        memcpy(mcu->board_public_key, &reply[1],
               OMNIA_MCU_CRYPTO_PUBLIC_KEY_LEN);

        return 0;
}

int omnia_mcu_register_keyctl(struct omnia_mcu *mcu)
{
        struct device *dev = &mcu->client->dev;
        char desc[48];
        int err;

        if (!(mcu->features & OMNIA_FEAT_CRYPTO))
                return 0;

        err = omnia_mcu_read_public_key(mcu);
        if (err)
                return dev_err_probe(dev, err,
                                     "Cannot read board public key\n");

        err = devm_mutex_init(dev, &mcu->sign_lock);
        if (err)
                return err;

        init_completion(&mcu->msg_signed);

        err = omnia_mcu_request_irq(mcu, OMNIA_INT_MESSAGE_SIGNED,
                                    omnia_msg_signed_irq_handler,
                                    "turris-omnia-mcu-keyctl");
        if (err)
                return dev_err_probe(dev, err,
                                     "Cannot request MESSAGE_SIGNED IRQ\n");

        sprintf(desc, "Turris Omnia SN %016llX MCU ECDSA key",
                mcu->board_serial_number);

        err = devm_turris_signing_key_create(dev, &omnia_signing_key_subtype,
                                             desc);
        if (err)
                return dev_err_probe(dev, err, "Cannot create signing key\n");

        return 0;
}