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