Commit 2c8f9257 authored by Konrad Sztyber's avatar Konrad Sztyber Committed by Jim Harris
Browse files

nvmf/auth: send DH-HMAC-CHAP_challenge message



This message transfers the challenge that the host will HMAC to
authenticate itself.  Currently, it doesn't include the DH part, that
will be added in the future.

Signed-off-by: default avatarKonrad Sztyber <konrad.sztyber@intel.com>
Change-Id: I72e172f88918854e5168b0350802c07f4a265f99
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/22665


Reviewed-by: default avatarJim Harris <jim.harris@samsung.com>
Tested-by: default avatarSPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: default avatarBen Walker <ben@nvidia.com>
Community-CI: Mellanox Build Bot
parent c06b0c79
Loading
Loading
Loading
Loading
+86 −0
Original line number Diff line number Diff line
@@ -9,9 +9,12 @@
#include "spdk/thread.h"
#include "spdk/util.h"

#include <openssl/rand.h>

#include "nvmf_internal.h"

#define NVMF_AUTH_DEFAULT_KATO_US (120ull * 1000 * 1000)
#define NVMF_AUTH_DIGEST_MAX_SIZE 64

#define AUTH_ERRLOG(q, fmt, ...) \
	SPDK_ERRLOG("[%s:%s:%u] " fmt, (q)->ctrlr->subsys->subnqn, (q)->ctrlr->hostnqn, \
@@ -23,6 +26,7 @@
enum nvmf_qpair_auth_state {
	NVMF_QPAIR_AUTH_NEGOTIATE,
	NVMF_QPAIR_AUTH_CHALLENGE,
	NVMF_QPAIR_AUTH_REPLY,
	NVMF_QPAIR_AUTH_FAILURE1,
	NVMF_QPAIR_AUTH_ERROR,
};
@@ -33,6 +37,8 @@ struct spdk_nvmf_qpair_auth {
	int				fail_reason;
	uint16_t			tid;
	int				digest;
	uint8_t				cval[NVMF_AUTH_DIGEST_MAX_SIZE];
	uint32_t			seqnum;
};

struct nvmf_auth_common_header {
@@ -60,6 +66,7 @@ nvmf_auth_get_state_name(enum nvmf_qpair_auth_state state)
	static const char *state_names[] = {
		[NVMF_QPAIR_AUTH_NEGOTIATE] = "negotiate",
		[NVMF_QPAIR_AUTH_CHALLENGE] = "challenge",
		[NVMF_QPAIR_AUTH_REPLY] = "reply",
		[NVMF_QPAIR_AUTH_FAILURE1] = "failure1",
		[NVMF_QPAIR_AUTH_ERROR] = "error",
	};
@@ -349,6 +356,79 @@ nvmf_auth_recv_failure1(struct spdk_nvmf_request *req, int fail_reason)
	nvmf_auth_disconnect_qpair(qpair);
}

static int
nvmf_auth_get_seqnum(struct spdk_nvmf_qpair *qpair)
{
	struct spdk_nvmf_subsystem *subsys = qpair->ctrlr->subsys;
	struct spdk_nvmf_qpair_auth *auth = qpair->auth;
	int rc;

	pthread_mutex_lock(&subsys->mutex);
	if (subsys->auth_seqnum == 0) {
		rc = RAND_bytes((void *)&subsys->auth_seqnum, sizeof(subsys->auth_seqnum));
		if (rc != 1) {
			pthread_mutex_unlock(&subsys->mutex);
			return -EIO;
		}
	}
	if (++subsys->auth_seqnum == 0) {
		subsys->auth_seqnum = 1;

	}
	auth->seqnum = subsys->auth_seqnum;
	pthread_mutex_unlock(&subsys->mutex);

	return 0;
}

static int
nvmf_auth_recv_challenge(struct spdk_nvmf_request *req)
{
	struct spdk_nvmf_qpair *qpair = req->qpair;
	struct spdk_nvmf_qpair_auth *auth = qpair->auth;
	struct spdk_nvmf_dhchap_challenge *challenge;
	uint8_t hl;
	int rc;

	hl = spdk_nvme_dhchap_get_digest_length(auth->digest);
	assert(hl > 0 && hl <= sizeof(auth->cval));

	challenge = nvmf_auth_get_message(req, sizeof(*challenge) + hl);
	if (challenge == NULL) {
		AUTH_ERRLOG(qpair, "invalid message length: %"PRIu32"\n", req->length);
		return SPDK_NVMF_AUTH_INCORRECT_PAYLOAD;
	}
	rc = nvmf_auth_get_seqnum(qpair);
	if (rc != 0) {
		return SPDK_NVMF_AUTH_FAILED;
	}
	rc = RAND_bytes(auth->cval, hl);
	if (rc != 1) {
		return SPDK_NVMF_AUTH_FAILED;
	}
	if (nvmf_auth_rearm_poller(qpair)) {
		nvmf_auth_request_complete(req, SPDK_NVME_SCT_GENERIC,
					   SPDK_NVME_SC_INTERNAL_DEVICE_ERROR, 1);
		nvmf_auth_disconnect_qpair(qpair);
		return 0;
	}

	memcpy(challenge->cval, auth->cval, hl);
	challenge->auth_type = SPDK_NVMF_AUTH_TYPE_DHCHAP;
	challenge->auth_id = SPDK_NVMF_AUTH_ID_DHCHAP_CHALLENGE;
	challenge->t_id = auth->tid;
	challenge->hl = hl;
	challenge->hash_id = (uint8_t)auth->digest;
	challenge->dhg_id = SPDK_NVMF_DHCHAP_DHGROUP_NULL;
	challenge->dhvlen = 0;
	challenge->seqnum = auth->seqnum;

	nvmf_auth_set_state(qpair, NVMF_QPAIR_AUTH_REPLY);
	nvmf_auth_recv_complete(req, sizeof(*challenge) + hl);

	return 0;
}

static void
nvmf_auth_recv_exec(struct spdk_nvmf_request *req)
{
@@ -366,6 +446,12 @@ nvmf_auth_recv_exec(struct spdk_nvmf_request *req)

	spdk_iov_memset(req->iov, req->iovcnt, 0);
	switch (auth->state) {
	case NVMF_QPAIR_AUTH_CHALLENGE:
		rc = nvmf_auth_recv_challenge(req);
		if (rc != 0) {
			nvmf_auth_recv_failure1(req, rc);
		}
		break;
	case NVMF_QPAIR_AUTH_FAILURE1:
		nvmf_auth_recv_failure1(req, auth->fail_reason);
		break;
+9 −5
Original line number Diff line number Diff line
@@ -286,6 +286,7 @@ struct spdk_nvmf_subsystem {
		uint8_t					reserved : 6;
	} flags;

	/* Protected against concurrent access by ->mutex */
	bool						allow_any_host;

	bool						destroying;
@@ -313,12 +314,13 @@ struct spdk_nvmf_subsystem {

	TAILQ_HEAD(, spdk_nvmf_ctrlr)			ctrlrs;

	/* A mutex used to protect the hosts list and allow_any_host flag. Unlike the namespace
	 * array, this list is not used on the I/O path (it's needed for handling things like
	 * the CONNECT command), so use a mutex to protect it instead of requiring the subsystem
	 * state to be paused. This removes the requirement to pause the subsystem when hosts
	 * are added or removed dynamically. */
	/* This mutex is used to protect fields that aren't touched on the I/O path (e.g. it's
	 * needed for handling things like the CONNECT command) instead of requiring the subsystem
	 * to be paused.  It makes it possible to modify those fields (e.g. add/remove hosts)
	 * without affecting outstanding I/O requests.
	 */
	pthread_mutex_t					mutex;
	/* Protected against concurrent access by ->mutex */
	TAILQ_HEAD(, spdk_nvmf_host)			hosts;
	TAILQ_HEAD(, spdk_nvmf_subsystem_listener)	listeners;
	struct spdk_bit_array				*used_listener_ids;
@@ -338,6 +340,8 @@ struct spdk_nvmf_subsystem {
	uint32_t					*ana_group;
	/* Queue of a state change requests */
	TAILQ_HEAD(, nvmf_subsystem_state_change_ctx)	state_changes;
	/* In-band authentication sequence number, protected by ->mutex */
	uint32_t					auth_seqnum;
};

static int
+2 −0
Original line number Diff line number Diff line
@@ -7,4 +7,6 @@ SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../../..)

TEST_FILE = auth_ut.c

SPDK_MOCK_SYMBOLS = RAND_bytes

include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
+102 −0
Original line number Diff line number Diff line
@@ -11,6 +11,18 @@

DEFINE_STUB(spdk_nvme_dhchap_get_digest_name, const char *, (int d), NULL);
DEFINE_STUB(spdk_nvme_dhchap_get_dhgroup_name, const char *, (int d), NULL);
DEFINE_STUB(spdk_nvme_dhchap_get_digest_length, uint8_t, (int d), 0);
DECLARE_WRAPPER(RAND_bytes, int, (unsigned char *buf, int num));

static uint8_t g_rand_val;
DEFINE_RETURN_MOCK(RAND_bytes, int) = 1;

int
__wrap_RAND_bytes(unsigned char *buf, int num)
{
	memset(buf, g_rand_val, num);
	return MOCK_GET(RAND_bytes);
}

int
spdk_nvmf_qpair_disconnect(struct spdk_nvmf_qpair *qpair)
@@ -577,6 +589,95 @@ test_auth_failure1(void)
	nvmf_qpair_auth_destroy(&qpair);
}

static void
test_auth_challenge(void)
{
	union nvmf_c2h_msg rsp = {};
	struct spdk_nvmf_subsystem subsys = { .mutex = PTHREAD_MUTEX_INITIALIZER };
	struct spdk_nvmf_ctrlr ctrlr = { .subsys = &subsys };
	struct spdk_nvmf_qpair qpair = { .ctrlr = &ctrlr };
	struct spdk_nvmf_request req = { .qpair = &qpair, .rsp = &rsp };
	struct spdk_nvmf_fabric_auth_recv_cmd cmd = {
		.fctype = SPDK_NVMF_FABRIC_COMMAND_AUTHENTICATION_RECV
	};
	struct spdk_nvmf_qpair_auth *auth;
	struct spdk_nvmf_dhchap_challenge *msg;
	struct spdk_nvmf_auth_failure *fail;
	uint8_t msgbuf[4096], cval[4096];
	int rc;

	msg = (void *)msgbuf;
	fail = (void *)msgbuf;
	rc = nvmf_qpair_auth_init(&qpair);
	SPDK_CU_ASSERT_FATAL(rc == 0);
	auth = qpair.auth;
	qpair.state = SPDK_NVMF_QPAIR_AUTHENTICATING;

	/* Successfully receive a challenge message */
	ut_prep_recv_cmd(&req, &cmd, msgbuf, sizeof(msgbuf));
	g_req_completed = false;
	auth->state = NVMF_QPAIR_AUTH_CHALLENGE;
	MOCK_SET(spdk_nvme_dhchap_get_digest_length, 48);
	g_rand_val = 0xa5;
	memset(cval, g_rand_val, sizeof(cval));
	auth->digest = SPDK_NVMF_DHCHAP_HASH_SHA384;
	auth->tid = 8;

	nvmf_auth_recv_exec(&req);
	CU_ASSERT(g_req_completed);
	CU_ASSERT_EQUAL(auth->state, NVMF_QPAIR_AUTH_REPLY);
	CU_ASSERT_EQUAL(msg->auth_type, SPDK_NVMF_AUTH_TYPE_DHCHAP);
	CU_ASSERT_EQUAL(msg->auth_id, SPDK_NVMF_AUTH_ID_DHCHAP_CHALLENGE);
	CU_ASSERT_EQUAL(msg->t_id, 8);
	CU_ASSERT_EQUAL(msg->hl, 48);
	CU_ASSERT_EQUAL(msg->hash_id, SPDK_NVMF_DHCHAP_HASH_SHA384);
	CU_ASSERT_EQUAL(msg->dhg_id, SPDK_NVMF_DHCHAP_DHGROUP_NULL);
	CU_ASSERT_EQUAL(msg->dhvlen, 0);
	CU_ASSERT_EQUAL(memcmp(msg->cval, cval, 48), 0);
	CU_ASSERT(msg->seqnum != 0);

	/* Check RAND_bytes failure */
	ut_prep_recv_cmd(&req, &cmd, msgbuf, sizeof(msgbuf));
	g_req_completed = false;
	auth->state = NVMF_QPAIR_AUTH_CHALLENGE;
	MOCK_SET(spdk_nvme_dhchap_get_digest_length, 48);
	auth->digest = SPDK_NVMF_DHCHAP_HASH_SHA384;
	auth->tid = 8;
	MOCK_SET(RAND_bytes, -1);

	nvmf_auth_recv_exec(&req);
	CU_ASSERT(g_req_completed);
	CU_ASSERT_EQUAL(qpair.state, SPDK_NVMF_QPAIR_ERROR);
	CU_ASSERT_EQUAL(fail->auth_type, SPDK_NVMF_AUTH_TYPE_COMMON_MESSAGE);
	CU_ASSERT_EQUAL(fail->auth_id, SPDK_NVMF_AUTH_ID_FAILURE1);
	CU_ASSERT_EQUAL(fail->t_id, 8);
	CU_ASSERT_EQUAL(fail->rc, SPDK_NVMF_AUTH_FAILURE);
	CU_ASSERT_EQUAL(fail->rce, SPDK_NVMF_AUTH_FAILED);
	qpair.state = SPDK_NVMF_QPAIR_AUTHENTICATING;
	MOCK_SET(RAND_bytes, 1);

	/* Check insufficient buffer size */
	ut_prep_recv_cmd(&req, &cmd, msgbuf, sizeof(msgbuf));
	g_req_completed = false;
	auth->state = NVMF_QPAIR_AUTH_CHALLENGE;
	MOCK_SET(spdk_nvme_dhchap_get_digest_length, 48);
	auth->tid = 8;
	cmd.al = req.length = req.iov[0].iov_len = sizeof(msg) + 47;

	nvmf_auth_recv_exec(&req);
	CU_ASSERT(g_req_completed);
	CU_ASSERT_EQUAL(qpair.state, SPDK_NVMF_QPAIR_ERROR);
	CU_ASSERT_EQUAL(fail->auth_type, SPDK_NVMF_AUTH_TYPE_COMMON_MESSAGE);
	CU_ASSERT_EQUAL(fail->auth_id, SPDK_NVMF_AUTH_ID_FAILURE1);
	CU_ASSERT_EQUAL(fail->t_id, 8);
	CU_ASSERT_EQUAL(fail->rc, SPDK_NVMF_AUTH_FAILURE);
	CU_ASSERT_EQUAL(fail->rce, SPDK_NVMF_AUTH_INCORRECT_PAYLOAD);
	qpair.state = SPDK_NVMF_QPAIR_AUTHENTICATING;
	MOCK_CLEAR(spdk_nvme_dhchap_get_digest_length);

	nvmf_qpair_auth_destroy(&qpair);
}

int
main(int argc, char **argv)
{
@@ -589,6 +690,7 @@ main(int argc, char **argv)
	CU_ADD_TEST(suite, test_auth_negotiate);
	CU_ADD_TEST(suite, test_auth_timeout);
	CU_ADD_TEST(suite, test_auth_failure1);
	CU_ADD_TEST(suite, test_auth_challenge);

	allocate_threads(1);
	set_thread(0);