[RFC PATCH] nvme-keyring: add request_key upcalls for dynamic key loading

Chris Leech
2026-04-22T23:11:31Z

This is a prototype of a "PSK request" key type, which requests one or
more PSKs for a host NQN, subsystem NQN pair. The matching PSKs and
needed metadata to recreate the correct identity are returned from the
request_key upcall, the unpacked into new psk keys in the nvme keyring.

Assisted-by: Claude:claude-sonnet-4.5
---
drivers/nvme/common/keyring.c | 373 +++++++++++++++++++++++++++++++++-
1 file changed, 372 insertions(+), 1 deletion(-)

diff --git a/drivers/nvme/common/keyring.c b/drivers/nvme/common/keyring.c
index 32d16c53133be..22fb64cbe16ef 100644
--- a/drivers/nvme/common/keyring.c
+++ b/drivers/nvme/common/keyring.c
@@ -7,12 +7,36 @@
#include <linux/seq_file.h>
#include <linux/key-type.h>
#include <keys/user-type.h>
+#include <linux/base64.h>
#include <linux/nvme.h>
#include <linux/nvme-tcp.h>
#include <linux/nvme-keyring.h>
+#include <linux/nvme-auth.h>

static struct key *nvme_keyring;

+/*
+ * Parsed PSK entry from binary payload.
+ */
+struct nvme_psk_entry {
+ u8 version;
+ u8 hmac;
+ u8 key_len;
+ u8 digest_len;
+ u8 key_data[48];
+ u8 digest[48];
+};
+
+/*
+ * Payload for nvme-psk-request key type.
+ * Stores parsed PSK entries from binary payload.
+ */
+struct psk_request_payload {
+ struct rcu_head rcu;
+ int psk_count;
+ struct nvme_psk_entry psk_entries[];
+};
+
key_serial_t nvme_keyring_id(void)
{
return nvme_keyring->serial;
@@ -77,7 +101,6 @@ static int nvme_tls_psk_match_preparse(struct key_match_data *match_data)

static struct key_type nvme_tls_psk_key_type = {
.name = "psk",
- .flags = KEY_TYPE_NET_DOMAIN,
.preparse = user_preparse,
.free_preparse = user_free_preparse,
.match_preparse = nvme_tls_psk_match_preparse,
@@ -88,6 +111,193 @@ static struct key_type nvme_tls_psk_key_type = {
.read = user_read,
};

+/*
+ * nvme-psk-request key type callbacks
+ */
+
+/*
+ * parse_binary_psk_payload - Parse binary PSK payload
+ * @payload: Binary PSK payload
+ * @paylen: Length of @payload
+ * @entries: Destination pointer for allocated PSK entry array
+ * @count: Destination for number of PSKs parsed
+ *
+ * Parses a binary payload containing one or more PSKs.
+ * Format per PSK: hmac(1) + key_len(1) + key_data[key_len]
+ *
+ * Returns 0 on success with *entries allocated and *count set.
+ * Returns negative error code if no valid PSKs found.
+ */
+static int parse_binary_psk_payload(const u8 *payload, size_t paylen,
+ struct nvme_psk_entry **entries,
+ int *count)
+{
+ const u8 *p = payload;
+ const u8 *end = payload + paylen;
+ struct nvme_psk_entry *temp_entries;
+ int n = 0, idx = 0;
+ const u8 *scan;
+
+ if (!payload || paylen == 0)
+ return -EINVAL;
+
+ /* Count entries first */
+ scan = p;
+ while (scan < end) {
+ u8 key_len, digest_len;
+
+ if (scan + 3 > end)
+ break;
+
+ key_len = scan[1];
+ digest_len = scan[2];
+
+ if (key_len > 48 || digest_len > 48 ||
+ scan + 3 + key_len + digest_len > end) {
+ pr_debug("invalid PSK entry at offset %ld: key_len=%u digest_len=%u\n",
+ scan - payload, key_len, digest_len);
+ break;
+ }
+ n++;
+ scan += 3 + key_len + digest_len;
+ }
+
+ if (n == 0)
+ return -EINVAL;
+
+ /* Allocate array */
+ temp_entries = kvmalloc_objs(*temp_entries, n, GFP_KERNEL);
+ if (!temp_entries)
+ return -ENOMEM;
+
+ /* Parse each PSK */
+ while (p < end) {
+ u8 hmac, key_len, digest_len;
+
+ if (p + 3 > end)
+ break;
+
+ hmac = p[0];
+ key_len = p[1];
+ digest_len = p[2];
+ p += 3;
+
+ if (key_len > 48 || digest_len > 48 ||
+ p + key_len + digest_len > end) {
+ pr_debug("truncated PSK entry: key_len=%u digest_len=%u, remaining=%ld\n",
+ key_len, digest_len, end - p);
+ break;
+ }
+
+ temp_entries[idx].version = 1;
+ temp_entries[idx].hmac = hmac;
+ temp_entries[idx].key_len = key_len;
+ temp_entries[idx].digest_len = digest_len;
+ memcpy(temp_entries[idx].key_data, p, key_len);
+ p += key_len;
+ memcpy(temp_entries[idx].digest, p, digest_len);
+ p += digest_len;
+ idx++;
+ }
+
+ if (idx == 0) {
+ kvfree(temp_entries);
+ return -EINVAL;
+ }
+
+ *entries = temp_entries;
+ *count = idx;
+ return 0;
+}
+
+static int psk_request_preparse(struct key_preparsed_payload *prep)
+{
+ struct psk_request_payload *payload;
+ struct nvme_psk_entry *entries;
+ int count, ret;
+ size_t payload_size;
+
+ /* Parse binary PSK payload */
+ ret = parse_binary_psk_payload(prep->data, prep->datalen, &entries, &count);
+ if (ret)
+ return ret;
+
+ /* Allocate payload structure with flexible array */
+ payload_size = sizeof(*payload) + count * sizeof(struct nvme_psk_entry);
+ payload = kzalloc(payload_size, GFP_KERNEL);
+ if (!payload) {
+ kvfree(entries);
+ return -ENOMEM;
+ }
+
+ payload->psk_count = count;
+ memcpy(payload->psk_entries, entries, count * sizeof(*entries));
+ kvfree(entries);
+
+ prep->quotalen = payload_size;
+ prep->payload.data[0] = payload;
+
+ return 0;
+}
+
+static void psk_request_free_preparse(struct key_preparsed_payload *prep)
+{
+ kfree(prep->payload.data[0]);
+}
+
+static int psk_request_instantiate(struct key *key,
+ struct key_preparsed_payload *prep)
+{
+ int ret;
+
+ ret = key_payload_reserve(key, prep->quotalen);
+ if (ret == 0) {
+ rcu_assign_keypointer(key, prep->payload.data[0]);
+ prep->payload.data[0] = NULL;
+ }
+ return ret;
+}
+
+static void psk_request_destroy(struct key *key)
+{
+ struct psk_request_payload *payload;
+
+ payload = rcu_access_pointer(key->payload.rcu_data0);
+ kfree(payload);
+}
+
+static void psk_request_describe(const struct key *key, struct seq_file *m)
+{
+ seq_puts(m, "nvme-psk-request: ");
+ seq_puts(m, key->description);
+}
+
+static long psk_request_read(const struct key *key, char *buffer,
+ size_t buflen)
+{
+ struct psk_request_payload *payload;
+ long ret;
+
+ payload = dereference_key_locked(key);
+ if (!payload)
+ return -EKEYREVOKED;
+
+ /* Return PSK count for debugging */
+ ret = scnprintf(buffer, buflen, "psk_count=%d\n", payload->psk_count);
+
+ return ret;
+}
+
+static struct key_type nvme_tls_psk_request_key_type = {
+ .name = "nvme-psk-request",
+ .preparse = psk_request_preparse,
+ .free_preparse = psk_request_free_preparse,
+ .instantiate = psk_request_instantiate,
+ .destroy = psk_request_destroy,
+ .describe = psk_request_describe,
+ .read = psk_request_read,
+};
+
static struct key *nvme_tls_psk_lookup(struct key *keyring,
const char *hostnqn, const char *subnqn,
u8 hmac, u8 psk_ver, bool generated)
@@ -123,6 +333,151 @@ static struct key *nvme_tls_psk_lookup(struct key *keyring,
return key_ref_to_ptr(keyref);
}

+/*
+ * nvme_tls_psk_request - Request PSK via userspace upcall
+ * @keyring: Target keyring for PSKs
+ * @hostnqn: Host NQN
+ * @subnqn: Subsystem NQN
+ *
+ * Invokes a userspace upcall via request_key() to obtain PSKs.
+ * Userspace returns a binary payload containing one or more PSKs.
+ * Binary format per PSK: hmac(1) + key_len(1) + key_data[key_len]
+ * This function parses the payload and creates PSK keys in the
+ * target keyring with full identities.
+ *
+ * Returns the serial of the first PSK created, or 0 on failure.
+ */
+static key_serial_t nvme_tls_psk_request(struct key *keyring,
+ const char *hostnqn,
+ const char *subnqn)
+{
+ char *description = NULL;
+ char *callout_info = NULL;
+ size_t description_len, callout_len;
+ struct key *req_key = NULL;
+ struct psk_request_payload *payload;
+ key_serial_t first_psk_serial = 0;
+ key_serial_t keyring_id;
+ int i;
+
+ if (!keyring)
+ keyring = nvme_keyring;
+ keyring_id = key_serial(keyring);
+
+ /* Build simple description: "<hostnqn> <subnqn>" */
+ description_len = strlen(hostnqn) + strlen(subnqn) + 2;
+ description = kzalloc(description_len, GFP_KERNEL);
+ if (!description)
+ return 0;
+
+ snprintf(description, description_len, "%s %s", hostnqn, subnqn);
+
+ /* Build callout info: "hostnqn=... subnqn=..." */
+ callout_len = strlen("hostnqn=") + strlen(hostnqn) +
+ strlen(" subnqn=") + strlen(subnqn) + 1;
+ callout_info = kzalloc(callout_len, GFP_KERNEL);
+ if (!callout_info) {
+ kfree(description);
+ return 0;
+ }
+
+ snprintf(callout_info, callout_len, "hostnqn=%s subnqn=%s",
+ hostnqn, subnqn);
+
+ pr_debug("keyring %x request PSKs for '%s'\n", keyring_id, description);
+
+ /* Request key from userspace */
+ req_key = request_key(&nvme_tls_psk_request_key_type,
+ description, callout_info);
+ if (IS_ERR(req_key)) {
+ pr_debug("request_key failed for '%s', error %ld\n",
+ description, PTR_ERR(req_key));
+ goto out_free;
+ }
+
+ /* Extract parsed PSKs from request key payload */
+ down_read(&req_key->sem);
+ payload = dereference_key_locked(req_key);
+ if (!payload) {
+ up_read(&req_key->sem);
+ pr_debug("request key has no payload\n");
+ goto out_put_req;
+ }
+
+ pr_debug("received %d PSK(s) from upcall\n", payload->psk_count);
+
+ /* Create each PSK in target keyring */
+ for (i = 0; i < payload->psk_count; i++) {
+ struct nvme_psk_entry *psk = &payload->psk_entries[i];
+ char *identity = NULL;
+ char *digest_b64 = NULL;
+ key_ref_t keyref;
+ struct key *key;
+ key_perm_t keyperm = KEY_POS_SEARCH | KEY_POS_VIEW |
+ KEY_POS_READ | KEY_POS_WRITE |
+ KEY_POS_LINK | KEY_POS_SETATTR |
+ KEY_USR_SEARCH | KEY_USR_VIEW |
+ KEY_USR_READ;
+ int digest_b64_len;
+
+ /* Convert raw digest to base64 string for identity */
+ digest_b64 = kmalloc(BASE64_CHARS(psk->digest_len) + 1, GFP_KERNEL);
+ if (!digest_b64)
+ continue;
+
+ digest_b64_len = base64_encode(psk->digest, psk->digest_len,
+ digest_b64, false, BASE64_STD);
+ digest_b64[digest_b64_len] = '\0';
+
+ /* Build identity: "NVMe<v>R<hh> <hostnqn> <subnqn> <digest>" */
+ identity = kasprintf(GFP_KERNEL, "NVMe%uR%02u %s %s %s",
+ psk->version, psk->hmac, hostnqn,
+ subnqn, digest_b64);
+ if (!identity) {
+ kfree(digest_b64);
+ continue;
+ }
+
+ /* Create PSK key in target keyring */
+ keyref = key_create_or_update(make_key_ref(keyring, true),
+ "psk", identity, psk->key_data,
+ psk->key_len, keyperm,
+ KEY_ALLOC_NOT_IN_QUOTA |
+ KEY_ALLOC_BUILT_IN |
+ KEY_ALLOC_BYPASS_RESTRICTION);
+
+ if (!IS_ERR(keyref)) {
+ key = key_ref_to_ptr(keyref);
+ pr_debug("created PSK %x '%s' in keyring %x\n",
+ key->serial, identity, keyring_id);
+ if (first_psk_serial == 0)
+ first_psk_serial = key->serial;
+ key_put(key);
+ } else {
+ pr_debug("failed to create PSK '%s': %ld\n",
+ identity, PTR_ERR(keyref));
+ }
+
+ kfree(digest_b64);
+ kfree(identity);
+ }
+
+ up_read(&req_key->sem);
+
+ if (first_psk_serial)
+ pr_debug("obtained %d PSK(s) via upcall, first: %08x\n",
+ payload->psk_count, first_psk_serial);
+ else
+ pr_debug("PSK upcall returned no valid PSKs\n");
+
+out_put_req:
+ key_put(req_key);
+out_free:
+ kfree(callout_info);
+ kfree(description);
+ return first_psk_serial;
+}
+
/**
* nvme_tls_psk_refresh - Refresh TLS PSK
* @keyring: Keyring holding the TLS PSK
@@ -235,6 +590,7 @@ key_serial_t nvme_tls_psk_default(struct key *keyring,
key_serial_t tls_key_id;
int prio;

+ /* First, search the keyring for existing PSKs (fast path) */
for (prio = 0; prio < ARRAY_SIZE(nvme_tls_psk_prio); prio++) {
bool generated = nvme_tls_psk_prio[prio].generated;
u8 ver = nvme_tls_psk_prio[prio].psk_ver;
@@ -248,6 +604,12 @@ key_serial_t nvme_tls_psk_default(struct key *keyring,
return tls_key_id;
}
}
+
+ /* If not found in keyring, try userspace upcall (slow path) */
+ tls_key_id = nvme_tls_psk_request(keyring, hostnqn, subnqn);
+ if (tls_key_id)
+ return tls_key_id;
+
return 0;
}
EXPORT_SYMBOL_GPL(nvme_tls_psk_default);
@@ -270,11 +632,20 @@ static int __init nvme_keyring_init(void)
key_put(nvme_keyring);
return err;
}
+
+ err = register_key_type(&nvme_tls_psk_request_key_type);
+ if (err) {
+ unregister_key_type(&nvme_tls_psk_key_type);
+ key_put(nvme_keyring);
+ return err;
+ }
+
return 0;
}

static void __exit nvme_keyring_exit(void)
{
+ unregister_key_type(&nvme_tls_psk_request_key_type);
unregister_key_type(&nvme_tls_psk_key_type);
key_revoke(nvme_keyring);
key_put(nvme_keyring);
--
2.53.0