platform: cznic: Add keyctl helpers for Turris platform

Some Turris devices support signing messages with a per-device unique
asymmetric key that was created on the device at manufacture time.

Add helper module that helps to expose this ability via the keyctl()
syscall.

A device-specific driver can register a signing key by calling
devm_turris_signing_key_create().

Both the `.turris-signing-keys` keyring and the signing key are created
with only the VIEW, READ and SEARCH permissions for userspace - it is
impossible to link / unlink / move them, set their attributes, or unlink
the keyring from userspace.

Signed-off-by: Marek Behún <kabel@kernel.org>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
Marek Behún 2025-02-04 14:14:12 +01:00 committed by Arnd Bergmann
parent ee7f8ed729
commit 0b28b7080e
No known key found for this signature in database
GPG Key ID: 60AB47FFC9095227
5 changed files with 233 additions and 0 deletions

View File

@ -2441,6 +2441,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

@ -77,4 +77,9 @@ config TURRIS_OMNIA_MCU_TRNG
endif # TURRIS_OMNIA_MCU
config TURRIS_SIGNING_KEY
tristate
depends on KEYS
depends on ASYMMETRIC_KEY_TYPE
endif # CZNIC_PLATFORMS

View File

@ -6,3 +6,5 @@ turris-omnia-mcu-$(CONFIG_TURRIS_OMNIA_MCU_GPIO) += turris-omnia-mcu-gpio.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

@ -0,0 +1,192 @@
// 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 *, struct key_preparsed_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,33 @@
/* 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;
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 /* __TURRIS_SIGNING_KEY_H */