Commit 5b83ef1c authored by Konrad Sztyber's avatar Konrad Sztyber Committed by Jim Harris
Browse files

nvmf/auth: Diffie-Hellman exchange support



The target now supports Diffie-Hellman exchange.  Similarly to digests,
dhgroup negotiation is prioritized based on the strength of the group.
I.e., the target selects the strongest dhgroup supported by both sides.

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


Reviewed-by: default avatarBen Walker <ben@nvidia.com>
Reviewed-by: default avatarSeung yeon Shin <syeon.shin@samsung.com>
Reviewed-by: default avatarJim Harris <jim.harris@samsung.com>
Community-CI: Mellanox Build Bot
Tested-by: default avatarSPDK CI Jenkins <sys_sgci@intel.com>
parent 5c45cee2
Loading
Loading
Loading
Loading
+56 −14
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@

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

#define AUTH_ERRLOG(q, fmt, ...) \
	SPDK_ERRLOG("[%s:%s:%u] " fmt, (q)->ctrlr->subsys->subnqn, (q)->ctrlr->hostnqn, \
@@ -43,8 +44,10 @@ struct spdk_nvmf_qpair_auth {
	int				fail_reason;
	uint16_t			tid;
	int				digest;
	int				dhgroup;
	uint8_t				cval[NVMF_AUTH_DIGEST_MAX_SIZE];
	uint32_t			seqnum;
	struct spdk_nvme_dhchap_dhkey	*dhkey;
};

struct nvmf_auth_common_header {
@@ -155,6 +158,7 @@ static void
nvmf_auth_qpair_cleanup(struct spdk_nvmf_qpair_auth *auth)
{
	spdk_poller_unregister(&auth->poller);
	spdk_nvme_dhchap_dhkey_free(&auth->dhkey);
}

static int
@@ -204,6 +208,11 @@ nvmf_auth_negotiate_exec(struct spdk_nvmf_request *req, struct spdk_nvmf_auth_ne
		SPDK_NVMF_DHCHAP_HASH_SHA256
	};
	enum spdk_nvmf_dhchap_dhgroup dhgroups[] = {
		SPDK_NVMF_DHCHAP_DHGROUP_8192,
		SPDK_NVMF_DHCHAP_DHGROUP_6144,
		SPDK_NVMF_DHCHAP_DHGROUP_4096,
		SPDK_NVMF_DHCHAP_DHGROUP_3072,
		SPDK_NVMF_DHCHAP_DHGROUP_2048,
		SPDK_NVMF_DHCHAP_DHGROUP_NULL,
	};
	int digest = -1, dhgroup = -1;
@@ -292,6 +301,7 @@ nvmf_auth_negotiate_exec(struct spdk_nvmf_request *req, struct spdk_nvmf_auth_ne
	}

	auth->digest = digest;
	auth->dhgroup = dhgroup;
	nvmf_auth_set_state(qpair, NVMF_QPAIR_AUTH_CHALLENGE);
	nvmf_auth_request_complete(req, SPDK_NVME_SCT_GENERIC, SPDK_NVME_SC_SUCCESS, 0);
}
@@ -303,7 +313,9 @@ nvmf_auth_reply_exec(struct spdk_nvmf_request *req, struct spdk_nvmf_dhchap_repl
	struct spdk_nvmf_ctrlr *ctrlr = qpair->ctrlr;
	struct spdk_nvmf_qpair_auth *auth = qpair->auth;
	uint8_t response[NVMF_AUTH_DIGEST_MAX_SIZE];
	uint8_t dhsec[NVMF_AUTH_DH_KEY_MAX_SIZE];
	struct spdk_key *key = NULL;
	size_t dhseclen = 0;
	uint8_t hl;
	int rc;

@@ -324,7 +336,7 @@ nvmf_auth_reply_exec(struct spdk_nvmf_request *req, struct spdk_nvmf_dhchap_repl
		nvmf_auth_request_fail1(req, SPDK_NVMF_AUTH_INCORRECT_PAYLOAD);
		goto out;
	}
	if (req->length != sizeof(*msg) + 2 * hl) {
	if (req->length != sizeof(*msg) + 2 * hl + msg->dhvlen) {
		AUTH_ERRLOG(qpair, "invalid message length: %"PRIu32" != %zu\n",
			    req->length, sizeof(*msg) + 2 * hl);
		nvmf_auth_request_fail1(req, SPDK_NVMF_AUTH_INCORRECT_PAYLOAD);
@@ -340,11 +352,6 @@ nvmf_auth_reply_exec(struct spdk_nvmf_request *req, struct spdk_nvmf_dhchap_repl
		nvmf_auth_request_fail1(req, SPDK_NVMF_AUTH_INCORRECT_PAYLOAD);
		goto out;
	}
	if (msg->dhvlen != 0) {
		AUTH_ERRLOG(qpair, "dhgroup length mismatch: %u != %u\n", msg->dhvlen, 0);
		nvmf_auth_request_fail1(req, SPDK_NVMF_AUTH_INCORRECT_PAYLOAD);
		goto out;
	}

	key = nvmf_subsystem_get_dhchap_key(ctrlr->subsys, ctrlr->hostnqn);
	if (key == NULL) {
@@ -353,10 +360,25 @@ nvmf_auth_reply_exec(struct spdk_nvmf_request *req, struct spdk_nvmf_dhchap_repl
		goto out;
	}

	if (auth->dhgroup != SPDK_NVMF_DHCHAP_DHGROUP_NULL) {
		AUTH_LOGDUMP("host pubkey:", &msg->rval[2 * hl], msg->dhvlen);
		dhseclen = sizeof(dhsec);
		rc = spdk_nvme_dhchap_dhkey_derive_secret(auth->dhkey, &msg->rval[2 * hl],
				msg->dhvlen, dhsec, &dhseclen);
		if (rc != 0) {
			AUTH_ERRLOG(qpair, "couldn't derive DH secret\n");
			nvmf_auth_request_fail1(req, SPDK_NVMF_AUTH_FAILED);
			goto out;
		}

		AUTH_LOGDUMP("dh secret:", dhsec, dhseclen);
	}

	assert(hl <= sizeof(response) && hl <= sizeof(auth->cval));
	rc = spdk_nvme_dhchap_calculate(key, (enum spdk_nvmf_dhchap_hash)auth->digest,
					"HostHost", auth->seqnum, auth->tid, 0,
					ctrlr->hostnqn, ctrlr->subsys->subnqn, NULL, 0,
					ctrlr->hostnqn, ctrlr->subsys->subnqn,
					dhseclen > 0 ? dhsec : NULL, dhseclen,
					auth->cval, response);
	if (rc != 0) {
		AUTH_ERRLOG(qpair, "failed to calculate challenge response: %s\n",
@@ -502,13 +524,31 @@ 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;
	uint8_t hl, dhv[NVMF_AUTH_DH_KEY_MAX_SIZE];
	size_t dhvlen = 0;
	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 (auth->dhgroup != SPDK_NVMF_DHCHAP_DHGROUP_NULL) {
		auth->dhkey = spdk_nvme_dhchap_generate_dhkey(auth->dhgroup);
		if (auth->dhkey == NULL) {
			AUTH_ERRLOG(qpair, "failed to generate DH key\n");
			return SPDK_NVMF_AUTH_FAILED;
		}

		dhvlen = sizeof(dhv);
		rc = spdk_nvme_dhchap_dhkey_get_pubkey(auth->dhkey, dhv, &dhvlen);
		if (rc != 0) {
			AUTH_ERRLOG(qpair, "failed to get DH public key\n");
			return SPDK_NVMF_AUTH_FAILED;
		}

		AUTH_LOGDUMP("ctrlr pubkey:", dhv, dhvlen);
	}

	challenge = nvmf_auth_get_message(req, sizeof(*challenge) + hl + dhvlen);
	if (challenge == NULL) {
		AUTH_ERRLOG(qpair, "invalid message length: %"PRIu32"\n", req->length);
		return SPDK_NVMF_AUTH_INCORRECT_PAYLOAD;
@@ -529,17 +569,18 @@ nvmf_auth_recv_challenge(struct spdk_nvmf_request *req)
	}

	memcpy(challenge->cval, auth->cval, hl);
	memcpy(&challenge->cval[hl], dhv, dhvlen);
	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->dhg_id = (uint8_t)auth->dhgroup;
	challenge->dhvlen = dhvlen;
	challenge->seqnum = auth->seqnum;

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

	return 0;
}
@@ -684,7 +725,7 @@ void
nvmf_qpair_auth_dump(struct spdk_nvmf_qpair *qpair, struct spdk_json_write_ctx *w)
{
	struct spdk_nvmf_qpair_auth *auth = qpair->auth;
	const char *digest;
	const char *digest, *dhgroup;

	if (auth == NULL) {
		return;
@@ -694,7 +735,8 @@ nvmf_qpair_auth_dump(struct spdk_nvmf_qpair *qpair, struct spdk_json_write_ctx *
	spdk_json_write_named_string(w, "state", nvmf_auth_get_state_name(auth->state));
	digest = spdk_nvme_dhchap_get_digest_name(auth->digest);
	spdk_json_write_named_string(w, "digest", digest ? digest : "unknown");
	spdk_json_write_named_string(w, "dhgroup", "null");
	dhgroup = spdk_nvme_dhchap_get_dhgroup_name(auth->dhgroup);
	spdk_json_write_named_string(w, "dhgroup", dhgroup ? dhgroup : "unknown");
	spdk_json_write_object_end(w);
}

+1 −1
Original line number Diff line number Diff line
@@ -11,7 +11,7 @@ source "$rootdir/test/nvmf/common.sh"

# shellcheck disable=SC2190
digests=("sha256" "sha384" "sha512")
dhgroups=("null")
dhgroups=("null" "ffdhe2048" "ffdhe3072" "ffdhe4096" "ffdhe6144" "ffdhe8192")
subnqn="nqn.2024-03.io.spdk:cnode0"
hostnqn="$NVME_HOSTNQN"
hostsock="/var/tmp/host.sock"
+107 −1
Original line number Diff line number Diff line
@@ -16,6 +16,12 @@ DEFINE_STUB(spdk_nvme_dhchap_get_digest_length, uint8_t, (int d), 0);
DEFINE_STUB_V(spdk_keyring_put_key, (struct spdk_key *k));
DEFINE_STUB(nvmf_subsystem_get_dhchap_key, struct spdk_key *,
	    (struct spdk_nvmf_subsystem *s, const char *h), NULL);
DEFINE_STUB(spdk_nvme_dhchap_generate_dhkey, struct spdk_nvme_dhchap_dhkey *,
	    (enum spdk_nvmf_dhchap_dhgroup dhgroup), NULL);
DEFINE_STUB_V(spdk_nvme_dhchap_dhkey_free, (struct spdk_nvme_dhchap_dhkey **key));
DEFINE_STUB(spdk_nvme_dhchap_dhkey_derive_secret, int,
	    (struct spdk_nvme_dhchap_dhkey *key, const void *peer, size_t peerlen, void *secret,
	     size_t *seclen), 0);
DECLARE_WRAPPER(RAND_bytes, int, (unsigned char *buf, int num));

static uint8_t g_rand_val;
@@ -63,6 +69,27 @@ spdk_nvme_dhchap_calculate(struct spdk_key *key, enum spdk_nvmf_dhchap_hash hash
	return MOCK_GET(spdk_nvme_dhchap_calculate);
}

static uint8_t g_dhv;
static size_t g_dhvlen;
DEFINE_RETURN_MOCK(spdk_nvme_dhchap_dhkey_get_pubkey, int);

int
spdk_nvme_dhchap_dhkey_get_pubkey(struct spdk_nvme_dhchap_dhkey *dhkey, void *pub, size_t *len)
{
	int rc;

	rc = MOCK_GET(spdk_nvme_dhchap_dhkey_get_pubkey);
	if (rc != 0) {
		return rc;
	}

	SPDK_CU_ASSERT_FATAL(*len >= g_dhvlen);
	memset(pub, g_dhv, g_dhvlen);
	*len = g_dhvlen;

	return rc;
}

static void
ut_clear_resp(struct spdk_nvmf_request *req)
{
@@ -271,6 +298,7 @@ test_auth_negotiate(void)
	nvmf_auth_send_exec(&req);
	CU_ASSERT(g_req_completed);
	CU_ASSERT_EQUAL(auth->digest, SPDK_NVMF_DHCHAP_HASH_SHA512);
	CU_ASSERT_EQUAL(auth->dhgroup, SPDK_NVMF_DHCHAP_DHGROUP_8192);
	CU_ASSERT_EQUAL(auth->state, NVMF_QPAIR_AUTH_CHALLENGE);

	/* Invalid auth state */
@@ -626,7 +654,7 @@ test_auth_challenge(void)
	struct spdk_nvmf_qpair_auth *auth;
	struct spdk_nvmf_dhchap_challenge *msg;
	struct spdk_nvmf_auth_failure *fail;
	uint8_t msgbuf[4096], cval[4096];
	uint8_t msgbuf[4096], cval[4096], dhv[4096];
	int rc;

	msg = (void *)msgbuf;
@@ -640,6 +668,7 @@ test_auth_challenge(void)
	ut_prep_recv_cmd(&req, &cmd, msgbuf, sizeof(msgbuf));
	g_req_completed = false;
	auth->state = NVMF_QPAIR_AUTH_CHALLENGE;
	auth->dhgroup = SPDK_NVMF_DHCHAP_DHGROUP_NULL;
	MOCK_SET(spdk_nvme_dhchap_get_digest_length, 48);
	g_rand_val = 0xa5;
	memset(cval, g_rand_val, sizeof(cval));
@@ -659,6 +688,35 @@ test_auth_challenge(void)
	CU_ASSERT_EQUAL(memcmp(msg->cval, cval, 48), 0);
	CU_ASSERT(msg->seqnum != 0);

	/* Successfully receive a challenge message w/ a non-NULL dhgroup */
	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);
	MOCK_SET(spdk_nvme_dhchap_generate_dhkey, (struct spdk_nvme_dhchap_dhkey *)0xdeadbeef);
	g_rand_val = 0xa5;
	g_dhv = 0xfe;
	g_dhvlen = 256;
	memset(cval, g_rand_val, sizeof(cval));
	memset(dhv, g_dhv, sizeof(dhv));
	auth->digest = SPDK_NVMF_DHCHAP_HASH_SHA384;
	auth->dhgroup = SPDK_NVMF_DHCHAP_DHGROUP_2048;
	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_2048);
	CU_ASSERT_EQUAL(msg->dhvlen, g_dhvlen);
	CU_ASSERT_EQUAL(memcmp(msg->cval, cval, 48), 0);
	CU_ASSERT_EQUAL(memcmp(&msg->cval[48], dhv, g_dhvlen), 0);
	CU_ASSERT(msg->seqnum != 0);

	/* Check RAND_bytes failure */
	ut_prep_recv_cmd(&req, &cmd, msgbuf, sizeof(msgbuf));
	g_req_completed = false;
@@ -679,6 +737,42 @@ test_auth_challenge(void)
	qpair.state = SPDK_NVMF_QPAIR_AUTHENTICATING;
	MOCK_SET(RAND_bytes, 1);

	/* Check spdk_nvme_dhchap_generate_dhkey failure */
	ut_prep_recv_cmd(&req, &cmd, msgbuf, sizeof(msgbuf));
	g_req_completed = false;
	MOCK_SET(spdk_nvme_dhchap_generate_dhkey, NULL);
	auth->state = NVMF_QPAIR_AUTH_CHALLENGE;
	auth->tid = 8;

	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;

	/* Check spdk_nvme_dhchap_dhkey_get_pubkey failure */
	ut_prep_recv_cmd(&req, &cmd, msgbuf, sizeof(msgbuf));
	g_req_completed = false;
	MOCK_SET(spdk_nvme_dhchap_generate_dhkey, (struct spdk_nvme_dhchap_dhkey *)0xdeadbeef);
	MOCK_SET(spdk_nvme_dhchap_dhkey_get_pubkey, -EIO);
	auth->state = NVMF_QPAIR_AUTH_CHALLENGE;
	auth->tid = 8;

	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(spdk_nvme_dhchap_dhkey_get_pubkey, 0);

	/* Check insufficient buffer size */
	ut_prep_recv_cmd(&req, &cmd, msgbuf, sizeof(msgbuf));
	g_req_completed = false;
@@ -860,6 +954,18 @@ test_auth_reply(void)
	CU_ASSERT_EQUAL(auth->fail_reason, SPDK_NVMF_AUTH_FAILED);
	g_rval = 0xa5;

	/* DH secret derivation failure */
	g_req_completed = false;
	auth->state = NVMF_QPAIR_AUTH_REPLY;
	auth->dhgroup = SPDK_NVMF_DHCHAP_DHGROUP_2048;
	MOCK_SET(spdk_nvme_dhchap_dhkey_derive_secret, -EIO);

	nvmf_auth_send_exec(&req);
	CU_ASSERT(g_req_completed);
	CU_ASSERT_EQUAL(auth->state, NVMF_QPAIR_AUTH_FAILURE1);
	CU_ASSERT_EQUAL(auth->fail_reason, SPDK_NVMF_AUTH_FAILED);
	MOCK_SET(spdk_nvme_dhchap_dhkey_derive_secret, 0);

	nvmf_qpair_auth_destroy(&qpair);
}