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

Hannes Reinecke hare at suse.de
Wed Sep 29 05:26:09 PDT 2021


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

> In other words, if the nvmet host is set to use hmac(sha512) then
> authentication fails, regardless of the key.
> 
Nope. The hash algorithm is figured out the 'negotiation' step,
so the host _should_ have set the correct hash length.
But as this is networking you should be validating each field in the
received packet to ensure it matches what you expect.

> 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...

Most definitely. Thank god you can test it setting up a nvmet
configuration using localhost 127.0.0.1, so it should be relatively easy
to implement.

Oh, wait. Did I just volunteer?
Bummer.

Cheers,

Hannes
-- 
Dr. Hannes Reinecke		           Kernel Storage Architect
hare at 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



More information about the Linux-nvme mailing list