[PATCH 2/8] nvme-keyring: add 'dhchap' key type

Hannes Reinecke
2026-03-17T13:01:24Z

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