public inbox for linux-nvme@lists.infradead.org
 help / color / mirror / Atom feed
* [RFC nvme-keyring nvme-cli] Should NVMe/TLS PSKs support the request_key API?
@ 2026-04-22 23:10 Chris Leech
  2026-04-22 23:10 ` [RFC PATCH] nvme-keyring: add request_key upcalls for dynamic key loading Chris Leech
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Chris Leech @ 2026-04-22 23:10 UTC (permalink / raw)
  To: linux-nvme; +Cc: Hannes Reinecke, Daniel Wagner, Maurizio Lombardi

Would it be of interest to support the request_key API with NVMe/TLS
PSKs? I think it would be an improvement, allowing PSKs to be loaded
into the keyring as needed.

Issues (if it was simple I'd just send a patch):

 - a key cannot have it's description changed
 - request_key() creates an uninstantiated key with the description set
 - tlshd (ktls-utils) uses the description as the PSK Identity
 - NVMe specifies this Identity in a way that the kernel cannot know it
   when a request_key() would be needed (it includes an encoding of the
   key length and a digest of the source key it was derived from)

As nvme_keyring can't just request the key it needs, I went through a
few ideas:

 - request a new keyring full of PSK keys
   (requesting a keyring is explicitly blocked)
 - have the user-space handler create a temporary keyring, put one or
   more PSKs in it, and return the keyring serial in a "psk request key"
   (worked, but seems unlikely to be accepted as intended use)
 - have a request key format that expects to receive a key containing
   one or more PSKs + identity metadata, unpack those in the kernel into
   actual psk keys than can be used

I'm going to follow this with a _rough_ prototype of the last option in
that list. The user-space side is a shell script, I'd replace that with
an new nvme-cli command (but the request_key configuration does
provide a convenient place to allow for other key storage solutions to
hook in).

Motivation: The current solution for loading NVMe/TLS PSKs into the
kernel keyring (70-nvmf-keys.rules in the nvme-cli repo) falls short in
a few areas by triggering from a kmod load uevent:

 - doesn't work for the target modules
   (that one's easy, /nvme_tcp/nvme_keyring/)
 - doesn't work if the modules are built into the kernel
 - doesn't work if the modules are loaded from initramfs, with more PSKs
   and connections are configured outside of the nBFT
 - only runs once, must load all keys and cannot help with new keys
   (ok, nvme-cli doesn't have a command to save to a keyfile without
    importing to the keyring first anyway)

 Biggest problem I see, requiring explicit loading of nvme_tcp before it
 can be used with TLS:

 - The async uevent handling loses the race against the kernel when the
   transport kmod is requested on demand.

  ┌────────┐     ┌──────────┐     ┌──────────┐     ┌──────┐
  │ kernel │     │ nvme-cli │     │ modprobe │     │ udev │
  └────┬───┘     └─────┬────┘     └─────┬────┘     └───┬──┘
       │               │                │              │
       │ connect       │                │              │
       │◄──────────────┤                │              │
       │               │                │              │
       │ request kmod  │                │              │
       ├───────────────────────────────►│              │
       │               │                │              │
       │ load kmod     │                │              │
       │◄───────────────────────────────┤              │
       │               │                │              │
       │ uevent kmod   │                │              │
       ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌►│
       │               │                │              │
       │ check keyring for PSK ❌       │              │
       ├──┐            │                │              │
       │  │            │                │              │
       │◄─┘            │                │              │
       │               │                │              │
       │ connect failed ❌              │              │
       ├──────────────►│                │              │
       │               │                │              │
       │               │ nvme tls --import --keyfile   │
       │               │◄──────────────────────────────┤
       │               │                │              │
       │ populate .nvme keyring         │              │
       │◄──────────────┤                │              │
       │               │                │              │

- Chris



^ permalink raw reply	[flat|nested] 5+ messages in thread

* [RFC PATCH] nvme-keyring: add request_key upcalls for dynamic key loading
  2026-04-22 23:10 [RFC nvme-keyring nvme-cli] Should NVMe/TLS PSKs support the request_key API? Chris Leech
@ 2026-04-22 23:10 ` Chris Leech
  2026-04-22 23:10 ` [RFC] example request-key helper script Chris Leech
  2026-04-23 12:05 ` [RFC nvme-keyring nvme-cli] Should NVMe/TLS PSKs support the request_key API? Hannes Reinecke
  2 siblings, 0 replies; 5+ messages in thread
From: Chris Leech @ 2026-04-22 23:10 UTC (permalink / raw)
  To: linux-nvme; +Cc: Hannes Reinecke, Daniel Wagner, Maurizio Lombardi

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



^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [RFC] example request-key helper script
  2026-04-22 23:10 [RFC nvme-keyring nvme-cli] Should NVMe/TLS PSKs support the request_key API? Chris Leech
  2026-04-22 23:10 ` [RFC PATCH] nvme-keyring: add request_key upcalls for dynamic key loading Chris Leech
@ 2026-04-22 23:10 ` Chris Leech
  2026-04-23 12:05 ` [RFC nvme-keyring nvme-cli] Should NVMe/TLS PSKs support the request_key API? Hannes Reinecke
  2 siblings, 0 replies; 5+ messages in thread
From: Chris Leech @ 2026-04-22 23:10 UTC (permalink / raw)
  To: linux-nvme; +Cc: Hannes Reinecke, Daniel Wagner, Maurizio Lombardi

This is a prototype of a request-key helper to instantiate a "PSK request" key.

Assisted-by: Claude:claude-sonnet-4.5
---
#!/usr/bin/bash
#
# NVMe PSK Provider
#
# This script is called by /sbin/request-key when the kernel requests
# NVMe TLS PSKs via the nvme-psk-request key type.
#
# configure /etc/request-key.conf with an entry like the following:
# create  nvme-psk-request  *  *  /usr/libexec/nvme-psk-provider %k %d %c %S

KEY_ID=$1
DESCRIPTION=$2
CALLOUT_INFO=$3
KEYRING=$4

exec > >(logger -t "key.nvme-psk") 2>&1
echo "$0: $@"

# Parse callout info
eval $(echo "$CALLOUT_INFO" | tr ' ' '\n' | grep '=')

KEYFILE="/etc/nvme/tls-keys"
HOST_NQN=${hostnqn}
SUBSYS_NQN=${subnqn}

if [ -z "$KEYFILE" ] || [ -z "$HOST_NQN" ] || [ -z "$SUBSYS_NQN" ]; then
    echo "Error: Missing required parameters"
    keyctl negate "$KEY_ID" 1 "$KEYRING"
    exit 1
fi

if [ ! -f "$KEYFILE" ]; then
    echo "Error: Keyfile '$KEYFILE' not found"
    keyctl negate "$KEY_ID" 1 "$KEYRING"
    exit 1
fi

# Temporary file for binary payload
PAYLOAD_FILE=$(mktemp)
trap "rm -f $PAYLOAD_FILE" EXIT

KEY_COUNT=0

# Search for all matching entries in keyfile
while IFS= read -r line; do
    # Skip empty lines and comments
    [ -z "$line" ] && continue
    [[ "$line" =~ ^#.* ]] && continue

    # Extract the last space-separated field (the encoded key)
    encoded_key="${line##* }"
    # Extract everything before the last space (the description)
    description="${line% *}"

    # Parse description: NVMe<ver><type>0<hmac> <hostnqn> <subsysnqn> <digest>
    if [[ "$description" =~ ^NVMe([0-9])([RG])([0-9][0-9])[[:space:]]+([^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]+([^[:space:]]+)$ ]]; then
        ver="${BASH_REMATCH[1]}"
        type="${BASH_REMATCH[2]}"
        hmac="${BASH_REMATCH[3]}"
        desc_hostnqn="${BASH_REMATCH[4]}"
        desc_subsysnqn="${BASH_REMATCH[5]}"
        desc_digest="${BASH_REMATCH[6]}"

        # Only process Retained keys (not Generated)
        if [ "$type" != "R" ]; then
            continue
        fi

        # Check if this matches our search criteria
        if [ "$desc_hostnqn" = "$HOST_NQN" ] && [ "$desc_subsysnqn" = "$SUBSYS_NQN" ]; then
            # Validate the encoded key format: NVMeTLSkey-1:xx:...:
            if [[ "$encoded_key" =~ ^NVMeTLSkey-1:([0-9][0-9]):(.+):$ ]]; then
                key_hmac="${BASH_REMATCH[1]}"
                base64_data="${BASH_REMATCH[2]}"

                echo "Found matching PSK: version=$ver type=$type hmac=$hmac"

                # Decode base64
                tmpfile=$(mktemp)
                echo -n "$base64_data" | base64 -d > "$tmpfile"
                decoded_len=$(stat -c%s "$tmpfile")

                if [ "$decoded_len" -lt 36 ]; then
                    echo "Warning: Decoded data too short ($decoded_len bytes), skipping"
                    rm -f "$tmpfile"
                    continue
                fi

                # Key length is decoded_len - 4 (CRC is last 4 bytes)
                key_len=$((decoded_len - 4))

                # Verify CRC32 (optional - nvme-cli already validated on import)
                # Extract key data (without CRC)
                key_data=$(dd if="$tmpfile" bs=1 count="$key_len" 2>/dev/null)

                # Decode digest from base64
                digest_data=$(echo -n "$desc_digest" | base64 -d)
                digest_len=$(echo -n "$digest_data" | wc -c)

                # Pack binary: hmac(1) + key_len(1) + digest_len(1) + key_data[key_len] + digest[digest_len]
                # Convert hmac from decimal string to hex byte
                printf "\\x$(printf '%02x' $key_hmac)" >> "$PAYLOAD_FILE"
                printf "\\x$(printf '%02x' $key_len)" >> "$PAYLOAD_FILE"
                printf "\\x$(printf '%02x' $digest_len)" >> "$PAYLOAD_FILE"
                echo -n "$key_data" >> "$PAYLOAD_FILE"
                echo -n "$digest_data" >> "$PAYLOAD_FILE"

                KEY_COUNT=$((KEY_COUNT + 1))
                rm -f "$tmpfile"
            else
                echo "Warning: Invalid encoded key format, skipping"
            fi
        fi
    fi
done < "$KEYFILE"

if [ "$KEY_COUNT" -eq 0 ]; then
    echo "Error: No matching keys found for host-nqn '$HOST_NQN' and subsystem-nqn '$SUBSYS_NQN'"
    keyctl negate "$KEY_ID" 1 "$KEYRING"
    exit 1
fi

# Instantiate the nvme-psk-request key with binary payload
echo keyctl instantiate "$KEY_ID" @s "$KEYRING" < "$PAYLOAD_FILE"
keyctl pinstantiate "$KEY_ID" "$KEYRING" < "$PAYLOAD_FILE"
if [ $? -eq 0 ]; then
    echo "Successfully instantiated $KEY_COUNT PSK(s)"
    exit 0
else
    echo "Error: Failed to instantiate request key"
    keyctl negate "$KEY_ID" 1 "$KEYRING"
    exit 1
fi



^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [RFC nvme-keyring nvme-cli] Should NVMe/TLS PSKs support the request_key API?
  2026-04-22 23:10 [RFC nvme-keyring nvme-cli] Should NVMe/TLS PSKs support the request_key API? Chris Leech
  2026-04-22 23:10 ` [RFC PATCH] nvme-keyring: add request_key upcalls for dynamic key loading Chris Leech
  2026-04-22 23:10 ` [RFC] example request-key helper script Chris Leech
@ 2026-04-23 12:05 ` Hannes Reinecke
  2026-04-23 17:15   ` Daniel Wagner
  2 siblings, 1 reply; 5+ messages in thread
From: Hannes Reinecke @ 2026-04-23 12:05 UTC (permalink / raw)
  To: Chris Leech, linux-nvme; +Cc: Daniel Wagner, Maurizio Lombardi

On 4/23/26 01:10, Chris Leech wrote:
> Would it be of interest to support the request_key API with NVMe/TLS
> PSKs? I think it would be an improvement, allowing PSKs to be loaded
> into the keyring as needed.
> 
Hehe. I knew someone would bring it up :-)

> Issues (if it was simple I'd just send a patch):
> 
>   - a key cannot have it's description changed
>   - request_key() creates an uninstantiated key with the description set
>   - tlshd (ktls-utils) uses the description as the PSK Identity
>   - NVMe specifies this Identity in a way that the kernel cannot know it
>     when a request_key() would be needed (it includes an encoding of the
>     key length and a digest of the source key it was derived from)
> 
That is correct.

> As nvme_keyring can't just request the key it needs, I went through a
> few ideas:
> 
>   - request a new keyring full of PSK keys
>     (requesting a keyring is explicitly blocked)
>   - have the user-space handler create a temporary keyring, put one or
>     more PSKs in it, and return the keyring serial in a "psk request key"
>     (worked, but seems unlikely to be accepted as intended use)
>   - have a request key format that expects to receive a key containing
>     one or more PSKs + identity metadata, unpack those in the kernel into
>     actual psk keys than can be used
> 
> I'm going to follow this with a _rough_ prototype of the last option in
> that list. The user-space side is a shell script, I'd replace that with
> an new nvme-cli command (but the request_key configuration does
> provide a convenient place to allow for other key storage solutions to
> hook in).
> 
> Motivation: The current solution for loading NVMe/TLS PSKs into the
> kernel keyring (70-nvmf-keys.rules in the nvme-cli repo) falls short in
> a few areas by triggering from a kmod load uevent:
> 
>   - doesn't work for the target modules
>     (that one's easy, /nvme_tcp/nvme_keyring/)
>   - doesn't work if the modules are built into the kernel
>   - doesn't work if the modules are loaded from initramfs, with more PSKs
>     and connections are configured outside of the nBFT
>   - only runs once, must load all keys and cannot help with new keys
>     (ok, nvme-cli doesn't have a command to save to a keyfile without
>      importing to the keyring first anyway)
> 
>   Biggest problem I see, requiring explicit loading of nvme_tcp before it
>   can be used with TLS:
> 
>   - The async uevent handling loses the race against the kernel when the
>     transport kmod is requested on demand.
> 
>    ┌────────┐     ┌──────────┐     ┌──────────┐     ┌──────┐
>    │ kernel │     │ nvme-cli │     │ modprobe │     │ udev │
>    └────┬───┘     └─────┬────┘     └─────┬────┘     └───┬──┘
>         │               │                │              │
>         │ connect       │                │              │
>         │◄──────────────┤                │              │
>         │               │                │              │
>         │ request kmod  │                │              │
>         ├───────────────────────────────►│              │
>         │               │                │              │
>         │ load kmod     │                │              │
>         │◄───────────────────────────────┤              │
>         │               │                │              │
>         │ uevent kmod   │                │              │
>         ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌►│
>         │               │                │              │
>         │ check keyring for PSK ❌       │              │
>         ├──┐            │                │              │
>         │  │            │                │              │
>         │◄─┘            │                │              │
>         │               │                │              │
>         │ connect failed ❌              │              │
>         ├──────────────►│                │              │
>         │               │                │              │
>         │               │ nvme tls --import --keyfile   │
>         │               │◄──────────────────────────────┤
>         │               │                │              │
>         │ populate .nvme keyring         │              │
>         │◄──────────────┤                │              │
>         │               │                │              │
> 
> - Chris
>
(Nice ASCII graphic, btw :-)

I know, the current model is ever so imperfect.
And relying on udev _module_ events clearly requires them
to be modules in the first place.
(Fun fact: I am rather positive that we _do_ get the module
uevents even when built in, it's just that they'll be issues
right at the start of the kernel where no udev (nor any userspace)
is available to process them ...)

But anyway; why can't we tweak nvmf-autoconnect.service to load
the keys?

Cheers,

Hannes
-- 
Dr. Hannes Reinecke                  Kernel Storage Architect
hare@suse.de                                +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [RFC nvme-keyring nvme-cli] Should NVMe/TLS PSKs support the request_key API?
  2026-04-23 12:05 ` [RFC nvme-keyring nvme-cli] Should NVMe/TLS PSKs support the request_key API? Hannes Reinecke
@ 2026-04-23 17:15   ` Daniel Wagner
  0 siblings, 0 replies; 5+ messages in thread
From: Daniel Wagner @ 2026-04-23 17:15 UTC (permalink / raw)
  To: Hannes Reinecke; +Cc: Chris Leech, linux-nvme, Maurizio Lombardi

On Thu, Apr 23, 2026 at 02:05:45PM +0200, Hannes Reinecke wrote:
> >   Biggest problem I see, requiring explicit loading of nvme_tcp before it
> >   can be used with TLS:
> > 
> >   - The async uevent handling loses the race against the kernel when the
> >     transport kmod is requested on demand.
> > 
> >    ┌────────┐     ┌──────────┐     ┌──────────┐     ┌──────┐
> >    │ kernel │     │ nvme-cli │     │ modprobe │     │ udev │
> >    └────┬───┘     └─────┬────┘     └─────┬────┘     └───┬──┘
> >         │               │                │              │
> >         │ connect       │                │              │
> >         │◄──────────────┤                │              │
> >         │               │                │              │
> >         │ request kmod  │                │              │
> >         ├───────────────────────────────►│              │
> >         │               │                │              │
> >         │ load kmod     │                │              │
> >         │◄───────────────────────────────┤              │
> >         │               │                │              │
> >         │ uevent kmod   │                │              │
> >         ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌►│
> >         │               │                │              │
> >         │ check keyring for PSK ❌       │              │
> >         ├──┐            │                │              │
> >         │  │            │                │              │
> >         │◄─┘            │                │              │
> >         │               │                │              │
> >         │ connect failed ❌              │              │
> >         ├──────────────►│                │              │
> >         │               │                │              │
> >         │               │ nvme tls --import --keyfile   │
> >         │               │◄──────────────────────────────┤
> >         │               │                │              │
> >         │ populate .nvme keyring         │              │
> >         │◄──────────────┤                │              │
> >         │               │                │              │

Maurizio just added

    	load_nvme_fabrics_module();

to the connect command. So we could make the connect call wait for the
udev event, load the keys into the kernel and then write to
/dev/nvme-fabrics.

I see the /etc/nvme/tls-keys file is merely a stopgap solution. There is
certainly better ways to do this.


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-04-23 17:15 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-22 23:10 [RFC nvme-keyring nvme-cli] Should NVMe/TLS PSKs support the request_key API? Chris Leech
2026-04-22 23:10 ` [RFC PATCH] nvme-keyring: add request_key upcalls for dynamic key loading Chris Leech
2026-04-22 23:10 ` [RFC] example request-key helper script Chris Leech
2026-04-23 12:05 ` [RFC nvme-keyring nvme-cli] Should NVMe/TLS PSKs support the request_key API? Hannes Reinecke
2026-04-23 17:15   ` Daniel Wagner

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox