[PATCH 10/12] nvmet: Implement basic In-Band Authentication

Sagi Grimberg sagi at grimberg.me
Wed Sep 29 03:37:46 PDT 2021



On 9/28/21 9:03 AM, Hannes Reinecke wrote:
> Implement NVMe-oF In-Band authentication according to NVMe TPAR 8006.
> This patch adds three additional configfs entries 'dhchap_key', 'dhchap_ctrl_key',
> and 'dhchap_hash' to the 'host' configfs directory. The 'dhchap_key' and
> 'dhchap_ctrl_key' entries need to be in the ASCII format as specified in
> NVMe Base Specification v2.0 section 8.13.5.8 'Secret representation'.
> 'dhchap_hash' defaults to 'hmac(sha256)', and can be changed to switch to
> a different HMAC algorithm.
> 
> Signed-off-by: Hannes Reinecke <hare at suse.de>
> ---
>   drivers/nvme/target/Kconfig            |  11 +
>   drivers/nvme/target/Makefile           |   1 +
>   drivers/nvme/target/admin-cmd.c        |   4 +
>   drivers/nvme/target/auth.c             | 345 ++++++++++++++++++
>   drivers/nvme/target/configfs.c         | 102 +++++-
>   drivers/nvme/target/core.c             |   8 +
>   drivers/nvme/target/fabrics-cmd-auth.c | 466 +++++++++++++++++++++++++
>   drivers/nvme/target/fabrics-cmd.c      |  30 +-
>   drivers/nvme/target/nvmet.h            |  67 ++++
>   9 files changed, 1031 insertions(+), 3 deletions(-)
>   create mode 100644 drivers/nvme/target/auth.c
>   create mode 100644 drivers/nvme/target/fabrics-cmd-auth.c
> 
> diff --git a/drivers/nvme/target/Kconfig b/drivers/nvme/target/Kconfig
> index 973561c93888..70f3c385fc9f 100644
> --- a/drivers/nvme/target/Kconfig
> +++ b/drivers/nvme/target/Kconfig
> @@ -83,3 +83,14 @@ config NVME_TARGET_TCP
>   	  devices over TCP.
>   
>   	  If unsure, say N.
> +
> +config NVME_TARGET_AUTH
> +	bool "NVMe over Fabrics In-band Authentication support"
> +	depends on NVME_TARGET
> +	select CRYPTO_HMAC
> +	select CRYPTO_SHA256
> +	select CRYPTO_SHA512
> +	help
> +	  This enables support for NVMe over Fabrics In-band Authentication
> +
> +	  If unsure, say N.
> diff --git a/drivers/nvme/target/Makefile b/drivers/nvme/target/Makefile
> index 9837e580fa7e..c66820102493 100644
> --- a/drivers/nvme/target/Makefile
> +++ b/drivers/nvme/target/Makefile
> @@ -13,6 +13,7 @@ nvmet-y		+= core.o configfs.o admin-cmd.o fabrics-cmd.o \
>   			discovery.o io-cmd-file.o io-cmd-bdev.o
>   nvmet-$(CONFIG_NVME_TARGET_PASSTHRU)	+= passthru.o
>   nvmet-$(CONFIG_BLK_DEV_ZONED)		+= zns.o
> +nvmet-$(CONFIG_NVME_TARGET_AUTH)	+= fabrics-cmd-auth.o auth.o
>   nvme-loop-y	+= loop.o
>   nvmet-rdma-y	+= rdma.o
>   nvmet-fc-y	+= fc.o
> diff --git a/drivers/nvme/target/admin-cmd.c b/drivers/nvme/target/admin-cmd.c
> index aa6d84d8848e..868d65c869cd 100644
> --- a/drivers/nvme/target/admin-cmd.c
> +++ b/drivers/nvme/target/admin-cmd.c
> @@ -1008,6 +1008,10 @@ u16 nvmet_parse_admin_cmd(struct nvmet_req *req)
>   
>   	if (nvme_is_fabrics(cmd))
>   		return nvmet_parse_fabrics_cmd(req);
> +
> +	if (unlikely(!nvmet_check_auth_status(req)))
> +		return NVME_SC_AUTH_REQUIRED | NVME_SC_DNR;
> +
>   	if (nvmet_req_subsys(req)->type == NVME_NQN_DISC)
>   		return nvmet_parse_discovery_cmd(req);
>   
> diff --git a/drivers/nvme/target/auth.c b/drivers/nvme/target/auth.c
> new file mode 100644
> index 000000000000..7247a7b97644
> --- /dev/null
> +++ b/drivers/nvme/target/auth.c
> @@ -0,0 +1,345 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * NVMe over Fabrics DH-HMAC-CHAP authentication.
> + * Copyright (c) 2020 Hannes Reinecke, SUSE Software Solutions.
> + * All rights reserved.
> + */
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/slab.h>
> +#include <linux/err.h>
> +#include <crypto/hash.h>
> +#include <linux/crc32.h>
> +#include <linux/base64.h>
> +#include <linux/ctype.h>
> +#include <linux/random.h>
> +#include <asm/unaligned.h>
> +
> +#include "nvmet.h"
> +#include "../host/auth.h"
> +
> +int nvmet_auth_set_host_key(struct nvmet_host *host, const char *secret)
> +{
> +	if (sscanf(secret, "DHHC-1:%hhd:%*s", &host->dhchap_key_hash) != 1)
> +		return -EINVAL;
> +	if (host->dhchap_key_hash > 3) {
> +		pr_warn("Invalid DH-HMAC-CHAP hash id %d\n",
> +			 host->dhchap_key_hash);
> +		return -EINVAL;
> +	}
> +	if (host->dhchap_key_hash > 0) {
> +		/* Validate selected hash algorithm */
> +		const char *hmac = nvme_auth_hmac_name(host->dhchap_key_hash);
> +
> +		if (!crypto_has_shash(hmac, 0, 0)) {
> +			pr_err("DH-HMAC-CHAP hash %s unsupported\n", hmac);
> +			host->dhchap_key_hash = -1;
> +			return -ENOTSUPP;
> +		}
> +		/* Use this hash as default */
> +		if (!host->dhchap_hash_id)
> +			host->dhchap_hash_id = host->dhchap_key_hash;
> +	}
> +	host->dhchap_secret = kstrdup(secret, GFP_KERNEL);
> +	if (!host->dhchap_secret)
> +		return -ENOMEM;
> +	/* Default to SHA256 */
> +	if (!host->dhchap_hash_id)
> +		host->dhchap_hash_id = NVME_AUTH_DHCHAP_SHA256;
> +
> +	pr_debug("Using hash %s\n",
> +		 nvme_auth_hmac_name(host->dhchap_hash_id));
> +	return 0;
> +}
> +
> +int nvmet_auth_set_ctrl_key(struct nvmet_host *host, const char *secret)
> +{
> +	unsigned char dhchap_key_hash;
> +
> +	if (sscanf(secret, "DHHC-1:%hhd:%*s", &dhchap_key_hash) != 1)
> +		return -EINVAL;
> +	if (dhchap_key_hash > 3) {
> +		pr_warn("Invalid DH-HMAC-CHAP hash id %d\n",
> +			 dhchap_key_hash);
> +		return -EINVAL;
> +	}
> +	if (dhchap_key_hash > 0) {
> +		/* Validate selected hash algorithm */
> +		const char *hmac = nvme_auth_hmac_name(dhchap_key_hash);
> +
> +		if (!crypto_has_shash(hmac, 0, 0)) {
> +			pr_err("DH-HMAC-CHAP hash %s unsupported\n", hmac);
> +			return -ENOTSUPP;
> +		}
> +	}
> +	host->dhchap_ctrl_secret = kstrdup(secret, GFP_KERNEL);
> +	return host->dhchap_ctrl_secret ? 0 : -ENOMEM;
> +}
> +
> +int nvmet_setup_auth(struct nvmet_ctrl *ctrl)
> +{
> +	int ret = 0;
> +	struct nvmet_host_link *p;
> +	struct nvmet_host *host = NULL;
> +	const char *hash_name;
> +
> +	down_read(&nvmet_config_sem);
> +	if (ctrl->subsys->type == NVME_NQN_DISC)
> +		goto out_unlock;
> +
> +	if (ctrl->subsys->allow_any_host)
> +		goto out_unlock;
> +
> +	list_for_each_entry(p, &ctrl->subsys->hosts, entry) {
> +		pr_debug("check %s\n", nvmet_host_name(p->host));
> +		if (strcmp(nvmet_host_name(p->host), ctrl->hostnqn))
> +			continue;
> +		host = p->host;
> +		break;
> +	}
> +	if (!host) {
> +		pr_debug("host %s not found\n", ctrl->hostnqn);
> +		ret = -EPERM;
> +		goto out_unlock;
> +	}
> +	if (!host->dhchap_secret) {
> +		pr_debug("No authentication provided\n");
> +		goto out_unlock;
> +	}
> +
> +	if (ctrl->shash_tfm) {
> +		if (host->dhchap_hash_id == ctrl->shash_id) {
> +			pr_debug("Re-use existing hash ID %d\n",
> +				 ctrl->shash_id);
> +			goto out_unlock;
> +		}
> +		crypto_free_shash(ctrl->shash_tfm);
> +		ctrl->shash_tfm = NULL;
> +	}
> +
> +	hash_name = nvme_auth_hmac_name(host->dhchap_hash_id);
> +	if (!hash_name) {
> +		pr_warn("Hash ID %d invalid\n", host->dhchap_hash_id);
> +		ret = -EINVAL;
> +		goto out_unlock;
> +	}
> +	ctrl->shash_tfm = crypto_alloc_shash(hash_name, 0,
> +					     CRYPTO_ALG_ALLOCATES_MEMORY);
> +	if (IS_ERR(ctrl->shash_tfm)) {
> +		pr_err("failed to allocate shash %s\n", hash_name);
> +		ret = PTR_ERR(ctrl->shash_tfm);
> +		ctrl->shash_tfm = NULL;
> +		goto out_unlock;
> +	}
> +	ctrl->shash_id = host->dhchap_hash_id;
> +
> +	/* Skip the 'DHHC-1:XX:' prefix */
> +	ctrl->dhchap_key = nvme_auth_extract_secret(host->dhchap_secret + 10,
> +						    &ctrl->dhchap_key_len);
> +	if (IS_ERR(ctrl->dhchap_key)) {
> +		ret = PTR_ERR(ctrl->dhchap_key);
> +		pr_debug("failed to extract host key, error %d\n", ret);
> +		ctrl->dhchap_key = NULL;
> +		goto out_free_hash;
> +	}
> +	pr_debug("%s: using key %*ph\n", __func__,
> +		 (int)ctrl->dhchap_key_len, ctrl->dhchap_key);
> +
> +	if (!host->dhchap_ctrl_secret)
> +		goto out_unlock;
> +
> +	ctrl->dhchap_ctrl_key = nvme_auth_extract_secret(host->dhchap_ctrl_secret + 10,
> +							 &ctrl->dhchap_ctrl_key_len);
> +	if (IS_ERR(ctrl->dhchap_ctrl_key)) {
> +		ret = PTR_ERR(ctrl->dhchap_ctrl_key);
> +		pr_debug("failed to extract ctrl key, error %d\n", ret);
> +		ctrl->dhchap_ctrl_key = NULL;
> +	}
> +
> +out_free_hash:
> +	if (ret) {
> +		if (ctrl->dhchap_key) {
> +			kfree_sensitive(ctrl->dhchap_key);
> +			ctrl->dhchap_key = NULL;
> +		}
> +		crypto_free_shash(ctrl->shash_tfm);
> +		ctrl->shash_tfm = NULL;
> +		ctrl->shash_id = 0;
> +	}
> +out_unlock:
> +	up_read(&nvmet_config_sem);
> +
> +	return ret;
> +}
> +
> +void nvmet_auth_sq_free(struct nvmet_sq *sq)
> +{
> +	kfree(sq->dhchap_c1);
> +	sq->dhchap_c1 = NULL;
> +	kfree(sq->dhchap_c2);
> +	sq->dhchap_c2 = NULL;
> +	kfree(sq->dhchap_skey);
> +	sq->dhchap_skey = NULL;
> +}
> +
> +void nvmet_destroy_auth(struct nvmet_ctrl *ctrl)
> +{
> +	if (ctrl->shash_tfm) {
> +		crypto_free_shash(ctrl->shash_tfm);
> +		ctrl->shash_tfm = NULL;
> +		ctrl->shash_id = 0;
> +	}
> +	if (ctrl->dhchap_key) {
> +		kfree_sensitive(ctrl->dhchap_key);
> +		ctrl->dhchap_key = NULL;
> +	}
> +	if (ctrl->dhchap_ctrl_key) {
> +		kfree_sensitive(ctrl->dhchap_ctrl_key);
> +		ctrl->dhchap_ctrl_key = NULL;
> +	}
> +}
> +
> +bool nvmet_check_auth_status(struct nvmet_req *req)
> +{
> +	if (req->sq->ctrl->shash_tfm &&
> +	    !req->sq->authenticated)
> +		return false;
> +	return true;
> +}
> +
> +int nvmet_auth_host_hash(struct nvmet_req *req, u8 *response,
> +			 unsigned int shash_len)
> +{
> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;
> +	SHASH_DESC_ON_STACK(shash, ctrl->shash_tfm);
> +	u8 *challenge = req->sq->dhchap_c1, *host_response;
> +	u8 buf[4];
> +	int ret;
> +
> +	if (shash_len != ctrl->dhchap_key_len)
> +		return -EINVAL;

Is this condition correct? the shash_len is sent by the host
and afaict the host is setting it by what the controller sent
in the challenge. the ctrl->dhchap_key_len is taken from
nvme_auth_extract_secret.

In other words, if the nvmet host is set to use hmac(sha512) then
authentication fails, regardless of the key.

I think we need a set of blktests to sanity test different hash,
dh_group, secrect-length, dhchap_key and dhchap_ctrl_key combinations.

The code is not trivial to follow with the different transformations, 
hashes etc...



More information about the Linux-nvme mailing list