Commit 572a0c8a authored by Krzysztof Karas's avatar Krzysztof Karas Committed by Konrad Sztyber
Browse files

nvme_tcp: use interchange format



Use PSK interchange format with base64 encoding as input.
Also use information about hash function in interchange
format for retained PSK generation. If no hash is selected,
use configured PSK as retained PSK.
Check the size of interchange PSK to determine cipher suite.
Calculate CRC-32 bytes to ensure validity of PSK.

Modify PSK related functions to accept PSKs in binary form.

Make sure memory containing configured PSK is zeroed out.

Change-Id: Ib3d1b7c7746cce8d3b39b05b627730530bddf983
Signed-off-by: default avatarKrzysztof Karas <krzysztof.karas@intel.com>
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/16118


Community-CI: Mellanox Build Bot
Reviewed-by: default avatarKonrad Sztyber <konrad.sztyber@intel.com>
Tested-by: default avatarSPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: default avatarJim Harris <james.r.harris@intel.com>
parent 80bc26b0
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -66,6 +66,8 @@ These functions will notify a transport about adding/removing hosts' access.

TLS PSK identity is now generated from subsystem NQN and host NQN.

PSK interchange format is now expected as input, when configuring TLS in SPDK.

### sock

Added a callback `get_key()` to `spdk_sock_impl_opts` structure.
+127 −23
Original line number Diff line number Diff line
@@ -12,6 +12,8 @@
#include "spdk/dif.h"
#include "spdk/hexlify.h"
#include "spdk/nvmf_spec.h"
#include "spdk/util.h"
#include "spdk/base64.h"

#include "sgl.h"

@@ -609,23 +611,29 @@ nvme_tcp_generate_psk_identity(char *out_id, size_t out_id_len, const char *host
	return 0;
}

enum nvme_tcp_hash_algorithm {
	NVME_TCP_HASH_ALGORITHM_NONE,
	NVME_TCP_HASH_ALGORITHM_SHA256,
	NVME_TCP_HASH_ALGORITHM_SHA384,
};

static inline int
nvme_tcp_derive_retained_psk(const char *psk_in, const char *hostnqn, uint8_t *psk_out,
			     uint64_t psk_out_len)
nvme_tcp_derive_retained_psk(const uint8_t *psk_in, uint64_t psk_in_size, const char *hostnqn,
			     uint8_t *psk_out, uint64_t psk_out_len, enum nvme_tcp_hash_algorithm psk_retained_hash)
{
	EVP_PKEY_CTX *ctx;
	uint64_t sha256_digest_len = SHA256_DIGEST_LENGTH;
	uint64_t digest_len;
	uint8_t hkdf_info[NVME_TCP_HKDF_INFO_MAX_LEN] = {};
	const char *label = "tls13 HostNQN";
	size_t pos, labellen, nqnlen;
	char *unhexlified = NULL;
	const EVP_MD *hash;
	int rc, hkdf_info_size;

	labellen = strlen(label);
	nqnlen = strlen(hostnqn);
	assert(nqnlen <= SPDK_NVMF_NQN_MAX_LEN);

	*(uint16_t *)&hkdf_info[0] = htons(strlen(psk_in) / 2);
	*(uint16_t *)&hkdf_info[0] = htons(psk_in_size);
	pos = sizeof(uint16_t);
	hkdf_info[pos] = (uint8_t)labellen;
	pos += sizeof(uint8_t);
@@ -637,7 +645,21 @@ nvme_tcp_derive_retained_psk(const char *psk_in, const char *hostnqn, uint8_t *p
	pos += nqnlen;
	hkdf_info_size = pos;

	if (sha256_digest_len > psk_out_len) {
	switch (psk_retained_hash) {
	case NVME_TCP_HASH_ALGORITHM_SHA256:
		digest_len = SHA256_DIGEST_LENGTH;
		hash = EVP_sha256();
		break;
	case NVME_TCP_HASH_ALGORITHM_SHA384:
		digest_len = SHA384_DIGEST_LENGTH;
		hash = EVP_sha384();
		break;
	default:
		SPDK_ERRLOG("Unknown PSK hash requested!\n");
		return -EOPNOTSUPP;
	}

	if (digest_len > psk_out_len) {
		SPDK_ERRLOG("Insufficient buffer size for out key!\n");
		return -EINVAL;
	}
@@ -654,19 +676,12 @@ nvme_tcp_derive_retained_psk(const char *psk_in, const char *hostnqn, uint8_t *p
		rc = -ENOMEM;
		goto end;
	}
	if (EVP_PKEY_CTX_set_hkdf_md(ctx, EVP_sha256()) != 1) {
		SPDK_ERRLOG("Unable to set SHA256 method for HKDF!\n");
	if (EVP_PKEY_CTX_set_hkdf_md(ctx, hash) != 1) {
		SPDK_ERRLOG("Unable to set hash for HKDF!\n");
		rc = -EOPNOTSUPP;
		goto end;
	}

	unhexlified = spdk_unhexlify(psk_in);
	if (unhexlified == NULL) {
		SPDK_ERRLOG("Unable to unhexlify PSK!\n");
		rc = -EINVAL;
		goto end;
	}
	if (EVP_PKEY_CTX_set1_hkdf_key(ctx, unhexlified, strlen(psk_in) / 2) != 1) {
	if (EVP_PKEY_CTX_set1_hkdf_key(ctx, psk_in, psk_in_size) != 1) {
		SPDK_ERRLOG("Unable to set PSK key for HKDF!\n");
		rc = -ENOBUFS;
		goto end;
@@ -682,22 +697,21 @@ nvme_tcp_derive_retained_psk(const char *psk_in, const char *hostnqn, uint8_t *p
		rc = -EINVAL;
		goto end;
	}
	if (EVP_PKEY_derive(ctx, psk_out, &sha256_digest_len) != 1) {
	if (EVP_PKEY_derive(ctx, psk_out, &digest_len) != 1) {
		SPDK_ERRLOG("Unable to derive the PSK key!\n");
		rc = -EINVAL;
		goto end;
	}

	rc = sha256_digest_len;
	rc = digest_len;

end:
	free(unhexlified);
	EVP_PKEY_CTX_free(ctx);
	return rc;
}

static inline int
nvme_tcp_derive_tls_psk(const uint8_t *psk_in, uint64_t psk_in_len, const char *psk_identity,
nvme_tcp_derive_tls_psk(const uint8_t *psk_in, uint64_t psk_in_size, const char *psk_identity,
			uint8_t *psk_out, uint64_t psk_out_size, enum nvme_tcp_cipher_suite tls_cipher_suite)
{
	EVP_PKEY_CTX *ctx;
@@ -726,7 +740,7 @@ nvme_tcp_derive_tls_psk(const uint8_t *psk_in, uint64_t psk_in_len, const char *
		return -1;
	}

	*(uint16_t *)&hkdf_info[0] = htons(psk_in_len);
	*(uint16_t *)&hkdf_info[0] = htons(psk_in_size);
	pos = sizeof(uint16_t);
	hkdf_info[pos] = (uint8_t)labellen;
	pos += sizeof(uint8_t);
@@ -759,8 +773,7 @@ nvme_tcp_derive_tls_psk(const uint8_t *psk_in, uint64_t psk_in_len, const char *
		rc = -EOPNOTSUPP;
		goto end;
	}
	/* PSK should already be in retained form, so we do not need to unhexlify it here. */
	if (EVP_PKEY_CTX_set1_hkdf_key(ctx, psk_in, psk_in_len) != 1) {
	if (EVP_PKEY_CTX_set1_hkdf_key(ctx, psk_in, psk_in_size) != 1) {
		SPDK_ERRLOG("Unable to set PSK key for HKDF!\n");
		rc = -ENOBUFS;
		goto end;
@@ -788,4 +801,95 @@ end:
	return rc;
}

static inline int
nvme_tcp_parse_interchange_psk(const char *psk_in, uint8_t *psk_out, size_t psk_out_size,
			       uint64_t *psk_out_decoded_size, uint8_t *hash)
{
	const char *delim = ":";
	char psk_cpy[SPDK_TLS_PSK_MAX_LEN] = {};
	uint8_t psk_base64_decoded[SPDK_TLS_PSK_MAX_LEN] = {};
	uint64_t psk_configured_size = 0;
	uint32_t crc32_calc, crc32;
	char *psk_base64;
	uint64_t psk_base64_decoded_size = 0;
	int rc;

	/* Verify PSK format. */
	if (sscanf(psk_in, "NVMeTLSkey-1:%02hhx:", hash) != 1 || psk_in[strlen(psk_in) - 1] != delim[0]) {
		SPDK_ERRLOG("Invalid format of PSK interchange!\n");
		return -EINVAL;
	}

	if (strlen(psk_in) > SPDK_TLS_PSK_MAX_LEN) {
		SPDK_ERRLOG("PSK interchange exceeds maximum %d characters!\n", SPDK_TLS_PSK_MAX_LEN);
		return -EINVAL;
	}
	if (*hash != NVME_TCP_HASH_ALGORITHM_NONE && *hash != NVME_TCP_HASH_ALGORITHM_SHA256 &&
	    *hash != NVME_TCP_HASH_ALGORITHM_SHA384) {
		SPDK_ERRLOG("Invalid PSK length!\n");
		return -EINVAL;
	}

	/* Check provided hash function string. */
	memcpy(psk_cpy, psk_in, strlen(psk_in));
	strtok(psk_cpy, delim);
	strtok(NULL, delim);

	psk_base64 = strtok(NULL, delim);
	if (psk_base64 == NULL) {
		SPDK_ERRLOG("Could not get base64 string from PSK interchange!\n");
		return -EINVAL;
	}

	rc = spdk_base64_decode(psk_base64_decoded, &psk_base64_decoded_size, psk_base64);
	if (rc) {
		SPDK_ERRLOG("Could not decode base64 PSK!\n");
		return -EINVAL;
	}

	switch (*hash) {
	case NVME_TCP_HASH_ALGORITHM_SHA256:
		psk_configured_size = SHA256_DIGEST_LENGTH;
		break;
	case NVME_TCP_HASH_ALGORITHM_SHA384:
		psk_configured_size = SHA384_DIGEST_LENGTH;
		break;
	case NVME_TCP_HASH_ALGORITHM_NONE:
		if (psk_base64_decoded_size == SHA256_DIGEST_LENGTH + SPDK_CRC32_SIZE_BYTES) {
			psk_configured_size = SHA256_DIGEST_LENGTH;
		} else if (psk_base64_decoded_size == SHA384_DIGEST_LENGTH + SPDK_CRC32_SIZE_BYTES) {
			psk_configured_size = SHA384_DIGEST_LENGTH;
		}
		break;
	default:
		SPDK_ERRLOG("Invalid key: unsupported key hash\n");
		assert(0);
		return -EINVAL;
	}
	if (psk_base64_decoded_size != psk_configured_size + SPDK_CRC32_SIZE_BYTES) {
		SPDK_ERRLOG("Invalid key: unsupported key length\n");
		return -EINVAL;
	}

	crc32 = from_le32(&psk_base64_decoded[psk_configured_size]);

	crc32_calc = spdk_crc32_ieee_update(psk_base64_decoded, psk_configured_size, ~0);
	crc32_calc = ~crc32_calc;

	if (crc32 != crc32_calc) {
		SPDK_ERRLOG("CRC-32 checksums do not match!\n");
		return -EINVAL;
	}

	if (psk_configured_size > psk_out_size) {
		SPDK_ERRLOG("Insufficient buffer size: %lu for configured PSK of size: %lu!\n",
			    psk_out_size, psk_configured_size);
		return -ENOBUFS;
	}
	memcpy(psk_out, psk_base64_decoded, psk_configured_size);
	*psk_out_decoded_size = psk_configured_size;

	return rc;
}

#endif /* SPDK_INTERNAL_NVME_TCP_H */
+46 −10
Original line number Diff line number Diff line
@@ -2151,36 +2151,72 @@ nvme_tcp_generate_tls_credentials(struct nvme_tcp_ctrlr *tctrlr)
{
	int rc;
	uint8_t psk_retained[SPDK_TLS_PSK_MAX_LEN] = {};
	uint8_t psk_configured[SPDK_TLS_PSK_MAX_LEN] = {};
	uint8_t tls_cipher_suite;
	uint8_t psk_retained_hash;
	uint64_t psk_configured_size;

	assert(tctrlr != NULL);

	rc = nvme_tcp_parse_interchange_psk(tctrlr->ctrlr.opts.psk, psk_configured, sizeof(psk_configured),
					    &psk_configured_size, &psk_retained_hash);
	if (rc < 0) {
		SPDK_ERRLOG("Failed to parse PSK interchange!\n");
		goto finish;
	}

	/* The Base64 string encodes the configured PSK (32 or 48 bytes binary).
	 * This check also ensures that psk_configured_size is smaller than
	 * psk_retained buffer size. */
	if (psk_configured_size == SHA256_DIGEST_LENGTH) {
		tls_cipher_suite = NVME_TCP_CIPHER_AES_128_GCM_SHA256;
		tctrlr->tls_cipher_suite = "TLS_AES_128_GCM_SHA256";
	} else if (psk_configured_size == SHA384_DIGEST_LENGTH) {
		tls_cipher_suite = NVME_TCP_CIPHER_AES_256_GCM_SHA384;
		tctrlr->tls_cipher_suite = "TLS_AES_256_GCM_SHA384";
	} else {
		SPDK_ERRLOG("Unrecognized cipher suite!\n");
		rc = -ENOTSUP;
		goto finish;
	}

	rc = nvme_tcp_generate_psk_identity(tctrlr->psk_identity, sizeof(tctrlr->psk_identity),
					    tctrlr->ctrlr.opts.hostnqn, tctrlr->ctrlr.trid.subnqn,
					    NVME_TCP_CIPHER_AES_128_GCM_SHA256);
					    tls_cipher_suite);
	if (rc) {
		SPDK_ERRLOG("could not generate PSK identity\n");
		return -EINVAL;
		goto finish;
	}

	rc = nvme_tcp_derive_retained_psk(tctrlr->ctrlr.opts.psk, tctrlr->ctrlr.opts.hostnqn, psk_retained,
					  sizeof(psk_retained));
	/* No hash indicates that Configured PSK must be used as Retained PSK. */
	if (psk_retained_hash == NVME_TCP_HASH_ALGORITHM_NONE) {
		assert(psk_configured_size < sizeof(psk_retained));
		memcpy(psk_retained, psk_configured, psk_configured_size);
		rc = psk_configured_size;
	} else {
		/* Derive retained PSK. */
		rc = nvme_tcp_derive_retained_psk(psk_configured, psk_configured_size, tctrlr->ctrlr.opts.hostnqn,
						  psk_retained, sizeof(psk_retained), psk_retained_hash);
		if (rc < 0) {
			SPDK_ERRLOG("Unable to derive retained PSK!\n");
		return -EINVAL;
			goto finish;
		}
	}

	rc = nvme_tcp_derive_tls_psk(psk_retained, rc, tctrlr->psk_identity, tctrlr->psk,
				     sizeof(tctrlr->psk), NVME_TCP_CIPHER_AES_128_GCM_SHA256);
				     sizeof(tctrlr->psk), tls_cipher_suite);
	if (rc < 0) {
		SPDK_ERRLOG("Could not generate TLS PSK!\n");
		return rc;
	}

	tctrlr->psk_size = rc;
	rc = 0;

	return 0;
finish:
	spdk_memset_s(psk_configured, sizeof(psk_configured), 0, sizeof(psk_configured));

	return rc;
}

static spdk_nvme_ctrlr_t *
+45 −8
Original line number Diff line number Diff line
@@ -3524,8 +3524,12 @@ nvmf_tcp_subsystem_add_host(struct spdk_nvmf_transport *transport,
	struct spdk_nvmf_tcp_transport *ttransport;
	struct tcp_psk_entry *entry;
	char psk_identity[NVMF_PSK_IDENTITY_LEN];
	uint8_t psk_configured[SPDK_TLS_PSK_MAX_LEN] = {};
	uint8_t tls_cipher_suite;
	uint64_t key_len;
	int rc = 0;
	uint8_t psk_retained_hash;
	uint64_t psk_configured_size;

	if (transport_specific == NULL) {
		return 0;
@@ -3547,10 +3551,33 @@ nvmf_tcp_subsystem_add_host(struct spdk_nvmf_transport *transport,
		return 0;
	}

	/* Parse PSK interchange to get length of base64 encoded data.
	 * This is then used to decide which cipher suite should be used
	 * to generate PSK identity and TLS PSK later on. */
	rc = nvme_tcp_parse_interchange_psk(opts.psk, psk_configured, sizeof(psk_configured),
					    &psk_configured_size, &psk_retained_hash);
	if (rc < 0) {
		SPDK_ERRLOG("Failed to parse PSK interchange!\n");
		goto end;
	}

	/* The Base64 string encodes the configured PSK (32 or 48 bytes binary).
	 * This check also ensures that psk_configured_size is smaller than
	 * psk_retained buffer size. */
	if (psk_configured_size == SHA256_DIGEST_LENGTH) {
		tls_cipher_suite = NVME_TCP_CIPHER_AES_128_GCM_SHA256;
	} else if (psk_configured_size == SHA384_DIGEST_LENGTH) {
		tls_cipher_suite = NVME_TCP_CIPHER_AES_256_GCM_SHA384;
	} else {
		SPDK_ERRLOG("Unrecognized cipher suite!\n");
		rc = -EINVAL;
		goto end;
	}

	ttransport = SPDK_CONTAINEROF(transport, struct spdk_nvmf_tcp_transport, transport);
	/* Generate PSK identity. */
	rc = nvme_tcp_generate_psk_identity(psk_identity, NVMF_PSK_IDENTITY_LEN, hostnqn,
					    subsystem->subnqn, NVME_TCP_CIPHER_AES_128_GCM_SHA256);
					    subsystem->subnqn, tls_cipher_suite);
	if (rc) {
		rc = -EINVAL;
		goto end;
@@ -3588,6 +3615,8 @@ nvmf_tcp_subsystem_add_host(struct spdk_nvmf_transport *transport,
		free(entry);
		goto end;
	}
	entry->tls_cipher_suite = tls_cipher_suite;

	if (strlen(opts.psk) >= sizeof(entry->psk)) {
		SPDK_ERRLOG("PSK of length: %ld cannot fit in max buffer size: %ld\n", strlen(opts.psk),
			    sizeof(entry->psk));
@@ -3595,20 +3624,28 @@ nvmf_tcp_subsystem_add_host(struct spdk_nvmf_transport *transport,
		free(entry);
		goto end;
	}
	entry->tls_cipher_suite = NVME_TCP_CIPHER_AES_128_GCM_SHA256;

	/* No hash indicates that Configured PSK must be used as Retained PSK. */
	if (psk_retained_hash == NVME_TCP_HASH_ALGORITHM_NONE) {
		/* Psk configured is either 32 or 48 bytes long. */
		memcpy(entry->psk, psk_configured, psk_configured_size);
		entry->psk_size = psk_configured_size;
	} else {
		/* Derive retained PSK. */
	rc = nvme_tcp_derive_retained_psk(opts.psk, hostnqn, entry->psk, SPDK_TLS_PSK_MAX_LEN);
		rc = nvme_tcp_derive_retained_psk(psk_configured, psk_configured_size, hostnqn, entry->psk,
						  SPDK_TLS_PSK_MAX_LEN, psk_retained_hash);
		if (rc < 0) {
			SPDK_ERRLOG("Unable to derive retained PSK!\n");
			goto end;
		}
		entry->psk_size = rc;
	}

	TAILQ_INSERT_TAIL(&ttransport->psks, entry, link);
	rc = 0;

end:
	spdk_memset_s(psk_configured, sizeof(psk_configured), 0, sizeof(psk_configured));
	key_len = strnlen(opts.psk, SPDK_TLS_PSK_MAX_LEN);
	spdk_memset_s(opts.psk, key_len, 0, key_len);
	free(opts.psk);
+9 −3
Original line number Diff line number Diff line
@@ -67,6 +67,9 @@ if [[ "$ktls" != "false" ]]; then
	exit 1
fi

key="00112233445566778899aabbccddeeff"
interchange_key="NVMeTLSkey-1:01:$(base64 <(echo -n $key$(echo -n $key | gzip -1 -c | tail -c8 | head -c 4))):"

$rpc_py sock_impl_set_options -i ssl --tls-version 13
$rpc_py framework_start_init
$rpc_py nvmf_create_transport $NVMF_TRANSPORT_OPTS
@@ -76,12 +79,14 @@ $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t $TEST_TRANSPOR
$rpc_py bdev_malloc_create 32 4096 -b malloc0
$rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 malloc0 -n 1

$rpc_py nvmf_subsystem_add_host nqn.2016-06.io.spdk:cnode1 nqn.2016-06.io.spdk:host1 --psk 1234567890ABCDEF
$rpc_py nvmf_subsystem_add_host nqn.2016-06.io.spdk:cnode1 nqn.2016-06.io.spdk:host1 \
	--psk $interchange_key

# Send IO
"${NVMF_TARGET_NS_CMD[@]}" $SPDK_EXAMPLE_DIR/perf -S ssl -q 64 -o 4096 -w randrw -M 30 -t 10 \
	-r "trtype:${TEST_TRANSPORT} adrfam:IPv4 traddr:${NVMF_FIRST_TARGET_IP} trsvcid:${NVMF_PORT} \
subnqn:nqn.2016-06.io.spdk:cnode1 hostnqn:nqn.2016-06.io.spdk:host1" --psk-key 1234567890ABCDEF
subnqn:nqn.2016-06.io.spdk:cnode1 hostnqn:nqn.2016-06.io.spdk:host1" \
	--psk-key $interchange_key

# use bdevperf to test "bdev_nvme_attach_controller"
bdevperf_rpc_sock=/var/tmp/bdevperf.sock
@@ -92,7 +97,8 @@ trap 'process_shm --id $NVMF_APP_SHM_ID; killprocess $bdevperf_pid; nvmftestfini
waitforlisten $bdevperf_pid $bdevperf_rpc_sock
# send RPC
$rpc_py -s $bdevperf_rpc_sock bdev_nvme_attach_controller -b TLSTEST -t $TEST_TRANSPORT -a $NVMF_FIRST_TARGET_IP \
	-s $NVMF_PORT -f ipv4 -n nqn.2016-06.io.spdk:cnode1 -q nqn.2016-06.io.spdk:host1 --psk 1234567890ABCDEF
	-s $NVMF_PORT -f ipv4 -n nqn.2016-06.io.spdk:cnode1 -q nqn.2016-06.io.spdk:host1 \
	--psk $interchange_key
# run I/O and wait
$rootdir/examples/bdev/bdevperf/bdevperf.py -t 20 -s $bdevperf_rpc_sock perform_tests
# finish
Loading