// SPDX-License-Identifier: GPL-2.0/* * Copyright (c) 2020 Hannes Reinecke, SUSE Linux */ #include <linux/crc32.h>#include <linux/base64.h>#include <linux/prandom.h>#include <linux/unaligned.h>#include <crypto/dh.h>#include <crypto/utils.h>#include "nvme.h"#include "fabrics.h"#include <linux/nvme-auth.h>#include <linux/nvme-keyring.h> #define CHAP_BUF_SIZE 4096static struct kmem_cache *nvme_chap_buf_cache;static mempool_t *nvme_chap_buf_pool; struct nvme_dhchap_queue_context { struct list_head entry; struct work_struct auth_work; struct nvme_ctrl *ctrl; struct crypto_kpp *dh_tfm; struct nvme_dhchap_key *transformed_key; void *buf; int qid; int error; u32 s1; u32 s2; bool bi_directional; bool authenticated; u16 transaction; u8 status; u8 dhgroup_id; u8 hash_id; u8 sc_c; size_t hash_len; u8 c1[NVME_AUTH_MAX_DIGEST_SIZE]; u8 c2[NVME_AUTH_MAX_DIGEST_SIZE]; u8 response[NVME_AUTH_MAX_DIGEST_SIZE]; u8 *ctrl_key; u8 *host_key; u8 *sess_key; int ctrl_key_len; int host_key_len; int sess_key_len;}; static struct workqueue_struct *nvme_auth_wq; static inline int ctrl_max_dhchaps(struct nvme_ctrl *ctrl){ return ctrl->opts->nr_io_queues + ctrl->opts->nr_write_queues + ctrl->opts->nr_poll_queues + 1;} static int nvme_auth_submit(struct nvme_ctrl *ctrl, int qid, void *data, size_t data_len, bool auth_send){ struct nvme_command cmd = {}; nvme_submit_flags_t flags = NVME_SUBMIT_RETRY; struct request_queue *q = ctrl->fabrics_q; int ret; if (qid != 0) { flags |= NVME_SUBMIT_NOWAIT | NVME_SUBMIT_RESERVED; q = ctrl->connect_q; } cmd.auth_common.opcode = nvme_fabrics_command; cmd.auth_common.secp = NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER; cmd.auth_common.spsp0 = 0x01; cmd.auth_common.spsp1 = 0x01; if (auth_send) { cmd.auth_send.fctype = nvme_fabrics_type_auth_send; cmd.auth_send.tl = cpu_to_le32(data_len); } else { cmd.auth_receive.fctype = nvme_fabrics_type_auth_receive; cmd.auth_receive.al = cpu_to_le32(data_len); } ret = __nvme_submit_sync_cmd(q, &cmd, NULL, data, data_len, qid == 0 ? NVME_QID_ANY : qid, 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_validate(struct nvme_ctrl *ctrl, int qid, struct nvmf_auth_dhchap_failure_data *data, u16 transaction, u8 expected_msg){ dev_dbg(ctrl->device, "%s: qid %d auth_type %d auth_id %x\n", __func__, qid, data->auth_type, data->auth_id); if (data->auth_type == NVME_AUTH_COMMON_MESSAGES && data->auth_id == NVME_AUTH_DHCHAP_MESSAGE_FAILURE1) { return data->rescode_exp; } if (data->auth_type != NVME_AUTH_DHCHAP_MESSAGES || data->auth_id != expected_msg) { dev_warn(ctrl->device, "qid %d invalid message %02x/%02x\n", qid, data->auth_type, data->auth_id); return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_MESSAGE; } if (le16_to_cpu(data->t_id) != transaction) { dev_warn(ctrl->device, "qid %d invalid transaction ID %d\n", qid, le16_to_cpu(data->t_id)); return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_MESSAGE; } return 0;} static int nvme_auth_set_dhchap_negotiate_data(struct nvme_ctrl *ctrl, struct nvme_dhchap_queue_context *chap){ struct nvmf_auth_dhchap_negotiate_data *data = chap->buf; size_t size = sizeof(*data) + sizeof(union nvmf_auth_protocol); u8 dh_list_offset = NVME_AUTH_DHCHAP_MAX_DH_IDS; u8 *idlist = data->auth_protocol[0].dhchap.idlist; if (size > CHAP_BUF_SIZE) { chap->status = NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD; return -EINVAL; } memset((u8 *)chap->buf, 0, size); data->auth_type = NVME_AUTH_COMMON_MESSAGES; data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE; data->t_id = cpu_to_le16(chap->transaction); if (ctrl->opts->concat && chap->qid == 0) { if (ctrl->opts->tls_key) data->sc_c = NVME_AUTH_SECP_REPLACETLSPSK; else data->sc_c = NVME_AUTH_SECP_NEWTLSPSK; } else data->sc_c = NVME_AUTH_SECP_NOSC; chap->sc_c = data->sc_c; data->napd = 1; data->auth_protocol[0].dhchap.authid = NVME_AUTH_DHCHAP_AUTH_ID; data->auth_protocol[0].dhchap.halen = 3; idlist[0] = NVME_AUTH_HASH_SHA256; idlist[1] = NVME_AUTH_HASH_SHA384; idlist[2] = NVME_AUTH_HASH_SHA512; if (chap->sc_c == NVME_AUTH_SECP_NOSC) idlist[dh_list_offset++] = NVME_AUTH_DHGROUP_NULL; idlist[dh_list_offset++] = NVME_AUTH_DHGROUP_2048; idlist[dh_list_offset++] = NVME_AUTH_DHGROUP_3072; idlist[dh_list_offset++] = NVME_AUTH_DHGROUP_4096; idlist[dh_list_offset++] = NVME_AUTH_DHGROUP_6144; idlist[dh_list_offset++] = NVME_AUTH_DHGROUP_8192; data->auth_protocol[0].dhchap.dhlen = dh_list_offset - NVME_AUTH_DHCHAP_MAX_DH_IDS; return size;} static int nvme_auth_process_dhchap_challenge(struct nvme_ctrl *ctrl, struct nvme_dhchap_queue_context *chap){ struct nvmf_auth_dhchap_challenge_data *data = chap->buf; u16 dhvlen = le16_to_cpu(data->dhvlen); size_t size = sizeof(*data) + data->hl + dhvlen; const char *gid_name = nvme_auth_dhgroup_name(data->dhgid); const char *hmac_name, *kpp_name; if (size > CHAP_BUF_SIZE) { chap->status = NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD; return -EINVAL; } hmac_name = nvme_auth_hmac_name(data->hashid); if (!hmac_name) { dev_warn(ctrl->device, "qid %d: invalid HASH ID %d\n", chap->qid, data->hashid); chap->status = NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE; return -EPROTO; } if (chap->hash_id == data->hashid && chap->hash_len == data->hl) { dev_dbg(ctrl->device, "qid %d: reuse existing hash %s\n", chap->qid, hmac_name); goto select_kpp; } if (nvme_auth_hmac_hash_len(data->hashid) != data->hl) { dev_warn(ctrl->device, "qid %d: invalid hash length %d\n", chap->qid, data->hl); chap->status = NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE; return -EPROTO; } chap->hash_id = data->hashid; chap->hash_len = data->hl; dev_dbg(ctrl->device, "qid %d: selected hash %s\n", chap->qid, hmac_name); select_kpp: kpp_name = nvme_auth_dhgroup_kpp(data->dhgid); if (!kpp_name) { dev_warn(ctrl->device, "qid %d: invalid DH group id %d\n", chap->qid, data->dhgid); chap->status = NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE; /* Leave previous dh_tfm intact */ return -EPROTO; } if (chap->dhgroup_id == data->dhgid && (data->dhgid == NVME_AUTH_DHGROUP_NULL || chap->dh_tfm)) { dev_dbg(ctrl->device, "qid %d: reuse existing DH group %s\n", chap->qid, gid_name); goto skip_kpp; } /* Reset dh_tfm if it can't be reused */ if (chap->dh_tfm) { crypto_free_kpp(chap->dh_tfm); chap->dh_tfm = NULL; } if (data->dhgid != NVME_AUTH_DHGROUP_NULL) { if (dhvlen == 0) { dev_warn(ctrl->device, "qid %d: empty DH value\n", chap->qid); chap->status = NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE; return -EPROTO; } chap->dh_tfm = crypto_alloc_kpp(kpp_name, 0, 0); if (IS_ERR(chap->dh_tfm)) { int ret = PTR_ERR(chap->dh_tfm); dev_warn(ctrl->device, "qid %d: error %d initializing DH group %s\n", chap->qid, ret, gid_name); chap->status = NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE; chap->dh_tfm = NULL; return ret; } dev_dbg(ctrl->device, "qid %d: selected DH group %s\n", chap->qid, gid_name); } else if (dhvlen != 0) { dev_warn(ctrl->device, "qid %d: invalid DH value for NULL DH\n", chap->qid); chap->status = NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD; return -EPROTO; } chap->dhgroup_id = data->dhgid; skip_kpp: chap->s1 = le32_to_cpu(data->seqnum); memcpy(chap->c1, data->cval, chap->hash_len); if (dhvlen) { chap->ctrl_key = kmalloc(dhvlen, GFP_KERNEL); if (!chap->ctrl_key) { chap->status = NVME_AUTH_DHCHAP_FAILURE_FAILED; return -ENOMEM; } chap->ctrl_key_len = dhvlen; memcpy(chap->ctrl_key, data->cval + chap->hash_len, dhvlen); dev_dbg(ctrl->device, "ctrl public key %*ph\n", (int)chap->ctrl_key_len, chap->ctrl_key); } return 0;} static int nvme_auth_set_dhchap_reply_data(struct nvme_ctrl *ctrl, struct nvme_dhchap_queue_context *chap){ struct nvmf_auth_dhchap_reply_data *data = chap->buf; size_t size = sizeof(*data); size += 2 * chap->hash_len; if (chap->host_key_len) size += chap->host_key_len; if (size > CHAP_BUF_SIZE) { chap->status = NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD; return -EINVAL; } memset(chap->buf, 0, size); data->auth_type = NVME_AUTH_DHCHAP_MESSAGES; data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_REPLY; data->t_id = cpu_to_le16(chap->transaction); data->hl = chap->hash_len; data->dhvlen = cpu_to_le16(chap->host_key_len); memcpy(data->rval, chap->response, chap->hash_len); if (ctrl->ctrl_key) chap->bi_directional = true; if (ctrl->ctrl_key || ctrl->opts->concat) { get_random_bytes(chap->c2, chap->hash_len); data->cvalid = 1; memcpy(data->rval + chap->hash_len, chap->c2, chap->hash_len); dev_dbg(ctrl->device, "%s: qid %d ctrl challenge %*ph\n", __func__, chap->qid, (int)chap->hash_len, chap->c2); } else { memset(chap->c2, 0, chap->hash_len); } if (ctrl->opts->concat) { chap->s2 = 0; chap->bi_directional = false; } else chap->s2 = nvme_auth_get_seqnum(); data->seqnum = cpu_to_le32(chap->s2); if (chap->host_key_len) { dev_dbg(ctrl->device, "%s: qid %d host public key %*ph\n", __func__, chap->qid, chap->host_key_len, chap->host_key); memcpy(data->rval + 2 * chap->hash_len, chap->host_key, chap->host_key_len); } return size;} static int nvme_auth_process_dhchap_success1(struct nvme_ctrl *ctrl, struct nvme_dhchap_queue_context *chap){ struct nvmf_auth_dhchap_success1_data *data = chap->buf; size_t size = sizeof(*data) + chap->hash_len; if (size > CHAP_BUF_SIZE) { chap->status = NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD; return -EINVAL; } if (data->hl != chap->hash_len) { dev_warn(ctrl->device, "qid %d: invalid hash length %u\n", chap->qid, data->hl); chap->status = NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE; return -EPROTO; } /* Just print out information for the admin queue */ if (chap->qid == 0) dev_info(ctrl->device, "qid 0: authenticated with hash %s dhgroup %s\n", nvme_auth_hmac_name(chap->hash_id), nvme_auth_dhgroup_name(chap->dhgroup_id)); if (!data->rvalid) return 0; /* Validate controller response */ if (crypto_memneq(chap->response, data->rval, data->hl)) { dev_dbg(ctrl->device, "%s: qid %d ctrl response %*ph\n", __func__, chap->qid, (int)chap->hash_len, data->rval); dev_dbg(ctrl->device, "%s: qid %d host response %*ph\n", __func__, chap->qid, (int)chap->hash_len, chap->response); dev_warn(ctrl->device, "qid %d: controller authentication failed\n", chap->qid); chap->status = NVME_AUTH_DHCHAP_FAILURE_FAILED; return -ECONNREFUSED; } /* Just print out information for the admin queue */ if (chap->qid == 0) dev_info(ctrl->device, "qid 0: controller authenticated\n"); return 0;} static int nvme_auth_set_dhchap_success2_data(struct nvme_ctrl *ctrl, struct nvme_dhchap_queue_context *chap){ struct nvmf_auth_dhchap_success2_data *data = chap->buf; size_t size = sizeof(*data); memset(chap->buf, 0, size); data->auth_type = NVME_AUTH_DHCHAP_MESSAGES; data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2; data->t_id = cpu_to_le16(chap->transaction); return size;} static int nvme_auth_set_dhchap_failure2_data(struct nvme_ctrl *ctrl, struct nvme_dhchap_queue_context *chap){ struct nvmf_auth_dhchap_failure_data *data = chap->buf; size_t size = sizeof(*data); memset(chap->buf, 0, size); data->auth_type = NVME_AUTH_COMMON_MESSAGES; data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_FAILURE2; data->t_id = cpu_to_le16(chap->transaction); data->rescode = NVME_AUTH_DHCHAP_FAILURE_REASON_FAILED; data->rescode_exp = chap->status; return size;} static int nvme_auth_dhchap_setup_host_response(struct nvme_ctrl *ctrl, struct nvme_dhchap_queue_context *chap){ struct nvme_auth_hmac_ctx hmac; u8 buf[4], *challenge = chap->c1; int ret; dev_dbg(ctrl->device, "%s: qid %d host response seq %u transaction %d\n", __func__, chap->qid, chap->s1, chap->transaction); if (!chap->transformed_key) { chap->transformed_key = nvme_auth_transform_key(ctrl->host_key, ctrl->opts->host->nqn); if (IS_ERR(chap->transformed_key)) { ret = PTR_ERR(chap->transformed_key); chap->transformed_key = NULL; return ret; } } else { dev_dbg(ctrl->device, "%s: qid %d re-using host response\n", __func__, chap->qid); } ret = nvme_auth_hmac_init(&hmac, chap->hash_id, chap->transformed_key->key, chap->transformed_key->len); if (ret) goto out; if (chap->dh_tfm) { challenge = kmalloc(chap->hash_len, GFP_KERNEL); if (!challenge) { ret = -ENOMEM; goto out; } ret = nvme_auth_augmented_challenge(chap->hash_id, chap->sess_key, chap->sess_key_len, chap->c1, challenge, chap->hash_len); if (ret) goto out; } nvme_auth_hmac_update(&hmac, challenge, chap->hash_len); put_unaligned_le32(chap->s1, buf); nvme_auth_hmac_update(&hmac, buf, 4); put_unaligned_le16(chap->transaction, buf); nvme_auth_hmac_update(&hmac, buf, 2); *buf = chap->sc_c; nvme_auth_hmac_update(&hmac, buf, 1); nvme_auth_hmac_update(&hmac, "HostHost", 8); nvme_auth_hmac_update(&hmac, ctrl->opts->host->nqn, strlen(ctrl->opts->host->nqn)); memset(buf, 0, sizeof(buf)); nvme_auth_hmac_update(&hmac, buf, 1); nvme_auth_hmac_update(&hmac, ctrl->opts->subsysnqn, strlen(ctrl->opts->subsysnqn)); nvme_auth_hmac_final(&hmac, chap->response); ret = 0;out: if (challenge != chap->c1) kfree(challenge); memzero_explicit(&hmac, sizeof(hmac)); return ret;} static int nvme_auth_dhchap_setup_ctrl_response(struct nvme_ctrl *ctrl, struct nvme_dhchap_queue_context *chap){ struct nvme_auth_hmac_ctx hmac; struct nvme_dhchap_key *transformed_key; u8 buf[4], *challenge = chap->c2; int ret; transformed_key = nvme_auth_transform_key(ctrl->ctrl_key, ctrl->opts->subsysnqn); if (IS_ERR(transformed_key)) { ret = PTR_ERR(transformed_key); return ret; } ret = nvme_auth_hmac_init(&hmac, chap->hash_id, transformed_key->key,Lines 1–500 of 1117. Load more
// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2020 Hannes Reinecke, SUSE Linux */ #include <linux/crc32.h> #include <linux/base64.h> #include <linux/prandom.h> #include <linux/unaligned.h> #include <crypto/dh.h> #include <crypto/utils.h> #include "nvme.h" #include "fabrics.h" #include <linux/nvme-auth.h> #include <linux/nvme-keyring.h> #define CHAP_BUF_SIZE 4096 static struct kmem_cache *nvme_chap_buf_cache; static mempool_t *nvme_chap_buf_pool; struct nvme_dhchap_queue_context { struct list_head entry; struct work_struct auth_work; struct nvme_ctrl *ctrl; struct crypto_kpp *dh_tfm; struct nvme_dhchap_key *transformed_key; void *buf; int qid; int error; u32 s1; u32 s2; bool bi_directional; bool authenticated; u16 transaction; u8 status; u8 dhgroup_id; u8 hash_id; u8 sc_c; size_t hash_len; u8 c1[NVME_AUTH_MAX_DIGEST_SIZE]; u8 c2[NVME_AUTH_MAX_DIGEST_SIZE]; u8 response[NVME_AUTH_MAX_DIGEST_SIZE]; u8 *ctrl_key; u8 *host_key; u8 *sess_key; int ctrl_key_len; int host_key_len; int sess_key_len; }; static struct workqueue_struct *nvme_auth_wq; static inline int ctrl_max_dhchaps(struct nvme_ctrl *ctrl) { return ctrl->opts->nr_io_queues + ctrl->opts->nr_write_queues + ctrl->opts->nr_poll_queues + 1; } static int nvme_auth_submit(struct nvme_ctrl *ctrl, int qid, void *data, size_t data_len, bool auth_send) { struct nvme_command cmd = {}; nvme_submit_flags_t flags = NVME_SUBMIT_RETRY; struct request_queue *q = ctrl->fabrics_q; int ret; if (qid != 0) { flags |= NVME_SUBMIT_NOWAIT | NVME_SUBMIT_RESERVED; q = ctrl->connect_q; } cmd.auth_common.opcode = nvme_fabrics_command; cmd.auth_common.secp = NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER; cmd.auth_common.spsp0 = 0x01; cmd.auth_common.spsp1 = 0x01; if (auth_send) { cmd.auth_send.fctype = nvme_fabrics_type_auth_send; cmd.auth_send.tl = cpu_to_le32(data_len); } else { cmd.auth_receive.fctype = nvme_fabrics_type_auth_receive; cmd.auth_receive.al = cpu_to_le32(data_len); } ret = __nvme_submit_sync_cmd(q, &cmd, NULL, data, data_len, qid == 0 ? NVME_QID_ANY : qid, 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_validate(struct nvme_ctrl *ctrl, int qid, struct nvmf_auth_dhchap_failure_data *data, u16 transaction, u8 expected_msg) { dev_dbg(ctrl->device, "%s: qid %d auth_type %d auth_id %x\n", __func__, qid, data->auth_type, data->auth_id); if (data->auth_type == NVME_AUTH_COMMON_MESSAGES && data->auth_id == NVME_AUTH_DHCHAP_MESSAGE_FAILURE1) { return data->rescode_exp; } if (data->auth_type != NVME_AUTH_DHCHAP_MESSAGES || data->auth_id != expected_msg) { dev_warn(ctrl->device, "qid %d invalid message %02x/%02x\n", qid, data->auth_type, data->auth_id); return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_MESSAGE; } if (le16_to_cpu(data->t_id) != transaction) { dev_warn(ctrl->device, "qid %d invalid transaction ID %d\n", qid, le16_to_cpu(data->t_id)); return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_MESSAGE; } return 0; } static int nvme_auth_set_dhchap_negotiate_data(struct nvme_ctrl *ctrl, struct nvme_dhchap_queue_context *chap) { struct nvmf_auth_dhchap_negotiate_data *data = chap->buf; size_t size = sizeof(*data) + sizeof(union nvmf_auth_protocol); u8 dh_list_offset = NVME_AUTH_DHCHAP_MAX_DH_IDS; u8 *idlist = data->auth_protocol[0].dhchap.idlist; if (size > CHAP_BUF_SIZE) { chap->status = NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD; return -EINVAL; } memset((u8 *)chap->buf, 0, size); data->auth_type = NVME_AUTH_COMMON_MESSAGES; data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE; data->t_id = cpu_to_le16(chap->transaction); if (ctrl->opts->concat && chap->qid == 0) { if (ctrl->opts->tls_key) data->sc_c = NVME_AUTH_SECP_REPLACETLSPSK; else data->sc_c = NVME_AUTH_SECP_NEWTLSPSK; } else data->sc_c = NVME_AUTH_SECP_NOSC; chap->sc_c = data->sc_c; data->napd = 1; data->auth_protocol[0].dhchap.authid = NVME_AUTH_DHCHAP_AUTH_ID; data->auth_protocol[0].dhchap.halen = 3; idlist[0] = NVME_AUTH_HASH_SHA256; idlist[1] = NVME_AUTH_HASH_SHA384; idlist[2] = NVME_AUTH_HASH_SHA512; if (chap->sc_c == NVME_AUTH_SECP_NOSC) idlist[dh_list_offset++] = NVME_AUTH_DHGROUP_NULL; idlist[dh_list_offset++] = NVME_AUTH_DHGROUP_2048; idlist[dh_list_offset++] = NVME_AUTH_DHGROUP_3072; idlist[dh_list_offset++] = NVME_AUTH_DHGROUP_4096; idlist[dh_list_offset++] = NVME_AUTH_DHGROUP_6144; idlist[dh_list_offset++] = NVME_AUTH_DHGROUP_8192; data->auth_protocol[0].dhchap.dhlen = dh_list_offset - NVME_AUTH_DHCHAP_MAX_DH_IDS; return size; } static int nvme_auth_process_dhchap_challenge(struct nvme_ctrl *ctrl, struct nvme_dhchap_queue_context *chap) { struct nvmf_auth_dhchap_challenge_data *data = chap->buf; u16 dhvlen = le16_to_cpu(data->dhvlen); size_t size = sizeof(*data) + data->hl + dhvlen; const char *gid_name = nvme_auth_dhgroup_name(data->dhgid); const char *hmac_name, *kpp_name; if (size > CHAP_BUF_SIZE) { chap->status = NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD; return -EINVAL; } hmac_name = nvme_auth_hmac_name(data->hashid); if (!hmac_name) { dev_warn(ctrl->device, "qid %d: invalid HASH ID %d\n", chap->qid, data->hashid); chap->status = NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE; return -EPROTO; } if (chap->hash_id == data->hashid && chap->hash_len == data->hl) { dev_dbg(ctrl->device, "qid %d: reuse existing hash %s\n", chap->qid, hmac_name); goto select_kpp; } if (nvme_auth_hmac_hash_len(data->hashid) != data->hl) { dev_warn(ctrl->device, "qid %d: invalid hash length %d\n", chap->qid, data->hl); chap->status = NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE; return -EPROTO; } chap->hash_id = data->hashid; chap->hash_len = data->hl; dev_dbg(ctrl->device, "qid %d: selected hash %s\n", chap->qid, hmac_name); select_kpp: kpp_name = nvme_auth_dhgroup_kpp(data->dhgid); if (!kpp_name) { dev_warn(ctrl->device, "qid %d: invalid DH group id %d\n", chap->qid, data->dhgid); chap->status = NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE; /* Leave previous dh_tfm intact */ return -EPROTO; } if (chap->dhgroup_id == data->dhgid && (data->dhgid == NVME_AUTH_DHGROUP_NULL || chap->dh_tfm)) { dev_dbg(ctrl->device, "qid %d: reuse existing DH group %s\n", chap->qid, gid_name); goto skip_kpp; } /* Reset dh_tfm if it can't be reused */ if (chap->dh_tfm) { crypto_free_kpp(chap->dh_tfm); chap->dh_tfm = NULL; } if (data->dhgid != NVME_AUTH_DHGROUP_NULL) { if (dhvlen == 0) { dev_warn(ctrl->device, "qid %d: empty DH value\n", chap->qid); chap->status = NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE; return -EPROTO; } chap->dh_tfm = crypto_alloc_kpp(kpp_name, 0, 0); if (IS_ERR(chap->dh_tfm)) { int ret = PTR_ERR(chap->dh_tfm); dev_warn(ctrl->device, "qid %d: error %d initializing DH group %s\n", chap->qid, ret, gid_name); chap->status = NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE; chap->dh_tfm = NULL; return ret; } dev_dbg(ctrl->device, "qid %d: selected DH group %s\n", chap->qid, gid_name); } else if (dhvlen != 0) { dev_warn(ctrl->device, "qid %d: invalid DH value for NULL DH\n", chap->qid); chap->status = NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD; return -EPROTO; } chap->dhgroup_id = data->dhgid; skip_kpp: chap->s1 = le32_to_cpu(data->seqnum); memcpy(chap->c1, data->cval, chap->hash_len); if (dhvlen) { chap->ctrl_key = kmalloc(dhvlen, GFP_KERNEL); if (!chap->ctrl_key) { chap->status = NVME_AUTH_DHCHAP_FAILURE_FAILED; return -ENOMEM; } chap->ctrl_key_len = dhvlen; memcpy(chap->ctrl_key, data->cval + chap->hash_len, dhvlen); dev_dbg(ctrl->device, "ctrl public key %*ph\n", (int)chap->ctrl_key_len, chap->ctrl_key); } return 0; } static int nvme_auth_set_dhchap_reply_data(struct nvme_ctrl *ctrl, struct nvme_dhchap_queue_context *chap) { struct nvmf_auth_dhchap_reply_data *data = chap->buf; size_t size = sizeof(*data); size += 2 * chap->hash_len; if (chap->host_key_len) size += chap->host_key_len; if (size > CHAP_BUF_SIZE) { chap->status = NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD; return -EINVAL; } memset(chap->buf, 0, size); data->auth_type = NVME_AUTH_DHCHAP_MESSAGES; data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_REPLY; data->t_id = cpu_to_le16(chap->transaction); data->hl = chap->hash_len; data->dhvlen = cpu_to_le16(chap->host_key_len); memcpy(data->rval, chap->response, chap->hash_len); if (ctrl->ctrl_key) chap->bi_directional = true; if (ctrl->ctrl_key || ctrl->opts->concat) { get_random_bytes(chap->c2, chap->hash_len); data->cvalid = 1; memcpy(data->rval + chap->hash_len, chap->c2, chap->hash_len); dev_dbg(ctrl->device, "%s: qid %d ctrl challenge %*ph\n", __func__, chap->qid, (int)chap->hash_len, chap->c2); } else { memset(chap->c2, 0, chap->hash_len); } if (ctrl->opts->concat) { chap->s2 = 0; chap->bi_directional = false; } else chap->s2 = nvme_auth_get_seqnum(); data->seqnum = cpu_to_le32(chap->s2); if (chap->host_key_len) { dev_dbg(ctrl->device, "%s: qid %d host public key %*ph\n", __func__, chap->qid, chap->host_key_len, chap->host_key); memcpy(data->rval + 2 * chap->hash_len, chap->host_key, chap->host_key_len); } return size; } static int nvme_auth_process_dhchap_success1(struct nvme_ctrl *ctrl, struct nvme_dhchap_queue_context *chap) { struct nvmf_auth_dhchap_success1_data *data = chap->buf; size_t size = sizeof(*data) + chap->hash_len; if (size > CHAP_BUF_SIZE) { chap->status = NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD; return -EINVAL; } if (data->hl != chap->hash_len) { dev_warn(ctrl->device, "qid %d: invalid hash length %u\n", chap->qid, data->hl); chap->status = NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE; return -EPROTO; } /* Just print out information for the admin queue */ if (chap->qid == 0) dev_info(ctrl->device, "qid 0: authenticated with hash %s dhgroup %s\n", nvme_auth_hmac_name(chap->hash_id), nvme_auth_dhgroup_name(chap->dhgroup_id)); if (!data->rvalid) return 0; /* Validate controller response */ if (crypto_memneq(chap->response, data->rval, data->hl)) { dev_dbg(ctrl->device, "%s: qid %d ctrl response %*ph\n", __func__, chap->qid, (int)chap->hash_len, data->rval); dev_dbg(ctrl->device, "%s: qid %d host response %*ph\n", __func__, chap->qid, (int)chap->hash_len, chap->response); dev_warn(ctrl->device, "qid %d: controller authentication failed\n", chap->qid); chap->status = NVME_AUTH_DHCHAP_FAILURE_FAILED; return -ECONNREFUSED; } /* Just print out information for the admin queue */ if (chap->qid == 0) dev_info(ctrl->device, "qid 0: controller authenticated\n"); return 0; } static int nvme_auth_set_dhchap_success2_data(struct nvme_ctrl *ctrl, struct nvme_dhchap_queue_context *chap) { struct nvmf_auth_dhchap_success2_data *data = chap->buf; size_t size = sizeof(*data); memset(chap->buf, 0, size); data->auth_type = NVME_AUTH_DHCHAP_MESSAGES; data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2; data->t_id = cpu_to_le16(chap->transaction); return size; } static int nvme_auth_set_dhchap_failure2_data(struct nvme_ctrl *ctrl, struct nvme_dhchap_queue_context *chap) { struct nvmf_auth_dhchap_failure_data *data = chap->buf; size_t size = sizeof(*data); memset(chap->buf, 0, size); data->auth_type = NVME_AUTH_COMMON_MESSAGES; data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_FAILURE2; data->t_id = cpu_to_le16(chap->transaction); data->rescode = NVME_AUTH_DHCHAP_FAILURE_REASON_FAILED; data->rescode_exp = chap->status; return size; } static int nvme_auth_dhchap_setup_host_response(struct nvme_ctrl *ctrl, struct nvme_dhchap_queue_context *chap) { struct nvme_auth_hmac_ctx hmac; u8 buf[4], *challenge = chap->c1; int ret; dev_dbg(ctrl->device, "%s: qid %d host response seq %u transaction %d\n", __func__, chap->qid, chap->s1, chap->transaction); if (!chap->transformed_key) { chap->transformed_key = nvme_auth_transform_key(ctrl->host_key, ctrl->opts->host->nqn); if (IS_ERR(chap->transformed_key)) { ret = PTR_ERR(chap->transformed_key); chap->transformed_key = NULL; return ret; } } else { dev_dbg(ctrl->device, "%s: qid %d re-using host response\n", __func__, chap->qid); } ret = nvme_auth_hmac_init(&hmac, chap->hash_id, chap->transformed_key->key, chap->transformed_key->len); if (ret) goto out; if (chap->dh_tfm) { challenge = kmalloc(chap->hash_len, GFP_KERNEL); if (!challenge) { ret = -ENOMEM; goto out; } ret = nvme_auth_augmented_challenge(chap->hash_id, chap->sess_key, chap->sess_key_len, chap->c1, challenge, chap->hash_len); if (ret) goto out; } nvme_auth_hmac_update(&hmac, challenge, chap->hash_len); put_unaligned_le32(chap->s1, buf); nvme_auth_hmac_update(&hmac, buf, 4); put_unaligned_le16(chap->transaction, buf); nvme_auth_hmac_update(&hmac, buf, 2); *buf = chap->sc_c; nvme_auth_hmac_update(&hmac, buf, 1); nvme_auth_hmac_update(&hmac, "HostHost", 8); nvme_auth_hmac_update(&hmac, ctrl->opts->host->nqn, strlen(ctrl->opts->host->nqn)); memset(buf, 0, sizeof(buf)); nvme_auth_hmac_update(&hmac, buf, 1); nvme_auth_hmac_update(&hmac, ctrl->opts->subsysnqn, strlen(ctrl->opts->subsysnqn)); nvme_auth_hmac_final(&hmac, chap->response); ret = 0; out: if (challenge != chap->c1) kfree(challenge); memzero_explicit(&hmac, sizeof(hmac)); return ret; } static int nvme_auth_dhchap_setup_ctrl_response(struct nvme_ctrl *ctrl, struct nvme_dhchap_queue_context *chap) { struct nvme_auth_hmac_ctx hmac; struct nvme_dhchap_key *transformed_key; u8 buf[4], *challenge = chap->c2; int ret; transformed_key = nvme_auth_transform_key(ctrl->ctrl_key, ctrl->opts->subsysnqn); if (IS_ERR(transformed_key)) { ret = PTR_ERR(transformed_key); return ret; } ret = nvme_auth_hmac_init(&hmac, chap->hash_id, transformed_key->key,