1117 lines · 29.5 KB · C
RawDownload
// 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.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t