[PATCH 1/2] Add 'gen-dhchap-key' command
Hannes Reinecke
hare at suse.de
Mon Sep 13 23:18:41 PDT 2021
On 9/13/21 9:26 PM, Sagi Grimberg wrote:
>
>
> On 9/13/21 1:24 PM, Hannes Reinecke wrote:
>> Add command 'gen-dhchap-key' to generate an DH-HMAC-CHAP host key
>> for NVMe in-band authentication.
>>
>> Signed-off-by: Hannes Reinecke <hare at suse.de>
>> ---
>> Documentation/nvme-gen-dhchap-key.txt | 52 +++++++++
>> Makefile | 17 ++-
>> nvme-builtin.h | 1 +
>> nvme.c | 155 ++++++++++++++++++++++++++
>> util/argconfig.h | 1 +
>> util/base64.c | 62 +++++++++++
>> util/base64.h | 6 +
>> 7 files changed, 293 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> ]
>
> Maybe it is a good idea to have some sane defaults create
> /etc/nvme/hostkey when installing nvme-cli? Or will it
> take it unconditionally?
>
That was the idea; will put some statements into the man-page.
But 'gen-dhchap-key' shouldn't be doing it automatically, but rather
behave similar to 'gen-hostnqn' (which also only prints out the host NQN
to stdout, and leaves is to the caller to forward that to
/etc/nvme/hostnqn).
>> +
>> +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 5fbdfd0..984b380 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 =
>> @@ -33,6 +35,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)
>> @@ -70,7 +84,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 79605f5..5791357 100644
>> --- a/nvme.c
>> +++ b/nvme.c
>> @@ -36,22 +36,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 "argconfig.h"
>> #include "fabrics.h"
>> @@ -5750,6 +5759,152 @@ 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 *hostnqn;
>> + 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;
>> +#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 = 32,
>> + .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", 'h', &cfg.hmac, hmac),
>> + OPT_END()
>> + };
>> +
>> + err = argconfig_parse(argc, argv, desc, opts);
>> + if (err)
>> + return err;
>> +
>> + 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;
>> + }
>> + 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();
>> + break;
>> + case 2:
>> + md = EVP_sha384();
>> + break;
>> + case 3:
>> + md = EVP_sha512();
>> + break;
>> + default:
>> + break;
>> + }
>> +#else
>> + fprintf(stderr, "HMAC transformation not supported; "\
>> + "recompile with OpenSSL support.\n");
>> + return -EINVAL;
>> +#endif
>> + }
>> + 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..328c8ea
>> --- /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) {
>> + bits -= 6;
>> + *cp++ = base64_table[(ac >> bits) & 0x3f];
>> + }
>> + 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 */
>>
>
>
> Overall looks good, do we want to allow different host keys
> for different NVM subsystems?
Sigh. It's the same issue as we currently have with allowing different
host NQNs for the same system.
In theory we could, and there is nothing in the current code which
forbids us to do so. But tooling around it will be getting arbitrarily
complex; remember, it's not only the different host keys for different
NVM subsystems, but possibly also the various NVM subsystem keys if
someone were to enable bi-directional authentication.
So would just stick with the simple case, and leave this as an exercise
to the reader.
Which reminds me; might be that there's an update required to handle
keys for bi-directional authentication ... hmm.
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare at suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Maxfeldstr. 5, 90409 Nürnberg
HRB 36809 (AG Nürnberg), Geschäftsführer: Felix Imendörffer
More information about the Linux-nvme
mailing list