On 11/21/21 23:47, Hannes Reinecke wrote: > Implement NVMe-oF In-Band authentication according to NVMe TPAR 8006. > This patch adds two new fabric options 'dhchap_secret' to specify the > pre-shared key (in ASCII respresentation according to NVMe 2.0 section > 8.13.5.8 'Secret representation') and 'dhchap_ctrl_secret' to specify > the pre-shared controller key for bi-directional authentication of both > the host and the controller. > Re-authentication can be triggered by writing the PSK into the new > controller sysfs attribute 'dhchap_secret' or 'dhchap_ctrl_secret'. > > Signed-off-by: Hannes Reinecke <hare@xxxxxxx> > --- > drivers/nvme/host/Kconfig | 11 + > drivers/nvme/host/Makefile | 1 + > drivers/nvme/host/auth.c | 1139 +++++++++++++++++++++++++++++++++++ > drivers/nvme/host/auth.h | 26 + > drivers/nvme/host/core.c | 141 ++++- > drivers/nvme/host/fabrics.c | 79 ++- > drivers/nvme/host/fabrics.h | 7 + > drivers/nvme/host/nvme.h | 35 ++ > drivers/nvme/host/tcp.c | 1 + > drivers/nvme/host/trace.c | 32 + > 10 files changed, 1465 insertions(+), 7 deletions(-) > create mode 100644 drivers/nvme/host/auth.c > create mode 100644 drivers/nvme/host/auth.h > > diff --git a/drivers/nvme/host/Kconfig b/drivers/nvme/host/Kconfig > index dc0450ca23a3..49269c581ec4 100644 > --- a/drivers/nvme/host/Kconfig > +++ b/drivers/nvme/host/Kconfig > @@ -83,3 +83,14 @@ config NVME_TCP > from https://github.com/linux-nvme/nvme-cli. > > If unsure, say N. > + > +config NVME_AUTH > + bool "NVM Express over Fabrics In-Band Authentication" > + depends on NVME_CORE > + select CRYPTO_HMAC > + select CRYPTO_SHA256 > + select CRYPTO_SHA512 > + help > + This provides support for NVMe over Fabrics In-Band Authentication. > + > + If unsure, say N. > diff --git a/drivers/nvme/host/Makefile b/drivers/nvme/host/Makefile > index dfaacd472e5d..4bae2a4a8d8c 100644 > --- a/drivers/nvme/host/Makefile > +++ b/drivers/nvme/host/Makefile > @@ -15,6 +15,7 @@ nvme-core-$(CONFIG_NVME_MULTIPATH) += multipath.o > nvme-core-$(CONFIG_BLK_DEV_ZONED) += zns.o > nvme-core-$(CONFIG_FAULT_INJECTION_DEBUG_FS) += fault_inject.o > nvme-core-$(CONFIG_NVME_HWMON) += hwmon.o > +nvme-core-$(CONFIG_NVME_AUTH) += auth.o > > nvme-y += pci.o > > diff --git a/drivers/nvme/host/auth.c b/drivers/nvme/host/auth.c > new file mode 100644 > index 000000000000..f74ab4d8b990 > --- /dev/null > +++ b/drivers/nvme/host/auth.c > @@ -0,0 +1,1139 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (c) 2020 Hannes Reinecke, SUSE Linux > + */ > + > +#include <linux/crc32.h> > +#include <linux/base64.h> > +#include <asm/unaligned.h> > +#include <crypto/hash.h> > +#include <crypto/dh.h> > +#include <crypto/ffdhe.h> > +#include "nvme.h" > +#include "fabrics.h" > +#include "auth.h" > + > +static atomic_t nvme_dhchap_seqnum = ATOMIC_INIT(0); > + > +struct nvme_dhchap_queue_context { > + struct list_head entry; > + struct work_struct auth_work; > + struct nvme_ctrl *ctrl; > + struct crypto_shash *shash_tfm; > + void *buf; > + size_t buf_size; > + int qid; > + int error; > + u32 s1; > + u32 s2; > + u16 transaction; > + u8 status; > + u8 hash_id; > + u8 hash_len; > + u8 dhgroup_id; > + u8 c1[64]; > + u8 c2[64]; > + u8 response[64]; > + u8 *host_response; > +}; > + > +static struct nvme_auth_dhgroup_map { > + int id; > + const char name[16]; > + const char kpp[16]; > + int privkey_size; > + int pubkey_size; > +} dhgroup_map[] = { > + { .id = NVME_AUTH_DHCHAP_DHGROUP_NULL, > + .name = "null", .kpp = "null", > + .privkey_size = 0, .pubkey_size = 0 }, > + { .id = NVME_AUTH_DHCHAP_DHGROUP_2048, > + .name = "ffdhe2048", .kpp = "dh", > + .privkey_size = 256, .pubkey_size = 256 }, > + { .id = NVME_AUTH_DHCHAP_DHGROUP_3072, > + .name = "ffdhe3072", .kpp = "dh", > + .privkey_size = 384, .pubkey_size = 384 }, > + { .id = NVME_AUTH_DHCHAP_DHGROUP_4096, > + .name = "ffdhe4096", .kpp = "dh", > + .privkey_size = 512, .pubkey_size = 512 }, > + { .id = NVME_AUTH_DHCHAP_DHGROUP_6144, > + .name = "ffdhe6144", .kpp = "dh", > + .privkey_size = 768, .pubkey_size = 768 }, > + { .id = NVME_AUTH_DHCHAP_DHGROUP_8192, > + .name = "ffdhe8192", .kpp = "dh", > + .privkey_size = 1024, .pubkey_size = 1024 }, > +}; > + > +const char *nvme_auth_dhgroup_name(int dhgroup_id) > +{ > + int i; > + > + for (i = 0; i < ARRAY_SIZE(dhgroup_map); i++) { > + if (dhgroup_map[i].id == dhgroup_id) > + return dhgroup_map[i].name; > + } > + return NULL; > +} > +EXPORT_SYMBOL_GPL(nvme_auth_dhgroup_name); > + > +int nvme_auth_dhgroup_pubkey_size(int dhgroup_id) > +{ > + int i; > + > + for (i = 0; i < ARRAY_SIZE(dhgroup_map); i++) { > + if (dhgroup_map[i].id == dhgroup_id) > + return dhgroup_map[i].pubkey_size; > + } > + return -1; > +} > +EXPORT_SYMBOL_GPL(nvme_auth_dhgroup_pubkey_size); > + > +int nvme_auth_dhgroup_privkey_size(int dhgroup_id) > +{ > + int i; > + > + for (i = 0; i < ARRAY_SIZE(dhgroup_map); i++) { > + if (dhgroup_map[i].id == dhgroup_id) > + return dhgroup_map[i].privkey_size; > + } > + return -1; > +} > +EXPORT_SYMBOL_GPL(nvme_auth_dhgroup_privkey_size); > + > +const char *nvme_auth_dhgroup_kpp(int dhgroup_id) > +{ > + int i; > + > + for (i = 0; i < ARRAY_SIZE(dhgroup_map); i++) { > + if (dhgroup_map[i].id == dhgroup_id) > + return dhgroup_map[i].kpp; > + } > + return NULL; > +} > +EXPORT_SYMBOL_GPL(nvme_auth_dhgroup_kpp); > + > +int nvme_auth_dhgroup_id(const char *dhgroup_name) > +{ > + int i; > + nit: for above declaration s/int/size_t or unsigned int ? for all helpers ... > + for (i = 0; i < ARRAY_SIZE(dhgroup_map); i++) { > + if (!strncmp(dhgroup_map[i].name, dhgroup_name, > + strlen(dhgroup_map[i].name))) > + return dhgroup_map[i].id; > + } > + return -1; > +} > +EXPORT_SYMBOL_GPL(nvme_auth_dhgroup_id); > + > +static struct nvme_dhchap_hash_map { > + int id; > + int len; > + const char hmac[15]; > + const char digest[15]; > +} hash_map[] = { > + {.id = NVME_AUTH_DHCHAP_SHA256, .len = 32, > + .hmac = "hmac(sha256)", .digest = "sha256" }, > + {.id = NVME_AUTH_DHCHAP_SHA384, .len = 48, > + .hmac = "hmac(sha384)", .digest = "sha384" }, > + {.id = NVME_AUTH_DHCHAP_SHA512, .len = 64, > + .hmac = "hmac(sha512)", .digest = "sha512" }, > +}; > + > +const char *nvme_auth_hmac_name(int hmac_id) > +{ > + int i; > + > + for (i = 0; i < ARRAY_SIZE(hash_map); i++) { > + if (hash_map[i].id == hmac_id) > + return hash_map[i].hmac; > + } > + return NULL; > +} > +EXPORT_SYMBOL_GPL(nvme_auth_hmac_name); > + > +const char *nvme_auth_digest_name(int hmac_id) > +{ > + int i; > + > + for (i = 0; i < ARRAY_SIZE(hash_map); i++) { > + if (hash_map[i].id == hmac_id) > + return hash_map[i].digest; > + } > + return NULL; > +} > +EXPORT_SYMBOL_GPL(nvme_auth_digest_name); > + > +int nvme_auth_hmac_id(const char *hmac_name) > +{ > + int i; > + > + for (i = 0; i < ARRAY_SIZE(hash_map); i++) { > + if (!strncmp(hash_map[i].hmac, hmac_name, > + strlen(hash_map[i].hmac))) > + return hash_map[i].id; > + } > + return -1; > +} > +EXPORT_SYMBOL_GPL(nvme_auth_hmac_id); > + > +int nvme_auth_hmac_hash_len(int hmac_id) > +{ > + int i; > + > + for (i = 0; i < ARRAY_SIZE(hash_map); i++) { > + if (hash_map[i].id == hmac_id) > + return hash_map[i].len; > + } > + return 0; > +} > +EXPORT_SYMBOL_GPL(nvme_auth_hmac_hash_len); > + > +unsigned char *nvme_auth_extract_secret(unsigned char *secret, u8 key_hash, > + size_t *out_len) > +{ > + unsigned char *key, *p; > + u32 crc; > + int key_len; > + size_t allocated_len = strlen(secret); > + nit:- reverse tree would nice above > + /* Secret might be affixed with a ':' */ > + p = strrchr(secret, ':'); > + if (p) > + allocated_len = p - secret; > + key = kzalloc(allocated_len, GFP_KERNEL); > + if (!key) > + return ERR_PTR(-ENOMEM); > + > + key_len = base64_decode(secret, allocated_len, key); > + if (key_len < 0) { > + pr_debug("base64 key decoding error %d\n", > + key_len); > + return ERR_PTR(key_len); > + } > + if (key_len != 36 && key_len != 52 && nit:- new line before above if > + key_len != 68) { > + pr_err("Invalid DH-HMAC-CHAP key len %d\n", > + key_len); > + kfree_sensitive(key); > + return ERR_PTR(-EINVAL); > + } > + if (key_hash > 0 && > + (key_len - 4) != nvme_auth_hmac_hash_len(key_hash)) { > + pr_err("Invalid DH-HMAC-CHAP key len %d for %s\n", key_len, > + nvme_auth_hmac_name(key_hash)); > + kfree_sensitive(key); > + return ERR_PTR(-EINVAL); > + } > + > + /* The last four bytes is the CRC in little-endian format */ > + key_len -= 4; > + /* > + * The linux implementation doesn't do pre- and post-increments, > + * so we have to do it manually. > + */ > + crc = ~crc32(~0, key, key_len); > + > + if (get_unaligned_le32(key + key_len) != crc) { > + pr_err("DH-HMAC-CHAP key crc mismatch (key %08x, crc %08x)\n", > + get_unaligned_le32(key + key_len), crc); > + kfree_sensitive(key); > + return ERR_PTR(-EKEYREJECTED); > + } > + *out_len = key_len; > + return key; > +} > +EXPORT_SYMBOL_GPL(nvme_auth_extract_secret); > + > +u8 *nvme_auth_transform_key(u8 *key, size_t key_len, u8 key_hash, char *nqn) > +{ > + const char *hmac_name = nvme_auth_hmac_name(key_hash); > + struct crypto_shash *key_tfm; > + struct shash_desc *shash; > + u8 *transformed_key; > + int ret; > + > + if (key_hash == 0) { > + transformed_key = kmemdup(key, key_len, GFP_KERNEL); > + return transformed_key ? transformed_key : ERR_PTR(-ENOMEM); > + } > + > + if (!key || !key_len) { > + pr_warn("No key specified\n"); > + return ERR_PTR(-ENOKEY); > + } > + if (!hmac_name) { > + pr_warn("Invalid key hash id %d\n", key_hash); > + return ERR_PTR(-EINVAL); > + } > + > + key_tfm = crypto_alloc_shash(hmac_name, 0, 0); > + if (IS_ERR(key_tfm)) > + return (u8 *)key_tfm; > + > + shash = kmalloc(sizeof(struct shash_desc) + > + crypto_shash_descsize(key_tfm), > + GFP_KERNEL); > + if (!shash) { > + ret = -ENOMEM; > + goto out_free_key; > + } > + > + transformed_key = kzalloc(crypto_shash_digestsize(key_tfm), GFP_KERNEL); > + if (!transformed_key) { > + ret = -ENOMEM; > + goto out_free_shash; > + } > + > + shash->tfm = key_tfm; > + ret = crypto_shash_setkey(key_tfm, key, key_len); > + if (ret < 0) > + goto out_free_shash; > + ret = crypto_shash_init(shash); > + if (ret < 0) > + goto out_free_shash; > + ret = crypto_shash_update(shash, nqn, strlen(nqn)); > + if (ret < 0) > + goto out_free_shash; > + ret = crypto_shash_update(shash, "NVMe-over-Fabrics", 17); > + if (ret < 0) > + goto out_free_shash; > + ret = crypto_shash_final(shash, transformed_key); > +out_free_shash: > + kfree(shash); > +out_free_key: > + crypto_free_shash(key_tfm); > + if (ret < 0) { > + kfree_sensitive(transformed_key); > + return ERR_PTR(ret); > + } > + return transformed_key; > +} > +EXPORT_SYMBOL_GPL(nvme_auth_transform_key); > + > +static int nvme_auth_send(struct nvme_ctrl *ctrl, int qid, > + void *data, size_t tl) > +{ > + struct nvme_command cmd = {}; > + blk_mq_req_flags_t flags = qid == NVME_QID_ANY ? > + 0 : BLK_MQ_REQ_NOWAIT | BLK_MQ_REQ_RESERVED; > + struct request_queue *q = qid == NVME_QID_ANY ? > + ctrl->fabrics_q : ctrl->connect_q; > + int ret; > + > + cmd.auth_send.opcode = nvme_fabrics_command; > + cmd.auth_send.fctype = nvme_fabrics_type_auth_send; > + cmd.auth_send.secp = NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER; > + cmd.auth_send.spsp0 = 0x01; > + cmd.auth_send.spsp1 = 0x01; > + cmd.auth_send.tl = cpu_to_le32(tl); > + > + ret = __nvme_submit_sync_cmd(q, &cmd, NULL, data, tl, 0, qid, > + 0, flags); > + if (ret > 0) > + dev_warn(ctrl->device, > + "qid %d auth_send failed with status %d\n", qid, ret); > + else if (ret < 0) > + dev_err(ctrl->device, > + "qid %d auth_send failed with error %d\n", qid, ret); > + return ret; > +} > + > +static int nvme_auth_receive(struct nvme_ctrl *ctrl, int qid, > + void *buf, size_t al) > +{ > + struct nvme_command cmd = {}; > + blk_mq_req_flags_t flags = qid == NVME_QID_ANY ? > + 0 : BLK_MQ_REQ_NOWAIT | BLK_MQ_REQ_RESERVED; > + struct request_queue *q = qid == NVME_QID_ANY ? > + ctrl->fabrics_q : ctrl->connect_q; above code is repeated for flags and q (correct me if I'm wrong), it makes sense to create one liner helpers something like name could be better :- 1. nvme_req_flags_from_qid() 2. nvme_blk_queue_from_qid() > + int ret; > + > + cmd.auth_receive.opcode = nvme_fabrics_command; > + cmd.auth_receive.fctype = nvme_fabrics_type_auth_receive; > + cmd.auth_receive.secp = NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER; > + cmd.auth_receive.spsp0 = 0x01; > + cmd.auth_receive.spsp1 = 0x01; > + cmd.auth_receive.al = cpu_to_le32(al); > + > + ret = __nvme_submit_sync_cmd(q, &cmd, NULL, buf, al, 0, qid, > + 0, flags); > + if (ret > 0) { > + dev_warn(ctrl->device, > + "qid %d auth_recv failed with status %x\n", qid, ret); > + ret = -EIO; > + } else if (ret < 0) { > + dev_err(ctrl->device, > + "qid %d auth_recv failed with error %d\n", qid, ret); > + } > + > + return ret; > +} > +