Commit f83615b5 authored by Konrad Sztyber's avatar Konrad Sztyber Committed by Tomasz Zawadzki
Browse files

nvmf/auth: bidirectional authentication



This patch adds support for bidirectional DH-HMAC-CHAP authentication.
It's performed only when the host requests it, i.e. when it sets the
cvalid field in the DH-HMAC-CHAP_reply message.  Of course, this
requires the DH-HMAC-CHAP controller key to be set, otherwise the
authentication will fail.

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


Tested-by: default avatarSPDK CI Jenkins <sys_sgci@intel.com>
Community-CI: Mellanox Build Bot
Reviewed-by: default avatarBen Walker <ben@nvidia.com>
Reviewed-by: default avatarJim Harris <jim.harris@samsung.com>
parent e5e1dac9
Loading
Loading
Loading
Loading
+89 −10
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ enum nvmf_qpair_auth_state {
	NVMF_QPAIR_AUTH_CHALLENGE,
	NVMF_QPAIR_AUTH_REPLY,
	NVMF_QPAIR_AUTH_SUCCESS1,
	NVMF_QPAIR_AUTH_SUCCESS2,
	NVMF_QPAIR_AUTH_FAILURE1,
	NVMF_QPAIR_AUTH_COMPLETED,
	NVMF_QPAIR_AUTH_ERROR,
@@ -48,6 +49,7 @@ struct spdk_nvmf_qpair_auth {
	uint8_t				cval[NVMF_AUTH_DIGEST_MAX_SIZE];
	uint32_t			seqnum;
	struct spdk_nvme_dhchap_dhkey	*dhkey;
	bool				cvalid;
};

struct nvmf_auth_common_header {
@@ -77,6 +79,7 @@ nvmf_auth_get_state_name(enum nvmf_qpair_auth_state state)
		[NVMF_QPAIR_AUTH_CHALLENGE] = "challenge",
		[NVMF_QPAIR_AUTH_REPLY] = "reply",
		[NVMF_QPAIR_AUTH_SUCCESS1] = "success1",
		[NVMF_QPAIR_AUTH_SUCCESS2] = "success2",
		[NVMF_QPAIR_AUTH_FAILURE1] = "failure1",
		[NVMF_QPAIR_AUTH_COMPLETED] = "completed",
		[NVMF_QPAIR_AUTH_ERROR] = "error",
@@ -314,7 +317,7 @@ nvmf_auth_reply_exec(struct spdk_nvmf_request *req, struct spdk_nvmf_dhchap_repl
	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;
	struct spdk_key *key = NULL, *ckey = NULL;
	size_t dhseclen = 0;
	uint8_t hl;
	int rc;
@@ -347,8 +350,13 @@ 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->cvalid) {
		AUTH_ERRLOG(qpair, "bidirection authentication isn't supported yet\n");
	if (msg->cvalid != 0 && msg->cvalid != 1) {
		AUTH_ERRLOG(qpair, "unexpected cvalid=%d\n", msg->cvalid);
		nvmf_auth_request_fail1(req, SPDK_NVMF_AUTH_INCORRECT_PAYLOAD);
		goto out;
	}
	if (msg->cvalid && msg->seqnum == 0) {
		AUTH_ERRLOG(qpair, "unexpected seqnum=0 with cvalid=1\n");
		nvmf_auth_request_fail1(req, SPDK_NVMF_AUTH_INCORRECT_PAYLOAD);
		goto out;
	}
@@ -395,6 +403,28 @@ nvmf_auth_reply_exec(struct spdk_nvmf_request *req, struct spdk_nvmf_dhchap_repl
		goto out;
	}

	if (msg->cvalid) {
		ckey = nvmf_subsystem_get_dhchap_key(ctrlr->subsys, ctrlr->hostnqn,
						     NVMF_AUTH_KEY_CTRLR);
		if (ckey == NULL) {
			AUTH_ERRLOG(qpair, "missing DH-HMAC-CHAP ctrlr key\n");
			nvmf_auth_request_fail1(req, SPDK_NVMF_AUTH_FAILED);
			goto out;
		}
		rc = spdk_nvme_dhchap_calculate(ckey, (enum spdk_nvmf_dhchap_hash)auth->digest,
						"Controller", msg->seqnum, auth->tid, 0,
						ctrlr->subsys->subnqn, ctrlr->hostnqn,
						dhseclen > 0 ? dhsec : NULL, dhseclen,
						&msg->rval[hl], auth->cval);
		if (rc != 0) {
			AUTH_ERRLOG(qpair, "failed to calculate ctrlr challenge response: %s\n",
				    spdk_strerror(-rc));
			nvmf_auth_request_fail1(req, SPDK_NVMF_AUTH_FAILED);
			goto out;
		}
		auth->cvalid = true;
	}

	if (nvmf_auth_rearm_poller(qpair)) {
		nvmf_auth_request_complete(req, SPDK_NVME_SCT_GENERIC,
					   SPDK_NVME_SC_INTERNAL_DEVICE_ERROR, 1);
@@ -405,9 +435,39 @@ nvmf_auth_reply_exec(struct spdk_nvmf_request *req, struct spdk_nvmf_dhchap_repl
	nvmf_auth_set_state(qpair, NVMF_QPAIR_AUTH_SUCCESS1);
	nvmf_auth_request_complete(req, SPDK_NVME_SCT_GENERIC, SPDK_NVME_SC_SUCCESS, 0);
out:
	spdk_keyring_put_key(ckey);
	spdk_keyring_put_key(key);
}

static void
nvmf_auth_success2_exec(struct spdk_nvmf_request *req, struct spdk_nvmf_dhchap_success2 *msg)
{
	struct spdk_nvmf_qpair *qpair = req->qpair;
	struct spdk_nvmf_qpair_auth *auth = qpair->auth;

	if (auth->state != NVMF_QPAIR_AUTH_SUCCESS2) {
		AUTH_ERRLOG(qpair, "invalid state=%s\n", nvmf_auth_get_state_name(auth->state));
		nvmf_auth_request_fail1(req, SPDK_NVMF_AUTH_INCORRECT_PROTOCOL_MESSAGE);
		return;
	}
	if (req->length != sizeof(*msg)) {
		AUTH_ERRLOG(qpair, "invalid message length=%"PRIu32"\n", req->length);
		nvmf_auth_request_fail1(req, SPDK_NVMF_AUTH_INCORRECT_PAYLOAD);
		return;
	}
	if (msg->t_id != auth->tid) {
		AUTH_ERRLOG(qpair, "transaction id mismatch: %u != %u\n", msg->t_id, auth->tid);
		nvmf_auth_request_fail1(req, SPDK_NVMF_AUTH_INCORRECT_PAYLOAD);
		return;
	}

	AUTH_DEBUGLOG(qpair, "controller authentication successful\n");
	nvmf_qpair_set_state(qpair, SPDK_NVMF_QPAIR_ENABLED);
	nvmf_auth_set_state(qpair, NVMF_QPAIR_AUTH_COMPLETED);
	nvmf_auth_qpair_cleanup(auth);
	nvmf_auth_request_complete(req, SPDK_NVME_SCT_GENERIC, SPDK_NVME_SC_SUCCESS, 0);
}

static void
nvmf_auth_send_exec(struct spdk_nvmf_request *req)
{
@@ -446,6 +506,9 @@ nvmf_auth_send_exec(struct spdk_nvmf_request *req)
		case SPDK_NVMF_AUTH_ID_DHCHAP_REPLY:
			nvmf_auth_reply_exec(req, (void *)header);
			break;
		case SPDK_NVMF_AUTH_ID_DHCHAP_SUCCESS2:
			nvmf_auth_success2_exec(req, (void *)header);
			break;
		default:
			AUTH_ERRLOG(qpair, "unexpected auth_id=%u\n", header->auth_id);
			nvmf_auth_request_fail1(req, SPDK_NVMF_AUTH_INCORRECT_PROTOCOL_MESSAGE);
@@ -591,26 +654,42 @@ nvmf_auth_recv_success1(struct spdk_nvmf_request *req)
	struct spdk_nvmf_qpair *qpair = req->qpair;
	struct spdk_nvmf_qpair_auth *auth = qpair->auth;
	struct spdk_nvmf_dhchap_success1 *success;
	uint8_t hl;

	success = nvmf_auth_get_message(req, sizeof(*success));
	hl = spdk_nvme_dhchap_get_digest_length(auth->digest);
	success = nvmf_auth_get_message(req, sizeof(*success) + auth->cvalid * hl);
	if (success == NULL) {
		AUTH_ERRLOG(qpair, "invalid message length: %"PRIu32"\n", req->length);
		return SPDK_NVMF_AUTH_INCORRECT_PAYLOAD;
	}

	AUTH_DEBUGLOG(qpair, "host authentication successful\n");
	success->auth_type = SPDK_NVMF_AUTH_TYPE_DHCHAP;
	success->auth_id = SPDK_NVMF_AUTH_ID_DHCHAP_SUCCESS1;
	success->t_id = auth->tid;
	/* Kernel initiator always expects hl to be set, regardless of rvalid */
	success->hl = spdk_nvme_dhchap_get_digest_length(auth->digest);
	success->hl = hl;
	success->rvalid = 0;

	AUTH_DEBUGLOG(qpair, "host authentication successful\n");
	nvmf_auth_recv_complete(req, sizeof(*success));
	if (!auth->cvalid) {
		/* Host didn't request to authenticate us, we're done */
		nvmf_qpair_set_state(qpair, SPDK_NVMF_QPAIR_ENABLED);
		nvmf_auth_set_state(qpair, NVMF_QPAIR_AUTH_COMPLETED);
		nvmf_auth_qpair_cleanup(auth);
	} else {
		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;
		}
		AUTH_DEBUGLOG(qpair, "cvalid=1, starting controller authentication\n");
		nvmf_auth_set_state(qpair, NVMF_QPAIR_AUTH_SUCCESS2);
		memcpy(success->rval, auth->cval, hl);
		success->rvalid = 1;
	}

	nvmf_auth_recv_complete(req, sizeof(*success) + auth->cvalid * hl);
	return 0;
}

+31 −10
Original line number Diff line number Diff line
@@ -15,12 +15,12 @@ dhgroups=("null" "ffdhe2048" "ffdhe3072" "ffdhe4096" "ffdhe6144" "ffdhe8192")
subnqn="nqn.2024-03.io.spdk:cnode0"
hostnqn="$NVME_HOSTNQN"
hostsock="/var/tmp/host.sock"
keys=()
keys=() ckeys=()

cleanup() {
	killprocess $hostpid || :
	nvmftestfini || :
	rm -f "${keys[@]}" "$output_dir"/nvm{e,f}-auth.log
	rm -f "${keys[@]}" "${ckeys[@]}" "$output_dir"/nvm{e,f}-auth.log
}

dumplogs() {
@@ -31,14 +31,15 @@ dumplogs() {
hostrpc() { "$rootdir/scripts/rpc.py" -s "$hostsock" "$@"; }

connect_authenticate() {
	local digest dhgroup key qpairs
	local digest dhgroup key ckey qpairs

	digest="$1" dhgroup="$2" key="key$3"
	ckey=(${ckeys[$3]:+--dhchap-ctrlr-key "ckey$3"})

	rpc_cmd nvmf_subsystem_add_host "$subnqn" "$hostnqn" --dhchap-key "$key"
	rpc_cmd nvmf_subsystem_add_host "$subnqn" "$hostnqn" --dhchap-key "$key" "${ckey[@]}"
	hostrpc bdev_nvme_attach_controller -b nvme0 -t "$TEST_TRANSPORT" -f ipv4 \
		-a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" -q "$hostnqn" -n "$subnqn" \
		--dhchap-key "${key}"
		--dhchap-key "${key}" "${ckey[@]}"

	[[ $(hostrpc bdev_nvme_get_controllers | jq -r '.[].name') == "nvme0" ]]
	qpairs=$(rpc_cmd nvmf_subsystem_get_qpairs "$subnqn")
@@ -49,7 +50,8 @@ connect_authenticate() {

	# Force 1 I/O queue to speed up the connection
	nvme connect -t "$TEST_TRANSPORT" -a "$NVMF_FIRST_TARGET_IP" -n "$subnqn" -i 1 \
		-q "$hostnqn" --hostid "$NVME_HOSTID" --dhchap-secret "$(< "${keys[$3]}")"
		-q "$hostnqn" --hostid "$NVME_HOSTID" --dhchap-secret "$(< "${keys[$3]}")" \
		${ckeys[$3]:+--dhchap-ctrl-secret "$(< "${ckeys[$3]}")"}
	nvme disconnect -n "$subnqn"
	rpc_cmd nvmf_subsystem_remove_host "$subnqn" "$hostnqn"
}
@@ -61,10 +63,11 @@ hostpid=$!

trap "dumplogs; cleanup" SIGINT SIGTERM EXIT

keys[0]=$(gen_dhchap_key "null" 48)
keys[1]=$(gen_dhchap_key "sha256" 32)
keys[2]=$(gen_dhchap_key "sha384" 48)
keys[3]=$(gen_dhchap_key "sha512" 64)
# Set host/ctrlr key pairs with one combination w/o bidirectional authentication
keys[0]=$(gen_dhchap_key "null" 48) ckeys[0]=$(gen_dhchap_key "sha512" 64)
keys[1]=$(gen_dhchap_key "sha256" 32) ckeys[1]=$(gen_dhchap_key "sha384" 48)
keys[2]=$(gen_dhchap_key "sha384" 48) ckeys[2]=$(gen_dhchap_key "sha256" 32)
keys[3]=$(gen_dhchap_key "sha512" 64) ckeys[3]=""

waitforlisten "$nvmfpid"
waitforlisten "$hostpid" "$hostsock"
@@ -78,6 +81,10 @@ CONFIG
for i in "${!keys[@]}"; do
	rpc_cmd keyring_file_add_key "key$i" "${keys[i]}"
	hostrpc keyring_file_add_key "key$i" "${keys[i]}"
	if [[ -n "${ckeys[i]}" ]]; then
		rpc_cmd keyring_file_add_key "ckey$i" "${ckeys[i]}"
		hostrpc keyring_file_add_key "ckey$i" "${ckeys[i]}"
	fi
done

# Check all digest/dhgroup/key combinations
@@ -113,5 +120,19 @@ NOT hostrpc bdev_nvme_attach_controller -b nvme0 -t "$TEST_TRANSPORT" -f ipv4 \
	--dhchap-key "key2"
rpc_cmd nvmf_subsystem_remove_host "$subnqn" "$hostnqn"

# Check that mismatched controller keys result in failed attach
rpc_cmd nvmf_subsystem_add_host "$subnqn" "$hostnqn" --dhchap-key "key1" --dhchap-ctrlr-key "ckey1"
NOT hostrpc bdev_nvme_attach_controller -b nvme0 -t "$TEST_TRANSPORT" -f ipv4 \
	-a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" -q "$hostnqn" -n "$subnqn" \
	--dhchap-key "key1" --dhchap-ctrlr-key "ckey2"
rpc_cmd nvmf_subsystem_remove_host "$subnqn" "$hostnqn"

# Check that a missing controller key results in a failed attach
rpc_cmd nvmf_subsystem_add_host "$subnqn" "$hostnqn" --dhchap-key "key1"
NOT hostrpc bdev_nvme_attach_controller -b nvme0 -t "$TEST_TRANSPORT" -f ipv4 \
	-a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" -q "$hostnqn" -n "$subnqn" \
	--dhchap-key "key1" --dhchap-ctrlr-key "ckey1"
rpc_cmd nvmf_subsystem_remove_host "$subnqn" "$hostnqn"

trap - SIGINT SIGTERM EXIT
cleanup
+88 −1
Original line number Diff line number Diff line
@@ -966,6 +966,53 @@ test_auth_reply(void)
	CU_ASSERT_EQUAL(auth->fail_reason, SPDK_NVMF_AUTH_FAILED);
	MOCK_SET(spdk_nvme_dhchap_dhkey_derive_secret, 0);

	/* Bad cvalid value */
	g_req_completed = false;
	auth->state = NVMF_QPAIR_AUTH_REPLY;
	msg->cvalid = 2;

	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_INCORRECT_PAYLOAD);

	/* Bad cvalid/seqnum combination */
	g_req_completed = false;
	auth->state = NVMF_QPAIR_AUTH_REPLY;
	msg->cvalid = 1;
	msg->seqnum = 0;

	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_INCORRECT_PAYLOAD);

	/* Missing controller key */
	g_req_completed = false;
	auth->state = NVMF_QPAIR_AUTH_REPLY;
	msg->cvalid = 1;
	msg->seqnum = 1;
	MOCK_ENQUEUE(nvmf_subsystem_get_dhchap_key, (struct spdk_key *)0xdeadbeef);
	MOCK_ENQUEUE(nvmf_subsystem_get_dhchap_key, NULL);

	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);

	/* Controller challange calcuation failure */
	g_req_completed = false;
	auth->state = NVMF_QPAIR_AUTH_REPLY;
	msg->cvalid = 1;
	msg->seqnum = 1;
	MOCK_ENQUEUE(spdk_nvme_dhchap_calculate, 0);
	MOCK_ENQUEUE(spdk_nvme_dhchap_calculate, -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);

	nvmf_qpair_auth_destroy(&qpair);
}

@@ -983,7 +1030,7 @@ test_auth_success1(void)
	struct spdk_nvmf_qpair_auth *auth;
	struct spdk_nvmf_dhchap_success1 *msg;
	struct spdk_nvmf_auth_failure *fail;
	uint8_t msgbuf[sizeof(*msg)];
	uint8_t msgbuf[sizeof(*msg) + 48];
	int rc;

	msg = (void *)msgbuf;
@@ -1010,6 +1057,26 @@ test_auth_success1(void)
	CU_ASSERT_EQUAL(msg->rvalid, 0);
	qpair.state = SPDK_NVMF_QPAIR_AUTHENTICATING;

	/* Successfully receive a success message w/ bidirectional authentication */
	ut_prep_recv_cmd(&req, &cmd, msgbuf, sizeof(*msg) + 48);
	g_req_completed = false;
	auth->state = NVMF_QPAIR_AUTH_SUCCESS1;
	auth->cvalid = true;
	memset(auth->cval, 0xa5, 48);
	MOCK_SET(spdk_nvme_dhchap_get_digest_length, 48);

	nvmf_auth_recv_exec(&req);
	CU_ASSERT(g_req_completed);
	CU_ASSERT_EQUAL(auth->state, NVMF_QPAIR_AUTH_SUCCESS2);
	CU_ASSERT_EQUAL(msg->auth_type, SPDK_NVMF_AUTH_TYPE_DHCHAP);
	CU_ASSERT_EQUAL(msg->auth_id, SPDK_NVMF_AUTH_ID_DHCHAP_SUCCESS1);
	CU_ASSERT_EQUAL(msg->t_id, 8);
	CU_ASSERT_EQUAL(msg->hl, 48);
	CU_ASSERT_EQUAL(msg->rvalid, 1);
	CU_ASSERT_EQUAL(memcmp(msg->rval, auth->cval, 48), 0);
	qpair.state = SPDK_NVMF_QPAIR_AUTHENTICATING;
	auth->cvalid = false;

	/* Bad message length (smaller than success1 message) */
	ut_prep_recv_cmd(&req, &cmd, msgbuf, sizeof(*msg));
	g_req_completed = false;
@@ -1026,6 +1093,26 @@ test_auth_success1(void)
	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;

	/* Bad message length (smaller than msg + hl) */
	ut_prep_recv_cmd(&req, &cmd, msgbuf, sizeof(*msg));
	g_req_completed = false;
	auth->state = NVMF_QPAIR_AUTH_SUCCESS1;
	auth->cvalid = true;
	MOCK_SET(spdk_nvme_dhchap_get_digest_length, 48);
	cmd.al = req.iov[0].iov_len = req.length = sizeof(*msg) + 47;

	nvmf_auth_recv_exec(&req);
	CU_ASSERT(g_req_completed);
	CU_ASSERT_EQUAL(auth->state, NVMF_QPAIR_AUTH_ERROR);
	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;
	auth->cvalid = false;
	cmd.al = req.iov[0].iov_len = req.length = sizeof(*msg);

	nvmf_qpair_auth_destroy(&qpair);