The PKWM trusted source currently uses a single default wrapping key created during initialization. However, PKWM also supports user-created wrapping keys for better isolation between trusted keys.
Expose wrapping key lifecycle operations through sysfs to allow users to create and manage their own wrapping keys. Create a sysfs interface at /sys/firmware/plpks/wrapkey with the following attributes: create - Create a new wrapping key with a user-provided label delete - Delete a revoked wrapping key by label revoke - Revoke a wrapping key by label unrevoke - Unrevoke a previously revoked wrapping key by label list_active - List labels of all active (non-revoked) wrapping keys list_revoked - List labels of all revoked wrapping keys User-provided labels are validated to contain only alphanumeric characters. Wrapping key lifecycle constraints: - A wrapping key must be revoked before it can be deleted - Revoked wrapping keys cannot be used for wrap operations until unrevoked - The default wrapping key cannot be revoked or deleted; it always appears in list_active The wrapping keys themselves are confidential objects stored in PLPKS. Only the user-provided labels are exposed to userspace for identification and management purposes. These user-created wrapping keys can be used in seal/unseal operations via the wrapping_key=<label> option. Signed-off-by: Srish Srinivasan <[email protected]> --- .../ABI/testing/sysfs-firmware-plpks | 106 +++++ arch/powerpc/include/asm/plpks.h | 9 + arch/powerpc/platforms/pseries/Kconfig | 13 + arch/powerpc/platforms/pseries/Makefile | 1 + arch/powerpc/platforms/pseries/plpks-sysfs.c | 11 + .../platforms/pseries/plpks-wrapkey-sysfs.c | 407 ++++++++++++++++++ arch/powerpc/platforms/pseries/plpks.c | 1 - 7 files changed, 547 insertions(+), 1 deletion(-) create mode 100644 arch/powerpc/platforms/pseries/plpks-wrapkey-sysfs.c diff --git a/Documentation/ABI/testing/sysfs-firmware-plpks b/Documentation/ABI/testing/sysfs-firmware-plpks index cba061e4eee2..ebcdef17a8a8 100644 --- a/Documentation/ABI/testing/sysfs-firmware-plpks +++ b/Documentation/ABI/testing/sysfs-firmware-plpks @@ -56,3 +56,109 @@ Description: Bitmask of the wrapping features indicating the wrapping algorithms that are supported for the H_PKS_WRAP_OBJECT requests , represented as a 8 byte hexadecimal ASCII string. Consult the hypervisor documentation for what these flags mean. + +What: /sys/firmware/plpks/wrapkey +Date: August 2026 +Contact: Srish Srinivasan <[email protected]> +Description: This directory provides the interface for creating and managing + user-created PKWM wrapping keys. + + The directory is present only when CONFIG_PLPKS_WRAPKEY_SYSFS is + enabled and the system supports the H_PKS_{UN}REVOKE_OBJECT + interfaces. + + Only alphanumeric characters are accepted in wrapping key + labels. The wrapping keys themselves are confidential PLPKS + objects and are not exposed to userspace. Only their labels are + exposed for identification and management. + + Each write is processed as an independent operation and accepts + exactly one wrapping key label. The resulting state can be + verified through list_active or list_revoked, as appropriate. + + The default wrapping key cannot be revoked or deleted. + +What: /sys/firmware/plpks/wrapkey/create +Date: August 2026 +Contact: Srish Srinivasan <[email protected]> +Description: (WO) Create a wrapping key with the specified label. + + On success, the label appears in list_active. + + For example, to create a wrapping key labelled "wk1": + + echo -n wk1 > /sys/firmware/plpks/wrapkey/create + + or: + + printf '%s' 'wk1' > /sys/firmware/plpks/wrapkey/create + +What: /sys/firmware/plpks/wrapkey/delete +Date: August 2026 +Contact: Srish Srinivasan <[email protected]> +Description: (WO) Delete a revoked wrapping key identified by the specified + label. A wrapping key must be revoked before it can be deleted. + + On success, the label no longer appears in list_revoked. + + For example, to delete a revoked wrapping key labelled "wk1": + + echo -n wk1 > /sys/firmware/plpks/wrapkey/delete + + or: + + printf '%s' 'wk1' > /sys/firmware/plpks/wrapkey/delete + +What: /sys/firmware/plpks/wrapkey/revoke +Date: August 2026 +Contact: Srish Srinivasan <[email protected]> +Description: (WO) Revoke a wrapping key identified by the specified label. A + revoked wrapping key cannot be used for seal or unseal + operations until it is unrevoked. + + On success, the label appears in list_revoked and no longer + appears in list_active. + + For example, to revoke a wrapping key labelled "wk1": + + echo -n wk1 > /sys/firmware/plpks/wrapkey/revoke + + or: + + printf '%s' 'wk1' > /sys/firmware/plpks/wrapkey/revoke + +What: /sys/firmware/plpks/wrapkey/unrevoke +Date: August 2026 +Contact: Srish Srinivasan <[email protected]> +Description: (WO) Unrevoke a revoked wrapping key identified by the specified + label. + + On success, the label appears in list_active and no longer + appears in list_revoked. + + For example, to unrevoke a revoked wrapping key labelled "wk1": + + echo -n wk1 > /sys/firmware/plpks/wrapkey/unrevoke + + or: + + printf '%s' 'wk1' > /sys/firmware/plpks/wrapkey/unrevoke + +What: /sys/firmware/plpks/wrapkey/list_active +Date: August 2026 +Contact: Srish Srinivasan <[email protected]> +Description: (RO) List the labels of active, non-revoked wrapping keys. The + default wrapping key is always included in this list. + + For example: + + cat /sys/firmware/plpks/wrapkey/list_active + +What: /sys/firmware/plpks/wrapkey/list_revoked +Date: August 2026 +Contact: Srish Srinivasan <[email protected]> +Description: (RO) List the labels of revoked wrapping keys. + + For example: + + cat /sys/firmware/plpks/wrapkey/list_revoked diff --git a/arch/powerpc/include/asm/plpks.h b/arch/powerpc/include/asm/plpks.h index badb07afe749..420945aeeb0e 100644 --- a/arch/powerpc/include/asm/plpks.h +++ b/arch/powerpc/include/asm/plpks.h @@ -56,6 +56,8 @@ // Component for a PKWM wrapping key #define PLPKS_WRAPKEY_COMPONENT "PLPKSWR" +#define PLPKS_OBJLABEL_LEN_FIELD_SIZE 2 + struct plpks_var { char *component; u8 *name; @@ -144,6 +146,9 @@ int plpks_get_object_labels(u8 **output_buf, u64 *output_len, bool plpks_revoke_is_supported(void); +int plpks_init_child_kobj(struct kobject *kobj, const struct kobj_type *ktype, + const char *name); + #else // CONFIG_PSERIES_PLPKS static inline bool plpks_is_available(void) { return false; } static inline u16 plpks_get_passwordlen(void) { BUILD_BUG(); } @@ -151,6 +156,10 @@ static inline void plpks_early_init_devtree(void) { } static inline int plpks_populate_fdt(void *fdt) { BUILD_BUG(); } static inline int plpks_config_create_softlink(struct kobject *from) { return 0; } +static inline int plpks_init_child_kobj(struct kobject *kobj, + const struct kobj_type *ktype, + const char *name) + { return 0; } #endif // CONFIG_PSERIES_PLPKS #endif // _ASM_POWERPC_PLPKS_H diff --git a/arch/powerpc/platforms/pseries/Kconfig b/arch/powerpc/platforms/pseries/Kconfig index 74910ce3a541..77afd9fd6c90 100644 --- a/arch/powerpc/platforms/pseries/Kconfig +++ b/arch/powerpc/platforms/pseries/Kconfig @@ -216,3 +216,16 @@ config PPC_SVM those guests. If unsure, say "N". + +config PLPKS_WRAPKEY_SYSFS + bool "Enable sysfs based user interface for managing PKWM wrapping keys" + default n + depends on PSERIES_PLPKS + depends on SYSFS + depends on TRUSTED_KEYS_PKWM + help + The PKWM trusted source has support for user-created wrapping keys + that are managed and controlled by OS. These wrapping keys can be + created, revoked, unrevoked, deleted, and their labels can be viewed + via the sysfs. Say Y if you have PLPKS enabled and want to setup PKWM + as the trusted source. diff --git a/arch/powerpc/platforms/pseries/Makefile b/arch/powerpc/platforms/pseries/Makefile index 3ced289a675b..c604be40f546 100644 --- a/arch/powerpc/platforms/pseries/Makefile +++ b/arch/powerpc/platforms/pseries/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_FA_DUMP) += rtas-fadump.o obj-$(CONFIG_PSERIES_PLPKS) += plpks.o plpks-sysfs.o obj-$(CONFIG_PPC_SECURE_BOOT) += plpks-secvar.o obj-$(CONFIG_PSERIES_PLPKS_SED) += plpks_sed_ops.o +obj-$(CONFIG_PLPKS_WRAPKEY_SYSFS) += plpks-wrapkey-sysfs.o obj-$(CONFIG_SUSPEND) += suspend.o obj-$(CONFIG_PPC_VAS) += vas.o vas-sysfs.o diff --git a/arch/powerpc/platforms/pseries/plpks-sysfs.c b/arch/powerpc/platforms/pseries/plpks-sysfs.c index f2436229f323..0ca13bb6405f 100644 --- a/arch/powerpc/platforms/pseries/plpks-sysfs.c +++ b/arch/powerpc/platforms/pseries/plpks-sysfs.c @@ -55,6 +55,17 @@ static umode_t plpks_config_attr_is_visible(struct kobject *kobj, return attr->mode; } +int plpks_init_child_kobj(struct kobject *kobj, const struct kobj_type *ktype, + const char *name) +{ + kobject_init(kobj, ktype); + + if (!plpks_kobj) + return -ENODEV; + + return kobject_add(kobj, plpks_kobj, "%s", name); +} + int plpks_config_create_softlink(struct kobject *from) { if (!plpks_config_kobj) diff --git a/arch/powerpc/platforms/pseries/plpks-wrapkey-sysfs.c b/arch/powerpc/platforms/pseries/plpks-wrapkey-sysfs.c new file mode 100644 index 000000000000..7493d3f58546 --- /dev/null +++ b/arch/powerpc/platforms/pseries/plpks-wrapkey-sysfs.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2025 IBM Corporation <[email protected]> + * + * This code exposes wrapping key management options to the user via the + * sysfs + */ + +#define pr_fmt(fmt) "wrapkey-sysfs: " fmt +#define PLPKS_WRAPKEY_REVOKED 1 +#define PLPKS_WRAPKEY_UNREVOKED 0 + +#include <linux/slab.h> +#include <linux/compat.h> +#include <linux/string.h> +#include <linux/of.h> +#include <linux/ctype.h> +#include <linux/unaligned.h> +#include <asm/plpks.h> + +static struct kobject *wrapkey_kobj; + +static bool is_wrapkey_label_alnum(const char *key_label, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) { + if (!isalnum((unsigned char)key_label[i])) { + pr_err("key label <%*pE> is not alphanumeric\n", + (int)len, key_label); + return false; + } + } + + return true; +} + +static ssize_t list_key_labels(char *buf, u8 *obj_labels_buf, + u64 obj_labels_count, int revoked) +{ + struct plpks_var var = {0}; + u8 *obj_labels_buf_ptr, *comp_prefix; + u8 *key_label_ptr; + u16 obj_label_len; + int len = 0; + int is_revoked; + u64 i; + + var.os = PLPKS_VAR_LINUX; + var.component = PLPKS_WRAPKEY_COMPONENT; + obj_labels_buf_ptr = obj_labels_buf; + + for (i = 0; i < obj_labels_count; ++i) { + obj_label_len = get_unaligned_be16(obj_labels_buf_ptr); + comp_prefix = obj_labels_buf_ptr + + PLPKS_OBJLABEL_LEN_FIELD_SIZE; + + var.namelen = obj_label_len - PLPKS_MAX_LABEL_ATTR_SIZE; + var.name = kzalloc(var.namelen + 1, GFP_KERNEL); + + if (!var.name) + return -ENOMEM; + + key_label_ptr = comp_prefix + PLPKS_MAX_LABEL_ATTR_SIZE; + memcpy(var.name, key_label_ptr, var.namelen); + + is_revoked = plpks_is_wrapping_key_revoked(&var); + if (is_revoked == revoked) + len += sysfs_emit_at(buf, len, "<%s>\n", + (char *)var.name); + else if (is_revoked < 0) + pr_warn("Failed to get revocation status for <%s>\n", + (char *)var.name); + + kfree(var.name); + obj_labels_buf_ptr = key_label_ptr + var.namelen; + } + + return len; +} + +static ssize_t list_active_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + u8 *obj_labels_buf; + u64 obj_labels_count = 0; + int rc; + + rc = plpks_get_object_labels(&obj_labels_buf, &obj_labels_count, + PLPKS_WRAPKEY_COMPONENT); + if (rc) { + pr_err("Retrieving object labels failed. rc=%d\n", rc); + goto out; + } + + rc = list_key_labels(buf, obj_labels_buf, obj_labels_count, + PLPKS_WRAPKEY_UNREVOKED); + +out: + kfree(obj_labels_buf); + return rc; +} + +static ssize_t list_revoked_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + u8 *obj_labels_buf; + u64 obj_labels_count = 0; + int rc; + + rc = plpks_get_object_labels(&obj_labels_buf, &obj_labels_count, + PLPKS_WRAPKEY_COMPONENT); + if (rc) { + pr_err("Retrieving object labels failed. rc=%d\n", rc); + goto out; + } + + rc = list_key_labels(buf, obj_labels_buf, obj_labels_count, + PLPKS_WRAPKEY_REVOKED); + +out: + kfree(obj_labels_buf); + return rc; +} + +static ssize_t create_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct plpks_var var = {0}; + int rc; + + if (!capable(CAP_SYS_ADMIN)) { + rc = -EPERM; + goto out; + } + + if (!count) { + rc = -EINVAL; + goto out; + } + + if (count > PLPKS_MAX_NAME_SIZE) { + rc = -ENAMETOOLONG; + goto out; + } + + if (!strcmp(buf, PLPKS_DEFAULT_WRAPKEY_LABEL)) { + pr_warn("<%s> creation is restricted to pkwm init\n", + PLPKS_DEFAULT_WRAPKEY_LABEL); + rc = -EPERM; + goto out; + } + + if (!is_wrapkey_label_alnum(buf, count)) { + rc = -EINVAL; + goto out; + } + + var.name = kstrndup(buf, count, GFP_KERNEL); + if (!var.name) { + rc = -ENOMEM; + goto out; + } + + var.namelen = count; + var.policy = PLPKS_WRAPPINGKEY; + var.os = PLPKS_VAR_LINUX; + var.component = PLPKS_WRAPKEY_COMPONENT; + + rc = plpks_gen_wrapping_key(&var); + if (rc) { + pr_err("creation of wrapping key <%s> failed. rc = %d\n", + (char *)var.name, rc); + goto out; + } + + rc = count; +out: + kfree(var.name); + return rc; +} + +static ssize_t revoke_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct plpks_var var = {0}; + int rc; + + if (!capable(CAP_SYS_ADMIN)) { + rc = -EPERM; + goto out; + } + + if (!count) { + rc = -EINVAL; + goto out; + } + + if (count > PLPKS_MAX_NAME_SIZE) { + rc = -ENAMETOOLONG; + goto out; + } + + if (!strcmp(buf, PLPKS_DEFAULT_WRAPKEY_LABEL)) { + pr_warn("<%s> must not be revoked\n", + PLPKS_DEFAULT_WRAPKEY_LABEL); + rc = -EPERM; + goto out; + } + + if (!is_wrapkey_label_alnum(buf, count)) { + rc = -EINVAL; + goto out; + } + + var.name = kstrndup(buf, count, GFP_KERNEL); + if (!var.name) { + rc = -ENOMEM; + goto out; + } + + var.namelen = count; + var.os = PLPKS_VAR_LINUX; + var.component = PLPKS_WRAPKEY_COMPONENT; + + rc = plpks_revoke_wrapping_key(&var); + + if (rc) { + pr_err("revocation of wrapping key <%s> failed. rc = %d\n", + (char *)var.name, rc); + goto out; + } + + rc = count; +out: + kfree(var.name); + return rc; +} + +static ssize_t unrevoke_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct plpks_var var = {0}; + int rc; + + if (!capable(CAP_SYS_ADMIN)) { + rc = -EPERM; + goto out; + } + + if (!count) { + rc = -EINVAL; + goto out; + } + + if (count > PLPKS_MAX_NAME_SIZE) { + rc = -ENAMETOOLONG; + goto out; + } + + if (!strcmp(buf, PLPKS_DEFAULT_WRAPKEY_LABEL)) { + pr_warn("unrevoke on <%s> is invalid\n", + PLPKS_DEFAULT_WRAPKEY_LABEL); + rc = -EINVAL; + goto out; + } + + if (!is_wrapkey_label_alnum(buf, count)) { + rc = -EINVAL; + goto out; + } + + var.name = kstrndup(buf, count, GFP_KERNEL); + if (!var.name) { + rc = -ENOMEM; + goto out; + } + + var.namelen = count; + var.os = PLPKS_VAR_LINUX; + var.component = PLPKS_WRAPKEY_COMPONENT; + + rc = plpks_unrevoke_wrapping_key(&var); + + if (rc) { + pr_err("un-revocation of wrapping key <%s> failed. rc = %d\n", + (char *)var.name, rc); + goto out; + } + + rc = count; +out: + kfree(var.name); + return rc; +} + +static ssize_t delete_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct plpks_var var = {0}; + int rc; + + if (!capable(CAP_SYS_ADMIN)) { + rc = -EPERM; + goto out; + } + + if (!count) { + rc = -EINVAL; + goto out; + } + + if (count > PLPKS_MAX_NAME_SIZE) { + rc = -ENAMETOOLONG; + goto out; + } + + if (!strcmp(buf, PLPKS_DEFAULT_WRAPKEY_LABEL)) { + pr_warn("<%s> must not be deleted\n", + PLPKS_DEFAULT_WRAPKEY_LABEL); + rc = -EPERM; + goto out; + } + + if (!is_wrapkey_label_alnum(buf, count)) { + rc = -EINVAL; + goto out; + } + + var.name = kstrndup(buf, count, GFP_KERNEL); + if (!var.name) { + rc = -ENOMEM; + goto out; + } + + var.namelen = count; + var.os = PLPKS_VAR_LINUX; + var.component = PLPKS_WRAPKEY_COMPONENT; + + rc = plpks_del_wrapping_key(&var); + + if (rc) { + pr_err("deletion of wrapping key <%s> failed. rc = %d\n", + (char *)var.name, rc); + goto out; + } + + rc = count; +out: + kfree(var.name); + return rc; +} + +static struct kobj_attribute view_active_attr = __ATTR_RO(list_active); + +static struct kobj_attribute view_revoked_attr = __ATTR_RO(list_revoked); + +static struct kobj_attribute create_attr = __ATTR_WO(create); + +static struct kobj_attribute revoke_attr = __ATTR_WO(revoke); + +static struct kobj_attribute unrevoke_attr = __ATTR_WO(unrevoke); + +static struct kobj_attribute delete_attr = __ATTR_WO(delete); + +static struct attribute *wrapkey_attrs[] = { + &view_active_attr.attr, + &view_revoked_attr.attr, + &create_attr.attr, + &revoke_attr.attr, + &unrevoke_attr.attr, + &delete_attr.attr, + NULL, +}; + +static const struct attribute_group wrapkey_attr_group = { + .attrs = wrapkey_attrs, +}; +__ATTRIBUTE_GROUPS(wrapkey_attr); + +static const struct kobj_type wrapkey_ktype = { + .sysfs_ops = &kobj_sysfs_ops, + .default_groups = wrapkey_attr_groups, +}; + +static __init int wrapkey_sysfs_init(void) +{ + int rc; + + if (!plpks_revoke_is_supported()) { + pr_err("H_PKS_{UN}REVOKE_OBJECT interface not supported\n"); + return -ENODEV; + } + + wrapkey_kobj = kzalloc_obj(*wrapkey_kobj); + if (!wrapkey_kobj) + return -ENOMEM; + + rc = plpks_init_child_kobj(wrapkey_kobj, &wrapkey_ktype, "wrapkey"); + + if (rc) + kobject_put(wrapkey_kobj); + + return rc; +} + +late_initcall(wrapkey_sysfs_init); diff --git a/arch/powerpc/platforms/pseries/plpks.c b/arch/powerpc/platforms/pseries/plpks.c index f8c263e29bb6..7edeca7eee62 100644 --- a/arch/powerpc/platforms/pseries/plpks.c +++ b/arch/powerpc/platforms/pseries/plpks.c @@ -25,7 +25,6 @@ */ #define PLPKS_OBJLABEL_BUF_MAX 2550 -#define PLPKS_OBJLABEL_LEN_FIELD_SIZE 2 #define PLPKS_OBJLABEL_PREFIX_LEN 8 #define PLPKS_WRAP_INTERFACE_BIT 3 -- 2.52.0

