From: Hannes Reinecke <hare@kernel.org>
To: Christoph Hellwig <hch@lst.de>
Cc: Sagi Grimberg <sagi@grimberg.me>, Keith Busch <kbusch@kernel.org>,
linux-nvme@lists.infradead.org, Hannes Reinecke <hare@kernel.org>
Subject: [PATCH 2/8] nvme-keyring: add 'dhchap' key type
Date: Tue, 17 Mar 2026 14:00:57 +0100 [thread overview]
Message-ID: <20260317130103.107360-3-hare@kernel.org> (raw)
In-Reply-To: <20260317130103.107360-1-hare@kernel.org>
Add a 'dhchap' keytype to store DH-HMAC-CHAP secret keys.
Keys are stored with a 'user-type' compatible payload, such
that one can use 'user_read()' to access the raw contents
and the 'read()' callback to get the base64-encoded key
data in the DH-HMAC-CHAP secret representation.
Signed-off-by: Hannes Reinecke <hare@kernel.org>
---
drivers/nvme/common/keyring.c | 216 ++++++++++++++++++++++++++++++++++
1 file changed, 216 insertions(+)
diff --git a/drivers/nvme/common/keyring.c b/drivers/nvme/common/keyring.c
index 32d16c53133b..f7e18df438e6 100644
--- a/drivers/nvme/common/keyring.c
+++ b/drivers/nvme/common/keyring.c
@@ -4,6 +4,9 @@
*/
#include <linux/module.h>
+#include <linux/crc32.h>
+#include <linux/base64.h>
+#include <linux/unaligned.h>
#include <linux/seq_file.h>
#include <linux/key-type.h>
#include <keys/user-type.h>
@@ -252,6 +255,212 @@ key_serial_t nvme_tls_psk_default(struct key *keyring,
}
EXPORT_SYMBOL_GPL(nvme_tls_psk_default);
+static void nvme_dhchap_psk_describe(const struct key *key, struct seq_file *m)
+{
+ seq_puts(m, key->description);
+ seq_printf(m, ": %u", key->datalen);
+}
+
+static bool nvme_dhchap_psk_match(const struct key *key,
+ const struct key_match_data *match_data)
+{
+ const char *match_id;
+ size_t match_len;
+
+ if (!key->description) {
+ pr_debug("%s: no key description\n", __func__);
+ return false;
+ }
+ if (!match_data->raw_data) {
+ pr_debug("%s: no match data\n", __func__);
+ return false;
+ }
+ match_id = match_data->raw_data;
+ match_len = strlen(match_id);
+ pr_debug("%s: match '%s' '%s' len %zd\n",
+ __func__, match_id, key->description, match_len);
+
+ return !memcmp(key->description, match_id, match_len);
+}
+
+static int nvme_dhchap_psk_match_preparse(struct key_match_data *match_data)
+{
+ match_data->lookup_type = KEYRING_SEARCH_LOOKUP_ITERATE;
+ match_data->cmp = nvme_dhchap_psk_match;
+ return 0;
+}
+
+/**
+ * nvme_dhchap_psk_preparse - prepare DH-HMAC-CHAP key data
+ * @prep: preparsed payload of the key data
+ *
+ * Decode the DH-HMAC-CHAP key data passed in in @prep and
+ * store the resulting binary data. The binary data includes
+ * space for the CRC, the version, and the hmac identifier,
+ * but the data length is just the key data without the CRC.
+ * This allows the user to read the key data via the
+ * 'user_read()' function. The additional 'version' ahd 'hmac'
+ * data is used in the ->read() callback to generate the
+ * base64 encoded key.
+ */
+static int nvme_dhchap_psk_preparse(struct key_preparsed_payload *prep)
+{
+ struct user_key_payload *upayload;
+ size_t datalen = prep->datalen, keylen;
+ int ret;
+ u32 crc;
+ u8 version, hmac;
+
+ if (!prep->data) {
+ pr_debug("%s: Empty data", __func__);
+ prep->payload.data[0] = NULL;
+ prep->quotalen = 0;
+ return -EINVAL;
+ }
+
+ if (sscanf(prep->data, "DHHC-%01hhu:%02hhu:%*s",
+ &version, &hmac) != 2) {
+ pr_debug("%s: invalid key data '%s'\n", __func__,
+ (char *)prep->data);
+ prep->payload.data[0] = NULL;
+ prep->quotalen = 0;
+ return -EINVAL;
+ }
+
+ /* skip header and final ':' character */
+ datalen -= 11;
+
+ /*
+ * payload is < key | version | hmac >
+ * base64 decode will always return less data
+ * than the encoded data, so allocating the size
+ * of the encoded data will be large enough.
+ */
+ upayload = kzalloc(sizeof(*upayload) + datalen, GFP_KERNEL);
+ if (!upayload) {
+ prep->payload.data[0] = NULL;
+ prep->quotalen = 0;
+ return -ENOMEM;
+ }
+
+ /* decode the data */
+ prep->quotalen = keylen;
+ prep->payload.data[0] = upayload;
+ ret = base64_decode(prep->data + 10, datalen, upayload->data,
+ true, BASE64_STD);
+ if (ret < 0) {
+ pr_debug("%s: Failed to decode key %s\n",
+ __func__, (char *)prep->data + 10);
+ return ret;
+ }
+ ret -= 4;
+ crc = ~crc32(~0, upayload->data, ret);
+ if (get_unaligned_le32(upayload->data + ret) != crc) {
+ pr_debug("%s: CRC mismatch for key\n", __func__);
+ /* CRC mismatch */
+ return -EKEYREJECTED;
+ }
+ /* append version and hmac to the payload */
+ upayload->data[ret + 4] = version;
+ upayload->data[ret + 5] = hmac;
+ upayload->datalen = ret;
+ return 0;
+}
+
+/**
+ * nvme_dhchap_decoded_key_size - Size of the base64-decoded key
+ * @size: size of the encoded key
+ *
+ * Returns the expected size of the key after base64 decoding.
+ */
+static inline int nvme_dhchap_decoded_key_size(int size)
+{
+ int keylen = -EINVAL;
+
+ switch (size) {
+ case 32:
+ keylen = 48;
+ break;
+ case 48:
+ keylen = 72;
+ break;
+ case 64:
+ keylen = 92;
+ break;
+ default:
+ break;
+ }
+ return keylen;
+}
+
+/**
+ * nvme_dhchap_psk_read - read callback for dhchap key types
+ * @key: key to read from
+ * @buffer: buffer for the key contents
+ * @buflen: length of @buffer
+ *
+ * Formets the DH-HMAC-CHAP key in base64-encoded form as
+ * defined in the NVM Express TCP Transport Specification Rev 1.1,
+ * Section 3.6.1.5 'PSK Interchange Format'. The payload of @key
+ * is assumed to be of 'struct user_key_payload', with the data
+ * consisting of the key data of length 'upayload->datalen', followed
+ * by four bytes of CRC, one byte version, and one byte hmac fields.
+ */
+static long nvme_dhchap_psk_read(const struct key *key,
+ char *buffer, size_t buflen)
+{
+ const struct user_key_payload *upayload;
+ size_t datalen, keylen;
+ u8 version, hmac;
+ long ret;
+
+ upayload = user_key_payload_locked(key);
+ /*
+ * Expected output string length:
+ * strlen("DHHC-") +
+ * version (= 1) +
+ * ':' (= 1) +
+ * hmac (= 2) +
+ * ':' (= 1) +
+ * encoded base64 key length +
+ * CRC length (= 4) +
+ * ':' (= 1)
+ */
+ keylen = nvme_dhchap_decoded_key_size(upayload->datalen) + 15;
+ if (keylen < 0 || !buffer || buflen == 0)
+ return keylen;
+
+ if (buflen < keylen)
+ return -EINVAL;
+
+ memset(buffer, 0, buflen);
+ version = upayload->data[upayload->datalen + 4];
+ hmac = upayload->data[upayload->datalen + 5];
+ ret = sprintf(buffer, "DHHC-%01hhu:%02hhu:", version, hmac);
+ if (ret < 0)
+ return -ENOKEY;
+ /* Include the trailing CRC */
+ datalen = upayload->datalen + 4;
+ ret += base64_encode(upayload->data, datalen, buffer + ret,
+ true, BASE64_STD);
+ buffer[ret] = ':';
+ ret++;
+ return ret;
+}
+
+static struct key_type nvme_dhchap_psk_key_type = {
+ .name = "dhchap",
+ .flags = KEY_TYPE_NET_DOMAIN,
+ .preparse = nvme_dhchap_psk_preparse,
+ .free_preparse = user_free_preparse,
+ .match_preparse = nvme_dhchap_psk_match_preparse,
+ .instantiate = generic_key_instantiate,
+ .revoke = user_revoke,
+ .destroy = user_destroy,
+ .describe = nvme_dhchap_psk_describe,
+ .read = nvme_dhchap_psk_read,
+};
+
static int __init nvme_keyring_init(void)
{
int err;
@@ -270,11 +479,18 @@ static int __init nvme_keyring_init(void)
key_put(nvme_keyring);
return err;
}
+ err = register_key_type(&nvme_dhchap_psk_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_dhchap_psk_key_type);
unregister_key_type(&nvme_tls_psk_key_type);
key_revoke(nvme_keyring);
key_put(nvme_keyring);
--
2.43.0
next prev parent reply other threads:[~2026-03-17 13:01 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-17 13:00 [PATCHv3 0/8] nvme-auth: switch to use the kernel keyring Hannes Reinecke
2026-03-17 13:00 ` [PATCH 1/8] nvme-auth: modify nvme_auth_transform_key() to return status Hannes Reinecke
2026-03-17 13:09 ` Maurizio Lombardi
2026-03-17 14:55 ` Hannes Reinecke
2026-03-17 13:00 ` Hannes Reinecke [this message]
2026-04-01 18:13 ` [PATCH 2/8] nvme-keyring: add 'dhchap' key type Chris Leech
2026-04-07 6:18 ` Hannes Reinecke
2026-03-17 13:00 ` [PATCH 3/8] nvme-auth: switch to use 'struct key' Hannes Reinecke
2026-04-01 18:36 ` Chris Leech
2026-04-07 6:20 ` Hannes Reinecke
2026-03-17 13:00 ` [PATCH 4/8] nvme: parse dhchap keys during option parsing Hannes Reinecke
2026-04-01 18:43 ` Chris Leech
2026-04-07 6:20 ` Hannes Reinecke
2026-03-17 13:01 ` [PATCH 5/8] nvmet-auth: parse dhchap key from configfs attribute Hannes Reinecke
2026-03-17 13:01 ` [PATCH 6/8] nvme: allow to pass in key description as dhchap secret Hannes Reinecke
2026-03-17 13:01 ` [PATCH 7/8] nvme-auth: wait for authentication to finish when changing keys Hannes Reinecke
2026-03-17 13:01 ` [PATCH 8/8] nvme-fabrics: allow to pass in keyring by name Hannes Reinecke
2026-03-17 13:20 ` [PATCHv3 0/8] nvme-auth: switch to use the kernel keyring Maurizio Lombardi
2026-03-17 14:44 ` Hannes Reinecke
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260317130103.107360-3-hare@kernel.org \
--to=hare@kernel.org \
--cc=hch@lst.de \
--cc=kbusch@kernel.org \
--cc=linux-nvme@lists.infradead.org \
--cc=sagi@grimberg.me \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.