]> www.infradead.org Git - users/sagi/nvme-cli.git/commitdiff
Add 'gen-dhchap-key' command
authorHannes Reinecke <hare@suse.de>
Thu, 17 Dec 2020 08:43:50 +0000 (09:43 +0100)
committerHannes Reinecke <hare@suse.de>
Thu, 18 Nov 2021 14:40:27 +0000 (15:40 +0100)
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 [new file with mode: 0644]
Makefile
meson.build
nvme-builtin.h
nvme.c
util/argconfig.h
util/base64.c [new file with mode: 0644]
util/base64.h [new file with mode: 0644]
util/meson.build

diff --git a/Documentation/nvme-gen-dhchap-key.txt b/Documentation/nvme-gen-dhchap-key.txt
new file mode 100644 (file)
index 0000000..eecde76
--- /dev/null
@@ -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
index eef169621346bb38f084e3b6923de57dffc25672..17a6a93596bbba18b9724ab8df9b7f42fac1c22c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,8 @@ LIBHUGETLBFS = $(shell $(LD) -o /dev/null -lhugetlbfs >/dev/null 2>&1; echo $$?)
 HAVE_SYSTEMD = $(shell pkg-config --exists libsystemd  --atleast-version=242; echo $$?)
 LIBJSONC_14 = $(shell pkg-config --atleast-version=0.14 json-c; echo $$?)
 LIBJSONC = $(shell pkg-config json-c; 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 =
@@ -38,6 +40,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)
@@ -89,7 +103,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
index fcfe446cb35fc86bec47d79a484505c49c64f695..8efc85251520198a05b7efe34d54d2d56170b2fa 100644 (file)
@@ -46,6 +46,13 @@ endif
 libhugetlbfs = dependency('hugetlbfs', required: false)
 conf.set('LIBHUGETLBFS', libhugetlbfs.found(), description: 'Is libhugetlbfs required?')
 
+# Check for zlib availability
+libz_dep = dependency('zlib', required: true)
+
+# Check for open-ssl availability
+libopenssl = dependency('openssl', required: false)
+conf.set('OPENSSL', libopenssl.found(), description: 'Is open-ssl required?')
+
 # Set the nvme-cli version
 conf.set('NVME_VERSION', '"' + meson.project_version() + '"')
 
@@ -169,7 +176,7 @@ subdir('Documentation')
 executable(
   'nvme',
   sources,
-  dependencies: [ libnvme_dep, libuuid, json_c ],
+  dependencies: [ libnvme_dep, libuuid, json_c, libz_dep, libopenssl ],
   include_directories: incdir,
   install: true,
   install_dir: get_option('sbindir')
index fe00f44f38843aca7193a2abbb6cff3c9d65e724..e8df46a55011ed4c43ef93e164799723207e2b43 100644 (file)
@@ -87,6 +87,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 27cc50678c67e3e874a3692ad03040ed67384f58..8820cd27a48c05c5f50f2be6aaa77395416c980a 100644 (file)
--- a/nvme.c
+++ b/nvme.c
 #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 "util/base64.h"
 
 #include "util/argconfig.h"
 #include "fabrics.h"
@@ -6634,6 +6643,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.";
index 1a5c693676cea235fb32a1433b748ab613e5f5ac..3b6da4cd4ff5996a22bbd93c32bbadca3c148cf9 100644 (file)
@@ -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 (file)
index 0000000..d9b8f01
--- /dev/null
@@ -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 (file)
index 0000000..374f5e6
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef _BASE64_H
+#define _BASE64_H
+
+int base64_encode(const unsigned char *src, int len, char *dst);
+
+#endif /* _BASE64_H */
index 9b8e7cf3942d6c34b4632928b1f144ac57f0ba5a..e525f0e692b7744df46836276a789801cf852392 100644 (file)
@@ -4,4 +4,5 @@ sources += [
   'util/json.c',
   'util/parser.c',
   'util/suffix.c',
+  'util/base64.c',
 ]