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