core/spdm/spdm_commands.c (3,087 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include <stdint.h> #include <string.h> #include "cmd_interface_spdm.h" #include "cmd_interface_spdm.h" #include "cmd_interface_spdm_responder.h" #include "cmd_interface_spdm_responder.h" #include "spdm_commands.h" #include "spdm_commands.h" #include "spdm_logging.h" #include "spdm_logging.h" #include "spdm_secure_session_manager.h" #include "attestation/attestation_responder.h" #include "cmd_interface/device_manager.h" #include "common/array_size.h" #include "common/buffer_util.h" #include "common/common_math.h" #include "common/unused.h" #include "crypto/ecc.h" #include "crypto/ecdsa.h" #include "crypto/hash.h" #include "crypto/kdf.h" #include "mctp/mctp_base_protocol.h" #include "riot/riot_key_manager.h" /** * Pre-process received SPDM protocol message and get the command Id. * * @param message The message being processed. * @param command_id Pointer to hold command ID of incoming message. * * @return 0 if the message was successfully processed or an error code. */ int spdm_get_command_id (struct cmd_interface_msg *message, uint8_t *command_id) { struct spdm_protocol_header *header = (struct spdm_protocol_header*) message->payload; message->crypto_timeout = false; if (message->payload_length < SPDM_PROTOCOL_MIN_MSG_LEN) { return CMD_HANDLER_SPDM_PAYLOAD_TOO_SHORT; } *command_id = header->req_rsp_code; return 0; } /** * Generate the header segment of a SPDM protocol request * * @param header Buffer to fill with SPDM protocol header * @param command Command ID to utilize in header * @param spdm_minor_version SPDM minor version to utilize in header */ static void spdm_populate_header (struct spdm_protocol_header *header, uint8_t command, uint8_t spdm_minor_version) { header->spdm_minor_version = spdm_minor_version; header->spdm_major_version = SPDM_MAJOR_VERSION; header->req_rsp_code = command; } /** * Generate the MCTP header for an SPDM request. * * @param header Buffer to fill with the MCTP protocol header. */ void spdm_populate_mctp_header (struct spdm_protocol_mctp_header *header) { if (header != NULL) { header->msg_type = MCTP_BASE_PROTOCOL_MSG_TYPE_SPDM; header->integrity_check = 0; } } /** * Construct an SPDM error response. * * @param response Container to populate with SPDM error response * @param spdm_minor_version SPDM minor version to utilize in header * @param error_code Error code * @param error_data Error data * @param optional_data Buffer containing optional data, can be set to NULL if not needed * @param optional_data_len Optional data buffer length * @param req_code SPDM request code in failed request received * @param internal_error_code Internal error code to include in debug log entry */ void spdm_generate_error_response (struct cmd_interface_msg *response, uint8_t spdm_minor_version, uint8_t error_code, uint8_t error_data, uint8_t *optional_data, size_t optional_data_len, uint8_t req_code, int internal_error_code) { struct spdm_error_response *rsp = (struct spdm_error_response*) response->payload; size_t response_length = sizeof (struct spdm_error_response) + optional_data_len; memset (rsp, 0, sizeof (struct spdm_error_response)); spdm_populate_header (&rsp->header, SPDM_RESPONSE_ERROR, spdm_minor_version); rsp->error_code = error_code; rsp->error_data = error_data; cmd_interface_msg_set_message_payload_length (response, sizeof (struct spdm_error_response)); if ((optional_data_len > 0) && (response_length <= cmd_interface_msg_get_max_response (response))) { cmd_interface_msg_add_payload_data (response, optional_data, optional_data_len); } debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_SPDM, SPDM_LOGGING_ERR_MSG, (req_code << 24 | response->source_eid << 16 | error_code << 8 | error_data), internal_error_code); } /** * Set the SPDM connection state. * * @param state SPDM state. * @param connection_state SPDM connection state. */ static void spdm_set_connection_state (struct spdm_state *state, enum spdm_connection_state connection_state) { state->connection_info.connection_state = connection_state; } /** * Handle the erroneous response state and return the corresponding SPDM error code. * * @param state SPDM state. * @param spdm_error Error code. */ static void spdm_handle_response_state (struct spdm_state *state, int *spdm_error) { switch (state->response_state) { case SPDM_RESPONSE_STATE_BUSY: *spdm_error = SPDM_ERROR_BUSY; break; case SPDM_RESPONSE_STATE_NEED_RESYNC: *spdm_error = SPDM_ERROR_REQUEST_RESYNCH; /* Reset connection state. */ spdm_set_connection_state (state, SPDM_CONNECTION_STATE_NOT_STARTED); break; case SPDM_RESPONSE_STATE_PROCESSING_ENCAP: *spdm_error = SPDM_ERROR_REQUEST_IN_FLIGHT; break; case SPDM_RESPONSE_STATE_NOT_READY: /* [TODO] Implement this case in later messages. */ default: *spdm_error = SPDM_ERROR_UNSPECIFIED; break; } } /** * Check the compatibility of the capabilities in the flags. * Some flags are mutually inclusive/exclusive. * * @param flags Capabilities to validate. * @param version SPDM message version. * * @return true if the received capabilities are valid, false otherwise. */ bool spdm_check_request_flag_compatibility (struct spdm_get_capabilities_flags_format flags, uint8_t version) { /* Illegal to return reserved values. */ if (flags.psk_cap >= SPDM_PSK_RESERVED) { return false; } /* Key exchange capabilities checks. */ if ((flags.key_ex_cap == 1) || (flags.psk_cap == SPDM_PSK_SUPPORTED_NO_CONTEXT)) { /* While clearing MAC_CAP and setting ENCRYPT_CAP is legal according to DSP0274, the SPDM * responder also implements DSP0277 secure messages, which requires at least MAC_CAP * to be set. */ if (flags.mac_cap == 0) { return false; } } else { /* mac_cap, encrypt_cap and key_upd_cap capabilities require either key exchange * or pre-shared key capability. * * heartbeat messages are sent in a secure session, the setup of which also require * either key exchange or pre-shared key capability. * * handshake_in_the_clear_cap requires key_ex_cap. */ if ((flags.mac_cap == 1) || (flags.encrypt_cap == 1) || (flags.handshake_in_the_clear_cap == 1) || (flags.hbeat_cap == 1) || (flags.key_upd_cap == 1)) { return false; } } /* This is per libSPDM, so keeping this check. */ if ((flags.key_ex_cap == 0) && (flags.psk_cap == SPDM_PSK_SUPPORTED_NO_CONTEXT) && (flags.handshake_in_the_clear_cap == 1)) { return false; } /* Certificate or public key capabilities checks. */ if ((flags.cert_cap == 1) || (flags.pub_key_id_cap == 1)) { /* Certificate capabilities and public key capabilities cannot both be set. */ if ((flags.cert_cap == 1) && (flags.pub_key_id_cap == 1)) { return false; } /* cert_cap and/or pub_key_id_cap are not needed if both chal_cap and key_ex_cap are 0. * Theoretically, this might be ok, but libSPDM has this check, so keeping it. * * TODO: This needs to be re-evaluated. There is no requirement per the SPDM spec for this * check as there is no specified coupling between certificate and challenge/key exchange * support. It's reasonable to envision an implementation that doesn't support challenge * but supports measurement signing. */ if ((flags.chal_cap == 0) && (flags.key_ex_cap == 0)) { return false; } } else { /** * If certificates or public keys are not enabled, then these capabilities * cannot be enabled. */ if ((flags.chal_cap == 1) || (flags.mut_auth_cap == 1)) { return false; } } /* Checks specific to v1.1. */ if (version == SPDM_VERSION_1_1) { /* Having mut_auth_cap requires encap_cap to be available. */ if ((flags.mut_auth_cap == 1) && (flags.encap_cap == 0)) { return false; } } return true; } /** * Check if the received SPDM version is supported. * * @param peer_version SPDM message version in <major.minor> format. * @param version_num Version number table. * @param version_num_count Number of entries in the version number table. * * @return true if the received SPDM version is supported, false otherwise. */ static bool spdm_is_version_supported (uint8_t peer_version, const struct spdm_version_num_entry *version_num, const uint8_t version_num_count) { uint8_t i; for (i = 0; i < version_num_count; i++) { if (SPDM_MAKE_VERSION (version_num[i].major_version, version_num[i].minor_version) == peer_version) { return true; } } return false; } /** * Check the compatibility of the received SPDM version. * If the received version is valid, subsequent SPDM communication will use this version. * * @param state SPDM state. * @param peer_version SPDM message version in <major.minor> format. * * @return true if the received SPDM version is valid, else false. */ static bool spdm_check_request_version_compatibility (struct spdm_state *state, const struct spdm_version_num_entry *version_num, const uint8_t version_num_count, uint8_t peer_version) { if (spdm_is_version_supported (peer_version, version_num, version_num_count) == true) { state->connection_info.version.major_version = SPDM_GET_MAJOR_VERSION (peer_version); state->connection_info.version.minor_version = SPDM_GET_MINOR_VERSION (peer_version); return true; } return false; } /** * Get the connection version negotiated by the GET_VERSION/VERSION messages. * * @param state SPDM state. * * @return Negotiated version. */ static uint8_t spdm_get_connection_version (const struct spdm_state *state) { return SPDM_MAKE_VERSION (state->connection_info.version.major_version, state->connection_info.version.minor_version); } /** * Select the preferred supported algorithm according to the priority table. If no priority table is * provided, the first common lowest numbered algorithm is selected. * * @param priority_table The priority table. * @param priority_table_count The count of the priority table entries. * @param local_algo Local supported algorithm. * @param peer_algo Peer supported algorithm. * @return Preferred supported algorithm. */ static uint32_t spdm_prioritize_algorithm (const uint32_t *priority_table, size_t priority_table_count, uint32_t local_algo, uint32_t peer_algo) { uint32_t common_algos; size_t index; uint32_t mask; common_algos = (local_algo & peer_algo); if (priority_table != NULL) { for (index = 0; index < priority_table_count; index++) { if ((common_algos & priority_table[index]) != 0) { return priority_table[index]; } } } else { /* If a priority table was not provided, use the first common lowest numbered algorithm. */ mask = common_algos & -common_algos; return (common_algos & mask); } return 0; } /** * Get the hash type for a single SPDM hash algorithm. * * @param hash_algo A single SPDM Hash algorithm. * * @return Hash type if algorithm is supported, HASH_TYPE_INVALID otherwise. */ enum hash_type spdm_get_hash_type (uint32_t hash_algo) { enum hash_type hash_type = HASH_TYPE_INVALID; switch (hash_algo) { case SPDM_TPM_ALG_SHA_256: hash_type = HASH_TYPE_SHA256; break; case SPDM_TPM_ALG_SHA_384: hash_type = HASH_TYPE_SHA384; break; case SPDM_TPM_ALG_SHA_512: hash_type = HASH_TYPE_SHA512; break; } return hash_type; } /** * Get the signature size for the signature algorithm. * * @param asym_algo Signature algorithm. * * @return Signature size if the algorithm is supported, 0 otherwise. */ static uint32_t spdm_get_asym_signature_size (uint32_t asym_algo) { switch (asym_algo) { case SPDM_TPM_ALG_ECDSA_ECC_NIST_P256: return (ECC_KEY_LENGTH_256 * 2); case SPDM_TPM_ALG_ECDSA_ECC_NIST_P384: return (ECC_KEY_LENGTH_384 * 2); case SPDM_TPM_ALG_ECDSA_ECC_NIST_P521: return (ECC_KEY_LENGTH_521 * 2); default: return 0; } } /** * Get the DHE algorithm public key size. * * @param dhe_named_group DHE named group. * * @return DHE algorithm public key size if the algorithm is supported, 0 otherwise. */ uint32_t spdm_get_dhe_pub_key_size (uint16_t dhe_named_group) { /* [TODO] Add support for other algorithms. */ switch (dhe_named_group) { case SPDM_ALG_DHE_NAMED_GROUP_SECP_384_R1: return (ECC_KEY_LENGTH_384 << 1); default: return 0; } } /** * Get the AEAD algorithm key size. * * @param aead_cipher_suite AEAD cipher suite * * @return AEAD algorithm key size if the algorithm is supported, 0 otherwise. */ uint32_t spdm_get_aead_key_size (uint16_t aead_cipher_suite) { /* [TODO] Add support for other algorithms. */ switch (aead_cipher_suite) { case SPDM_ALG_AEAD_CIPHER_SUITE_AES_256_GCM: return 32; default: return 0; } } /** * Get the AEAD algorithm IV size. * * @param aead_cipher_suite aead cipher suite * * @return AEAD algorithm IV size if the algorithm is supported, 0 otherwise. */ uint32_t spdm_get_aead_iv_size (uint16_t aead_cipher_suite) { /* [TODO] Add support for other algorithms. */ switch (aead_cipher_suite) { case SPDM_ALG_AEAD_CIPHER_SUITE_AES_256_GCM: return 12; default: return 0; } } /** * Get the AEAD algorithm tag size. * * @param aead_cipher_suite AEAD cipher suite * * @return AEAD algorithm tag size if the algorithm is supported, 0 otherwise. */ uint32_t spdm_get_aead_tag_size (uint16_t aead_cipher_suite) { /* [TODO] Add support for other algorithms. */ switch (aead_cipher_suite) { case SPDM_ALG_AEAD_CIPHER_SUITE_AES_256_GCM: return 16; default: return 0; } } /** * Validate the opaque data in KEY_EXCHANGE request. * * @param spdm_version SPDM version. * @param opaque_data_format Opaque data format. * @param data_in Opaque data pointer. * @param data_in_size Size of opaque data. * * @retval 0 if the general opaque data is valid, error code otherwise. */ static int spdm_validate_general_opaque_data (uint8_t spdm_version, uint8_t opaque_data_format, const void *data_in, size_t data_in_size) { int status = CMD_HANDLER_SPDM_RESPONDER_INVALID_OPAQUE_DATA_FORMAT; const struct spdm_general_opaque_data_table_header *general_opaque_data_table_header; const struct spdm_opaque_element_table_header *opaque_element_table_header; uint8_t element_num; uint8_t element_index; uint16_t opaque_element_data_len; size_t data_element_size; size_t current_element_len; size_t total_element_len; uint8_t zero_padding[4] = {0}; total_element_len = 0; if ((spdm_version >= SPDM_VERSION_1_2) && (opaque_data_format & SPDM_ALGORITHMS_OPAQUE_DATA_FORMAT_1)) { /* Check byte alignment. */ if ((data_in_size & 3) != 0) { goto exit; } general_opaque_data_table_header = data_in; /* Buffer will be atleast of size struct spdm_general_opaque_data_table_header (4 Bytes) * due to the alignment check above, so no additional check needed. */ if (general_opaque_data_table_header->total_elements == 0) { goto exit; } opaque_element_table_header = (const void*) (general_opaque_data_table_header + 1); element_num = general_opaque_data_table_header->total_elements; data_element_size = data_in_size - sizeof (struct spdm_general_opaque_data_table_header); for (element_index = 0; element_index < element_num; element_index++) { /* Ensure the opaque_element_table_header is valid. */ if ((total_element_len + sizeof (struct spdm_opaque_element_table_header) + sizeof (opaque_element_data_len)) > data_element_size) { goto exit; } /* Validate the element header id. */ if (opaque_element_table_header->id > SPDM_REGISTRY_ID_MAX) { goto exit; } opaque_element_data_len = *(uint16_t*) ((size_t) (opaque_element_table_header + 1)) + opaque_element_table_header->vendor_len; current_element_len = sizeof (struct spdm_opaque_element_table_header) + opaque_element_table_header->vendor_len + sizeof (opaque_element_data_len) + opaque_element_data_len; if ((current_element_len & 3) != 0) { if (memcmp (zero_padding, (uint8_t*) (size_t) (opaque_element_table_header) + current_element_len, 4 - (current_element_len & 3)) != 0) { goto exit; } } /* Add Padding. */ current_element_len = (current_element_len + 3) & ~3; total_element_len += current_element_len; if (total_element_len > data_element_size) { goto exit; } /* Move to next the element. */ opaque_element_table_header = (const struct spdm_opaque_element_table_header*) ((const uint8_t*) opaque_element_table_header + current_element_len); } } status = 0; exit: return status; } /** * Get the size of the opaque data supported version. * * @param negotiated_version Negotiated connection version. * @param version_count Version count. * * @return Size of the opaque data supported version. **/ static size_t spdm_get_untrusted_opaque_data_supported_version_data_size ( uint8_t negotiated_version, uint8_t version_count) { size_t size; if (negotiated_version >= SPDM_VERSION_1_2) { size = sizeof (struct spdm_general_opaque_data_table_header) + sizeof (struct spdm_secured_message_opaque_element_table_header) + sizeof (struct spdm_secured_message_opaque_element_supported_version) + sizeof (struct spdm_version_number) * version_count; } else { size = sizeof (struct spdm_secured_message_general_opaque_data_table_header) + sizeof (struct spdm_secured_message_opaque_element_table_header) + sizeof (struct spdm_secured_message_opaque_element_supported_version) + sizeof (struct spdm_version_number) * version_count; } /* Add Padding. */ return (size + 3) & ~3; } /** * Get the size of opaque data version selection. * * @param negotiated_version Negotiated connection version. * @param secure_message_version_count Number of secure message versions supported. * * @return Size in bytes of opaque data version selection. */ static size_t spdm_get_opaque_data_version_selection_data_size (uint8_t negotiated_version, uint8_t secure_message_version_count) { size_t size; /* If no secure message version(s) supported, no opaque data is added. */ if (secure_message_version_count == 0) { return 0; } if (negotiated_version >= SPDM_VERSION_1_2) { size = sizeof (struct spdm_general_opaque_data_table_header) + sizeof (struct spdm_secured_message_opaque_element_table_header) + sizeof (struct spdm_secured_message_opaque_element_version_selection); } else { size = sizeof (struct spdm_secured_message_general_opaque_data_table_header) + sizeof (struct spdm_secured_message_opaque_element_table_header) + sizeof (struct spdm_secured_message_opaque_element_version_selection); } /* Add Padding*/ return (size + 3) & ~3; } /** * Get element from multi-element opaque data by element id. * * @param state SPDM state. * @param data_in_size Size of multi-element opaque data. * @param data_in Multi-element opaque data buffer. * @param element_id Element id. * @param sm_data_id Id to identify the secured message data type. * @param get_element_ptr Pointer to the element found. * @param get_element_len Length of the element found. * * @retval 0 if the element was rerieved successfully, error code otherwise. */ static int spdm_get_element_from_opaque_data (struct spdm_state *state, size_t data_in_size, const void *data_in, uint8_t element_id, uint8_t sm_data_id, const void **get_element_ptr, size_t *get_element_len) { int status = CMD_HANDLER_SPDM_RESPONDER_INVALID_OPAQUE_DATA_FORMAT; const struct spdm_secured_message_general_opaque_data_table_header *general_opaque_data_table_header; const struct spdm_general_opaque_data_table_header *spdm_general_opaque_data_table_header; const struct spdm_secured_message_opaque_element_table_header *opaque_element_table_header; const struct spdm_secured_message_opaque_element_header *secured_message_element_header; uint8_t element_num; uint8_t element_index; size_t data_element_size; size_t current_element_len; size_t total_element_len; total_element_len = 0; if ((element_id > SPDM_REGISTRY_ID_MAX) || (data_in_size == 0) || (data_in == NULL)) { goto exit; } if (spdm_get_connection_version (state) >= SPDM_VERSION_1_2) { spdm_general_opaque_data_table_header = data_in; if (data_in_size < sizeof (struct spdm_general_opaque_data_table_header)) { goto exit; } if (spdm_general_opaque_data_table_header->total_elements < 1) { goto exit; } opaque_element_table_header = (const void*) (spdm_general_opaque_data_table_header + 1); element_num = spdm_general_opaque_data_table_header->total_elements; data_element_size = data_in_size - sizeof (struct spdm_general_opaque_data_table_header); } else { general_opaque_data_table_header = data_in; if (data_in_size < sizeof (struct spdm_secured_message_general_opaque_data_table_header)) { goto exit; } if ((general_opaque_data_table_header->spec_id != SPDM_SECURED_MESSAGE_OPAQUE_DATA_SPEC_ID) || (general_opaque_data_table_header->opaque_version != SPDM_SECURED_MESSAGE_OPAQUE_VERSION) || (general_opaque_data_table_header->total_elements < 1)) { goto exit; } opaque_element_table_header = (const void*) (general_opaque_data_table_header + 1); element_num = general_opaque_data_table_header->total_elements; data_element_size = data_in_size - sizeof (struct spdm_secured_message_general_opaque_data_table_header); } for (element_index = 0; element_index < element_num; element_index++) { /* Ensure the opaque_element_table_header is valid. */ if ((total_element_len + sizeof (struct spdm_secured_message_opaque_element_table_header)) > data_element_size) { goto exit; } /* Check element header Id. */ if ((opaque_element_table_header->id > SPDM_REGISTRY_ID_MAX) || (opaque_element_table_header->vendor_len != 0)) { goto exit; } current_element_len = sizeof (struct spdm_secured_message_opaque_element_table_header) + opaque_element_table_header->opaque_element_data_len; /* Add Padding. */ current_element_len = (current_element_len + 3) & ~3; total_element_len += current_element_len; if (data_element_size < total_element_len) { goto exit; } if (opaque_element_table_header->id == element_id) { secured_message_element_header = (const void*) (opaque_element_table_header + 1); if (((const uint8_t*) secured_message_element_header + sizeof (struct spdm_secured_message_opaque_element_header)) > ((const uint8_t*) data_in + data_in_size)) { goto exit; } if ((secured_message_element_header->sm_data_id == sm_data_id) && (secured_message_element_header->sm_data_version == SPDM_SECURED_MESSAGE_OPAQUE_ELEMENT_SMDATA_DATA_VERSION)) { /* Get the element by element id. */ *get_element_ptr = opaque_element_table_header; *get_element_len = current_element_len; } } /* Move to the next element. */ opaque_element_table_header = (const struct spdm_secured_message_opaque_element_table_header*) ((const uint8_t*) opaque_element_table_header + current_element_len); } /* Ensure the data size is correct. */ if (data_element_size != total_element_len) { goto exit; } status = 0; exit: return status; } /** * Process the opaque data supported version data and find a common secure message version. * * @param state SPDM state. * @param secure_message_version_num Local secure message version number(s). * @param secure_message_version_num_count Secure message version number count. * @param data_in Opaque data buffer. * @param data_in_size Opaque data buffer size. * * @return 0 if the opaque data supported version data is valid, error code otherwise. */ static int spdm_process_opaque_data_supported_version_data (struct spdm_state *state, const struct spdm_version_num_entry *secure_message_version_num, uint8_t secure_message_version_num_count, const void *data_in, size_t data_in_size) { int status; const struct spdm_secured_message_opaque_element_table_header *opaque_element_table_header; const struct spdm_secured_message_opaque_element_supported_version *opaque_element_support_version; const struct spdm_version_number *versions_list; struct spdm_version_number common_version = {0}; struct spdm_version_number temp_version; uint8_t version_count; const void *get_element_ptr; size_t get_element_len; uint8_t local_ver_idx, peer_ver_idx; uint8_t local_ver, peer_ver; get_element_ptr = NULL; if (secure_message_version_num_count == 0) { return 0; } if (data_in_size < spdm_get_untrusted_opaque_data_supported_version_data_size (spdm_get_connection_version ( state), 1)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; goto exit; } status = spdm_get_element_from_opaque_data (state, data_in_size, data_in, SPDM_REGISTRY_ID_DMTF, SPDM_SECURED_MESSAGE_OPAQUE_ELEMENT_SMDATA_ID_SUPPORTED_VERSION, &get_element_ptr, &get_element_len); if (status != 0) { goto exit; } if (get_element_ptr == NULL) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_OPAQUE_DATA_FORMAT; goto exit; } opaque_element_table_header = (const struct spdm_secured_message_opaque_element_table_header*) get_element_ptr; /* Check for supported version data. */ opaque_element_support_version = (const void*) (opaque_element_table_header + 1); if ((const uint8_t*) opaque_element_support_version + sizeof (struct spdm_secured_message_opaque_element_supported_version) > (const uint8_t*) opaque_element_table_header + get_element_len) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_OPAQUE_DATA_FORMAT; goto exit; } if (opaque_element_support_version->version_count == 0) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_OPAQUE_DATA_FORMAT; goto exit; } version_count = opaque_element_support_version->version_count; if ((opaque_element_table_header->vendor_len != 0) || (opaque_element_table_header->opaque_element_data_len != sizeof (struct spdm_secured_message_opaque_element_supported_version) + sizeof (struct spdm_version_number) * version_count)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_OPAQUE_DATA_FORMAT; goto exit; } versions_list = (const void*) (opaque_element_support_version + 1); if (((const uint8_t*) versions_list + (sizeof (struct spdm_version_number) * version_count)) > ((const uint8_t*) opaque_element_table_header + get_element_len)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_OPAQUE_DATA_FORMAT; goto exit; } /* Find a common secure message version. */ for (local_ver_idx = 0; local_ver_idx < secure_message_version_num_count; local_ver_idx++) { local_ver = SPDM_MAKE_VERSION (secure_message_version_num[local_ver_idx].major_version, secure_message_version_num[local_ver_idx].minor_version); for (peer_ver_idx = 0; peer_ver_idx < version_count; peer_ver_idx++) { memcpy (&temp_version, &versions_list[peer_ver_idx], sizeof (temp_version)); peer_ver = SPDM_MAKE_VERSION (temp_version.major_version, temp_version.minor_version); if (local_ver == peer_ver) { memcpy (&common_version, &versions_list[peer_ver_idx], sizeof (common_version)); break; } } } if (SPDM_MAKE_VERSION (common_version.major_version, common_version.minor_version) == 0) { status = CMD_HANDLER_SPDM_RESPONDER_UNSUPPORTED_CAPABILITY; goto exit; } state->connection_info.secure_message_version = common_version; exit: return status; } /** * Build the opaque data version selection data. * * @param spdm_responder SPDM responder instance. * @param data_out A pointer to the buffer to store the opaque data version selection. **/ static void spdm_build_opaque_data_version_selection_data ( const struct cmd_interface_spdm_responder *spdm_responder, void *data_out) { size_t final_data_size; struct spdm_secured_message_general_opaque_data_table_header* secured_general_opaque_data_table_header; struct spdm_general_opaque_data_table_header *general_opaque_data_table_header; struct spdm_secured_message_opaque_element_table_header *opaque_element_table_header; struct spdm_secured_message_opaque_element_version_selection *opaque_element_version_section; void *end; struct spdm_state *state = spdm_responder->state; if (spdm_responder->secure_message_version_num_count == 0) { return; } final_data_size = spdm_get_opaque_data_version_selection_data_size (spdm_get_connection_version (state), spdm_responder->secure_message_version_num_count); if (spdm_get_connection_version (state) >= SPDM_VERSION_1_2) { general_opaque_data_table_header = data_out; general_opaque_data_table_header->total_elements = 1; buffer_unaligned_write24 (general_opaque_data_table_header->reserved, 0); opaque_element_table_header = (void*) (general_opaque_data_table_header + 1); } else { secured_general_opaque_data_table_header = data_out; secured_general_opaque_data_table_header->spec_id = SPDM_SECURED_MESSAGE_OPAQUE_DATA_SPEC_ID; secured_general_opaque_data_table_header->opaque_version = SPDM_SECURED_MESSAGE_OPAQUE_VERSION; secured_general_opaque_data_table_header->total_elements = 1; secured_general_opaque_data_table_header->reserved = 0; opaque_element_table_header = (void*) (secured_general_opaque_data_table_header + 1); } opaque_element_table_header->id = SPDM_REGISTRY_ID_DMTF; opaque_element_table_header->vendor_len = 0; opaque_element_table_header->opaque_element_data_len = sizeof (struct spdm_secured_message_opaque_element_version_selection); opaque_element_version_section = (void*) (opaque_element_table_header + 1); opaque_element_version_section->sm_data_version = SPDM_SECURED_MESSAGE_OPAQUE_ELEMENT_SMDATA_DATA_VERSION; opaque_element_version_section->sm_data_id = SPDM_SECURED_MESSAGE_OPAQUE_ELEMENT_SMDATA_ID_VERSION_SELECTION; opaque_element_version_section->selected_version = state->connection_info.secure_message_version; /* Zero Padding */ end = opaque_element_version_section + 1; memset (end, 0, (size_t) data_out + final_data_size - (size_t) end); } /** * Reset transcript(s) in the Transcript Manager according to the request/response code. * * @param state SPDM state. * @param transcript_manager SPDM transcript manager. * @param req_rsp_code The SPDM request/response code. */ static void spdm_reset_transcript_via_request_code (struct spdm_state *state, const struct spdm_transcript_manager *transcript_manager, uint8_t req_rsp_code) { /* Any requests other than SPDM_GET_MEASUREMENTS resets L1/L2 */ if (req_rsp_code != SPDM_REQUEST_GET_MEASUREMENTS) { transcript_manager->reset_transcript (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_L1L2, false, SPDM_MAX_SESSION_COUNT); } /* If the Requester issued GET_MEASUREMENTS or KEY_EXCHANGE or FINISH or PSK_EXCHANGE * or PSK_FINISH or KEY_UPDATE or HEARTBEAT or GET_ENCAPSULATED_REQUEST or * DELIVER_ENCAPSULATED_RESPONSE or END_SESSION request(s) and skipped CHALLENGE completion, * M1 and M2 are reset to null. */ switch (req_rsp_code) { case SPDM_REQUEST_KEY_EXCHANGE: case SPDM_REQUEST_GET_MEASUREMENTS: case SPDM_REQUEST_FINISH: case SPDM_REQUEST_PSK_EXCHANGE: case SPDM_REQUEST_PSK_FINISH: case SPDM_REQUEST_KEY_UPDATE: case SPDM_REQUEST_HEARTBEAT: case SPDM_REQUEST_GET_ENCAPSULATED_REQUEST: case SPDM_REQUEST_END_SESSION: case SPDM_REQUEST_DELIVER_ENCAPSULATED_RESPONSE: if (state->connection_info.connection_state < SPDM_CONNECTION_STATE_AUTHENTICATED) { transcript_manager->reset_transcript (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_M1M2, false, SPDM_MAX_SESSION_COUNT); } break; case SPDM_REQUEST_GET_DIGESTS: transcript_manager->reset_transcript (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_M1M2, false, SPDM_MAX_SESSION_COUNT); break; default: break; } } /** * Get the list of the cerficates in slot 0. * * @param key_manager RIoT device key manager. * @param cert_count_out Number of certificates in the chain. * @param cert Certificate chain list in DER format. * @param keys_out On success, ptr. to the RIoT keys. This must be released by the caller by calling * riot_key_manager_release_riot_keys. * * @return 0 if certificate list was retrieved successfully or an error code. */ static int spdm_get_certificate_list (const struct riot_key_manager *key_manager, uint8_t *cert_count_out, struct der_cert *cert, const struct riot_keys **keys_out) { int status = 0; const struct der_cert *int_ca; const struct der_cert *root_ca; const struct riot_keys *keys = NULL; uint8_t cert_count = 0; root_ca = riot_key_manager_get_root_ca (key_manager); if (root_ca != NULL) { cert[cert_count] = *root_ca; cert_count += 1; } int_ca = riot_key_manager_get_intermediate_ca (key_manager); if (int_ca != NULL) { cert[cert_count] = *int_ca; cert_count += 1; } keys = riot_key_manager_get_riot_keys (key_manager); if ((keys->devid_cert == NULL) || (keys->devid_cert_length == 0)) { status = CMD_HANDLER_SPDM_RESPONDER_DEVICE_CERT_NOT_AVAILABLE; goto exit; } cert[cert_count].cert = keys->devid_cert; cert[cert_count].length = keys->devid_cert_length; cert_count += 1; if ((keys->alias_cert == NULL) || (keys->alias_cert_length == 0)) { status = CMD_HANDLER_SPDM_RESPONDER_ALIAS_CERT_NOT_AVAILABLE; goto exit; } cert[cert_count].cert = keys->alias_cert; cert[cert_count].length = keys->alias_cert_length; cert_count += 1; *cert_count_out = cert_count; *keys_out = keys; keys = NULL; exit: if (keys != NULL) { riot_key_manager_release_riot_keys (key_manager, keys); } return status; } /** * Get the digest of the spdm certificate chain. * * @param key_manager RIoT device key manager. * @param hash_engine Hash engine for hashing operations. * @param hash_type Hash type. * @param digest Buffer to hold the digest. * * @return 0 if the digest was calculated successfully or an error code. */ static int spdm_get_certificate_chain_digest (const struct riot_key_manager *key_manager, const struct hash_engine *hash_engine, enum hash_type hash_type, uint8_t *digest) { int status; uint8_t i_cert; struct der_cert cert[SPDM_MAX_CERT_COUNT_IN_CHAIN]; uint8_t cert_count; struct spdm_cert_chain cert_chain; uint32_t cert_chain_length; const struct riot_keys *keys = NULL; int hash_size; bool cancel_hash = false; /* Retrieve the certificate chain. */ status = spdm_get_certificate_list (key_manager, &cert_count, cert, &keys); if (status != 0) { goto exit; } hash_size = hash_get_hash_length (hash_type); if (hash_size == HASH_ENGINE_UNKNOWN_HASH) { status = HASH_ENGINE_UNKNOWN_HASH; goto exit; } /* Hash the root cert. */ status = hash_calculate (hash_engine, hash_type, cert[0].cert, cert[0].length, cert_chain.root_hash, hash_size); if (ROT_IS_ERROR (status)) { goto exit; } status = 0; /* Calculate the cert chain length. */ cert_chain_length = 0; for (i_cert = 0; i_cert < cert_count; ++i_cert) { cert_chain_length += cert[i_cert].length; } cert_chain.header.length = spdm_get_digests_cert_chain_length (hash_size, cert_chain_length); cert_chain.header.reserved = 0; /* Start the cert chain hash. */ status = hash_start_new_hash (hash_engine, hash_type); if (status != 0) { goto exit; } cancel_hash = true; /* Hash the header of the cert chain and the root cert digest. */ status = hash_engine->update (hash_engine, (uint8_t*) &cert_chain, offsetof (struct spdm_cert_chain, root_hash) + hash_size); if (status != 0) { goto exit; } /* Hash the individual certs in the cert chain. */ for (i_cert = 0; i_cert < cert_count; i_cert++) { status = hash_engine->update (hash_engine, cert[i_cert].cert, cert[i_cert].length); if (status != 0) { goto exit; } } status = hash_engine->finish (hash_engine, digest, hash_size); if (status != 0) { goto exit; } cancel_hash = false; exit: if (keys != NULL) { riot_key_manager_release_riot_keys (key_manager, keys); } if (cancel_hash == true) { hash_engine->cancel (hash_engine); } return status; } /** * SPDM signature context as described in Section 15 of the SPDM specification. */ static const struct spdm_signing_context_str spdm_signing_context_str_table[] = { { .is_requester = false, .op_code = SPDM_RESPONSE_CHALLENGE, .context = SPDM_CHALLENGE_AUTH_SIGN_CONTEXT, .context_size = SPDM_CHALLENGE_AUTH_SIGN_CONTEXT_SIZE, .zero_pad_size = 36 - SPDM_CHALLENGE_AUTH_SIGN_CONTEXT_SIZE }, { .is_requester = true, .op_code = SPDM_RESPONSE_CHALLENGE, .context = SPDM_MUT_CHALLENGE_AUTH_SIGN_CONTEXT, .context_size = SPDM_MUT_CHALLENGE_AUTH_SIGN_CONTEXT_SIZE, .zero_pad_size = 36 - SPDM_MUT_CHALLENGE_AUTH_SIGN_CONTEXT_SIZE }, { .is_requester = false, .op_code = SPDM_RESPONSE_GET_MEASUREMENTS, .context = SPDM_MEASUREMENTS_SIGN_CONTEXT, .context_size = SPDM_MEASUREMENTS_SIGN_CONTEXT_SIZE, .zero_pad_size = 36 - SPDM_MEASUREMENTS_SIGN_CONTEXT_SIZE }, { .is_requester = false, .op_code = SPDM_RESPONSE_KEY_EXCHANGE, .context = SPDM_KEY_EXCHANGE_RESPONSE_SIGN_CONTEXT, .context_size = SPDM_KEY_EXCHANGE_RESPONSE_SIGN_CONTEXT_SIZE, .zero_pad_size = 36 - SPDM_KEY_EXCHANGE_RESPONSE_SIGN_CONTEXT_SIZE }, { .is_requester = true, .op_code = SPDM_REQUEST_FINISH, .context = SPDM_FINISH_SIGN_CONTEXT, .context_size = SPDM_FINISH_SIGN_CONTEXT_SIZE, .zero_pad_size = 36 - SPDM_FINISH_SIGN_CONTEXT_SIZE }, }; /** * Create a SPDM signing context, which is required since SPDM 1.2. * * @param state SPDM state. * @param op_code SPDM request/response opcode. * @param is_requester True if the message is from the requester, false if from the responder. * @param spdm_signing_context SPDM signing context. */ static void spdm_create_signing_context (struct spdm_state *state, uint8_t op_code, bool is_requester, char *spdm_signing_context) { uint8_t index; struct spdm_version_number version = state->connection_info.version; for (index = 0; index < 4; index++) { memcpy (spdm_signing_context, SPDM_VERSION_1_2_SIGNING_PREFIX_CONTEXT, SPDM_VERSION_1_2_SIGNING_PREFIX_CONTEXT_SIZE); /* Patch the version. */ spdm_signing_context[SPDM_VERSION_1_2_SIGNING_PREFIX_CONTEXT_MAJOR_VERSION_OFFSET] = (char) ('0' + (version.major_version)); spdm_signing_context[SPDM_VERSION_1_2_SIGNING_PREFIX_CONTEXT_MINOR_VERSION_OFFSET] = (char) ('0' + (version.minor_version)); spdm_signing_context[SPDM_VERSION_1_2_SIGNING_PREFIX_CONTEXT_ASTERIX_OFFSET] = (char) ('*'); spdm_signing_context += SPDM_VERSION_1_2_SIGNING_PREFIX_CONTEXT_SIZE; } for (index = 0; index < ARRAY_SIZE (spdm_signing_context_str_table); index++) { if ((spdm_signing_context_str_table[index].is_requester == is_requester) && (spdm_signing_context_str_table[index].op_code == op_code)) { memset (spdm_signing_context, 0, spdm_signing_context_str_table[index].zero_pad_size); memcpy (spdm_signing_context + spdm_signing_context_str_table[index].zero_pad_size, spdm_signing_context_str_table[index].context, spdm_signing_context_str_table[index].context_size); return; } } } /** * Generate a SPDM response signature. * * @param state SPDM state. * @param key_manager RIoT device key manager. * @param ecc_engine ECC engine. * @param hash_engine Hash engine. * @param op_code SPDM request opcode. * @param message_hash The message hash to be signed. * @param hash_size The size in bytes of the message hash. * @param signature Buffer to store the signature. * @param sig_size The size of the signature buffer. * * @return 0 if signature is generated successfully, error code otherwise. */ static int spdm_responder_data_sign (struct spdm_state *state, const struct riot_key_manager *key_manager, const struct ecc_engine *ecc_engine, const struct hash_engine *hash_engine, uint8_t op_code, const uint8_t *message_hash, size_t hash_size, uint8_t *signature, size_t sig_size) { int status; uint8_t *message; size_t message_size; uint8_t spdm12_signing_context_with_hash[SPDM_VERSION_1_2_SIGNING_CONTEXT_SIZE + HASH_MAX_HASH_LEN]; struct ecc_private_key alias_priv_key; bool release_alias_key = false; uint8_t spdm_version; int sig_size_der; uint8_t sig_der[ECC_DER_ECDSA_MAX_LENGTH] = {0}; uint32_t sig_r_component_size = sig_size >> 1; enum hash_type hash_type; const struct riot_keys *keys = NULL; spdm_version = SPDM_MAKE_VERSION (state->connection_info.version.major_version, state->connection_info.version.minor_version); hash_type = spdm_get_hash_type (state->connection_info.peer_algorithms.base_hash_algo); keys = riot_key_manager_get_riot_keys (key_manager); /* v1.2 (and greater) requires a signing context prepended to the hash. */ if (spdm_version > SPDM_VERSION_1_1) { /* Create the signing context. */ spdm_create_signing_context (state, op_code, false, (char*) spdm12_signing_context_with_hash); /* Copy the hash to the signing context buffer. */ memcpy (&spdm12_signing_context_with_hash[SPDM_VERSION_1_2_SIGNING_CONTEXT_SIZE], message_hash, hash_size); /* Assign message and message_size for signing. */ message = spdm12_signing_context_with_hash; message_size = SPDM_VERSION_1_2_SIGNING_CONTEXT_SIZE + hash_size; /* Calculate the message hash as required by ECDSA. It may not be needed for other algos. */ sig_size_der = ecdsa_sign_message (ecc_engine, hash_engine, hash_type, NULL, keys->alias_key, keys->alias_key_length, message, message_size, sig_der, sizeof (sig_der)); } else { /* SPDM 1.1 and earlier cannot be made FIPS compliant with the current architecture, since * the ECDSA hash and sign happen in different steps. This is not much of a concern since * everything is expected to use at least SPDM 1.2. * * TODO: Perhaps FIPS compliant implementations need to explicitly fail requests using * SPDM 1.1 or earlier? */ status = ecc_engine->init_key_pair (ecc_engine, keys->alias_key, keys->alias_key_length, &alias_priv_key, NULL); if (status != 0) { goto exit; } release_alias_key = true; sig_size_der = ecc_engine->sign (ecc_engine, &alias_priv_key, message_hash, hash_size, NULL, sig_der, sizeof (sig_der)); } if (ROT_IS_ERROR (sig_size_der)) { status = sig_size_der; goto exit; } /* Convert signature from DER encoding to <r,s> format. */ status = ecc_der_decode_ecdsa_signature (sig_der, sig_size_der, signature, &signature[sig_r_component_size], sig_r_component_size); if (status != 0) { goto exit; } exit: buffer_zeroize (sig_der, sizeof (sig_der)); if (release_alias_key == true) { ecc_engine->release_key_pair (ecc_engine, &alias_priv_key, NULL); } if (keys != NULL) { riot_key_manager_release_riot_keys (key_manager, keys); } return status; } /** * Generate the SPDM measurement signature. * * @param transcript_manager SPDM transcript manager. * @param state SPDM state. * @param key_manager RIoT device key manager. * @param ecc_engine ECC engine. * @param hash_engine Hash engine. * @param session Session information. * @param signature Buffer to store the signature. * @param sig_size The size of the signature. * * @return 0 if signature is generated successfully, error code otherwise. */ static int spdm_generate_measurement_signature ( const struct spdm_transcript_manager *transcript_manager, struct spdm_state *state, const struct riot_key_manager *key_manager, const struct ecc_engine *ecc_engine, const struct hash_engine *hash_engine, struct spdm_secure_session *session_info, uint8_t *signature, size_t sig_size) { int status; uint8_t l1l2_hash[HASH_MAX_HASH_LEN]; int l1l2_hash_size; uint8_t session_idx = SPDM_MAX_SESSION_COUNT; if (session_info != NULL) { session_idx = session_info->session_index; } l1l2_hash_size = hash_get_hash_length (spdm_get_hash_type ( state->connection_info.peer_algorithms.base_hash_algo)); if (session_info != NULL) { session_idx = session_info->session_index; } /* Get the L1L2 hash. */ status = transcript_manager->get_hash (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_L1L2, true /* finish hash */, (session_info != NULL), session_idx, l1l2_hash, l1l2_hash_size); /* Reset the L1L2 hash context. */ transcript_manager->reset_transcript (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_L1L2, (session_info != NULL), session_idx); if (status != 0) { goto exit; } /* Sign the L1L2 hash. */ status = spdm_responder_data_sign (state, key_manager, ecc_engine, hash_engine, SPDM_RESPONSE_GET_MEASUREMENTS, l1l2_hash, l1l2_hash_size, signature, sig_size); if (status != 0) { goto exit; } exit: return status; } /** * Generate the KEY_EXCHANGE response signature. * * @param transcript_manager SPDM transcript manager. * @param state SPDM state. * @param key_manager RIoT device key manager. * @param ecc_engine ECC engine. * @param hash_engine Hash engine. * @param session SPDM secure session. * @param signature Buffer to store the signature. * @param sig_size The size of the signature buffer. * * @return 0 if the signature is generated successfully, error code otherwise. */ static int spdm_generate_key_exchange_rsp_signature ( const struct spdm_transcript_manager *transcript_manager, struct spdm_state *state, const struct riot_key_manager *key_manager, const struct ecc_engine *ecc_engine, const struct hash_engine *hash_engine, struct spdm_secure_session *session, uint8_t *signature, uint32_t sig_size) { int status; uint8_t th_hash[HASH_MAX_HASH_LEN]; int th_hash_size; th_hash_size = hash_get_hash_length (spdm_get_hash_type ( state->connection_info.peer_algorithms.base_hash_algo)); /* Get the TH hash; do not complete the hash context as it is needed later. */ status = transcript_manager->get_hash (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_TH, false, true, session->session_index, th_hash, th_hash_size); if (status != 0) { goto exit; } /* Sign the TH hash. */ status = spdm_responder_data_sign (state, key_manager, ecc_engine, hash_engine, SPDM_RESPONSE_KEY_EXCHANGE, th_hash, th_hash_size, signature, sig_size); if (status != 0) { goto exit; } exit: return status; } /* * Calculate the TH HMAC with the response finished_key. * * @param transcript_manager SPDM transcript manager. * @param state SPDM state. * @param ecc_engine ECC engine. * @param hash_engine Hash engine. * @param session SPDM session. * @param th_hmac_buffer Buffer to store the TH HMAC * * @return 0 if the current TH HMAC is calculated successfully, error code otherwise. */ static int spdm_calculate_th_hmac_for_key_exchange_rsp ( const struct spdm_transcript_manager *transcript_manager, struct spdm_state *state, const struct ecc_engine *ecc_engine, const struct hash_engine *hash_engine, struct spdm_secure_session *session, uint8_t *th_hmac_buffer) { int status; uint8_t th_hash[HASH_MAX_HASH_LEN]; int hash_size; enum hash_type hash_type; UNUSED (ecc_engine); hash_type = spdm_get_hash_type (state->connection_info.peer_algorithms.base_hash_algo); hash_size = hash_get_hash_length (hash_type); /* Get the TH hash; do not complete the hash as it is needed later. */ status = transcript_manager->get_hash (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_TH, false, true, session->session_index, th_hash, hash_size); if (status != 0) { goto exit; } /* Calculate the TH HMAC. */ status = hash_generate_hmac (hash_engine, session->handshake_secret.response_finished_key, hash_size, th_hash, hash_size, (enum hmac_hash) hash_type, th_hmac_buffer, hash_size); if (status != 0) { goto exit; } exit: return status; } /** * Verify the reqester HMAC. * * @param transcript_manager SPDM transcript manager. * @param hash_engine Hash engine. * @param session SPDM session state. * @param hmac Requester HMAC. * @param hmac_size Requester HMAC size. * * @return 0 if the HMAC is verified successfully, error code otherwise. */ static int spdm_verify_finish_req_hmac (const struct spdm_transcript_manager *transcript_manager, const struct hash_engine *hash_engine, struct spdm_secure_session *session, const uint8_t *hmac, size_t hmac_size) { int status; uint8_t th_hash[HASH_MAX_HASH_LEN]; uint8_t hmac_computed[HASH_MAX_HASH_LEN]; /* Get the TH hash; do not complete the hash as it is needed later. */ status = transcript_manager->get_hash (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_TH, false, true, session->session_index, th_hash, session->hash_size); if (status != 0) { goto exit; } /* Generate the HMAC with the Requester Finished key. */ status = hash_generate_hmac (hash_engine, session->handshake_secret.request_finished_key, session->hash_size, th_hash, session->hash_size, (enum hmac_hash) spdm_get_hash_type (session->base_hash_algo), hmac_computed, hmac_size); if (status != 0) { goto exit; } /* Compare the HMAC values. */ if (memcmp (hmac, hmac_computed, hmac_size) != 0) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; goto exit; } exit: return status; } /** * Process SPDM GET_VERSION request. * * @param spdm_responder SPDM responder instance. * @param request GET_VERSION request to process. * * @return 0 if request processed successfully (including SPDM error msg) or an error code. */ int spdm_get_version (const struct cmd_interface_spdm_responder *spdm_responder, struct cmd_interface_msg *request) { int status; int spdm_error; struct spdm_get_version_request *rq; struct spdm_get_version_response *rsp; const struct spdm_transcript_manager *transcript_manager; struct spdm_state *state; struct spdm_secure_session_manager *session_manager; uint16_t minor_ver_in_error_msg; if ((spdm_responder == NULL) || (request == NULL)) { return CMD_HANDLER_SPDM_RESPONDER_INVALID_ARGUMENT; } rq = (struct spdm_get_version_request*) request->payload; state = spdm_responder->state; transcript_manager = spdm_responder->transcript_manager; session_manager = spdm_responder->session_manager; minor_ver_in_error_msg = state->connection_info.version.minor_version; if (request->payload_length < sizeof (struct spdm_get_version_request)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } /* * If the GET_VERSION request is improperly formed, then the version of the error message * must be 1.0, regardless of what the negotiated version is. */ if (SPDM_MAKE_VERSION (rq->header.spdm_major_version, rq->header.spdm_minor_version) != SPDM_VERSION_1_0) { minor_ver_in_error_msg = 0; status = CMD_HANDLER_SPDM_RESPONDER_VERSION_MISMATCH; spdm_error = SPDM_ERROR_VERSION_MISMATCH; goto exit; } /* Receiving a GET_VERSION resets the need to resynchronize. */ if ((state->response_state == SPDM_RESPONSE_STATE_NEED_RESYNC) || (state->response_state == SPDM_RESPONSE_STATE_PROCESSING_ENCAP)) { state->response_state = SPDM_RESPONSE_STATE_NORMAL; } if (state->response_state != SPDM_RESPONSE_STATE_NORMAL) { spdm_handle_response_state (state, &spdm_error); status = CMD_HANDLER_SPDM_RESPONDER_INTERNAL_ERROR; goto exit; } /* Process the request. */ /* Reset transcript manager state. */ transcript_manager->reset (transcript_manager); /* Append request to VCA buffer. */ status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_VCA, (const uint8_t*) rq, sizeof (struct spdm_get_version_request), false, SPDM_MAX_SESSION_COUNT); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Initialize the SPDM state. No error check as this function call cannot fail. */ spdm_init_state (state); /* Reset any in-progress session(s). */ if (session_manager) { session_manager->reset (session_manager); } /* Contruct the response. */ rsp = (struct spdm_get_version_response*) request->payload; rsp->header.req_rsp_code = SPDM_RESPONSE_GET_VERSION; rsp->reserved = 0; rsp->reserved2 = 0; rsp->reserved3 = 0; /* Copy the supported version(s) to the response buffer. */ rsp->version_num_entry_count = spdm_responder->version_num_count; memcpy ((void*) spdm_get_version_resp_version_table (rsp), (void*) spdm_responder->version_num, spdm_responder->version_num_count * sizeof (struct spdm_version_num_entry)); cmd_interface_msg_set_message_payload_length (request, spdm_get_version_resp_length (rsp)); /* Append response to the VCA buffer. */ status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_VCA, (const uint8_t*) rsp, request->payload_length, false, SPDM_MAX_SESSION_COUNT); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Update the connection state */ spdm_set_connection_state (state, SPDM_CONNECTION_STATE_AFTER_VERSION); exit: if (status != 0) { spdm_generate_error_response (request, minor_ver_in_error_msg, spdm_error, 0x00, NULL, 0, SPDM_REQUEST_GET_VERSION, status); } return 0; } /** * Construct SPDM get version request. * * @param buf Output buffer for the generated request data. * @param buf_len Maximum size of buffer. * * @return Length of the generated request data if the request was successfully constructed or an * error code. */ int spdm_generate_get_version_request (uint8_t *buf, size_t buf_len) { struct spdm_get_version_request *rq = (struct spdm_get_version_request*) buf; if (buf == NULL) { return CMD_HANDLER_SPDM_INVALID_ARGUMENT; } if (buf_len < sizeof (struct spdm_get_version_request)) { return CMD_HANDLER_SPDM_BUF_TOO_SMALL; } memset (rq, 0, sizeof (struct spdm_get_version_request)); spdm_populate_header (&rq->header, SPDM_REQUEST_GET_VERSION, 0); return sizeof (struct spdm_get_version_request); } /** * Process a SPDM get version response. * * @param response Response buffer to process. * * @return Response processing completion status, 0 if successful or error code otherwise. */ int spdm_process_get_version_response (struct cmd_interface_msg *response) { struct spdm_get_version_response *resp; if (response == NULL) { return CMD_HANDLER_SPDM_INVALID_ARGUMENT; } resp = (struct spdm_get_version_response*) response->payload; if ((response->payload_length < sizeof (struct spdm_get_version_response)) || (response->payload_length != spdm_get_version_resp_length (resp))) { return CMD_HANDLER_SPDM_BAD_LENGTH; } return 0; } /** * Process SPDM GET_CAPABILITIES request. * * @param spdm_responder SPDM responder instance. * @param request The GET_CAPABILITIES request to process. * * @return 0 if request processed successfully or an error code. */ int spdm_get_capabilities (const struct cmd_interface_spdm_responder *spdm_responder, struct cmd_interface_msg *request) { int status; int spdm_error; struct spdm_protocol_header *header; struct spdm_get_capabilities *req_resp; uint8_t spdm_version; size_t req_resp_size; const struct spdm_transcript_manager *transcript_manager; struct spdm_state *state; const struct spdm_device_capability *local_capabilities; uint16_t minor_ver_in_error_msg; 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; /* Validate request version and save it in the connection info. */ header = (struct spdm_protocol_header*) request->payload; spdm_version = SPDM_MAKE_VERSION (header->spdm_major_version, header->spdm_minor_version); if (spdm_check_request_version_compatibility (state, spdm_responder->version_num, spdm_responder->version_num_count, spdm_version) == false) { minor_ver_in_error_msg = 0; status = CMD_HANDLER_SPDM_RESPONDER_VERSION_MISMATCH; spdm_error = SPDM_ERROR_VERSION_MISMATCH; goto exit; } minor_ver_in_error_msg = state->connection_info.version.minor_version; /* Verify the 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_AFTER_VERSION) { status = CMD_HANDLER_SPDM_RESPONDER_UNEXPECTED_REQUEST; spdm_error = SPDM_ERROR_UNEXPECTED_REQUEST; goto exit; } /* Check request size. */ if ((spdm_version >= SPDM_VERSION_1_2) && (request->payload_length >= sizeof (struct spdm_get_capabilities))) { req_resp_size = sizeof (struct spdm_get_capabilities); } else if ((spdm_version == SPDM_VERSION_1_1) && (request->payload_length >= sizeof (struct spdm_get_capabilities_1_1))) { req_resp_size = sizeof (struct spdm_get_capabilities_1_1); } else { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } /* Process the request. */ req_resp = (struct spdm_get_capabilities*) request->payload; /* Check for request flag compatibility. */ if (spdm_check_request_flag_compatibility (req_resp->base_capabilities.flags, spdm_version) == false) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } /* Check the data transfer size. */ if (spdm_version >= SPDM_VERSION_1_2) { if ((req_resp->data_transfer_size < SPDM_MIN_DATA_TRANSFER_SIZE_VERSION_1_2) || (req_resp->data_transfer_size > req_resp->max_spdm_msg_size)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } if ((req_resp->base_capabilities.flags.chunk_cap == 0) && (req_resp->data_transfer_size != req_resp->max_spdm_msg_size)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } } /* Check the CT Exponent. */ if (spdm_version >= SPDM_VERSION_1_1) { if (req_resp->base_capabilities.ct_exponent > SPDM_MAX_CT_EXPONENT) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } } /* Update SPDM version in transcript manager to make sure proper behavior */ transcript_manager->set_spdm_version (spdm_responder->transcript_manager, spdm_version); /* Reset the transcript manager state as per the request code. */ spdm_reset_transcript_via_request_code (state, transcript_manager, SPDM_REQUEST_GET_CAPABILITIES); /* Append the request to the VCA buffer. */ status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_VCA, (const uint8_t*) req_resp, req_resp_size, false, SPDM_MAX_SESSION_COUNT); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Save the requester capabilities in the connection info. */ state->connection_info.peer_capabilities.flags = req_resp->base_capabilities.flags; state->connection_info.peer_capabilities.ct_exponent = req_resp->base_capabilities.ct_exponent; if (spdm_version >= SPDM_VERSION_1_2) { state->connection_info.peer_capabilities.data_transfer_size = req_resp->data_transfer_size; state->connection_info.peer_capabilities.max_spdm_msg_size = req_resp->max_spdm_msg_size; } else { state->connection_info.peer_capabilities.data_transfer_size = 0; state->connection_info.peer_capabilities.max_spdm_msg_size = 0; } /* Response phase. */ /* Contruct the response. */ memset (req_resp, 0, req_resp_size); spdm_populate_header (&req_resp->base_capabilities.header, SPDM_RESPONSE_GET_CAPABILITIES, SPDM_GET_MINOR_VERSION (spdm_version)); req_resp->base_capabilities.reserved = 0; req_resp->base_capabilities.reserved2 = 0; req_resp->base_capabilities.reserved3 = 0; req_resp->base_capabilities.reserved4 = 0; req_resp->base_capabilities.ct_exponent = local_capabilities->ct_exponent; req_resp->base_capabilities.flags = local_capabilities->flags; if (spdm_version >= SPDM_VERSION_1_2) { req_resp->data_transfer_size = local_capabilities->data_transfer_size; req_resp->max_spdm_msg_size = local_capabilities->max_spdm_msg_size; } /* Append the reponse to the VCA buffer. */ status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_VCA, (const uint8_t*) req_resp, req_resp_size, false, SPDM_MAX_SESSION_COUNT); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Set the payload length. */ cmd_interface_msg_set_message_payload_length (request, req_resp_size); /* Update connection state */ spdm_set_connection_state (state, SPDM_CONNECTION_STATE_AFTER_CAPABILITIES); exit: if (status != 0) { spdm_generate_error_response (request, minor_ver_in_error_msg, spdm_error, 0x00, NULL, 0, SPDM_REQUEST_GET_CAPABILITIES, status); } return 0; } /** * Construct SPDM get capabilities request. * * @param buf Output buffer for the generated request data. * @param buf_len Maximum size of buffer. * @param spdm_minor_version SPDM minor version to utilize in request. * * @return Length of the generated request data if the request was successfully constructed or an * error code. */ int spdm_generate_get_capabilities_request (uint8_t *buf, size_t buf_len, uint8_t spdm_minor_version) { struct spdm_get_capabilities *rq = (struct spdm_get_capabilities*) buf; if (buf == NULL) { return CMD_HANDLER_SPDM_INVALID_ARGUMENT; } if (spdm_minor_version < 1) { if (buf_len < sizeof (struct spdm_get_capabilities_1_0)) { return CMD_HANDLER_SPDM_BUF_TOO_SMALL; } memset (rq, 0, sizeof (struct spdm_get_capabilities_1_0)); } else if (spdm_minor_version < 2) { if (buf_len < sizeof (struct spdm_get_capabilities_1_1)) { return CMD_HANDLER_SPDM_BUF_TOO_SMALL; } memset (rq, 0, sizeof (struct spdm_get_capabilities_1_1)); } else { if (buf_len < sizeof (struct spdm_get_capabilities)) { return CMD_HANDLER_SPDM_BUF_TOO_SMALL; } memset (rq, 0, sizeof (struct spdm_get_capabilities)); } spdm_populate_header (&rq->base_capabilities.header, SPDM_REQUEST_GET_CAPABILITIES, spdm_minor_version); if (spdm_minor_version > 0) { rq->base_capabilities.ct_exponent = SPDM_MAX_CT_EXPONENT; rq->base_capabilities.flags.cache_cap = SPDM_REQUESTER_CACHE_CAP; rq->base_capabilities.flags.cert_cap = SPDM_REQUESTER_CERT_CAP; rq->base_capabilities.flags.chal_cap = SPDM_REQUESTER_CHAL_CAP; rq->base_capabilities.flags.meas_cap = SPDM_REQUESTER_MEAS_CAP; rq->base_capabilities.flags.meas_fresh_cap = SPDM_REQUESTER_MEAS_FRESH_CAP; rq->base_capabilities.flags.encrypt_cap = SPDM_REQUESTER_ENCRYPT_CAP; rq->base_capabilities.flags.mac_cap = SPDM_REQUESTER_MAC_CAP; rq->base_capabilities.flags.mut_auth_cap = SPDM_REQUESTER_MUT_AUTH_CAP; rq->base_capabilities.flags.key_ex_cap = SPDM_REQUESTER_KEY_EX_CAP; rq->base_capabilities.flags.psk_cap = SPDM_REQUESTER_PSK_CAP; rq->base_capabilities.flags.encap_cap = SPDM_REQUESTER_ENCAP_CAP; rq->base_capabilities.flags.hbeat_cap = SPDM_REQUESTER_HBEAT_CAP; rq->base_capabilities.flags.key_upd_cap = SPDM_REQUESTER_KEY_UPD_CAP; rq->base_capabilities.flags.handshake_in_the_clear_cap = SPDM_REQUESTER_HANDSHAKE_IN_THE_CLEAR_CAP; rq->base_capabilities.flags.pub_key_id_cap = SPDM_REQUESTER_PUB_KEY_ID_CAP; rq->base_capabilities.flags.chunk_cap = SPDM_REQUESTER_CHUNK_CAP; rq->base_capabilities.flags.alias_cert_cap = SPDM_REQUESTER_ALIAS_CERT_CAP; } if (spdm_minor_version < 1) { return sizeof (struct spdm_get_capabilities_1_0); } else if (spdm_minor_version < 2) { return sizeof (struct spdm_get_capabilities_1_1); } else { rq->data_transfer_size = MCTP_BASE_PROTOCOL_MAX_MESSAGE_BODY; rq->max_spdm_msg_size = MCTP_BASE_PROTOCOL_MAX_MESSAGE_BODY; return sizeof (struct spdm_get_capabilities); } } /** * Process a SPDM get capabilities response. * * @param response Response buffer to process. * * @return Response processing completion status, 0 if successful or error code otherwise. */ int spdm_process_get_capabilities_response (struct cmd_interface_msg *response) { struct spdm_get_capabilities *resp; if (response == NULL) { return CMD_HANDLER_SPDM_INVALID_ARGUMENT; } resp = (struct spdm_get_capabilities*) response->payload; if (resp->base_capabilities.header.spdm_minor_version < 2) { if (response->payload_length != sizeof (struct spdm_get_capabilities_1_1)) { return CMD_HANDLER_SPDM_BAD_LENGTH; } } else { if (response->payload_length != sizeof (struct spdm_get_capabilities)) { return CMD_HANDLER_SPDM_BAD_LENGTH; } } return 0; } /** * Constructs a NEGOTIATE_ALGORITHMS response. * * @param state SPDM state. * @param local_capabilities Local SPDM device capabilities. * @param local_device_algorithms Local SPDM device algorithms and their priority order. * @param rq NEGOTIATE_ALGORITHMS request to process. * @param resp_no_ext_alg NEGOTIATE_ALGORITHMS response to be filled. * @param spdm_error SPDM error code. * * @return 0 if response was populated successfully or an error code. */ static int spdm_negotiate_algorithms_construct_response (struct spdm_state *state, const struct spdm_device_capability *local_capabilities, const struct spdm_local_device_algorithms *local_device_algorithms, struct spdm_negotiate_algorithms_request *rq, struct spdm_negotiate_algorithms_response_no_ext_alg *resp_no_ext_alg, int *spdm_error) { int status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; size_t response_size; uint8_t spdm_version; struct spdm_algorithm_request *algstruct_table; size_t i_algstruct; struct spdm_negotiate_algorithms_response *resp = (struct spdm_negotiate_algorithms_response*) resp_no_ext_alg; const struct spdm_device_algorithms *local_algorithms; const struct spdm_local_device_algorithms_priority_table *local_algo_priority_table; uint32_t measurement_hash_algo; *spdm_error = SPDM_ERROR_INVALID_REQUEST; local_algorithms = &local_device_algorithms->device_algorithms; local_algo_priority_table = &local_device_algorithms->algorithms_priority_table; /* Construct the response. */ memset (resp, 0, sizeof (struct spdm_negotiate_algorithms_response_no_ext_alg)); resp->header.spdm_major_version = rq->header.spdm_major_version; resp->header.spdm_minor_version = rq->header.spdm_minor_version; resp->num_alg_structure_tables = rq->num_alg_structure_tables; /* Respond with the same number of Algorithms Structure Tables as requested. */ response_size = spdm_negotiate_algorithms_rsp_size (rq); resp->header.req_rsp_code = SPDM_RESPONSE_NEGOTIATE_ALGORITHMS; resp->reserved = 0; resp->length = (uint16_t) response_size; /* Save requester algorithms in connection info. */ state->connection_info.peer_algorithms.measurement_spec = rq->measurement_specification; if (rq->measurement_specification != 0) { /* Measurement hash algorithm is a responder selected value. It is not negotiated. */ measurement_hash_algo = local_algorithms->measurement_hash_algo; } else { measurement_hash_algo = 0; } state->connection_info.peer_algorithms.base_asym_algo = rq->base_asym_algo; state->connection_info.peer_algorithms.base_hash_algo = rq->base_hash_algo; /* Process the request algorithm structures. */ spdm_version = SPDM_MAKE_VERSION (rq->header.spdm_major_version, rq->header.spdm_minor_version); algstruct_table = spdm_negotiate_algorithms_req_algstruct_table (rq); for (i_algstruct = 0; i_algstruct < rq->num_alg_structure_tables; ++i_algstruct) { switch (algstruct_table->alg_type) { case SPDM_NEGOTIATE_ALGORITHMS_STRUCT_TABLE_ALG_TYPE_DHE: if (algstruct_table->alg_supported == 0) { goto exit; } resp_no_ext_alg->algstruct_table[i_algstruct].alg_type = algstruct_table->alg_type; resp_no_ext_alg->algstruct_table[i_algstruct].fixed_alg_count = 2; resp_no_ext_alg->algstruct_table[i_algstruct].ext_alg_count = 0; resp_no_ext_alg->algstruct_table[i_algstruct].alg_supported = (uint16_t) spdm_prioritize_algorithm ( local_algo_priority_table->dhe_priority_table, local_algo_priority_table->dhe_priority_table_count, local_algorithms->dhe_named_group, algstruct_table->alg_supported); state->connection_info.peer_algorithms.dhe_named_group = resp_no_ext_alg->algstruct_table[i_algstruct].alg_supported; break; case SPDM_NEGOTIATE_ALGORITHMS_STRUCT_TABLE_ALG_TYPE_AEAD: if (algstruct_table->alg_supported == 0) { goto exit; } resp_no_ext_alg->algstruct_table[i_algstruct].alg_type = algstruct_table->alg_type; resp_no_ext_alg->algstruct_table[i_algstruct].fixed_alg_count = 2; resp_no_ext_alg->algstruct_table[i_algstruct].ext_alg_count = 0; resp_no_ext_alg->algstruct_table[i_algstruct].alg_supported = (uint16_t) spdm_prioritize_algorithm ( local_algo_priority_table->aead_priority_table, local_algo_priority_table->aead_priority_table_count, local_algorithms->aead_cipher_suite, algstruct_table->alg_supported); state->connection_info.peer_algorithms.aead_cipher_suite = resp_no_ext_alg->algstruct_table[i_algstruct].alg_supported; break; case SPDM_NEGOTIATE_ALGORITHMS_STRUCT_TABLE_ALG_TYPE_REQ_BASE_ASYM_ALG: if (algstruct_table->alg_supported == 0) { goto exit; } resp_no_ext_alg->algstruct_table[i_algstruct].alg_type = algstruct_table->alg_type; resp_no_ext_alg->algstruct_table[i_algstruct].fixed_alg_count = 2; resp_no_ext_alg->algstruct_table[i_algstruct].ext_alg_count = 0; resp_no_ext_alg->algstruct_table[i_algstruct].alg_supported = (uint16_t) spdm_prioritize_algorithm ( local_algo_priority_table->req_asym_priority_table, local_algo_priority_table->req_asym_priority_table_count, local_algorithms->req_base_asym_alg, algstruct_table->alg_supported); state->connection_info.peer_algorithms.req_base_asym_alg = resp_no_ext_alg->algstruct_table[i_algstruct].alg_supported; break; case SPDM_NEGOTIATE_ALGORITHMS_STRUCT_TABLE_ALG_TYPE_KEY_SCHEDULE: if (algstruct_table->alg_supported == 0) { goto exit; } resp_no_ext_alg->algstruct_table[i_algstruct].alg_type = algstruct_table->alg_type; resp_no_ext_alg->algstruct_table[i_algstruct].fixed_alg_count = 2; resp_no_ext_alg->algstruct_table[i_algstruct].ext_alg_count = 0; resp_no_ext_alg->algstruct_table[i_algstruct].alg_supported = (uint16_t) spdm_prioritize_algorithm ( local_algo_priority_table->key_schedule_priority_table, local_algo_priority_table->key_schedule_priority_table_count, local_algorithms->key_schedule, algstruct_table->alg_supported); state->connection_info.peer_algorithms.key_schedule = resp_no_ext_alg->algstruct_table[i_algstruct].alg_supported; break; } /* Go to the next algstruct_table entry. */ algstruct_table = spdm_negotiate_algorithms_get_next_alg_struct_table_entry (algstruct_table); } if ((local_capabilities->flags.meas_cap == SPDM_MEASUREMENT_RSP_CAP_MEASUREMENTS_WITHOUT_SIG) || (local_capabilities->flags.meas_cap == SPDM_MEASUREMENT_RSP_CAP_MEASUREMENTS_WITH_SIG)) { resp->measurement_specification = (uint8_t) spdm_prioritize_algorithm ( local_algo_priority_table->measurement_spec_priority_table, local_algo_priority_table->measurement_spec_priority_table_count, local_algorithms->measurement_spec, state->connection_info.peer_algorithms.measurement_spec); /* Measurement hash algorithm is not negotiated but rather selected by the responder. * Thus, there is no priority table for measurement hash algorithm. */ resp->measurement_hash_algo = spdm_prioritize_algorithm (NULL, 0, local_algorithms->measurement_hash_algo, measurement_hash_algo); } else { resp->measurement_specification = 0; resp->measurement_hash_algo = 0; } state->connection_info.peer_algorithms.measurement_spec = resp->measurement_specification; state->connection_info.peer_algorithms.measurement_hash_algo = resp->measurement_hash_algo; resp->base_asym_sel = spdm_prioritize_algorithm (local_algo_priority_table->asym_priority_table, local_algo_priority_table->asym_priority_table_count, local_algorithms->base_asym_algo, state->connection_info.peer_algorithms.base_asym_algo); state->connection_info.peer_algorithms.base_asym_algo = resp->base_asym_sel; resp->base_hash_sel = spdm_prioritize_algorithm (local_algo_priority_table->hash_priority_table, local_algo_priority_table->hash_priority_table_count, local_algorithms->base_hash_algo, state->connection_info.peer_algorithms.base_hash_algo); state->connection_info.peer_algorithms.base_hash_algo = resp->base_hash_sel; if (spdm_version >= SPDM_VERSION_1_2) { resp->other_params_selection.opaque_data_format = (uint8_t) spdm_prioritize_algorithm ( local_algo_priority_table->other_params_support_priority_table, local_algo_priority_table->other_params_support_priority_table_count, local_algorithms->other_params_support.opaque_data_format, rq->other_params_support.opaque_data_format); state->connection_info.peer_algorithms.other_params_support.opaque_data_format = resp->other_params_selection.opaque_data_format; } status = 0; *spdm_error = SPDM_ERROR_RESERVED; exit: return status; } /** * Process the SPDM NEGOTIATE_ALGORITHMS request. * * @param spdm_responder SPDM responder instance. * @param request NEGOTIATE_ALGORITHMS request to process. * * @return 0 if request processed successfully or an error code. */ int spdm_negotiate_algorithms (const struct cmd_interface_spdm_responder *spdm_responder, struct cmd_interface_msg *request) { int status; int spdm_error; struct spdm_protocol_header *header; struct spdm_negotiate_algorithms_request *rq; struct spdm_negotiate_algorithms_response_no_ext_alg resp_no_ext_alg; struct spdm_algorithm_request *algstruct_table; size_t i_algstruct; uint8_t spdm_version; uint8_t alg_type_pre; uint16_t ext_alg_total_count = 0; size_t request_size; const struct spdm_transcript_manager *transcript_manager; struct spdm_state *state; const struct spdm_device_capability *local_capabilities; const struct spdm_local_device_algorithms *local_algorithms; 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; local_algorithms = spdm_responder->local_algorithms; /* Validate the request. */ header = (struct spdm_protocol_header*) request->payload; spdm_version = SPDM_MAKE_VERSION (header->spdm_major_version, 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 the 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_AFTER_CAPABILITIES) { status = CMD_HANDLER_SPDM_RESPONDER_UNEXPECTED_REQUEST; spdm_error = SPDM_ERROR_UNEXPECTED_REQUEST; goto exit; } /* Check request size. */ rq = (struct spdm_negotiate_algorithms_request*) request->payload; if ((request->payload_length < sizeof (struct spdm_negotiate_algorithms_request)) || (request->payload_length < spdm_negotiate_algorithms_min_req_length (rq))) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } if (rq->length > SPDM_NEGOTIATE_ALGORITHMS_REQUEST_MAX_LENGTH) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } /* Validate the algorithm structs. */ algstruct_table = spdm_negotiate_algorithms_req_algstruct_table (rq); for (i_algstruct = 0; i_algstruct < rq->num_alg_structure_tables; i_algstruct++) { /* Check if alg_type is valid. */ if ((algstruct_table->alg_type < SPDM_ALG_REQ_STRUCT_ALG_TYPE_DHE) || (algstruct_table->alg_type > SPDM_ALG_REQ_STRUCT_ALG_TYPE_KEY_SCHEDULE)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } /* Check if alg_type is monotonically increasing for subsequent entries. */ if ((i_algstruct != 0) && (algstruct_table->alg_type <= alg_type_pre)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } alg_type_pre = algstruct_table->alg_type; ext_alg_total_count += algstruct_table->ext_alg_count; if (algstruct_table->fixed_alg_count != 2) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } /* Check if payload contains the extended algorithm(s) in the algstruct_table entry. */ if (spdm_negotiate_algorithms_actual_extended_algo_size (rq, algstruct_table) < spdm_negotiate_algorithms_expected_extended_algo_size (algstruct_table)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } /* Go to the next algstruct_table entry. */ algstruct_table = spdm_negotiate_algorithms_get_next_alg_struct_table_entry (algstruct_table); } ext_alg_total_count += (rq->ext_asym_count + rq->ext_hash_count); /* Check the algorithm count and message size. */ if (ext_alg_total_count > SPDM_NEGOTIATE_ALGORITHMS_REQUEST_MAX_EXT_ALG_COUNT_VERSION) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } /* Check Opaque Data Format. */ if (spdm_version >= SPDM_VERSION_1_2) { /* Opaque data format is 4 bits, [1:0] are used to communicate requestor supported * formats and could be any combination of the bits, [3:2] are reserved */ if ((rq->other_params_support.opaque_data_format >> 2) != 0) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } } request_size = (size_t) algstruct_table - (size_t) rq; if (request_size != rq->length) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } /* Construct the response. */ status = spdm_negotiate_algorithms_construct_response (state, local_capabilities, local_algorithms, rq, &resp_no_ext_alg, &spdm_error); if (status != 0) { goto exit; } /* Reset transcript manager state as per request code. */ spdm_reset_transcript_via_request_code (state, transcript_manager, SPDM_REQUEST_NEGOTIATE_ALGORITHMS); /* Append the request to the VCA buffer. */ status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_VCA, (const uint8_t*) rq, request_size, false, SPDM_MAX_SESSION_COUNT); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Append the response to the VCA buffer. */ status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_VCA, (const uint8_t*) &resp_no_ext_alg, resp_no_ext_alg.base.length, false, SPDM_MAX_SESSION_COUNT); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Set the negotiated hash algorithm on the Transcript Manager. */ if (state->connection_info.peer_algorithms.base_hash_algo != 0) { status = transcript_manager->set_hash_algo (transcript_manager, spdm_get_hash_type (state->connection_info.peer_algorithms.base_hash_algo)); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } } /* Copy response in the payload buffer. */ memcpy (request->payload, &resp_no_ext_alg, resp_no_ext_alg.base.length); cmd_interface_msg_set_message_payload_length (request, resp_no_ext_alg.base.length); /* Update the connection state */ spdm_set_connection_state (state, SPDM_CONNECTION_STATE_NEGOTIATED); exit: if (status != 0) { spdm_generate_error_response (request, state->connection_info.version.minor_version, spdm_error, 0x00, NULL, 0, SPDM_REQUEST_NEGOTIATE_ALGORITHMS, status); } return 0; } /** * Construct SPDM negotiate algorithms request. * * @param buf Output buffer for the generated request data. * @param buf_len Maximum size of buffer. * @param base_asym_algo SPDM-enumerated supported asymmetric key signature algorithms. * @param base_hash_algo SPDM-enumerated supported cryptographic hashing algorithms. * @param spdm_minor_version SPDM minor version to utilize in request. * * @return Length of the generated request data if the request was successfully constructed or an * error code. */ int spdm_generate_negotiate_algorithms_request (uint8_t *buf, size_t buf_len, uint32_t base_asym_algo, uint32_t base_hash_algo, uint8_t spdm_minor_version) { struct spdm_negotiate_algorithms_request *rq = (struct spdm_negotiate_algorithms_request*) buf; if (buf == NULL) { return CMD_HANDLER_SPDM_INVALID_ARGUMENT; } if (buf_len < sizeof (struct spdm_negotiate_algorithms_request)) { return CMD_HANDLER_SPDM_BUF_TOO_SMALL; } memset (rq, 0, sizeof (struct spdm_negotiate_algorithms_request)); spdm_populate_header (&rq->header, SPDM_REQUEST_NEGOTIATE_ALGORITHMS, spdm_minor_version); rq->length = sizeof (struct spdm_negotiate_algorithms_request); rq->measurement_specification = SPDM_MEASUREMENT_SPEC_DMTF; rq->base_asym_algo = base_asym_algo; rq->base_hash_algo = base_hash_algo; return sizeof (struct spdm_negotiate_algorithms_request); } /** * Process a SPDM negotiate algorithms response. * * @param response Response buffer to process. * * @return Response processing completion status, 0 if successful or error code otherwise. */ int spdm_process_negotiate_algorithms_response (struct cmd_interface_msg *response) { struct spdm_negotiate_algorithms_response *resp; struct spdm_algorithm_request *algstruct_table; size_t i_algstruct; size_t offset; if (response == NULL) { return CMD_HANDLER_SPDM_INVALID_ARGUMENT; } resp = (struct spdm_negotiate_algorithms_response*) response->payload; if (resp->header.spdm_minor_version < 1) { if (resp->num_alg_structure_tables != 0) { return CMD_HANDLER_SPDM_BAD_RESPONSE; } } if ((response->payload_length < sizeof (struct spdm_negotiate_algorithms_response)) || (response->payload_length != resp->length) || (response->payload_length < spdm_negotiate_algorithms_min_rsp_length (resp))) { return CMD_HANDLER_SPDM_BAD_LENGTH; } algstruct_table = spdm_negotiate_algorithms_rsp_algstruct_table (resp); offset = spdm_negotiate_algorithms_min_rsp_length (resp); for (i_algstruct = 0; i_algstruct < resp->num_alg_structure_tables; ++i_algstruct) { /* TODO: Maybe macro for length check. */ if (response->payload_length < (offset + algstruct_table->ext_alg_count * sizeof (struct spdm_extended_algorithm))) { return CMD_HANDLER_SPDM_BAD_LENGTH; } algstruct_table = (struct spdm_algorithm_request*) (((uint8_t*) (algstruct_table + 1)) + (algstruct_table->ext_alg_count * sizeof (struct spdm_extended_algorithm))); offset += (algstruct_table->ext_alg_count * sizeof (struct spdm_extended_algorithm)); } return 0; } /** * Process the SPDM GET_DIGESTS request. * * @param spdm_responder SPDM responder instance. * @param request GET_DIGESTS request to process. * * @return 0 if the request was processed successfully or an error code. */ int spdm_get_digests (const struct cmd_interface_spdm_responder *spdm_responder, struct cmd_interface_msg *request) { int status; int spdm_error; struct spdm_get_digests_request *spdm_request; struct spdm_get_digests_response *spdm_response; uint8_t spdm_version; uint32_t response_size; int hash_size; 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; enum hash_type hash_type; struct spdm_secure_session_manager *session_manager; struct spdm_secure_session *session = NULL; 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]; hash_type = spdm_get_hash_type (state->connection_info.peer_algorithms.base_hash_algo); session_manager = spdm_responder->session_manager; /* Validate the request. */ if (request->payload_length < sizeof (struct spdm_get_digests_request)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } spdm_request = (struct spdm_get_digests_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 if the certificate capability is supported. */ if (local_capabilities->flags.cert_cap == 0) { status = CMD_HANDLER_SPDM_RESPONDER_UNSUPPORTED_CAPABILITY; spdm_error = SPDM_ERROR_UNSUPPORTED_REQUEST; goto exit; } /* Check if a session is ongoing. */ if ((session_manager != NULL) && (session_manager->is_last_session_id_valid (session_manager) == true)) { session = session_manager->get_session (session_manager, session_manager->get_last_session_id (session_manager)); if (session == NULL) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_SESSION_STATE; spdm_error = SPDM_ERROR_UNEXPECTED_REQUEST; goto exit; } /* Check session state. */ if (session->session_state != SPDM_SESSION_STATE_ESTABLISHED) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_SESSION_STATE; spdm_error = SPDM_ERROR_UNEXPECTED_REQUEST; goto exit; } } /* Reset transcript manager state as per request code. */ spdm_reset_transcript_via_request_code (state, transcript_manager, SPDM_REQUEST_GET_DIGESTS); /* Add request to M1M2 hash context. */ if (session == NULL) { status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_M1M2, request->payload, sizeof (struct spdm_get_digests_request), false, SPDM_MAX_SESSION_COUNT); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } } /* Construct the response. */ hash_size = hash_get_hash_length (hash_type); if (hash_size == HASH_ENGINE_UNKNOWN_HASH) { status = HASH_ENGINE_UNKNOWN_HASH; spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } response_size = sizeof (struct spdm_get_digests_response) + hash_size; 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_get_digests_response*) request->payload; memset (spdm_response, 0, response_size); spdm_populate_header (&spdm_response->header, SPDM_RESPONSE_GET_DIGESTS, SPDM_GET_MINOR_VERSION (spdm_version)); spdm_response->slot_mask = 1; /* Get the digest of the certificate chain. */ status = spdm_get_certificate_chain_digest (key_manager, hash_engine, hash_type, (uint8_t*) (spdm_response + 1)); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Add response to M1M2 hash context. */ if (session == NULL) { status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_M1M2, (uint8_t*) spdm_response, response_size, false, SPDM_MAX_SESSION_COUNT); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } } /* Set the payload length. */ cmd_interface_msg_set_message_payload_length (request, response_size); /* Update connection state */ if (state->connection_info.connection_state < SPDM_CONNECTION_STATE_AFTER_DIGESTS) { spdm_set_connection_state (state, SPDM_CONNECTION_STATE_AFTER_DIGESTS); } exit: if (status != 0) { spdm_generate_error_response (request, state->connection_info.version.minor_version, spdm_error, 0x00, NULL, 0, SPDM_REQUEST_GET_DIGESTS, status); } return 0; } /** * Construct SPDM get digests request. * * @param buf Output buffer for the generated request data. * @param buf_len Maximum size of buffer. * @param spdm_minor_version SPDM minor version to utilize in request. * * @return Length of the generated request data if the request was successfully constructed or an * error code. */ int spdm_generate_get_digests_request (uint8_t *buf, size_t buf_len, uint8_t spdm_minor_version) { struct spdm_get_digests_request *rq = (struct spdm_get_digests_request*) buf; if (buf == NULL) { return CMD_HANDLER_SPDM_INVALID_ARGUMENT; } if (buf_len < sizeof (struct spdm_get_digests_request)) { return CMD_HANDLER_SPDM_BUF_TOO_SMALL; } memset (rq, 0, sizeof (struct spdm_get_digests_request)); spdm_populate_header (&rq->header, SPDM_REQUEST_GET_DIGESTS, spdm_minor_version); return sizeof (struct spdm_get_digests_request); } /** * Process a SPDM get digests response. * * @param response Response buffer to process. * * @return Response processing completion status, 0 if successful or error code otherwise. */ int spdm_process_get_digests_response (struct cmd_interface_msg *response) { if (response == NULL) { return CMD_HANDLER_SPDM_INVALID_ARGUMENT; } if (response->payload_length < sizeof (struct spdm_get_digests_response)) { return CMD_HANDLER_SPDM_BAD_LENGTH; } return 0; } /** * Process SPDM GET_CERTIFICATE request. * * @param spdm_responder SPDM responder instance. * @param request GET_CERTIFICATE request to process. * * @return 0 if request processed successfully or an error code. */ int spdm_get_certificate (const struct cmd_interface_spdm_responder *spdm_responder, struct cmd_interface_msg *request) { int status; int spdm_error; uint8_t spdm_version; uint8_t slot_id; struct spdm_get_certificate_request *spdm_request; struct spdm_get_certificate_response *spdm_response; uint16_t requested_offset; uint16_t requested_length; size_t remainder_length = 0; size_t response_size; struct der_cert cert[SPDM_MAX_CERT_COUNT_IN_CHAIN]; uint8_t cert_count = ARRAY_SIZE (cert); uint32_t hash_size; uint8_t *cert_chain = NULL; uint32_t cert_chain_length; uint32_t cert_chain_offset; uint8_t i_cert; uint32_t max_cert_block_len; struct spdm_cert_chain_header *cert_chain_header; 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; enum hash_type hash_type; const struct riot_keys *keys = NULL; struct spdm_secure_session_manager *session_manager; struct spdm_secure_session *session = NULL; 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]; hash_type = spdm_get_hash_type (state->connection_info.peer_algorithms.base_hash_algo); session_manager = spdm_responder->session_manager; /* Validate the request. */ if (request->payload_length < sizeof (struct spdm_get_certificate_request)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } spdm_request = (struct spdm_get_certificate_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 if the certificate capability is supported. */ if (local_capabilities->flags.cert_cap == 0) { status = CMD_HANDLER_SPDM_RESPONDER_UNSUPPORTED_CAPABILITY; spdm_error = SPDM_ERROR_UNSUPPORTED_REQUEST; goto exit; } /* Check if a session is ongoing. */ if ((session_manager != NULL) && (session_manager->is_last_session_id_valid (session_manager) == true)) { session = session_manager->get_session (session_manager, session_manager->get_last_session_id (session_manager)); if (session == NULL) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_SESSION_STATE; spdm_error = SPDM_ERROR_UNEXPECTED_REQUEST; goto exit; } /* Check session state. */ if (session->session_state != SPDM_SESSION_STATE_ESTABLISHED) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_SESSION_STATE; spdm_error = SPDM_ERROR_UNEXPECTED_REQUEST; goto exit; } } slot_id = spdm_request->slot_num & SPDM_GET_CERTIFICATE_SLOT_ID_MASK; if (slot_id != 0) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } /* Retrieve the list of certificates in the certificate chain. */ status = spdm_get_certificate_list (key_manager, &cert_count, cert, &keys); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } hash_size = hash_get_hash_length (hash_type); if (hash_size == HASH_ENGINE_UNKNOWN_HASH) { status = HASH_ENGINE_UNKNOWN_HASH; spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Calculate the cert chain data struct. length. */ cert_chain_length = sizeof (struct spdm_cert_chain_header) + hash_size; for (i_cert = 0; i_cert < cert_count; ++i_cert) { cert_chain_length += cert[i_cert].length; } /* Allocate a temp. buffer to hold the certificate chain. */ cert_chain = platform_malloc (cert_chain_length); if (cert_chain == NULL) { status = CMD_HANDLER_SPDM_RESPONDER_NO_MEMORY; spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } cert_chain_header = (struct spdm_cert_chain_header*) cert_chain; cert_chain_header->length = (uint16_t) cert_chain_length; cert_chain_header->reserved = 0; /* Copy cert(s) to the temp. buffer. */ cert_chain_offset = sizeof (struct spdm_cert_chain_header) + hash_size; for (i_cert = 0; i_cert < cert_count; i_cert++) { memcpy (&cert_chain[cert_chain_offset], cert[i_cert].cert, cert[i_cert].length); cert_chain_offset += cert[i_cert].length; } requested_offset = spdm_request->offset; requested_length = spdm_request->length; /* Check if the requested cert. chain offset is valid. */ if (requested_offset >= cert_chain_length) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } /* Compute the maximum cert block that can be sent. */ max_cert_block_len = cmd_interface_msg_get_max_response (request) - sizeof (struct spdm_get_certificate_response); /* If chunking capability is not supported, adjust the requested cert chain length. */ if (!(state->connection_info.peer_capabilities.flags.chunk_cap && local_capabilities->flags.chunk_cap)) { if (requested_length > max_cert_block_len) { requested_length = max_cert_block_len; } } /* Adjust the requested length. */ if ((size_t) (requested_offset + requested_length) > cert_chain_length) { requested_length = (uint16_t) (cert_chain_length - requested_offset); } remainder_length = cert_chain_length - (requested_length + requested_offset); response_size = sizeof (struct spdm_get_certificate_response) + requested_length; /* Reset transcript manager state as per request code. */ spdm_reset_transcript_via_request_code (state, transcript_manager, SPDM_REQUEST_GET_CERTIFICATE); /* Add request to M1M2 hash context. */ if (session == NULL) { status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_M1M2, (uint8_t*) spdm_request, sizeof (struct spdm_get_certificate_request), false, SPDM_MAX_SESSION_COUNT); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } } /* Construct the response. */ spdm_response = (struct spdm_get_certificate_response*) request->payload; memset (spdm_response, 0, response_size); spdm_populate_header (&spdm_response->header, SPDM_RESPONSE_GET_CERTIFICATE, SPDM_GET_MINOR_VERSION (spdm_version)); spdm_response->slot_num = slot_id; spdm_response->portion_len = requested_length; spdm_response->remainder_len = (uint16_t) remainder_length; /* Hash the root certificate if not already provided to the requester. */ if (requested_offset < (sizeof (struct spdm_cert_chain_header) + hash_size)) { status = hash_calculate (hash_engine, hash_type, cert[0].cert, cert[0].length, cert_chain + sizeof (struct spdm_cert_chain_header), hash_size); if (ROT_IS_ERROR (status)) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } status = 0; } /* Copy cert_chain portion to response. */ memcpy (spdm_get_certificate_resp_cert_chain (spdm_response), cert_chain + requested_offset, requested_length); /* Add response to M1M2 hash context. */ if (session == NULL) { status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_M1M2, (uint8_t*) spdm_response, response_size, false, SPDM_MAX_SESSION_COUNT); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } } /* Set the payload length. */ cmd_interface_msg_set_message_payload_length (request, response_size); /* Update connection state */ if (state->connection_info.connection_state < SPDM_CONNECTION_STATE_AFTER_CERTIFICATE) { spdm_set_connection_state (state, SPDM_CONNECTION_STATE_AFTER_CERTIFICATE); } exit: if (keys != NULL) { riot_key_manager_release_riot_keys (key_manager, keys); } platform_free (cert_chain); if (status != 0) { spdm_generate_error_response (request, state->connection_info.version.minor_version, spdm_error, 0x00, NULL, 0, SPDM_REQUEST_GET_CERTIFICATE, status); } return 0; } /** * Construct SPDM get certificate request. * * @param buf Output buffer for the generated request data. * @param buf_len Maximum size of buffer. * @param slot_num Slot number of certificate chain requested. * @param offset Offset in bytes from start of certificate chain requested. * @param length Length in bytes of certificate chain requested. * @param spdm_minor_version SPDM minor version to utilize in request. * * @return Length of the generated request data if the request was successfully constructed or an * error code. */ int spdm_generate_get_certificate_request (uint8_t *buf, size_t buf_len, uint8_t slot_num, uint16_t offset, uint16_t length, uint8_t spdm_minor_version) { struct spdm_get_certificate_request *rq = (struct spdm_get_certificate_request*) buf; if (buf == NULL) { return CMD_HANDLER_SPDM_INVALID_ARGUMENT; } if (buf_len < sizeof (struct spdm_get_certificate_request)) { return CMD_HANDLER_SPDM_BUF_TOO_SMALL; } memset (rq, 0, sizeof (struct spdm_get_certificate_request)); spdm_populate_header (&rq->header, SPDM_REQUEST_GET_CERTIFICATE, spdm_minor_version); rq->slot_num = slot_num; rq->offset = offset; rq->length = length; return sizeof (struct spdm_get_certificate_request); } /** * Process a SPDM get certificate response. * * @param response Response buffer to process. * * @return Response processing completion status, 0 if successful or error code otherwise. */ int spdm_process_get_certificate_response (struct cmd_interface_msg *response) { struct spdm_get_certificate_response *resp; if (response == NULL) { return CMD_HANDLER_SPDM_INVALID_ARGUMENT; } resp = (struct spdm_get_certificate_response*) response->payload; if ((response->payload_length < sizeof (struct spdm_get_certificate_response)) || (response->payload_length != spdm_get_certificate_resp_length (resp))) { return CMD_HANDLER_SPDM_BAD_LENGTH; } return 0; } int spdm_challenge (const struct cmd_interface_spdm_responder *spdm_responder, struct cmd_interface_msg *request) { int status; int spdm_error; const struct spdm_challenge_request *spdm_request; struct spdm_challenge_response *spdm_response; uint8_t spdm_version; struct spdm_state *state; const struct spdm_device_capability *local_capabilities; const struct spdm_transcript_manager *transcript_manager; const struct hash_engine *hash_engine; const struct riot_key_manager *key_manager; const struct rng_engine *rng_engine; const struct ecc_engine *ecc_engine; enum hash_type hash_type; int hash_size; size_t signature_size; size_t request_size; size_t response_size; const struct spdm_measurements *measurements; uint8_t measurement_summary_type; uint32_t meas_summary_hash_size; uint8_t slot_num; uint8_t m1_hash[HASH_MAX_HASH_LEN]; uint8_t *response_ptr; 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; ecc_engine = spdm_responder->ecc_engine; hash_engine = spdm_responder->hash_engine[0]; hash_type = spdm_get_hash_type (state->connection_info.peer_algorithms.base_hash_algo); measurements = spdm_responder->measurements; rng_engine = spdm_responder->rng_engine; /* Validate the request. */ if (request->payload_length < sizeof (struct spdm_challenge_request)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } request_size = sizeof (struct spdm_challenge_request); spdm_request = (struct spdm_challenge_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; } measurement_summary_type = spdm_request->req_measurement_summary_hash_type; if ((measurement_summary_type != SPDM_MEASUREMENT_SUMMARY_HASH_NONE) && (measurement_summary_type != SPDM_MEASUREMENT_SUMMARY_HASH_TCB) && (measurement_summary_type != SPDM_MEASUREMENT_SUMMARY_HASH_ALL)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } if ((measurement_summary_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; } if (spdm_request->slot_num != 0) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } /* Verify SPDM state. */ if (state->response_state != SPDM_RESPONSE_STATE_NORMAL) { /* [TODO] Handle RESPOND_IF_READY condition when supported. */ 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 if the measurement capability is supported. */ if (local_capabilities->flags.chal_cap == 0) { status = CMD_HANDLER_SPDM_RESPONDER_UNSUPPORTED_CAPABILITY; spdm_error = SPDM_ERROR_UNSUPPORTED_REQUEST; goto exit; } slot_num = spdm_request->slot_num; signature_size = spdm_get_asym_signature_size (state->connection_info.peer_algorithms.base_asym_algo); if (signature_size == 0) { status = CMD_HANDLER_SPDM_RESPONDER_UNSUPPORTED_SIG_SIZE; spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } hash_size = hash_get_hash_length (hash_type); if (hash_size == HASH_ENGINE_UNKNOWN_HASH) { status = HASH_ENGINE_UNKNOWN_HASH; spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } meas_summary_hash_size = (measurement_summary_type == SPDM_MEASUREMENT_SUMMARY_HASH_NONE) ? 0 : hash_size; /* Check if sufficient buffer is available for the response including the optional signature. */ response_size = sizeof (struct spdm_challenge_response) + hash_size + SPDM_NONCE_LEN + meas_summary_hash_size + sizeof (uint16_t) + signature_size; if (cmd_interface_msg_get_max_response (request) < response_size) { status = CMD_HANDLER_SPDM_RESPONDER_RESPONSE_TOO_LARGE; spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Reset transcript manager state as per request code. */ spdm_reset_transcript_via_request_code (state, transcript_manager, SPDM_RESPONSE_CHALLENGE); /* Add request to M1M2 hash context. */ status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_M1M2, request->payload, request_size, false, SPDM_MAX_SESSION_COUNT); if (status != 0) { status = CMD_HANDLER_SPDM_RESPONDER_INTERNAL_ERROR; spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } spdm_response = (struct spdm_challenge_response*) request->payload; memset (spdm_response, 0, response_size); spdm_populate_header (&spdm_response->header, SPDM_RESPONSE_CHALLENGE, SPDM_GET_MINOR_VERSION (spdm_version)); spdm_response->basic_mutual_auth_req = 0; spdm_response->slot_num = slot_num; spdm_response->slot_mask = (1 << slot_num); response_ptr = (uint8_t*) (spdm_response + 1); /* Get the digest of the certificate chain. */ status = spdm_get_certificate_chain_digest (key_manager, hash_engine, hash_type, response_ptr); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } response_ptr += hash_size; /* Generate random data for the response. */ status = rng_engine->generate_random_buffer (rng_engine, SPDM_NONCE_LEN, response_ptr); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } response_ptr += SPDM_NONCE_LEN; /* Add the measurement summary hash if requested. */ if (measurement_summary_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, (measurement_summary_type == SPDM_MEASUREMENT_SUMMARY_HASH_TCB), response_ptr, meas_summary_hash_size); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } response_ptr += meas_summary_hash_size; } /* Write opaque data size as 0 */ buffer_unaligned_write16 ((uint16_t*) response_ptr, 0); response_ptr += sizeof (uint16_t); /* Add response to M1M2 hash context. */ status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_M1M2, request->payload, response_size - signature_size, false, SPDM_MAX_SESSION_COUNT); if (status != 0) { status = CMD_HANDLER_SPDM_RESPONDER_INTERNAL_ERROR; spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Get the M1 hash and complete the hash context. */ status = transcript_manager->get_hash (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_M1M2, true, false, SPDM_MAX_SESSION_COUNT, m1_hash, hash_size); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Sign the M1 hash. */ status = spdm_responder_data_sign (state, key_manager, ecc_engine, hash_engine, SPDM_RESPONSE_CHALLENGE, m1_hash, hash_size, response_ptr, signature_size); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Set the payload length. */ cmd_interface_msg_set_message_payload_length (request, response_size); exit: if (status != 0) { spdm_generate_error_response (request, state->connection_info.version.minor_version, spdm_error, 0x00, NULL, 0, SPDM_REQUEST_CHALLENGE, status); } return 0; } /** * Construct SPDM challenge request. * * @param buf Output buffer for the generated request data. * @param buf_len Maximum size of buffer. * @param slot_num Slot number requested for challenge. * @param req_measurement_summary_hash_type Requested measurement summary hash type. * @param nonce Random nonce to send in request. * @param spdm_minor_version SPDM minor version to utilize in request. * * @return Length of the generated request data if the request was successfully constructed or an * error code. */ int spdm_generate_challenge_request (uint8_t *buf, size_t buf_len, uint8_t slot_num, uint8_t req_measurement_summary_hash_type, uint8_t *nonce, uint8_t spdm_minor_version) { struct spdm_challenge_request *rq = (struct spdm_challenge_request*) buf; if ((buf == NULL) || (nonce == NULL)) { return CMD_HANDLER_SPDM_INVALID_ARGUMENT; } if (buf_len < sizeof (struct spdm_challenge_request)) { return CMD_HANDLER_SPDM_BUF_TOO_SMALL; } memset (rq, 0, sizeof (struct spdm_challenge_request)); spdm_populate_header (&rq->header, SPDM_REQUEST_CHALLENGE, spdm_minor_version); rq->slot_num = slot_num; rq->req_measurement_summary_hash_type = req_measurement_summary_hash_type; memcpy (rq->nonce, nonce, sizeof (rq->nonce)); return sizeof (struct spdm_challenge_request); } /** * Process a SPDM challenge response. * * @param response Response buffer to process. * * @return Response processing completion status, 0 if successful or error code otherwise. */ int spdm_process_challenge_response (struct cmd_interface_msg *response) { if (response == NULL) { return CMD_HANDLER_SPDM_INVALID_ARGUMENT; } if (response->payload_length <= sizeof (struct spdm_challenge_response)) { return CMD_HANDLER_SPDM_BAD_LENGTH; } return 0; } /** * Process the SPDM GET_MEASUREMENTS request. * * @param spdm_responder SPDM responder instance. * @param request GET_MEASUREMENTS request to process. * * @return 0 if the request was processed successfully or an error code. */ int spdm_get_measurements (const struct cmd_interface_spdm_responder *spdm_responder, struct cmd_interface_msg *request) { int status; int spdm_error; const struct spdm_get_measurements_request *spdm_request; struct spdm_get_measurements_response *spdm_response; uint8_t spdm_version; const struct spdm_transcript_manager *transcript_manager; struct spdm_state *state; const struct spdm_device_capability *local_capabilities; const struct hash_engine *hash_engine; enum hash_type hash_type; size_t signature_size; size_t request_size; size_t response_size; const struct spdm_measurements *measurements; const struct rng_engine *rng_engine; uint8_t measurement_operation; int measurement_count; int measurement_length; bool raw_bit_stream_requested; bool signature_requested; struct spdm_secure_session_manager *session_manager; struct spdm_secure_session *session = NULL; uint8_t session_idx = SPDM_MAX_SESSION_COUNT; 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; hash_engine = spdm_responder->hash_engine[0]; hash_type = spdm_get_hash_type (state->connection_info.peer_algorithms.base_hash_algo); measurements = spdm_responder->measurements; rng_engine = spdm_responder->rng_engine; session_manager = spdm_responder->session_manager; /* Check if a session is ongoing. */ if ((session_manager != NULL) && (session_manager->is_last_session_id_valid (session_manager) == true)) { session = session_manager->get_session (session_manager, session_manager->get_last_session_id (session_manager)); if (session == NULL) { /* Don't reset the L1L2 hash context in this failure case as the correct session context * is not known. This behavior is per libSPDM. */ spdm_generate_error_response (request, state->connection_info.version.minor_version, SPDM_ERROR_UNEXPECTED_REQUEST, 0x00, NULL, 0, SPDM_REQUEST_GET_MEASUREMENTS, CMD_HANDLER_SPDM_RESPONDER_UNEXPECTED_REQUEST); return 0; } session_idx = session->session_index; /* Check session state. */ if (session->session_state != SPDM_SESSION_STATE_ESTABLISHED) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_SESSION_STATE; spdm_error = SPDM_ERROR_UNEXPECTED_REQUEST; goto exit; } } /* Validate the request. */ if (request->payload_length < sizeof (struct spdm_get_measurements_request)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } request_size = sizeof (struct spdm_get_measurements_request); spdm_request = (struct spdm_get_measurements_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) { /* [TODO] Handle RESPOND_IF_READY condition when supported. */ 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 if the measurement capability is supported. */ if (local_capabilities->flags.meas_cap == 0) { status = CMD_HANDLER_SPDM_RESPONDER_UNSUPPORTED_CAPABILITY; spdm_error = SPDM_ERROR_UNSUPPORTED_REQUEST; goto exit; } /* Check if the negotiated parameters for measurement are valid. */ if ((state->connection_info.peer_algorithms.measurement_spec == 0) || (state->connection_info.peer_algorithms.measurement_hash_algo == 0)) { status = CMD_HANDLER_SPDM_RESPONDER_UNEXPECTED_REQUEST; spdm_error = SPDM_ERROR_UNEXPECTED_REQUEST; goto exit; } /* If signature generation is requested, check if responder has the support for it. */ signature_requested = spdm_request->sig_required; if (signature_requested == true) { if (local_capabilities->flags.meas_cap != SPDM_MEAS_CAP_WITH_SIG) { status = CMD_HANDLER_SPDM_RESPONDER_UNSUPPORTED_CAPABILITY; spdm_error = SPDM_ERROR_UNSUPPORTED_REQUEST; goto exit; } request_size += (SPDM_NONCE_LEN + sizeof (uint8_t)); if (request->payload_length < request_size) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } /* Check if slot id is valid. */ if (*(spdm_get_measurements_rq_slot_id_ptr (spdm_request)) != 0) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } signature_size = spdm_get_asym_signature_size (state->connection_info.peer_algorithms.base_asym_algo); } else { signature_size = 0; } /* Check if sufficient buffer is available for the response including the optional signature. */ response_size = SPDM_GET_MEASUREMENTS_RESP_MIN_LENGTH + signature_size; if (cmd_interface_msg_get_max_response (request) < response_size) { status = CMD_HANDLER_SPDM_RESPONDER_RESPONSE_TOO_LARGE; spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } raw_bit_stream_requested = spdm_request->raw_bit_stream_requested; measurement_operation = spdm_request->measurement_operation; /* Reset transcript manager state as per request code. */ spdm_reset_transcript_via_request_code (state, transcript_manager, SPDM_REQUEST_GET_MEASUREMENTS); /* Add request to L1L2 hash context. */ status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_L1L2, request->payload, request_size, (session != NULL), session_idx); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Construct the response. */ spdm_response = (struct spdm_get_measurements_response*) request->payload; memset (spdm_response, 0, response_size); spdm_populate_header (&spdm_response->header, SPDM_RESPONSE_GET_MEASUREMENTS, SPDM_GET_MINOR_VERSION (spdm_version)); /* Get the total number of measurement(s) available on the device. */ measurement_count = measurements->get_measurement_count (measurements); if (ROT_IS_ERROR (measurement_count)) { status = measurement_count; spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } switch (measurement_operation) { case SPDM_GET_MEASUREMENTS_REQUEST_MEASUREMENT_OPERATION_TOTAL_NUMBER_OF_MEASUREMENTS: spdm_response->num_measurement_indices = measurement_count; break; case SPDM_GET_MEASUREMENTS_REQUEST_MEASUREMENT_OPERATION_ALL_MEASUREMENTS: /* Get the measurement record. */ measurement_length = measurements->get_all_measurement_blocks (measurements, raw_bit_stream_requested, hash_engine, hash_type, spdm_get_measurements_resp_measurement_record (spdm_response), (cmd_interface_msg_get_max_response (request) - response_size)); if (ROT_IS_ERROR (measurement_length)) { status = measurement_length; spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } response_size += measurement_length; spdm_response->number_of_blocks = (uint8_t) measurement_count; buffer_unaligned_write24 (spdm_response->measurement_record_len, (uint32_t) measurement_length); break; default: /* Get a single measurement. */ measurement_length = measurements->get_measurement_block (measurements, measurement_operation, raw_bit_stream_requested, hash_engine, hash_type, spdm_get_measurements_resp_measurement_record (spdm_response), (cmd_interface_msg_get_max_response (request) - response_size)); if (ROT_IS_ERROR (measurement_length)) { status = measurement_length; spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } response_size += measurement_length; spdm_response->number_of_blocks = 1; buffer_unaligned_write24 (spdm_response->measurement_record_len, (uint32_t) measurement_length); break; } /* Add the random nonce. */ status = rng_engine->generate_random_buffer (rng_engine, SPDM_NONCE_LEN, spdm_get_measurements_resp_nonce (spdm_response)); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Set opaque data size to 0 */ buffer_unaligned_write16 ((uint16_t*) (spdm_get_measurements_resp_nonce (spdm_response) + SPDM_NONCE_LEN), 0); /* Add response to L1L2 hash context. Signature is not included in the hash. */ status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_L1L2, (const uint8_t*) spdm_response, response_size - signature_size, (session != NULL), session_idx); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Generate the signature, if requested. */ if (signature_requested == true) { status = spdm_generate_measurement_signature (transcript_manager, state, spdm_responder->key_manager, spdm_responder->ecc_engine, hash_engine, session, spdm_get_measurements_resp_signature (spdm_response), signature_size); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } } /* Set the payload length. */ cmd_interface_msg_set_message_payload_length (request, response_size); exit: if (status != 0) { /* Reset L1L2 hash context on error. */ transcript_manager->reset_transcript (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_L1L2, (session != NULL), session_idx); spdm_generate_error_response (request, state->connection_info.version.minor_version, spdm_error, 0x00, NULL, 0, SPDM_REQUEST_GET_MEASUREMENTS, status); } return 0; } /** * Construct SPDM get measurements request. * * @param buf Output buffer for the generated request data. * @param buf_len Maximum size of buffer. * @param slot_num Slot number requested. * @param measurement_operation Requested measurement operation. * @param sig_required Flag indicating if signature is required in response. * @param raw_bitstream_requested For SPDM v1.2+, indicate whether to request raw or hashed * measurement block. * @param nonce Random nonce to send in request. * @param spdm_minor_version SPDM minor version to utilize in request. * * @return Length of the generated request data if the request was successfully constructed or an * error code. */ int spdm_generate_get_measurements_request (uint8_t *buf, size_t buf_len, uint8_t slot_num, uint8_t measurement_operation, bool sig_required, bool raw_bitstream_requested, uint8_t *nonce, uint8_t spdm_minor_version) { struct spdm_get_measurements_request *rq = (struct spdm_get_measurements_request*) buf; size_t rq_length = sizeof (struct spdm_get_measurements_request) + ((SPDM_NONCE_LEN) *sig_required); uint8_t *slot_id; if ((buf == NULL) || ((nonce == NULL) && sig_required)) { return CMD_HANDLER_SPDM_INVALID_ARGUMENT; } if ((spdm_minor_version == 0) && (slot_num != 0)) { return CMD_HANDLER_SPDM_UNSUPPORTED_SLOT_ID; } if (spdm_minor_version > 0) { rq_length += sig_required; } if (buf_len < rq_length) { return CMD_HANDLER_SPDM_BUF_TOO_SMALL; } memset (rq, 0, rq_length); spdm_populate_header (&rq->header, SPDM_REQUEST_GET_MEASUREMENTS, spdm_minor_version); rq->sig_required = sig_required; rq->measurement_operation = measurement_operation; if (spdm_minor_version >= 2) { rq->raw_bit_stream_requested = raw_bitstream_requested; } if (sig_required) { if (spdm_minor_version > 0) { slot_id = spdm_get_measurements_rq_slot_id_ptr (rq); *slot_id = slot_num; } memcpy (spdm_get_measurements_rq_nonce (rq), nonce, SPDM_NONCE_LEN); } return rq_length; } /** * Process a SPDM get measurements response. * * @param response Response buffer to process. * * @return Response processing completion status, 0 if successful or error code otherwise. */ int spdm_process_get_measurements_response (struct cmd_interface_msg *response) { struct spdm_get_measurements_response *resp; if (response == NULL) { return CMD_HANDLER_SPDM_INVALID_ARGUMENT; } resp = (struct spdm_get_measurements_response*) response->payload; if (((response->payload_length < sizeof (struct spdm_get_measurements_response)) || (response->payload_length < (sizeof (struct spdm_get_measurements_response) + spdm_get_measurements_resp_measurement_record_len (resp) + SPDM_NONCE_LEN))) || (response->payload_length < spdm_get_measurements_resp_length (resp))) { return CMD_HANDLER_SPDM_BAD_LENGTH; } return 0; } /** * Construct SPDM respond if ready request. * * @param buf Output buffer for the generated request data. * @param buf_len Maximum size of buffer. * @param original_request_code Original request code that triggered ResponseNotReady response. * @param token Token received in ResponseNotReady response. * @param spdm_minor_version SPDM minor version to utilize in request. * * @return Length of the generated request data if the request was successfully constructed or an * error code. */ int spdm_generate_respond_if_ready_request (uint8_t *buf, size_t buf_len, uint8_t original_request_code, uint8_t token, uint8_t spdm_minor_version) { struct spdm_respond_if_ready_request *rq = (struct spdm_respond_if_ready_request*) buf; size_t rq_length = sizeof (struct spdm_respond_if_ready_request); if (buf == NULL) { return CMD_HANDLER_SPDM_INVALID_ARGUMENT; } if (buf_len < rq_length) { return CMD_HANDLER_SPDM_BUF_TOO_SMALL; } memset (rq, 0, rq_length); spdm_populate_header (&rq->header, SPDM_REQUEST_RESPOND_IF_READY, spdm_minor_version); rq->token = token; rq->original_request_code = original_request_code; return rq_length; } /** * Process the SPDM KEY_EXCHANGE request. * * @param spdm_responder SPDM responder instance. * @param request KEY_EXCHANGE request to process. * * @return 0 if request was processed successfully, or an error code. */ 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; } /** * Process SPDM FINISH request. * * @param spdm_responder SPDM responder instance. * @param request FINISH request to process. * * @return 0 if request processed successfully or an error code. */ int spdm_finish (const struct cmd_interface_spdm_responder *spdm_responder, struct cmd_interface_msg *request) { int status; int spdm_error; uint8_t spdm_version; struct spdm_finish_request *spdm_request; struct spdm_finish_response *spdm_response; size_t response_size; uint32_t session_id; struct spdm_secure_session *session; uint32_t hmac_size; uint32_t sig_size = 0; struct spdm_state *state; const struct spdm_transcript_manager *transcript_manager; const struct spdm_device_capability *local_capabilities; struct spdm_secure_session_manager *session_manager; const uint8_t *hmac_ptr; if ((spdm_responder == NULL) || (request == NULL)) { return CMD_HANDLER_SPDM_RESPONDER_INVALID_ARGUMENT; } state = spdm_responder->state; transcript_manager = spdm_responder->transcript_manager; local_capabilities = spdm_responder->local_capabilities; session_manager = spdm_responder->session_manager; if (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_finish_request)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } spdm_request = (struct spdm_finish_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)) { spdm_error = SPDM_ERROR_VERSION_MISMATCH; status = CMD_HANDLER_SPDM_RESPONDER_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; } /* Confirm that we are in a session.*/ if ((local_capabilities->flags.handshake_in_the_clear_cap == 0) || (state->connection_info.peer_capabilities.flags.handshake_in_the_clear_cap == 0)) { if (session_manager->is_last_session_id_valid (session_manager) == false) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_CONNECTION_STATE; spdm_error = SPDM_ERROR_SESSION_REQUIRED; goto exit; } } else { /* [TODO] Check if handshake in clear needs to be supported.*/ status = CMD_HANDLER_SPDM_RESPONDER_INVALID_CONNECTION_STATE; spdm_error = SPDM_ERROR_SESSION_REQUIRED; goto exit; } /* Session id is retrieved from the secure session message header. */ session_id = session_manager->get_last_session_id (session_manager); session = session_manager->get_session (session_manager, session_id); if (session == NULL) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_CONNECTION_STATE; spdm_error = SPDM_ERROR_SESSION_REQUIRED; goto exit; } /* Check session state. */ if (session->session_state != SPDM_SESSION_STATE_HANDSHAKING) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_CONNECTION_STATE; spdm_error = SPDM_ERROR_UNEXPECTED_REQUEST; goto exit; } /* Since mutual auth is not supported, requester should not have included a signature. */ if ((spdm_request->signature_included & SPDM_FINISH_REQUEST_ATTRIBUTES_SIGNATURE_INCLUDED) != 0) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } hmac_size = session->hash_size; sig_size = 0; if (request->payload_length < (sizeof (struct spdm_finish_request) + sig_size + hmac_size)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } /* Reset the transcript manager state as per the request code. */ spdm_reset_transcript_via_request_code (state, transcript_manager, SPDM_REQUEST_FINISH); /* Add the FINISH request (no HMAC) to the TH hash context. */ status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_TH, (const uint8_t*) spdm_request, sizeof (struct spdm_finish_request), true, session->session_index); if (status != 0) { status = CMD_HANDLER_SPDM_RESPONDER_INTERNAL_ERROR; spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Verify the request HMAC. */ hmac_ptr = spdm_finish_rq_hmac (spdm_request, sig_size); status = spdm_verify_finish_req_hmac (transcript_manager, spdm_responder->hash_engine[0], session, hmac_ptr, hmac_size); if (status != 0) { if ((state->handle_error_return_policy & SPDM_DATA_HANDLE_ERROR_RETURN_POLICY_DROP_ON_DECRYPT_ERROR) == 0) { spdm_error = SPDM_ERROR_DECRYPT_ERROR; goto exit; } else { /* Ignore this message. Don't send a response back. */ return status; } } /* Add the HMAC from the FINISH request to the TH hash context. */ status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_TH, hmac_ptr, hmac_size, true, session->session_index); if (status != 0) { status = CMD_HANDLER_SPDM_RESPONDER_INTERNAL_ERROR; spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Construct the response. Payload buffer is guranteed to be greater than response size. */ response_size = sizeof (struct spdm_finish_response); spdm_response = (struct spdm_finish_response*) request->payload; memset (spdm_response, 0, response_size); spdm_populate_header (&spdm_response->header, SPDM_RESPONSE_FINISH, SPDM_GET_MINOR_VERSION (spdm_version)); spdm_response->reserved1 = 0; spdm_response->reserved2 = 0; /* Add the FINISH response to the TH hash context. */ status = transcript_manager->update (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_TH, (const uint8_t*) spdm_response, response_size, true, session->session_index); if (status != 0) { status = CMD_HANDLER_SPDM_RESPONDER_INTERNAL_ERROR; spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Generate the session data keys. */ status = session_manager->generate_session_data_keys (session_manager, session); if (status != 0) { status = CMD_HANDLER_SPDM_RESPONDER_INTERNAL_ERROR; spdm_error = SPDM_ERROR_UNSPECIFIED; goto exit; } /* Set the payload length. */ cmd_interface_msg_set_message_payload_length (request, response_size); exit: if (status != 0) { spdm_generate_error_response (request, state->connection_info.version.minor_version, spdm_error, 0x00, NULL, 0, SPDM_REQUEST_FINISH, status); } return 0; } /** * Process SPDM end session request. * * @param spdm_responder SPDM responder instance. * @param request END_SESSION request to process. * * @return 0 if request processed successfully or an error code. */ int spdm_end_session (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_end_session_request *spdm_request; struct spdm_end_session_response *spdm_response; size_t response_size; uint32_t session_id; struct spdm_secure_session *session; struct spdm_state *state; const struct spdm_transcript_manager *transcript_manager; struct spdm_secure_session_manager *session_manager; if ((spdm_responder == NULL) || (request == NULL)) { return CMD_HANDLER_SPDM_RESPONDER_INVALID_ARGUMENT; } transcript_manager = spdm_responder->transcript_manager; state = spdm_responder->state; session_manager = spdm_responder->session_manager; if (session_manager == NULL) { status = CMD_HANDLER_SPDM_RESPONDER_UNEXPECTED_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } /* Validate request. This message can only be in a secured session, so checking exact size. */ if (request->payload_length != sizeof (struct spdm_end_session_request)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } spdm_request = (struct spdm_end_session_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_INVALID_CONNECTION_STATE; spdm_error = SPDM_ERROR_UNEXPECTED_REQUEST; goto exit; } /* Confirm that we are in a session. */ if (session_manager->is_last_session_id_valid (session_manager) == false) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_CONNECTION_STATE; spdm_error = SPDM_ERROR_SESSION_REQUIRED; goto exit; } /* Session id is retrieved from the secure session message header. */ session_id = session_manager->get_last_session_id (session_manager); session = session_manager->get_session (session_manager, session_id); if (session == NULL) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_CONNECTION_STATE; spdm_error = SPDM_ERROR_SESSION_REQUIRED; goto exit; } /* Check if the session is in the correct state. */ if (session->session_state != SPDM_SESSION_STATE_ESTABLISHED) { status = CMD_HANDLER_SPDM_RESPONDER_UNEXPECTED_REQUEST; spdm_error = SPDM_ERROR_UNEXPECTED_REQUEST; goto exit; } /* Reset the transcript manager state as per the request code. */ spdm_reset_transcript_via_request_code (state, transcript_manager, SPDM_REQUEST_END_SESSION); session->end_session_attributes = spdm_request->end_session_attributes; if (spdm_request->end_session_attributes.negotiated_state_preservation_indicator == SPDM_END_SESSION_REQUEST_ATTRIBUTES_PRESERVE_NEGOTIATED_STATE_CLEAR) { state->connection_info.end_session_attributes.negotiated_state_preservation_indicator = SPDM_END_SESSION_REQUEST_ATTRIBUTES_PRESERVE_NEGOTIATED_STATE_CLEAR; } /* Construct the response. */ response_size = sizeof (struct spdm_end_session_response); spdm_response = (struct spdm_end_session_response*) request->payload; memset (spdm_response, 0, response_size); spdm_populate_header (&spdm_response->header, SPDM_RESPONSE_END_SESSION, SPDM_GET_MINOR_VERSION (spdm_version)); spdm_response->reserved1 = 0; spdm_response->reserved2 = 0; /* Set the payload length. */ cmd_interface_msg_set_message_payload_length (request, response_size); exit: if (status != 0) { spdm_generate_error_response (request, state->connection_info.version.minor_version, spdm_error, 0x00, NULL, 0, SPDM_REQUEST_END_SESSION, status); } return 0; } /** * Process SPDM vendor defined request. * * @param context SPDM context. * @param request VENDOR_DEFINED_REQUEST request to process. * * @return 0 if request processed successfully or an error code. */ int spdm_vendor_defined_request (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_state *state; uint32_t session_id; struct spdm_secure_session *session; struct spdm_secure_session_manager *session_manager; struct spdm_vendor_defined_request_response *spdm_req_resp; if ((spdm_responder == NULL) || (request == NULL)) { return CMD_HANDLER_SPDM_RESPONDER_INVALID_ARGUMENT; } state = spdm_responder->state; session_manager = spdm_responder->session_manager; /* vdm_handler is optional */ if (spdm_responder->vdm_handler == NULL) { status = CMD_HANDLER_SPDM_RESPONDER_UNSUPPORTED_CAPABILITY; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } /* Validate request */ if (request->payload_length < sizeof (struct spdm_vendor_defined_request_response)) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST; spdm_error = SPDM_ERROR_INVALID_REQUEST; goto exit; } spdm_req_resp = (struct spdm_vendor_defined_request_response*) request->payload; spdm_version = SPDM_MAKE_VERSION (spdm_req_resp->header.spdm_major_version, spdm_req_resp->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) { status = CMD_HANDLER_SPDM_RESPONDER_INTERNAL_ERROR; spdm_handle_response_state (state, &spdm_error); goto exit; } if (state->connection_info.connection_state < SPDM_CONNECTION_STATE_NEGOTIATED) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_CONNECTION_STATE; spdm_error = SPDM_ERROR_UNEXPECTED_REQUEST; goto exit; } /* Confirm that we are in a session. */ if (request->is_encrypted) { if (session_manager->is_last_session_id_valid (session_manager) == false) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_CONNECTION_STATE; spdm_error = SPDM_ERROR_SESSION_REQUIRED; goto exit; } /* Session id is retrieved from the secure session message header. */ session_id = session_manager->get_last_session_id (session_manager); session = session_manager->get_session (session_manager, session_id); if (session == NULL) { status = CMD_HANDLER_SPDM_RESPONDER_INVALID_CONNECTION_STATE; spdm_error = SPDM_ERROR_SESSION_REQUIRED; goto exit; } /* Check if the session is in the correct state. */ if (session->session_state != SPDM_SESSION_STATE_ESTABLISHED) { status = CMD_HANDLER_SPDM_RESPONDER_UNEXPECTED_REQUEST; spdm_error = SPDM_ERROR_UNEXPECTED_REQUEST; goto exit; } } /* consume SPDM related header */ cmd_interface_msg_remove_protocol_header (request, offsetof (struct spdm_vendor_defined_request_response, standard_id)); status = spdm_responder->vdm_handler->process_request (spdm_responder->vdm_handler, request); cmd_interface_msg_add_protocol_header (request, offsetof (struct spdm_vendor_defined_request_response, standard_id)); if (status != 0) { spdm_error = SPDM_ERROR_UNSPECIFIED; if (status == CMD_HANDLER_UNKNOWN_MESSAGE_TYPE) { spdm_error = SPDM_ERROR_UNSUPPORTED_REQUEST; } goto exit; } /* Update SPDM header. */ spdm_populate_header (&spdm_req_resp->header, SPDM_RESPONSE_VENDOR_DEFINED_REQUEST, SPDM_GET_MINOR_VERSION (spdm_version)); exit: if (status != 0) { spdm_generate_error_response (request, state->connection_info.version.minor_version, spdm_error, 0x00, NULL, 0, SPDM_REQUEST_VENDOR_DEFINED_REQUEST, status); } return 0; } /** * Format signature digest for SPDM v1.2+ according to section 15 in DSP0274 SPDM spec. * * @param hash Hashing engine to utilize. * @param hash_type Hash type to utilize. * @param spdm_minor_version SPDM minor version to utilize. * @param spdm_context Context string to utilize. * @param digest Buffer of size HASH_MAX_HASH_LEN with data to be signed incoming, formatted digest * outgoing. * * @return 0 if completed successfully, or an error code */ int spdm_format_signature_digest (const struct hash_engine *hash, enum hash_type hash_type, uint8_t spdm_minor_version, char *spdm_context, uint8_t *digest) { uint8_t combined_spdm_prefix[SPDM_COMBINED_PREFIX_LEN] = {0}; char spdm_prefix[] = "dmtf-spdm-v1.x.*"; size_t spdm_prefix_len = strlen (spdm_prefix); size_t hash_len = hash_get_hash_length (hash_type); int status; spdm_prefix[13] = spdm_minor_version + '0'; strcpy ((char*) combined_spdm_prefix, spdm_prefix); strcpy ((char*) &combined_spdm_prefix[spdm_prefix_len], spdm_prefix); strcpy ((char*) &combined_spdm_prefix[spdm_prefix_len * 2], spdm_prefix); strcpy ((char*) &combined_spdm_prefix[spdm_prefix_len * 3], spdm_prefix); strcpy ((char*) &combined_spdm_prefix[100 - strlen (spdm_context)], spdm_context); status = hash_start_new_hash (hash, hash_type); if (status != 0) { return status; } status = hash->update (hash, combined_spdm_prefix, sizeof (combined_spdm_prefix)); if (status != 0) { goto fail; } status = hash->update (hash, digest, hash_len); if (status != 0) { goto fail; } status = hash->finish (hash, digest, HASH_MAX_HASH_LEN); if (status == 0) { return status; } fail: hash->cancel (hash); return status; } /** * Initialize the SPDM state. * * @param state SPDM state. * * @return 0 if the state was successfully initialized or an error code. */ int spdm_init_state (struct spdm_state *state) { int status = 0; if (state == NULL) { status = CMD_HANDLER_SPDM_INVALID_ARGUMENT; goto exit; } memset (state, 0, sizeof (struct spdm_state)); /* Initialize the state. */ state->connection_info.connection_state = SPDM_CONNECTION_STATE_NOT_STARTED; state->response_state = SPDM_RESPONSE_STATE_NORMAL; exit: return status; }