diff mbox series

[09/11] nvmet: Implement basic In-Band Authentication

Message ID 20210716110428.9727-10-hare@suse.de
State New
Headers show
Series nvme: In-band authentication support | expand

Commit Message

Hannes Reinecke July 16, 2021, 11:04 a.m. UTC
Implement support for NVMe-oF In-Band authentication. This patch
adds two additional configfs entries 'dhchap_key' and 'dhchap_hash'
to the 'host' configfs directory. The 'dhchap_key' needs to be
specified in the format outlined in the base spec.
Augmented challenge support is not implemented, and concatenation
with TLS encryption is not supported.

Signed-off-by: Hannes Reinecke <hare@suse.de>
---
 drivers/nvme/target/Kconfig            |  10 +
 drivers/nvme/target/Makefile           |   1 +
 drivers/nvme/target/admin-cmd.c        |   4 +
 drivers/nvme/target/auth.c             | 352 +++++++++++++++++++
 drivers/nvme/target/configfs.c         |  71 +++-
 drivers/nvme/target/core.c             |   8 +
 drivers/nvme/target/fabrics-cmd-auth.c | 460 +++++++++++++++++++++++++
 drivers/nvme/target/fabrics-cmd.c      |  30 +-
 drivers/nvme/target/nvmet.h            |  71 ++++
 9 files changed, 1004 insertions(+), 3 deletions(-)
 create mode 100644 drivers/nvme/target/auth.c
 create mode 100644 drivers/nvme/target/fabrics-cmd-auth.c

Comments

Stephan Mueller July 17, 2021, 4:49 p.m. UTC | #1
Am Freitag, 16. Juli 2021, 13:04:26 CEST schrieb Hannes Reinecke:

Hi Hannes,

> Implement support for NVMe-oF In-Band authentication. This patch

> adds two additional configfs entries 'dhchap_key' and 'dhchap_hash'

> to the 'host' configfs directory. The 'dhchap_key' needs to be

> specified in the format outlined in the base spec.

> Augmented challenge support is not implemented, and concatenation

> with TLS encryption is not supported.

> 

> Signed-off-by: Hannes Reinecke <hare@suse.de>

> ---

>  drivers/nvme/target/Kconfig            |  10 +

>  drivers/nvme/target/Makefile           |   1 +

>  drivers/nvme/target/admin-cmd.c        |   4 +

>  drivers/nvme/target/auth.c             | 352 +++++++++++++++++++

>  drivers/nvme/target/configfs.c         |  71 +++-

>  drivers/nvme/target/core.c             |   8 +

>  drivers/nvme/target/fabrics-cmd-auth.c | 460 +++++++++++++++++++++++++

>  drivers/nvme/target/fabrics-cmd.c      |  30 +-

>  drivers/nvme/target/nvmet.h            |  71 ++++

>  9 files changed, 1004 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 4be2ececbc45..d5656ef1559e 100644

> --- a/drivers/nvme/target/Kconfig

> +++ b/drivers/nvme/target/Kconfig

> @@ -85,3 +85,13 @@ 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_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 0cb98f2bbc8c..320cefc64ee0 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..00c7d051dfb1

> --- /dev/null

> +++ b/drivers/nvme/target/auth.c

> @@ -0,0 +1,352 @@

> +// 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 <crypto/kpp.h>

> +#include <crypto/dh.h>

> +#include <crypto/ffdhe.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_warn("DH-HMAC-CHAP hash %s unsupported\n", hmac);

> +			host->dhchap_key_hash = -1;

> +			return -EAGAIN;

> +		}

> +		/* 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);


Just like before - are you sure that the secret is an ASCII string and no 
binary blob?

> +	if (!host->dhchap_secret)

> +		return -ENOMEM;

> +	/* Default to SHA256 */

> +	if (!host->dhchap_hash_id)

> +		host->dhchap_hash_id = NVME_AUTH_DHCHAP_HASH_SHA256;

> +

> +	pr_debug("Using hash %s\n",

> +		 nvme_auth_hmac_name(host->dhchap_hash_id));

> +	return 0;

> +}

> +

> +int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, int dhgroup_id)

> +{

> +	int ret = -ENOTSUPP;

> +

> +	if (dhgroup_id == NVME_AUTH_DHCHAP_DHGROUP_NULL)

> +		return 0;

> +

> +	return ret;

> +}

> +

> +int nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req)

> +{

> +	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;

> +

> +	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;

> +	}

> +

> +	hash_name = nvme_auth_hmac_name(host->dhchap_hash_id);

> +	if (!hash_name) {

> +		pr_debug("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_debug("failed to allocate shash %s\n", hash_name);

> +		ret = PTR_ERR(ctrl->shash_tfm);

> +		ctrl->shash_tfm = NULL;

> +		goto out_unlock;

> +	}

> +

> +	ctrl->dhchap_key = nvme_auth_extract_secret(host->dhchap_secret,

> +						    &ctrl->dhchap_key_len);

> +	if (IS_ERR(ctrl->dhchap_key)) {

> +		pr_debug("failed to extract host key, error %d\n", ret);

> +		ret = PTR_ERR(ctrl->dhchap_key);

> +		ctrl->dhchap_key = NULL;

> +		goto out_free_hash;

> +	}

> +	if (host->dhchap_key_hash) {

> +		struct crypto_shash *key_tfm;

> +

> +		hash_name = nvme_auth_hmac_name(host->dhchap_key_hash);

> +		key_tfm = crypto_alloc_shash(hash_name, 0, 0);

> +		if (IS_ERR(key_tfm)) {

> +			ret = PTR_ERR(key_tfm);

> +			goto out_free_hash;

> +		} else {

> +			SHASH_DESC_ON_STACK(shash, key_tfm);

> +

> +			shash->tfm = key_tfm;

> +			ret = crypto_shash_setkey(key_tfm, ctrl->dhchap_key,

> +						  ctrl->dhchap_key_len);

> +			crypto_shash_init(shash);

> +			crypto_shash_update(shash, ctrl->subsys->subsysnqn,

> +					    strlen(ctrl->subsys->subsysnqn));

> +			crypto_shash_update(shash, "NVMe-over-Fabrics", 17);

> +			crypto_shash_final(shash, ctrl->dhchap_key);

> +			crypto_free_shash(key_tfm);

> +		}

> +	}

> +	pr_debug("%s: using key %*ph\n", __func__,

> +		 (int)ctrl->dhchap_key_len, ctrl->dhchap_key);

> +	ret = crypto_shash_setkey(ctrl->shash_tfm, ctrl->dhchap_key,


Is it truly necessary to keep the key around in ctrl->dhchap_key? It looks to 
me that this buffer is only used here and thus could be turned into a local 
variable. Keys flying around in memory is not a good idea. :-)

> +				  ctrl->dhchap_key_len);

> +out_free_hash:

> +	if (ret) {

> +		if (ctrl->dhchap_key) {

> +			kfree(ctrl->dhchap_key);


kfree_sensitive?

> +			ctrl->dhchap_key = NULL;

> +		}

> +		crypto_free_shash(ctrl->shash_tfm);

> +		ctrl->shash_tfm = NULL;

> +	}

> +out_unlock:

> +	up_read(&nvmet_config_sem);

> +

> +	return ret;

> +}

> +

> +void nvmet_auth_sq_free(struct nvmet_sq *sq)

> +{

> +	if (sq->dhchap_c1)

> +		kfree(sq->dhchap_c1);

> +	if (sq->dhchap_c2)

> +		kfree(sq->dhchap_c2);

> +	if (sq->dhchap_skey)

> +		kfree(sq->dhchap_skey);


kfree_sensitive?

> +}

> +

> +void nvmet_reset_auth(struct nvmet_ctrl *ctrl)

> +{

> +	if (ctrl->shash_tfm) {

> +		crypto_free_shash(ctrl->shash_tfm);

> +		ctrl->shash_tfm = NULL;

> +	}

> +	if (ctrl->dh_tfm) {

> +		crypto_free_kpp(ctrl->dh_tfm);

> +		ctrl->dh_tfm = NULL;

> +	}

> +	if (ctrl->dhchap_key) {

> +		kfree(ctrl->dhchap_key);


kfree_sensitive?

> +		ctrl->dhchap_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;

> +	u8 buf[4];

> +	int ret;

> +

> +	if (ctrl->dh_gid != NVME_AUTH_DHCHAP_DHGROUP_NULL) {

> +		ret = -ENOTSUPP;

> +		goto out;

> +	}

> +

> +	shash->tfm = ctrl->shash_tfm;

> +	ret = crypto_shash_init(shash);

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, challenge, shash_len);

> +	if (ret)

> +		goto out;

> +	put_unaligned_le32(req->sq->dhchap_s1, buf);

> +	ret = crypto_shash_update(shash, buf, 4);

> +	if (ret)

> +		goto out;

> +	put_unaligned_le16(req->sq->dhchap_tid, buf);

> +	ret = crypto_shash_update(shash, buf, 2);

> +	if (ret)

> +		goto out;

> +	memset(buf, 0, 4);

> +	ret = crypto_shash_update(shash, buf, 1);

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, "HostHost", 8);

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, ctrl->hostnqn, strlen(ctrl->hostnqn));

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, buf, 1);

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, ctrl->subsysnqn,

> +				  strlen(ctrl->subsysnqn));

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_final(shash, response);

> +out:

> +	if (challenge != req->sq->dhchap_c1)

> +		kfree(challenge);

> +	return 0;

> +}

> +

> +int nvmet_auth_ctrl_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_c2;

> +	u8 buf[4];

> +	int ret;

> +

> +	pr_debug("%s: ctrl %d hash seq %d transaction %u\n", __func__,

> +		 ctrl->cntlid, req->sq->dhchap_s2, req->sq->dhchap_tid);

> +	pr_debug("%s: ctrl %d challenge %*ph\n", __func__,

> +		 ctrl->cntlid, shash_len, req->sq->dhchap_c2);

> +	pr_debug("%s: ctrl %d subsysnqn %s\n", __func__,

> +		 ctrl->cntlid, ctrl->subsysnqn);

> +	pr_debug("%s: ctrl %d hostnqn %s\n", __func__,

> +		 ctrl->cntlid, ctrl->hostnqn);

> +

> +	if (ctrl->dh_gid != NVME_AUTH_DHCHAP_DHGROUP_NULL) {

> +		ret = -ENOTSUPP;

> +		goto out;

> +	}

> +

> +	shash->tfm = ctrl->shash_tfm;

> +	ret = crypto_shash_init(shash);

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, challenge, shash_len);

> +	if (ret)

> +		goto out;

> +	put_unaligned_le32(req->sq->dhchap_s2, buf);

> +	ret = crypto_shash_update(shash, buf, 4);

> +	if (ret)

> +		goto out;

> +	put_unaligned_le16(req->sq->dhchap_tid, buf);

> +	ret = crypto_shash_update(shash, buf, 2);

> +	if (ret)

> +		goto out;

> +	memset(buf, 0, 4);

> +	ret = crypto_shash_update(shash, buf, 1);

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, "Controller", 10);

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, ctrl->subsysnqn,

> +			    strlen(ctrl->subsysnqn));

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, buf, 1);

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, ctrl->hostnqn, strlen(ctrl->hostnqn));

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_final(shash, response);

> +out:

> +	if (challenge != req->sq->dhchap_c2)

> +		kfree(challenge);

> +	return 0;

> +}

> +

> +int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,

> +			    u8 *pkey, int pkey_size)

> +{

> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

> +	struct kpp_request *kpp_req;

> +	struct crypto_wait wait;

> +	struct scatterlist src, dst;

> +	int ret;

> +

> +	req->sq->dhchap_skey_len =

> +		nvme_auth_dhgroup_privkey_size(ctrl->dh_gid);

> +	req->sq->dhchap_skey = kzalloc(req->sq->dhchap_skey_len, GFP_KERNEL);

> +	if (!req->sq->dhchap_skey)

> +		return -ENOMEM;

> +	kpp_req = kpp_request_alloc(ctrl->dh_tfm, GFP_KERNEL);

> +	if (!kpp_req) {

> +		kfree(req->sq->dhchap_skey);

> +		req->sq->dhchap_skey = NULL;

> +		return -ENOMEM;

> +	}

> +

> +	pr_debug("%s: host public key %*ph\n", __func__,

> +		 (int)pkey_size, pkey);

> +	crypto_init_wait(&wait);

> +	sg_init_one(&src, pkey, pkey_size);

> +	kpp_request_set_input(kpp_req, &src, pkey_size);

> +	sg_init_one(&dst, req->sq->dhchap_skey,

> +		req->sq->dhchap_skey_len);

> +	kpp_request_set_output(kpp_req, &dst, req->sq->dhchap_skey_len);

> +	kpp_request_set_callback(kpp_req, CRYPTO_TFM_REQ_MAY_BACKLOG,

> +				 crypto_req_done, &wait);

> +

> +	ret = crypto_wait_req(crypto_kpp_compute_shared_secret(kpp_req), &wait);

> +	kpp_request_free(kpp_req);

> +	if (ret)

> +		pr_debug("failed to compute shared secred, err %d\n", ret);

> +	else

> +		pr_debug("%s: shared secret %*ph\n", __func__,

> +			 (int)req->sq->dhchap_skey_len,

> +			 req->sq->dhchap_skey);

> +

> +	return ret;

> +}

> diff --git a/drivers/nvme/target/configfs.c b/drivers/nvme/target/configfs.c

> index 273555127188..e0760911a761 100644

> --- a/drivers/nvme/target/configfs.c

> +++ b/drivers/nvme/target/configfs.c

> @@ -11,8 +11,13 @@

>  #include <linux/ctype.h>

>  #include <linux/pci.h>

>  #include <linux/pci-p2pdma.h>

> +#include <crypto/hash.h>

> +#include <crypto/kpp.h>

> 

>  #include "nvmet.h"

> +#ifdef CONFIG_NVME_TARGET_AUTH

> +#include "../host/auth.h"

> +#endif

> 

>  static const struct config_item_type nvmet_host_type;

>  static const struct config_item_type nvmet_subsys_type;

> @@ -1656,10 +1661,71 @@ static const struct config_item_type

> nvmet_ports_type = { static struct config_group nvmet_subsystems_group;

>  static struct config_group nvmet_ports_group;

> 

> -static void nvmet_host_release(struct config_item *item)

> +#ifdef CONFIG_NVME_TARGET_AUTH

> +static ssize_t nvmet_host_dhchap_key_show(struct config_item *item,

> +		char *page)

> +{

> +	u8 *dhchap_secret = to_host(item)->dhchap_secret;

> +

> +	if (!dhchap_secret)

> +		return sprintf(page, "\n");

> +	return sprintf(page, "%s\n", dhchap_secret);

> +}

> +

> +static ssize_t nvmet_host_dhchap_key_store(struct config_item *item,

> +		const char *page, size_t count)

>  {

>  	struct nvmet_host *host = to_host(item);

> +	int ret;

> 

> +	ret = nvmet_auth_set_host_key(host, page);

> +	if (ret < 0)

> +		return ret;

> +	return count;

> +}

> +

> +CONFIGFS_ATTR(nvmet_host_, dhchap_key);

> +

> +static ssize_t nvmet_host_dhchap_hash_show(struct config_item *item,

> +		char *page)

> +{

> +	struct nvmet_host *host = to_host(item);

> +	const char *hash_name = nvme_auth_hmac_name(host->dhchap_hash_id);

> +

> +	return sprintf(page, "%s\n", hash_name ? hash_name : "none");

> +}

> +

> +static ssize_t nvmet_host_dhchap_hash_store(struct config_item *item,

> +		const char *page, size_t count)

> +{

> +	struct nvmet_host *host = to_host(item);

> +	int hmac_id;

> +

> +	hmac_id = nvme_auth_hmac_id(page);

> +	if (hmac_id < 0)

> +		return -EINVAL;

> +	if (!crypto_has_shash(nvme_auth_hmac_name(hmac_id), 0, 0))

> +		return -ENOTSUPP;

> +	host->dhchap_hash_id = hmac_id;

> +	return count;

> +}

> +

> +CONFIGFS_ATTR(nvmet_host_, dhchap_hash);

> +

> +static struct configfs_attribute *nvmet_host_attrs[] = {

> +	&nvmet_host_attr_dhchap_key,

> +	&nvmet_host_attr_dhchap_hash,

> +	NULL,

> +};

> +#endif /* CONFIG_NVME_TARGET_AUTH */

> +

> +static void nvmet_host_release(struct config_item *item)

> +{

> +	struct nvmet_host *host = to_host(item);

> +#ifdef CONFIG_NVME_TARGET_AUTH

> +	if (host->dhchap_secret)

> +		kfree(host->dhchap_secret);

> +#endif

>  	kfree(host);

>  }

> 

> @@ -1669,6 +1735,9 @@ static struct configfs_item_operations

> nvmet_host_item_ops = {

> 

>  static const struct config_item_type nvmet_host_type = {

>  	.ct_item_ops		= &nvmet_host_item_ops,

> +#ifdef CONFIG_NVME_TARGET_AUTH

> +	.ct_attrs		= nvmet_host_attrs,

> +#endif

>  	.ct_owner		= THIS_MODULE,

>  };

> 

> diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c

> index 163f7dc1a929..b5d7971f566b 100644

> --- a/drivers/nvme/target/core.c

> +++ b/drivers/nvme/target/core.c

> @@ -793,6 +793,7 @@ void nvmet_sq_destroy(struct nvmet_sq *sq)

>  	wait_for_completion(&sq->confirm_done);

>  	wait_for_completion(&sq->free_done);

>  	percpu_ref_exit(&sq->ref);

> +	nvmet_auth_sq_free(sq);

> 

>  	if (ctrl) {

>  		/*

> @@ -1264,6 +1265,11 @@ u16 nvmet_check_ctrl_status(struct nvmet_req *req)

>  		       req->cmd->common.opcode, req->sq->qid);

>  		return NVME_SC_CMD_SEQ_ERROR | NVME_SC_DNR;

>  	}

> +

> +	if (unlikely(!nvmet_check_auth_status(req))) {

> +		pr_warn("qid %d not authenticated\n", req->sq->qid);

> +		return NVME_SC_AUTH_REQUIRED | NVME_SC_DNR;

> +	}

>  	return 0;

>  }

> 

> @@ -1456,6 +1462,8 @@ static void nvmet_ctrl_free(struct kref *ref)

>  	flush_work(&ctrl->async_event_work);

>  	cancel_work_sync(&ctrl->fatal_err_work);

> 

> +	nvmet_reset_auth(ctrl);

> +

>  	ida_simple_remove(&cntlid_ida, ctrl->cntlid);

> 

>  	nvmet_async_events_free(ctrl);

> diff --git a/drivers/nvme/target/fabrics-cmd-auth.c

> b/drivers/nvme/target/fabrics-cmd-auth.c new file mode 100644

> index 000000000000..962f9f5e9d89

> --- /dev/null

> +++ b/drivers/nvme/target/fabrics-cmd-auth.c

> @@ -0,0 +1,460 @@

> +// SPDX-License-Identifier: GPL-2.0

> +/*

> + * NVMe over Fabrics DH-HMAC-CHAP authentication command handling.

> + * Copyright (c) 2020 Hannes Reinecke, SUSE Software Solutions.

> + * All rights reserved.

> + */

> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

> +#include <linux/blkdev.h>

> +#include <linux/random.h>

> +#include <crypto/hash.h>

> +#include <crypto/kpp.h>

> +#include "nvmet.h"

> +#include "../host/auth.h"

> +

> +void nvmet_init_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req)

> +{

> +	/* Initialize in-band authentication */

> +	req->sq->authenticated = false;

> +	req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE;

> +	req->cqe->result.u32 |= 0x2 << 16;

> +}

> +

> +static u16 nvmet_auth_negotiate(struct nvmet_req *req, void *d)

> +{

> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

> +	struct nvmf_auth_dhchap_negotiate_data *data = d;

> +	int i, hash_id, null_dh = -1;

> +

> +	pr_debug("%s: ctrl %d qid %d: data sc_d %d napd %d authid %d halen %d

> dhlen %d\n", +		 __func__, ctrl->cntlid, req->sq->qid,

> +		 data->sc_c, data->napd, data->auth_protocol[0].dhchap.authid,

> +		 data->auth_protocol[0].dhchap.halen,

> +		 data->auth_protocol[0].dhchap.dhlen);

> +	req->sq->dhchap_tid = le16_to_cpu(data->t_id);

> +	if (data->sc_c)

> +		return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;

> +

> +	if (data->napd != 1)

> +		return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;

> +

> +	if (data->auth_protocol[0].dhchap.authid != 0x01)

> +		return NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;

> +

> +	hash_id = nvme_auth_hmac_id(crypto_shash_alg_name(ctrl->shash_tfm));

> +	for (i = 0; i < data->auth_protocol[0].dhchap.halen; i++) {

> +		pr_debug("%s: ctrl %d qid %d checking hash %d for %d\n",

> +			 __func__, ctrl->cntlid, req->sq->qid,

> +			 data->auth_protocol[0].dhchap.idlist[i], hash_id);

> +		if (hash_id != data->auth_protocol[0].dhchap.idlist[i])

> +			continue;

> +		req->sq->dhchap_hash_id = hash_id;

> +		req->sq->dhchap_hash_len = crypto_shash_digestsize(ctrl-

>shash_tfm);

> +		break;

> +	}

> +	if (req->sq->dhchap_hash_id == 0) {

> +		pr_debug("%s: ctrl %d qid %d: no usable hash found\n",

> +			 __func__, ctrl->cntlid, req->sq->qid);

> +		return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;

> +	}

> +

> +	for (i = data->auth_protocol[0].dhchap.halen;

> +	     i < data->auth_protocol[0].dhchap.halen +

> +		     data->auth_protocol[0].dhchap.dhlen; i++) {

> +		int dhgid = data->auth_protocol[0].dhchap.idlist[i];

> +

> +		if (dhgid == NVME_AUTH_DHCHAP_DHGROUP_NULL) {

> +			null_dh = dhgid;

> +			continue;

> +		}

> +		if (nvmet_setup_dhgroup(ctrl, dhgid) == 0)

> +			break;

> +	}

> +	if (!ctrl->dh_tfm && null_dh < 0) {

> +		pr_debug("%s: ctrl %d qid %d: no DH group selected\n",

> +			 __func__, ctrl->cntlid, req->sq->qid);

> +		return NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE;

> +	}

> +	if (ctrl->dh_gid == -1) {

> +		ctrl->dh_gid = null_dh;

> +		ctrl->dh_tfm = NULL;

> +	}

> +	pr_debug("%s: ctrl %d qid %d: DH group %s (%d)\n",

> +		 __func__, ctrl->cntlid, req->sq->qid,

> +		 nvme_auth_dhgroup_name(ctrl->dh_gid), ctrl->dh_gid);

> +	return 0;

> +}

> +

> +static u16 nvmet_auth_reply(struct nvmet_req *req, void *d)

> +{

> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

> +	struct nvmf_auth_dhchap_reply_data *data = d;

> +	u8 *response;

> +

> +	pr_debug("%s: ctrl %d qid %d: data hl %d cvalid %d dhvlen %d\n",

> +		 __func__, ctrl->cntlid, req->sq->qid,

> +		 data->hl, data->cvalid, data->dhvlen);

> +	if (data->hl != req->sq->dhchap_hash_len)

> +		return NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;

> +

> +	if (data->dhvlen) {

> +		return NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;

> +	}

> +

> +	response = kmalloc(data->hl, GFP_KERNEL);


Again, align to CRYPTO_MINALIGN_ATTR?

> +	if (!response)

> +		return NVME_AUTH_DHCHAP_FAILURE_FAILED;

> +

> +	if (nvmet_auth_host_hash(req, response, data->hl) < 0) {

> +		pr_debug("ctrl %d qid %d DH-HMAC-CHAP hash failed\n",

> +			 ctrl->cntlid, req->sq->qid);

> +		kfree(response);

> +		return NVME_AUTH_DHCHAP_FAILURE_FAILED;

> +	}

> +

> +	if (memcmp(data->rval, response, data->hl)) {

> +		pr_info("ctrl %d qid %d DH-HMAC-CHAP response mismatch\n",

> +			ctrl->cntlid, req->sq->qid);

> +		kfree(response);

> +		return NVME_AUTH_DHCHAP_FAILURE_FAILED;

> +	}

> +	kfree(response);

> +	pr_info("ctrl %d qid %d DH-HMAC-CHAP host authenticated\n",

> +		ctrl->cntlid, req->sq->qid);

> +	if (data->cvalid) {

> +		req->sq->dhchap_c2 = kmalloc(data->hl, GFP_KERNEL);

> +		if (!req->sq->dhchap_c2)

> +			return NVME_AUTH_DHCHAP_FAILURE_FAILED;

> +		memcpy(req->sq->dhchap_c2, data->rval + data->hl, data->hl);

> +

> +		pr_debug("ctrl %d qid %d challenge %*ph\n",

> +			 ctrl->cntlid, req->sq->qid, data->hl,

> +			 req->sq->dhchap_c2);

> +		req->sq->dhchap_s2 = le32_to_cpu(data->seqnum);

> +	} else

> +		req->sq->dhchap_c2 = NULL;

> +

> +	return 0;

> +}

> +

> +static u16 nvmet_auth_failure2(struct nvmet_req *req, void *d)

> +{

> +	struct nvmf_auth_dhchap_failure_data *data = d;

> +

> +	return data->reason_code_explanation;

> +}

> +

> +void nvmet_execute_auth_send(struct nvmet_req *req)

> +{

> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

> +	struct nvmf_auth_dhchap_success2_data *data;

> +	void *d;

> +	u32 tl;

> +	u16 status = 0;

> +

> +	if (req->cmd->auth_send.secp != NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER) {

> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

> +		req->error_loc =

> +			offsetof(struct nvmf_auth_send_command, secp);

> +		goto done;

> +	}

> +	if (req->cmd->auth_send.spsp0 != 0x01) {

> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

> +		req->error_loc =

> +			offsetof(struct nvmf_auth_send_command, spsp0);

> +		goto done;

> +	}

> +	if (req->cmd->auth_send.spsp1 != 0x01) {

> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

> +		req->error_loc =

> +			offsetof(struct nvmf_auth_send_command, spsp1);

> +		goto done;

> +	}

> +	tl = le32_to_cpu(req->cmd->auth_send.tl);

> +	if (!tl) {

> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

> +		req->error_loc =

> +			offsetof(struct nvmf_auth_send_command, tl);

> +		goto done;

> +	}

> +	if (!nvmet_check_transfer_len(req, tl)) {

> +		pr_debug("%s: transfer length mismatch (%u)\n", __func__, tl);

> +		return;

> +	}

> +

> +	d = kmalloc(tl, GFP_KERNEL);

> +	if (!d) {

> +		status = NVME_SC_INTERNAL;

> +		goto done;

> +	}

> +

> +	status = nvmet_copy_from_sgl(req, 0, d, tl);

> +	if (status) {

> +		kfree(d);

> +		goto done;

> +	}

> +

> +	data = d;

> +	pr_debug("%s: ctrl %d qid %d type %d id %d step %x\n", __func__,

> +		 ctrl->cntlid, req->sq->qid, data->auth_type, data->auth_id,

> +		 req->sq->dhchap_step);

> +	if (data->auth_type != NVME_AUTH_COMMON_MESSAGES &&

> +	    data->auth_type != NVME_AUTH_DHCHAP_MESSAGES) {

> +		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

> +		req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

> +	} else if (data->auth_type == NVME_AUTH_COMMON_MESSAGES) {

> +		if (data->auth_id != req->sq->dhchap_step) {

> +			req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

> +			req->sq->dhchap_status = 

NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;
> +		} else if (data->auth_id != NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE) {

> +			req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

> +			req->sq->dhchap_status = 

NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;
> +		} else {

> +			/* Validate negotiation parameters */

> +			status = nvmet_auth_negotiate(req, d);

> +			if (status == 0)

> +				req->sq->dhchap_step = 

NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE;
> +			else {

> +				req->sq->dhchap_step = 

NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
> +				req->sq->dhchap_status = status;

> +				status = 0;

> +			}

> +		}

> +	} else if (data->auth_type == NVME_AUTH_DHCHAP_MESSAGES) {

> +		if (data->auth_id != req->sq->dhchap_step) {

> +			pr_debug("%s: ctrl %d qid %d step mismatch (%d != %d)\n",

> +				 __func__, ctrl->cntlid, req->sq->qid,

> +				 data->auth_id, req->sq->dhchap_step);

> +			req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

> +			req->sq->dhchap_status = 

NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;
> +		} else if (le16_to_cpu(data->t_id) != req->sq->dhchap_tid) {

> +			pr_debug("%s: ctrl %d qid %d invalid transaction %d 

(expected %d)\n",
> +				 __func__, ctrl->cntlid, req->sq->qid,

> +				 le16_to_cpu(data->t_id),

> +				 req->sq->dhchap_tid);

> +			req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

> +			req->sq->dhchap_status = 

NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;
> +		} else {

> +			switch (data->auth_id) {

> +			case NVME_AUTH_DHCHAP_MESSAGE_REPLY:

> +				status = nvmet_auth_reply(req, d);

> +				if (status == 0)

> +					req->sq->dhchap_step = 

NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1;
> +				else {

> +					req->sq->dhchap_step = 

NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
> +					req->sq->dhchap_status = status;

> +					status = 0;

> +				}

> +				break;

> +			case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2:

> +				req->sq->authenticated = true;

> +				pr_debug("%s: ctrl %d qid %d authenticated\n",

> +					 __func__, ctrl->cntlid, req->sq->qid);

> +				break;

> +			case NVME_AUTH_DHCHAP_MESSAGE_FAILURE2:

> +				status = nvmet_auth_failure2(req, d);

> +				if (status) {

> +					pr_warn("ctrl %d qid %d: DH-HMAC-CHAP 

negotiation failed (%d)\n",
> +						ctrl->cntlid, req->sq->qid,

> +						status);

> +					req->sq->dhchap_status = status;

> +					status = 0;

> +				}

> +				break;

> +			default:

> +				req->sq->dhchap_status = 

NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;
> +				req->sq->dhchap_step = 

NVME_AUTH_DHCHAP_MESSAGE_FAILURE2;
> +				break;

> +			}

> +		}

> +	} else {

> +		req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

> +		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE2;

> +	}

> +	kfree(d);

> +done:

> +	pr_debug("%s: ctrl %d qid %d dhchap status %x step %x\n", __func__,

> +		 ctrl->cntlid, req->sq->qid,

> +		 req->sq->dhchap_status, req->sq->dhchap_step);

> +	if (status)

> +		pr_debug("%s: ctrl %d qid %d nvme status %x error loc %d\n",

> +			 __func__, ctrl->cntlid, req->sq->qid,

> +			 status, req->error_loc);

> +	req->cqe->result.u64 = 0;

> +	nvmet_req_complete(req, status);

> +	if (req->sq->dhchap_step != NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2 &&

> +	    req->sq->dhchap_step != NVME_AUTH_DHCHAP_MESSAGE_FAILURE2)

> +		return;

> +	/* Final states, clear up variables */

> +	kfree(req->sq->dhchap_c1);

> +	kfree(req->sq->dhchap_c2);

> +	if (req->sq->dhchap_step == NVME_AUTH_DHCHAP_MESSAGE_FAILURE2)

> +		nvmet_ctrl_fatal_error(ctrl);

> +}

> +

> +static int nvmet_auth_challenge(struct nvmet_req *req, void *d, int al)

> +{

> +	struct nvmf_auth_dhchap_challenge_data *data = d;

> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

> +	int ret = 0;

> +	int data_size = sizeof(*d) + req->sq->dhchap_hash_len;

> +

> +	if (al < data_size) {

> +		pr_debug("%s: buffer too small (al %d need %d)\n", __func__,

> +			 al, data_size);

> +		return -EINVAL;

> +	}

> +	memset(data, 0, data_size);

> +	req->sq->dhchap_s1 = ctrl->dhchap_seqnum++;

> +	data->auth_type = NVME_AUTH_DHCHAP_MESSAGES;

> +	data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE;

> +	data->t_id = cpu_to_le16(req->sq->dhchap_tid);

> +	data->hashid = req->sq->dhchap_hash_id;

> +	data->hl = req->sq->dhchap_hash_len;

> +	data->seqnum = cpu_to_le32(req->sq->dhchap_s1);

> +	req->sq->dhchap_c1 = kmalloc(data->hl, GFP_KERNEL);

> +	if (!req->sq->dhchap_c1)

> +		return -ENOMEM;

> +	get_random_bytes(req->sq->dhchap_c1, data->hl);

> +	memcpy(data->cval, req->sq->dhchap_c1, data->hl);

> +	pr_debug("%s: ctrl %d qid %d seq %d transaction %d hl %d dhvlen %d\n",

> +		 __func__, ctrl->cntlid, req->sq->qid, req->sq->dhchap_s1,

> +		 req->sq->dhchap_tid, data->hl, data->dhvlen);

> +	return ret;

> +}

> +

> +static int nvmet_auth_success1(struct nvmet_req *req, void *d, int al)

> +{

> +	struct nvmf_auth_dhchap_success1_data *data = d;

> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

> +

> +	WARN_ON(al < sizeof(*data));

> +	memset(data, 0, sizeof(*data));

> +	data->auth_type = NVME_AUTH_DHCHAP_MESSAGES;

> +	data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1;

> +	data->t_id = cpu_to_le16(req->sq->dhchap_tid);

> +	data->hl = req->sq->dhchap_hash_len;

> +	if (req->sq->dhchap_c2) {

> +		if (nvmet_auth_ctrl_hash(req, data->rval, data->hl))

> +			return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;

> +		data->rvalid = 1;

> +		pr_debug("ctrl %d qid %d response %*ph\n",

> +			 ctrl->cntlid, req->sq->qid, data->hl, data->rval);

> +	}

> +	return 0;

> +}

> +

> +static void nvmet_auth_failure1(struct nvmet_req *req, void *d, int al)

> +{

> +	struct nvmf_auth_dhchap_failure_data *data = d;

> +

> +	WARN_ON(al < sizeof(*data));

> +	data->auth_type = NVME_AUTH_COMMON_MESSAGES;

> +	data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

> +	data->t_id = cpu_to_le32(req->sq->dhchap_tid);

> +	data->reason_code = NVME_AUTH_DHCHAP_FAILURE_REASON_FAILED;

> +	data->reason_code_explanation = req->sq->dhchap_status;

> +}

> +

> +void nvmet_execute_auth_receive(struct nvmet_req *req)

> +{

> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

> +	void *d;

> +	u32 al;

> +	u16 status = 0;

> +

> +	if (req->cmd->auth_receive.secp != NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER) 

{
> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

> +		req->error_loc =

> +			offsetof(struct nvmf_auth_receive_command, secp);

> +		goto done;

> +	}

> +	if (req->cmd->auth_receive.spsp0 != 0x01) {

> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

> +		req->error_loc =

> +			offsetof(struct nvmf_auth_receive_command, spsp0);

> +		goto done;

> +	}

> +	if (req->cmd->auth_receive.spsp1 != 0x01) {

> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

> +		req->error_loc =

> +			offsetof(struct nvmf_auth_receive_command, spsp1);

> +		goto done;

> +	}

> +	al = le32_to_cpu(req->cmd->auth_receive.al);

> +	if (!al) {

> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

> +		req->error_loc =

> +			offsetof(struct nvmf_auth_receive_command, al);

> +		goto done;

> +	}

> +	if (!nvmet_check_transfer_len(req, al)) {

> +		pr_debug("%s: transfer length mismatch (%u)\n", __func__, al);

> +		return;

> +	}

> +

> +	d = kmalloc(al, GFP_KERNEL);

> +	if (!d) {

> +		status = NVME_SC_INTERNAL;

> +		goto done;

> +	}

> +	pr_debug("%s: ctrl %d qid %d step %x\n", __func__,

> +		 ctrl->cntlid, req->sq->qid, req->sq->dhchap_step);

> +	switch (req->sq->dhchap_step) {

> +	case NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE:

> +		status = nvmet_auth_challenge(req, d, al);

> +		if (status < 0) {

> +			pr_warn("ctrl %d qid %d: challenge error (%d)\n",

> +				ctrl->cntlid, req->sq->qid, status);

> +			status = NVME_SC_INTERNAL;

> +			break;

> +		}

> +		if (status) {

> +			req->sq->dhchap_status = status;

> +			nvmet_auth_failure1(req, d, al);

> +			pr_warn("ctrl %d qid %d: challenge status (%x)\n",

> +				ctrl->cntlid, req->sq->qid,

> +				req->sq->dhchap_status);

> +			status = 0;

> +			break;

> +		}

> +		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_REPLY;

> +		break;

> +	case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1:

> +		status = nvmet_auth_success1(req, d, al);

> +		if (status) {

> +			req->sq->dhchap_status = status;

> +			nvmet_auth_failure1(req, d, al);

> +			pr_warn("ctrl %d qid %d: success1 status (%x)\n",

> +				ctrl->cntlid, req->sq->qid,

> +				req->sq->dhchap_status);

> +			break;

> +		}

> +		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2;

> +		break;

> +	case NVME_AUTH_DHCHAP_MESSAGE_FAILURE1:

> +		nvmet_auth_failure1(req, d, al);

> +		pr_warn("ctrl %d qid %d failure1 (%x)\n",

> +			ctrl->cntlid, req->sq->qid, req->sq->dhchap_status);

> +		break;

> +	default:

> +		pr_warn("ctrl %d qid %d unhandled step (%d)\n",

> +			ctrl->cntlid, req->sq->qid, req->sq->dhchap_step);

> +		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

> +		req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_FAILED;

> +		nvmet_auth_failure1(req, d, al);

> +		status = 0;

> +		break;

> +	}

> +

> +	status = nvmet_copy_to_sgl(req, 0, d, al);

> +	kfree(d);

> +done:

> +	req->cqe->result.u64 = 0;

> +	nvmet_req_complete(req, status);

> +	if (req->sq->dhchap_step == NVME_AUTH_DHCHAP_MESSAGE_FAILURE1) {

> +		kfree(req->sq->dhchap_c1);

> +		kfree(req->sq->dhchap_c2);

> +		nvmet_ctrl_fatal_error(ctrl);

> +	}

> +}

> diff --git a/drivers/nvme/target/fabrics-cmd.c

> b/drivers/nvme/target/fabrics-cmd.c index 7d0f3523fdab..53fb853cd8fe 100644

> --- a/drivers/nvme/target/fabrics-cmd.c

> +++ b/drivers/nvme/target/fabrics-cmd.c

> @@ -93,6 +93,14 @@ u16 nvmet_parse_fabrics_cmd(struct nvmet_req *req)

>  	case nvme_fabrics_type_property_get:

>  		req->execute = nvmet_execute_prop_get;

>  		break;

> +#ifdef CONFIG_NVME_TARGET_AUTH

> +	case nvme_fabrics_type_auth_send:

> +		req->execute = nvmet_execute_auth_send;

> +		break;

> +	case nvme_fabrics_type_auth_receive:

> +		req->execute = nvmet_execute_auth_receive;

> +		break;

> +#endif

>  	default:

>  		pr_debug("received unknown capsule type 0x%x\n",

>  			cmd->fabrics.fctype);

> @@ -155,6 +163,7 @@ static void nvmet_execute_admin_connect(struct nvmet_req

> *req) struct nvmf_connect_data *d;

>  	struct nvmet_ctrl *ctrl = NULL;

>  	u16 status = 0;

> +	int ret;

> 

>  	if (!nvmet_check_transfer_len(req, sizeof(struct nvmf_connect_data)))

>  		return;

> @@ -197,17 +206,31 @@ static void nvmet_execute_admin_connect(struct

> nvmet_req *req)

> 

>  	uuid_copy(&ctrl->hostid, &d->hostid);

> 

> +	ret = nvmet_setup_auth(ctrl, req);

> +	if (ret < 0) {

> +		pr_err("Failed to setup authentication, error %d\n", ret);

> +		nvmet_ctrl_put(ctrl);

> +		if (ret == -EPERM)

> +			status = (NVME_SC_CONNECT_INVALID_HOST | NVME_SC_DNR);

> +		else

> +			status = NVME_SC_INTERNAL;

> +		goto out;

> +	}

> +

>  	status = nvmet_install_queue(ctrl, req);

>  	if (status) {

>  		nvmet_ctrl_put(ctrl);

>  		goto out;

>  	}

> 

> -	pr_info("creating controller %d for subsystem %s for NQN %s%s.\n",

> +	pr_info("creating controller %d for subsystem %s for NQN %s%s%s.\n",

>  		ctrl->cntlid, ctrl->subsys->subsysnqn, ctrl->hostnqn,

> -		ctrl->pi_support ? " T10-PI is enabled" : "");

> +		ctrl->pi_support ? " T10-PI is enabled" : "",

> +		nvmet_has_auth(ctrl) ? " with DH-HMAC-CHAP" : "");

>  	req->cqe->result.u16 = cpu_to_le16(ctrl->cntlid);

> 

> +	if (nvmet_has_auth(ctrl))

> +		nvmet_init_auth(ctrl, req);

>  out:

>  	kfree(d);

>  complete:

> @@ -267,6 +290,9 @@ static void nvmet_execute_io_connect(struct nvmet_req

> *req) }

> 

>  	pr_debug("adding queue %d to ctrl %d.\n", qid, ctrl->cntlid);

> +	req->cqe->result.u16 = cpu_to_le16(ctrl->cntlid);

> +	if (nvmet_has_auth(ctrl))

> +		nvmet_init_auth(ctrl, req);

> 

>  out:

>  	kfree(d);

> diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h

> index 06dd3d537f07..ef8815e137d7 100644

> --- a/drivers/nvme/target/nvmet.h

> +++ b/drivers/nvme/target/nvmet.h

> @@ -108,6 +108,20 @@ struct nvmet_sq {

>  	u16			size;

>  	u32			sqhd;

>  	bool			sqhd_disabled;

> +#ifdef CONFIG_NVME_TARGET_AUTH

> +	bool			authenticated;

> +	u16			dhchap_tid;

> +	u16			dhchap_status;

> +	int			dhchap_step;

> +	u8			dhchap_hash_id;

> +	u8			dhchap_hash_len;

> +	u8			*dhchap_c1;

> +	u8			*dhchap_c2;

> +	u32			dhchap_s1;

> +	u32			dhchap_s2;

> +	u8			*dhchap_skey;

> +	int			dhchap_skey_len;

> +#endif

>  	struct completion	free_done;

>  	struct completion	confirm_done;

>  };

> @@ -209,6 +223,15 @@ struct nvmet_ctrl {

>  	u64			err_counter;

>  	struct nvme_error_slot	slots[NVMET_ERROR_LOG_SLOTS];

>  	bool			pi_support;

> +#ifdef CONFIG_NVME_TARGET_AUTH

> +	u32			dhchap_seqnum;

> +	u8			*dhchap_key;

> +	size_t			dhchap_key_len;

> +	struct crypto_shash	*shash_tfm;

> +	struct crypto_kpp	*dh_tfm;

> +	u32			dh_gid;

> +	u32			dh_keysize;

> +#endif

>  };

> 

>  struct nvmet_subsys {

> @@ -270,6 +293,10 @@ static inline struct nvmet_subsys

> *namespaces_to_subsys(

> 

>  struct nvmet_host {

>  	struct config_group	group;

> +	u8			*dhchap_secret;

> +	u8			dhchap_key_hash;

> +	u8			dhchap_hash_id;

> +	u8			dhchap_dhgroup_id;

>  };

> 

>  static inline struct nvmet_host *to_host(struct config_item *item)

> @@ -659,4 +686,48 @@ static inline void nvmet_req_bio_put(struct nvmet_req

> *req, struct bio *bio) bio_put(bio);

>  }

> 

> +#ifdef CONFIG_NVME_TARGET_AUTH

> +void nvmet_execute_auth_send(struct nvmet_req *req);

> +void nvmet_execute_auth_receive(struct nvmet_req *req);

> +int nvmet_auth_set_host_key(struct nvmet_host *host, const char *secret);

> +int nvmet_auth_set_host_hash(struct nvmet_host *host, const char *hash);

> +int nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req);

> +void nvmet_init_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req);

> +void nvmet_reset_auth(struct nvmet_ctrl *ctrl);

> +void nvmet_auth_sq_free(struct nvmet_sq *sq);

> +int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, int dhgroup_id);

> +bool nvmet_check_auth_status(struct nvmet_req *req);

> +int nvmet_auth_host_hash(struct nvmet_req *req, u8 *response,

> +			 unsigned int hash_len);

> +int nvmet_auth_ctrl_hash(struct nvmet_req *req, u8 *response,

> +			 unsigned int hash_len);

> +static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl)

> +{

> +	return ctrl->shash_tfm != NULL;

> +}

> +int nvmet_auth_ctrl_exponential(struct nvmet_req *req,

> +				u8 *buf, int buf_size);

> +int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,

> +			    u8 *buf, int buf_size);

> +#else

> +static inline int nvmet_setup_auth(struct nvmet_ctrl *ctrl,

> +				   struct nvmet_req *req)

> +{

> +	return 0;

> +}

> +static inline void nvmet_init_auth(struct nvmet_ctrl *ctrl,

> +				   struct nvmet_req *req) {};

> +static inline void nvmet_reset_auth(struct nvmet_ctrl *ctrl) {};

> +static inline void nvmet_auth_sq_free(struct nvmet_sq *sq) {};

> +static inline bool nvmet_check_auth_status(struct nvmet_req *req)

> +{

> +	return true;

> +}

> +static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl)

> +{

> +	return false;

> +}

> +static inline const char *nvmet_dhchap_dhgroup_name(int dhgid) { return

> NULL; } +#endif

> +

>  #endif /* _NVMET_H */



Ciao
Stephan
Hannes Reinecke July 18, 2021, 12:37 p.m. UTC | #2
On 7/17/21 6:49 PM, Stephan Müller wrote:
> Am Freitag, 16. Juli 2021, 13:04:26 CEST schrieb Hannes Reinecke:

> 

> Hi Hannes,

> 

>> Implement support for NVMe-oF In-Band authentication. This patch

>> adds two additional configfs entries 'dhchap_key' and 'dhchap_hash'

>> to the 'host' configfs directory. The 'dhchap_key' needs to be

>> specified in the format outlined in the base spec.

>> Augmented challenge support is not implemented, and concatenation

>> with TLS encryption is not supported.

>>

>> Signed-off-by: Hannes Reinecke <hare@suse.de>

>> ---

>>   drivers/nvme/target/Kconfig            |  10 +

>>   drivers/nvme/target/Makefile           |   1 +

>>   drivers/nvme/target/admin-cmd.c        |   4 +

>>   drivers/nvme/target/auth.c             | 352 +++++++++++++++++++

>>   drivers/nvme/target/configfs.c         |  71 +++-

>>   drivers/nvme/target/core.c             |   8 +

>>   drivers/nvme/target/fabrics-cmd-auth.c | 460 +++++++++++++++++++++++++

>>   drivers/nvme/target/fabrics-cmd.c      |  30 +-

>>   drivers/nvme/target/nvmet.h            |  71 ++++

>>   9 files changed, 1004 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 4be2ececbc45..d5656ef1559e 100644

>> --- a/drivers/nvme/target/Kconfig

>> +++ b/drivers/nvme/target/Kconfig

>> @@ -85,3 +85,13 @@ 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_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 0cb98f2bbc8c..320cefc64ee0 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..00c7d051dfb1

>> --- /dev/null

>> +++ b/drivers/nvme/target/auth.c

>> @@ -0,0 +1,352 @@

>> +// 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 <crypto/kpp.h>

>> +#include <crypto/dh.h>

>> +#include <crypto/ffdhe.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_warn("DH-HMAC-CHAP hash %s unsupported\n", hmac);

>> +			host->dhchap_key_hash = -1;

>> +			return -EAGAIN;

>> +		}

>> +		/* 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);

> 

> Just like before - are you sure that the secret is an ASCII string and no

> binary blob?

> 


That is ensured by the transport encoding (cf NVMe Base Specification 
version 2.0). Also, this information is being passed in via the configfs 
interface, so it's bounded by PAGE_SIZE. But yes, we should be inserting 
a terminating 'NULL' character at the end of the page to ensure we don't 
incur an buffer overrun. Any other failure will be checked for during 
base64 decoding.

>> +	if (!host->dhchap_secret)

>> +		return -ENOMEM;

>> +	/* Default to SHA256 */

>> +	if (!host->dhchap_hash_id)

>> +		host->dhchap_hash_id = NVME_AUTH_DHCHAP_HASH_SHA256;

>> +

>> +	pr_debug("Using hash %s\n",

>> +		 nvme_auth_hmac_name(host->dhchap_hash_id));

>> +	return 0;

>> +}

>> +

>> +int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, int dhgroup_id)

>> +{

>> +	int ret = -ENOTSUPP;

>> +

>> +	if (dhgroup_id == NVME_AUTH_DHCHAP_DHGROUP_NULL)

>> +		return 0;

>> +

>> +	return ret;

>> +}

>> +

>> +int nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req)

>> +{

>> +	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;

>> +

>> +	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;

>> +	}

>> +

>> +	hash_name = nvme_auth_hmac_name(host->dhchap_hash_id);

>> +	if (!hash_name) {

>> +		pr_debug("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_debug("failed to allocate shash %s\n", hash_name);

>> +		ret = PTR_ERR(ctrl->shash_tfm);

>> +		ctrl->shash_tfm = NULL;

>> +		goto out_unlock;

>> +	}

>> +

>> +	ctrl->dhchap_key = nvme_auth_extract_secret(host->dhchap_secret,

>> +						    &ctrl->dhchap_key_len);

>> +	if (IS_ERR(ctrl->dhchap_key)) {

>> +		pr_debug("failed to extract host key, error %d\n", ret);

>> +		ret = PTR_ERR(ctrl->dhchap_key);

>> +		ctrl->dhchap_key = NULL;

>> +		goto out_free_hash;

>> +	}

>> +	if (host->dhchap_key_hash) {

>> +		struct crypto_shash *key_tfm;

>> +

>> +		hash_name = nvme_auth_hmac_name(host->dhchap_key_hash);

>> +		key_tfm = crypto_alloc_shash(hash_name, 0, 0);

>> +		if (IS_ERR(key_tfm)) {

>> +			ret = PTR_ERR(key_tfm);

>> +			goto out_free_hash;

>> +		} else {

>> +			SHASH_DESC_ON_STACK(shash, key_tfm);

>> +

>> +			shash->tfm = key_tfm;

>> +			ret = crypto_shash_setkey(key_tfm, ctrl->dhchap_key,

>> +						  ctrl->dhchap_key_len);

>> +			crypto_shash_init(shash);

>> +			crypto_shash_update(shash, ctrl->subsys->subsysnqn,

>> +					    strlen(ctrl->subsys->subsysnqn));

>> +			crypto_shash_update(shash, "NVMe-over-Fabrics", 17);

>> +			crypto_shash_final(shash, ctrl->dhchap_key);

>> +			crypto_free_shash(key_tfm);

>> +		}

>> +	}

>> +	pr_debug("%s: using key %*ph\n", __func__,

>> +		 (int)ctrl->dhchap_key_len, ctrl->dhchap_key);

>> +	ret = crypto_shash_setkey(ctrl->shash_tfm, ctrl->dhchap_key,

> 

> Is it truly necessary to keep the key around in ctrl->dhchap_key? It looks to

> me that this buffer is only used here and thus could be turned into a local

> variable. Keys flying around in memory is not a good idea. :-)

> 

The key is also used when using the ffdhe algorithm.
Note: I _think_ that I need to use this key for the ffdhe algorithm, 
because the implementation I came up with is essentially plain DH with 
pre-defined 'p', 'q' and 'g' values. But the DH implementation also 
requires a 'key', and for that I'm using this key here.

It might be that I'm completely off, and don't need to use a key for our 
DH implementation. In that case you are correct.
(And that's why I said I'll need a review of the FFDHE implementation).
But for now I'll need the key for FFDHE.

>> +				  ctrl->dhchap_key_len);

>> +out_free_hash:

>> +	if (ret) {

>> +		if (ctrl->dhchap_key) {

>> +			kfree(ctrl->dhchap_key);

> 

> kfree_sensitive?

> 


Yes, will be fixing it.

>> +			ctrl->dhchap_key = NULL;

>> +		}

>> +		crypto_free_shash(ctrl->shash_tfm);

>> +		ctrl->shash_tfm = NULL;

>> +	}

>> +out_unlock:

>> +	up_read(&nvmet_config_sem);

>> +

>> +	return ret;

>> +}

>> +

>> +void nvmet_auth_sq_free(struct nvmet_sq *sq)

>> +{

>> +	if (sq->dhchap_c1)

>> +		kfree(sq->dhchap_c1);

>> +	if (sq->dhchap_c2)

>> +		kfree(sq->dhchap_c2);

>> +	if (sq->dhchap_skey)

>> +		kfree(sq->dhchap_skey);

> 

> kfree_sensitive?

> 


Yes.

>> +}

>> +

>> +void nvmet_reset_auth(struct nvmet_ctrl *ctrl)

>> +{

>> +	if (ctrl->shash_tfm) {

>> +		crypto_free_shash(ctrl->shash_tfm);

>> +		ctrl->shash_tfm = NULL;

>> +	}

>> +	if (ctrl->dh_tfm) {

>> +		crypto_free_kpp(ctrl->dh_tfm);

>> +		ctrl->dh_tfm = NULL;

>> +	}

>> +	if (ctrl->dhchap_key) {

>> +		kfree(ctrl->dhchap_key);

> 

> kfree_sensitive?

> 


Yes.

>> +		ctrl->dhchap_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;

>> +	u8 buf[4];

>> +	int ret;

>> +

>> +	if (ctrl->dh_gid != NVME_AUTH_DHCHAP_DHGROUP_NULL) {

>> +		ret = -ENOTSUPP;

>> +		goto out;

>> +	}

>> +

>> +	shash->tfm = ctrl->shash_tfm;

>> +	ret = crypto_shash_init(shash);

>> +	if (ret)

>> +		goto out;

>> +	ret = crypto_shash_update(shash, challenge, shash_len);

>> +	if (ret)

>> +		goto out;

>> +	put_unaligned_le32(req->sq->dhchap_s1, buf);

>> +	ret = crypto_shash_update(shash, buf, 4);

>> +	if (ret)

>> +		goto out;

>> +	put_unaligned_le16(req->sq->dhchap_tid, buf);

>> +	ret = crypto_shash_update(shash, buf, 2);

>> +	if (ret)

>> +		goto out;

>> +	memset(buf, 0, 4);

>> +	ret = crypto_shash_update(shash, buf, 1);

>> +	if (ret)

>> +		goto out;

>> +	ret = crypto_shash_update(shash, "HostHost", 8);

>> +	if (ret)

>> +		goto out;

>> +	ret = crypto_shash_update(shash, ctrl->hostnqn, strlen(ctrl->hostnqn));

>> +	if (ret)

>> +		goto out;

>> +	ret = crypto_shash_update(shash, buf, 1);

>> +	if (ret)

>> +		goto out;

>> +	ret = crypto_shash_update(shash, ctrl->subsysnqn,

>> +				  strlen(ctrl->subsysnqn));

>> +	if (ret)

>> +		goto out;

>> +	ret = crypto_shash_final(shash, response);

>> +out:

>> +	if (challenge != req->sq->dhchap_c1)

>> +		kfree(challenge);

>> +	return 0;

>> +}

>> +

>> +int nvmet_auth_ctrl_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_c2;

>> +	u8 buf[4];

>> +	int ret;

>> +

>> +	pr_debug("%s: ctrl %d hash seq %d transaction %u\n", __func__,

>> +		 ctrl->cntlid, req->sq->dhchap_s2, req->sq->dhchap_tid);

>> +	pr_debug("%s: ctrl %d challenge %*ph\n", __func__,

>> +		 ctrl->cntlid, shash_len, req->sq->dhchap_c2);

>> +	pr_debug("%s: ctrl %d subsysnqn %s\n", __func__,

>> +		 ctrl->cntlid, ctrl->subsysnqn);

>> +	pr_debug("%s: ctrl %d hostnqn %s\n", __func__,

>> +		 ctrl->cntlid, ctrl->hostnqn);

>> +

>> +	if (ctrl->dh_gid != NVME_AUTH_DHCHAP_DHGROUP_NULL) {

>> +		ret = -ENOTSUPP;

>> +		goto out;

>> +	}

>> +

>> +	shash->tfm = ctrl->shash_tfm;

>> +	ret = crypto_shash_init(shash);

>> +	if (ret)

>> +		goto out;

>> +	ret = crypto_shash_update(shash, challenge, shash_len);

>> +	if (ret)

>> +		goto out;

>> +	put_unaligned_le32(req->sq->dhchap_s2, buf);

>> +	ret = crypto_shash_update(shash, buf, 4);

>> +	if (ret)

>> +		goto out;

>> +	put_unaligned_le16(req->sq->dhchap_tid, buf);

>> +	ret = crypto_shash_update(shash, buf, 2);

>> +	if (ret)

>> +		goto out;

>> +	memset(buf, 0, 4);

>> +	ret = crypto_shash_update(shash, buf, 1);

>> +	if (ret)

>> +		goto out;

>> +	ret = crypto_shash_update(shash, "Controller", 10);

>> +	if (ret)

>> +		goto out;

>> +	ret = crypto_shash_update(shash, ctrl->subsysnqn,

>> +			    strlen(ctrl->subsysnqn));

>> +	if (ret)

>> +		goto out;

>> +	ret = crypto_shash_update(shash, buf, 1);

>> +	if (ret)

>> +		goto out;

>> +	ret = crypto_shash_update(shash, ctrl->hostnqn, strlen(ctrl->hostnqn));

>> +	if (ret)

>> +		goto out;

>> +	ret = crypto_shash_final(shash, response);

>> +out:

>> +	if (challenge != req->sq->dhchap_c2)

>> +		kfree(challenge);

>> +	return 0;

>> +}

>> +

>> +int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,

>> +			    u8 *pkey, int pkey_size)

>> +{

>> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

>> +	struct kpp_request *kpp_req;

>> +	struct crypto_wait wait;

>> +	struct scatterlist src, dst;

>> +	int ret;

>> +

>> +	req->sq->dhchap_skey_len =

>> +		nvme_auth_dhgroup_privkey_size(ctrl->dh_gid);

>> +	req->sq->dhchap_skey = kzalloc(req->sq->dhchap_skey_len, GFP_KERNEL);

>> +	if (!req->sq->dhchap_skey)

>> +		return -ENOMEM;

>> +	kpp_req = kpp_request_alloc(ctrl->dh_tfm, GFP_KERNEL);

>> +	if (!kpp_req) {

>> +		kfree(req->sq->dhchap_skey);

>> +		req->sq->dhchap_skey = NULL;

>> +		return -ENOMEM;

>> +	}

>> +

>> +	pr_debug("%s: host public key %*ph\n", __func__,

>> +		 (int)pkey_size, pkey);

>> +	crypto_init_wait(&wait);

>> +	sg_init_one(&src, pkey, pkey_size);

>> +	kpp_request_set_input(kpp_req, &src, pkey_size);

>> +	sg_init_one(&dst, req->sq->dhchap_skey,

>> +		req->sq->dhchap_skey_len);

>> +	kpp_request_set_output(kpp_req, &dst, req->sq->dhchap_skey_len);

>> +	kpp_request_set_callback(kpp_req, CRYPTO_TFM_REQ_MAY_BACKLOG,

>> +				 crypto_req_done, &wait);

>> +

>> +	ret = crypto_wait_req(crypto_kpp_compute_shared_secret(kpp_req), &wait);

>> +	kpp_request_free(kpp_req);

>> +	if (ret)

>> +		pr_debug("failed to compute shared secred, err %d\n", ret);

>> +	else

>> +		pr_debug("%s: shared secret %*ph\n", __func__,

>> +			 (int)req->sq->dhchap_skey_len,

>> +			 req->sq->dhchap_skey);

>> +

>> +	return ret;

>> +}

>> diff --git a/drivers/nvme/target/configfs.c b/drivers/nvme/target/configfs.c

>> index 273555127188..e0760911a761 100644

>> --- a/drivers/nvme/target/configfs.c

>> +++ b/drivers/nvme/target/configfs.c

>> @@ -11,8 +11,13 @@

>>   #include <linux/ctype.h>

>>   #include <linux/pci.h>

>>   #include <linux/pci-p2pdma.h>

>> +#include <crypto/hash.h>

>> +#include <crypto/kpp.h>

>>

>>   #include "nvmet.h"

>> +#ifdef CONFIG_NVME_TARGET_AUTH

>> +#include "../host/auth.h"

>> +#endif

>>

>>   static const struct config_item_type nvmet_host_type;

>>   static const struct config_item_type nvmet_subsys_type;

>> @@ -1656,10 +1661,71 @@ static const struct config_item_type

>> nvmet_ports_type = { static struct config_group nvmet_subsystems_group;

>>   static struct config_group nvmet_ports_group;

>>

>> -static void nvmet_host_release(struct config_item *item)

>> +#ifdef CONFIG_NVME_TARGET_AUTH

>> +static ssize_t nvmet_host_dhchap_key_show(struct config_item *item,

>> +		char *page)

>> +{

>> +	u8 *dhchap_secret = to_host(item)->dhchap_secret;

>> +

>> +	if (!dhchap_secret)

>> +		return sprintf(page, "\n");

>> +	return sprintf(page, "%s\n", dhchap_secret);

>> +}

>> +

>> +static ssize_t nvmet_host_dhchap_key_store(struct config_item *item,

>> +		const char *page, size_t count)

>>   {

>>   	struct nvmet_host *host = to_host(item);

>> +	int ret;

>>

>> +	ret = nvmet_auth_set_host_key(host, page);

>> +	if (ret < 0)

>> +		return ret;

>> +	return count;

>> +}

>> +

>> +CONFIGFS_ATTR(nvmet_host_, dhchap_key);

>> +

>> +static ssize_t nvmet_host_dhchap_hash_show(struct config_item *item,

>> +		char *page)

>> +{

>> +	struct nvmet_host *host = to_host(item);

>> +	const char *hash_name = nvme_auth_hmac_name(host->dhchap_hash_id);

>> +

>> +	return sprintf(page, "%s\n", hash_name ? hash_name : "none");

>> +}

>> +

>> +static ssize_t nvmet_host_dhchap_hash_store(struct config_item *item,

>> +		const char *page, size_t count)

>> +{

>> +	struct nvmet_host *host = to_host(item);

>> +	int hmac_id;

>> +

>> +	hmac_id = nvme_auth_hmac_id(page);

>> +	if (hmac_id < 0)

>> +		return -EINVAL;

>> +	if (!crypto_has_shash(nvme_auth_hmac_name(hmac_id), 0, 0))

>> +		return -ENOTSUPP;

>> +	host->dhchap_hash_id = hmac_id;

>> +	return count;

>> +}

>> +

>> +CONFIGFS_ATTR(nvmet_host_, dhchap_hash);

>> +

>> +static struct configfs_attribute *nvmet_host_attrs[] = {

>> +	&nvmet_host_attr_dhchap_key,

>> +	&nvmet_host_attr_dhchap_hash,

>> +	NULL,

>> +};

>> +#endif /* CONFIG_NVME_TARGET_AUTH */

>> +

>> +static void nvmet_host_release(struct config_item *item)

>> +{

>> +	struct nvmet_host *host = to_host(item);

>> +#ifdef CONFIG_NVME_TARGET_AUTH

>> +	if (host->dhchap_secret)

>> +		kfree(host->dhchap_secret);

>> +#endif

>>   	kfree(host);

>>   }

>>

>> @@ -1669,6 +1735,9 @@ static struct configfs_item_operations

>> nvmet_host_item_ops = {

>>

>>   static const struct config_item_type nvmet_host_type = {

>>   	.ct_item_ops		= &nvmet_host_item_ops,

>> +#ifdef CONFIG_NVME_TARGET_AUTH

>> +	.ct_attrs		= nvmet_host_attrs,

>> +#endif

>>   	.ct_owner		= THIS_MODULE,

>>   };

>>

>> diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c

>> index 163f7dc1a929..b5d7971f566b 100644

>> --- a/drivers/nvme/target/core.c

>> +++ b/drivers/nvme/target/core.c

>> @@ -793,6 +793,7 @@ void nvmet_sq_destroy(struct nvmet_sq *sq)

>>   	wait_for_completion(&sq->confirm_done);

>>   	wait_for_completion(&sq->free_done);

>>   	percpu_ref_exit(&sq->ref);

>> +	nvmet_auth_sq_free(sq);

>>

>>   	if (ctrl) {

>>   		/*

>> @@ -1264,6 +1265,11 @@ u16 nvmet_check_ctrl_status(struct nvmet_req *req)

>>   		       req->cmd->common.opcode, req->sq->qid);

>>   		return NVME_SC_CMD_SEQ_ERROR | NVME_SC_DNR;

>>   	}

>> +

>> +	if (unlikely(!nvmet_check_auth_status(req))) {

>> +		pr_warn("qid %d not authenticated\n", req->sq->qid);

>> +		return NVME_SC_AUTH_REQUIRED | NVME_SC_DNR;

>> +	}

>>   	return 0;

>>   }

>>

>> @@ -1456,6 +1462,8 @@ static void nvmet_ctrl_free(struct kref *ref)

>>   	flush_work(&ctrl->async_event_work);

>>   	cancel_work_sync(&ctrl->fatal_err_work);

>>

>> +	nvmet_reset_auth(ctrl);

>> +

>>   	ida_simple_remove(&cntlid_ida, ctrl->cntlid);

>>

>>   	nvmet_async_events_free(ctrl);

>> diff --git a/drivers/nvme/target/fabrics-cmd-auth.c

>> b/drivers/nvme/target/fabrics-cmd-auth.c new file mode 100644

>> index 000000000000..962f9f5e9d89

>> --- /dev/null

>> +++ b/drivers/nvme/target/fabrics-cmd-auth.c

>> @@ -0,0 +1,460 @@

>> +// SPDX-License-Identifier: GPL-2.0

>> +/*

>> + * NVMe over Fabrics DH-HMAC-CHAP authentication command handling.

>> + * Copyright (c) 2020 Hannes Reinecke, SUSE Software Solutions.

>> + * All rights reserved.

>> + */

>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

>> +#include <linux/blkdev.h>

>> +#include <linux/random.h>

>> +#include <crypto/hash.h>

>> +#include <crypto/kpp.h>

>> +#include "nvmet.h"

>> +#include "../host/auth.h"

>> +

>> +void nvmet_init_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req)

>> +{

>> +	/* Initialize in-band authentication */

>> +	req->sq->authenticated = false;

>> +	req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE;

>> +	req->cqe->result.u32 |= 0x2 << 16;

>> +}

>> +

>> +static u16 nvmet_auth_negotiate(struct nvmet_req *req, void *d)

>> +{

>> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

>> +	struct nvmf_auth_dhchap_negotiate_data *data = d;

>> +	int i, hash_id, null_dh = -1;

>> +

>> +	pr_debug("%s: ctrl %d qid %d: data sc_d %d napd %d authid %d halen %d

>> dhlen %d\n", +		 __func__, ctrl->cntlid, req->sq->qid,

>> +		 data->sc_c, data->napd, data->auth_protocol[0].dhchap.authid,

>> +		 data->auth_protocol[0].dhchap.halen,

>> +		 data->auth_protocol[0].dhchap.dhlen);

>> +	req->sq->dhchap_tid = le16_to_cpu(data->t_id);

>> +	if (data->sc_c)

>> +		return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;

>> +

>> +	if (data->napd != 1)

>> +		return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;

>> +

>> +	if (data->auth_protocol[0].dhchap.authid != 0x01)

>> +		return NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;

>> +

>> +	hash_id = nvme_auth_hmac_id(crypto_shash_alg_name(ctrl->shash_tfm));

>> +	for (i = 0; i < data->auth_protocol[0].dhchap.halen; i++) {

>> +		pr_debug("%s: ctrl %d qid %d checking hash %d for %d\n",

>> +			 __func__, ctrl->cntlid, req->sq->qid,

>> +			 data->auth_protocol[0].dhchap.idlist[i], hash_id);

>> +		if (hash_id != data->auth_protocol[0].dhchap.idlist[i])

>> +			continue;

>> +		req->sq->dhchap_hash_id = hash_id;

>> +		req->sq->dhchap_hash_len = crypto_shash_digestsize(ctrl-

>> shash_tfm);

>> +		break;

>> +	}

>> +	if (req->sq->dhchap_hash_id == 0) {

>> +		pr_debug("%s: ctrl %d qid %d: no usable hash found\n",

>> +			 __func__, ctrl->cntlid, req->sq->qid);

>> +		return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;

>> +	}

>> +

>> +	for (i = data->auth_protocol[0].dhchap.halen;

>> +	     i < data->auth_protocol[0].dhchap.halen +

>> +		     data->auth_protocol[0].dhchap.dhlen; i++) {

>> +		int dhgid = data->auth_protocol[0].dhchap.idlist[i];

>> +

>> +		if (dhgid == NVME_AUTH_DHCHAP_DHGROUP_NULL) {

>> +			null_dh = dhgid;

>> +			continue;

>> +		}

>> +		if (nvmet_setup_dhgroup(ctrl, dhgid) == 0)

>> +			break;

>> +	}

>> +	if (!ctrl->dh_tfm && null_dh < 0) {

>> +		pr_debug("%s: ctrl %d qid %d: no DH group selected\n",

>> +			 __func__, ctrl->cntlid, req->sq->qid);

>> +		return NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE;

>> +	}

>> +	if (ctrl->dh_gid == -1) {

>> +		ctrl->dh_gid = null_dh;

>> +		ctrl->dh_tfm = NULL;

>> +	}

>> +	pr_debug("%s: ctrl %d qid %d: DH group %s (%d)\n",

>> +		 __func__, ctrl->cntlid, req->sq->qid,

>> +		 nvme_auth_dhgroup_name(ctrl->dh_gid), ctrl->dh_gid);

>> +	return 0;

>> +}

>> +

>> +static u16 nvmet_auth_reply(struct nvmet_req *req, void *d)

>> +{

>> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

>> +	struct nvmf_auth_dhchap_reply_data *data = d;

>> +	u8 *response;

>> +

>> +	pr_debug("%s: ctrl %d qid %d: data hl %d cvalid %d dhvlen %d\n",

>> +		 __func__, ctrl->cntlid, req->sq->qid,

>> +		 data->hl, data->cvalid, data->dhvlen);

>> +	if (data->hl != req->sq->dhchap_hash_len)

>> +		return NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;

>> +

>> +	if (data->dhvlen) {

>> +		return NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;

>> +	}

>> +

>> +	response = kmalloc(data->hl, GFP_KERNEL);

> 

> Again, align to CRYPTO_MINALIGN_ATTR?

> 


Ah, _that_ alignment.
Wasn't aware that I need to align to anything.
But if that's required, sure, I'll be fixing it.

>> +	if (!response)

>> +		return NVME_AUTH_DHCHAP_FAILURE_FAILED;

>> +

>> +	if (nvmet_auth_host_hash(req, response, data->hl) < 0) {

>> +		pr_debug("ctrl %d qid %d DH-HMAC-CHAP hash failed\n",

>> +			 ctrl->cntlid, req->sq->qid);

>> +		kfree(response);

>> +		return NVME_AUTH_DHCHAP_FAILURE_FAILED;

>> +	}

>> +

>> +	if (memcmp(data->rval, response, data->hl)) {

>> +		pr_info("ctrl %d qid %d DH-HMAC-CHAP response mismatch\n",

>> +			ctrl->cntlid, req->sq->qid);

>> +		kfree(response);

>> +		return NVME_AUTH_DHCHAP_FAILURE_FAILED;

>> +	}

>> +	kfree(response);

>> +	pr_info("ctrl %d qid %d DH-HMAC-CHAP host authenticated\n",

>> +		ctrl->cntlid, req->sq->qid);

>> +	if (data->cvalid) {

>> +		req->sq->dhchap_c2 = kmalloc(data->hl, GFP_KERNEL);

>> +		if (!req->sq->dhchap_c2)

>> +			return NVME_AUTH_DHCHAP_FAILURE_FAILED;

>> +		memcpy(req->sq->dhchap_c2, data->rval + data->hl, data->hl);

>> +

>> +		pr_debug("ctrl %d qid %d challenge %*ph\n",

>> +			 ctrl->cntlid, req->sq->qid, data->hl,

>> +			 req->sq->dhchap_c2);

>> +		req->sq->dhchap_s2 = le32_to_cpu(data->seqnum);

>> +	} else

>> +		req->sq->dhchap_c2 = NULL;

>> +

>> +	return 0;

>> +}

>> +

>> +static u16 nvmet_auth_failure2(struct nvmet_req *req, void *d)

>> +{

>> +	struct nvmf_auth_dhchap_failure_data *data = d;

>> +

>> +	return data->reason_code_explanation;

>> +}

>> +

>> +void nvmet_execute_auth_send(struct nvmet_req *req)

>> +{

>> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

>> +	struct nvmf_auth_dhchap_success2_data *data;

>> +	void *d;

>> +	u32 tl;

>> +	u16 status = 0;

>> +

>> +	if (req->cmd->auth_send.secp != NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER) {

>> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

>> +		req->error_loc =

>> +			offsetof(struct nvmf_auth_send_command, secp);

>> +		goto done;

>> +	}

>> +	if (req->cmd->auth_send.spsp0 != 0x01) {

>> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

>> +		req->error_loc =

>> +			offsetof(struct nvmf_auth_send_command, spsp0);

>> +		goto done;

>> +	}

>> +	if (req->cmd->auth_send.spsp1 != 0x01) {

>> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

>> +		req->error_loc =

>> +			offsetof(struct nvmf_auth_send_command, spsp1);

>> +		goto done;

>> +	}

>> +	tl = le32_to_cpu(req->cmd->auth_send.tl);

>> +	if (!tl) {

>> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

>> +		req->error_loc =

>> +			offsetof(struct nvmf_auth_send_command, tl);

>> +		goto done;

>> +	}

>> +	if (!nvmet_check_transfer_len(req, tl)) {

>> +		pr_debug("%s: transfer length mismatch (%u)\n", __func__, tl);

>> +		return;

>> +	}

>> +

>> +	d = kmalloc(tl, GFP_KERNEL);

>> +	if (!d) {

>> +		status = NVME_SC_INTERNAL;

>> +		goto done;

>> +	}

>> +

>> +	status = nvmet_copy_from_sgl(req, 0, d, tl);

>> +	if (status) {

>> +		kfree(d);

>> +		goto done;

>> +	}

>> +

>> +	data = d;

>> +	pr_debug("%s: ctrl %d qid %d type %d id %d step %x\n", __func__,

>> +		 ctrl->cntlid, req->sq->qid, data->auth_type, data->auth_id,

>> +		 req->sq->dhchap_step);

>> +	if (data->auth_type != NVME_AUTH_COMMON_MESSAGES &&

>> +	    data->auth_type != NVME_AUTH_DHCHAP_MESSAGES) {

>> +		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +		req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

>> +	} else if (data->auth_type == NVME_AUTH_COMMON_MESSAGES) {

>> +		if (data->auth_id != req->sq->dhchap_step) {

>> +			req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +			req->sq->dhchap_status =

> NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

>> +		} else if (data->auth_id != NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE) {

>> +			req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +			req->sq->dhchap_status =

> NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

>> +		} else {

>> +			/* Validate negotiation parameters */

>> +			status = nvmet_auth_negotiate(req, d);

>> +			if (status == 0)

>> +				req->sq->dhchap_step =

> NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE;

>> +			else {

>> +				req->sq->dhchap_step =

> NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +				req->sq->dhchap_status = status;

>> +				status = 0;

>> +			}

>> +		}

>> +	} else if (data->auth_type == NVME_AUTH_DHCHAP_MESSAGES) {

>> +		if (data->auth_id != req->sq->dhchap_step) {

>> +			pr_debug("%s: ctrl %d qid %d step mismatch (%d != %d)\n",

>> +				 __func__, ctrl->cntlid, req->sq->qid,

>> +				 data->auth_id, req->sq->dhchap_step);

>> +			req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +			req->sq->dhchap_status =

> NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

>> +		} else if (le16_to_cpu(data->t_id) != req->sq->dhchap_tid) {

>> +			pr_debug("%s: ctrl %d qid %d invalid transaction %d

> (expected %d)\n",

>> +				 __func__, ctrl->cntlid, req->sq->qid,

>> +				 le16_to_cpu(data->t_id),

>> +				 req->sq->dhchap_tid);

>> +			req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +			req->sq->dhchap_status =

> NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;

>> +		} else {

>> +			switch (data->auth_id) {

>> +			case NVME_AUTH_DHCHAP_MESSAGE_REPLY:

>> +				status = nvmet_auth_reply(req, d);

>> +				if (status == 0)

>> +					req->sq->dhchap_step =

> NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1;

>> +				else {

>> +					req->sq->dhchap_step =

> NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +					req->sq->dhchap_status = status;

>> +					status = 0;

>> +				}

>> +				break;

>> +			case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2:

>> +				req->sq->authenticated = true;

>> +				pr_debug("%s: ctrl %d qid %d authenticated\n",

>> +					 __func__, ctrl->cntlid, req->sq->qid);

>> +				break;

>> +			case NVME_AUTH_DHCHAP_MESSAGE_FAILURE2:

>> +				status = nvmet_auth_failure2(req, d);

>> +				if (status) {

>> +					pr_warn("ctrl %d qid %d: DH-HMAC-CHAP

> negotiation failed (%d)\n",

>> +						ctrl->cntlid, req->sq->qid,

>> +						status);

>> +					req->sq->dhchap_status = status;

>> +					status = 0;

>> +				}

>> +				break;

>> +			default:

>> +				req->sq->dhchap_status =

> NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

>> +				req->sq->dhchap_step =

> NVME_AUTH_DHCHAP_MESSAGE_FAILURE2;

>> +				break;

>> +			}

>> +		}

>> +	} else {

>> +		req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

>> +		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE2;

>> +	}

>> +	kfree(d);

>> +done:

>> +	pr_debug("%s: ctrl %d qid %d dhchap status %x step %x\n", __func__,

>> +		 ctrl->cntlid, req->sq->qid,

>> +		 req->sq->dhchap_status, req->sq->dhchap_step);

>> +	if (status)

>> +		pr_debug("%s: ctrl %d qid %d nvme status %x error loc %d\n",

>> +			 __func__, ctrl->cntlid, req->sq->qid,

>> +			 status, req->error_loc);

>> +	req->cqe->result.u64 = 0;

>> +	nvmet_req_complete(req, status);

>> +	if (req->sq->dhchap_step != NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2 &&

>> +	    req->sq->dhchap_step != NVME_AUTH_DHCHAP_MESSAGE_FAILURE2)

>> +		return;

>> +	/* Final states, clear up variables */

>> +	kfree(req->sq->dhchap_c1);

>> +	kfree(req->sq->dhchap_c2);

>> +	if (req->sq->dhchap_step == NVME_AUTH_DHCHAP_MESSAGE_FAILURE2)

>> +		nvmet_ctrl_fatal_error(ctrl);

>> +}

>> +

>> +static int nvmet_auth_challenge(struct nvmet_req *req, void *d, int al)

>> +{

>> +	struct nvmf_auth_dhchap_challenge_data *data = d;

>> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

>> +	int ret = 0;

>> +	int data_size = sizeof(*d) + req->sq->dhchap_hash_len;

>> +

>> +	if (al < data_size) {

>> +		pr_debug("%s: buffer too small (al %d need %d)\n", __func__,

>> +			 al, data_size);

>> +		return -EINVAL;

>> +	}

>> +	memset(data, 0, data_size);

>> +	req->sq->dhchap_s1 = ctrl->dhchap_seqnum++;

>> +	data->auth_type = NVME_AUTH_DHCHAP_MESSAGES;

>> +	data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE;

>> +	data->t_id = cpu_to_le16(req->sq->dhchap_tid);

>> +	data->hashid = req->sq->dhchap_hash_id;

>> +	data->hl = req->sq->dhchap_hash_len;

>> +	data->seqnum = cpu_to_le32(req->sq->dhchap_s1);

>> +	req->sq->dhchap_c1 = kmalloc(data->hl, GFP_KERNEL);

>> +	if (!req->sq->dhchap_c1)

>> +		return -ENOMEM;

>> +	get_random_bytes(req->sq->dhchap_c1, data->hl);

>> +	memcpy(data->cval, req->sq->dhchap_c1, data->hl);

>> +	pr_debug("%s: ctrl %d qid %d seq %d transaction %d hl %d dhvlen %d\n",

>> +		 __func__, ctrl->cntlid, req->sq->qid, req->sq->dhchap_s1,

>> +		 req->sq->dhchap_tid, data->hl, data->dhvlen);

>> +	return ret;

>> +}

>> +

>> +static int nvmet_auth_success1(struct nvmet_req *req, void *d, int al)

>> +{

>> +	struct nvmf_auth_dhchap_success1_data *data = d;

>> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

>> +

>> +	WARN_ON(al < sizeof(*data));

>> +	memset(data, 0, sizeof(*data));

>> +	data->auth_type = NVME_AUTH_DHCHAP_MESSAGES;

>> +	data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1;

>> +	data->t_id = cpu_to_le16(req->sq->dhchap_tid);

>> +	data->hl = req->sq->dhchap_hash_len;

>> +	if (req->sq->dhchap_c2) {

>> +		if (nvmet_auth_ctrl_hash(req, data->rval, data->hl))

>> +			return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;

>> +		data->rvalid = 1;

>> +		pr_debug("ctrl %d qid %d response %*ph\n",

>> +			 ctrl->cntlid, req->sq->qid, data->hl, data->rval);

>> +	}

>> +	return 0;

>> +}

>> +

>> +static void nvmet_auth_failure1(struct nvmet_req *req, void *d, int al)

>> +{

>> +	struct nvmf_auth_dhchap_failure_data *data = d;

>> +

>> +	WARN_ON(al < sizeof(*data));

>> +	data->auth_type = NVME_AUTH_COMMON_MESSAGES;

>> +	data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +	data->t_id = cpu_to_le32(req->sq->dhchap_tid);

>> +	data->reason_code = NVME_AUTH_DHCHAP_FAILURE_REASON_FAILED;

>> +	data->reason_code_explanation = req->sq->dhchap_status;

>> +}

>> +

>> +void nvmet_execute_auth_receive(struct nvmet_req *req)

>> +{

>> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

>> +	void *d;

>> +	u32 al;

>> +	u16 status = 0;

>> +

>> +	if (req->cmd->auth_receive.secp != NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER)

> {

>> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

>> +		req->error_loc =

>> +			offsetof(struct nvmf_auth_receive_command, secp);

>> +		goto done;

>> +	}

>> +	if (req->cmd->auth_receive.spsp0 != 0x01) {

>> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

>> +		req->error_loc =

>> +			offsetof(struct nvmf_auth_receive_command, spsp0);

>> +		goto done;

>> +	}

>> +	if (req->cmd->auth_receive.spsp1 != 0x01) {

>> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

>> +		req->error_loc =

>> +			offsetof(struct nvmf_auth_receive_command, spsp1);

>> +		goto done;

>> +	}

>> +	al = le32_to_cpu(req->cmd->auth_receive.al);

>> +	if (!al) {

>> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

>> +		req->error_loc =

>> +			offsetof(struct nvmf_auth_receive_command, al);

>> +		goto done;

>> +	}

>> +	if (!nvmet_check_transfer_len(req, al)) {

>> +		pr_debug("%s: transfer length mismatch (%u)\n", __func__, al);

>> +		return;

>> +	}

>> +

>> +	d = kmalloc(al, GFP_KERNEL);

>> +	if (!d) {

>> +		status = NVME_SC_INTERNAL;

>> +		goto done;

>> +	}

>> +	pr_debug("%s: ctrl %d qid %d step %x\n", __func__,

>> +		 ctrl->cntlid, req->sq->qid, req->sq->dhchap_step);

>> +	switch (req->sq->dhchap_step) {

>> +	case NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE:

>> +		status = nvmet_auth_challenge(req, d, al);

>> +		if (status < 0) {

>> +			pr_warn("ctrl %d qid %d: challenge error (%d)\n",

>> +				ctrl->cntlid, req->sq->qid, status);

>> +			status = NVME_SC_INTERNAL;

>> +			break;

>> +		}

>> +		if (status) {

>> +			req->sq->dhchap_status = status;

>> +			nvmet_auth_failure1(req, d, al);

>> +			pr_warn("ctrl %d qid %d: challenge status (%x)\n",

>> +				ctrl->cntlid, req->sq->qid,

>> +				req->sq->dhchap_status);

>> +			status = 0;

>> +			break;

>> +		}

>> +		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_REPLY;

>> +		break;

>> +	case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1:

>> +		status = nvmet_auth_success1(req, d, al);

>> +		if (status) {

>> +			req->sq->dhchap_status = status;

>> +			nvmet_auth_failure1(req, d, al);

>> +			pr_warn("ctrl %d qid %d: success1 status (%x)\n",

>> +				ctrl->cntlid, req->sq->qid,

>> +				req->sq->dhchap_status);

>> +			break;

>> +		}

>> +		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2;

>> +		break;

>> +	case NVME_AUTH_DHCHAP_MESSAGE_FAILURE1:

>> +		nvmet_auth_failure1(req, d, al);

>> +		pr_warn("ctrl %d qid %d failure1 (%x)\n",

>> +			ctrl->cntlid, req->sq->qid, req->sq->dhchap_status);

>> +		break;

>> +	default:

>> +		pr_warn("ctrl %d qid %d unhandled step (%d)\n",

>> +			ctrl->cntlid, req->sq->qid, req->sq->dhchap_step);

>> +		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +		req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_FAILED;

>> +		nvmet_auth_failure1(req, d, al);

>> +		status = 0;

>> +		break;

>> +	}

>> +

>> +	status = nvmet_copy_to_sgl(req, 0, d, al);

>> +	kfree(d);

>> +done:

>> +	req->cqe->result.u64 = 0;

>> +	nvmet_req_complete(req, status);

>> +	if (req->sq->dhchap_step == NVME_AUTH_DHCHAP_MESSAGE_FAILURE1) {

>> +		kfree(req->sq->dhchap_c1);

>> +		kfree(req->sq->dhchap_c2);

>> +		nvmet_ctrl_fatal_error(ctrl);

>> +	}

>> +}

>> diff --git a/drivers/nvme/target/fabrics-cmd.c

>> b/drivers/nvme/target/fabrics-cmd.c index 7d0f3523fdab..53fb853cd8fe 100644

>> --- a/drivers/nvme/target/fabrics-cmd.c

>> +++ b/drivers/nvme/target/fabrics-cmd.c

>> @@ -93,6 +93,14 @@ u16 nvmet_parse_fabrics_cmd(struct nvmet_req *req)

>>   	case nvme_fabrics_type_property_get:

>>   		req->execute = nvmet_execute_prop_get;

>>   		break;

>> +#ifdef CONFIG_NVME_TARGET_AUTH

>> +	case nvme_fabrics_type_auth_send:

>> +		req->execute = nvmet_execute_auth_send;

>> +		break;

>> +	case nvme_fabrics_type_auth_receive:

>> +		req->execute = nvmet_execute_auth_receive;

>> +		break;

>> +#endif

>>   	default:

>>   		pr_debug("received unknown capsule type 0x%x\n",

>>   			cmd->fabrics.fctype);

>> @@ -155,6 +163,7 @@ static void nvmet_execute_admin_connect(struct nvmet_req

>> *req) struct nvmf_connect_data *d;

>>   	struct nvmet_ctrl *ctrl = NULL;

>>   	u16 status = 0;

>> +	int ret;

>>

>>   	if (!nvmet_check_transfer_len(req, sizeof(struct nvmf_connect_data)))

>>   		return;

>> @@ -197,17 +206,31 @@ static void nvmet_execute_admin_connect(struct

>> nvmet_req *req)

>>

>>   	uuid_copy(&ctrl->hostid, &d->hostid);

>>

>> +	ret = nvmet_setup_auth(ctrl, req);

>> +	if (ret < 0) {

>> +		pr_err("Failed to setup authentication, error %d\n", ret);

>> +		nvmet_ctrl_put(ctrl);

>> +		if (ret == -EPERM)

>> +			status = (NVME_SC_CONNECT_INVALID_HOST | NVME_SC_DNR);

>> +		else

>> +			status = NVME_SC_INTERNAL;

>> +		goto out;

>> +	}

>> +

>>   	status = nvmet_install_queue(ctrl, req);

>>   	if (status) {

>>   		nvmet_ctrl_put(ctrl);

>>   		goto out;

>>   	}

>>

>> -	pr_info("creating controller %d for subsystem %s for NQN %s%s.\n",

>> +	pr_info("creating controller %d for subsystem %s for NQN %s%s%s.\n",

>>   		ctrl->cntlid, ctrl->subsys->subsysnqn, ctrl->hostnqn,

>> -		ctrl->pi_support ? " T10-PI is enabled" : "");

>> +		ctrl->pi_support ? " T10-PI is enabled" : "",

>> +		nvmet_has_auth(ctrl) ? " with DH-HMAC-CHAP" : "");

>>   	req->cqe->result.u16 = cpu_to_le16(ctrl->cntlid);

>>

>> +	if (nvmet_has_auth(ctrl))

>> +		nvmet_init_auth(ctrl, req);

>>   out:

>>   	kfree(d);

>>   complete:

>> @@ -267,6 +290,9 @@ static void nvmet_execute_io_connect(struct nvmet_req

>> *req) }

>>

>>   	pr_debug("adding queue %d to ctrl %d.\n", qid, ctrl->cntlid);

>> +	req->cqe->result.u16 = cpu_to_le16(ctrl->cntlid);

>> +	if (nvmet_has_auth(ctrl))

>> +		nvmet_init_auth(ctrl, req);

>>

>>   out:

>>   	kfree(d);

>> diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h

>> index 06dd3d537f07..ef8815e137d7 100644

>> --- a/drivers/nvme/target/nvmet.h

>> +++ b/drivers/nvme/target/nvmet.h

>> @@ -108,6 +108,20 @@ struct nvmet_sq {

>>   	u16			size;

>>   	u32			sqhd;

>>   	bool			sqhd_disabled;

>> +#ifdef CONFIG_NVME_TARGET_AUTH

>> +	bool			authenticated;

>> +	u16			dhchap_tid;

>> +	u16			dhchap_status;

>> +	int			dhchap_step;

>> +	u8			dhchap_hash_id;

>> +	u8			dhchap_hash_len;

>> +	u8			*dhchap_c1;

>> +	u8			*dhchap_c2;

>> +	u32			dhchap_s1;

>> +	u32			dhchap_s2;

>> +	u8			*dhchap_skey;

>> +	int			dhchap_skey_len;

>> +#endif

>>   	struct completion	free_done;

>>   	struct completion	confirm_done;

>>   };

>> @@ -209,6 +223,15 @@ struct nvmet_ctrl {

>>   	u64			err_counter;

>>   	struct nvme_error_slot	slots[NVMET_ERROR_LOG_SLOTS];

>>   	bool			pi_support;

>> +#ifdef CONFIG_NVME_TARGET_AUTH

>> +	u32			dhchap_seqnum;

>> +	u8			*dhchap_key;

>> +	size_t			dhchap_key_len;

>> +	struct crypto_shash	*shash_tfm;

>> +	struct crypto_kpp	*dh_tfm;

>> +	u32			dh_gid;

>> +	u32			dh_keysize;

>> +#endif

>>   };

>>

>>   struct nvmet_subsys {

>> @@ -270,6 +293,10 @@ static inline struct nvmet_subsys

>> *namespaces_to_subsys(

>>

>>   struct nvmet_host {

>>   	struct config_group	group;

>> +	u8			*dhchap_secret;

>> +	u8			dhchap_key_hash;

>> +	u8			dhchap_hash_id;

>> +	u8			dhchap_dhgroup_id;

>>   };

>>

>>   static inline struct nvmet_host *to_host(struct config_item *item)

>> @@ -659,4 +686,48 @@ static inline void nvmet_req_bio_put(struct nvmet_req

>> *req, struct bio *bio) bio_put(bio);

>>   }

>>

>> +#ifdef CONFIG_NVME_TARGET_AUTH

>> +void nvmet_execute_auth_send(struct nvmet_req *req);

>> +void nvmet_execute_auth_receive(struct nvmet_req *req);

>> +int nvmet_auth_set_host_key(struct nvmet_host *host, const char *secret);

>> +int nvmet_auth_set_host_hash(struct nvmet_host *host, const char *hash);

>> +int nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req);

>> +void nvmet_init_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req);

>> +void nvmet_reset_auth(struct nvmet_ctrl *ctrl);

>> +void nvmet_auth_sq_free(struct nvmet_sq *sq);

>> +int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, int dhgroup_id);

>> +bool nvmet_check_auth_status(struct nvmet_req *req);

>> +int nvmet_auth_host_hash(struct nvmet_req *req, u8 *response,

>> +			 unsigned int hash_len);

>> +int nvmet_auth_ctrl_hash(struct nvmet_req *req, u8 *response,

>> +			 unsigned int hash_len);

>> +static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl)

>> +{

>> +	return ctrl->shash_tfm != NULL;

>> +}

>> +int nvmet_auth_ctrl_exponential(struct nvmet_req *req,

>> +				u8 *buf, int buf_size);

>> +int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,

>> +			    u8 *buf, int buf_size);

>> +#else

>> +static inline int nvmet_setup_auth(struct nvmet_ctrl *ctrl,

>> +				   struct nvmet_req *req)

>> +{

>> +	return 0;

>> +}

>> +static inline void nvmet_init_auth(struct nvmet_ctrl *ctrl,

>> +				   struct nvmet_req *req) {};

>> +static inline void nvmet_reset_auth(struct nvmet_ctrl *ctrl) {};

>> +static inline void nvmet_auth_sq_free(struct nvmet_sq *sq) {};

>> +static inline bool nvmet_check_auth_status(struct nvmet_req *req)

>> +{

>> +	return true;

>> +}

>> +static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl)

>> +{

>> +	return false;

>> +}

>> +static inline const char *nvmet_dhchap_dhgroup_name(int dhgid) { return

>> NULL; } +#endif

>> +

>>   #endif /* _NVMET_H */


Thanks for the review!

Cheers,

Hannes
-- 
Dr. Hannes Reinecke                Kernel Storage Architect
hare@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
Stephan Mueller July 18, 2021, 12:56 p.m. UTC | #3
Am Sonntag, 18. Juli 2021, 14:37:34 CEST schrieb Hannes Reinecke:

Hi Hannes,

> On 7/17/21 6:49 PM, Stephan Müller wrote:

> > Am Freitag, 16. Juli 2021, 13:04:26 CEST schrieb Hannes Reinecke:

> > 

> > Hi Hannes,

> > 

> >> Implement support for NVMe-oF In-Band authentication. This patch

> >> adds two additional configfs entries 'dhchap_key' and 'dhchap_hash'

> >> to the 'host' configfs directory. The 'dhchap_key' needs to be

> >> specified in the format outlined in the base spec.

> >> Augmented challenge support is not implemented, and concatenation

> >> with TLS encryption is not supported.

> >> 

> >> Signed-off-by: Hannes Reinecke <hare@suse.de>

> >> ---

> >> 

> >>   drivers/nvme/target/Kconfig            |  10 +

> >>   drivers/nvme/target/Makefile           |   1 +

> >>   drivers/nvme/target/admin-cmd.c        |   4 +

> >>   drivers/nvme/target/auth.c             | 352 +++++++++++++++++++

> >>   drivers/nvme/target/configfs.c         |  71 +++-

> >>   drivers/nvme/target/core.c             |   8 +

> >>   drivers/nvme/target/fabrics-cmd-auth.c | 460 +++++++++++++++++++++++++

> >>   drivers/nvme/target/fabrics-cmd.c      |  30 +-

> >>   drivers/nvme/target/nvmet.h            |  71 ++++

> >>   9 files changed, 1004 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 4be2ececbc45..d5656ef1559e 100644

> >> --- a/drivers/nvme/target/Kconfig

> >> +++ b/drivers/nvme/target/Kconfig

> >> @@ -85,3 +85,13 @@ 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_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 0cb98f2bbc8c..320cefc64ee0 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..00c7d051dfb1

> >> --- /dev/null

> >> +++ b/drivers/nvme/target/auth.c

> >> @@ -0,0 +1,352 @@

> >> +// 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 <crypto/kpp.h>

> >> +#include <crypto/dh.h>

> >> +#include <crypto/ffdhe.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_warn("DH-HMAC-CHAP hash %s unsupported\n", hmac);

> >> +			host->dhchap_key_hash = -1;

> >> +			return -EAGAIN;

> >> +		}

> >> +		/* 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);

> > 

> > Just like before - are you sure that the secret is an ASCII string and no

> > binary blob?

> 

> That is ensured by the transport encoding (cf NVMe Base Specification

> version 2.0). Also, this information is being passed in via the configfs

> interface, so it's bounded by PAGE_SIZE. But yes, we should be inserting

> a terminating 'NULL' character at the end of the page to ensure we don't

> incur an buffer overrun. Any other failure will be checked for during

> base64 decoding.

> 

> >> +	if (!host->dhchap_secret)

> >> +		return -ENOMEM;

> >> +	/* Default to SHA256 */

> >> +	if (!host->dhchap_hash_id)

> >> +		host->dhchap_hash_id = NVME_AUTH_DHCHAP_HASH_SHA256;

> >> +

> >> +	pr_debug("Using hash %s\n",

> >> +		 nvme_auth_hmac_name(host->dhchap_hash_id));

> >> +	return 0;

> >> +}

> >> +

> >> +int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, int dhgroup_id)

> >> +{

> >> +	int ret = -ENOTSUPP;

> >> +

> >> +	if (dhgroup_id == NVME_AUTH_DHCHAP_DHGROUP_NULL)

> >> +		return 0;

> >> +

> >> +	return ret;

> >> +}

> >> +

> >> +int nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req)

> >> +{

> >> +	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;

> >> +

> >> +	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;

> >> +	}

> >> +

> >> +	hash_name = nvme_auth_hmac_name(host->dhchap_hash_id);

> >> +	if (!hash_name) {

> >> +		pr_debug("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_debug("failed to allocate shash %s\n", hash_name);

> >> +		ret = PTR_ERR(ctrl->shash_tfm);

> >> +		ctrl->shash_tfm = NULL;

> >> +		goto out_unlock;

> >> +	}

> >> +

> >> +	ctrl->dhchap_key = nvme_auth_extract_secret(host->dhchap_secret,

> >> +						    &ctrl->dhchap_key_len);

> >> +	if (IS_ERR(ctrl->dhchap_key)) {

> >> +		pr_debug("failed to extract host key, error %d\n", ret);

> >> +		ret = PTR_ERR(ctrl->dhchap_key);

> >> +		ctrl->dhchap_key = NULL;

> >> +		goto out_free_hash;

> >> +	}

> >> +	if (host->dhchap_key_hash) {

> >> +		struct crypto_shash *key_tfm;

> >> +

> >> +		hash_name = nvme_auth_hmac_name(host->dhchap_key_hash);

> >> +		key_tfm = crypto_alloc_shash(hash_name, 0, 0);

> >> +		if (IS_ERR(key_tfm)) {

> >> +			ret = PTR_ERR(key_tfm);

> >> +			goto out_free_hash;

> >> +		} else {

> >> +			SHASH_DESC_ON_STACK(shash, key_tfm);

> >> +

> >> +			shash->tfm = key_tfm;

> >> +			ret = crypto_shash_setkey(key_tfm, ctrl->dhchap_key,

> >> +						  ctrl->dhchap_key_len);

> >> +			crypto_shash_init(shash);

> >> +			crypto_shash_update(shash, ctrl->subsys->subsysnqn,

> >> +					    strlen(ctrl->subsys->subsysnqn));

> >> +			crypto_shash_update(shash, "NVMe-over-Fabrics", 17);

> >> +			crypto_shash_final(shash, ctrl->dhchap_key);

> >> +			crypto_free_shash(key_tfm);

> >> +		}

> >> +	}

> >> +	pr_debug("%s: using key %*ph\n", __func__,

> >> +		 (int)ctrl->dhchap_key_len, ctrl->dhchap_key);

> >> +	ret = crypto_shash_setkey(ctrl->shash_tfm, ctrl->dhchap_key,

> > 

> > Is it truly necessary to keep the key around in ctrl->dhchap_key? It looks

> > to me that this buffer is only used here and thus could be turned into a

> > local variable. Keys flying around in memory is not a good idea. :-)

> 

> The key is also used when using the ffdhe algorithm.

> Note: I _think_ that I need to use this key for the ffdhe algorithm,

> because the implementation I came up with is essentially plain DH with

> pre-defined 'p', 'q' and 'g' values. But the DH implementation also

> requires a 'key', and for that I'm using this key here.

> 

> It might be that I'm completely off, and don't need to use a key for our

> DH implementation. In that case you are correct.

> (And that's why I said I'll need a review of the FFDHE implementation).

> But for now I'll need the key for FFDHE.


Do I understand you correctly that the dhchap_key is used as the input to the 
DH - i.e. it is the remote public key then? It looks strange that this is used 
for DH but then it is changed here by hashing it together with something else 
to form a new dhchap_key. Maybe that is what the protocol says. But it sounds 
strange to me, especially when you think that dhchap_key would be, say, 2048 
bits if it is truly the remote public key and then after the hashing it is 256 
or 512 bits depending on the HMAC type. This means that after the hashing, 
this dhchap_key cannot be used for FFC-DH.

Or are you using the dhchap_key for two different purposes?

It seems I miss something here.


> >> +	response = kmalloc(data->hl, GFP_KERNEL);

> > 

> > Again, align to CRYPTO_MINALIGN_ATTR?

> 

> Ah, _that_ alignment.

> Wasn't aware that I need to align to anything.

> But if that's required, sure, I'll be fixing it.


Again, that only saves you a memcpy in shash_final.

Ciao
Stephan
Herbert Xu July 18, 2021, 1:26 p.m. UTC | #4
On Sun, Jul 18, 2021 at 02:37:34PM +0200, Hannes Reinecke wrote:
>

> > > +	response = kmalloc(data->hl, GFP_KERNEL);

> > 

> > Again, align to CRYPTO_MINALIGN_ATTR?

> 

> Ah, _that_ alignment.

> Wasn't aware that I need to align to anything.

> But if that's required, sure, I'll be fixing it.


Memory returned by kmalloc is guaranteed to be aligned to
CRYPTO_MINALIGN_ATTR, in fact that's the whole point of that
attribute.

Cheers,
-- 
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
Hannes Reinecke July 19, 2021, 8:15 a.m. UTC | #5
On 7/18/21 2:56 PM, Stephan Müller wrote:
> Am Sonntag, 18. Juli 2021, 14:37:34 CEST schrieb Hannes Reinecke:

> 

> Hi Hannes,

> 

>> On 7/17/21 6:49 PM, Stephan Müller wrote:

>>> Am Freitag, 16. Juli 2021, 13:04:26 CEST schrieb Hannes Reinecke:

>>>

>>> Hi Hannes,

>>>

>>>> Implement support for NVMe-oF In-Band authentication. This patch

>>>> adds two additional configfs entries 'dhchap_key' and 'dhchap_hash'

>>>> to the 'host' configfs directory. The 'dhchap_key' needs to be

>>>> specified in the format outlined in the base spec.

>>>> Augmented challenge support is not implemented, and concatenation

>>>> with TLS encryption is not supported.

>>>>

>>>> Signed-off-by: Hannes Reinecke <hare@suse.de>

>>>> ---

>>>>

>>>>   drivers/nvme/target/Kconfig            |  10 +

>>>>   drivers/nvme/target/Makefile           |   1 +

>>>>   drivers/nvme/target/admin-cmd.c        |   4 +

>>>>   drivers/nvme/target/auth.c             | 352 +++++++++++++++++++

>>>>   drivers/nvme/target/configfs.c         |  71 +++-

>>>>   drivers/nvme/target/core.c             |   8 +

>>>>   drivers/nvme/target/fabrics-cmd-auth.c | 460 +++++++++++++++++++++++++

>>>>   drivers/nvme/target/fabrics-cmd.c      |  30 +-

>>>>   drivers/nvme/target/nvmet.h            |  71 ++++

>>>>   9 files changed, 1004 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 4be2ececbc45..d5656ef1559e 100644

>>>> --- a/drivers/nvme/target/Kconfig

>>>> +++ b/drivers/nvme/target/Kconfig

>>>> @@ -85,3 +85,13 @@ 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_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 0cb98f2bbc8c..320cefc64ee0 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..00c7d051dfb1

>>>> --- /dev/null

>>>> +++ b/drivers/nvme/target/auth.c

>>>> @@ -0,0 +1,352 @@

>>>> +// 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 <crypto/kpp.h>

>>>> +#include <crypto/dh.h>

>>>> +#include <crypto/ffdhe.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_warn("DH-HMAC-CHAP hash %s unsupported\n", hmac);

>>>> +			host->dhchap_key_hash = -1;

>>>> +			return -EAGAIN;

>>>> +		}

>>>> +		/* 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);

>>>

>>> Just like before - are you sure that the secret is an ASCII string and no

>>> binary blob?

>>

>> That is ensured by the transport encoding (cf NVMe Base Specification

>> version 2.0). Also, this information is being passed in via the configfs

>> interface, so it's bounded by PAGE_SIZE. But yes, we should be inserting

>> a terminating 'NULL' character at the end of the page to ensure we don't

>> incur an buffer overrun. Any other failure will be checked for during

>> base64 decoding.

>>

>>>> +	if (!host->dhchap_secret)

>>>> +		return -ENOMEM;

>>>> +	/* Default to SHA256 */

>>>> +	if (!host->dhchap_hash_id)

>>>> +		host->dhchap_hash_id = NVME_AUTH_DHCHAP_HASH_SHA256;

>>>> +

>>>> +	pr_debug("Using hash %s\n",

>>>> +		 nvme_auth_hmac_name(host->dhchap_hash_id));

>>>> +	return 0;

>>>> +}

>>>> +

>>>> +int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, int dhgroup_id)

>>>> +{

>>>> +	int ret = -ENOTSUPP;

>>>> +

>>>> +	if (dhgroup_id == NVME_AUTH_DHCHAP_DHGROUP_NULL)

>>>> +		return 0;

>>>> +

>>>> +	return ret;

>>>> +}

>>>> +

>>>> +int nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req)

>>>> +{

>>>> +	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;

>>>> +

>>>> +	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;

>>>> +	}

>>>> +

>>>> +	hash_name = nvme_auth_hmac_name(host->dhchap_hash_id);

>>>> +	if (!hash_name) {

>>>> +		pr_debug("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_debug("failed to allocate shash %s\n", hash_name);

>>>> +		ret = PTR_ERR(ctrl->shash_tfm);

>>>> +		ctrl->shash_tfm = NULL;

>>>> +		goto out_unlock;

>>>> +	}

>>>> +

>>>> +	ctrl->dhchap_key = nvme_auth_extract_secret(host->dhchap_secret,

>>>> +						    &ctrl->dhchap_key_len);

>>>> +	if (IS_ERR(ctrl->dhchap_key)) {

>>>> +		pr_debug("failed to extract host key, error %d\n", ret);

>>>> +		ret = PTR_ERR(ctrl->dhchap_key);

>>>> +		ctrl->dhchap_key = NULL;

>>>> +		goto out_free_hash;

>>>> +	}

>>>> +	if (host->dhchap_key_hash) {

>>>> +		struct crypto_shash *key_tfm;

>>>> +

>>>> +		hash_name = nvme_auth_hmac_name(host->dhchap_key_hash);

>>>> +		key_tfm = crypto_alloc_shash(hash_name, 0, 0);

>>>> +		if (IS_ERR(key_tfm)) {

>>>> +			ret = PTR_ERR(key_tfm);

>>>> +			goto out_free_hash;

>>>> +		} else {

>>>> +			SHASH_DESC_ON_STACK(shash, key_tfm);

>>>> +

>>>> +			shash->tfm = key_tfm;

>>>> +			ret = crypto_shash_setkey(key_tfm, ctrl->dhchap_key,

>>>> +						  ctrl->dhchap_key_len);

>>>> +			crypto_shash_init(shash);

>>>> +			crypto_shash_update(shash, ctrl->subsys->subsysnqn,

>>>> +					    strlen(ctrl->subsys->subsysnqn));

>>>> +			crypto_shash_update(shash, "NVMe-over-Fabrics", 17);

>>>> +			crypto_shash_final(shash, ctrl->dhchap_key);

>>>> +			crypto_free_shash(key_tfm);

>>>> +		}

>>>> +	}

>>>> +	pr_debug("%s: using key %*ph\n", __func__,

>>>> +		 (int)ctrl->dhchap_key_len, ctrl->dhchap_key);

>>>> +	ret = crypto_shash_setkey(ctrl->shash_tfm, ctrl->dhchap_key,

>>>

>>> Is it truly necessary to keep the key around in ctrl->dhchap_key? It looks

>>> to me that this buffer is only used here and thus could be turned into a

>>> local variable. Keys flying around in memory is not a good idea. :-)

>>

>> The key is also used when using the ffdhe algorithm.

>> Note: I _think_ that I need to use this key for the ffdhe algorithm,

>> because the implementation I came up with is essentially plain DH with

>> pre-defined 'p', 'q' and 'g' values. But the DH implementation also

>> requires a 'key', and for that I'm using this key here.

>>

>> It might be that I'm completely off, and don't need to use a key for our

>> DH implementation. In that case you are correct.

>> (And that's why I said I'll need a review of the FFDHE implementation).

>> But for now I'll need the key for FFDHE.

> 

> Do I understand you correctly that the dhchap_key is used as the input to the 

> DH - i.e. it is the remote public key then? It looks strange that this is used 

> for DH but then it is changed here by hashing it together with something else 

> to form a new dhchap_key. Maybe that is what the protocol says. But it sounds 

> strange to me, especially when you think that dhchap_key would be, say, 2048 

> bits if it is truly the remote public key and then after the hashing it is 256 

> or 512 bits depending on the HMAC type. This means that after the hashing, 

> this dhchap_key cannot be used for FFC-DH.

> 

> Or are you using the dhchap_key for two different purposes?

> 

> It seems I miss something here.

> 

No, not entirely. It's me who buggered it up.
I got carried away by the fact that there is a crypto_dh_encode_key()
function, and thought I need to use it here.

Which I don't (apparently).
Will be fixing it up.

Cheers,

Hannes
-- 
Dr. Hannes Reinecke		           Kernel Storage Architect
hare@suse.de			                  +49 911 74053 688
SUSE Software Solutions Germany GmbH, Maxfeldstr. 5, 90409 Nürnberg
HRB 36809 (AG Nürnberg), GF: Felix Imendörffer
Stephan Mueller July 19, 2021, 8:51 a.m. UTC | #6
Am Montag, dem 19.07.2021 um 10:15 +0200 schrieb Hannes Reinecke:
> On 7/18/21 2:56 PM, Stephan Müller wrote:

> > Am Sonntag, 18. Juli 2021, 14:37:34 CEST schrieb Hannes Reinecke:


> > > The key is also used when using the ffdhe algorithm.

> > > Note: I _think_ that I need to use this key for the ffdhe algorithm,

> > > because the implementation I came up with is essentially plain DH with

> > > pre-defined 'p', 'q' and 'g' values. But the DH implementation also

> > > requires a 'key', and for that I'm using this key here.

> > > 

> > > It might be that I'm completely off, and don't need to use a key for our

> > > DH implementation. In that case you are correct.

> > > (And that's why I said I'll need a review of the FFDHE implementation).

> > > But for now I'll need the key for FFDHE.

> > 

> > Do I understand you correctly that the dhchap_key is used as the input to

> > the 

> > DH - i.e. it is the remote public key then? It looks strange that this is

> > used 

> > for DH but then it is changed here by hashing it together with something

> > else 

> > to form a new dhchap_key. Maybe that is what the protocol says. But it

> > sounds 

> > strange to me, especially when you think that dhchap_key would be, say,

> > 2048 

> > bits if it is truly the remote public key and then after the hashing it is

> > 256 

> > this dhchap_key cannot be used for FFC-DH.

> > 

> > Or are you using the dhchap_key for two different purposes?

> > 

> > It seems I miss something here.

> > 

> No, not entirely. It's me who buggered it up.

> I got carried away by the fact that there is a crypto_dh_encode_key()

> function, and thought I need to use it here.


Thank you for clarifying that. It sounds to me that there is no defined
protocol (or if there, I would be wondering how the code would have worked
with a different implementation). Would it make sense to first specify a
protocol for authentication and have it discussed? I personally think it is a
bit difficult to fully understand the protocol from the code and discuss
protocol-level items based on the code.

Thanks
Stephan
Hannes Reinecke July 19, 2021, 9:57 a.m. UTC | #7
On 7/19/21 10:51 AM, Stephan Mueller wrote:
> Am Montag, dem 19.07.2021 um 10:15 +0200 schrieb Hannes Reinecke:

>> On 7/18/21 2:56 PM, Stephan Müller wrote:

>>> Am Sonntag, 18. Juli 2021, 14:37:34 CEST schrieb Hannes Reinecke:

> 

>>>> The key is also used when using the ffdhe algorithm.

>>>> Note: I _think_ that I need to use this key for the ffdhe algorithm,

>>>> because the implementation I came up with is essentially plain DH with

>>>> pre-defined 'p', 'q' and 'g' values. But the DH implementation also

>>>> requires a 'key', and for that I'm using this key here.

>>>>

>>>> It might be that I'm completely off, and don't need to use a key for our

>>>> DH implementation. In that case you are correct.

>>>> (And that's why I said I'll need a review of the FFDHE implementation).

>>>> But for now I'll need the key for FFDHE.

>>>

>>> Do I understand you correctly that the dhchap_key is used as the input to

>>> the 

>>> DH - i.e. it is the remote public key then? It looks strange that this is

>>> used 

>>> for DH but then it is changed here by hashing it together with something

>>> else 

>>> to form a new dhchap_key. Maybe that is what the protocol says. But it

>>> sounds 

>>> strange to me, especially when you think that dhchap_key would be, say,

>>> 2048 

>>> bits if it is truly the remote public key and then after the hashing it is

>>> 256 

>>> this dhchap_key cannot be used for FFC-DH.

>>>

>>> Or are you using the dhchap_key for two different purposes?

>>>

>>> It seems I miss something here.

>>>

>> No, not entirely. It's me who buggered it up.

>> I got carried away by the fact that there is a crypto_dh_encode_key()

>> function, and thought I need to use it here.

> 

> Thank you for clarifying that. It sounds to me that there is no defined

> protocol (or if there, I would be wondering how the code would have worked

> with a different implementation). Would it make sense to first specify a

> protocol for authentication and have it discussed? I personally think it is a

> bit difficult to fully understand the protocol from the code and discuss

> protocol-level items based on the code.

> 

Oh, the protocol _is_ specified:

https://nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-2_0-2021.06.02-Ratified-5.pdf

It's just that I have issues translating that spec onto what the kernel
provides.

Cheers,

Hannes
-- 
Dr. Hannes Reinecke		           Kernel Storage Architect
hare@suse.de			                  +49 911 74053 688
SUSE Software Solutions Germany GmbH, Maxfeldstr. 5, 90409 Nürnberg
HRB 36809 (AG Nürnberg), GF: Felix Imendörffer
Stephan Mueller July 19, 2021, 10:19 a.m. UTC | #8
Am Montag, dem 19.07.2021 um 11:57 +0200 schrieb Hannes Reinecke:
> On 7/19/21 10:51 AM, Stephan Mueller wrote:

> > Am Montag, dem 19.07.2021 um 10:15 +0200 schrieb Hannes Reinecke:

> > > On 7/18/21 2:56 PM, Stephan Müller wrote:

> > > > Am Sonntag, 18. Juli 2021, 14:37:34 CEST schrieb Hannes Reinecke:

> > 

> > > > > The key is also used when using the ffdhe algorithm.

> > > > > Note: I _think_ that I need to use this key for the ffdhe algorithm,

> > > > > because the implementation I came up with is essentially plain DH

> > > > > with

> > > > > pre-defined 'p', 'q' and 'g' values. But the DH implementation also

> > > > > requires a 'key', and for that I'm using this key here.

> > > > > 

> > > > > It might be that I'm completely off, and don't need to use a key for

> > > > > our

> > > > > DH implementation. In that case you are correct.

> > > > > (And that's why I said I'll need a review of the FFDHE

> > > > > implementation).

> > > > > But for now I'll need the key for FFDHE.

> > > > 

> > > > Do I understand you correctly that the dhchap_key is used as the input

> > > > to

> > > > the 

> > > > DH - i.e. it is the remote public key then? It looks strange that this

> > > > is

> > > > used 

> > > > for DH but then it is changed here by hashing it together with

> > > > something

> > > > else 

> > > > to form a new dhchap_key. Maybe that is what the protocol says. But it

> > > > sounds 

> > > > strange to me, especially when you think that dhchap_key would be,

> > > > say,

> > > > 2048 

> > > > bits if it is truly the remote public key and then after the hashing

> > > > it is

> > > > 256 

> > > > this dhchap_key cannot be used for FFC-DH.

> > > > 

> > > > Or are you using the dhchap_key for two different purposes?

> > > > 

> > > > It seems I miss something here.

> > > > 

> > > No, not entirely. It's me who buggered it up.

> > > I got carried away by the fact that there is a crypto_dh_encode_key()

> > > function, and thought I need to use it here.

> > 

> > Thank you for clarifying that. It sounds to me that there is no defined

> > protocol (or if there, I would be wondering how the code would have worked

> > with a different implementation). Would it make sense to first specify a

> > protocol for authentication and have it discussed? I personally think it

> > is a

> > bit difficult to fully understand the protocol from the code and discuss

> > protocol-level items based on the code.

> > 

> Oh, the protocol _is_ specified:

> 

> https://nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-2_0-2021.06.02-Ratified-5.pdf

> 

> It's just that I have issues translating that spec onto what the kernel

> provides.


according to the naming conventions there in figures 447 and following:

- x and y: DH private key (kernel calls it secret set with dh_set_secret or
encoded into param.key)

- g^x mod p  / g^y mod p: DH public keys from either end that is communicated
over the wire (corresponding to the the DH private keys of x and y) - to set
it, you initialize a dh request and set the public key to it with
kpp_request_set_input. After performing the crypto_kpp_compute_shared_secret
you receive the shared secret

- g^xy mod p: DH shared secret - this is the one that is to be used for the
subsequent hashing /HMAC operations as this is the one that is identical on
both, the host and the controller.

Ciao
Stephan
Hannes Reinecke July 19, 2021, 11:10 a.m. UTC | #9
On 7/19/21 12:19 PM, Stephan Mueller wrote:
> Am Montag, dem 19.07.2021 um 11:57 +0200 schrieb Hannes Reinecke:

>> On 7/19/21 10:51 AM, Stephan Mueller wrote:

>>> Am Montag, dem 19.07.2021 um 10:15 +0200 schrieb Hannes Reinecke:

>>>> On 7/18/21 2:56 PM, Stephan Müller wrote:

>>>>> Am Sonntag, 18. Juli 2021, 14:37:34 CEST schrieb Hannes Reinecke:

>>>

>>>>>> The key is also used when using the ffdhe algorithm.

>>>>>> Note: I _think_ that I need to use this key for the ffdhe algorithm,

>>>>>> because the implementation I came up with is essentially plain DH

>>>>>> with

>>>>>> pre-defined 'p', 'q' and 'g' values. But the DH implementation also

>>>>>> requires a 'key', and for that I'm using this key here.

>>>>>>

>>>>>> It might be that I'm completely off, and don't need to use a key for

>>>>>> our

>>>>>> DH implementation. In that case you are correct.

>>>>>> (And that's why I said I'll need a review of the FFDHE

>>>>>> implementation).

>>>>>> But for now I'll need the key for FFDHE.

>>>>>

>>>>> Do I understand you correctly that the dhchap_key is used as the input

>>>>> to

>>>>> the 

>>>>> DH - i.e. it is the remote public key then? It looks strange that this

>>>>> is

>>>>> used 

>>>>> for DH but then it is changed here by hashing it together with

>>>>> something

>>>>> else 

>>>>> to form a new dhchap_key. Maybe that is what the protocol says. But it

>>>>> sounds 

>>>>> strange to me, especially when you think that dhchap_key would be,

>>>>> say,

>>>>> 2048 

>>>>> bits if it is truly the remote public key and then after the hashing

>>>>> it is

>>>>> 256 

>>>>> this dhchap_key cannot be used for FFC-DH.

>>>>>

>>>>> Or are you using the dhchap_key for two different purposes?

>>>>>

>>>>> It seems I miss something here.

>>>>>

>>>> No, not entirely. It's me who buggered it up.

>>>> I got carried away by the fact that there is a crypto_dh_encode_key()

>>>> function, and thought I need to use it here.

>>>

>>> Thank you for clarifying that. It sounds to me that there is no defined

>>> protocol (or if there, I would be wondering how the code would have worked

>>> with a different implementation). Would it make sense to first specify a

>>> protocol for authentication and have it discussed? I personally think it

>>> is a

>>> bit difficult to fully understand the protocol from the code and discuss

>>> protocol-level items based on the code.

>>>

>> Oh, the protocol _is_ specified:

>>

>> https://nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-2_0-2021.06.02-Ratified-5.pdf

>>

>> It's just that I have issues translating that spec onto what the kernel

>> provides.

> 

> according to the naming conventions there in figures 447 and following:

> 

> - x and y: DH private key (kernel calls it secret set with dh_set_secret or

> encoded into param.key)

> 


But that's were I got confused; one needs a private key here, but there
is no obvious candidate for it. But reading it more closely I guess the
private key is just a random number (cf the spec: g^y mod p, where y is
a random number selected by the host that shall be at least 256 bits
long). So I'll fix it up with the next round.

> - g^x mod p  / g^y mod p: DH public keys from either end that is communicated

> over the wire (corresponding to the the DH private keys of x and y) - to set

> it, you initialize a dh request and set the public key to it with

> kpp_request_set_input. After performing the crypto_kpp_compute_shared_secret

> you receive the shared secret

> 

> - g^xy mod p: DH shared secret - this is the one that is to be used for the

> subsequent hashing /HMAC operations as this is the one that is identical on

> both, the host and the controller.

> 

Thanks. Will be checking the code if I do it correctly.

Cheers,

Hannes
-- 
Dr. Hannes Reinecke		           Kernel Storage Architect
hare@suse.de			                  +49 911 74053 688
SUSE Software Solutions Germany GmbH, Maxfeldstr. 5, 90409 Nürnberg
HRB 36809 (AG Nürnberg), GF: Felix Imendörffer
Stephan Mueller July 19, 2021, 11:52 a.m. UTC | #10
Am Montag, dem 19.07.2021 um 13:10 +0200 schrieb Hannes Reinecke:
> On 7/19/21 12:19 PM, Stephan Mueller wrote:

> > Am Montag, dem 19.07.2021 um 11:57 +0200 schrieb Hannes Reinecke:

> > > On 7/19/21 10:51 AM, Stephan Mueller wrote:

> > > > Am Montag, dem 19.07.2021 um 10:15 +0200 schrieb Hannes Reinecke:

> > > > > On 7/18/21 2:56 PM, Stephan Müller wrote:

> > > > > > Am Sonntag, 18. Juli 2021, 14:37:34 CEST schrieb Hannes Reinecke:

> > > > 

> > > > > > > The key is also used when using the ffdhe algorithm.

> > > > > > > Note: I _think_ that I need to use this key for the ffdhe

> > > > > > > algorithm,

> > > > > > > because the implementation I came up with is essentially plain

> > > > > > > DH

> > > > > > > with

> > > > > > > pre-defined 'p', 'q' and 'g' values. But the DH implementation

> > > > > > > also

> > > > > > > requires a 'key', and for that I'm using this key here.

> > > > > > > 

> > > > > > > It might be that I'm completely off, and don't need to use a key

> > > > > > > for

> > > > > > > our

> > > > > > > DH implementation. In that case you are correct.

> > > > > > > (And that's why I said I'll need a review of the FFDHE

> > > > > > > implementation).

> > > > > > > But for now I'll need the key for FFDHE.

> > > > > > 

> > > > > > Do I understand you correctly that the dhchap_key is used as the

> > > > > > input

> > > > > > to

> > > > > > the 

> > > > > > DH - i.e. it is the remote public key then? It looks strange that

> > > > > > this

> > > > > > is

> > > > > > used 

> > > > > > for DH but then it is changed here by hashing it together with

> > > > > > something

> > > > > > else 

> > > > > > to form a new dhchap_key. Maybe that is what the protocol says.

> > > > > > But it

> > > > > > sounds 

> > > > > > strange to me, especially when you think that dhchap_key would be,

> > > > > > say,

> > > > > > 2048 

> > > > > > bits if it is truly the remote public key and then after the

> > > > > > hashing

> > > > > > it is

> > > > > > 256 

> > > > > > this dhchap_key cannot be used for FFC-DH.

> > > > > > 

> > > > > > Or are you using the dhchap_key for two different purposes?

> > > > > > 

> > > > > > It seems I miss something here.

> > > > > > 

> > > > > No, not entirely. It's me who buggered it up.

> > > > > I got carried away by the fact that there is a

> > > > > crypto_dh_encode_key()

> > > > > function, and thought I need to use it here.

> > > > 

> > > > Thank you for clarifying that. It sounds to me that there is no

> > > > defined

> > > > protocol (or if there, I would be wondering how the code would have

> > > > worked

> > > > with a different implementation). Would it make sense to first specify

> > > > a

> > > > protocol for authentication and have it discussed? I personally think

> > > > it

> > > > is a

> > > > bit difficult to fully understand the protocol from the code and

> > > > discuss

> > > > protocol-level items based on the code.

> > > > 

> > > Oh, the protocol _is_ specified:

> > > 

> > >  

> > > https://nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-2_0-2021.06.02-Ratified-5.pdf

> > > 

> > > It's just that I have issues translating that spec onto what the kernel

> > > provides.

> > 

> > according to the naming conventions there in figures 447 and following:

> > 

> > - x and y: DH private key (kernel calls it secret set with dh_set_secret

> > or

> > encoded into param.key)

> > 

> 

> But that's were I got confused; one needs a private key here, but there

> is no obvious candidate for it. But reading it more closely I guess the

> private key is just a random number (cf the spec: g^y mod p, where y is

> a random number selected by the host that shall be at least 256 bits

> long). So I'll fix it up with the next round.


Here comes the crux: the kernel has an ECC private key generation function
ecdh_set_secret triggered with crypto_kpp_set_secret using a NULL key, but it
has no FFC-DH counterpart.

That said, generating a random number is the most obvious choice, but not the
right one.

The correct one would be following SP800-56A rev 3 and here either section
5.6.1.1.3 or 5.6.1.1.4.

Ciao
Stephan
> 

> > - g^x mod p  / g^y mod p: DH public keys from either end that is

> > communicated

> > over the wire (corresponding to the the DH private keys of x and y) - to

> > set

> > it, you initialize a dh request and set the public key to it with

> > kpp_request_set_input. After performing the

> > crypto_kpp_compute_shared_secret

> > you receive the shared secret

> > 

> > - g^xy mod p: DH shared secret - this is the one that is to be used for

> > the

> > subsequent hashing /HMAC operations as this is the one that is identical

> > on

> > both, the host and the controller.

> > 

> Thanks. Will be checking the code if I do it correctly.

> 

> Cheers,

> 

> Hannes
Hannes Reinecke July 19, 2021, 12:08 p.m. UTC | #11
On 7/19/21 1:52 PM, Stephan Mueller wrote:
> Am Montag, dem 19.07.2021 um 13:10 +0200 schrieb Hannes Reinecke:

>> On 7/19/21 12:19 PM, Stephan Mueller wrote:

>>> Am Montag, dem 19.07.2021 um 11:57 +0200 schrieb Hannes Reinecke:

>>>> On 7/19/21 10:51 AM, Stephan Mueller wrote:

>>>>> Am Montag, dem 19.07.2021 um 10:15 +0200 schrieb Hannes Reinecke:

>>>>>> On 7/18/21 2:56 PM, Stephan Müller wrote:

>>>>>>> Am Sonntag, 18. Juli 2021, 14:37:34 CEST schrieb Hannes Reinecke:

>>>>>

>>>>>>>> The key is also used when using the ffdhe algorithm.

>>>>>>>> Note: I _think_ that I need to use this key for the ffdhe

>>>>>>>> algorithm,

>>>>>>>> because the implementation I came up with is essentially plain

>>>>>>>> DH

>>>>>>>> with

>>>>>>>> pre-defined 'p', 'q' and 'g' values. But the DH implementation

>>>>>>>> also

>>>>>>>> requires a 'key', and for that I'm using this key here.

>>>>>>>>

>>>>>>>> It might be that I'm completely off, and don't need to use a key

>>>>>>>> for

>>>>>>>> our

>>>>>>>> DH implementation. In that case you are correct.

>>>>>>>> (And that's why I said I'll need a review of the FFDHE

>>>>>>>> implementation).

>>>>>>>> But for now I'll need the key for FFDHE.

>>>>>>>

>>>>>>> Do I understand you correctly that the dhchap_key is used as the

>>>>>>> input

>>>>>>> to

>>>>>>> the 

>>>>>>> DH - i.e. it is the remote public key then? It looks strange that

>>>>>>> this

>>>>>>> is

>>>>>>> used 

>>>>>>> for DH but then it is changed here by hashing it together with

>>>>>>> something

>>>>>>> else 

>>>>>>> to form a new dhchap_key. Maybe that is what the protocol says.

>>>>>>> But it

>>>>>>> sounds 

>>>>>>> strange to me, especially when you think that dhchap_key would be,

>>>>>>> say,

>>>>>>> 2048 

>>>>>>> bits if it is truly the remote public key and then after the

>>>>>>> hashing

>>>>>>> it is

>>>>>>> 256 

>>>>>>> this dhchap_key cannot be used for FFC-DH.

>>>>>>>

>>>>>>> Or are you using the dhchap_key for two different purposes?

>>>>>>>

>>>>>>> It seems I miss something here.

>>>>>>>

>>>>>> No, not entirely. It's me who buggered it up.

>>>>>> I got carried away by the fact that there is a

>>>>>> crypto_dh_encode_key()

>>>>>> function, and thought I need to use it here.

>>>>>

>>>>> Thank you for clarifying that. It sounds to me that there is no

>>>>> defined

>>>>> protocol (or if there, I would be wondering how the code would have

>>>>> worked

>>>>> with a different implementation). Would it make sense to first specify

>>>>> a

>>>>> protocol for authentication and have it discussed? I personally think

>>>>> it

>>>>> is a

>>>>> bit difficult to fully understand the protocol from the code and

>>>>> discuss

>>>>> protocol-level items based on the code.

>>>>>

>>>> Oh, the protocol _is_ specified:

>>>>

>>>>  

>>>> https://nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-2_0-2021.06.02-Ratified-5.pdf

>>>>

>>>> It's just that I have issues translating that spec onto what the kernel

>>>> provides.

>>>

>>> according to the naming conventions there in figures 447 and following:

>>>

>>> - x and y: DH private key (kernel calls it secret set with dh_set_secret

>>> or

>>> encoded into param.key)

>>>

>>

>> But that's were I got confused; one needs a private key here, but there

>> is no obvious candidate for it. But reading it more closely I guess the

>> private key is just a random number (cf the spec: g^y mod p, where y is

>> a random number selected by the host that shall be at least 256 bits

>> long). So I'll fix it up with the next round.

> 

> Here comes the crux: the kernel has an ECC private key generation function

> ecdh_set_secret triggered with crypto_kpp_set_secret using a NULL key, but it

> has no FFC-DH counterpart.

> 

> That said, generating a random number is the most obvious choice, but not the

> right one.

> 

> The correct one would be following SP800-56A rev 3 and here either section

> 5.6.1.1.3 or 5.6.1.1.4.

> 


Oh fsck. Of course. Would've been too easy.
Well, more coding required then. Let's see how it goes.
But thanks for your help!

(And I wouldn't say no if someone would step in an provide a 'real'
FFDHE crypto algorithm; now that the parameters are already present :-)

Cheers,

Hannes
-- 
Dr. Hannes Reinecke		           Kernel Storage Architect
hare@suse.de			                  +49 911 74053 688
SUSE Software Solutions Germany GmbH, Maxfeldstr. 5, 90409 Nürnberg
HRB 36809 (AG Nürnberg), GF: Felix Imendörffer
Sagi Grimberg July 19, 2021, 8:38 p.m. UTC | #12
On 7/16/21 4:04 AM, Hannes Reinecke wrote:
> Implement support for NVMe-oF In-Band authentication. This patch

> adds two additional configfs entries 'dhchap_key' and 'dhchap_hash'

> to the 'host' configfs directory. The 'dhchap_key' needs to be

> specified in the format outlined in the base spec.

> Augmented challenge support is not implemented, and concatenation

> with TLS encryption is not supported.

> 

> Signed-off-by: Hannes Reinecke <hare@suse.de>

> ---

>   drivers/nvme/target/Kconfig            |  10 +

>   drivers/nvme/target/Makefile           |   1 +

>   drivers/nvme/target/admin-cmd.c        |   4 +

>   drivers/nvme/target/auth.c             | 352 +++++++++++++++++++

>   drivers/nvme/target/configfs.c         |  71 +++-

>   drivers/nvme/target/core.c             |   8 +

>   drivers/nvme/target/fabrics-cmd-auth.c | 460 +++++++++++++++++++++++++

>   drivers/nvme/target/fabrics-cmd.c      |  30 +-

>   drivers/nvme/target/nvmet.h            |  71 ++++

>   9 files changed, 1004 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 4be2ececbc45..d5656ef1559e 100644

> --- a/drivers/nvme/target/Kconfig

> +++ b/drivers/nvme/target/Kconfig

> @@ -85,3 +85,13 @@ 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_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 0cb98f2bbc8c..320cefc64ee0 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..00c7d051dfb1

> --- /dev/null

> +++ b/drivers/nvme/target/auth.c

> @@ -0,0 +1,352 @@

> +// 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 <crypto/kpp.h>

> +#include <crypto/dh.h>

> +#include <crypto/ffdhe.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_warn("DH-HMAC-CHAP hash %s unsupported\n", hmac);


pr_err

> +			host->dhchap_key_hash = -1;

> +			return -EAGAIN;


Why EAGAIN?

> +		}

> +		/* Use this hash as default */

> +		if (!host->dhchap_hash_id)

> +			host->dhchap_hash_id = host->dhchap_key_hash;


Why?

> +	}

> +	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_HASH_SHA256;


What is the thought here?

> +

> +	pr_debug("Using hash %s\n",

> +		 nvme_auth_hmac_name(host->dhchap_hash_id));

> +	return 0;

> +}

> +

> +int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, int dhgroup_id)

> +{

> +	int ret = -ENOTSUPP;

> +

> +	if (dhgroup_id == NVME_AUTH_DHCHAP_DHGROUP_NULL)

> +		return 0;

> +

> +	return ret;

> +}

> +

> +int nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req)

> +{

> +	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;

> +

> +	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;


I think you should propogate the nvme status code instead...

> +		goto out_unlock;

> +	}

> +	if (!host->dhchap_secret) {

> +		pr_debug("No authentication provided\n");

> +		goto out_unlock;

> +	}

> +

> +	hash_name = nvme_auth_hmac_name(host->dhchap_hash_id);

> +	if (!hash_name) {


Can this actually happen?

> +		pr_debug("Hash ID %d invalid\n", host->dhchap_hash_id);


warning, not debug.

> +		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_debug("failed to allocate shash %s\n", hash_name);

> +		ret = PTR_ERR(ctrl->shash_tfm);

> +		ctrl->shash_tfm = NULL;

> +		goto out_unlock;

> +	}

> +

> +	ctrl->dhchap_key = nvme_auth_extract_secret(host->dhchap_secret,

> +						    &ctrl->dhchap_key_len);

> +	if (IS_ERR(ctrl->dhchap_key)) {

> +		pr_debug("failed to extract host key, error %d\n", ret);

> +		ret = PTR_ERR(ctrl->dhchap_key);

> +		ctrl->dhchap_key = NULL;

> +		goto out_free_hash;

> +	}

> +	if (host->dhchap_key_hash) {

> +		struct crypto_shash *key_tfm;

> +

> +		hash_name = nvme_auth_hmac_name(host->dhchap_key_hash);

> +		key_tfm = crypto_alloc_shash(hash_name, 0, 0);

> +		if (IS_ERR(key_tfm)) {

> +			ret = PTR_ERR(key_tfm);

> +			goto out_free_hash;

> +		} else {

> +			SHASH_DESC_ON_STACK(shash, key_tfm);

> +

> +			shash->tfm = key_tfm;

> +			ret = crypto_shash_setkey(key_tfm, ctrl->dhchap_key,

> +						  ctrl->dhchap_key_len);

> +			crypto_shash_init(shash);

> +			crypto_shash_update(shash, ctrl->subsys->subsysnqn,

> +					    strlen(ctrl->subsys->subsysnqn));

> +			crypto_shash_update(shash, "NVMe-over-Fabrics", 17);

> +			crypto_shash_final(shash, ctrl->dhchap_key);

> +			crypto_free_shash(key_tfm);

> +		}

> +	}

> +	pr_debug("%s: using key %*ph\n", __func__,

> +		 (int)ctrl->dhchap_key_len, ctrl->dhchap_key);

> +	ret = crypto_shash_setkey(ctrl->shash_tfm, ctrl->dhchap_key,

> +				  ctrl->dhchap_key_len);

> +out_free_hash:

> +	if (ret) {

> +		if (ctrl->dhchap_key) {

> +			kfree(ctrl->dhchap_key);

> +			ctrl->dhchap_key = NULL;

> +		}

> +		crypto_free_shash(ctrl->shash_tfm);

> +		ctrl->shash_tfm = NULL;

> +	}

> +out_unlock:

> +	up_read(&nvmet_config_sem);

> +

> +	return ret;

> +}

> +

> +void nvmet_auth_sq_free(struct nvmet_sq *sq)

> +{

> +	if (sq->dhchap_c1)

> +		kfree(sq->dhchap_c1);


just kfree, no need to if

> +	if (sq->dhchap_c2)

> +		kfree(sq->dhchap_c2);

> +	if (sq->dhchap_skey)

> +		kfree(sq->dhchap_skey);

> +}

> +

> +void nvmet_reset_auth(struct nvmet_ctrl *ctrl)


Shouldn't this be nvmet_destroy_auth? reset indicates
it can be reused again...

> +{

> +	if (ctrl->shash_tfm) {

> +		crypto_free_shash(ctrl->shash_tfm);

> +		ctrl->shash_tfm = NULL;

> +	}

> +	if (ctrl->dh_tfm) {

> +		crypto_free_kpp(ctrl->dh_tfm);

> +		ctrl->dh_tfm = NULL;

> +	}

> +	if (ctrl->dhchap_key) {

> +		kfree(ctrl->dhchap_key);

> +		ctrl->dhchap_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;

> +	u8 buf[4];

> +	int ret;

> +

> +	if (ctrl->dh_gid != NVME_AUTH_DHCHAP_DHGROUP_NULL) {

> +		ret = -ENOTSUPP;

> +		goto out;

> +	}

> +

> +	shash->tfm = ctrl->shash_tfm;

> +	ret = crypto_shash_init(shash);

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, challenge, shash_len);

> +	if (ret)

> +		goto out;

> +	put_unaligned_le32(req->sq->dhchap_s1, buf);

> +	ret = crypto_shash_update(shash, buf, 4);

> +	if (ret)

> +		goto out;

> +	put_unaligned_le16(req->sq->dhchap_tid, buf);

> +	ret = crypto_shash_update(shash, buf, 2);

> +	if (ret)

> +		goto out;

> +	memset(buf, 0, 4);

> +	ret = crypto_shash_update(shash, buf, 1);

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, "HostHost", 8);

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, ctrl->hostnqn, strlen(ctrl->hostnqn));

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, buf, 1);

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, ctrl->subsysnqn,

> +				  strlen(ctrl->subsysnqn));

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_final(shash, response);

> +out:

> +	if (challenge != req->sq->dhchap_c1)

> +		kfree(challenge);


What about actually failing?

> +	return 0;

> +}

> +

> +int nvmet_auth_ctrl_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_c2;

> +	u8 buf[4];

> +	int ret;

> +

> +	pr_debug("%s: ctrl %d hash seq %d transaction %u\n", __func__,

> +		 ctrl->cntlid, req->sq->dhchap_s2, req->sq->dhchap_tid);

> +	pr_debug("%s: ctrl %d challenge %*ph\n", __func__,

> +		 ctrl->cntlid, shash_len, req->sq->dhchap_c2);

> +	pr_debug("%s: ctrl %d subsysnqn %s\n", __func__,

> +		 ctrl->cntlid, ctrl->subsysnqn);

> +	pr_debug("%s: ctrl %d hostnqn %s\n", __func__,

> +		 ctrl->cntlid, ctrl->hostnqn);

> +

> +	if (ctrl->dh_gid != NVME_AUTH_DHCHAP_DHGROUP_NULL) {

> +		ret = -ENOTSUPP;

> +		goto out;

> +	}

> +

> +	shash->tfm = ctrl->shash_tfm;

> +	ret = crypto_shash_init(shash);

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, challenge, shash_len);

> +	if (ret)

> +		goto out;

> +	put_unaligned_le32(req->sq->dhchap_s2, buf);

> +	ret = crypto_shash_update(shash, buf, 4);

> +	if (ret)

> +		goto out;

> +	put_unaligned_le16(req->sq->dhchap_tid, buf);

> +	ret = crypto_shash_update(shash, buf, 2);

> +	if (ret)

> +		goto out;

> +	memset(buf, 0, 4);

> +	ret = crypto_shash_update(shash, buf, 1);

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, "Controller", 10);

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, ctrl->subsysnqn,

> +			    strlen(ctrl->subsysnqn));

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, buf, 1);

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_update(shash, ctrl->hostnqn, strlen(ctrl->hostnqn));

> +	if (ret)

> +		goto out;

> +	ret = crypto_shash_final(shash, response);

> +out:

> +	if (challenge != req->sq->dhchap_c2)

> +		kfree(challenge);

> +	return 0;

> +}

> +

> +int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,

> +			    u8 *pkey, int pkey_size)

> +{

> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

> +	struct kpp_request *kpp_req;

> +	struct crypto_wait wait;

> +	struct scatterlist src, dst;

> +	int ret;

> +

> +	req->sq->dhchap_skey_len =

> +		nvme_auth_dhgroup_privkey_size(ctrl->dh_gid);

> +	req->sq->dhchap_skey = kzalloc(req->sq->dhchap_skey_len, GFP_KERNEL);

> +	if (!req->sq->dhchap_skey)

> +		return -ENOMEM;

> +	kpp_req = kpp_request_alloc(ctrl->dh_tfm, GFP_KERNEL);

> +	if (!kpp_req) {

> +		kfree(req->sq->dhchap_skey);

> +		req->sq->dhchap_skey = NULL;

> +		return -ENOMEM;

> +	}

> +

> +	pr_debug("%s: host public key %*ph\n", __func__,

> +		 (int)pkey_size, pkey);

> +	crypto_init_wait(&wait);

> +	sg_init_one(&src, pkey, pkey_size);

> +	kpp_request_set_input(kpp_req, &src, pkey_size);

> +	sg_init_one(&dst, req->sq->dhchap_skey,

> +		req->sq->dhchap_skey_len);

> +	kpp_request_set_output(kpp_req, &dst, req->sq->dhchap_skey_len);

> +	kpp_request_set_callback(kpp_req, CRYPTO_TFM_REQ_MAY_BACKLOG,

> +				 crypto_req_done, &wait);

> +

> +	ret = crypto_wait_req(crypto_kpp_compute_shared_secret(kpp_req), &wait);

> +	kpp_request_free(kpp_req);

> +	if (ret)

> +		pr_debug("failed to compute shared secred, err %d\n", ret);

> +	else

> +		pr_debug("%s: shared secret %*ph\n", __func__,

> +			 (int)req->sq->dhchap_skey_len,

> +			 req->sq->dhchap_skey);

> +

> +	return ret;

> +}

> diff --git a/drivers/nvme/target/configfs.c b/drivers/nvme/target/configfs.c

> index 273555127188..e0760911a761 100644

> --- a/drivers/nvme/target/configfs.c

> +++ b/drivers/nvme/target/configfs.c

> @@ -11,8 +11,13 @@

>   #include <linux/ctype.h>

>   #include <linux/pci.h>

>   #include <linux/pci-p2pdma.h>

> +#include <crypto/hash.h>

> +#include <crypto/kpp.h>

>   

>   #include "nvmet.h"

> +#ifdef CONFIG_NVME_TARGET_AUTH

> +#include "../host/auth.h"

> +#endif

>   

>   static const struct config_item_type nvmet_host_type;

>   static const struct config_item_type nvmet_subsys_type;

> @@ -1656,10 +1661,71 @@ static const struct config_item_type nvmet_ports_type = {

>   static struct config_group nvmet_subsystems_group;

>   static struct config_group nvmet_ports_group;

>   

> -static void nvmet_host_release(struct config_item *item)

> +#ifdef CONFIG_NVME_TARGET_AUTH

> +static ssize_t nvmet_host_dhchap_key_show(struct config_item *item,

> +		char *page)

> +{

> +	u8 *dhchap_secret = to_host(item)->dhchap_secret;

> +

> +	if (!dhchap_secret)

> +		return sprintf(page, "\n");

> +	return sprintf(page, "%s\n", dhchap_secret);

> +}

> +

> +static ssize_t nvmet_host_dhchap_key_store(struct config_item *item,

> +		const char *page, size_t count)

>   {

>   	struct nvmet_host *host = to_host(item);

> +	int ret;

>   

> +	ret = nvmet_auth_set_host_key(host, page);

> +	if (ret < 0)

> +		return ret;

> +	return count;

> +}

> +

> +CONFIGFS_ATTR(nvmet_host_, dhchap_key);

> +

> +static ssize_t nvmet_host_dhchap_hash_show(struct config_item *item,

> +		char *page)

> +{

> +	struct nvmet_host *host = to_host(item);

> +	const char *hash_name = nvme_auth_hmac_name(host->dhchap_hash_id);

> +

> +	return sprintf(page, "%s\n", hash_name ? hash_name : "none");

> +}

> +

> +static ssize_t nvmet_host_dhchap_hash_store(struct config_item *item,

> +		const char *page, size_t count)

> +{

> +	struct nvmet_host *host = to_host(item);

> +	int hmac_id;

> +

> +	hmac_id = nvme_auth_hmac_id(page);

> +	if (hmac_id < 0)

> +		return -EINVAL;

> +	if (!crypto_has_shash(nvme_auth_hmac_name(hmac_id), 0, 0))

> +		return -ENOTSUPP;

> +	host->dhchap_hash_id = hmac_id;

> +	return count;

> +}

> +

> +CONFIGFS_ATTR(nvmet_host_, dhchap_hash);

> +

> +static struct configfs_attribute *nvmet_host_attrs[] = {

> +	&nvmet_host_attr_dhchap_key,

> +	&nvmet_host_attr_dhchap_hash,

> +	NULL,

> +};

> +#endif /* CONFIG_NVME_TARGET_AUTH */

> +

> +static void nvmet_host_release(struct config_item *item)

> +{

> +	struct nvmet_host *host = to_host(item);

> +#ifdef CONFIG_NVME_TARGET_AUTH

> +	if (host->dhchap_secret)

> +		kfree(host->dhchap_secret);


No need for if condition.

> +#endif

>   	kfree(host);

>   }

>   

> @@ -1669,6 +1735,9 @@ static struct configfs_item_operations nvmet_host_item_ops = {

>   

>   static const struct config_item_type nvmet_host_type = {

>   	.ct_item_ops		= &nvmet_host_item_ops,

> +#ifdef CONFIG_NVME_TARGET_AUTH

> +	.ct_attrs		= nvmet_host_attrs,

> +#endif

>   	.ct_owner		= THIS_MODULE,

>   };

>   

> diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c

> index 163f7dc1a929..b5d7971f566b 100644

> --- a/drivers/nvme/target/core.c

> +++ b/drivers/nvme/target/core.c

> @@ -793,6 +793,7 @@ void nvmet_sq_destroy(struct nvmet_sq *sq)

>   	wait_for_completion(&sq->confirm_done);

>   	wait_for_completion(&sq->free_done);

>   	percpu_ref_exit(&sq->ref);

> +	nvmet_auth_sq_free(sq);

>   

>   	if (ctrl) {

>   		/*

> @@ -1264,6 +1265,11 @@ u16 nvmet_check_ctrl_status(struct nvmet_req *req)

>   		       req->cmd->common.opcode, req->sq->qid);

>   		return NVME_SC_CMD_SEQ_ERROR | NVME_SC_DNR;

>   	}

> +

> +	if (unlikely(!nvmet_check_auth_status(req))) {

> +		pr_warn("qid %d not authenticated\n", req->sq->qid);

> +		return NVME_SC_AUTH_REQUIRED | NVME_SC_DNR;

> +	}

>   	return 0;

>   }

>   

> @@ -1456,6 +1462,8 @@ static void nvmet_ctrl_free(struct kref *ref)

>   	flush_work(&ctrl->async_event_work);

>   	cancel_work_sync(&ctrl->fatal_err_work);

>   

> +	nvmet_reset_auth(ctrl);

> +

>   	ida_simple_remove(&cntlid_ida, ctrl->cntlid);

>   

>   	nvmet_async_events_free(ctrl);

> diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/target/fabrics-cmd-auth.c

> new file mode 100644

> index 000000000000..962f9f5e9d89

> --- /dev/null

> +++ b/drivers/nvme/target/fabrics-cmd-auth.c

> @@ -0,0 +1,460 @@

> +// SPDX-License-Identifier: GPL-2.0

> +/*

> + * NVMe over Fabrics DH-HMAC-CHAP authentication command handling.

> + * Copyright (c) 2020 Hannes Reinecke, SUSE Software Solutions.

> + * All rights reserved.

> + */

> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

> +#include <linux/blkdev.h>

> +#include <linux/random.h>

> +#include <crypto/hash.h>

> +#include <crypto/kpp.h>

> +#include "nvmet.h"

> +#include "../host/auth.h"

> +

> +void nvmet_init_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req)

> +{

> +	/* Initialize in-band authentication */

> +	req->sq->authenticated = false;

> +	req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE;

> +	req->cqe->result.u32 |= 0x2 << 16;


Can you add a define for this: NVME_CONNECT_AUTHREQ_INBAND

> +}

> +

> +static u16 nvmet_auth_negotiate(struct nvmet_req *req, void *d)

> +{

> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

> +	struct nvmf_auth_dhchap_negotiate_data *data = d;

> +	int i, hash_id, null_dh = -1;

> +

> +	pr_debug("%s: ctrl %d qid %d: data sc_d %d napd %d authid %d halen %d dhlen %d\n",

> +		 __func__, ctrl->cntlid, req->sq->qid,

> +		 data->sc_c, data->napd, data->auth_protocol[0].dhchap.authid,

> +		 data->auth_protocol[0].dhchap.halen,

> +		 data->auth_protocol[0].dhchap.dhlen);

> +	req->sq->dhchap_tid = le16_to_cpu(data->t_id);

> +	if (data->sc_c)

> +		return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;

> +

> +	if (data->napd != 1)

> +		return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;

> +

> +	if (data->auth_protocol[0].dhchap.authid != 0x01)

> +		return NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;

> +

> +	hash_id = nvme_auth_hmac_id(crypto_shash_alg_name(ctrl->shash_tfm));

> +	for (i = 0; i < data->auth_protocol[0].dhchap.halen; i++) {

> +		pr_debug("%s: ctrl %d qid %d checking hash %d for %d\n",

> +			 __func__, ctrl->cntlid, req->sq->qid,

> +			 data->auth_protocol[0].dhchap.idlist[i], hash_id);

> +		if (hash_id != data->auth_protocol[0].dhchap.idlist[i])

> +			continue;

> +		req->sq->dhchap_hash_id = hash_id;

> +		req->sq->dhchap_hash_len = crypto_shash_digestsize(ctrl->shash_tfm);

> +		break;

> +	}

> +	if (req->sq->dhchap_hash_id == 0) {

> +		pr_debug("%s: ctrl %d qid %d: no usable hash found\n",

> +			 __func__, ctrl->cntlid, req->sq->qid);

> +		return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;

> +	}

> +

> +	for (i = data->auth_protocol[0].dhchap.halen;

> +	     i < data->auth_protocol[0].dhchap.halen +

> +		     data->auth_protocol[0].dhchap.dhlen; i++) {

> +		int dhgid = data->auth_protocol[0].dhchap.idlist[i];

> +

> +		if (dhgid == NVME_AUTH_DHCHAP_DHGROUP_NULL) {

> +			null_dh = dhgid;

> +			continue;

> +		}

> +		if (nvmet_setup_dhgroup(ctrl, dhgid) == 0)

> +			break;

> +	}

> +	if (!ctrl->dh_tfm && null_dh < 0) {

> +		pr_debug("%s: ctrl %d qid %d: no DH group selected\n",

> +			 __func__, ctrl->cntlid, req->sq->qid);

> +		return NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE;

> +	}

> +	if (ctrl->dh_gid == -1) {

> +		ctrl->dh_gid = null_dh;

> +		ctrl->dh_tfm = NULL;

> +	}

> +	pr_debug("%s: ctrl %d qid %d: DH group %s (%d)\n",

> +		 __func__, ctrl->cntlid, req->sq->qid,

> +		 nvme_auth_dhgroup_name(ctrl->dh_gid), ctrl->dh_gid);

> +	return 0;

> +}

> +

> +static u16 nvmet_auth_reply(struct nvmet_req *req, void *d)

> +{

> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

> +	struct nvmf_auth_dhchap_reply_data *data = d;

> +	u8 *response;

> +

> +	pr_debug("%s: ctrl %d qid %d: data hl %d cvalid %d dhvlen %d\n",

> +		 __func__, ctrl->cntlid, req->sq->qid,

> +		 data->hl, data->cvalid, data->dhvlen);

> +	if (data->hl != req->sq->dhchap_hash_len)

> +		return NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;

> +

> +	if (data->dhvlen) {

> +		return NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;

> +	}

> +

> +	response = kmalloc(data->hl, GFP_KERNEL);

> +	if (!response)

> +		return NVME_AUTH_DHCHAP_FAILURE_FAILED;

> +

> +	if (nvmet_auth_host_hash(req, response, data->hl) < 0) {

> +		pr_debug("ctrl %d qid %d DH-HMAC-CHAP hash failed\n",

> +			 ctrl->cntlid, req->sq->qid);

> +		kfree(response);

> +		return NVME_AUTH_DHCHAP_FAILURE_FAILED;

> +	}

> +

> +	if (memcmp(data->rval, response, data->hl)) {

> +		pr_info("ctrl %d qid %d DH-HMAC-CHAP response mismatch\n",

> +			ctrl->cntlid, req->sq->qid);

> +		kfree(response);

> +		return NVME_AUTH_DHCHAP_FAILURE_FAILED;

> +	}

> +	kfree(response);

> +	pr_info("ctrl %d qid %d DH-HMAC-CHAP host authenticated\n",

> +		ctrl->cntlid, req->sq->qid);

> +	if (data->cvalid) {

> +		req->sq->dhchap_c2 = kmalloc(data->hl, GFP_KERNEL);

> +		if (!req->sq->dhchap_c2)

> +			return NVME_AUTH_DHCHAP_FAILURE_FAILED;

> +		memcpy(req->sq->dhchap_c2, data->rval + data->hl, data->hl);

> +

> +		pr_debug("ctrl %d qid %d challenge %*ph\n",

> +			 ctrl->cntlid, req->sq->qid, data->hl,

> +			 req->sq->dhchap_c2);

> +		req->sq->dhchap_s2 = le32_to_cpu(data->seqnum);

> +	} else

> +		req->sq->dhchap_c2 = NULL;

> +

> +	return 0;

> +}

> +

> +static u16 nvmet_auth_failure2(struct nvmet_req *req, void *d)

> +{

> +	struct nvmf_auth_dhchap_failure_data *data = d;

> +

> +	return data->reason_code_explanation;

> +}

> +

> +void nvmet_execute_auth_send(struct nvmet_req *req)

> +{

> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

> +	struct nvmf_auth_dhchap_success2_data *data;

> +	void *d;

> +	u32 tl;

> +	u16 status = 0;

> +

> +	if (req->cmd->auth_send.secp != NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER) {

> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

> +		req->error_loc =

> +			offsetof(struct nvmf_auth_send_command, secp);

> +		goto done;

> +	}

> +	if (req->cmd->auth_send.spsp0 != 0x01) {

> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

> +		req->error_loc =

> +			offsetof(struct nvmf_auth_send_command, spsp0);

> +		goto done;

> +	}

> +	if (req->cmd->auth_send.spsp1 != 0x01) {

> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

> +		req->error_loc =

> +			offsetof(struct nvmf_auth_send_command, spsp1);

> +		goto done;

> +	}

> +	tl = le32_to_cpu(req->cmd->auth_send.tl);

> +	if (!tl) {

> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

> +		req->error_loc =

> +			offsetof(struct nvmf_auth_send_command, tl);

> +		goto done;

> +	}

> +	if (!nvmet_check_transfer_len(req, tl)) {

> +		pr_debug("%s: transfer length mismatch (%u)\n", __func__, tl);

> +		return;

> +	}

> +

> +	d = kmalloc(tl, GFP_KERNEL);

> +	if (!d) {

> +		status = NVME_SC_INTERNAL;

> +		goto done;

> +	}

> +

> +	status = nvmet_copy_from_sgl(req, 0, d, tl);

> +	if (status) {

> +		kfree(d);

> +		goto done;

> +	}

> +


This whole block below should move to something like 
nvmet_process_auth_send_data()

> +	data = d;

> +	pr_debug("%s: ctrl %d qid %d type %d id %d step %x\n", __func__,

> +		 ctrl->cntlid, req->sq->qid, data->auth_type, data->auth_id,

> +		 req->sq->dhchap_step);

> +	if (data->auth_type != NVME_AUTH_COMMON_MESSAGES &&

> +	    data->auth_type != NVME_AUTH_DHCHAP_MESSAGES) {

> +		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

> +		req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

> +	} else if (data->auth_type == NVME_AUTH_COMMON_MESSAGES) {

> +		if (data->auth_id != req->sq->dhchap_step) {

> +			req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

> +			req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

> +		} else if (data->auth_id != NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE) {

> +			req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

> +			req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

> +		} else {

> +			/* Validate negotiation parameters */

> +			status = nvmet_auth_negotiate(req, d);

> +			if (status == 0)

> +				req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE;

> +			else {

> +				req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

> +				req->sq->dhchap_status = status;

> +				status = 0;

> +			}

> +		}

> +	} else if (data->auth_type == NVME_AUTH_DHCHAP_MESSAGES) {

> +		if (data->auth_id != req->sq->dhchap_step) {

> +			pr_debug("%s: ctrl %d qid %d step mismatch (%d != %d)\n",

> +				 __func__, ctrl->cntlid, req->sq->qid,

> +				 data->auth_id, req->sq->dhchap_step);

> +			req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

> +			req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

> +		} else if (le16_to_cpu(data->t_id) != req->sq->dhchap_tid) {

> +			pr_debug("%s: ctrl %d qid %d invalid transaction %d (expected %d)\n",

> +				 __func__, ctrl->cntlid, req->sq->qid,

> +				 le16_to_cpu(data->t_id),

> +				 req->sq->dhchap_tid);

> +			req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

> +			req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;

> +		} else {

> +			switch (data->auth_id) {

> +			case NVME_AUTH_DHCHAP_MESSAGE_REPLY:

> +				status = nvmet_auth_reply(req, d);

> +				if (status == 0)

> +					req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1;

> +				else {

> +					req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

> +					req->sq->dhchap_status = status;

> +					status = 0;

> +				}

> +				break;

> +			case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2:

> +				req->sq->authenticated = true;

> +				pr_debug("%s: ctrl %d qid %d authenticated\n",

> +					 __func__, ctrl->cntlid, req->sq->qid);

> +				break;

> +			case NVME_AUTH_DHCHAP_MESSAGE_FAILURE2:

> +				status = nvmet_auth_failure2(req, d);

> +				if (status) {

> +					pr_warn("ctrl %d qid %d: DH-HMAC-CHAP negotiation failed (%d)\n",

> +						ctrl->cntlid, req->sq->qid,

> +						status);

> +					req->sq->dhchap_status = status;

> +					status = 0;

> +				}

> +				break;

> +			default:

> +				req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

> +				req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE2;

> +				break;

> +			}

> +		}

> +	} else {

> +		req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

> +		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE2;

> +	}

> +	kfree(d);

> +done:

> +	pr_debug("%s: ctrl %d qid %d dhchap status %x step %x\n", __func__,

> +		 ctrl->cntlid, req->sq->qid,

> +		 req->sq->dhchap_status, req->sq->dhchap_step);

> +	if (status)

> +		pr_debug("%s: ctrl %d qid %d nvme status %x error loc %d\n",

> +			 __func__, ctrl->cntlid, req->sq->qid,

> +			 status, req->error_loc);

> +	req->cqe->result.u64 = 0;

> +	nvmet_req_complete(req, status);

> +	if (req->sq->dhchap_step != NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2 &&

> +	    req->sq->dhchap_step != NVME_AUTH_DHCHAP_MESSAGE_FAILURE2)

> +		return;

> +	/* Final states, clear up variables */

> +	kfree(req->sq->dhchap_c1);

> +	kfree(req->sq->dhchap_c2);

> +	if (req->sq->dhchap_step == NVME_AUTH_DHCHAP_MESSAGE_FAILURE2)

> +		nvmet_ctrl_fatal_error(ctrl);

> +}

> +

> +static int nvmet_auth_challenge(struct nvmet_req *req, void *d, int al)


nvmet_auth_set_challange

> +{

> +	struct nvmf_auth_dhchap_challenge_data *data = d;

> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

> +	int ret = 0;

> +	int data_size = sizeof(*d) + req->sq->dhchap_hash_len;

> +

> +	if (al < data_size) {

> +		pr_debug("%s: buffer too small (al %d need %d)\n", __func__,

> +			 al, data_size);

> +		return -EINVAL;

> +	}

> +	memset(data, 0, data_size);

> +	req->sq->dhchap_s1 = ctrl->dhchap_seqnum++;

> +	data->auth_type = NVME_AUTH_DHCHAP_MESSAGES;

> +	data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE;

> +	data->t_id = cpu_to_le16(req->sq->dhchap_tid);

> +	data->hashid = req->sq->dhchap_hash_id;

> +	data->hl = req->sq->dhchap_hash_len;

> +	data->seqnum = cpu_to_le32(req->sq->dhchap_s1);

> +	req->sq->dhchap_c1 = kmalloc(data->hl, GFP_KERNEL);

> +	if (!req->sq->dhchap_c1)

> +		return -ENOMEM;

> +	get_random_bytes(req->sq->dhchap_c1, data->hl);

> +	memcpy(data->cval, req->sq->dhchap_c1, data->hl);

> +	pr_debug("%s: ctrl %d qid %d seq %d transaction %d hl %d dhvlen %d\n",

> +		 __func__, ctrl->cntlid, req->sq->qid, req->sq->dhchap_s1,

> +		 req->sq->dhchap_tid, data->hl, data->dhvlen);

> +	return ret;

> +}

> +

> +static int nvmet_auth_success1(struct nvmet_req *req, void *d, int al)

> +{

> +	struct nvmf_auth_dhchap_success1_data *data = d;

> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

> +

> +	WARN_ON(al < sizeof(*data));

> +	memset(data, 0, sizeof(*data));

> +	data->auth_type = NVME_AUTH_DHCHAP_MESSAGES;

> +	data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1;

> +	data->t_id = cpu_to_le16(req->sq->dhchap_tid);

> +	data->hl = req->sq->dhchap_hash_len;

> +	if (req->sq->dhchap_c2) {

> +		if (nvmet_auth_ctrl_hash(req, data->rval, data->hl))

> +			return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;

> +		data->rvalid = 1;

> +		pr_debug("ctrl %d qid %d response %*ph\n",

> +			 ctrl->cntlid, req->sq->qid, data->hl, data->rval);

> +	}

> +	return 0;

> +}

> +

> +static void nvmet_auth_failure1(struct nvmet_req *req, void *d, int al)

> +{

> +	struct nvmf_auth_dhchap_failure_data *data = d;

> +

> +	WARN_ON(al < sizeof(*data));

> +	data->auth_type = NVME_AUTH_COMMON_MESSAGES;

> +	data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

> +	data->t_id = cpu_to_le32(req->sq->dhchap_tid);

> +	data->reason_code = NVME_AUTH_DHCHAP_FAILURE_REASON_FAILED;

> +	data->reason_code_explanation = req->sq->dhchap_status;

> +}

> +

> +void nvmet_execute_auth_receive(struct nvmet_req *req)

> +{

> +	struct nvmet_ctrl *ctrl = req->sq->ctrl;

> +	void *d;

> +	u32 al;

> +	u16 status = 0;

> +

> +	if (req->cmd->auth_receive.secp != NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER) {

> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

> +		req->error_loc =

> +			offsetof(struct nvmf_auth_receive_command, secp);

> +		goto done;

> +	}

> +	if (req->cmd->auth_receive.spsp0 != 0x01) {

> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

> +		req->error_loc =

> +			offsetof(struct nvmf_auth_receive_command, spsp0);

> +		goto done;

> +	}

> +	if (req->cmd->auth_receive.spsp1 != 0x01) {

> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

> +		req->error_loc =

> +			offsetof(struct nvmf_auth_receive_command, spsp1);

> +		goto done;

> +	}

> +	al = le32_to_cpu(req->cmd->auth_receive.al);

> +	if (!al) {

> +		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

> +		req->error_loc =

> +			offsetof(struct nvmf_auth_receive_command, al);

> +		goto done;

> +	}

> +	if (!nvmet_check_transfer_len(req, al)) {

> +		pr_debug("%s: transfer length mismatch (%u)\n", __func__, al);

> +		return;

> +	}

> +

> +	d = kmalloc(al, GFP_KERNEL);

> +	if (!d) {

> +		status = NVME_SC_INTERNAL;

> +		goto done;

> +	}

> +	pr_debug("%s: ctrl %d qid %d step %x\n", __func__,

> +		 ctrl->cntlid, req->sq->qid, req->sq->dhchap_step);

> +	switch (req->sq->dhchap_step) {

> +	case NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE:

> +		status = nvmet_auth_challenge(req, d, al);

> +		if (status < 0) {

> +			pr_warn("ctrl %d qid %d: challenge error (%d)\n",

> +				ctrl->cntlid, req->sq->qid, status);

> +			status = NVME_SC_INTERNAL;

> +			break;

> +		}

> +		if (status) {

> +			req->sq->dhchap_status = status;

> +			nvmet_auth_failure1(req, d, al);

> +			pr_warn("ctrl %d qid %d: challenge status (%x)\n",

> +				ctrl->cntlid, req->sq->qid,

> +				req->sq->dhchap_status);

> +			status = 0;

> +			break;

> +		}

> +		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_REPLY;

> +		break;

> +	case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1:

> +		status = nvmet_auth_success1(req, d, al);

> +		if (status) {

> +			req->sq->dhchap_status = status;

> +			nvmet_auth_failure1(req, d, al);

> +			pr_warn("ctrl %d qid %d: success1 status (%x)\n",

> +				ctrl->cntlid, req->sq->qid,

> +				req->sq->dhchap_status);

> +			break;

> +		}

> +		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2;

> +		break;

> +	case NVME_AUTH_DHCHAP_MESSAGE_FAILURE1:

> +		nvmet_auth_failure1(req, d, al);

> +		pr_warn("ctrl %d qid %d failure1 (%x)\n",

> +			ctrl->cntlid, req->sq->qid, req->sq->dhchap_status);

> +		break;

> +	default:

> +		pr_warn("ctrl %d qid %d unhandled step (%d)\n",

> +			ctrl->cntlid, req->sq->qid, req->sq->dhchap_step);

> +		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

> +		req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_FAILED;

> +		nvmet_auth_failure1(req, d, al);

> +		status = 0;

> +		break;

> +	}

> +

> +	status = nvmet_copy_to_sgl(req, 0, d, al);

> +	kfree(d);

> +done:

> +	req->cqe->result.u64 = 0;

> +	nvmet_req_complete(req, status);

> +	if (req->sq->dhchap_step == NVME_AUTH_DHCHAP_MESSAGE_FAILURE1) {

> +		kfree(req->sq->dhchap_c1);

> +		kfree(req->sq->dhchap_c2);

> +		nvmet_ctrl_fatal_error(ctrl);

> +	}

> +}

> diff --git a/drivers/nvme/target/fabrics-cmd.c b/drivers/nvme/target/fabrics-cmd.c

> index 7d0f3523fdab..53fb853cd8fe 100644

> --- a/drivers/nvme/target/fabrics-cmd.c

> +++ b/drivers/nvme/target/fabrics-cmd.c

> @@ -93,6 +93,14 @@ u16 nvmet_parse_fabrics_cmd(struct nvmet_req *req)

>   	case nvme_fabrics_type_property_get:

>   		req->execute = nvmet_execute_prop_get;

>   		break;

> +#ifdef CONFIG_NVME_TARGET_AUTH

> +	case nvme_fabrics_type_auth_send:

> +		req->execute = nvmet_execute_auth_send;

> +		break;

> +	case nvme_fabrics_type_auth_receive:

> +		req->execute = nvmet_execute_auth_receive;

> +		break;

> +#endif

>   	default:

>   		pr_debug("received unknown capsule type 0x%x\n",

>   			cmd->fabrics.fctype);

> @@ -155,6 +163,7 @@ static void nvmet_execute_admin_connect(struct nvmet_req *req)

>   	struct nvmf_connect_data *d;

>   	struct nvmet_ctrl *ctrl = NULL;

>   	u16 status = 0;

> +	int ret;

>   

>   	if (!nvmet_check_transfer_len(req, sizeof(struct nvmf_connect_data)))

>   		return;

> @@ -197,17 +206,31 @@ static void nvmet_execute_admin_connect(struct nvmet_req *req)

>   

>   	uuid_copy(&ctrl->hostid, &d->hostid);

>   

> +	ret = nvmet_setup_auth(ctrl, req);

> +	if (ret < 0) {

> +		pr_err("Failed to setup authentication, error %d\n", ret);

> +		nvmet_ctrl_put(ctrl);

> +		if (ret == -EPERM)

> +			status = (NVME_SC_CONNECT_INVALID_HOST | NVME_SC_DNR);

> +		else

> +			status = NVME_SC_INTERNAL;

> +		goto out;

> +	}

> +

>   	status = nvmet_install_queue(ctrl, req);

>   	if (status) {

>   		nvmet_ctrl_put(ctrl);

>   		goto out;

>   	}

>   

> -	pr_info("creating controller %d for subsystem %s for NQN %s%s.\n",

> +	pr_info("creating controller %d for subsystem %s for NQN %s%s%s.\n",

>   		ctrl->cntlid, ctrl->subsys->subsysnqn, ctrl->hostnqn,

> -		ctrl->pi_support ? " T10-PI is enabled" : "");

> +		ctrl->pi_support ? " T10-PI is enabled" : "",

> +		nvmet_has_auth(ctrl) ? " with DH-HMAC-CHAP" : "");

>   	req->cqe->result.u16 = cpu_to_le16(ctrl->cntlid);

>   

> +	if (nvmet_has_auth(ctrl))

> +		nvmet_init_auth(ctrl, req);

>   out:

>   	kfree(d);

>   complete:

> @@ -267,6 +290,9 @@ static void nvmet_execute_io_connect(struct nvmet_req *req)

>   	}

>   

>   	pr_debug("adding queue %d to ctrl %d.\n", qid, ctrl->cntlid);

> +	req->cqe->result.u16 = cpu_to_le16(ctrl->cntlid);


Is this related to the patch?

> +	if (nvmet_has_auth(ctrl))

> +		nvmet_init_auth(ctrl, req);

>   

>   out:

>   	kfree(d);

> diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h

> index 06dd3d537f07..ef8815e137d7 100644

> --- a/drivers/nvme/target/nvmet.h

> +++ b/drivers/nvme/target/nvmet.h

> @@ -108,6 +108,20 @@ struct nvmet_sq {

>   	u16			size;

>   	u32			sqhd;

>   	bool			sqhd_disabled;

> +#ifdef CONFIG_NVME_TARGET_AUTH

> +	bool			authenticated;

> +	u16			dhchap_tid;

> +	u16			dhchap_status;

> +	int			dhchap_step;

> +	u8			dhchap_hash_id;

> +	u8			dhchap_hash_len;

> +	u8			*dhchap_c1;

> +	u8			*dhchap_c2;

> +	u32			dhchap_s1;

> +	u32			dhchap_s2;

> +	u8			*dhchap_skey;

> +	int			dhchap_skey_len;

> +#endif

>   	struct completion	free_done;

>   	struct completion	confirm_done;

>   };

> @@ -209,6 +223,15 @@ struct nvmet_ctrl {

>   	u64			err_counter;

>   	struct nvme_error_slot	slots[NVMET_ERROR_LOG_SLOTS];

>   	bool			pi_support;

> +#ifdef CONFIG_NVME_TARGET_AUTH

> +	u32			dhchap_seqnum;

> +	u8			*dhchap_key;

> +	size_t			dhchap_key_len;

> +	struct crypto_shash	*shash_tfm;

> +	struct crypto_kpp	*dh_tfm;

> +	u32			dh_gid;

> +	u32			dh_keysize;

> +#endif

>   };

>   

>   struct nvmet_subsys {

> @@ -270,6 +293,10 @@ static inline struct nvmet_subsys *namespaces_to_subsys(

>   

>   struct nvmet_host {

>   	struct config_group	group;

> +	u8			*dhchap_secret;

> +	u8			dhchap_key_hash;

> +	u8			dhchap_hash_id;

> +	u8			dhchap_dhgroup_id;

>   };

>   

>   static inline struct nvmet_host *to_host(struct config_item *item)

> @@ -659,4 +686,48 @@ static inline void nvmet_req_bio_put(struct nvmet_req *req, struct bio *bio)

>   		bio_put(bio);

>   }

>   

> +#ifdef CONFIG_NVME_TARGET_AUTH

> +void nvmet_execute_auth_send(struct nvmet_req *req);

> +void nvmet_execute_auth_receive(struct nvmet_req *req);

> +int nvmet_auth_set_host_key(struct nvmet_host *host, const char *secret);

> +int nvmet_auth_set_host_hash(struct nvmet_host *host, const char *hash);

> +int nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req);

> +void nvmet_init_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req);

> +void nvmet_reset_auth(struct nvmet_ctrl *ctrl);

> +void nvmet_auth_sq_free(struct nvmet_sq *sq);

> +int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, int dhgroup_id);

> +bool nvmet_check_auth_status(struct nvmet_req *req);

> +int nvmet_auth_host_hash(struct nvmet_req *req, u8 *response,

> +			 unsigned int hash_len);

> +int nvmet_auth_ctrl_hash(struct nvmet_req *req, u8 *response,

> +			 unsigned int hash_len);

> +static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl)

> +{

> +	return ctrl->shash_tfm != NULL;

> +}

> +int nvmet_auth_ctrl_exponential(struct nvmet_req *req,

> +				u8 *buf, int buf_size);

> +int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,

> +			    u8 *buf, int buf_size);

> +#else

> +static inline int nvmet_setup_auth(struct nvmet_ctrl *ctrl,

> +				   struct nvmet_req *req)

> +{

> +	return 0;

> +}

> +static inline void nvmet_init_auth(struct nvmet_ctrl *ctrl,

> +				   struct nvmet_req *req) {};

> +static inline void nvmet_reset_auth(struct nvmet_ctrl *ctrl) {};

> +static inline void nvmet_auth_sq_free(struct nvmet_sq *sq) {};

> +static inline bool nvmet_check_auth_status(struct nvmet_req *req)

> +{

> +	return true;

> +}

> +static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl)

> +{

> +	return false;

> +}

> +static inline const char *nvmet_dhchap_dhgroup_name(int dhgid) { return NULL; }

> +#endif

> +

>   #endif /* _NVMET_H */

>
Hannes Reinecke July 20, 2021, 6:08 a.m. UTC | #13
On 7/19/21 10:38 PM, Sagi Grimberg wrote:
> 

> 

> On 7/16/21 4:04 AM, Hannes Reinecke wrote:

>> Implement support for NVMe-oF In-Band authentication. This patch

>> adds two additional configfs entries 'dhchap_key' and 'dhchap_hash'

>> to the 'host' configfs directory. The 'dhchap_key' needs to be

>> specified in the format outlined in the base spec.

>> Augmented challenge support is not implemented, and concatenation

>> with TLS encryption is not supported.

>>

>> Signed-off-by: Hannes Reinecke <hare@suse.de>

>> ---

>>   drivers/nvme/target/Kconfig            |  10 +

>>   drivers/nvme/target/Makefile           |   1 +

>>   drivers/nvme/target/admin-cmd.c        |   4 +

>>   drivers/nvme/target/auth.c             | 352 +++++++++++++++++++

>>   drivers/nvme/target/configfs.c         |  71 +++-

>>   drivers/nvme/target/core.c             |   8 +

>>   drivers/nvme/target/fabrics-cmd-auth.c | 460 +++++++++++++++++++++++++

>>   drivers/nvme/target/fabrics-cmd.c      |  30 +-

>>   drivers/nvme/target/nvmet.h            |  71 ++++

>>   9 files changed, 1004 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 4be2ececbc45..d5656ef1559e 100644

>> --- a/drivers/nvme/target/Kconfig

>> +++ b/drivers/nvme/target/Kconfig

>> @@ -85,3 +85,13 @@ 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_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 0cb98f2bbc8c..320cefc64ee0 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..00c7d051dfb1

>> --- /dev/null

>> +++ b/drivers/nvme/target/auth.c

>> @@ -0,0 +1,352 @@

>> +// 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 <crypto/kpp.h>

>> +#include <crypto/dh.h>

>> +#include <crypto/ffdhe.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_warn("DH-HMAC-CHAP hash %s unsupported\n", hmac);

> 

> pr_err

> 

>> +            host->dhchap_key_hash = -1;

>> +            return -EAGAIN;

> 

> Why EAGAIN?

> 


What else? ENOTSUPP?

>> +        }

>> +        /* Use this hash as default */

>> +        if (!host->dhchap_hash_id)

>> +            host->dhchap_hash_id = host->dhchap_key_hash;

> 

> Why?

> 


Because there is no mechanism how the controller selects the DHCHAP hmac
algorithm.
The host will send a list of supported hmac algorithms, and the
controller has to pick one of them.

And as we are sure that the hmac algorithm from the PSK will be
supported on the controller I set that as default (if nothing was
specified otherwise).

>> +    }

>> +    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_HASH_SHA256;

> 

> What is the thought here?

> 


This case is triggered when the user specifies a PSK without a hmac
algorithm (ie DHHC-1:00:XXXXX).
Then the above mechanism doesn't work, but we still have to specify a
default HMAC algorithm such that the selection mechanism can work.

>> +

>> +    pr_debug("Using hash %s\n",

>> +         nvme_auth_hmac_name(host->dhchap_hash_id));

>> +    return 0;

>> +}

>> +

>> +int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, int dhgroup_id)

>> +{

>> +    int ret = -ENOTSUPP;

>> +

>> +    if (dhgroup_id == NVME_AUTH_DHCHAP_DHGROUP_NULL)

>> +        return 0;

>> +

>> +    return ret;

>> +}

>> +

>> +int nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req)

>> +{

>> +    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;

>> +

>> +    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;

> 

> I think you should propogate the nvme status code instead...

> 


I _thought_ it got translated into one; but yeah, can do.

>> +        goto out_unlock;

>> +    }

>> +    if (!host->dhchap_secret) {

>> +        pr_debug("No authentication provided\n");

>> +        goto out_unlock;

>> +    }

>> +

>> +    hash_name = nvme_auth_hmac_name(host->dhchap_hash_id);

>> +    if (!hash_name) {

> 

> Can this actually happen?

> 


Good question. I don't think so; will be changing it into a WARN_ON().

>> +        pr_debug("Hash ID %d invalid\n", host->dhchap_hash_id);

> 

> warning, not debug.

> 


See above. Yes.

>> +        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_debug("failed to allocate shash %s\n", hash_name);

>> +        ret = PTR_ERR(ctrl->shash_tfm);

>> +        ctrl->shash_tfm = NULL;

>> +        goto out_unlock;

>> +    }

>> +

>> +    ctrl->dhchap_key = nvme_auth_extract_secret(host->dhchap_secret,

>> +                            &ctrl->dhchap_key_len);

>> +    if (IS_ERR(ctrl->dhchap_key)) {

>> +        pr_debug("failed to extract host key, error %d\n", ret);

>> +        ret = PTR_ERR(ctrl->dhchap_key);

>> +        ctrl->dhchap_key = NULL;

>> +        goto out_free_hash;

>> +    }

>> +    if (host->dhchap_key_hash) {

>> +        struct crypto_shash *key_tfm;

>> +

>> +        hash_name = nvme_auth_hmac_name(host->dhchap_key_hash);

>> +        key_tfm = crypto_alloc_shash(hash_name, 0, 0);

>> +        if (IS_ERR(key_tfm)) {

>> +            ret = PTR_ERR(key_tfm);

>> +            goto out_free_hash;

>> +        } else {

>> +            SHASH_DESC_ON_STACK(shash, key_tfm);

>> +

>> +            shash->tfm = key_tfm;

>> +            ret = crypto_shash_setkey(key_tfm, ctrl->dhchap_key,

>> +                          ctrl->dhchap_key_len);

>> +            crypto_shash_init(shash);

>> +            crypto_shash_update(shash, ctrl->subsys->subsysnqn,

>> +                        strlen(ctrl->subsys->subsysnqn));

>> +            crypto_shash_update(shash, "NVMe-over-Fabrics", 17);

>> +            crypto_shash_final(shash, ctrl->dhchap_key);

>> +            crypto_free_shash(key_tfm);

>> +        }

>> +    }

>> +    pr_debug("%s: using key %*ph\n", __func__,

>> +         (int)ctrl->dhchap_key_len, ctrl->dhchap_key);

>> +    ret = crypto_shash_setkey(ctrl->shash_tfm, ctrl->dhchap_key,

>> +                  ctrl->dhchap_key_len);

>> +out_free_hash:

>> +    if (ret) {

>> +        if (ctrl->dhchap_key) {

>> +            kfree(ctrl->dhchap_key);

>> +            ctrl->dhchap_key = NULL;

>> +        }

>> +        crypto_free_shash(ctrl->shash_tfm);

>> +        ctrl->shash_tfm = NULL;

>> +    }

>> +out_unlock:

>> +    up_read(&nvmet_config_sem);

>> +

>> +    return ret;

>> +}

>> +

>> +void nvmet_auth_sq_free(struct nvmet_sq *sq)

>> +{

>> +    if (sq->dhchap_c1)

>> +        kfree(sq->dhchap_c1);

> 

> just kfree, no need to if

> 


Yeah.

>> +    if (sq->dhchap_c2)

>> +        kfree(sq->dhchap_c2);

>> +    if (sq->dhchap_skey)

>> +        kfree(sq->dhchap_skey);

>> +}

>> +

>> +void nvmet_reset_auth(struct nvmet_ctrl *ctrl)

> 

> Shouldn't this be nvmet_destroy_auth? reset indicates

> it can be reused again...

> 


Oh, it should. I've coded nvmet_destroy_auth() pretty late in the game,
so I missed that one.

>> +{

>> +    if (ctrl->shash_tfm) {

>> +        crypto_free_shash(ctrl->shash_tfm);

>> +        ctrl->shash_tfm = NULL;

>> +    }

>> +    if (ctrl->dh_tfm) {

>> +        crypto_free_kpp(ctrl->dh_tfm);

>> +        ctrl->dh_tfm = NULL;

>> +    }

>> +    if (ctrl->dhchap_key) {

>> +        kfree(ctrl->dhchap_key);

>> +        ctrl->dhchap_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;

>> +    u8 buf[4];

>> +    int ret;

>> +

>> +    if (ctrl->dh_gid != NVME_AUTH_DHCHAP_DHGROUP_NULL) {

>> +        ret = -ENOTSUPP;

>> +        goto out;

>> +    }

>> +

>> +    shash->tfm = ctrl->shash_tfm;

>> +    ret = crypto_shash_init(shash);

>> +    if (ret)

>> +        goto out;

>> +    ret = crypto_shash_update(shash, challenge, shash_len);

>> +    if (ret)

>> +        goto out;

>> +    put_unaligned_le32(req->sq->dhchap_s1, buf);

>> +    ret = crypto_shash_update(shash, buf, 4);

>> +    if (ret)

>> +        goto out;

>> +    put_unaligned_le16(req->sq->dhchap_tid, buf);

>> +    ret = crypto_shash_update(shash, buf, 2);

>> +    if (ret)

>> +        goto out;

>> +    memset(buf, 0, 4);

>> +    ret = crypto_shash_update(shash, buf, 1);

>> +    if (ret)

>> +        goto out;

>> +    ret = crypto_shash_update(shash, "HostHost", 8);

>> +    if (ret)

>> +        goto out;

>> +    ret = crypto_shash_update(shash, ctrl->hostnqn,

>> strlen(ctrl->hostnqn));

>> +    if (ret)

>> +        goto out;

>> +    ret = crypto_shash_update(shash, buf, 1);

>> +    if (ret)

>> +        goto out;

>> +    ret = crypto_shash_update(shash, ctrl->subsysnqn,

>> +                  strlen(ctrl->subsysnqn));

>> +    if (ret)

>> +        goto out;

>> +    ret = crypto_shash_final(shash, response);

>> +out:

>> +    if (challenge != req->sq->dhchap_c1)

>> +        kfree(challenge);

> 

> What about actually failing?

> 


Ho-hum. Of course.

>> +    return 0;

>> +}

>> +

>> +int nvmet_auth_ctrl_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_c2;

>> +    u8 buf[4];

>> +    int ret;

>> +

>> +    pr_debug("%s: ctrl %d hash seq %d transaction %u\n", __func__,

>> +         ctrl->cntlid, req->sq->dhchap_s2, req->sq->dhchap_tid);

>> +    pr_debug("%s: ctrl %d challenge %*ph\n", __func__,

>> +         ctrl->cntlid, shash_len, req->sq->dhchap_c2);

>> +    pr_debug("%s: ctrl %d subsysnqn %s\n", __func__,

>> +         ctrl->cntlid, ctrl->subsysnqn);

>> +    pr_debug("%s: ctrl %d hostnqn %s\n", __func__,

>> +         ctrl->cntlid, ctrl->hostnqn);

>> +

>> +    if (ctrl->dh_gid != NVME_AUTH_DHCHAP_DHGROUP_NULL) {

>> +        ret = -ENOTSUPP;

>> +        goto out;

>> +    }

>> +

>> +    shash->tfm = ctrl->shash_tfm;

>> +    ret = crypto_shash_init(shash);

>> +    if (ret)

>> +        goto out;

>> +    ret = crypto_shash_update(shash, challenge, shash_len);

>> +    if (ret)

>> +        goto out;

>> +    put_unaligned_le32(req->sq->dhchap_s2, buf);

>> +    ret = crypto_shash_update(shash, buf, 4);

>> +    if (ret)

>> +        goto out;

>> +    put_unaligned_le16(req->sq->dhchap_tid, buf);

>> +    ret = crypto_shash_update(shash, buf, 2);

>> +    if (ret)

>> +        goto out;

>> +    memset(buf, 0, 4);

>> +    ret = crypto_shash_update(shash, buf, 1);

>> +    if (ret)

>> +        goto out;

>> +    ret = crypto_shash_update(shash, "Controller", 10);

>> +    if (ret)

>> +        goto out;

>> +    ret = crypto_shash_update(shash, ctrl->subsysnqn,

>> +                strlen(ctrl->subsysnqn));

>> +    if (ret)

>> +        goto out;

>> +    ret = crypto_shash_update(shash, buf, 1);

>> +    if (ret)

>> +        goto out;

>> +    ret = crypto_shash_update(shash, ctrl->hostnqn,

>> strlen(ctrl->hostnqn));

>> +    if (ret)

>> +        goto out;

>> +    ret = crypto_shash_final(shash, response);

>> +out:

>> +    if (challenge != req->sq->dhchap_c2)

>> +        kfree(challenge);

>> +    return 0;

>> +}

>> +

>> +int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,

>> +                u8 *pkey, int pkey_size)

>> +{

>> +    struct nvmet_ctrl *ctrl = req->sq->ctrl;

>> +    struct kpp_request *kpp_req;

>> +    struct crypto_wait wait;

>> +    struct scatterlist src, dst;

>> +    int ret;

>> +

>> +    req->sq->dhchap_skey_len =

>> +        nvme_auth_dhgroup_privkey_size(ctrl->dh_gid);

>> +    req->sq->dhchap_skey = kzalloc(req->sq->dhchap_skey_len,

>> GFP_KERNEL);

>> +    if (!req->sq->dhchap_skey)

>> +        return -ENOMEM;

>> +    kpp_req = kpp_request_alloc(ctrl->dh_tfm, GFP_KERNEL);

>> +    if (!kpp_req) {

>> +        kfree(req->sq->dhchap_skey);

>> +        req->sq->dhchap_skey = NULL;

>> +        return -ENOMEM;

>> +    }

>> +

>> +    pr_debug("%s: host public key %*ph\n", __func__,

>> +         (int)pkey_size, pkey);

>> +    crypto_init_wait(&wait);

>> +    sg_init_one(&src, pkey, pkey_size);

>> +    kpp_request_set_input(kpp_req, &src, pkey_size);

>> +    sg_init_one(&dst, req->sq->dhchap_skey,

>> +        req->sq->dhchap_skey_len);

>> +    kpp_request_set_output(kpp_req, &dst, req->sq->dhchap_skey_len);

>> +    kpp_request_set_callback(kpp_req, CRYPTO_TFM_REQ_MAY_BACKLOG,

>> +                 crypto_req_done, &wait);

>> +

>> +    ret = crypto_wait_req(crypto_kpp_compute_shared_secret(kpp_req),

>> &wait);

>> +    kpp_request_free(kpp_req);

>> +    if (ret)

>> +        pr_debug("failed to compute shared secred, err %d\n", ret);

>> +    else

>> +        pr_debug("%s: shared secret %*ph\n", __func__,

>> +             (int)req->sq->dhchap_skey_len,

>> +             req->sq->dhchap_skey);

>> +

>> +    return ret;

>> +}

>> diff --git a/drivers/nvme/target/configfs.c

>> b/drivers/nvme/target/configfs.c

>> index 273555127188..e0760911a761 100644

>> --- a/drivers/nvme/target/configfs.c

>> +++ b/drivers/nvme/target/configfs.c

>> @@ -11,8 +11,13 @@

>>   #include <linux/ctype.h>

>>   #include <linux/pci.h>

>>   #include <linux/pci-p2pdma.h>

>> +#include <crypto/hash.h>

>> +#include <crypto/kpp.h>

>>     #include "nvmet.h"

>> +#ifdef CONFIG_NVME_TARGET_AUTH

>> +#include "../host/auth.h"

>> +#endif

>>     static const struct config_item_type nvmet_host_type;

>>   static const struct config_item_type nvmet_subsys_type;

>> @@ -1656,10 +1661,71 @@ static const struct config_item_type

>> nvmet_ports_type = {

>>   static struct config_group nvmet_subsystems_group;

>>   static struct config_group nvmet_ports_group;

>>   -static void nvmet_host_release(struct config_item *item)

>> +#ifdef CONFIG_NVME_TARGET_AUTH

>> +static ssize_t nvmet_host_dhchap_key_show(struct config_item *item,

>> +        char *page)

>> +{

>> +    u8 *dhchap_secret = to_host(item)->dhchap_secret;

>> +

>> +    if (!dhchap_secret)

>> +        return sprintf(page, "\n");

>> +    return sprintf(page, "%s\n", dhchap_secret);

>> +}

>> +

>> +static ssize_t nvmet_host_dhchap_key_store(struct config_item *item,

>> +        const char *page, size_t count)

>>   {

>>       struct nvmet_host *host = to_host(item);

>> +    int ret;

>>   +    ret = nvmet_auth_set_host_key(host, page);

>> +    if (ret < 0)

>> +        return ret;

>> +    return count;

>> +}

>> +

>> +CONFIGFS_ATTR(nvmet_host_, dhchap_key);

>> +

>> +static ssize_t nvmet_host_dhchap_hash_show(struct config_item *item,

>> +        char *page)

>> +{

>> +    struct nvmet_host *host = to_host(item);

>> +    const char *hash_name = nvme_auth_hmac_name(host->dhchap_hash_id);

>> +

>> +    return sprintf(page, "%s\n", hash_name ? hash_name : "none");

>> +}

>> +

>> +static ssize_t nvmet_host_dhchap_hash_store(struct config_item *item,

>> +        const char *page, size_t count)

>> +{

>> +    struct nvmet_host *host = to_host(item);

>> +    int hmac_id;

>> +

>> +    hmac_id = nvme_auth_hmac_id(page);

>> +    if (hmac_id < 0)

>> +        return -EINVAL;

>> +    if (!crypto_has_shash(nvme_auth_hmac_name(hmac_id), 0, 0))

>> +        return -ENOTSUPP;

>> +    host->dhchap_hash_id = hmac_id;

>> +    return count;

>> +}

>> +

>> +CONFIGFS_ATTR(nvmet_host_, dhchap_hash);

>> +

>> +static struct configfs_attribute *nvmet_host_attrs[] = {

>> +    &nvmet_host_attr_dhchap_key,

>> +    &nvmet_host_attr_dhchap_hash,

>> +    NULL,

>> +};

>> +#endif /* CONFIG_NVME_TARGET_AUTH */

>> +

>> +static void nvmet_host_release(struct config_item *item)

>> +{

>> +    struct nvmet_host *host = to_host(item);

>> +#ifdef CONFIG_NVME_TARGET_AUTH

>> +    if (host->dhchap_secret)

>> +        kfree(host->dhchap_secret);

> 

> No need for if condition.

> 


Ok.

>> +#endif

>>       kfree(host);

>>   }

>>   @@ -1669,6 +1735,9 @@ static struct configfs_item_operations

>> nvmet_host_item_ops = {

>>     static const struct config_item_type nvmet_host_type = {

>>       .ct_item_ops        = &nvmet_host_item_ops,

>> +#ifdef CONFIG_NVME_TARGET_AUTH

>> +    .ct_attrs        = nvmet_host_attrs,

>> +#endif

>>       .ct_owner        = THIS_MODULE,

>>   };

>>   diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c

>> index 163f7dc1a929..b5d7971f566b 100644

>> --- a/drivers/nvme/target/core.c

>> +++ b/drivers/nvme/target/core.c

>> @@ -793,6 +793,7 @@ void nvmet_sq_destroy(struct nvmet_sq *sq)

>>       wait_for_completion(&sq->confirm_done);

>>       wait_for_completion(&sq->free_done);

>>       percpu_ref_exit(&sq->ref);

>> +    nvmet_auth_sq_free(sq);

>>         if (ctrl) {

>>           /*

>> @@ -1264,6 +1265,11 @@ u16 nvmet_check_ctrl_status(struct nvmet_req *req)

>>                  req->cmd->common.opcode, req->sq->qid);

>>           return NVME_SC_CMD_SEQ_ERROR | NVME_SC_DNR;

>>       }

>> +

>> +    if (unlikely(!nvmet_check_auth_status(req))) {

>> +        pr_warn("qid %d not authenticated\n", req->sq->qid);

>> +        return NVME_SC_AUTH_REQUIRED | NVME_SC_DNR;

>> +    }

>>       return 0;

>>   }

>>   @@ -1456,6 +1462,8 @@ static void nvmet_ctrl_free(struct kref *ref)

>>       flush_work(&ctrl->async_event_work);

>>       cancel_work_sync(&ctrl->fatal_err_work);

>>   +    nvmet_reset_auth(ctrl);

>> +

>>       ida_simple_remove(&cntlid_ida, ctrl->cntlid);

>>         nvmet_async_events_free(ctrl);

>> diff --git a/drivers/nvme/target/fabrics-cmd-auth.c

>> b/drivers/nvme/target/fabrics-cmd-auth.c

>> new file mode 100644

>> index 000000000000..962f9f5e9d89

>> --- /dev/null

>> +++ b/drivers/nvme/target/fabrics-cmd-auth.c

>> @@ -0,0 +1,460 @@

>> +// SPDX-License-Identifier: GPL-2.0

>> +/*

>> + * NVMe over Fabrics DH-HMAC-CHAP authentication command handling.

>> + * Copyright (c) 2020 Hannes Reinecke, SUSE Software Solutions.

>> + * All rights reserved.

>> + */

>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

>> +#include <linux/blkdev.h>

>> +#include <linux/random.h>

>> +#include <crypto/hash.h>

>> +#include <crypto/kpp.h>

>> +#include "nvmet.h"

>> +#include "../host/auth.h"

>> +

>> +void nvmet_init_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req)

>> +{

>> +    /* Initialize in-band authentication */

>> +    req->sq->authenticated = false;

>> +    req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE;

>> +    req->cqe->result.u32 |= 0x2 << 16;

> 

> Can you add a define for this: NVME_CONNECT_AUTHREQ_INBAND

> 


Sure.

>> +}

>> +

>> +static u16 nvmet_auth_negotiate(struct nvmet_req *req, void *d)

>> +{

>> +    struct nvmet_ctrl *ctrl = req->sq->ctrl;

>> +    struct nvmf_auth_dhchap_negotiate_data *data = d;

>> +    int i, hash_id, null_dh = -1;

>> +

>> +    pr_debug("%s: ctrl %d qid %d: data sc_d %d napd %d authid %d

>> halen %d dhlen %d\n",

>> +         __func__, ctrl->cntlid, req->sq->qid,

>> +         data->sc_c, data->napd, data->auth_protocol[0].dhchap.authid,

>> +         data->auth_protocol[0].dhchap.halen,

>> +         data->auth_protocol[0].dhchap.dhlen);

>> +    req->sq->dhchap_tid = le16_to_cpu(data->t_id);

>> +    if (data->sc_c)

>> +        return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;

>> +

>> +    if (data->napd != 1)

>> +        return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;

>> +

>> +    if (data->auth_protocol[0].dhchap.authid != 0x01)

>> +        return NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;

>> +

>> +    hash_id = nvme_auth_hmac_id(crypto_shash_alg_name(ctrl->shash_tfm));

>> +    for (i = 0; i < data->auth_protocol[0].dhchap.halen; i++) {

>> +        pr_debug("%s: ctrl %d qid %d checking hash %d for %d\n",

>> +             __func__, ctrl->cntlid, req->sq->qid,

>> +             data->auth_protocol[0].dhchap.idlist[i], hash_id);

>> +        if (hash_id != data->auth_protocol[0].dhchap.idlist[i])

>> +            continue;

>> +        req->sq->dhchap_hash_id = hash_id;

>> +        req->sq->dhchap_hash_len =

>> crypto_shash_digestsize(ctrl->shash_tfm);

>> +        break;

>> +    }

>> +    if (req->sq->dhchap_hash_id == 0) {

>> +        pr_debug("%s: ctrl %d qid %d: no usable hash found\n",

>> +             __func__, ctrl->cntlid, req->sq->qid);

>> +        return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;

>> +    }

>> +

>> +    for (i = data->auth_protocol[0].dhchap.halen;

>> +         i < data->auth_protocol[0].dhchap.halen +

>> +             data->auth_protocol[0].dhchap.dhlen; i++) {

>> +        int dhgid = data->auth_protocol[0].dhchap.idlist[i];

>> +

>> +        if (dhgid == NVME_AUTH_DHCHAP_DHGROUP_NULL) {

>> +            null_dh = dhgid;

>> +            continue;

>> +        }

>> +        if (nvmet_setup_dhgroup(ctrl, dhgid) == 0)

>> +            break;

>> +    }

>> +    if (!ctrl->dh_tfm && null_dh < 0) {

>> +        pr_debug("%s: ctrl %d qid %d: no DH group selected\n",

>> +             __func__, ctrl->cntlid, req->sq->qid);

>> +        return NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE;

>> +    }

>> +    if (ctrl->dh_gid == -1) {

>> +        ctrl->dh_gid = null_dh;

>> +        ctrl->dh_tfm = NULL;

>> +    }

>> +    pr_debug("%s: ctrl %d qid %d: DH group %s (%d)\n",

>> +         __func__, ctrl->cntlid, req->sq->qid,

>> +         nvme_auth_dhgroup_name(ctrl->dh_gid), ctrl->dh_gid);

>> +    return 0;

>> +}

>> +

>> +static u16 nvmet_auth_reply(struct nvmet_req *req, void *d)

>> +{

>> +    struct nvmet_ctrl *ctrl = req->sq->ctrl;

>> +    struct nvmf_auth_dhchap_reply_data *data = d;

>> +    u8 *response;

>> +

>> +    pr_debug("%s: ctrl %d qid %d: data hl %d cvalid %d dhvlen %d\n",

>> +         __func__, ctrl->cntlid, req->sq->qid,

>> +         data->hl, data->cvalid, data->dhvlen);

>> +    if (data->hl != req->sq->dhchap_hash_len)

>> +        return NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;

>> +

>> +    if (data->dhvlen) {

>> +        return NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;

>> +    }

>> +

>> +    response = kmalloc(data->hl, GFP_KERNEL);

>> +    if (!response)

>> +        return NVME_AUTH_DHCHAP_FAILURE_FAILED;

>> +

>> +    if (nvmet_auth_host_hash(req, response, data->hl) < 0) {

>> +        pr_debug("ctrl %d qid %d DH-HMAC-CHAP hash failed\n",

>> +             ctrl->cntlid, req->sq->qid);

>> +        kfree(response);

>> +        return NVME_AUTH_DHCHAP_FAILURE_FAILED;

>> +    }

>> +

>> +    if (memcmp(data->rval, response, data->hl)) {

>> +        pr_info("ctrl %d qid %d DH-HMAC-CHAP response mismatch\n",

>> +            ctrl->cntlid, req->sq->qid);

>> +        kfree(response);

>> +        return NVME_AUTH_DHCHAP_FAILURE_FAILED;

>> +    }

>> +    kfree(response);

>> +    pr_info("ctrl %d qid %d DH-HMAC-CHAP host authenticated\n",

>> +        ctrl->cntlid, req->sq->qid);

>> +    if (data->cvalid) {

>> +        req->sq->dhchap_c2 = kmalloc(data->hl, GFP_KERNEL);

>> +        if (!req->sq->dhchap_c2)

>> +            return NVME_AUTH_DHCHAP_FAILURE_FAILED;

>> +        memcpy(req->sq->dhchap_c2, data->rval + data->hl, data->hl);

>> +

>> +        pr_debug("ctrl %d qid %d challenge %*ph\n",

>> +             ctrl->cntlid, req->sq->qid, data->hl,

>> +             req->sq->dhchap_c2);

>> +        req->sq->dhchap_s2 = le32_to_cpu(data->seqnum);

>> +    } else

>> +        req->sq->dhchap_c2 = NULL;

>> +

>> +    return 0;

>> +}

>> +

>> +static u16 nvmet_auth_failure2(struct nvmet_req *req, void *d)

>> +{

>> +    struct nvmf_auth_dhchap_failure_data *data = d;

>> +

>> +    return data->reason_code_explanation;

>> +}

>> +

>> +void nvmet_execute_auth_send(struct nvmet_req *req)

>> +{

>> +    struct nvmet_ctrl *ctrl = req->sq->ctrl;

>> +    struct nvmf_auth_dhchap_success2_data *data;

>> +    void *d;

>> +    u32 tl;

>> +    u16 status = 0;

>> +

>> +    if (req->cmd->auth_send.secp !=

>> NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER) {

>> +        status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

>> +        req->error_loc =

>> +            offsetof(struct nvmf_auth_send_command, secp);

>> +        goto done;

>> +    }

>> +    if (req->cmd->auth_send.spsp0 != 0x01) {

>> +        status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

>> +        req->error_loc =

>> +            offsetof(struct nvmf_auth_send_command, spsp0);

>> +        goto done;

>> +    }

>> +    if (req->cmd->auth_send.spsp1 != 0x01) {

>> +        status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

>> +        req->error_loc =

>> +            offsetof(struct nvmf_auth_send_command, spsp1);

>> +        goto done;

>> +    }

>> +    tl = le32_to_cpu(req->cmd->auth_send.tl);

>> +    if (!tl) {

>> +        status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

>> +        req->error_loc =

>> +            offsetof(struct nvmf_auth_send_command, tl);

>> +        goto done;

>> +    }

>> +    if (!nvmet_check_transfer_len(req, tl)) {

>> +        pr_debug("%s: transfer length mismatch (%u)\n", __func__, tl);

>> +        return;

>> +    }

>> +

>> +    d = kmalloc(tl, GFP_KERNEL);

>> +    if (!d) {

>> +        status = NVME_SC_INTERNAL;

>> +        goto done;

>> +    }

>> +

>> +    status = nvmet_copy_from_sgl(req, 0, d, tl);

>> +    if (status) {

>> +        kfree(d);

>> +        goto done;

>> +    }

>> +

> 

> This whole block below should move to something like

> nvmet_process_auth_send_data()

> 


Ok.

>> +    data = d;

>> +    pr_debug("%s: ctrl %d qid %d type %d id %d step %x\n", __func__,

>> +         ctrl->cntlid, req->sq->qid, data->auth_type, data->auth_id,

>> +         req->sq->dhchap_step);

>> +    if (data->auth_type != NVME_AUTH_COMMON_MESSAGES &&

>> +        data->auth_type != NVME_AUTH_DHCHAP_MESSAGES) {

>> +        req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +        req->sq->dhchap_status =

>> NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

>> +    } else if (data->auth_type == NVME_AUTH_COMMON_MESSAGES) {

>> +        if (data->auth_id != req->sq->dhchap_step) {

>> +            req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +            req->sq->dhchap_status =

>> NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

>> +        } else if (data->auth_id !=

>> NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE) {

>> +            req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +            req->sq->dhchap_status =

>> NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

>> +        } else {

>> +            /* Validate negotiation parameters */

>> +            status = nvmet_auth_negotiate(req, d);

>> +            if (status == 0)

>> +                req->sq->dhchap_step =

>> NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE;

>> +            else {

>> +                req->sq->dhchap_step =

>> NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +                req->sq->dhchap_status = status;

>> +                status = 0;

>> +            }

>> +        }

>> +    } else if (data->auth_type == NVME_AUTH_DHCHAP_MESSAGES) {

>> +        if (data->auth_id != req->sq->dhchap_step) {

>> +            pr_debug("%s: ctrl %d qid %d step mismatch (%d != %d)\n",

>> +                 __func__, ctrl->cntlid, req->sq->qid,

>> +                 data->auth_id, req->sq->dhchap_step);

>> +            req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +            req->sq->dhchap_status =

>> NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

>> +        } else if (le16_to_cpu(data->t_id) != req->sq->dhchap_tid) {

>> +            pr_debug("%s: ctrl %d qid %d invalid transaction %d

>> (expected %d)\n",

>> +                 __func__, ctrl->cntlid, req->sq->qid,

>> +                 le16_to_cpu(data->t_id),

>> +                 req->sq->dhchap_tid);

>> +            req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +            req->sq->dhchap_status =

>> NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;

>> +        } else {

>> +            switch (data->auth_id) {

>> +            case NVME_AUTH_DHCHAP_MESSAGE_REPLY:

>> +                status = nvmet_auth_reply(req, d);

>> +                if (status == 0)

>> +                    req->sq->dhchap_step =

>> NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1;

>> +                else {

>> +                    req->sq->dhchap_step =

>> NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +                    req->sq->dhchap_status = status;

>> +                    status = 0;

>> +                }

>> +                break;

>> +            case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2:

>> +                req->sq->authenticated = true;

>> +                pr_debug("%s: ctrl %d qid %d authenticated\n",

>> +                     __func__, ctrl->cntlid, req->sq->qid);

>> +                break;

>> +            case NVME_AUTH_DHCHAP_MESSAGE_FAILURE2:

>> +                status = nvmet_auth_failure2(req, d);

>> +                if (status) {

>> +                    pr_warn("ctrl %d qid %d: DH-HMAC-CHAP negotiation

>> failed (%d)\n",

>> +                        ctrl->cntlid, req->sq->qid,

>> +                        status);

>> +                    req->sq->dhchap_status = status;

>> +                    status = 0;

>> +                }

>> +                break;

>> +            default:

>> +                req->sq->dhchap_status =

>> NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

>> +                req->sq->dhchap_step =

>> NVME_AUTH_DHCHAP_MESSAGE_FAILURE2;

>> +                break;

>> +            }

>> +        }

>> +    } else {

>> +        req->sq->dhchap_status =

>> NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;

>> +        req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE2;

>> +    }

>> +    kfree(d);

>> +done:

>> +    pr_debug("%s: ctrl %d qid %d dhchap status %x step %x\n", __func__,

>> +         ctrl->cntlid, req->sq->qid,

>> +         req->sq->dhchap_status, req->sq->dhchap_step);

>> +    if (status)

>> +        pr_debug("%s: ctrl %d qid %d nvme status %x error loc %d\n",

>> +             __func__, ctrl->cntlid, req->sq->qid,

>> +             status, req->error_loc);

>> +    req->cqe->result.u64 = 0;

>> +    nvmet_req_complete(req, status);

>> +    if (req->sq->dhchap_step != NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2 &&

>> +        req->sq->dhchap_step != NVME_AUTH_DHCHAP_MESSAGE_FAILURE2)

>> +        return;

>> +    /* Final states, clear up variables */

>> +    kfree(req->sq->dhchap_c1);

>> +    kfree(req->sq->dhchap_c2);

>> +    if (req->sq->dhchap_step == NVME_AUTH_DHCHAP_MESSAGE_FAILURE2)

>> +        nvmet_ctrl_fatal_error(ctrl);

>> +}

>> +

>> +static int nvmet_auth_challenge(struct nvmet_req *req, void *d, int al)

> 

> nvmet_auth_set_challenge

> 


Ok.

>> +{

>> +    struct nvmf_auth_dhchap_challenge_data *data = d;

>> +    struct nvmet_ctrl *ctrl = req->sq->ctrl;

>> +    int ret = 0;

>> +    int data_size = sizeof(*d) + req->sq->dhchap_hash_len;

>> +

>> +    if (al < data_size) {

>> +        pr_debug("%s: buffer too small (al %d need %d)\n", __func__,

>> +             al, data_size);

>> +        return -EINVAL;

>> +    }

>> +    memset(data, 0, data_size);

>> +    req->sq->dhchap_s1 = ctrl->dhchap_seqnum++;

>> +    data->auth_type = NVME_AUTH_DHCHAP_MESSAGES;

>> +    data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE;

>> +    data->t_id = cpu_to_le16(req->sq->dhchap_tid);

>> +    data->hashid = req->sq->dhchap_hash_id;

>> +    data->hl = req->sq->dhchap_hash_len;

>> +    data->seqnum = cpu_to_le32(req->sq->dhchap_s1);

>> +    req->sq->dhchap_c1 = kmalloc(data->hl, GFP_KERNEL);

>> +    if (!req->sq->dhchap_c1)

>> +        return -ENOMEM;

>> +    get_random_bytes(req->sq->dhchap_c1, data->hl);

>> +    memcpy(data->cval, req->sq->dhchap_c1, data->hl);

>> +    pr_debug("%s: ctrl %d qid %d seq %d transaction %d hl %d dhvlen

>> %d\n",

>> +         __func__, ctrl->cntlid, req->sq->qid, req->sq->dhchap_s1,

>> +         req->sq->dhchap_tid, data->hl, data->dhvlen);

>> +    return ret;

>> +}

>> +

>> +static int nvmet_auth_success1(struct nvmet_req *req, void *d, int al)

>> +{

>> +    struct nvmf_auth_dhchap_success1_data *data = d;

>> +    struct nvmet_ctrl *ctrl = req->sq->ctrl;

>> +

>> +    WARN_ON(al < sizeof(*data));

>> +    memset(data, 0, sizeof(*data));

>> +    data->auth_type = NVME_AUTH_DHCHAP_MESSAGES;

>> +    data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1;

>> +    data->t_id = cpu_to_le16(req->sq->dhchap_tid);

>> +    data->hl = req->sq->dhchap_hash_len;

>> +    if (req->sq->dhchap_c2) {

>> +        if (nvmet_auth_ctrl_hash(req, data->rval, data->hl))

>> +            return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;

>> +        data->rvalid = 1;

>> +        pr_debug("ctrl %d qid %d response %*ph\n",

>> +             ctrl->cntlid, req->sq->qid, data->hl, data->rval);

>> +    }

>> +    return 0;

>> +}

>> +

>> +static void nvmet_auth_failure1(struct nvmet_req *req, void *d, int al)

>> +{

>> +    struct nvmf_auth_dhchap_failure_data *data = d;

>> +

>> +    WARN_ON(al < sizeof(*data));

>> +    data->auth_type = NVME_AUTH_COMMON_MESSAGES;

>> +    data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +    data->t_id = cpu_to_le32(req->sq->dhchap_tid);

>> +    data->reason_code = NVME_AUTH_DHCHAP_FAILURE_REASON_FAILED;

>> +    data->reason_code_explanation = req->sq->dhchap_status;

>> +}

>> +

>> +void nvmet_execute_auth_receive(struct nvmet_req *req)

>> +{

>> +    struct nvmet_ctrl *ctrl = req->sq->ctrl;

>> +    void *d;

>> +    u32 al;

>> +    u16 status = 0;

>> +

>> +    if (req->cmd->auth_receive.secp !=

>> NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER) {

>> +        status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

>> +        req->error_loc =

>> +            offsetof(struct nvmf_auth_receive_command, secp);

>> +        goto done;

>> +    }

>> +    if (req->cmd->auth_receive.spsp0 != 0x01) {

>> +        status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

>> +        req->error_loc =

>> +            offsetof(struct nvmf_auth_receive_command, spsp0);

>> +        goto done;

>> +    }

>> +    if (req->cmd->auth_receive.spsp1 != 0x01) {

>> +        status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

>> +        req->error_loc =

>> +            offsetof(struct nvmf_auth_receive_command, spsp1);

>> +        goto done;

>> +    }

>> +    al = le32_to_cpu(req->cmd->auth_receive.al);

>> +    if (!al) {

>> +        status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;

>> +        req->error_loc =

>> +            offsetof(struct nvmf_auth_receive_command, al);

>> +        goto done;

>> +    }

>> +    if (!nvmet_check_transfer_len(req, al)) {

>> +        pr_debug("%s: transfer length mismatch (%u)\n", __func__, al);

>> +        return;

>> +    }

>> +

>> +    d = kmalloc(al, GFP_KERNEL);

>> +    if (!d) {

>> +        status = NVME_SC_INTERNAL;

>> +        goto done;

>> +    }

>> +    pr_debug("%s: ctrl %d qid %d step %x\n", __func__,

>> +         ctrl->cntlid, req->sq->qid, req->sq->dhchap_step);

>> +    switch (req->sq->dhchap_step) {

>> +    case NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE:

>> +        status = nvmet_auth_challenge(req, d, al);

>> +        if (status < 0) {

>> +            pr_warn("ctrl %d qid %d: challenge error (%d)\n",

>> +                ctrl->cntlid, req->sq->qid, status);

>> +            status = NVME_SC_INTERNAL;

>> +            break;

>> +        }

>> +        if (status) {

>> +            req->sq->dhchap_status = status;

>> +            nvmet_auth_failure1(req, d, al);

>> +            pr_warn("ctrl %d qid %d: challenge status (%x)\n",

>> +                ctrl->cntlid, req->sq->qid,

>> +                req->sq->dhchap_status);

>> +            status = 0;

>> +            break;

>> +        }

>> +        req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_REPLY;

>> +        break;

>> +    case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1:

>> +        status = nvmet_auth_success1(req, d, al);

>> +        if (status) {

>> +            req->sq->dhchap_status = status;

>> +            nvmet_auth_failure1(req, d, al);

>> +            pr_warn("ctrl %d qid %d: success1 status (%x)\n",

>> +                ctrl->cntlid, req->sq->qid,

>> +                req->sq->dhchap_status);

>> +            break;

>> +        }

>> +        req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2;

>> +        break;

>> +    case NVME_AUTH_DHCHAP_MESSAGE_FAILURE1:

>> +        nvmet_auth_failure1(req, d, al);

>> +        pr_warn("ctrl %d qid %d failure1 (%x)\n",

>> +            ctrl->cntlid, req->sq->qid, req->sq->dhchap_status);

>> +        break;

>> +    default:

>> +        pr_warn("ctrl %d qid %d unhandled step (%d)\n",

>> +            ctrl->cntlid, req->sq->qid, req->sq->dhchap_step);

>> +        req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;

>> +        req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_FAILED;

>> +        nvmet_auth_failure1(req, d, al);

>> +        status = 0;

>> +        break;

>> +    }

>> +

>> +    status = nvmet_copy_to_sgl(req, 0, d, al);

>> +    kfree(d);

>> +done:

>> +    req->cqe->result.u64 = 0;

>> +    nvmet_req_complete(req, status);

>> +    if (req->sq->dhchap_step == NVME_AUTH_DHCHAP_MESSAGE_FAILURE1) {

>> +        kfree(req->sq->dhchap_c1);

>> +        kfree(req->sq->dhchap_c2);

>> +        nvmet_ctrl_fatal_error(ctrl);

>> +    }

>> +}

>> diff --git a/drivers/nvme/target/fabrics-cmd.c

>> b/drivers/nvme/target/fabrics-cmd.c

>> index 7d0f3523fdab..53fb853cd8fe 100644

>> --- a/drivers/nvme/target/fabrics-cmd.c

>> +++ b/drivers/nvme/target/fabrics-cmd.c

>> @@ -93,6 +93,14 @@ u16 nvmet_parse_fabrics_cmd(struct nvmet_req *req)

>>       case nvme_fabrics_type_property_get:

>>           req->execute = nvmet_execute_prop_get;

>>           break;

>> +#ifdef CONFIG_NVME_TARGET_AUTH

>> +    case nvme_fabrics_type_auth_send:

>> +        req->execute = nvmet_execute_auth_send;

>> +        break;

>> +    case nvme_fabrics_type_auth_receive:

>> +        req->execute = nvmet_execute_auth_receive;

>> +        break;

>> +#endif

>>       default:

>>           pr_debug("received unknown capsule type 0x%x\n",

>>               cmd->fabrics.fctype);

>> @@ -155,6 +163,7 @@ static void nvmet_execute_admin_connect(struct

>> nvmet_req *req)

>>       struct nvmf_connect_data *d;

>>       struct nvmet_ctrl *ctrl = NULL;

>>       u16 status = 0;

>> +    int ret;

>>         if (!nvmet_check_transfer_len(req, sizeof(struct

>> nvmf_connect_data)))

>>           return;

>> @@ -197,17 +206,31 @@ static void nvmet_execute_admin_connect(struct

>> nvmet_req *req)

>>         uuid_copy(&ctrl->hostid, &d->hostid);

>>   +    ret = nvmet_setup_auth(ctrl, req);

>> +    if (ret < 0) {

>> +        pr_err("Failed to setup authentication, error %d\n", ret);

>> +        nvmet_ctrl_put(ctrl);

>> +        if (ret == -EPERM)

>> +            status = (NVME_SC_CONNECT_INVALID_HOST | NVME_SC_DNR);

>> +        else

>> +            status = NVME_SC_INTERNAL;

>> +        goto out;

>> +    }

>> +

>>       status = nvmet_install_queue(ctrl, req);

>>       if (status) {

>>           nvmet_ctrl_put(ctrl);

>>           goto out;

>>       }

>>   -    pr_info("creating controller %d for subsystem %s for NQN %s%s.\n",

>> +    pr_info("creating controller %d for subsystem %s for NQN %s%s%s.\n",

>>           ctrl->cntlid, ctrl->subsys->subsysnqn, ctrl->hostnqn,

>> -        ctrl->pi_support ? " T10-PI is enabled" : "");

>> +        ctrl->pi_support ? " T10-PI is enabled" : "",

>> +        nvmet_has_auth(ctrl) ? " with DH-HMAC-CHAP" : "");

>>       req->cqe->result.u16 = cpu_to_le16(ctrl->cntlid);

>>   +    if (nvmet_has_auth(ctrl))

>> +        nvmet_init_auth(ctrl, req);

>>   out:

>>       kfree(d);

>>   complete:

>> @@ -267,6 +290,9 @@ static void nvmet_execute_io_connect(struct

>> nvmet_req *req)

>>       }

>>         pr_debug("adding queue %d to ctrl %d.\n", qid, ctrl->cntlid);

>> +    req->cqe->result.u16 = cpu_to_le16(ctrl->cntlid);

> 

> Is this related to the patch?

> 


Have to check. I thought it did, but now that you mention it...

>> +    if (nvmet_has_auth(ctrl))

>> +        nvmet_init_auth(ctrl, req);

>>     out:

>>       kfree(d);

>> diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h

>> index 06dd3d537f07..ef8815e137d7 100644

>> --- a/drivers/nvme/target/nvmet.h

>> +++ b/drivers/nvme/target/nvmet.h

>> @@ -108,6 +108,20 @@ struct nvmet_sq {

>>       u16            size;

>>       u32            sqhd;

>>       bool            sqhd_disabled;

>> +#ifdef CONFIG_NVME_TARGET_AUTH

>> +    bool            authenticated;

>> +    u16            dhchap_tid;

>> +    u16            dhchap_status;

>> +    int            dhchap_step;

>> +    u8            dhchap_hash_id;

>> +    u8            dhchap_hash_len;

>> +    u8            *dhchap_c1;

>> +    u8            *dhchap_c2;

>> +    u32            dhchap_s1;

>> +    u32            dhchap_s2;

>> +    u8            *dhchap_skey;

>> +    int            dhchap_skey_len;

>> +#endif

>>       struct completion    free_done;

>>       struct completion    confirm_done;

>>   };

>> @@ -209,6 +223,15 @@ struct nvmet_ctrl {

>>       u64            err_counter;

>>       struct nvme_error_slot    slots[NVMET_ERROR_LOG_SLOTS];

>>       bool            pi_support;

>> +#ifdef CONFIG_NVME_TARGET_AUTH

>> +    u32            dhchap_seqnum;

>> +    u8            *dhchap_key;

>> +    size_t            dhchap_key_len;

>> +    struct crypto_shash    *shash_tfm;

>> +    struct crypto_kpp    *dh_tfm;

>> +    u32            dh_gid;

>> +    u32            dh_keysize;

>> +#endif

>>   };

>>     struct nvmet_subsys {

>> @@ -270,6 +293,10 @@ static inline struct nvmet_subsys

>> *namespaces_to_subsys(

>>     struct nvmet_host {

>>       struct config_group    group;

>> +    u8            *dhchap_secret;

>> +    u8            dhchap_key_hash;

>> +    u8            dhchap_hash_id;

>> +    u8            dhchap_dhgroup_id;

>>   };

>>     static inline struct nvmet_host *to_host(struct config_item *item)

>> @@ -659,4 +686,48 @@ static inline void nvmet_req_bio_put(struct

>> nvmet_req *req, struct bio *bio)

>>           bio_put(bio);

>>   }

>>   +#ifdef CONFIG_NVME_TARGET_AUTH

>> +void nvmet_execute_auth_send(struct nvmet_req *req);

>> +void nvmet_execute_auth_receive(struct nvmet_req *req);

>> +int nvmet_auth_set_host_key(struct nvmet_host *host, const char

>> *secret);

>> +int nvmet_auth_set_host_hash(struct nvmet_host *host, const char *hash);

>> +int nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req);

>> +void nvmet_init_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req);

>> +void nvmet_reset_auth(struct nvmet_ctrl *ctrl);

>> +void nvmet_auth_sq_free(struct nvmet_sq *sq);

>> +int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, int dhgroup_id);

>> +bool nvmet_check_auth_status(struct nvmet_req *req);

>> +int nvmet_auth_host_hash(struct nvmet_req *req, u8 *response,

>> +             unsigned int hash_len);

>> +int nvmet_auth_ctrl_hash(struct nvmet_req *req, u8 *response,

>> +             unsigned int hash_len);

>> +static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl)

>> +{

>> +    return ctrl->shash_tfm != NULL;

>> +}

>> +int nvmet_auth_ctrl_exponential(struct nvmet_req *req,

>> +                u8 *buf, int buf_size);

>> +int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,

>> +                u8 *buf, int buf_size);

>> +#else

>> +static inline int nvmet_setup_auth(struct nvmet_ctrl *ctrl,

>> +                   struct nvmet_req *req)

>> +{

>> +    return 0;

>> +}

>> +static inline void nvmet_init_auth(struct nvmet_ctrl *ctrl,

>> +                   struct nvmet_req *req) {};

>> +static inline void nvmet_reset_auth(struct nvmet_ctrl *ctrl) {};

>> +static inline void nvmet_auth_sq_free(struct nvmet_sq *sq) {};

>> +static inline bool nvmet_check_auth_status(struct nvmet_req *req)

>> +{

>> +    return true;

>> +}

>> +static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl)

>> +{

>> +    return false;

>> +}

>> +static inline const char *nvmet_dhchap_dhgroup_name(int dhgid) {

>> return NULL; }

>> +#endif

>> +

>>   #endif /* _NVMET_H */

>>

> 


Thanks for the review!

Cheers,

Hannes
-- 
Dr. Hannes Reinecke		           Kernel Storage Architect
hare@suse.de			                  +49 911 74053 688
SUSE Software Solutions Germany GmbH, Maxfeldstr. 5, 90409 Nürnberg
HRB 36809 (AG Nürnberg), GF: Felix Imendörffer
Hannes Reinecke July 20, 2021, 10:14 a.m. UTC | #14
On 7/19/21 1:52 PM, Stephan Mueller wrote:
> Am Montag, dem 19.07.2021 um 13:10 +0200 schrieb Hannes Reinecke:

>> On 7/19/21 12:19 PM, Stephan Mueller wrote:

>>> Am Montag, dem 19.07.2021 um 11:57 +0200 schrieb Hannes Reinecke:

>>>> On 7/19/21 10:51 AM, Stephan Mueller wrote:

[ .. ]
>>>>>

>>>>> Thank you for clarifying that. It sounds to me that there is no

>>>>> defined protocol (or if there, I would be wondering how the code would have

>>>>> worked

>>>>> with a different implementation). Would it make sense to first specify

>>>>> a protocol for authentication and have it discussed? I personally think

>>>>> it is a bit difficult to fully understand the protocol from the code and

>>>>> discuss protocol-level items based on the code.

>>>>>

>>>> Oh, the protocol _is_ specified:

>>>>

>>>>  

>>>> https://nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-2_0-2021.06.02-Ratified-5.pdf

>>>>

>>>> It's just that I have issues translating that spec onto what the kernel

>>>> provides.

>>>

>>> according to the naming conventions there in figures 447 and following:

>>>

>>> - x and y: DH private key (kernel calls it secret set with dh_set_secret

>>> or

>>> encoded into param.key)

>>>

>>

>> But that's were I got confused; one needs a private key here, but there

>> is no obvious candidate for it. But reading it more closely I guess the

>> private key is just a random number (cf the spec: g^y mod p, where y is

>> a random number selected by the host that shall be at least 256 bits

>> long). So I'll fix it up with the next round.

> 

> Here comes the crux: the kernel has an ECC private key generation function

> ecdh_set_secret triggered with crypto_kpp_set_secret using a NULL key, but it

> has no FFC-DH counterpart.

> 

> That said, generating a random number is the most obvious choice, but not the

> right one.

> 

> The correct one would be following SP800-56A rev 3 and here either section

> 5.6.1.1.3 or 5.6.1.1.4.

> 

Hmm. Okay. But after having read section 5.6.1.1.4, I still do have some
questions.

Assume we will be using a bit length of 512 for FFDHE, then we will
trivially pass Step 2 for all supported FFDHE groups (the maximum
symmetric-equivalent strength for ffdhe8192 is 192 bits).

From my understanding, the random number generator will fill out all
available bytes in the string (and nothing more), so we trivially
satisfy step 3 and 4.

And as q is always larger than the random number, step 6 reduces to
'if (c > 2^N - 2)', ie we just need to check if the random number is a
string of 0xff characters. Which hardly is a random number at all, so
it'll be impossible to get this.

Which then would mean that our 'x' is simply the random number + 1,
which arguably is slightly pointless (one more than a random number is
as random as the number itself), so I do feel justified with just
returning a random number here.

Am I wrong with that reasoning?

Cheers,

Hannes
-- 
Dr. Hannes Reinecke		           Kernel Storage Architect
hare@suse.de			                  +49 911 74053 688
SUSE Software Solutions Germany GmbH, Maxfeldstr. 5, 90409 Nürnberg
HRB 36809 (AG Nürnberg), GF: Felix Imendörffer
Simo Sorce July 20, 2021, 10:49 a.m. UTC | #15
On Tue, 2021-07-20 at 12:14 +0200, Hannes Reinecke wrote:
> On 7/19/21 1:52 PM, Stephan Mueller wrote:

> > Am Montag, dem 19.07.2021 um 13:10 +0200 schrieb Hannes Reinecke:

> > > On 7/19/21 12:19 PM, Stephan Mueller wrote:

> > > > Am Montag, dem 19.07.2021 um 11:57 +0200 schrieb Hannes Reinecke:

> > > > > On 7/19/21 10:51 AM, Stephan Mueller wrote:

> [ .. ]

> > > > > > 

> > > > > > Thank you for clarifying that. It sounds to me that there is no

> > > > > > defined protocol (or if there, I would be wondering how the code would have

> > > > > > worked

> > > > > > with a different implementation). Would it make sense to first specify

> > > > > > a protocol for authentication and have it discussed? I personally think

> > > > > > it is a bit difficult to fully understand the protocol from the code and

> > > > > > discuss protocol-level items based on the code.

> > > > > > 

> > > > > Oh, the protocol _is_ specified:

> > > > > 

> > > > >  

> > > > > https://nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-2_0-2021.06.02-Ratified-5.pdf

> > > > > 

> > > > > It's just that I have issues translating that spec onto what the kernel

> > > > > provides.

> > > > 

> > > > according to the naming conventions there in figures 447 and following:

> > > > 

> > > > - x and y: DH private key (kernel calls it secret set with dh_set_secret

> > > > or

> > > > encoded into param.key)

> > > > 

> > > 

> > > But that's were I got confused; one needs a private key here, but there

> > > is no obvious candidate for it. But reading it more closely I guess the

> > > private key is just a random number (cf the spec: g^y mod p, where y is

> > > a random number selected by the host that shall be at least 256 bits

> > > long). So I'll fix it up with the next round.

> > 

> > Here comes the crux: the kernel has an ECC private key generation function

> > ecdh_set_secret triggered with crypto_kpp_set_secret using a NULL key, but it

> > has no FFC-DH counterpart.

> > 

> > That said, generating a random number is the most obvious choice, but not the

> > right one.

> > 

> > The correct one would be following SP800-56A rev 3 and here either section

> > 5.6.1.1.3 or 5.6.1.1.4.

> > 

> Hmm. Okay. But after having read section 5.6.1.1.4, I still do have some

> questions.

> 

> Assume we will be using a bit length of 512 for FFDHE, then we will

> trivially pass Step 2 for all supported FFDHE groups (the maximum

> symmetric-equivalent strength for ffdhe8192 is 192 bits).


N = 512 is not a good choice, minimum length these days for DH should
be 2048 or more.

> From my understanding, the random number generator will fill out all

> available bytes in the string (and nothing more), so we trivially

> satisfy step 3 and 4.

> 

> And as q is always larger than the random number, step 6 reduces to

> 'if (c > 2^N - 2)',


Where is this coming from ?
It seem you assume M = 2^N but M = min(2^N, q)

The point here is to make sure the number X you return is:
0 < X < (q-1)

>  ie we just need to check if the random number is a

> string of 0xff characters. Which hardly is a random number at all, so

> it'll be impossible to get this.

> 

> Which then would mean that our 'x' is simply the random number + 1,


This is an artifact due to the random number being 0 <= c < 2^N - 1,
therefore 1 needs to be added to make sure you never return 0.

> which arguably is slightly pointless (one more than a random number is

> as random as the number itself), so I do feel justified with just

> returning a random number here.

> 

> Am I wrong with that reasoning?


Looks to me you are not accounting for the fact that N = 512 is too
small and a random number falling in the interval (q - 2) < X < 2^N is
unsuitable?

Simo.

-- 
Simo Sorce
RHEL Crypto Team
Red Hat, Inc
Hannes Reinecke July 20, 2021, 11:31 a.m. UTC | #16
On 7/20/21 12:49 PM, Simo Sorce wrote:
> On Tue, 2021-07-20 at 12:14 +0200, Hannes Reinecke wrote:

>> On 7/19/21 1:52 PM, Stephan Mueller wrote:

>>> Am Montag, dem 19.07.2021 um 13:10 +0200 schrieb Hannes Reinecke:

>>>> On 7/19/21 12:19 PM, Stephan Mueller wrote:

>>>>> Am Montag, dem 19.07.2021 um 11:57 +0200 schrieb Hannes Reinecke:

>>>>>> On 7/19/21 10:51 AM, Stephan Mueller wrote:

>> [ .. ]

>>>>>>>

>>>>>>> Thank you for clarifying that. It sounds to me that there is no

>>>>>>> defined protocol (or if there, I would be wondering how the code would have

>>>>>>> worked

>>>>>>> with a different implementation). Would it make sense to first specify

>>>>>>> a protocol for authentication and have it discussed? I personally think

>>>>>>> it is a bit difficult to fully understand the protocol from the code and

>>>>>>> discuss protocol-level items based on the code.

>>>>>>>

>>>>>> Oh, the protocol _is_ specified:

>>>>>>

>>>>>>  

>>>>>> https://nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-2_0-2021.06.02-Ratified-5.pdf

>>>>>>

>>>>>> It's just that I have issues translating that spec onto what the kernel

>>>>>> provides.

>>>>>

>>>>> according to the naming conventions there in figures 447 and following:

>>>>>

>>>>> - x and y: DH private key (kernel calls it secret set with dh_set_secret

>>>>> or

>>>>> encoded into param.key)

>>>>>

>>>>

>>>> But that's were I got confused; one needs a private key here, but there

>>>> is no obvious candidate for it. But reading it more closely I guess the

>>>> private key is just a random number (cf the spec: g^y mod p, where y is

>>>> a random number selected by the host that shall be at least 256 bits

>>>> long). So I'll fix it up with the next round.

>>>

>>> Here comes the crux: the kernel has an ECC private key generation function

>>> ecdh_set_secret triggered with crypto_kpp_set_secret using a NULL key, but it

>>> has no FFC-DH counterpart.

>>>

>>> That said, generating a random number is the most obvious choice, but not the

>>> right one.

>>>

>>> The correct one would be following SP800-56A rev 3 and here either section

>>> 5.6.1.1.3 or 5.6.1.1.4.

>>>

>> Hmm. Okay. But after having read section 5.6.1.1.4, I still do have some

>> questions.

>>

>> Assume we will be using a bit length of 512 for FFDHE, then we will

>> trivially pass Step 2 for all supported FFDHE groups (the maximum

>> symmetric-equivalent strength for ffdhe8192 is 192 bits).

> 

> N = 512 is not a good choice, minimum length these days for DH should

> be 2048 or more.

> 


According to RFC7919:
Peers using ffdhe8192 that want to optimize their key exchange with a
short exponent (Section 5.2) should choose a secret key of at least
400 bits.

So what is wrong with 512 bits?

>> From my understanding, the random number generator will fill out all

>> available bytes in the string (and nothing more), so we trivially

>> satisfy step 3 and 4.

>>

>> And as q is always larger than the random number, step 6 reduces to

>> 'if (c > 2^N - 2)',

> 

> Where is this coming from ?

> It seem you assume M = 2^N but M = min(2^N, q)

> 

> The point here is to make sure the number X you return is:

> 0 < X < (q-1)

> 


Which is what I've tried to argue. For 512 bits private key and the
smallest possible FFDHE group (which has 2048 bits, with the top bit
non-zero) 2^N is always smaller than (q - 1).
As the other FFHDE groups are using even larger 'q' values, this is true
for all FFHDE groups.

>>  ie we just need to check if the random number is a

>> string of 0xff characters. Which hardly is a random number at all, so

>> it'll be impossible to get this.

>>

>> Which then would mean that our 'x' is simply the random number + 1,

> 

> This is an artifact due to the random number being 0 <= c < 2^N - 1,

> therefore 1 needs to be added to make sure you never return 0.

> 


And my argument here is that all zeros (and all ones) are not a value I
would expect from our RNG.

>> which arguably is slightly pointless (one more than a random number is

>> as random as the number itself), so I do feel justified with just

>> returning a random number here.

>>

>> Am I wrong with that reasoning?

> 

> Looks to me you are not accounting for the fact that N = 512 is too

> small and a random number falling in the interval (q - 2) < X < 2^N is

> unsuitable?

> 


Only if (q - 2) < 2^N. And my point is that it's not.

Cheers,

Hannes
-- 
Dr. Hannes Reinecke		           Kernel Storage Architect
hare@suse.de			                  +49 911 74053 688
SUSE Software Solutions Germany GmbH, Maxfeldstr. 5, 90409 Nürnberg
HRB 36809 (AG Nürnberg), GF: Felix Imendörffer
Simo Sorce July 20, 2021, 2:44 p.m. UTC | #17
On Tue, 2021-07-20 at 13:31 +0200, Hannes Reinecke wrote:
> On 7/20/21 12:49 PM, Simo Sorce wrote:

> > On Tue, 2021-07-20 at 12:14 +0200, Hannes Reinecke wrote:

> > > On 7/19/21 1:52 PM, Stephan Mueller wrote:

> > > > Am Montag, dem 19.07.2021 um 13:10 +0200 schrieb Hannes Reinecke:

> > > > > On 7/19/21 12:19 PM, Stephan Mueller wrote:

> > > > > > Am Montag, dem 19.07.2021 um 11:57 +0200 schrieb Hannes Reinecke:

> > > > > > > On 7/19/21 10:51 AM, Stephan Mueller wrote:

> > > [ .. ]

> > > > > > > > 

> > > > > > > > Thank you for clarifying that. It sounds to me that there is no

> > > > > > > > defined protocol (or if there, I would be wondering how the code would have

> > > > > > > > worked

> > > > > > > > with a different implementation). Would it make sense to first specify

> > > > > > > > a protocol for authentication and have it discussed? I personally think

> > > > > > > > it is a bit difficult to fully understand the protocol from the code and

> > > > > > > > discuss protocol-level items based on the code.

> > > > > > > > 

> > > > > > > Oh, the protocol _is_ specified:

> > > > > > > 

> > > > > > >  

> > > > > > > https://nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-2_0-2021.06.02-Ratified-5.pdf

> > > > > > > 

> > > > > > > It's just that I have issues translating that spec onto what the kernel

> > > > > > > provides.

> > > > > > 

> > > > > > according to the naming conventions there in figures 447 and following:

> > > > > > 

> > > > > > - x and y: DH private key (kernel calls it secret set with dh_set_secret

> > > > > > or

> > > > > > encoded into param.key)

> > > > > > 

> > > > > 

> > > > > But that's were I got confused; one needs a private key here, but there

> > > > > is no obvious candidate for it. But reading it more closely I guess the

> > > > > private key is just a random number (cf the spec: g^y mod p, where y is

> > > > > a random number selected by the host that shall be at least 256 bits

> > > > > long). So I'll fix it up with the next round.

> > > > 

> > > > Here comes the crux: the kernel has an ECC private key generation function

> > > > ecdh_set_secret triggered with crypto_kpp_set_secret using a NULL key, but it

> > > > has no FFC-DH counterpart.

> > > > 

> > > > That said, generating a random number is the most obvious choice, but not the

> > > > right one.

> > > > 

> > > > The correct one would be following SP800-56A rev 3 and here either section

> > > > 5.6.1.1.3 or 5.6.1.1.4.

> > > > 

> > > Hmm. Okay. But after having read section 5.6.1.1.4, I still do have some

> > > questions.

> > > 

> > > Assume we will be using a bit length of 512 for FFDHE, then we will

> > > trivially pass Step 2 for all supported FFDHE groups (the maximum

> > > symmetric-equivalent strength for ffdhe8192 is 192 bits).

> > 

> > N = 512 is not a good choice, minimum length these days for DH should

> > be 2048 or more.

> > 

> 

> According to RFC7919:

> Peers using ffdhe8192 that want to optimize their key exchange with a

> short exponent (Section 5.2) should choose a secret key of at least

> 400 bits.

> 

> So what is wrong with 512 bits?



RFC7519 is TLS Specific.
I do not know if short-exponents are safe to use in all use cases.

If it is safe, your choice is fine and your arguments will follow, but
then a comment that explains the choice and warns about key checks if
it is changed would be a good idea.

Otherwise the default should be to use N = len(q), which implies the
proper checks need to be applied.

Simo.

> > > From my understanding, the random number generator will fill out all

> > > available bytes in the string (and nothing more), so we trivially

> > > satisfy step 3 and 4.

> > > 

> > > And as q is always larger than the random number, step 6 reduces to

> > > 'if (c > 2^N - 2)',

> > 

> > Where is this coming from ?

> > It seem you assume M = 2^N but M = min(2^N, q)

> > 

> > The point here is to make sure the number X you return is:

> > 0 < X < (q-1)

> > 

> 

> Which is what I've tried to argue. For 512 bits private key and the

> smallest possible FFDHE group (which has 2048 bits, with the top bit

> non-zero) 2^N is always smaller than (q - 1).

> As the other FFHDE groups are using even larger 'q' values, this is true

> for all FFHDE groups.

> 

> > >  ie we just need to check if the random number is a

> > > string of 0xff characters. Which hardly is a random number at all, so

> > > it'll be impossible to get this.

> > > 

> > > Which then would mean that our 'x' is simply the random number + 1,

> > 

> > This is an artifact due to the random number being 0 <= c < 2^N - 1,

> > therefore 1 needs to be added to make sure you never return 0.

> > 

> 

> And my argument here is that all zeros (and all ones) are not a value I

> would expect from our RNG.

> 

> > > which arguably is slightly pointless (one more than a random number is

> > > as random as the number itself), so I do feel justified with just

> > > returning a random number here.

> > > 

> > > Am I wrong with that reasoning?

> > 

> > Looks to me you are not accounting for the fact that N = 512 is too

> > small and a random number falling in the interval (q - 2) < X < 2^N is

> > unsuitable?

> > 

> 

> Only if (q - 2) < 2^N. And my point is that it's not.

> 

> Cheers,

> 

> Hannes


-- 
Simo Sorce
RHEL Crypto Team
Red Hat, Inc
Stephan Mueller July 20, 2021, 2:47 p.m. UTC | #18
Am Dienstag, dem 20.07.2021 um 10:44 -0400 schrieb Simo Sorce:
> On Tue, 2021-07-20 at 13:31 +0200, Hannes Reinecke wrote:

> > On 7/20/21 12:49 PM, Simo Sorce wrote:

> > > On Tue, 2021-07-20 at 12:14 +0200, Hannes Reinecke wrote:

> > > > On 7/19/21 1:52 PM, Stephan Mueller wrote:

> > > > > Am Montag, dem 19.07.2021 um 13:10 +0200 schrieb Hannes Reinecke:

> > > > > > On 7/19/21 12:19 PM, Stephan Mueller wrote:

> > > > > > > Am Montag, dem 19.07.2021 um 11:57 +0200 schrieb Hannes

> > > > > > > Reinecke:

> > > > > > > > On 7/19/21 10:51 AM, Stephan Mueller wrote:

> > > > [ .. ]

> > > > > > > > > 

> > > > > > > > > Thank you for clarifying that. It sounds to me that there is

> > > > > > > > > no

> > > > > > > > > defined protocol (or if there, I would be wondering how the

> > > > > > > > > code would have

> > > > > > > > > worked

> > > > > > > > > with a different implementation). Would it make sense to

> > > > > > > > > first specify

> > > > > > > > > a protocol for authentication and have it discussed? I

> > > > > > > > > personally think

> > > > > > > > > it is a bit difficult to fully understand the protocol from

> > > > > > > > > the code and

> > > > > > > > > discuss protocol-level items based on the code.

> > > > > > > > > 

> > > > > > > > Oh, the protocol _is_ specified:

> > > > > > > > 

> > > > > > > >  

> > > > > > > > https://nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-2_0-2021.06.02-Ratified-5.pdf

> > > > > > > > 

> > > > > > > > It's just that I have issues translating that spec onto what

> > > > > > > > the kernel

> > > > > > > > provides.

> > > > > > > 

> > > > > > > according to the naming conventions there in figures 447 and

> > > > > > > following:

> > > > > > > 

> > > > > > > - x and y: DH private key (kernel calls it secret set with

> > > > > > > dh_set_secret

> > > > > > > or

> > > > > > > encoded into param.key)

> > > > > > > 

> > > > > > 

> > > > > > But that's were I got confused; one needs a private key here, but

> > > > > > there

> > > > > > is no obvious candidate for it. But reading it more closely I

> > > > > > guess the

> > > > > > private key is just a random number (cf the spec: g^y mod p, where

> > > > > > y is

> > > > > > a random number selected by the host that shall be at least 256

> > > > > > bits

> > > > > > long). So I'll fix it up with the next round.

> > > > > 

> > > > > Here comes the crux: the kernel has an ECC private key generation

> > > > > function

> > > > > ecdh_set_secret triggered with crypto_kpp_set_secret using a NULL

> > > > > key, but it

> > > > > has no FFC-DH counterpart.

> > > > > 

> > > > > That said, generating a random number is the most obvious choice,

> > > > > but not the

> > > > > right one.

> > > > > 

> > > > > The correct one would be following SP800-56A rev 3 and here either

> > > > > section

> > > > > 5.6.1.1.3 or 5.6.1.1.4.

> > > > > 

> > > > Hmm. Okay. But after having read section 5.6.1.1.4, I still do have

> > > > some

> > > > questions.

> > > > 

> > > > Assume we will be using a bit length of 512 for FFDHE, then we will

> > > > trivially pass Step 2 for all supported FFDHE groups (the maximum

> > > > symmetric-equivalent strength for ffdhe8192 is 192 bits).

> > > 

> > > N = 512 is not a good choice, minimum length these days for DH should

> > > be 2048 or more.

> > > 

> > 

> > According to RFC7919:

> > Peers using ffdhe8192 that want to optimize their key exchange with a

> > short exponent (Section 5.2) should choose a secret key of at least

> > 400 bits.

> > 

> > So what is wrong with 512 bits?

> 

> 

> RFC7519 is TLS Specific.

> I do not know if short-exponents are safe to use in all use cases.

> 

> If it is safe, your choice is fine and your arguments will follow, but

> then a comment that explains the choice and warns about key checks if

> it is changed would be a good idea.

> 

> Otherwise the default should be to use N = len(q), which implies the

> proper checks need to be applied.


Agreed.

Ciao
Stephan
> 

> Simo.

> 

> > > > From my understanding, the random number generator will fill out all

> > > > available bytes in the string (and nothing more), so we trivially

> > > > satisfy step 3 and 4.

> > > > 

> > > > And as q is always larger than the random number, step 6 reduces to

> > > > 'if (c > 2^N - 2)',

> > > 

> > > Where is this coming from ?

> > > It seem you assume M = 2^N but M = min(2^N, q)

> > > 

> > > The point here is to make sure the number X you return is:

> > > 0 < X < (q-1)

> > > 

> > 

> > Which is what I've tried to argue. For 512 bits private key and the

> > smallest possible FFDHE group (which has 2048 bits, with the top bit

> > non-zero) 2^N is always smaller than (q - 1).

> > As the other FFHDE groups are using even larger 'q' values, this is true

> > for all FFHDE groups.

> > 

> > > >  ie we just need to check if the random number is a

> > > > string of 0xff characters. Which hardly is a random number at all, so

> > > > it'll be impossible to get this.

> > > > 

> > > > Which then would mean that our 'x' is simply the random number + 1,

> > > 

> > > This is an artifact due to the random number being 0 <= c < 2^N - 1,

> > > therefore 1 needs to be added to make sure you never return 0.

> > > 

> > 

> > And my argument here is that all zeros (and all ones) are not a value I

> > would expect from our RNG.

> > 

> > > > which arguably is slightly pointless (one more than a random number is

> > > > as random as the number itself), so I do feel justified with just

> > > > returning a random number here.

> > > > 

> > > > Am I wrong with that reasoning?

> > > 

> > > Looks to me you are not accounting for the fact that N = 512 is too

> > > small and a random number falling in the interval (q - 2) < X < 2^N is

> > > unsuitable?

> > > 

> > 

> > Only if (q - 2) < 2^N. And my point is that it's not.

> > 

> > Cheers,

> > 

> > Hannes

>
Vladislav Bolkhovitin July 23, 2021, 8:02 p.m. UTC | #19
On 7/19/21 1:19 PM, Stephan Mueller wrote:
> Am Montag, dem 19.07.2021 um 11:57 +0200 schrieb Hannes Reinecke:

>> On 7/19/21 10:51 AM, Stephan Mueller wrote:

>>> Am Montag, dem 19.07.2021 um 10:15 +0200 schrieb Hannes Reinecke:

>>>> On 7/18/21 2:56 PM, Stephan Müller wrote:

>>>>> Am Sonntag, 18. Juli 2021, 14:37:34 CEST schrieb Hannes Reinecke:

>>>

>>>>>> The key is also used when using the ffdhe algorithm.

>>>>>> Note: I _think_ that I need to use this key for the ffdhe algorithm,

>>>>>> because the implementation I came up with is essentially plain DH

>>>>>> with

>>>>>> pre-defined 'p', 'q' and 'g' values. But the DH implementation also

>>>>>> requires a 'key', and for that I'm using this key here.

>>>>>>

>>>>>> It might be that I'm completely off, and don't need to use a key for

>>>>>> our

>>>>>> DH implementation. In that case you are correct.

>>>>>> (And that's why I said I'll need a review of the FFDHE

>>>>>> implementation).

>>>>>> But for now I'll need the key for FFDHE.

>>>>>

>>>>> Do I understand you correctly that the dhchap_key is used as the input

>>>>> to

>>>>> the 

>>>>> DH - i.e. it is the remote public key then? It looks strange that this

>>>>> is

>>>>> used 

>>>>> for DH but then it is changed here by hashing it together with

>>>>> something

>>>>> else 

>>>>> to form a new dhchap_key. Maybe that is what the protocol says. But it

>>>>> sounds 

>>>>> strange to me, especially when you think that dhchap_key would be,

>>>>> say,

>>>>> 2048 

>>>>> bits if it is truly the remote public key and then after the hashing

>>>>> it is

>>>>> 256 

>>>>> this dhchap_key cannot be used for FFC-DH.

>>>>>

>>>>> Or are you using the dhchap_key for two different purposes?

>>>>>

>>>>> It seems I miss something here.

>>>>>

>>>> No, not entirely. It's me who buggered it up.

>>>> I got carried away by the fact that there is a crypto_dh_encode_key()

>>>> function, and thought I need to use it here.

>>>

>>> Thank you for clarifying that. It sounds to me that there is no defined

>>> protocol (or if there, I would be wondering how the code would have worked

>>> with a different implementation). Would it make sense to first specify a

>>> protocol for authentication and have it discussed? I personally think it

>>> is a

>>> bit difficult to fully understand the protocol from the code and discuss

>>> protocol-level items based on the code.

>>>

>> Oh, the protocol _is_ specified:

>>

>> https://nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-2_0-2021.06.02-Ratified-5.pdf

>>

>> It's just that I have issues translating that spec onto what the kernel

>> provides.

> 

> according to the naming conventions there in figures 447 and following:

> 

> - x and y: DH private key (kernel calls it secret set with dh_set_secret or

> encoded into param.key)


x and y are defined in Figure 444: "Random numbers used as exponents in
a DH exchange". Sections 8.13.5.3 "DH-HMAC-CHAP_Challenge Message" and
8.13.5.4 "DH-HMAC-CHAP_Reply Message" additionally specify that "x is a
random number selected by the controller that shall be at least 256 bits
long" and "y is a random number selected by the host that shall be at
least 256 bits long".

So, x and y are just random numbers, no need to overcomplicate their
generation in the way that is beyond of the standard scope.

Vlad
diff mbox series

Patch

diff --git a/drivers/nvme/target/Kconfig b/drivers/nvme/target/Kconfig
index 4be2ececbc45..d5656ef1559e 100644
--- a/drivers/nvme/target/Kconfig
+++ b/drivers/nvme/target/Kconfig
@@ -85,3 +85,13 @@  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_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 0cb98f2bbc8c..320cefc64ee0 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..00c7d051dfb1
--- /dev/null
+++ b/drivers/nvme/target/auth.c
@@ -0,0 +1,352 @@ 
+// 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 <crypto/kpp.h>
+#include <crypto/dh.h>
+#include <crypto/ffdhe.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_warn("DH-HMAC-CHAP hash %s unsupported\n", hmac);
+			host->dhchap_key_hash = -1;
+			return -EAGAIN;
+		}
+		/* 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_HASH_SHA256;
+
+	pr_debug("Using hash %s\n",
+		 nvme_auth_hmac_name(host->dhchap_hash_id));
+	return 0;
+}
+
+int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, int dhgroup_id)
+{
+	int ret = -ENOTSUPP;
+
+	if (dhgroup_id == NVME_AUTH_DHCHAP_DHGROUP_NULL)
+		return 0;
+
+	return ret;
+}
+
+int nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req)
+{
+	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;
+
+	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;
+	}
+
+	hash_name = nvme_auth_hmac_name(host->dhchap_hash_id);
+	if (!hash_name) {
+		pr_debug("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_debug("failed to allocate shash %s\n", hash_name);
+		ret = PTR_ERR(ctrl->shash_tfm);
+		ctrl->shash_tfm = NULL;
+		goto out_unlock;
+	}
+
+	ctrl->dhchap_key = nvme_auth_extract_secret(host->dhchap_secret,
+						    &ctrl->dhchap_key_len);
+	if (IS_ERR(ctrl->dhchap_key)) {
+		pr_debug("failed to extract host key, error %d\n", ret);
+		ret = PTR_ERR(ctrl->dhchap_key);
+		ctrl->dhchap_key = NULL;
+		goto out_free_hash;
+	}
+	if (host->dhchap_key_hash) {
+		struct crypto_shash *key_tfm;
+
+		hash_name = nvme_auth_hmac_name(host->dhchap_key_hash);
+		key_tfm = crypto_alloc_shash(hash_name, 0, 0);
+		if (IS_ERR(key_tfm)) {
+			ret = PTR_ERR(key_tfm);
+			goto out_free_hash;
+		} else {
+			SHASH_DESC_ON_STACK(shash, key_tfm);
+
+			shash->tfm = key_tfm;
+			ret = crypto_shash_setkey(key_tfm, ctrl->dhchap_key,
+						  ctrl->dhchap_key_len);
+			crypto_shash_init(shash);
+			crypto_shash_update(shash, ctrl->subsys->subsysnqn,
+					    strlen(ctrl->subsys->subsysnqn));
+			crypto_shash_update(shash, "NVMe-over-Fabrics", 17);
+			crypto_shash_final(shash, ctrl->dhchap_key);
+			crypto_free_shash(key_tfm);
+		}
+	}
+	pr_debug("%s: using key %*ph\n", __func__,
+		 (int)ctrl->dhchap_key_len, ctrl->dhchap_key);
+	ret = crypto_shash_setkey(ctrl->shash_tfm, ctrl->dhchap_key,
+				  ctrl->dhchap_key_len);
+out_free_hash:
+	if (ret) {
+		if (ctrl->dhchap_key) {
+			kfree(ctrl->dhchap_key);
+			ctrl->dhchap_key = NULL;
+		}
+		crypto_free_shash(ctrl->shash_tfm);
+		ctrl->shash_tfm = NULL;
+	}
+out_unlock:
+	up_read(&nvmet_config_sem);
+
+	return ret;
+}
+
+void nvmet_auth_sq_free(struct nvmet_sq *sq)
+{
+	if (sq->dhchap_c1)
+		kfree(sq->dhchap_c1);
+	if (sq->dhchap_c2)
+		kfree(sq->dhchap_c2);
+	if (sq->dhchap_skey)
+		kfree(sq->dhchap_skey);
+}
+
+void nvmet_reset_auth(struct nvmet_ctrl *ctrl)
+{
+	if (ctrl->shash_tfm) {
+		crypto_free_shash(ctrl->shash_tfm);
+		ctrl->shash_tfm = NULL;
+	}
+	if (ctrl->dh_tfm) {
+		crypto_free_kpp(ctrl->dh_tfm);
+		ctrl->dh_tfm = NULL;
+	}
+	if (ctrl->dhchap_key) {
+		kfree(ctrl->dhchap_key);
+		ctrl->dhchap_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;
+	u8 buf[4];
+	int ret;
+
+	if (ctrl->dh_gid != NVME_AUTH_DHCHAP_DHGROUP_NULL) {
+		ret = -ENOTSUPP;
+		goto out;
+	}
+
+	shash->tfm = ctrl->shash_tfm;
+	ret = crypto_shash_init(shash);
+	if (ret)
+		goto out;
+	ret = crypto_shash_update(shash, challenge, shash_len);
+	if (ret)
+		goto out;
+	put_unaligned_le32(req->sq->dhchap_s1, buf);
+	ret = crypto_shash_update(shash, buf, 4);
+	if (ret)
+		goto out;
+	put_unaligned_le16(req->sq->dhchap_tid, buf);
+	ret = crypto_shash_update(shash, buf, 2);
+	if (ret)
+		goto out;
+	memset(buf, 0, 4);
+	ret = crypto_shash_update(shash, buf, 1);
+	if (ret)
+		goto out;
+	ret = crypto_shash_update(shash, "HostHost", 8);
+	if (ret)
+		goto out;
+	ret = crypto_shash_update(shash, ctrl->hostnqn, strlen(ctrl->hostnqn));
+	if (ret)
+		goto out;
+	ret = crypto_shash_update(shash, buf, 1);
+	if (ret)
+		goto out;
+	ret = crypto_shash_update(shash, ctrl->subsysnqn,
+				  strlen(ctrl->subsysnqn));
+	if (ret)
+		goto out;
+	ret = crypto_shash_final(shash, response);
+out:
+	if (challenge != req->sq->dhchap_c1)
+		kfree(challenge);
+	return 0;
+}
+
+int nvmet_auth_ctrl_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_c2;
+	u8 buf[4];
+	int ret;
+
+	pr_debug("%s: ctrl %d hash seq %d transaction %u\n", __func__,
+		 ctrl->cntlid, req->sq->dhchap_s2, req->sq->dhchap_tid);
+	pr_debug("%s: ctrl %d challenge %*ph\n", __func__,
+		 ctrl->cntlid, shash_len, req->sq->dhchap_c2);
+	pr_debug("%s: ctrl %d subsysnqn %s\n", __func__,
+		 ctrl->cntlid, ctrl->subsysnqn);
+	pr_debug("%s: ctrl %d hostnqn %s\n", __func__,
+		 ctrl->cntlid, ctrl->hostnqn);
+
+	if (ctrl->dh_gid != NVME_AUTH_DHCHAP_DHGROUP_NULL) {
+		ret = -ENOTSUPP;
+		goto out;
+	}
+
+	shash->tfm = ctrl->shash_tfm;
+	ret = crypto_shash_init(shash);
+	if (ret)
+		goto out;
+	ret = crypto_shash_update(shash, challenge, shash_len);
+	if (ret)
+		goto out;
+	put_unaligned_le32(req->sq->dhchap_s2, buf);
+	ret = crypto_shash_update(shash, buf, 4);
+	if (ret)
+		goto out;
+	put_unaligned_le16(req->sq->dhchap_tid, buf);
+	ret = crypto_shash_update(shash, buf, 2);
+	if (ret)
+		goto out;
+	memset(buf, 0, 4);
+	ret = crypto_shash_update(shash, buf, 1);
+	if (ret)
+		goto out;
+	ret = crypto_shash_update(shash, "Controller", 10);
+	if (ret)
+		goto out;
+	ret = crypto_shash_update(shash, ctrl->subsysnqn,
+			    strlen(ctrl->subsysnqn));
+	if (ret)
+		goto out;
+	ret = crypto_shash_update(shash, buf, 1);
+	if (ret)
+		goto out;
+	ret = crypto_shash_update(shash, ctrl->hostnqn, strlen(ctrl->hostnqn));
+	if (ret)
+		goto out;
+	ret = crypto_shash_final(shash, response);
+out:
+	if (challenge != req->sq->dhchap_c2)
+		kfree(challenge);
+	return 0;
+}
+
+int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,
+			    u8 *pkey, int pkey_size)
+{
+	struct nvmet_ctrl *ctrl = req->sq->ctrl;
+	struct kpp_request *kpp_req;
+	struct crypto_wait wait;
+	struct scatterlist src, dst;
+	int ret;
+
+	req->sq->dhchap_skey_len =
+		nvme_auth_dhgroup_privkey_size(ctrl->dh_gid);
+	req->sq->dhchap_skey = kzalloc(req->sq->dhchap_skey_len, GFP_KERNEL);
+	if (!req->sq->dhchap_skey)
+		return -ENOMEM;
+	kpp_req = kpp_request_alloc(ctrl->dh_tfm, GFP_KERNEL);
+	if (!kpp_req) {
+		kfree(req->sq->dhchap_skey);
+		req->sq->dhchap_skey = NULL;
+		return -ENOMEM;
+	}
+
+	pr_debug("%s: host public key %*ph\n", __func__,
+		 (int)pkey_size, pkey);
+	crypto_init_wait(&wait);
+	sg_init_one(&src, pkey, pkey_size);
+	kpp_request_set_input(kpp_req, &src, pkey_size);
+	sg_init_one(&dst, req->sq->dhchap_skey,
+		req->sq->dhchap_skey_len);
+	kpp_request_set_output(kpp_req, &dst, req->sq->dhchap_skey_len);
+	kpp_request_set_callback(kpp_req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+				 crypto_req_done, &wait);
+
+	ret = crypto_wait_req(crypto_kpp_compute_shared_secret(kpp_req), &wait);
+	kpp_request_free(kpp_req);
+	if (ret)
+		pr_debug("failed to compute shared secred, err %d\n", ret);
+	else
+		pr_debug("%s: shared secret %*ph\n", __func__,
+			 (int)req->sq->dhchap_skey_len,
+			 req->sq->dhchap_skey);
+
+	return ret;
+}
diff --git a/drivers/nvme/target/configfs.c b/drivers/nvme/target/configfs.c
index 273555127188..e0760911a761 100644
--- a/drivers/nvme/target/configfs.c
+++ b/drivers/nvme/target/configfs.c
@@ -11,8 +11,13 @@ 
 #include <linux/ctype.h>
 #include <linux/pci.h>
 #include <linux/pci-p2pdma.h>
+#include <crypto/hash.h>
+#include <crypto/kpp.h>
 
 #include "nvmet.h"
+#ifdef CONFIG_NVME_TARGET_AUTH
+#include "../host/auth.h"
+#endif
 
 static const struct config_item_type nvmet_host_type;
 static const struct config_item_type nvmet_subsys_type;
@@ -1656,10 +1661,71 @@  static const struct config_item_type nvmet_ports_type = {
 static struct config_group nvmet_subsystems_group;
 static struct config_group nvmet_ports_group;
 
-static void nvmet_host_release(struct config_item *item)
+#ifdef CONFIG_NVME_TARGET_AUTH
+static ssize_t nvmet_host_dhchap_key_show(struct config_item *item,
+		char *page)
+{
+	u8 *dhchap_secret = to_host(item)->dhchap_secret;
+
+	if (!dhchap_secret)
+		return sprintf(page, "\n");
+	return sprintf(page, "%s\n", dhchap_secret);
+}
+
+static ssize_t nvmet_host_dhchap_key_store(struct config_item *item,
+		const char *page, size_t count)
 {
 	struct nvmet_host *host = to_host(item);
+	int ret;
 
+	ret = nvmet_auth_set_host_key(host, page);
+	if (ret < 0)
+		return ret;
+	return count;
+}
+
+CONFIGFS_ATTR(nvmet_host_, dhchap_key);
+
+static ssize_t nvmet_host_dhchap_hash_show(struct config_item *item,
+		char *page)
+{
+	struct nvmet_host *host = to_host(item);
+	const char *hash_name = nvme_auth_hmac_name(host->dhchap_hash_id);
+
+	return sprintf(page, "%s\n", hash_name ? hash_name : "none");
+}
+
+static ssize_t nvmet_host_dhchap_hash_store(struct config_item *item,
+		const char *page, size_t count)
+{
+	struct nvmet_host *host = to_host(item);
+	int hmac_id;
+
+	hmac_id = nvme_auth_hmac_id(page);
+	if (hmac_id < 0)
+		return -EINVAL;
+	if (!crypto_has_shash(nvme_auth_hmac_name(hmac_id), 0, 0))
+		return -ENOTSUPP;
+	host->dhchap_hash_id = hmac_id;
+	return count;
+}
+
+CONFIGFS_ATTR(nvmet_host_, dhchap_hash);
+
+static struct configfs_attribute *nvmet_host_attrs[] = {
+	&nvmet_host_attr_dhchap_key,
+	&nvmet_host_attr_dhchap_hash,
+	NULL,
+};
+#endif /* CONFIG_NVME_TARGET_AUTH */
+
+static void nvmet_host_release(struct config_item *item)
+{
+	struct nvmet_host *host = to_host(item);
+#ifdef CONFIG_NVME_TARGET_AUTH
+	if (host->dhchap_secret)
+		kfree(host->dhchap_secret);
+#endif
 	kfree(host);
 }
 
@@ -1669,6 +1735,9 @@  static struct configfs_item_operations nvmet_host_item_ops = {
 
 static const struct config_item_type nvmet_host_type = {
 	.ct_item_ops		= &nvmet_host_item_ops,
+#ifdef CONFIG_NVME_TARGET_AUTH
+	.ct_attrs		= nvmet_host_attrs,
+#endif
 	.ct_owner		= THIS_MODULE,
 };
 
diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c
index 163f7dc1a929..b5d7971f566b 100644
--- a/drivers/nvme/target/core.c
+++ b/drivers/nvme/target/core.c
@@ -793,6 +793,7 @@  void nvmet_sq_destroy(struct nvmet_sq *sq)
 	wait_for_completion(&sq->confirm_done);
 	wait_for_completion(&sq->free_done);
 	percpu_ref_exit(&sq->ref);
+	nvmet_auth_sq_free(sq);
 
 	if (ctrl) {
 		/*
@@ -1264,6 +1265,11 @@  u16 nvmet_check_ctrl_status(struct nvmet_req *req)
 		       req->cmd->common.opcode, req->sq->qid);
 		return NVME_SC_CMD_SEQ_ERROR | NVME_SC_DNR;
 	}
+
+	if (unlikely(!nvmet_check_auth_status(req))) {
+		pr_warn("qid %d not authenticated\n", req->sq->qid);
+		return NVME_SC_AUTH_REQUIRED | NVME_SC_DNR;
+	}
 	return 0;
 }
 
@@ -1456,6 +1462,8 @@  static void nvmet_ctrl_free(struct kref *ref)
 	flush_work(&ctrl->async_event_work);
 	cancel_work_sync(&ctrl->fatal_err_work);
 
+	nvmet_reset_auth(ctrl);
+
 	ida_simple_remove(&cntlid_ida, ctrl->cntlid);
 
 	nvmet_async_events_free(ctrl);
diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/target/fabrics-cmd-auth.c
new file mode 100644
index 000000000000..962f9f5e9d89
--- /dev/null
+++ b/drivers/nvme/target/fabrics-cmd-auth.c
@@ -0,0 +1,460 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * NVMe over Fabrics DH-HMAC-CHAP authentication command handling.
+ * Copyright (c) 2020 Hannes Reinecke, SUSE Software Solutions.
+ * All rights reserved.
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/blkdev.h>
+#include <linux/random.h>
+#include <crypto/hash.h>
+#include <crypto/kpp.h>
+#include "nvmet.h"
+#include "../host/auth.h"
+
+void nvmet_init_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req)
+{
+	/* Initialize in-band authentication */
+	req->sq->authenticated = false;
+	req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE;
+	req->cqe->result.u32 |= 0x2 << 16;
+}
+
+static u16 nvmet_auth_negotiate(struct nvmet_req *req, void *d)
+{
+	struct nvmet_ctrl *ctrl = req->sq->ctrl;
+	struct nvmf_auth_dhchap_negotiate_data *data = d;
+	int i, hash_id, null_dh = -1;
+
+	pr_debug("%s: ctrl %d qid %d: data sc_d %d napd %d authid %d halen %d dhlen %d\n",
+		 __func__, ctrl->cntlid, req->sq->qid,
+		 data->sc_c, data->napd, data->auth_protocol[0].dhchap.authid,
+		 data->auth_protocol[0].dhchap.halen,
+		 data->auth_protocol[0].dhchap.dhlen);
+	req->sq->dhchap_tid = le16_to_cpu(data->t_id);
+	if (data->sc_c)
+		return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
+
+	if (data->napd != 1)
+		return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;
+
+	if (data->auth_protocol[0].dhchap.authid != 0x01)
+		return NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;
+
+	hash_id = nvme_auth_hmac_id(crypto_shash_alg_name(ctrl->shash_tfm));
+	for (i = 0; i < data->auth_protocol[0].dhchap.halen; i++) {
+		pr_debug("%s: ctrl %d qid %d checking hash %d for %d\n",
+			 __func__, ctrl->cntlid, req->sq->qid,
+			 data->auth_protocol[0].dhchap.idlist[i], hash_id);
+		if (hash_id != data->auth_protocol[0].dhchap.idlist[i])
+			continue;
+		req->sq->dhchap_hash_id = hash_id;
+		req->sq->dhchap_hash_len = crypto_shash_digestsize(ctrl->shash_tfm);
+		break;
+	}
+	if (req->sq->dhchap_hash_id == 0) {
+		pr_debug("%s: ctrl %d qid %d: no usable hash found\n",
+			 __func__, ctrl->cntlid, req->sq->qid);
+		return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;
+	}
+
+	for (i = data->auth_protocol[0].dhchap.halen;
+	     i < data->auth_protocol[0].dhchap.halen +
+		     data->auth_protocol[0].dhchap.dhlen; i++) {
+		int dhgid = data->auth_protocol[0].dhchap.idlist[i];
+
+		if (dhgid == NVME_AUTH_DHCHAP_DHGROUP_NULL) {
+			null_dh = dhgid;
+			continue;
+		}
+		if (nvmet_setup_dhgroup(ctrl, dhgid) == 0)
+			break;
+	}
+	if (!ctrl->dh_tfm && null_dh < 0) {
+		pr_debug("%s: ctrl %d qid %d: no DH group selected\n",
+			 __func__, ctrl->cntlid, req->sq->qid);
+		return NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE;
+	}
+	if (ctrl->dh_gid == -1) {
+		ctrl->dh_gid = null_dh;
+		ctrl->dh_tfm = NULL;
+	}
+	pr_debug("%s: ctrl %d qid %d: DH group %s (%d)\n",
+		 __func__, ctrl->cntlid, req->sq->qid,
+		 nvme_auth_dhgroup_name(ctrl->dh_gid), ctrl->dh_gid);
+	return 0;
+}
+
+static u16 nvmet_auth_reply(struct nvmet_req *req, void *d)
+{
+	struct nvmet_ctrl *ctrl = req->sq->ctrl;
+	struct nvmf_auth_dhchap_reply_data *data = d;
+	u8 *response;
+
+	pr_debug("%s: ctrl %d qid %d: data hl %d cvalid %d dhvlen %d\n",
+		 __func__, ctrl->cntlid, req->sq->qid,
+		 data->hl, data->cvalid, data->dhvlen);
+	if (data->hl != req->sq->dhchap_hash_len)
+		return NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;
+
+	if (data->dhvlen) {
+		return NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;
+	}
+
+	response = kmalloc(data->hl, GFP_KERNEL);
+	if (!response)
+		return NVME_AUTH_DHCHAP_FAILURE_FAILED;
+
+	if (nvmet_auth_host_hash(req, response, data->hl) < 0) {
+		pr_debug("ctrl %d qid %d DH-HMAC-CHAP hash failed\n",
+			 ctrl->cntlid, req->sq->qid);
+		kfree(response);
+		return NVME_AUTH_DHCHAP_FAILURE_FAILED;
+	}
+
+	if (memcmp(data->rval, response, data->hl)) {
+		pr_info("ctrl %d qid %d DH-HMAC-CHAP response mismatch\n",
+			ctrl->cntlid, req->sq->qid);
+		kfree(response);
+		return NVME_AUTH_DHCHAP_FAILURE_FAILED;
+	}
+	kfree(response);
+	pr_info("ctrl %d qid %d DH-HMAC-CHAP host authenticated\n",
+		ctrl->cntlid, req->sq->qid);
+	if (data->cvalid) {
+		req->sq->dhchap_c2 = kmalloc(data->hl, GFP_KERNEL);
+		if (!req->sq->dhchap_c2)
+			return NVME_AUTH_DHCHAP_FAILURE_FAILED;
+		memcpy(req->sq->dhchap_c2, data->rval + data->hl, data->hl);
+
+		pr_debug("ctrl %d qid %d challenge %*ph\n",
+			 ctrl->cntlid, req->sq->qid, data->hl,
+			 req->sq->dhchap_c2);
+		req->sq->dhchap_s2 = le32_to_cpu(data->seqnum);
+	} else
+		req->sq->dhchap_c2 = NULL;
+
+	return 0;
+}
+
+static u16 nvmet_auth_failure2(struct nvmet_req *req, void *d)
+{
+	struct nvmf_auth_dhchap_failure_data *data = d;
+
+	return data->reason_code_explanation;
+}
+
+void nvmet_execute_auth_send(struct nvmet_req *req)
+{
+	struct nvmet_ctrl *ctrl = req->sq->ctrl;
+	struct nvmf_auth_dhchap_success2_data *data;
+	void *d;
+	u32 tl;
+	u16 status = 0;
+
+	if (req->cmd->auth_send.secp != NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER) {
+		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;
+		req->error_loc =
+			offsetof(struct nvmf_auth_send_command, secp);
+		goto done;
+	}
+	if (req->cmd->auth_send.spsp0 != 0x01) {
+		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;
+		req->error_loc =
+			offsetof(struct nvmf_auth_send_command, spsp0);
+		goto done;
+	}
+	if (req->cmd->auth_send.spsp1 != 0x01) {
+		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;
+		req->error_loc =
+			offsetof(struct nvmf_auth_send_command, spsp1);
+		goto done;
+	}
+	tl = le32_to_cpu(req->cmd->auth_send.tl);
+	if (!tl) {
+		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;
+		req->error_loc =
+			offsetof(struct nvmf_auth_send_command, tl);
+		goto done;
+	}
+	if (!nvmet_check_transfer_len(req, tl)) {
+		pr_debug("%s: transfer length mismatch (%u)\n", __func__, tl);
+		return;
+	}
+
+	d = kmalloc(tl, GFP_KERNEL);
+	if (!d) {
+		status = NVME_SC_INTERNAL;
+		goto done;
+	}
+
+	status = nvmet_copy_from_sgl(req, 0, d, tl);
+	if (status) {
+		kfree(d);
+		goto done;
+	}
+
+	data = d;
+	pr_debug("%s: ctrl %d qid %d type %d id %d step %x\n", __func__,
+		 ctrl->cntlid, req->sq->qid, data->auth_type, data->auth_id,
+		 req->sq->dhchap_step);
+	if (data->auth_type != NVME_AUTH_COMMON_MESSAGES &&
+	    data->auth_type != NVME_AUTH_DHCHAP_MESSAGES) {
+		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
+		req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;
+	} else if (data->auth_type == NVME_AUTH_COMMON_MESSAGES) {
+		if (data->auth_id != req->sq->dhchap_step) {
+			req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
+			req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;
+		} else if (data->auth_id != NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE) {
+			req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
+			req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;
+		} else {
+			/* Validate negotiation parameters */
+			status = nvmet_auth_negotiate(req, d);
+			if (status == 0)
+				req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE;
+			else {
+				req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
+				req->sq->dhchap_status = status;
+				status = 0;
+			}
+		}
+	} else if (data->auth_type == NVME_AUTH_DHCHAP_MESSAGES) {
+		if (data->auth_id != req->sq->dhchap_step) {
+			pr_debug("%s: ctrl %d qid %d step mismatch (%d != %d)\n",
+				 __func__, ctrl->cntlid, req->sq->qid,
+				 data->auth_id, req->sq->dhchap_step);
+			req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
+			req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;
+		} else if (le16_to_cpu(data->t_id) != req->sq->dhchap_tid) {
+			pr_debug("%s: ctrl %d qid %d invalid transaction %d (expected %d)\n",
+				 __func__, ctrl->cntlid, req->sq->qid,
+				 le16_to_cpu(data->t_id),
+				 req->sq->dhchap_tid);
+			req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
+			req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_PAYLOAD;
+		} else {
+			switch (data->auth_id) {
+			case NVME_AUTH_DHCHAP_MESSAGE_REPLY:
+				status = nvmet_auth_reply(req, d);
+				if (status == 0)
+					req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1;
+				else {
+					req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
+					req->sq->dhchap_status = status;
+					status = 0;
+				}
+				break;
+			case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2:
+				req->sq->authenticated = true;
+				pr_debug("%s: ctrl %d qid %d authenticated\n",
+					 __func__, ctrl->cntlid, req->sq->qid);
+				break;
+			case NVME_AUTH_DHCHAP_MESSAGE_FAILURE2:
+				status = nvmet_auth_failure2(req, d);
+				if (status) {
+					pr_warn("ctrl %d qid %d: DH-HMAC-CHAP negotiation failed (%d)\n",
+						ctrl->cntlid, req->sq->qid,
+						status);
+					req->sq->dhchap_status = status;
+					status = 0;
+				}
+				break;
+			default:
+				req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;
+				req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE2;
+				break;
+			}
+		}
+	} else {
+		req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INVALID_MESSAGE;
+		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE2;
+	}
+	kfree(d);
+done:
+	pr_debug("%s: ctrl %d qid %d dhchap status %x step %x\n", __func__,
+		 ctrl->cntlid, req->sq->qid,
+		 req->sq->dhchap_status, req->sq->dhchap_step);
+	if (status)
+		pr_debug("%s: ctrl %d qid %d nvme status %x error loc %d\n",
+			 __func__, ctrl->cntlid, req->sq->qid,
+			 status, req->error_loc);
+	req->cqe->result.u64 = 0;
+	nvmet_req_complete(req, status);
+	if (req->sq->dhchap_step != NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2 &&
+	    req->sq->dhchap_step != NVME_AUTH_DHCHAP_MESSAGE_FAILURE2)
+		return;
+	/* Final states, clear up variables */
+	kfree(req->sq->dhchap_c1);
+	kfree(req->sq->dhchap_c2);
+	if (req->sq->dhchap_step == NVME_AUTH_DHCHAP_MESSAGE_FAILURE2)
+		nvmet_ctrl_fatal_error(ctrl);
+}
+
+static int nvmet_auth_challenge(struct nvmet_req *req, void *d, int al)
+{
+	struct nvmf_auth_dhchap_challenge_data *data = d;
+	struct nvmet_ctrl *ctrl = req->sq->ctrl;
+	int ret = 0;
+	int data_size = sizeof(*d) + req->sq->dhchap_hash_len;
+
+	if (al < data_size) {
+		pr_debug("%s: buffer too small (al %d need %d)\n", __func__,
+			 al, data_size);
+		return -EINVAL;
+	}
+	memset(data, 0, data_size);
+	req->sq->dhchap_s1 = ctrl->dhchap_seqnum++;
+	data->auth_type = NVME_AUTH_DHCHAP_MESSAGES;
+	data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE;
+	data->t_id = cpu_to_le16(req->sq->dhchap_tid);
+	data->hashid = req->sq->dhchap_hash_id;
+	data->hl = req->sq->dhchap_hash_len;
+	data->seqnum = cpu_to_le32(req->sq->dhchap_s1);
+	req->sq->dhchap_c1 = kmalloc(data->hl, GFP_KERNEL);
+	if (!req->sq->dhchap_c1)
+		return -ENOMEM;
+	get_random_bytes(req->sq->dhchap_c1, data->hl);
+	memcpy(data->cval, req->sq->dhchap_c1, data->hl);
+	pr_debug("%s: ctrl %d qid %d seq %d transaction %d hl %d dhvlen %d\n",
+		 __func__, ctrl->cntlid, req->sq->qid, req->sq->dhchap_s1,
+		 req->sq->dhchap_tid, data->hl, data->dhvlen);
+	return ret;
+}
+
+static int nvmet_auth_success1(struct nvmet_req *req, void *d, int al)
+{
+	struct nvmf_auth_dhchap_success1_data *data = d;
+	struct nvmet_ctrl *ctrl = req->sq->ctrl;
+
+	WARN_ON(al < sizeof(*data));
+	memset(data, 0, sizeof(*data));
+	data->auth_type = NVME_AUTH_DHCHAP_MESSAGES;
+	data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1;
+	data->t_id = cpu_to_le16(req->sq->dhchap_tid);
+	data->hl = req->sq->dhchap_hash_len;
+	if (req->sq->dhchap_c2) {
+		if (nvmet_auth_ctrl_hash(req, data->rval, data->hl))
+			return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;
+		data->rvalid = 1;
+		pr_debug("ctrl %d qid %d response %*ph\n",
+			 ctrl->cntlid, req->sq->qid, data->hl, data->rval);
+	}
+	return 0;
+}
+
+static void nvmet_auth_failure1(struct nvmet_req *req, void *d, int al)
+{
+	struct nvmf_auth_dhchap_failure_data *data = d;
+
+	WARN_ON(al < sizeof(*data));
+	data->auth_type = NVME_AUTH_COMMON_MESSAGES;
+	data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
+	data->t_id = cpu_to_le32(req->sq->dhchap_tid);
+	data->reason_code = NVME_AUTH_DHCHAP_FAILURE_REASON_FAILED;
+	data->reason_code_explanation = req->sq->dhchap_status;
+}
+
+void nvmet_execute_auth_receive(struct nvmet_req *req)
+{
+	struct nvmet_ctrl *ctrl = req->sq->ctrl;
+	void *d;
+	u32 al;
+	u16 status = 0;
+
+	if (req->cmd->auth_receive.secp != NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER) {
+		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;
+		req->error_loc =
+			offsetof(struct nvmf_auth_receive_command, secp);
+		goto done;
+	}
+	if (req->cmd->auth_receive.spsp0 != 0x01) {
+		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;
+		req->error_loc =
+			offsetof(struct nvmf_auth_receive_command, spsp0);
+		goto done;
+	}
+	if (req->cmd->auth_receive.spsp1 != 0x01) {
+		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;
+		req->error_loc =
+			offsetof(struct nvmf_auth_receive_command, spsp1);
+		goto done;
+	}
+	al = le32_to_cpu(req->cmd->auth_receive.al);
+	if (!al) {
+		status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;
+		req->error_loc =
+			offsetof(struct nvmf_auth_receive_command, al);
+		goto done;
+	}
+	if (!nvmet_check_transfer_len(req, al)) {
+		pr_debug("%s: transfer length mismatch (%u)\n", __func__, al);
+		return;
+	}
+
+	d = kmalloc(al, GFP_KERNEL);
+	if (!d) {
+		status = NVME_SC_INTERNAL;
+		goto done;
+	}
+	pr_debug("%s: ctrl %d qid %d step %x\n", __func__,
+		 ctrl->cntlid, req->sq->qid, req->sq->dhchap_step);
+	switch (req->sq->dhchap_step) {
+	case NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE:
+		status = nvmet_auth_challenge(req, d, al);
+		if (status < 0) {
+			pr_warn("ctrl %d qid %d: challenge error (%d)\n",
+				ctrl->cntlid, req->sq->qid, status);
+			status = NVME_SC_INTERNAL;
+			break;
+		}
+		if (status) {
+			req->sq->dhchap_status = status;
+			nvmet_auth_failure1(req, d, al);
+			pr_warn("ctrl %d qid %d: challenge status (%x)\n",
+				ctrl->cntlid, req->sq->qid,
+				req->sq->dhchap_status);
+			status = 0;
+			break;
+		}
+		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_REPLY;
+		break;
+	case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1:
+		status = nvmet_auth_success1(req, d, al);
+		if (status) {
+			req->sq->dhchap_status = status;
+			nvmet_auth_failure1(req, d, al);
+			pr_warn("ctrl %d qid %d: success1 status (%x)\n",
+				ctrl->cntlid, req->sq->qid,
+				req->sq->dhchap_status);
+			break;
+		}
+		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2;
+		break;
+	case NVME_AUTH_DHCHAP_MESSAGE_FAILURE1:
+		nvmet_auth_failure1(req, d, al);
+		pr_warn("ctrl %d qid %d failure1 (%x)\n",
+			ctrl->cntlid, req->sq->qid, req->sq->dhchap_status);
+		break;
+	default:
+		pr_warn("ctrl %d qid %d unhandled step (%d)\n",
+			ctrl->cntlid, req->sq->qid, req->sq->dhchap_step);
+		req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
+		req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_FAILED;
+		nvmet_auth_failure1(req, d, al);
+		status = 0;
+		break;
+	}
+
+	status = nvmet_copy_to_sgl(req, 0, d, al);
+	kfree(d);
+done:
+	req->cqe->result.u64 = 0;
+	nvmet_req_complete(req, status);
+	if (req->sq->dhchap_step == NVME_AUTH_DHCHAP_MESSAGE_FAILURE1) {
+		kfree(req->sq->dhchap_c1);
+		kfree(req->sq->dhchap_c2);
+		nvmet_ctrl_fatal_error(ctrl);
+	}
+}
diff --git a/drivers/nvme/target/fabrics-cmd.c b/drivers/nvme/target/fabrics-cmd.c
index 7d0f3523fdab..53fb853cd8fe 100644
--- a/drivers/nvme/target/fabrics-cmd.c
+++ b/drivers/nvme/target/fabrics-cmd.c
@@ -93,6 +93,14 @@  u16 nvmet_parse_fabrics_cmd(struct nvmet_req *req)
 	case nvme_fabrics_type_property_get:
 		req->execute = nvmet_execute_prop_get;
 		break;
+#ifdef CONFIG_NVME_TARGET_AUTH
+	case nvme_fabrics_type_auth_send:
+		req->execute = nvmet_execute_auth_send;
+		break;
+	case nvme_fabrics_type_auth_receive:
+		req->execute = nvmet_execute_auth_receive;
+		break;
+#endif
 	default:
 		pr_debug("received unknown capsule type 0x%x\n",
 			cmd->fabrics.fctype);
@@ -155,6 +163,7 @@  static void nvmet_execute_admin_connect(struct nvmet_req *req)
 	struct nvmf_connect_data *d;
 	struct nvmet_ctrl *ctrl = NULL;
 	u16 status = 0;
+	int ret;
 
 	if (!nvmet_check_transfer_len(req, sizeof(struct nvmf_connect_data)))
 		return;
@@ -197,17 +206,31 @@  static void nvmet_execute_admin_connect(struct nvmet_req *req)
 
 	uuid_copy(&ctrl->hostid, &d->hostid);
 
+	ret = nvmet_setup_auth(ctrl, req);
+	if (ret < 0) {
+		pr_err("Failed to setup authentication, error %d\n", ret);
+		nvmet_ctrl_put(ctrl);
+		if (ret == -EPERM)
+			status = (NVME_SC_CONNECT_INVALID_HOST | NVME_SC_DNR);
+		else
+			status = NVME_SC_INTERNAL;
+		goto out;
+	}
+
 	status = nvmet_install_queue(ctrl, req);
 	if (status) {
 		nvmet_ctrl_put(ctrl);
 		goto out;
 	}
 
-	pr_info("creating controller %d for subsystem %s for NQN %s%s.\n",
+	pr_info("creating controller %d for subsystem %s for NQN %s%s%s.\n",
 		ctrl->cntlid, ctrl->subsys->subsysnqn, ctrl->hostnqn,
-		ctrl->pi_support ? " T10-PI is enabled" : "");
+		ctrl->pi_support ? " T10-PI is enabled" : "",
+		nvmet_has_auth(ctrl) ? " with DH-HMAC-CHAP" : "");
 	req->cqe->result.u16 = cpu_to_le16(ctrl->cntlid);
 
+	if (nvmet_has_auth(ctrl))
+		nvmet_init_auth(ctrl, req);
 out:
 	kfree(d);
 complete:
@@ -267,6 +290,9 @@  static void nvmet_execute_io_connect(struct nvmet_req *req)
 	}
 
 	pr_debug("adding queue %d to ctrl %d.\n", qid, ctrl->cntlid);
+	req->cqe->result.u16 = cpu_to_le16(ctrl->cntlid);
+	if (nvmet_has_auth(ctrl))
+		nvmet_init_auth(ctrl, req);
 
 out:
 	kfree(d);
diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h
index 06dd3d537f07..ef8815e137d7 100644
--- a/drivers/nvme/target/nvmet.h
+++ b/drivers/nvme/target/nvmet.h
@@ -108,6 +108,20 @@  struct nvmet_sq {
 	u16			size;
 	u32			sqhd;
 	bool			sqhd_disabled;
+#ifdef CONFIG_NVME_TARGET_AUTH
+	bool			authenticated;
+	u16			dhchap_tid;
+	u16			dhchap_status;
+	int			dhchap_step;
+	u8			dhchap_hash_id;
+	u8			dhchap_hash_len;
+	u8			*dhchap_c1;
+	u8			*dhchap_c2;
+	u32			dhchap_s1;
+	u32			dhchap_s2;
+	u8			*dhchap_skey;
+	int			dhchap_skey_len;
+#endif
 	struct completion	free_done;
 	struct completion	confirm_done;
 };
@@ -209,6 +223,15 @@  struct nvmet_ctrl {
 	u64			err_counter;
 	struct nvme_error_slot	slots[NVMET_ERROR_LOG_SLOTS];
 	bool			pi_support;
+#ifdef CONFIG_NVME_TARGET_AUTH
+	u32			dhchap_seqnum;
+	u8			*dhchap_key;
+	size_t			dhchap_key_len;
+	struct crypto_shash	*shash_tfm;
+	struct crypto_kpp	*dh_tfm;
+	u32			dh_gid;
+	u32			dh_keysize;
+#endif
 };
 
 struct nvmet_subsys {
@@ -270,6 +293,10 @@  static inline struct nvmet_subsys *namespaces_to_subsys(
 
 struct nvmet_host {
 	struct config_group	group;
+	u8			*dhchap_secret;
+	u8			dhchap_key_hash;
+	u8			dhchap_hash_id;
+	u8			dhchap_dhgroup_id;
 };
 
 static inline struct nvmet_host *to_host(struct config_item *item)
@@ -659,4 +686,48 @@  static inline void nvmet_req_bio_put(struct nvmet_req *req, struct bio *bio)
 		bio_put(bio);
 }
 
+#ifdef CONFIG_NVME_TARGET_AUTH
+void nvmet_execute_auth_send(struct nvmet_req *req);
+void nvmet_execute_auth_receive(struct nvmet_req *req);
+int nvmet_auth_set_host_key(struct nvmet_host *host, const char *secret);
+int nvmet_auth_set_host_hash(struct nvmet_host *host, const char *hash);
+int nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req);
+void nvmet_init_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req);
+void nvmet_reset_auth(struct nvmet_ctrl *ctrl);
+void nvmet_auth_sq_free(struct nvmet_sq *sq);
+int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, int dhgroup_id);
+bool nvmet_check_auth_status(struct nvmet_req *req);
+int nvmet_auth_host_hash(struct nvmet_req *req, u8 *response,
+			 unsigned int hash_len);
+int nvmet_auth_ctrl_hash(struct nvmet_req *req, u8 *response,
+			 unsigned int hash_len);
+static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl)
+{
+	return ctrl->shash_tfm != NULL;
+}
+int nvmet_auth_ctrl_exponential(struct nvmet_req *req,
+				u8 *buf, int buf_size);
+int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,
+			    u8 *buf, int buf_size);
+#else
+static inline int nvmet_setup_auth(struct nvmet_ctrl *ctrl,
+				   struct nvmet_req *req)
+{
+	return 0;
+}
+static inline void nvmet_init_auth(struct nvmet_ctrl *ctrl,
+				   struct nvmet_req *req) {};
+static inline void nvmet_reset_auth(struct nvmet_ctrl *ctrl) {};
+static inline void nvmet_auth_sq_free(struct nvmet_sq *sq) {};
+static inline bool nvmet_check_auth_status(struct nvmet_req *req)
+{
+	return true;
+}
+static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl)
+{
+	return false;
+}
+static inline const char *nvmet_dhchap_dhgroup_name(int dhgid) { return NULL; }
+#endif
+
 #endif /* _NVMET_H */