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


Reply via email to