All of lore.kernel.org
 help / color / mirror / Atom feed
From: Hannes Reinecke <hare@suse.de>
To: Sagi Grimberg <sagi@grimberg.me>
Cc: Keith Busch <keith.busch@wdc.com>, Christoph Hellwig <hch@lst.de>,
	linux-nvme@lists.infradead.org, Hannes Reinecke <hare@suse.de>
Subject: [PATCH nvme-cli 1/3] Add 'gen-dhchap-key' command
Date: Fri, 12 Nov 2021 14:11:09 +0100	[thread overview]
Message-ID: <20211112131111.97599-2-hare@suse.de> (raw)
In-Reply-To: <20211112131111.97599-1-hare@suse.de>

Add command 'gen-dhchap-key' to generate an DH-HMAC-CHAP host key
for NVMe in-band authentication.

Signed-off-by: Hannes Reinecke <hare@suse.de>
---
 Documentation/nvme-gen-dhchap-key.txt |  52 ++++++++
 Makefile                              |  17 ++-
 nvme-builtin.h                        |   1 +
 nvme.c                                | 178 ++++++++++++++++++++++++++
 util/argconfig.h                      |   1 +
 util/base64.c                         |  62 +++++++++
 util/base64.h                         |   6 +
 7 files changed, 316 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/nvme-gen-dhchap-key.txt
 create mode 100644 util/base64.c
 create mode 100644 util/base64.h

diff --git a/Documentation/nvme-gen-dhchap-key.txt b/Documentation/nvme-gen-dhchap-key.txt
new file mode 100644
index 0000000..eecde76
--- /dev/null
+++ b/Documentation/nvme-gen-dhchap-key.txt
@@ -0,0 +1,52 @@
+nvme-gen-dhchap-key(1)
+===================
+
+NAME
+----
+nvme-gen-dhchap-key - Generate a host DH-HMAC-CHAP key
+
+SYNOPSIS
+--------
+[verse]
+'nvme gen-dhchap-key' [--hmac=<hmac-id> | -h <hmac-id>]
+		      [--secret=<secret> | -s <secret> ]
+		      [--key-length=<len> | -l <len> ]
+		      [--nqn=<host-nqn> | -n <host-nqn> ]
+
+DESCRIPTION
+-----------
+Generate a base64-encoded DH-HMAC-CHAP host key in the form:
+DHHC-1:00:ia6zGodOr4SEG0Zzaw398rpY0wqipUWj4jWjUh4HWUz6aQ2n:
+and prints it to stdout.
+
+OPTIONS
+-------
+-h <hmac-id>::
+--hmac=<hmac-id>::
+	Select a HMAC algorithm to use. Possible values are:
+	0 - No HMAC algorithm
+	1 - SHA-256
+	2 - SHA-384
+	3 - SHA-512
+
+-s <secret>::
+--secret=<secret>::
+	Secret value (in hexadecimal) to be used for the key. If none are
+	provided a random value is used.
+
+-l <len>::
+--key-length=<len>::
+	Length of the resulting key. Possible values are 32, 48, or 64.
+
+-n <hostnqn>::
+--nqn=<hostnqn>::
+	Host-NQN to be used for the transformation. This parameter is only
+	valid if a non-zero HMAC function has been specified.
+
+EXAMPLES
+--------
+No Examples
+
+NVME
+----
+Part of the nvme-user suite
diff --git a/Makefile b/Makefile
index 4da13d5..22aafd3 100644
--- a/Makefile
+++ b/Makefile
@@ -5,6 +5,8 @@ LIBUUID = $(shell $(LD) -o /dev/null -luuid >/dev/null 2>&1; echo $$?)
 LIBHUGETLBFS = $(shell $(LD) -o /dev/null -lhugetlbfs >/dev/null 2>&1; echo $$?)
 HAVE_SYSTEMD = $(shell pkg-config --exists libsystemd  --atleast-version=242; echo $$?)
 LIBJSONC = $(shell $(LD) -o /dev/null -ljson-c >/dev/null 2>&1; echo $$?)
+LIBZ = $(shell $(LD) -o /dev/null -lz >/dev/null 2>&1; echo $$?)
+LIBOPENSSL = $(shell $(LD) -o /dev/null -lssl >/dev/null 2>&1; echo $$?)
 NVME = nvme
 INSTALL ?= install
 DESTDIR =
@@ -37,6 +39,18 @@ ifeq ($(LIBHUGETLBFS),0)
 	override LIB_DEPENDS += hugetlbfs
 endif
 
+ifeq ($(LIBZ),0)
+	override LDFLAGS += -lz
+	override CFLAGS += -DLIBZ
+	override LIB_DEPENDS += zlib
+endif
+
+ifeq ($(LIBOPENSSL),0)
+	override LDFLAGS += -lssl -lcrypto
+	override CFLAGS += -DOPENSSL
+	override LIB_DEPENDS += openssl
+endif
+
 INC=-Iutil
 
 ifeq ($(HAVE_SYSTEMD),0)
@@ -88,7 +102,8 @@ OBJS := nvme-print.o nvme-rpmb.o \
 	fabrics.o nvme-models.o plugin.o
 
 UTIL_OBJS := util/argconfig.o util/suffix.o util/parser.o \
-	util/cleanup.o
+	util/cleanup.o util/base64.o
+
 ifneq ($(LIBJSONC), 0)
 override UTIL_OBJS += util/json.o
 endif
diff --git a/nvme-builtin.h b/nvme-builtin.h
index a413d00..a256f05 100644
--- a/nvme-builtin.h
+++ b/nvme-builtin.h
@@ -81,6 +81,7 @@ COMMAND_LIST(
 	ENTRY("disconnect-all", "Disconnect from all connected NVMeoF subsystems", disconnect_all_cmd)
 	ENTRY("gen-hostnqn", "Generate NVMeoF host NQN", gen_hostnqn_cmd)
 	ENTRY("show-hostnqn", "Show NVMeoF host NQN", show_hostnqn_cmd)
+	ENTRY("gen-dhchap-key", "Generate NVMeoF DH-HMAC-CHAP host key", gen_dhchap_key)
 	ENTRY("dir-receive", "Submit a Directive Receive command, return results", dir_receive)
 	ENTRY("dir-send", "Submit a Directive Send command, return results", dir_send)
 	ENTRY("virt-mgmt", "Manage Flexible Resources between Primary and Secondary Controller ", virtual_mgmt)
diff --git a/nvme.c b/nvme.c
index dd51468..4ad91da 100644
--- a/nvme.c
+++ b/nvme.c
@@ -38,22 +38,31 @@
 #include <math.h>
 #include <dirent.h>
 #include <libgen.h>
+#include <zlib.h>
 
 #ifdef LIBHUGETLBFS
 #include <hugetlbfs.h>
 #endif
 
+#ifdef OPENSSL
+#include <openssl/engine.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#endif
+
 #include <linux/fs.h>
 
 #include <sys/mman.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/random.h>
 
 #include "common.h"
 #include "nvme.h"
 #include "libnvme.h"
 #include "nvme-print.h"
 #include "plugin.h"
+#include "base64.h"
 
 #include "util/argconfig.h"
 #include "fabrics.h"
@@ -5915,6 +5924,175 @@ static int show_hostnqn_cmd(int argc, char **argv, struct command *command, stru
 	return 0;
 }
 
+static int gen_dhchap_key(int argc, char **argv, struct command *command, struct plugin *plugin)
+{
+	const char *desc = "Generate a DH-HMAC-CHAP host key usable "\
+		"for NVMe In-Band Authentication.";
+	const char *secret = "Optional secret (in hexadecimal characters) "\
+		"to be used to initialize the host key.";
+	const char *key_len = "Length of the resulting key "\
+		"(32, 48, or 64 bytes).";
+	const char *hmac = "HMAC function to use for key transformation "\
+		"(0 = none, 1 = SHA-256, 2 = SHA-384, 3 = SHA-512).";
+	const char *nqn = "Host NQN to use for key transformation.";
+
+	char *raw_secret;
+	unsigned char key[68];
+	char encoded_key[128];
+	unsigned long crc = crc32(0L, NULL, 0);
+	int err = 0;
+#ifdef OPENSSL
+	const EVP_MD *md = NULL;
+	const char *hostnqn;
+#else
+	const char *md = NULL;
+#endif
+	struct config {
+		char *secret;
+		unsigned int key_len;
+		char *nqn;
+		unsigned int hmac;
+	};
+
+	struct config cfg = {
+		.secret = NULL,
+		.key_len = 0,
+		.nqn = NULL,
+		.hmac = 0,
+	};
+
+	OPT_ARGS(opts) = {
+		OPT_STR("secret", 's', &cfg.secret, secret),
+		OPT_UINT("key-length", 'l', &cfg.key_len, key_len),
+		OPT_STR("nqn", 'n', &cfg.nqn, nqn),
+		OPT_UINT("hmac", 'm', &cfg.hmac, hmac),
+		OPT_END()
+	};
+
+	err = argconfig_parse(argc, argv, desc, opts);
+	if (err)
+		return err;
+
+	if (cfg.hmac > 3) {
+		fprintf(stderr, "Invalid HMAC identifier %u\n", cfg.hmac);
+		return -EINVAL;
+	}
+	if (cfg.hmac > 0) {
+#ifdef OPENSSL
+		if (!cfg.nqn) {
+			hostnqn = nvmf_hostnqn_from_file();
+			if (!hostnqn) {
+				fprintf(stderr, "Could not read host NQN\n");
+				return -ENOENT;
+			}
+		} else {
+			hostnqn = cfg.nqn;
+		}
+		switch (cfg.hmac) {
+		case 1:
+			md = EVP_sha256();
+			if (!cfg.key_len)
+				cfg.key_len = 32;
+			else if (cfg.key_len != 32) {
+				fprintf(stderr, "Invalid key length %d for SHA(256)\n",
+					cfg.key_len);
+				return -EINVAL;
+			}
+			break;
+		case 2:
+			md = EVP_sha384();
+			if (!cfg.key_len)
+				cfg.key_len = 48;
+			else if (cfg.key_len != 48) {
+				fprintf(stderr, "Invalid key length %d for SHA(384)\n",
+					cfg.key_len);
+				return -EINVAL;
+			}
+			break;
+		case 3:
+			md = EVP_sha512();
+			if (!cfg.key_len)
+				cfg.key_len = 64;
+			else if (cfg.key_len != 64) {
+				fprintf(stderr, "Invalid key length %d for SHA(512)\n",
+					cfg.key_len);
+				return -EINVAL;
+			}
+			break;
+		default:
+			break;
+		}
+#else
+		fprintf(stderr, "HMAC transformation not supported; "\
+			"recompile with OpenSSL support.\n");
+		return -EINVAL;
+#endif
+	} else if (!cfg.key_len)
+		cfg.key_len = 32;
+
+	if (cfg.key_len != 32 && cfg.key_len != 48 && cfg.key_len != 64) {
+		fprintf(stderr, "Invalid key length %u\n", cfg.key_len);
+		return -EINVAL;
+	}
+	raw_secret = malloc(cfg.key_len);
+	if (!raw_secret)
+		return -ENOMEM;
+	if (!cfg.secret) {
+		if (getrandom(raw_secret, cfg.key_len, GRND_NONBLOCK) < 0)
+			return errno;
+	} else {
+		int secret_len = 0, i;
+		unsigned int c;
+
+		for (i = 0; i < strlen(cfg.secret); i+=2) {
+			if (sscanf(&cfg.secret[i], "%02x", &c) != 1) {
+				fprintf(stderr, "Invalid secret '%s'\n",
+					cfg.secret);
+				return -EINVAL;
+			}
+			raw_secret[secret_len++] = (unsigned char)c;
+		}
+		if (secret_len != cfg.key_len) {
+			fprintf(stderr, "Invalid key length (%d bytes)\n",
+				secret_len);
+			return -EINVAL;
+		}
+	}
+
+	if (md) {
+#ifdef OPENSSL
+		HMAC_CTX *hmac_ctx = HMAC_CTX_new();
+		const char hmac_seed[] = "NVMe-over-Fabrics";
+		unsigned int key_len;
+
+		ENGINE_load_builtin_engines();
+		ENGINE_register_all_complete();
+
+		HMAC_Init_ex(hmac_ctx, raw_secret, cfg.key_len,md, NULL);
+		HMAC_Update(hmac_ctx, (unsigned char *)hostnqn,
+			    strlen(hostnqn));
+		HMAC_Update(hmac_ctx, (unsigned char *)hmac_seed,
+			    strlen(hmac_seed));
+		HMAC_Final(hmac_ctx, key, &key_len);
+		HMAC_CTX_free(hmac_ctx);
+#endif
+	} else {
+		memcpy(key, raw_secret, cfg.key_len);
+	}
+
+	crc = crc32(crc, key, cfg.key_len);
+	key[cfg.key_len++] = crc & 0xff;
+	key[cfg.key_len++] = (crc >> 8) & 0xff;
+	key[cfg.key_len++] = (crc >> 16) & 0xff;
+	key[cfg.key_len++] = (crc >> 24) & 0xff;
+
+	memset(encoded_key, 0, sizeof(encoded_key));
+	base64_encode(key, cfg.key_len, encoded_key);
+
+	printf("DHHC-1:%02x:%s:\n", cfg.hmac, encoded_key);
+	return 0;
+}
+
 static int discover_cmd(int argc, char **argv, struct command *command, struct plugin *plugin)
 {
 	const char *desc = "Send Get Log Page request to Discovery Controller.";
diff --git a/util/argconfig.h b/util/argconfig.h
index 1a5c693..3b6da4c 100644
--- a/util/argconfig.h
+++ b/util/argconfig.h
@@ -100,6 +100,7 @@ enum argconfig_types {
 #define OPT_FMT(l, s, v, d)  OPT_STRING(l, s, "FMT", v, d)
 #define OPT_FILE(l, s, v, d) OPT_STRING(l, s, "FILE", v, d)
 #define OPT_LIST(l, s, v, d) OPT_STRING(l, s, "LIST", v, d)
+#define OPT_STR(l, s, v, d) OPT_STRING(l, s, "STRING", v, d)
 
 struct argconfig_commandline_options {
 	const char *option;
diff --git a/util/base64.c b/util/base64.c
new file mode 100644
index 0000000..d9b8f01
--- /dev/null
+++ b/util/base64.c
@@ -0,0 +1,62 @@
+/*
+ * base64.c - RFC4648-compliant base64 encoding
+ *
+ * Copyright (c) 2020 Hannes Reinecke, SUSE
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA  02110-1301, USA.
+ */
+
+#include <stdlib.h>
+
+static const char base64_table[65] =
+	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/**
+ * base64_encode() - base64-encode some bytes
+ * @src: the bytes to encode
+ * @srclen: number of bytes to encode
+ * @dst: (output) the base64-encoded string.  Not NUL-terminated.
+ *
+ * Encodes the input string using characters from the set [A-Za-z0-9+,].
+ * The encoded string is roughly 4/3 times the size of the input string.
+ *
+ * Return: length of the encoded string
+ */
+int base64_encode(const unsigned char *src, int srclen, char *dst)
+{
+	int i, bits = 0;
+	u_int32_t ac = 0;
+	char *cp = dst;
+
+	for (i = 0; i < srclen; i++) {
+		ac = (ac << 8) | src[i];
+		bits += 8;
+		do {
+			bits -= 6;
+			*cp++ = base64_table[(ac >> bits) & 0x3f];
+		} while (bits >= 6);
+	}
+	if (bits) {
+		*cp++ = base64_table[(ac << (6 - bits)) & 0x3f];
+		bits -= 6;
+	}
+	while (bits < 0) {
+		*cp++ = '=';
+		bits += 2;
+	}
+
+	return cp - dst;
+}
diff --git a/util/base64.h b/util/base64.h
new file mode 100644
index 0000000..374f5e6
--- /dev/null
+++ b/util/base64.h
@@ -0,0 +1,6 @@
+#ifndef _BASE64_H
+#define _BASE64_H
+
+int base64_encode(const unsigned char *src, int len, char *dst);
+
+#endif /* _BASE64_H */
-- 
2.31.1



  reply	other threads:[~2021-11-12 13:11 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-11-12 13:11 [PATCHv5 nvme-cli 0/3] nvme-cli authentication support Hannes Reinecke
2021-11-12 13:11 ` Hannes Reinecke [this message]
2021-11-12 13:11 ` [PATCH nvme-cli 2/3] Add 'check-dhchap-key' function Hannes Reinecke
2021-11-12 13:11 ` [PATCH nvme-cli 3/3] nvme-connect: Add 'dhchap-secret' and 'dhchap-ctrl-secret' arguments 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=20211112131111.97599-2-hare@suse.de \
    --to=hare@suse.de \
    --cc=hch@lst.de \
    --cc=keith.busch@wdc.com \
    --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.