int spdm_key_exchange()

in core/spdm/spdm_commands.c [3660:4046]


int spdm_key_exchange (const struct cmd_interface_spdm_responder *spdm_responder,
	struct cmd_interface_msg *request)
{
	int status = 0;
	int spdm_error;
	uint8_t spdm_version;
	struct spdm_key_exchange_request *spdm_request;
	struct spdm_key_exchange_response *spdm_response;
	uint8_t slot_id;
	size_t dhe_key_size;
	size_t hash_size;
	uint32_t meas_summary_hash_size;
	uint32_t sig_size;
	size_t request_size;
	size_t response_size;
	uint16_t req_session_id;
	uint16_t rsp_session_id;
	uint32_t session_id = SPDM_INVALID_SESSION_ID;
	struct spdm_secure_session *session = NULL;
	uint8_t *ptr;
	uint16_t opaque_data_length;
	size_t opaque_key_exchange_rsp_size;
	size_t pub_key_component_size;
	uint8_t session_policy = 0;
	const struct spdm_transcript_manager *transcript_manager;
	struct spdm_state *state;
	const struct spdm_device_capability *local_capabilities;
	const struct riot_key_manager *key_manager;
	const struct hash_engine *hash_engine;
	const struct rng_engine *rng_engine;
	bool release_session = false;
	uint8_t cert_chain_hash[HASH_MAX_HASH_LEN];
	struct spdm_secure_session_manager *session_manager;
	struct ecc_point_public_key peer_pub_key_point;
	enum hash_type hash_type;
	const struct spdm_measurements *measurements;
	uint8_t meas_summary_hash_type;

	if ((spdm_responder == NULL) || (request == NULL)) {
		return CMD_HANDLER_SPDM_RESPONDER_INVALID_ARGUMENT;
	}

	transcript_manager = spdm_responder->transcript_manager;
	state = spdm_responder->state;
	local_capabilities = spdm_responder->local_capabilities;
	key_manager = spdm_responder->key_manager;
	hash_engine = spdm_responder->hash_engine[0];
	rng_engine = spdm_responder->rng_engine;
	session_manager = spdm_responder->session_manager;
	measurements = spdm_responder->measurements;
	hash_type = spdm_get_hash_type (state->connection_info.peer_algorithms.base_hash_algo);

	/* Check if secure session support is available. This is excessive check, as session_manager
	 * can't be NULL if secure_message_version_num_count !=0 based on initialization checks, but
	 * it is still safer to check both */
	if ((spdm_responder->secure_message_version_num_count == 0) || (session_manager == NULL)) {
		status = CMD_HANDLER_SPDM_RESPONDER_UNEXPECTED_REQUEST;
		spdm_error = SPDM_ERROR_INVALID_REQUEST;
		goto exit;
	}

	/* Validate the request. */
	if (request->payload_length < sizeof (struct spdm_key_exchange_request)) {
		status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST;
		spdm_error = SPDM_ERROR_INVALID_REQUEST;
		goto exit;
	}
	spdm_request = (struct spdm_key_exchange_request*) request->payload;
	spdm_version = SPDM_MAKE_VERSION (spdm_request->header.spdm_major_version,
		spdm_request->header.spdm_minor_version);
	if (spdm_version != spdm_get_connection_version (state)) {
		status = CMD_HANDLER_SPDM_RESPONDER_VERSION_MISMATCH;
		spdm_error = SPDM_ERROR_VERSION_MISMATCH;
		goto exit;
	}

	/* Verify SPDM state. */
	if (state->response_state != SPDM_RESPONSE_STATE_NORMAL) {
		spdm_handle_response_state (state, &spdm_error);
		status = CMD_HANDLER_SPDM_RESPONDER_INTERNAL_ERROR;
		goto exit;
	}
	if (state->connection_info.connection_state < SPDM_CONNECTION_STATE_NEGOTIATED) {
		status = CMD_HANDLER_SPDM_RESPONDER_UNEXPECTED_REQUEST;
		spdm_error = SPDM_ERROR_UNEXPECTED_REQUEST;
		goto exit;
	}

	/* Check for key exchange capability support. */
	if ((local_capabilities->flags.key_ex_cap == 0) ||
		(state->connection_info.peer_capabilities.flags.key_ex_cap == 0)) {
		status = CMD_HANDLER_SPDM_RESPONDER_UNSUPPORTED_CAPABILITY;
		spdm_error = SPDM_ERROR_UNSUPPORTED_REQUEST;
		goto exit;
	}

	/* Check if a previous session is active. */
	if (session_manager->is_last_session_id_valid (session_manager) == true) {
		status = CMD_HANDLER_SPDM_RESPONDER_PREV_SESSION_VALID;
		spdm_error = SPDM_ERROR_UNEXPECTED_REQUEST;
		goto exit;
	}

	/* Check the type of measurement summary hash. */
	meas_summary_hash_type = spdm_request->measurement_summary_hash_type;

	if ((meas_summary_hash_type != SPDM_MEASUREMENT_SUMMARY_HASH_NONE) &&
		(meas_summary_hash_type != SPDM_MEASUREMENT_SUMMARY_HASH_TCB) &&
		(meas_summary_hash_type != SPDM_MEASUREMENT_SUMMARY_HASH_ALL)) {
		status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST;
		spdm_error = SPDM_ERROR_INVALID_REQUEST;
		goto exit;
	}

	if ((meas_summary_hash_type > SPDM_MEASUREMENT_SUMMARY_HASH_NONE) &&
		((local_capabilities->flags.meas_cap == 0) ||
		(state->connection_info.peer_algorithms.measurement_spec == 0) ||
		(state->connection_info.peer_algorithms.measurement_hash_algo == 0))) {
		status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST;
		spdm_error = SPDM_ERROR_INVALID_REQUEST;
		goto exit;
	}

	/* Check the slot Id. */
	slot_id = spdm_request->slot_id;
	if (slot_id != 0) {
		status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST;
		spdm_error = SPDM_ERROR_INVALID_REQUEST;
		goto exit;
	}

	/* Get the crypto parameter sizes. */
	hash_size = hash_get_hash_length (hash_type);
	sig_size = spdm_get_asym_signature_size (state->connection_info.peer_algorithms.base_asym_algo);
	dhe_key_size =
		spdm_get_dhe_pub_key_size (state->connection_info.peer_algorithms.dhe_named_group);
	meas_summary_hash_size =
		(spdm_request->measurement_summary_hash_type == SPDM_MEASUREMENT_SUMMARY_HASH_NONE) ?
			0 : hash_size;

	/* Check if the request contains the DHE public key and the opaque data length. */
	if (request->payload_length < (sizeof (struct spdm_key_exchange_request) + dhe_key_size +
		sizeof (uint16_t))) {
		status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST;
		spdm_error = SPDM_ERROR_INVALID_REQUEST;
		goto exit;
	}

	/* Copy the public key from the request. Public key is in point format (x, y). */
	ptr = spdm_key_exchange_rq_exchange_data (spdm_request);
	pub_key_component_size = dhe_key_size >> 1;
	memcpy (peer_pub_key_point.x, ptr, pub_key_component_size);
	memcpy (peer_pub_key_point.y, ptr + pub_key_component_size, pub_key_component_size);
	peer_pub_key_point.key_length = pub_key_component_size;

	/* Read the opaque data length. */
	ptr += dhe_key_size;
	opaque_data_length = buffer_unaligned_read16 ((const uint16_t*) ptr);

	/* Check if the request contains the DHE public key, the opaque data length and the opaque data. */
	if (request->payload_length < (sizeof (struct spdm_key_exchange_request) + dhe_key_size +
		sizeof (uint16_t) + opaque_data_length)) {
		status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST;
		spdm_error = SPDM_ERROR_INVALID_REQUEST;
		goto exit;
	}
	request_size = sizeof (struct spdm_key_exchange_request) + dhe_key_size + sizeof (uint16_t) +
		opaque_data_length;

	ptr += sizeof (uint16_t);
	if (opaque_data_length != 0) {
		/* Validate the opaque data. */
		status = spdm_validate_general_opaque_data (spdm_version,
			state->connection_info.peer_algorithms.other_params_support.opaque_data_format, ptr,
			opaque_data_length);
		if (status != 0) {
			spdm_error = SPDM_ERROR_INVALID_REQUEST;
			goto exit;
		}

		/* Process the opaque data and negotiate the secure message version. */
		status = spdm_process_opaque_data_supported_version_data (state,
			spdm_responder->secure_message_version_num,
			spdm_responder->secure_message_version_num_count, ptr, opaque_data_length);
		if (status != 0) {
			spdm_error = SPDM_ERROR_INVALID_REQUEST;
			goto exit;
		}
	}

	/* Get the size of the opaque data for the response. */
	opaque_key_exchange_rsp_size = spdm_get_opaque_data_version_selection_data_size (spdm_version,
		spdm_responder->secure_message_version_num_count);

	/* Reset the transcript manager state as per the request code. */
	spdm_reset_transcript_via_request_code (state, transcript_manager, SPDM_REQUEST_KEY_EXCHANGE);

	/* Construct the session Id from the requester and responder session Ids. */
	req_session_id = spdm_request->req_session_id;
	rsp_session_id = (state->current_local_session_id + 1);
	session_id = MAKE_SESSION_ID (req_session_id, rsp_session_id);

	/* Create a session and assign the constructed session Id to it. */
	session = session_manager->create_session (session_manager, session_id, false,
		&state->connection_info);
	if (session == NULL) {
		status = CMD_HANDLER_SPDM_RESPONDER_SESSION_LIMIT_EXCEEDED;
		spdm_error = SPDM_ERROR_SESSION_LIMIT_EXCEEDED;
		goto exit;
	}
	release_session = true;

	/* Obtain the cert chain hash. */
	status = spdm_get_certificate_chain_digest (key_manager, hash_engine, hash_type,
		cert_chain_hash);
	if (status != 0) {
		spdm_error = SPDM_ERROR_UNSPECIFIED;
		goto exit;
	}

	/* Add the cert chain hash to the TH session hash context. This is needed for signature and hmac. */
	status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_TH,
		cert_chain_hash, hash_size, true, session->session_index);
	if (status != 0) {
		spdm_error = SPDM_ERROR_UNSPECIFIED;
		goto exit;
	}

	/* Add the request to the TH session hash context. */
	status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_TH,
		(uint8_t*) spdm_request, request_size, true, session->session_index);
	if (status != 0) {
		spdm_error = SPDM_ERROR_UNSPECIFIED;
		goto exit;
	}

	/* Save the session policy from the request. */
	if (spdm_version >= SPDM_VERSION_1_2) {
		session_policy = spdm_request->session_policy;
	}

	/* Construct the response. */
	response_size = sizeof (struct spdm_key_exchange_response) + dhe_key_size +
		meas_summary_hash_size + sizeof (uint16_t) + opaque_key_exchange_rsp_size + sig_size;

	if ((state->connection_info.peer_capabilities.flags.handshake_in_the_clear_cap == 0) ||
		(local_capabilities->flags.handshake_in_the_clear_cap == 0)) {
		response_size += hash_size;	/* HMAC */
	}

	if (response_size > cmd_interface_msg_get_max_response (request)) {
		status = CMD_HANDLER_SPDM_RESPONDER_RESPONSE_TOO_LARGE;
		spdm_error = SPDM_ERROR_UNSPECIFIED;
		goto exit;
	}

	spdm_response = (struct spdm_key_exchange_response*) request->payload;
	memset (spdm_response, 0, response_size);

	spdm_populate_header (&spdm_response->header, SPDM_RESPONSE_KEY_EXCHANGE,
		SPDM_GET_MINOR_VERSION (spdm_version));
	spdm_response->heartbeat_period = 0;
	spdm_response->rsp_session_id = rsp_session_id;
	spdm_response->mut_auth_requested = 0;
	spdm_response->req_slot_id_param = 0;

	/* Generate random data for the response. */
	status = rng_engine->generate_random_buffer (rng_engine, sizeof (spdm_response->random_data),
		spdm_response->random_data);
	if (status != 0) {
		spdm_error = SPDM_ERROR_UNSPECIFIED;
		goto exit;
	}

	/* Generate the shared secret. Also, copy the generated local public key to the response buffer. */
	ptr = spdm_key_exchange_resp_exchange_data (spdm_response);
	status = session_manager->generate_shared_secret (session_manager, session, &peer_pub_key_point,
		ptr);
	if (status != 0) {
		spdm_error = SPDM_ERROR_UNSPECIFIED;
		goto exit;
	}
	ptr += dhe_key_size;

	/* Add the measurement summary hash if requested. */
	if (meas_summary_hash_type > SPDM_MEASUREMENT_SUMMARY_HASH_NONE) {
		status = measurements->get_measurement_summary_hash (measurements,
			spdm_responder->hash_engine[0], hash_type, spdm_responder->hash_engine[1], hash_type,
			(meas_summary_hash_type == SPDM_MEASUREMENT_SUMMARY_HASH_TCB), ptr,
			meas_summary_hash_size);
		if (status != 0) {
			spdm_error = SPDM_ERROR_UNSPECIFIED;
			goto exit;
		}
		ptr += meas_summary_hash_size;
	}

	/* Write the opaque data length. */
	buffer_unaligned_write16 ((uint16_t*) ptr, opaque_key_exchange_rsp_size);
	ptr += sizeof (uint16_t);

	/* Build the selected secure session version as opaque data. */
	spdm_build_opaque_data_version_selection_data (spdm_responder, ptr);
	ptr += opaque_key_exchange_rsp_size;

	/* Add the response to the TH session hash context. */
	status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_TH,
		(uint8_t*) spdm_response, ((size_t) ptr - (size_t) spdm_response), true,
		session->session_index);
	if (status != 0) {
		spdm_error = SPDM_ERROR_UNSPECIFIED;
		goto exit;
	}

	/* Generate the response signature. */
	status = spdm_generate_key_exchange_rsp_signature (transcript_manager, state,
		spdm_responder->key_manager, spdm_responder->ecc_engine, hash_engine, session, ptr,
		sig_size);
	if (status != 0) {
		spdm_error = SPDM_ERROR_UNSPECIFIED;
		goto exit;
	}

	/* Add the signature to the TH session hash context. */
	status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_TH, ptr,
		sig_size, true, session->session_index);
	if (status != 0) {
		spdm_error = SPDM_ERROR_UNSPECIFIED;
		goto exit;
	}
	ptr += sig_size;

	/* Generate the session handshake keys. */
	status = session_manager->generate_session_handshake_keys (session_manager, session);
	if (status != 0) {
		spdm_error = SPDM_ERROR_UNSPECIFIED;
		goto exit;
	}

	/* Generate the responder verification data. */
	if ((state->connection_info.peer_capabilities.flags.handshake_in_the_clear_cap == 0) ||
		(local_capabilities->flags.handshake_in_the_clear_cap == 0)) {
		status = spdm_calculate_th_hmac_for_key_exchange_rsp (transcript_manager, state,
			spdm_responder->ecc_engine, hash_engine, session, ptr);
		if (status != 0) {
			spdm_error = SPDM_ERROR_UNSPECIFIED;
			goto exit;
		}

		/* Add the HMAC to the TH Session transcript. */
		status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_TH, ptr,
			hash_size, true, session->session_index);
		if (status != 0) {
			spdm_error = SPDM_ERROR_UNSPECIFIED;
			goto exit;
		}
	}

	/* Set the request session policy on the session. */
	if (spdm_version >= SPDM_VERSION_1_2) {
		session->session_policy = session_policy;
	}

	/* Set the payload length. */
	cmd_interface_msg_set_message_payload_length (request, response_size);

	/* Set the session state. */
	session_manager->set_session_state (session_manager, session_id,
		SPDM_SESSION_STATE_HANDSHAKING);

	/* Update the Responder state. */
	state->current_local_session_id += 1;

	release_session = false;

exit:
	if (release_session == true) {
		session_manager->release_session (session_manager, session_id);
	}

	if (status != 0) {
		spdm_generate_error_response (request, state->connection_info.version.minor_version,
			spdm_error, 0x00, NULL, 0, SPDM_REQUEST_KEY_EXCHANGE, status);
	}

	return 0;
}