Merge branch 'cznic/platform' of https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc into soc/drivers

These are updates from Marek Behún for the cznic platform drivers:

  This series adds support for generating ECDSA signatures with hardware
  stored private key on Turris Omnia and Turris MOX.
  This ability is exposed via the keyctl() syscall.

* 'cznic/platform' of https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc:
  platform: cznic: use ffs() instead of __bf_shf()
  firmware: turris-mox-rwtm: fix building without CONFIG_KEYS
  platform: cznic: fix function parameter names
  firmware: turris-mox-rwtm: Add support for ECDSA signatures with HW private key
  firmware: turris-mox-rwtm: Drop ECDSA signatures via debugfs
  platform: cznic: turris-omnia-mcu: Add support for digital message signing with HW private key
  platform: cznic: Add keyctl helpers for Turris platform
  platform: cznic: turris-omnia-mcu: Refactor requesting MCU interrupt

Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
Arnd Bergmann 2025-05-12 13:33:57 +02:00
commit 94b247343f
14 changed files with 615 additions and 179 deletions

View File

@ -1,14 +0,0 @@
What: /sys/kernel/debug/turris-mox-rwtm/do_sign
Date: Jun 2020
KernelVersion: 5.8
Contact: Marek Behún <kabel@kernel.org>
Description:
======= ===========================================================
(Write) Message to sign with the ECDSA private key stored in
device's OTP. The message must be exactly 64 bytes
(since this is intended for SHA-512 hashes).
(Read) The resulting signature, 136 bytes. This contains the
R and S values of the ECDSA signature, both in
big-endian format.
======= ===========================================================

View File

@ -12,15 +12,6 @@ Contact: Marek Behún <kabel@kernel.org>
Description: (Read) MAC addresses burned into eFuses of this Turris Mox board.
Format: %pM
What: /sys/firmware/turris-mox-rwtm/pubkey
Date: August 2019
KernelVersion: 5.4
Contact: Marek Behún <kabel@kernel.org>
Description: (Read) ECDSA public key (in pubkey hex compressed form) computed
as pair to the ECDSA private key burned into eFuses of this
Turris Mox Board.
Format: string
What: /sys/firmware/turris-mox-rwtm/ram_size
Date: August 2019
KernelVersion: 5.4

View File

@ -2491,6 +2491,7 @@ F: include/dt-bindings/bus/moxtet.h
F: include/linux/armada-37xx-rwtm-mailbox.h
F: include/linux/moxtet.h
F: include/linux/turris-omnia-mcu-interface.h
F: include/linux/turris-signing-key.h
ARM/FARADAY FA526 PORT
M: Hans Ulli Kroll <ulli.kroll@googlemail.com>

View File

@ -268,6 +268,23 @@ config TURRIS_MOX_RWTM
other manufacturing data and also utilize the Entropy Bit Generator
for hardware random number generation.
if TURRIS_MOX_RWTM
config TURRIS_MOX_RWTM_KEYCTL
bool "Turris Mox rWTM ECDSA message signing"
default y
depends on KEYS
depends on ASYMMETRIC_KEY_TYPE
select CZNIC_PLATFORMS
select TURRIS_SIGNING_KEY
help
Say Y here to add support for ECDSA message signing with board private
key (each Turris Mox has an ECDSA private key generated in the secure
coprocessor when manufactured). This functionality is exposed via the
keyctl() syscall.
endif # TURRIS_MOX_RWTM
source "drivers/firmware/arm_ffa/Kconfig"
source "drivers/firmware/broadcom/Kconfig"
source "drivers/firmware/cirrus/Kconfig"

View File

@ -2,29 +2,31 @@
/*
* Turris Mox rWTM firmware driver
*
* Copyright (C) 2019, 2024 Marek Behún <kabel@kernel.org>
* Copyright (C) 2019, 2024, 2025 Marek Behún <kabel@kernel.org>
*/
#include <crypto/sha2.h>
#include <linux/align.h>
#include <linux/armada-37xx-rwtm-mailbox.h>
#include <linux/cleanup.h>
#include <linux/completion.h>
#include <linux/container_of.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/hw_random.h>
#include <linux/if_ether.h>
#include <linux/key.h>
#include <linux/kobject.h>
#include <linux/mailbox_client.h>
#include <linux/math.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/sizes.h>
#include <linux/sysfs.h>
#include <linux/turris-signing-key.h>
#include <linux/types.h>
#define DRIVER_NAME "turris-mox-rwtm"
@ -37,10 +39,13 @@
* https://gitlab.labs.nic.cz/turris/mox-boot-builder/tree/master/wtmi.
*/
#define MOX_ECC_NUMBER_WORDS 17
#define MOX_ECC_NUMBER_LEN (MOX_ECC_NUMBER_WORDS * sizeof(u32))
#define MOX_ECC_SIGNATURE_WORDS (2 * MOX_ECC_NUMBER_WORDS)
enum {
MOX_ECC_NUM_BITS = 521,
MOX_ECC_NUM_LEN = DIV_ROUND_UP(MOX_ECC_NUM_BITS, 8),
MOX_ECC_NUM_WORDS = DIV_ROUND_UP(MOX_ECC_NUM_BITS, 32),
MOX_ECC_SIG_LEN = 2 * MOX_ECC_NUM_LEN,
MOX_ECC_PUBKEY_LEN = 1 + MOX_ECC_NUM_LEN,
};
#define MBOX_STS_SUCCESS (0 << 30)
#define MBOX_STS_FAIL (1 << 30)
@ -77,10 +82,7 @@ enum mbox_cmd {
* @ram_size: RAM size of the device
* @mac_address1: first MAC address of the device
* @mac_address2: second MAC address of the device
* @has_pubkey: whether board ECDSA public key is present
* @pubkey: board ECDSA public key
* @last_sig: last ECDSA signature generated with board ECDSA private key
* @last_sig_done: whether the last ECDSA signing is complete
*/
struct mox_rwtm {
struct mbox_client mbox_client;
@ -100,18 +102,8 @@ struct mox_rwtm {
int board_version, ram_size;
u8 mac_address1[ETH_ALEN], mac_address2[ETH_ALEN];
bool has_pubkey;
u8 pubkey[135];
#ifdef CONFIG_DEBUG_FS
/*
* Signature process. This is currently done via debugfs, because it
* does not conform to the sysfs standard "one file per attribute".
* It should be rewritten via crypto API once akcipher API is available
* from userspace.
*/
u32 last_sig[MOX_ECC_SIGNATURE_WORDS];
bool last_sig_done;
#ifdef CONFIG_TURRIS_MOX_RWTM_KEYCTL
u8 pubkey[MOX_ECC_PUBKEY_LEN];
#endif
};
@ -120,24 +112,23 @@ static inline struct device *rwtm_dev(struct mox_rwtm *rwtm)
return rwtm->mbox_client.dev;
}
#define MOX_ATTR_RO(name, format, cat) \
#define MOX_ATTR_RO(name, format) \
static ssize_t \
name##_show(struct device *dev, struct device_attribute *a, \
char *buf) \
{ \
struct mox_rwtm *rwtm = dev_get_drvdata(dev); \
if (!rwtm->has_##cat) \
if (!rwtm->has_board_info) \
return -ENODATA; \
return sysfs_emit(buf, format, rwtm->name); \
} \
static DEVICE_ATTR_RO(name)
MOX_ATTR_RO(serial_number, "%016llX\n", board_info);
MOX_ATTR_RO(board_version, "%i\n", board_info);
MOX_ATTR_RO(ram_size, "%i\n", board_info);
MOX_ATTR_RO(mac_address1, "%pM\n", board_info);
MOX_ATTR_RO(mac_address2, "%pM\n", board_info);
MOX_ATTR_RO(pubkey, "%s\n", pubkey);
MOX_ATTR_RO(serial_number, "%016llX\n");
MOX_ATTR_RO(board_version, "%i\n");
MOX_ATTR_RO(ram_size, "%i\n");
MOX_ATTR_RO(mac_address1, "%pM\n");
MOX_ATTR_RO(mac_address2, "%pM\n");
static struct attribute *turris_mox_rwtm_attrs[] = {
&dev_attr_serial_number.attr,
@ -145,7 +136,6 @@ static struct attribute *turris_mox_rwtm_attrs[] = {
&dev_attr_ram_size.attr,
&dev_attr_mac_address1.attr,
&dev_attr_mac_address2.attr,
&dev_attr_pubkey.attr,
NULL
};
ATTRIBUTE_GROUPS(turris_mox_rwtm);
@ -247,24 +237,6 @@ static int mox_get_board_info(struct mox_rwtm *rwtm)
pr_info(" burned RAM size %i MiB\n", rwtm->ram_size);
}
ret = mox_rwtm_exec(rwtm, MBOX_CMD_ECDSA_PUB_KEY, NULL, false);
if (ret == -ENODATA) {
dev_warn(dev, "Board has no public key burned!\n");
} else if (ret == -EOPNOTSUPP) {
dev_notice(dev,
"Firmware does not support the ECDSA_PUB_KEY command\n");
} else if (ret < 0) {
return ret;
} else {
u32 *s = reply->status;
rwtm->has_pubkey = true;
sprintf(rwtm->pubkey,
"%06x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x",
ret, s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7],
s[8], s[9], s[10], s[11], s[12], s[13], s[14], s[15]);
}
return 0;
}
@ -306,127 +278,139 @@ unlock_mutex:
return ret;
}
#ifdef CONFIG_DEBUG_FS
static int rwtm_debug_open(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
#ifdef CONFIG_TURRIS_MOX_RWTM_KEYCTL
return nonseekable_open(inode, file);
static void mox_ecc_number_to_bin(void *dst, const u32 *src)
{
__be32 tmp[MOX_ECC_NUM_WORDS];
cpu_to_be32_array(tmp, src, MOX_ECC_NUM_WORDS);
memcpy(dst, (void *)tmp + 2, MOX_ECC_NUM_LEN);
}
static ssize_t do_sign_read(struct file *file, char __user *buf, size_t len,
loff_t *ppos)
static void mox_ecc_public_key_to_bin(void *dst, u32 src_first,
const u32 *src_rest)
{
struct mox_rwtm *rwtm = file->private_data;
ssize_t ret;
__be32 tmp[MOX_ECC_NUM_WORDS - 1];
u8 *p = dst;
/* only allow one read, of whole signature, from position 0 */
if (*ppos != 0)
return 0;
/* take 3 bytes from the first word */
*p++ = src_first >> 16;
*p++ = src_first >> 8;
*p++ = src_first;
if (len < sizeof(rwtm->last_sig))
return -EINVAL;
if (!rwtm->last_sig_done)
return -ENODATA;
ret = simple_read_from_buffer(buf, len, ppos, rwtm->last_sig,
sizeof(rwtm->last_sig));
rwtm->last_sig_done = false;
return ret;
/* take the rest of the words */
cpu_to_be32_array(tmp, src_rest, MOX_ECC_NUM_WORDS - 1);
memcpy(p, tmp, sizeof(tmp));
}
static ssize_t do_sign_write(struct file *file, const char __user *buf,
size_t len, loff_t *ppos)
static int mox_rwtm_sign(const struct key *key, const void *data, void *signature)
{
struct mox_rwtm *rwtm = file->private_data;
struct armada_37xx_rwtm_tx_msg msg;
loff_t dummy = 0;
ssize_t ret;
struct mox_rwtm *rwtm = dev_get_drvdata(turris_signing_key_get_dev(key));
struct armada_37xx_rwtm_tx_msg msg = {};
u32 offset_r, offset_s;
int ret;
if (len != SHA512_DIGEST_SIZE)
return -EINVAL;
/* if last result is not zero user has not read that information yet */
if (rwtm->last_sig_done)
return -EBUSY;
if (!mutex_trylock(&rwtm->busy))
return -EBUSY;
guard(mutex)(&rwtm->busy);
/*
* Here we have to send:
* 1. Address of the input to sign.
* The input is an array of 17 32-bit words, the first (most
* significat) is 0, the rest 16 words are copied from the SHA-512
* hash given by the user and converted from BE to LE.
* 2. Address of the buffer where ECDSA signature value R shall be
* stored by the rWTM firmware.
* 3. Address of the buffer where ECDSA signature value S shall be
* stored by the rWTM firmware.
* For MBOX_CMD_SIGN command:
* args[0] - must be 1
* args[1] - address of message M to sign; message is a 521-bit number
* args[2] - address where the R part of the signature will be stored
* args[3] - address where the S part of the signature will be stored
*
* M, R and S are 521-bit numbers encoded as seventeen 32-bit words,
* most significat word first.
* Since the message in @data is a sha512 digest, the most significat
* word is always zero.
*/
offset_r = MOX_ECC_NUM_WORDS * sizeof(u32);
offset_s = 2 * MOX_ECC_NUM_WORDS * sizeof(u32);
memset(rwtm->buf, 0, sizeof(u32));
ret = simple_write_to_buffer(rwtm->buf + sizeof(u32),
SHA512_DIGEST_SIZE, &dummy, buf, len);
if (ret < 0)
goto unlock_mutex;
be32_to_cpu_array(rwtm->buf, rwtm->buf, MOX_ECC_NUMBER_WORDS);
memcpy(rwtm->buf + sizeof(u32), data, SHA512_DIGEST_SIZE);
be32_to_cpu_array(rwtm->buf, rwtm->buf, MOX_ECC_NUM_WORDS);
msg.args[0] = 1;
msg.args[1] = rwtm->buf_phys;
msg.args[2] = rwtm->buf_phys + MOX_ECC_NUMBER_LEN;
msg.args[3] = rwtm->buf_phys + 2 * MOX_ECC_NUMBER_LEN;
msg.args[2] = rwtm->buf_phys + offset_r;
msg.args[3] = rwtm->buf_phys + offset_s;
ret = mox_rwtm_exec(rwtm, MBOX_CMD_SIGN, &msg, true);
if (ret < 0)
goto unlock_mutex;
return ret;
/*
* Here we read the R and S values of the ECDSA signature
* computed by the rWTM firmware and convert their words from
* LE to BE.
*/
memcpy(rwtm->last_sig, rwtm->buf + MOX_ECC_NUMBER_LEN,
sizeof(rwtm->last_sig));
cpu_to_be32_array(rwtm->last_sig, rwtm->last_sig,
MOX_ECC_SIGNATURE_WORDS);
rwtm->last_sig_done = true;
/* convert R and S parts of the signature */
mox_ecc_number_to_bin(signature, rwtm->buf + offset_r);
mox_ecc_number_to_bin(signature + MOX_ECC_NUM_LEN, rwtm->buf + offset_s);
mutex_unlock(&rwtm->busy);
return len;
unlock_mutex:
mutex_unlock(&rwtm->busy);
return ret;
return 0;
}
static const struct file_operations do_sign_fops = {
.owner = THIS_MODULE,
.open = rwtm_debug_open,
.read = do_sign_read,
.write = do_sign_write,
static const void *mox_rwtm_get_public_key(const struct key *key)
{
struct mox_rwtm *rwtm = dev_get_drvdata(turris_signing_key_get_dev(key));
return rwtm->pubkey;
}
static const struct turris_signing_key_subtype mox_signing_key_subtype = {
.key_size = MOX_ECC_NUM_BITS,
.data_size = SHA512_DIGEST_SIZE,
.sig_size = MOX_ECC_SIG_LEN,
.public_key_size = MOX_ECC_PUBKEY_LEN,
.hash_algo = "sha512",
.get_public_key = mox_rwtm_get_public_key,
.sign = mox_rwtm_sign,
};
static void rwtm_debugfs_release(void *root)
static int mox_register_signing_key(struct mox_rwtm *rwtm)
{
debugfs_remove_recursive(root);
struct armada_37xx_rwtm_rx_msg *reply = &rwtm->reply;
struct device *dev = rwtm_dev(rwtm);
int ret;
ret = mox_rwtm_exec(rwtm, MBOX_CMD_ECDSA_PUB_KEY, NULL, false);
if (ret == -ENODATA) {
dev_warn(dev, "Board has no public key burned!\n");
} else if (ret == -EOPNOTSUPP) {
dev_notice(dev,
"Firmware does not support the ECDSA_PUB_KEY command\n");
} else if (ret < 0) {
return ret;
} else {
char sn[17] = "unknown";
char desc[46];
if (rwtm->has_board_info)
sprintf(sn, "%016llX", rwtm->serial_number);
sprintf(desc, "Turris MOX SN %s rWTM ECDSA key", sn);
mox_ecc_public_key_to_bin(rwtm->pubkey, ret, reply->status);
ret = devm_turris_signing_key_create(dev,
&mox_signing_key_subtype,
desc);
if (ret)
return dev_err_probe(dev, ret,
"Cannot create signing key\n");
}
return 0;
}
static void rwtm_register_debugfs(struct mox_rwtm *rwtm)
#else /* CONFIG_TURRIS_MOX_RWTM_KEYCTL */
static inline int mox_register_signing_key(struct mox_rwtm *rwtm)
{
struct dentry *root;
root = debugfs_create_dir("turris-mox-rwtm", NULL);
debugfs_create_file_unsafe("do_sign", 0600, root, rwtm, &do_sign_fops);
devm_add_action_or_reset(rwtm_dev(rwtm), rwtm_debugfs_release, root);
return 0;
}
#else
static inline void rwtm_register_debugfs(struct mox_rwtm *rwtm)
{
}
#endif
#endif /* !CONFIG_TURRIS_MOX_RWTM_KEYCTL */
static void rwtm_devm_mbox_release(void *mbox)
{
@ -477,6 +461,10 @@ static int turris_mox_rwtm_probe(struct platform_device *pdev)
if (ret < 0)
dev_warn(dev, "Cannot read board information: %i\n", ret);
ret = mox_register_signing_key(rwtm);
if (ret < 0)
return ret;
ret = check_get_random_support(rwtm);
if (ret < 0) {
dev_notice(dev,
@ -491,8 +479,6 @@ static int turris_mox_rwtm_probe(struct platform_device *pdev)
if (ret)
return dev_err_probe(dev, ret, "Cannot register HWRNG!\n");
rwtm_register_debugfs(rwtm);
dev_info(dev, "HWRNG successfully registered\n");
/*

View File

@ -76,6 +76,23 @@ config TURRIS_OMNIA_MCU_TRNG
Say Y here to add support for the true random number generator
provided by CZ.NIC's Turris Omnia MCU.
config TURRIS_OMNIA_MCU_KEYCTL
bool "Turris Omnia MCU ECDSA message signing"
default y
depends on KEYS
depends on ASYMMETRIC_KEY_TYPE
depends on TURRIS_OMNIA_MCU_GPIO
select TURRIS_SIGNING_KEY
help
Say Y here to add support for ECDSA message signing with board private
key (if available on the MCU). This is exposed via the keyctl()
syscall.
endif # TURRIS_OMNIA_MCU
config TURRIS_SIGNING_KEY
tristate
depends on KEYS
depends on ASYMMETRIC_KEY_TYPE
endif # CZNIC_PLATFORMS

View File

@ -3,6 +3,9 @@
obj-$(CONFIG_TURRIS_OMNIA_MCU) += turris-omnia-mcu.o
turris-omnia-mcu-y := turris-omnia-mcu-base.o
turris-omnia-mcu-$(CONFIG_TURRIS_OMNIA_MCU_GPIO) += turris-omnia-mcu-gpio.o
turris-omnia-mcu-$(CONFIG_TURRIS_OMNIA_MCU_KEYCTL) += turris-omnia-mcu-keyctl.o
turris-omnia-mcu-$(CONFIG_TURRIS_OMNIA_MCU_SYSOFF_WAKEUP) += turris-omnia-mcu-sys-off-wakeup.o
turris-omnia-mcu-$(CONFIG_TURRIS_OMNIA_MCU_TRNG) += turris-omnia-mcu-trng.o
turris-omnia-mcu-$(CONFIG_TURRIS_OMNIA_MCU_WATCHDOG) += turris-omnia-mcu-watchdog.o
obj-$(CONFIG_TURRIS_SIGNING_KEY) += turris-signing-key.o

View File

@ -392,6 +392,10 @@ static int omnia_mcu_probe(struct i2c_client *client)
if (err)
return err;
err = omnia_mcu_register_keyctl(mcu);
if (err)
return err;
return omnia_mcu_register_trng(mcu);
}

View File

@ -13,6 +13,7 @@
#include <linux/device.h>
#include <linux/devm-helpers.h>
#include <linux/errno.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
@ -195,7 +196,7 @@ static const struct omnia_gpio omnia_gpios[64] = {
};
/* mapping from interrupts to indexes of GPIOs in the omnia_gpios array */
const u8 omnia_int_to_gpio_idx[32] = {
static const u8 omnia_int_to_gpio_idx[32] = {
[__bf_shf(OMNIA_INT_CARD_DET)] = 4,
[__bf_shf(OMNIA_INT_MSATA_IND)] = 5,
[__bf_shf(OMNIA_INT_USB30_OVC)] = 6,
@ -1093,3 +1094,21 @@ int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu)
return 0;
}
int omnia_mcu_request_irq(struct omnia_mcu *mcu, u32 spec,
irq_handler_t thread_fn, const char *devname)
{
u8 irq_idx;
int irq;
if (!spec)
return -EINVAL;
irq_idx = omnia_int_to_gpio_idx[ffs(spec) - 1];
irq = gpiod_to_irq(gpio_device_get_desc(mcu->gc.gpiodev, irq_idx));
if (irq < 0)
return irq;
return devm_request_threaded_irq(&mcu->client->dev, irq, NULL,
thread_fn, IRQF_ONESHOT, devname, mcu);
}

View File

@ -0,0 +1,162 @@
// 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;
}

View File

@ -5,12 +5,9 @@
* 2024 by Marek Behún <kabel@kernel.org>
*/
#include <linux/bitfield.h>
#include <linux/completion.h>
#include <linux/container_of.h>
#include <linux/errno.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>
#include <linux/hw_random.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
@ -62,17 +59,12 @@ static int omnia_trng_read(struct hwrng *rng, void *data, size_t max, bool wait)
int omnia_mcu_register_trng(struct omnia_mcu *mcu)
{
struct device *dev = &mcu->client->dev;
u8 irq_idx, dummy;
int irq, err;
u8 dummy;
int err;
if (!(mcu->features & OMNIA_FEAT_TRNG))
return 0;
irq_idx = omnia_int_to_gpio_idx[__bf_shf(OMNIA_INT_TRNG)];
irq = gpiod_to_irq(gpio_device_get_desc(mcu->gc.gpiodev, irq_idx));
if (irq < 0)
return dev_err_probe(dev, irq, "Cannot get TRNG IRQ\n");
/*
* If someone else cleared the TRNG interrupt but did not read the
* entropy, a new interrupt won't be generated, and entropy collection
@ -86,9 +78,8 @@ int omnia_mcu_register_trng(struct omnia_mcu *mcu)
init_completion(&mcu->trng_entropy_ready);
err = devm_request_threaded_irq(dev, irq, NULL, omnia_trng_irq_handler,
IRQF_ONESHOT, "turris-omnia-mcu-trng",
mcu);
err = omnia_mcu_request_irq(mcu, OMNIA_INT_TRNG, omnia_trng_irq_handler,
"turris-omnia-mcu-trng");
if (err)
return dev_err_probe(dev, err, "Cannot request TRNG IRQ\n");

View File

@ -12,11 +12,17 @@
#include <linux/gpio/driver.h>
#include <linux/hw_random.h>
#include <linux/if_ether.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include <linux/watchdog.h>
#include <linux/workqueue.h>
enum {
OMNIA_MCU_CRYPTO_PUBLIC_KEY_LEN = 1 + 32,
OMNIA_MCU_CRYPTO_SIGNATURE_LEN = 64,
};
struct i2c_client;
struct rtc_device;
@ -55,6 +61,12 @@ struct rtc_device;
* @wdt: watchdog driver structure
* @trng: RNG driver structure
* @trng_entropy_ready: RNG entropy ready completion
* @msg_signed: message signed completion
* @sign_lock: mutex to protect message signing state
* @sign_requested: flag indicating that message signing was requested but not completed
* @sign_err: message signing error number, filled in interrupt handler
* @signature: message signing signature, filled in interrupt handler
* @board_public_key: board public key, if stored in MCU
*/
struct omnia_mcu {
struct i2c_client *client;
@ -88,12 +100,22 @@ struct omnia_mcu {
struct hwrng trng;
struct completion trng_entropy_ready;
#endif
#ifdef CONFIG_TURRIS_OMNIA_MCU_KEYCTL
struct completion msg_signed;
struct mutex sign_lock;
bool sign_requested;
int sign_err;
u8 signature[OMNIA_MCU_CRYPTO_SIGNATURE_LEN];
u8 board_public_key[OMNIA_MCU_CRYPTO_PUBLIC_KEY_LEN];
#endif
};
#ifdef CONFIG_TURRIS_OMNIA_MCU_GPIO
extern const u8 omnia_int_to_gpio_idx[32];
extern const struct attribute_group omnia_mcu_gpio_group;
int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu);
int omnia_mcu_request_irq(struct omnia_mcu *mcu, u32 spec,
irq_handler_t thread_fn, const char *devname);
#else
static inline int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu)
{
@ -101,6 +123,15 @@ static inline int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu)
}
#endif
#ifdef CONFIG_TURRIS_OMNIA_MCU_KEYCTL
int omnia_mcu_register_keyctl(struct omnia_mcu *mcu);
#else
static inline int omnia_mcu_register_keyctl(struct omnia_mcu *mcu)
{
return 0;
}
#endif
#ifdef CONFIG_TURRIS_OMNIA_MCU_SYSOFF_WAKEUP
extern const struct attribute_group omnia_mcu_poweroff_group;
int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu);

View File

@ -0,0 +1,193 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Some of CZ.NIC's Turris devices support signing messages with a per-device unique asymmetric
* cryptographic key that was burned into the device at manufacture.
*
* This helper module exposes this message signing ability via the keyctl() syscall. Upon load, it
* creates the `.turris-signing-keys` keyring. A device-specific driver then has to create a signing
* key by calling devm_turris_signing_key_create().
*
* 2025 by Marek Behún <kabel@kernel.org>
*/
#include <linux/device.h>
#include <linux/err.h>
#include <linux/key-type.h>
#include <linux/key.h>
#include <linux/keyctl.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/turris-signing-key.h>
static int turris_signing_key_instantiate(struct key *key,
struct key_preparsed_payload *payload)
{
return 0;
}
static void turris_signing_key_describe(const struct key *key, struct seq_file *m)
{
const struct turris_signing_key_subtype *subtype = dereference_key_rcu(key);
if (!subtype)
return;
seq_printf(m, "%s: %*phN", key->description, subtype->public_key_size,
subtype->get_public_key(key));
}
static long turris_signing_key_read(const struct key *key, char *buffer, size_t buflen)
{
const struct turris_signing_key_subtype *subtype = dereference_key_rcu(key);
if (!subtype)
return -EIO;
if (buffer) {
if (buflen > subtype->public_key_size)
buflen = subtype->public_key_size;
memcpy(buffer, subtype->get_public_key(key), subtype->public_key_size);
}
return subtype->public_key_size;
}
static bool turris_signing_key_asym_valid_params(const struct turris_signing_key_subtype *subtype,
const struct kernel_pkey_params *params)
{
if (params->encoding && strcmp(params->encoding, "raw"))
return false;
if (params->hash_algo && strcmp(params->hash_algo, subtype->hash_algo))
return false;
return true;
}
static int turris_signing_key_asym_query(const struct kernel_pkey_params *params,
struct kernel_pkey_query *info)
{
const struct turris_signing_key_subtype *subtype = dereference_key_rcu(params->key);
if (!subtype)
return -EIO;
if (!turris_signing_key_asym_valid_params(subtype, params))
return -EINVAL;
info->supported_ops = KEYCTL_SUPPORTS_SIGN;
info->key_size = subtype->key_size;
info->max_data_size = subtype->data_size;
info->max_sig_size = subtype->sig_size;
info->max_enc_size = 0;
info->max_dec_size = 0;
return 0;
}
static int turris_signing_key_asym_eds_op(struct kernel_pkey_params *params,
const void *in, void *out)
{
const struct turris_signing_key_subtype *subtype = dereference_key_rcu(params->key);
int err;
if (!subtype)
return -EIO;
if (!turris_signing_key_asym_valid_params(subtype, params))
return -EINVAL;
if (params->op != kernel_pkey_sign)
return -EOPNOTSUPP;
if (params->in_len != subtype->data_size || params->out_len != subtype->sig_size)
return -EINVAL;
err = subtype->sign(params->key, in, out);
if (err)
return err;
return subtype->sig_size;
}
static struct key_type turris_signing_key_type = {
.name = "turris-signing-key",
.instantiate = turris_signing_key_instantiate,
.describe = turris_signing_key_describe,
.read = turris_signing_key_read,
.asym_query = turris_signing_key_asym_query,
.asym_eds_op = turris_signing_key_asym_eds_op,
};
static struct key *turris_signing_keyring;
static void turris_signing_key_release(void *key)
{
key_unlink(turris_signing_keyring, key);
key_put(key);
}
int
devm_turris_signing_key_create(struct device *dev, const struct turris_signing_key_subtype *subtype,
const char *desc)
{
struct key *key;
key_ref_t kref;
kref = key_create(make_key_ref(turris_signing_keyring, true),
turris_signing_key_type.name, desc, NULL, 0,
(KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW | KEY_USR_READ |
KEY_USR_SEARCH,
KEY_ALLOC_BUILT_IN | KEY_ALLOC_SET_KEEP | KEY_ALLOC_NOT_IN_QUOTA);
if (IS_ERR(kref))
return PTR_ERR(kref);
key = key_ref_to_ptr(kref);
key->payload.data[1] = dev;
rcu_assign_keypointer(key, subtype);
return devm_add_action_or_reset(dev, turris_signing_key_release, key);
}
EXPORT_SYMBOL_GPL(devm_turris_signing_key_create);
static int turris_signing_key_init(void)
{
int err;
err = register_key_type(&turris_signing_key_type);
if (err)
return err;
turris_signing_keyring = keyring_alloc(".turris-signing-keys",
GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, current_cred(),
(KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW |
KEY_USR_READ | KEY_USR_SEARCH,
KEY_ALLOC_BUILT_IN | KEY_ALLOC_SET_KEEP |
KEY_ALLOC_NOT_IN_QUOTA,
NULL, NULL);
if (IS_ERR(turris_signing_keyring)) {
pr_err("Cannot allocate Turris keyring\n");
unregister_key_type(&turris_signing_key_type);
return PTR_ERR(turris_signing_keyring);
}
return 0;
}
module_init(turris_signing_key_init);
static void turris_signing_key_exit(void)
{
key_put(turris_signing_keyring);
unregister_key_type(&turris_signing_key_type);
}
module_exit(turris_signing_key_exit);
MODULE_AUTHOR("Marek Behun <kabel@kernel.org>");
MODULE_DESCRIPTION("CZ.NIC's Turris signing key helper");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,35 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* 2025 by Marek Behún <kabel@kernel.org>
*/
#ifndef __TURRIS_SIGNING_KEY_H
#define __TURRIS_SIGNING_KEY_H
#include <linux/key.h>
#include <linux/types.h>
struct device;
#ifdef CONFIG_KEYS
struct turris_signing_key_subtype {
u16 key_size;
u8 data_size;
u8 sig_size;
u8 public_key_size;
const char *hash_algo;
const void *(*get_public_key)(const struct key *key);
int (*sign)(const struct key *key, const void *msg, void *signature);
};
static inline struct device *turris_signing_key_get_dev(const struct key *key)
{
return key->payload.data[1];
}
int
devm_turris_signing_key_create(struct device *dev, const struct turris_signing_key_subtype *subtype,
const char *desc);
#endif
#endif /* __TURRIS_SIGNING_KEY_H */