diff options
-rw-r--r-- | Documentation/ABI/testing/debugfs-turris-mox-rwtm | 14 | ||||
-rw-r--r-- | Documentation/ABI/testing/sysfs-firmware-turris-mox-rwtm | 9 | ||||
-rw-r--r-- | MAINTAINERS | 1 | ||||
-rw-r--r-- | drivers/firmware/Kconfig | 17 | ||||
-rw-r--r-- | drivers/firmware/turris-mox-rwtm.c | 260 | ||||
-rw-r--r-- | drivers/platform/cznic/Kconfig | 17 | ||||
-rw-r--r-- | drivers/platform/cznic/Makefile | 3 | ||||
-rw-r--r-- | drivers/platform/cznic/turris-omnia-mcu-base.c | 4 | ||||
-rw-r--r-- | drivers/platform/cznic/turris-omnia-mcu-gpio.c | 21 | ||||
-rw-r--r-- | drivers/platform/cznic/turris-omnia-mcu-keyctl.c | 162 | ||||
-rw-r--r-- | drivers/platform/cznic/turris-omnia-mcu-trng.c | 17 | ||||
-rw-r--r-- | drivers/platform/cznic/turris-omnia-mcu.h | 33 | ||||
-rw-r--r-- | drivers/platform/cznic/turris-signing-key.c | 193 | ||||
-rw-r--r-- | include/linux/turris-signing-key.h | 35 |
14 files changed, 611 insertions, 175 deletions
diff --git a/Documentation/ABI/testing/debugfs-turris-mox-rwtm b/Documentation/ABI/testing/debugfs-turris-mox-rwtm deleted file mode 100644 index 813987d5de4e..000000000000 --- a/Documentation/ABI/testing/debugfs-turris-mox-rwtm +++ /dev/null @@ -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. - ======= =========================================================== diff --git a/Documentation/ABI/testing/sysfs-firmware-turris-mox-rwtm b/Documentation/ABI/testing/sysfs-firmware-turris-mox-rwtm index ea5e5b489bc7..26741cb84504 100644 --- a/Documentation/ABI/testing/sysfs-firmware-turris-mox-rwtm +++ b/Documentation/ABI/testing/sysfs-firmware-turris-mox-rwtm @@ -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 diff --git a/MAINTAINERS b/MAINTAINERS index 7e194b92e1c7..4a02888b8178 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -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> diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index aadc395ee168..307d6bddcd08 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -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" diff --git a/drivers/firmware/turris-mox-rwtm.c b/drivers/firmware/turris-mox-rwtm.c index 47fe6261f5a3..1eac9948148f 100644 --- a/drivers/firmware/turris-mox-rwtm.c +++ b/drivers/firmware/turris-mox-rwtm.c @@ -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 ssize_t do_sign_read(struct file *file, char __user *buf, size_t len, - loff_t *ppos) +static void mox_ecc_number_to_bin(void *dst, const u32 *src) { - struct mox_rwtm *rwtm = file->private_data; - ssize_t ret; + __be32 tmp[MOX_ECC_NUM_WORDS]; - /* only allow one read, of whole signature, from position 0 */ - if (*ppos != 0) - return 0; + cpu_to_be32_array(tmp, src, MOX_ECC_NUM_WORDS); - if (len < sizeof(rwtm->last_sig)) - return -EINVAL; + memcpy(dst, (void *)tmp + 2, MOX_ECC_NUM_LEN); +} - if (!rwtm->last_sig_done) - return -ENODATA; +static void mox_ecc_public_key_to_bin(void *dst, u32 src_first, + const u32 *src_rest) +{ + __be32 tmp[MOX_ECC_NUM_WORDS - 1]; + u8 *p = dst; - ret = simple_read_from_buffer(buf, len, ppos, rwtm->last_sig, - sizeof(rwtm->last_sig)); - rwtm->last_sig_done = false; + /* take 3 bytes from the first word */ + *p++ = src_first >> 16; + *p++ = src_first >> 8; + *p++ = src_first; - 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; - - 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; + 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 (!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 void rwtm_debugfs_release(void *root) +static const void *mox_rwtm_get_public_key(const struct key *key) { - debugfs_remove_recursive(root); + struct mox_rwtm *rwtm = dev_get_drvdata(turris_signing_key_get_dev(key)); + + return rwtm->pubkey; } -static void rwtm_register_debugfs(struct mox_rwtm *rwtm) +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 int mox_register_signing_key(struct mox_rwtm *rwtm) { - struct dentry *root; + struct armada_37xx_rwtm_rx_msg *reply = &rwtm->reply; + struct device *dev = rwtm_dev(rwtm); + int ret; - root = debugfs_create_dir("turris-mox-rwtm", NULL); + 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); - debugfs_create_file_unsafe("do_sign", 0600, root, rwtm, &do_sign_fops); + mox_ecc_public_key_to_bin(rwtm->pubkey, ret, reply->status); - devm_add_action_or_reset(rwtm_dev(rwtm), rwtm_debugfs_release, root); + 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; } -#else -static inline 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) { + return 0; } -#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"); /* diff --git a/drivers/platform/cznic/Kconfig b/drivers/platform/cznic/Kconfig index 13e37b49d9d0..61cff5f7e02e 100644 --- a/drivers/platform/cznic/Kconfig +++ b/drivers/platform/cznic/Kconfig @@ -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 diff --git a/drivers/platform/cznic/Makefile b/drivers/platform/cznic/Makefile index ce6d997f34d6..ccad7bec82e1 100644 --- a/drivers/platform/cznic/Makefile +++ b/drivers/platform/cznic/Makefile @@ -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 diff --git a/drivers/platform/cznic/turris-omnia-mcu-base.c b/drivers/platform/cznic/turris-omnia-mcu-base.c index 770e680b96f9..e8fc0d7b3343 100644 --- a/drivers/platform/cznic/turris-omnia-mcu-base.c +++ b/drivers/platform/cznic/turris-omnia-mcu-base.c @@ -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); } diff --git a/drivers/platform/cznic/turris-omnia-mcu-gpio.c b/drivers/platform/cznic/turris-omnia-mcu-gpio.c index 5f35f7c5d5d7..c2df24ea8686 100644 --- a/drivers/platform/cznic/turris-omnia-mcu-gpio.c +++ b/drivers/platform/cznic/turris-omnia-mcu-gpio.c @@ -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); +} diff --git a/drivers/platform/cznic/turris-omnia-mcu-keyctl.c b/drivers/platform/cznic/turris-omnia-mcu-keyctl.c new file mode 100644 index 000000000000..dc40f942f082 --- /dev/null +++ b/drivers/platform/cznic/turris-omnia-mcu-keyctl.c @@ -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; +} diff --git a/drivers/platform/cznic/turris-omnia-mcu-trng.c b/drivers/platform/cznic/turris-omnia-mcu-trng.c index 9a1d9292dc9a..e3826959e6de 100644 --- a/drivers/platform/cznic/turris-omnia-mcu-trng.c +++ b/drivers/platform/cznic/turris-omnia-mcu-trng.c @@ -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"); diff --git a/drivers/platform/cznic/turris-omnia-mcu.h b/drivers/platform/cznic/turris-omnia-mcu.h index 088541be3f4c..8473a3031917 100644 --- a/drivers/platform/cznic/turris-omnia-mcu.h +++ b/drivers/platform/cznic/turris-omnia-mcu.h @@ -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); diff --git a/drivers/platform/cznic/turris-signing-key.c b/drivers/platform/cznic/turris-signing-key.c new file mode 100644 index 000000000000..3827178565e2 --- /dev/null +++ b/drivers/platform/cznic/turris-signing-key.c @@ -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"); diff --git a/include/linux/turris-signing-key.h b/include/linux/turris-signing-key.h new file mode 100644 index 000000000000..8a435b73c3a9 --- /dev/null +++ b/include/linux/turris-signing-key.h @@ -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 */ |