core/attestation/attestation_requester.c (2,549 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include <stdint.h> #include <string.h> #include "attestation.h" #include "attestation_logging.h" #include "attestation_requester.h" #include "pcr_store.h" #include "asn1/asn1_util.h" #include "asn1/ecc_der_util.h" #include "cmd_interface/cerberus_protocol.h" #include "cmd_interface/cerberus_protocol_master_commands.h" #include "cmd_interface/cerberus_protocol_required_commands.h" #include "common/buffer_util.h" #include "common/common_math.h" #include "common/type_cast.h" #include "common/unused.h" #include "logging/debug_log.h" #include "manifest/cfm/cfm_manager.h" #include "mctp/mctp_control_protocol.h" #include "mctp/mctp_control_protocol_commands.h" #include "mctp/mctp_interface.h" #include "spdm/spdm_commands.h" #include "spdm/spdm_discovery.h" #include "spdm/spdm_measurements.h" #include "spdm/spdm_protocol.h" // Context strings used in SPDM signatures #define SPDM_CHALLENGE_SIGNATURE_CONTEXT_STR "responder-challenge_auth signing" #define SPDM_GET_MEASUREMENTS_SIGNATURE_CONTEXT_STR "responder-measurements signing" /** * Maximum length for an SPDM message. */ #define ATTESTATION_REQUESTER_MAX_SPDM_REQUEST \ (MCTP_BASE_PROTOCOL_MAX_MESSAGE_BODY - sizeof (struct spdm_protocol_mctp_header)) /** * Check to see if response received is for pending Cerberus request * * @param attestation Attestation requester instance to utilize * @param command Command expected */ #define attestation_requester_check_cerberus_unexpected_rsp(attestation, command) \ ((attestation->state->txn.protocol != ATTESTATION_PROTOCOL_CERBERUS) || \ (attestation->state->txn.requested_command != command)) /** * Check to see if response received is for pending SDPM request * * @param attestation Attestation requester instance to utilize * @param command Command expected */ #define attestation_requester_check_spdm_unexpected_rsp(attestation, command) \ ((attestation->state->txn.protocol != ATTESTATION_PROTOCOL_DMTF_SPDM) || \ (attestation->state->txn.requested_command != command)) /** * Check to see if device version set found * * @param attestation Attestation requester instance to utilize */ #define attestation_requester_is_version_set_selected(attestation) \ (attestation->state->txn.device_version_set != 0) #if defined (ATTESTATION_SUPPORT_SPDM) || defined (ATTESTATION_SUPPORT_CERBERUS_CHALLENGE) /** * Function to send Cerberus protocol or SPDM request and wait for a response. This function * assumes a pregenerated request is in attestation_requester's msg_buffer. * * @param attestation Attestation requester instance to utilize. * @param request_len Length of request to send. * @param dest_addr SMBus address of destination device. * @param dest_eid MCTP EID of destination device. * @param crypto_timeout Flag indicating whether to use the crypto timeout with device. * @param mctp_ctrl_cmd Flag indicating whether request is from MCTP control protocol. * @param command Requested command to send out. * * @return 0 if successful or error code otherwise */ static int attestation_requester_send_request_and_get_response ( const struct attestation_requester *attestation, size_t request_len, uint8_t dest_addr, uint8_t dest_eid, bool crypto_timeout, bool mctp_ctrl_cmd, uint8_t command) { uint32_t timeout_ms; uint32_t max_rsp_not_ready_timeout_ms; uint8_t max_rsp_not_ready_retries; bool rsp_ready = false; int device_state; int status; attestation->state->txn.request_status = ATTESTATION_REQUESTER_REQUEST_IDLE; attestation->state->txn.requested_command = command; if (mctp_ctrl_cmd) { timeout_ms = device_manager_get_mctp_ctrl_timeout (attestation->device_mgr); } else { if (crypto_timeout) { timeout_ms = device_manager_get_crypto_timeout_by_eid (attestation->device_mgr, dest_eid); } else { timeout_ms = device_manager_get_reponse_timeout_by_eid (attestation->device_mgr, dest_eid); } } status = device_manager_get_rsp_not_ready_limits (attestation->device_mgr, &max_rsp_not_ready_timeout_ms, &max_rsp_not_ready_retries); if (status != 0) { return status; } while (!rsp_ready) { /* Send request and await response. mctp_interface_issue_request will block till a response * is received or timeout period elapses. If response is received, the notification * callbacks will process response and update the request_status. */ status = mctp_interface_issue_request (attestation->mctp, attestation->channel, dest_addr, dest_eid, attestation->state->txn.msg_buffer, request_len, attestation->state->txn.msg_buffer, sizeof (attestation->state->txn.msg_buffer), timeout_ms); if (status != 0) { if (status == MCTP_BASE_PROTOCOL_RESPONSE_TIMEOUT) { device_state = device_manager_get_device_state_by_eid (attestation->device_mgr, dest_eid); if (device_state == DEVICE_MANAGER_AUTHENTICATED) { device_manager_update_device_state_by_eid (attestation->device_mgr, dest_eid, DEVICE_MANAGER_AUTHENTICATED_WITH_TIMEOUT); } else if (device_state == DEVICE_MANAGER_AUTHENTICATED_WITHOUT_CERTS) { device_manager_update_device_state_by_eid (attestation->device_mgr, dest_eid, DEVICE_MANAGER_AUTHENTICATED_WITHOUT_CERTS_WITH_TIMEOUT); } else { device_manager_update_device_state_by_eid (attestation->device_mgr, dest_eid, DEVICE_MANAGER_ATTESTATION_INTERRUPTED); } } else if ((status == MCTP_BASE_PROTOCOL_ERROR_RESPONSE) || (status == MCTP_BASE_PROTOCOL_FAIL_RESPONSE)) { device_manager_update_device_state_by_eid (attestation->device_mgr, dest_eid, DEVICE_MANAGER_ATTESTATION_INVALID_RESPONSE); } return status; } if (attestation->state->txn.request_status != ATTESTATION_REQUESTER_REQUEST_SUCCESSFUL) { device_manager_update_device_state_by_eid (attestation->device_mgr, dest_eid, DEVICE_MANAGER_ATTESTATION_INVALID_RESPONSE); return ATTESTATION_REQUEST_FAILED; } /* If SPDM, responder might send a ResponseNotReady error. The ResponseNotReady notification * will set sleep_duration_ms to a non-zero value based on the error response as per the * SPDM DSP0274 spec. First, sleep for the duration requested until response is ready, then * send a RESPOND_IF_READY request to retrieve response to original request. */ if (attestation->state->txn.sleep_duration_ms != 0) { if (max_rsp_not_ready_retries == 0) { device_manager_update_device_state_by_eid (attestation->device_mgr, dest_eid, DEVICE_MANAGER_ATTESTATION_INVALID_RESPONSE); return ATTESTATION_TOO_MANY_RETRIES_REQUESTED; } --max_rsp_not_ready_retries; attestation->state->txn.sleep_duration_ms = min (max_rsp_not_ready_timeout_ms, attestation->state->txn.sleep_duration_ms); platform_msleep (attestation->state->txn.sleep_duration_ms); attestation->state->txn.sleep_duration_ms = 0; request_len = spdm_generate_respond_if_ready_request (attestation->state->spdm_msg_buffer, ATTESTATION_REQUESTER_MAX_SPDM_REQUEST, attestation->state->txn.requested_command, attestation->state->txn.respond_if_ready_token, attestation->state->txn.spdm_minor_version); if (ROT_IS_ERROR ((int) request_len)) { return request_len; } spdm_populate_mctp_header (attestation->state->spdm_mctp); request_len += sizeof (struct spdm_protocol_mctp_header); } else { rsp_ready = true; } } return 0; } /** * Check if digest matches an allowable digest for the device. * * @param attestation Attestation requester instance to utilize. * @param allowable_digests Allowable digests container to utilize. * @param digest Buffer populated with digest to verify. If set to NULL, msg_buffer will be used for * the verification. * @param digest_type Type of digest in digest buffer. * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_verify_digest_in_allowable_list ( const struct attestation_requester *attestation, struct cfm_digests *allowable_digests, uint8_t *digest, enum hash_type digest_type) { size_t offset = 0; size_t digest_len; size_t i_digest; int status; if (digest == NULL) { digest = attestation->state->txn.msg_buffer; } if (allowable_digests->hash_type != digest_type) { status = ATTESTATION_CFM_INVALID_ATTESTATION; } else { digest_len = hash_get_hash_length (digest_type); for (i_digest = 0; i_digest < allowable_digests->digest_count; ++i_digest) { status = buffer_compare (digest, &allowable_digests->digests[offset], digest_len); if (status == 0) { break; } else { offset += digest_len; status = ATTESTATION_CFM_ATTESTATION_RULE_FAIL; } } } return status; } /** * Check if PMR digest received in msg_buffer matches an allowable PMR digest for the device from * the active CFM. * * @param attestation Attestation requester instance to utilize. * @param active_cfm Active CFM to utilize. * @param component_id The component ID of the device. * @param eid EID of device to attest. * @param pmr_id ID of PMR to verify. * * @return Completion status, 0 if success, CFM_PMR_DIGEST_NOT_FOUND if PMR has no entry in CFM, or * an error code otherwise */ static int attestation_requester_verify_pmr (const struct attestation_requester *attestation, const struct cfm *active_cfm, uint32_t component_id, uint8_t eid, uint8_t pmr_id) { struct cfm_pmr_digest pmr_digest; int status; status = active_cfm->get_component_pmr_digest (active_cfm, component_id, pmr_id, &pmr_digest); if (status == 0) { status = attestation_requester_verify_digest_in_allowable_list (attestation, &pmr_digest.digests, NULL, attestation->state->txn.transcript_hash_type); if (status != 0) { device_manager_update_device_state_by_eid (attestation->device_mgr, eid, DEVICE_MANAGER_ATTESTATION_MEASUREMENT_MISMATCH); } active_cfm->free_component_pmr_digest (active_cfm, &pmr_digest); } return status; } #endif #if defined (ATTESTATION_SUPPORT_SPDM) || defined (ATTESTATION_SUPPORT_CERBERUS_CHALLENGE) /** * Update the cached certificate chain digest for a device. * * @param attestation Attestation requester processing the received digest. * @param device_eid EID of the device reporting the digest. * @param digest Reported digest of the certificate chain. * @param digest_length Length of the cert chain digest. * * @return 0 if the cert chain digest was updated successfully or an error code. */ static int attestation_requester_update_cert_chain_digest ( const struct attestation_requester *attestation, uint8_t device_eid, const uint8_t *digest, size_t digest_length) { const struct device_manager_key *alias_key; int status; alias_key = device_manager_get_alias_key (attestation->device_mgr, device_eid); if (alias_key == NULL) { status = device_manager_update_cert_chain_digest (attestation->device_mgr, device_eid, attestation->state->txn.slot_num, digest, digest_length); } else { status = device_manager_compare_cert_chain_digest (attestation->device_mgr, device_eid, digest, digest_length); if ((status == DEVICE_MGR_DIGEST_MISMATCH) || (status == DEVICE_MGR_DIGEST_LEN_MISMATCH)) { status = device_manager_clear_alias_key (attestation->device_mgr, device_eid); if (status != 0) { return status; } status = device_manager_update_cert_chain_digest (attestation->device_mgr, device_eid, attestation->state->txn.slot_num, digest, digest_length); } } return status; } /** * Verify that the root certificate is trusted. If so, load it into the certificate store for cert * chain authentication. * * @param attestation Attestation requester validating the root CA. * @param eid EID of the device that provided the certificate. * @param active_cfm Active CFM being used for attestation. * @param component_id The component ID of the device being attested. * @param root_ca Certificate data for the root CA to verify. * @param root_ca_length Length of the root CA certificate data. * @param header_data Additional header data that needs to be included in the certificate chain * hash. Set to null if there is no header data. * @param header_length Length of the additional certificate chain header. * @param certs_chain CA certificate storage that will be initialized and updated with the validated * root CA. * * @return 0 if the root cert is trusted or an error code. */ static int attestation_requester_verify_and_load_root_cert ( const struct attestation_requester *attestation, uint8_t eid, const struct cfm *active_cfm, uint32_t component_id, const uint8_t *root_ca, size_t root_ca_length, const uint8_t *header_data, size_t header_length, struct x509_ca_certs *certs_chain) { uint8_t digest[HASH_MAX_HASH_LEN]; struct cfm_root_ca_digests root_ca_digests; const struct der_cert *local_root_ca = riot_key_manager_get_root_ca (attestation->riot); bool cfm_root_ca = false; int status; /* 1) If CFM has an alternate allowed root CA, make sure root CA provided by device matches * 2) If CFM has no alternate root CA, and requester has no provisioned root CA, then just use * device's root CA provided. The requester with no root CA will ultimately fail * attestation but device's attestation can succeed, useful for testing. * 3) If CFM has no alternate root CA, but requester has a provisioned root CA, use the * requester's root CA then skip over root CA provided by device. */ status = active_cfm->get_root_ca_digest (active_cfm, component_id, &root_ca_digests); if (status == 0) { status = hash_calculate (attestation->primary_hash, root_ca_digests.digests.hash_type, root_ca, root_ca_length, digest, sizeof (digest)); if (ROT_IS_ERROR (status)) { active_cfm->free_root_ca_digest (active_cfm, &root_ca_digests); return status; } status = attestation_requester_verify_digest_in_allowable_list (attestation, &root_ca_digests.digests, digest, root_ca_digests.digests.hash_type); if (status != 0) { device_manager_update_device_state_by_eid (attestation->device_mgr, eid, DEVICE_MANAGER_ATTESTATION_UNTRUSTED_CERTS); active_cfm->free_root_ca_digest (active_cfm, &root_ca_digests); return status; } active_cfm->free_root_ca_digest (active_cfm, &root_ca_digests); cfm_root_ca = true; } else if (status != CFM_ROOT_CA_NOT_FOUND) { return status; } status = attestation->x509->init_ca_cert_store (attestation->x509, certs_chain); if (status != 0) { return status; } if (cfm_root_ca || (local_root_ca == NULL)) { status = attestation->x509->add_root_ca (attestation->x509, certs_chain, root_ca, root_ca_length); } else { status = attestation->x509->add_root_ca (attestation->x509, certs_chain, local_root_ca->cert, local_root_ca->length); } if (status != 0) { goto release_cert_store; } /* Begin hashing the received certificate chain, starting with the certificate header and root * certificate. */ status = hash_start_new_hash (attestation->primary_hash, attestation->state->txn.transcript_hash_type); if (status != 0) { goto release_cert_store; } if (header_data != NULL) { status = attestation->primary_hash->update (attestation->primary_hash, header_data, header_length); if (status != 0) { goto cancel_hash; } } status = attestation->primary_hash->update (attestation->primary_hash, root_ca, root_ca_length); if (status != 0) { goto cancel_hash; } return 0; cancel_hash: attestation->primary_hash->cancel (attestation->primary_hash); release_cert_store: attestation->x509->release_ca_cert_store (attestation->x509, certs_chain); return status; } /** * Verify that a received certificate is trusted. The trust anchor for this certificate must * already be present in the certificate store. If the certificate is valid and is a CA, the * certificate store will be updated to make this certificate the new trust anchor for future certs. * * In error scenarios, the active hash for the current certificate chain will be cancelled and all * allocated certificate components will be released. * * For CA certs, the provided cert store will be valid upon successful return and the certificate * instance will always be released. * For leaf certs, the provided certificate instance will contain the parsed cert upon successful * return and the cert store will always be released. * * @param attestation Attestation requester validating the certificate. * @param eid EID of the device that provided the certificate. * @param cert_data Certificate data for the to verify. * @param cert_length Length of the certificate data. * @param is_leaf Flag to indicate if this is the last cert in the chain or if it's expected to be a * CA certificate. * @param cert Certificate container to use for parsing the received certificate. This must not * currently be in use for a different certificate. This will only contain a certificate upon * successfully verification of the leaf certificate. In all other cases, this instance will be * empty. * @param certs_chain CA certificate storage that will be used to verify the received cert. This * will be updated with the new trust anchor for CA certificates. If verifying the leaf cert, this * instance will always be released upon return. * * @return 0 if the certificate was parsed and validated successfully or an error code. */ static int attestation_requester_verify_and_load_certificate ( const struct attestation_requester *attestation, uint8_t eid, const uint8_t *cert_data, size_t cert_length, bool is_leaf, struct x509_certificate *cert, struct x509_ca_certs *certs_chain) { int status; status = attestation->primary_hash->update (attestation->primary_hash, cert_data, cert_length); if (status != 0) { goto release_cert_store; } /* Authenticate the received certificate against the trusted CA already in the certificate * store. */ status = attestation->x509->load_certificate (attestation->x509, cert, cert_data, cert_length); if (status != 0) { goto release_cert_store; } status = attestation->x509->authenticate (attestation->x509, cert, certs_chain); if (status != 0) { device_manager_update_device_state_by_eid (attestation->device_mgr, eid, DEVICE_MANAGER_ATTESTATION_UNTRUSTED_CERTS); goto release_cert; } if (!is_leaf) { /* If this is not the last certificate in the chain, initialize a new certificate store to * use for authenticating the next cert. */ attestation->x509->release_certificate (attestation->x509, cert); attestation->x509->release_ca_cert_store (attestation->x509, certs_chain); status = attestation->x509->init_ca_cert_store (attestation->x509, certs_chain); if (status != 0) { goto cancel_hash; } status = attestation->x509->add_trusted_ca (attestation->x509, certs_chain, cert_data, cert_length); if (status != 0) { goto release_cert_store; } } else { /* If this is the last cert, release the cert store since it won't be needed anymore. */ attestation->x509->release_ca_cert_store (attestation->x509, certs_chain); } return 0; release_cert: attestation->x509->release_certificate (attestation->x509, cert); release_cert_store: attestation->x509->release_ca_cert_store (attestation->x509, certs_chain); cancel_hash: attestation->primary_hash->cancel (attestation->primary_hash); return status; } /** * Parse the alias key from a leaf certificate and set the alias key for the device. This will only * happen if the certificate chain digest matches the expected digest. * * Upon returning from this call, in all cases, the certificate chain hash will be finished and the * leaf certificate instance will be released. * * @param attestation The attestation requester processing a received alias certificate. * @param eid EID of the device associated with the key. * @param digest_length Length of the certificate chain digest. * @param cert Certificate for the alias key. This must have already been authenticated. Upon * returning from this call, this instance will always be released. * * @return 0 if the alias key was updated successfully or an error code. */ static int attestation_requester_update_alias_key (const struct attestation_requester *attestation, uint8_t eid, size_t digest_length, struct x509_certificate *cert) { uint8_t digest[HASH_MAX_HASH_LEN]; int leaf_key_type; uint8_t *leaf_key; size_t leaf_key_len; int status; status = attestation->primary_hash->finish (attestation->primary_hash, digest, sizeof (digest)); if (ROT_IS_ERROR (status)) { attestation->primary_hash->cancel (attestation->primary_hash); goto release_leaf_cert; } status = device_manager_compare_cert_chain_digest (attestation->device_mgr, eid, digest, digest_length); if (status != 0) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_CERT_CHAIN_COMPUTED_DIGEST_MISMATCH, (eid << 8) | attestation->state->txn.slot_num, status); device_manager_update_device_state_by_eid (attestation->device_mgr, eid, DEVICE_MANAGER_ATTESTATION_UNTRUSTED_CERTS); goto release_leaf_cert; } leaf_key_type = attestation->x509->get_public_key_type (attestation->x509, cert); if (ROT_IS_ERROR (leaf_key_type)) { status = leaf_key_type; goto release_leaf_cert; } status = attestation->x509->get_public_key (attestation->x509, cert, &leaf_key, &leaf_key_len); if (status != 0) { goto release_leaf_cert; } status = device_manager_update_alias_key (attestation->device_mgr, eid, leaf_key, leaf_key_len, leaf_key_type); platform_free (leaf_key); release_leaf_cert: attestation->x509->release_certificate (attestation->x509, cert); return status; } /** * Verify provided ECDSA signature using cached alias key of target device. * * @param attestation Attestation requester instance to utilize. * @param signature Buffer with signature sent by target device to verify. * @param signature_len Length of signature. * @param digest Hash to be verified against the signature. * @param transcript_hash_len Length of the digest. * @param alias_key Key used to verify the signature. * * @return 0 if completed successfully, or an error code */ static int attestation_requester_verify_ecdsa_signature ( const struct attestation_requester *attestation, uint8_t *signature, size_t signature_len, uint8_t *digest, size_t transcript_hash_len, const struct device_manager_key *alias_key) { struct ecc_public_key ecc_key; int signature_der_len; uint8_t signature_der[ECC_DER_ECDSA_MAX_LENGTH]; int status; // SPDM signatures are not DER encoded, encode before processing if (attestation->state->txn.protocol != ATTESTATION_PROTOCOL_CERBERUS) { signature_der_len = ecc_der_encode_ecdsa_signature (signature, &signature[attestation->state->txn.alias_signature_len], attestation->state->txn.alias_signature_len, signature_der, sizeof (signature_der)); if (ROT_IS_ERROR (signature_der_len)) { return signature_der_len; } } else { memcpy (signature_der, signature, signature_len); signature_der_len = signature_len; } status = attestation->ecc->init_public_key (attestation->ecc, alias_key->key, alias_key->key_len, &ecc_key); if (status != 0) { return status; } status = attestation->ecc->verify (attestation->ecc, &ecc_key, digest, transcript_hash_len, signature_der, signature_der_len); attestation->ecc->release_key_pair (attestation->ecc, NULL, &ecc_key); return status; } /** * Finalize current hash operation, then verify provided signature using cached alias key of * target device using provided EID. * * @param attestation Attestation requester instance to utilize. * @param hash Hashing engine to utilize. * @param eid EID of target device. * @param signature Buffer with signature sent by target device to verify. * @param signature_len Length of signature. * @param spdm_context Context string to utilize. Can be set to NULL if not used. * * @return 0 if completed successfully, or an error code */ static int attestation_requester_verify_signature (const struct attestation_requester *attestation, const struct hash_engine *hash, uint8_t eid, uint8_t *signature, size_t signature_len, char *spdm_context) { uint8_t digest[HASH_MAX_HASH_LEN]; const struct device_manager_key *alias_key; size_t transcript_hash_len = hash_get_hash_length (attestation->state->txn.transcript_hash_type); int status; /* TODO: This signature verification flow does not use the ECDSA APIs so is not compatible with * FIPS. Updating this flow to use the ECDSA implementation is more complicated, so this is * deferred until necessary or this piece gets refactored to make the migration easier. * * The same is true for RSASSA, if that's supported. */ status = hash->finish (hash, digest, sizeof (digest)); if (status != 0) { return status; } attestation->state->txn.hash_finish = true; // Skip signature verification for SPDM protocol when cert is not supported. if ((attestation->state->txn.protocol != ATTESTATION_PROTOCOL_CERBERUS) && !attestation->state->txn.cert_supported) { return 0; } #ifdef ATTESTATION_SUPPORT_SPDM if ((attestation->state->txn.protocol != ATTESTATION_PROTOCOL_CERBERUS) && (attestation->state->txn.spdm_minor_version >= ATTESTATION_PROTOCOL_DMTF_SPDM_1_2)) { status = spdm_format_signature_digest (hash, attestation->state->txn.transcript_hash_type, attestation->state->txn.spdm_minor_version, spdm_context, digest); if (status != 0) { return status; } } #endif alias_key = device_manager_get_alias_key (attestation->device_mgr, eid); if (alias_key == NULL) { return ATTESTATION_ALIAS_KEY_LOAD_FAIL; } if (alias_key->key_type == X509_PUBLIC_KEY_ECC) { status = attestation_requester_verify_ecdsa_signature (attestation, signature, signature_len, digest, transcript_hash_len, alias_key); /* The endianess of the signature is not clearly declared in the SPDM 1.0 and 1.1 * specification. It's not possible to programmatically identify the endianess used. * To cover the case where the signature fails due to the endianess, * this code swaps the signature endianess, and retries the verification. */ if ((status == ECC_ENGINE_BAD_SIGNATURE) && (attestation->state->txn.spdm_minor_version < ATTESTATION_PROTOCOL_DMTF_SPDM_1_2) && (attestation->state->txn.protocol != ATTESTATION_PROTOCOL_CERBERUS)) { buffer_reverse (signature, signature_len / 2); buffer_reverse (signature + (signature_len / 2), signature_len / 2); status = attestation_requester_verify_ecdsa_signature (attestation, signature, signature_len, digest, transcript_hash_len, alias_key); } } #ifdef ATTESTATION_SUPPORT_RSA_CHALLENGE else if ((alias_key->key_type == X509_PUBLIC_KEY_RSA) && (attestation->rsa != NULL)) { struct rsa_public_key rsa_key; status = attestation->rsa->init_public_key (attestation->rsa, &rsa_key, alias_key->key, alias_key->key_len); if (status != 0) { return status; } status = attestation->rsa->sig_verify (attestation->rsa, &rsa_key, signature, signature_len, HASH_TYPE_SHA256, digest, transcript_hash_len); } #endif else { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_ALIAS_KEY_TYPE_UNSUPPORTED, eid, alias_key->key_type); return ATTESTATION_UNSUPPORTED_ALGORITHM; } return status; } #endif #ifdef ATTESTATION_SUPPORT_SPDM /** * SPDM get version response post processing function. * * @param attestation Attestation requester instance to utilize. * @param device_eid EID of device to utilize. * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_get_version_rsp_post_processing ( const struct attestation_requester *attestation, uint8_t device_eid) { struct spdm_get_version_response *rsp = (struct spdm_get_version_response*) attestation->state->txn.msg_buffer; struct spdm_version_num_entry *version_table = spdm_get_version_resp_version_table (rsp); uint8_t minor_version = SPDM_MIN_MINOR_VERSION; bool found = false; int i_version; for (i_version = 0; i_version < rsp->version_num_entry_count; ++i_version, ++version_table) { if ((version_table->major_version != SPDM_MAJOR_VERSION) || (version_table->alpha > 0)) { continue; } if ((version_table->minor_version >= minor_version) && (version_table->minor_version <= SPDM_MAX_MINOR_VERSION)) { minor_version = version_table->minor_version; found = true; } } if (found) { attestation->state->txn.spdm_minor_version = (enum attestation_spdm_minor_version) minor_version; attestation->state->txn.protocol = (enum attestation_protocol) ATTESTATION_PROTOCOL_DMTF_SPDM; return 0; } debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_DEVICE_NOT_INTEROPERABLE, device_eid, 0); return ATTESTATION_DEVICE_NOT_INTEROPERABLE; } /** * SPDM get capabilities response post processing function. * * @param attestation Attestation requester instance to utilize. * @param device_eid EID of device to utilize. * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_get_capabilities_rsp_post_processing ( const struct attestation_requester *attestation, uint8_t device_eid) { struct spdm_get_capabilities *rsp = (struct spdm_get_capabilities*) attestation->state->txn.msg_buffer; struct device_manager_full_capabilities capabilities; uint8_t ct_exponent; int device_num; int status; /* Device is assumed to either support, or eventually support after a FW update, minimum * capabilities needed to complete attestation since it is included in CFM. If device does not, * attestation will fail but will continue to retry until an expected FW adds required * support. */ attestation->state->txn.cert_supported = rsp->base_capabilities.flags.cert_cap; if (!rsp->base_capabilities.flags.meas_cap) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_MEASUREMENT_CAP_NOT_SUPPORTED, device_eid, *((uint32_t*) &rsp->base_capabilities.flags)); return ATTESTATION_GET_MEAS_NOT_SUPPORTED_BY_DEVICE; } if (attestation->state->txn.cert_supported) { if (rsp->base_capabilities.flags.meas_cap != SPDM_MEASUREMENT_RSP_CAP_MEASUREMENTS_WITH_SIG) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_GET_MEASUREMENT_CAP_MISMATCH_ERROR, device_eid, *((uint32_t*) &rsp->base_capabilities.flags)); return ATTESTATION_GET_MEAS_CAP_MISMATCH_BY_DEVICE; } } else { if (rsp->base_capabilities.flags.meas_cap != SPDM_MEASUREMENT_RSP_CAP_MEASUREMENTS_WITHOUT_SIG) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_GET_MEASUREMENT_CAP_MISMATCH_ERROR, device_eid, *((uint32_t*) &rsp->base_capabilities.flags)); return ATTESTATION_GET_MEAS_CAP_MISMATCH_BY_DEVICE; } if (rsp->base_capabilities.flags.chal_cap) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_CHALLENGE_CAP_MISMATCH_ERROR, device_eid, *((uint32_t*) &rsp->base_capabilities.flags)); return ATTESTATION_CHAL_CAP_MISMATCH_BY_DEVICE; } } attestation->state->txn.challenge_supported = rsp->base_capabilities.flags.chal_cap; if (attestation->state->txn.device_discovery) { return 0; } device_num = device_manager_get_device_num (attestation->device_mgr, device_eid); if (ROT_IS_ERROR (device_num)) { return device_num; } status = device_manager_get_device_capabilities (attestation->device_mgr, device_num, &capabilities); if (status != 0) { return status; } /* Limit maximum cryptographic timeout period of responder to prevent overflows. This * assumes 25.5 seconds is plenty of time, so if a device responds with a timeout longer * than that, we will fail attestation at 25.5s even if device thinks it has more time to * respond. */ ct_exponent = (rsp->base_capabilities.ct_exponent > 24) ? 24 : rsp->base_capabilities.ct_exponent; capabilities.max_timeout = device_manager_set_timeout_ms (SPDM_MAX_RESPONSE_TIMEOUT_MS); capabilities.max_sig = device_manager_set_crypto_timeout_ms (spdm_capabilities_rsp_ct_to_ms (ct_exponent)); if (rsp->base_capabilities.header.spdm_minor_version > 1) { capabilities.request.max_message_size = rsp->data_transfer_size; } return device_manager_update_device_capabilities (attestation->device_mgr, device_num, &capabilities); } /** * SPDM negotiate algorithms response post processing function. * * @param attestation Attestation requester instance to utilize. * @param device_eid EID of device to utilize. * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_negotiate_algorithms_rsp_post_processing ( const struct attestation_requester *attestation, uint8_t device_eid) { struct spdm_negotiate_algorithms_response *rsp = (struct spdm_negotiate_algorithms_response*) attestation->state->txn.msg_buffer; // Currently, only SPDM measurement blocks following the DMTF format are supported if (rsp->measurement_specification != SPDM_MEASUREMENT_SPEC_DMTF) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_MEASUREMENT_SPEC_UNSUPPORTED, device_eid, rsp->measurement_specification); return ATTESTATION_UNSUPPORTED_MEASUREMENT_SPEC; } if (attestation->state->txn.cert_supported) { if (rsp->base_asym_sel == SPDM_TPM_ALG_ECDSA_ECC_NIST_P256) { attestation->state->txn.alias_signature_len = ECC_KEY_LENGTH_256; } #if ECC_MAX_KEY_LENGTH >= ECC_KEY_LENGTH_384 else if (rsp->base_asym_sel == SPDM_TPM_ALG_ECDSA_ECC_NIST_P384) { attestation->state->txn.alias_signature_len = ECC_KEY_LENGTH_384; } #endif #if ECC_MAX_KEY_LENGTH >= ECC_KEY_LENGTH_521 else if (rsp->base_asym_sel == SPDM_TPM_ALG_ECDSA_ECC_NIST_P521) { attestation->state->txn.alias_signature_len = ECC_KEY_LENGTH_521; } #endif else { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_BASE_ASYM_KEY_SIG_ALG_UNSUPPORTED, device_eid, rsp->base_asym_sel); return ATTESTATION_UNSUPPORTED_ALGORITHM; } if ((rsp->base_hash_sel != SPDM_TPM_ALG_SHA_256) #ifdef HASH_ENABLE_SHA384 && (rsp->base_hash_sel != SPDM_TPM_ALG_SHA_384) #endif #ifdef HASH_ENABLE_SHA512 && (rsp->base_hash_sel != SPDM_TPM_ALG_SHA_512) #endif ) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_HASHING_ALGORITHM_UNSUPPORTED, device_eid, rsp->base_hash_sel); return ATTESTATION_UNSUPPORTED_ALGORITHM; } } else { /* DSP0274 SPDM spec indicates base_asym_sel and base_hash_sel shall be 0 if they are not * supported by the device. */ if (rsp->base_asym_sel != 0) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_BASE_ASYM_KEY_SIG_ALG_UNSUPPORTED, device_eid, rsp->base_asym_sel); return ATTESTATION_UNSUPPORTED_ALGORITHM; } if (rsp->base_hash_sel != 0) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_HASHING_ALGORITHM_UNSUPPORTED, device_eid, rsp->base_hash_sel); return ATTESTATION_UNSUPPORTED_ALGORITHM; } } if ((rsp->measurement_hash_algo != SPDM_MEAS_RSP_TPM_ALG_SHA_256) && (rsp->measurement_hash_algo != SPDM_MEAS_RSP_TPM_ALG_SHA_384) && (rsp->measurement_hash_algo != SPDM_MEAS_RSP_TPM_ALG_SHA_512)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_HASHING_MEAS_ALGORITHM_UNSUPPORTED, device_eid, rsp->measurement_hash_algo); return ATTESTATION_UNSUPPORTED_ALGORITHM; } if (!attestation->state->txn.device_discovery) { if (attestation->state->txn.cert_supported) { if (((rsp->base_hash_sel == SPDM_TPM_ALG_SHA_256) && (attestation->state->txn.transcript_hash_type != HASH_TYPE_SHA256)) || ((rsp->base_hash_sel == SPDM_TPM_ALG_SHA_384) && (attestation->state->txn.transcript_hash_type != HASH_TYPE_SHA384)) || ((rsp->base_hash_sel == SPDM_TPM_ALG_SHA_512) && (attestation->state->txn.transcript_hash_type != HASH_TYPE_SHA512))) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_HASH_ALGO_IN_RSP, device_eid, rsp->base_hash_sel); return ATTESTATION_UNEXPECTED_ALG_IN_RESPONSE; } } if (((rsp->measurement_hash_algo == SPDM_MEAS_RSP_TPM_ALG_SHA_256) && (attestation->state->txn.measurement_hash_type != HASH_TYPE_SHA256)) || ((rsp->measurement_hash_algo == SPDM_MEAS_RSP_TPM_ALG_SHA_384) && (attestation->state->txn.measurement_hash_type != HASH_TYPE_SHA384)) || ((rsp->measurement_hash_algo == SPDM_MEAS_RSP_TPM_ALG_SHA_512) && (attestation->state->txn.measurement_hash_type != HASH_TYPE_SHA512))) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_MEAS_HASH_ALGO_IN_RSP, device_eid, rsp->measurement_hash_algo); return ATTESTATION_UNEXPECTED_ALG_IN_RESPONSE; } } return 0; } /** * SPDM get digests response post processing function. * * @param attestation Attestation requester instance to utilize. * @param device_eid EID of device to utilize. * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_get_digests_rsp_post_processing ( const struct attestation_requester *attestation, uint8_t device_eid) { struct spdm_get_digests_response *rsp = (struct spdm_get_digests_response*) attestation->state->txn.msg_buffer; size_t transcript_hash_len; size_t rsp_len; uint8_t *digest; int status; transcript_hash_len = hash_get_hash_length (attestation->state->txn.transcript_hash_type); rsp_len = spdm_get_digests_resp_length (rsp, transcript_hash_len); if (attestation->state->txn.msg_buffer_len != rsp_len) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_RSP_LEN, (device_eid << 8) | SPDM_RESPONSE_GET_DIGESTS, ((uint16_t) rsp_len) << 16 | ((uint16_t) device_eid)); return ATTESTATION_BAD_LENGTH; } if ((rsp->slot_mask & (1 << attestation->state->txn.slot_num)) == 0) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_SLOT_NUMBER_EMPTY, device_eid, (attestation->state->txn.slot_num << 8) | rsp->slot_mask); status = ATTESTATION_REQUESTED_SLOT_NUM_EMPTY; } else { digest = spdm_get_digests_resp_digest (rsp, attestation->state->txn.slot_num, transcript_hash_len); status = attestation_requester_update_cert_chain_digest (attestation, device_eid, digest, transcript_hash_len); } return status; } /** * SPDM get certificate response post processing function. * * @param attestation Attestation requester instance to utilize. * @param device_eid EID of device to utilize. * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_get_certificate_rsp_post_processing ( const struct attestation_requester *attestation, uint8_t device_eid) { struct spdm_get_certificate_response *rsp = (struct spdm_get_certificate_response*) attestation->state->txn.msg_buffer; if (rsp->slot_num != attestation->state->txn.slot_num) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_SLOT_NUM_IN_RSP, device_eid, (attestation->state->txn.slot_num << 8) | rsp->slot_num); return ATTESTATION_UNEXPECTED_SLOT_NUM; } return 0; } /** * SPDM challenge response post processing function. * * @param attestation Attestation requester instance to utilize. * @param eid EID of device to utilize. * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_spdm_challenge_rsp_post_processing ( const struct attestation_requester *attestation, uint8_t device_eid) { struct spdm_challenge_response *rsp = (struct spdm_challenge_response*) attestation->state->txn.msg_buffer; size_t transcript_hash_len = hash_get_hash_length (attestation->state->txn.transcript_hash_type); int status; if (rsp->slot_num != attestation->state->txn.slot_num) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_SLOT_NUM_IN_RSP, device_eid, (attestation->state->txn.slot_num << 8) | rsp->slot_num); return ATTESTATION_UNEXPECTED_SLOT_NUM; } if (rsp->basic_mutual_auth_req) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_TARGET_REQ_UNSUPPORTED_MUTUAL_AUTH, device_eid, 0); return ATTESTATION_UNSUPPORTED_OPERATION; } if ((rsp->slot_mask & (1 << attestation->state->txn.slot_num)) == 0) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_SLOT_NUMBER_EMPTY, device_eid, (attestation->state->txn.slot_num << 8) | rsp->slot_mask); return ATTESTATION_REQUESTED_SLOT_NUM_EMPTY; } if (attestation->state->txn.msg_buffer_len <= spdm_get_challenge_resp_length (rsp, transcript_hash_len, transcript_hash_len)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_RSP_LEN, (device_eid << 8) | SPDM_RESPONSE_CHALLENGE, (((uint16_t) (spdm_get_challenge_resp_length (rsp, transcript_hash_len, transcript_hash_len))) << 16) | ((uint16_t) attestation->state->txn.msg_buffer_len)); return ATTESTATION_BAD_LENGTH; } status = device_manager_compare_cert_chain_digest (attestation->device_mgr, device_eid, spdm_get_challenge_resp_cert_chain_hash (rsp), transcript_hash_len); if (status != 0) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_CERT_CHAIN_DIGEST_MISMATCH, device_eid, rsp->slot_num); } return status; } /** * SPDM get measurements response post processing function. * * @param attestation Attestation requester instance to utilize. * @param eid EID of device to utilize. * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_spdm_get_measurements_rsp_post_processing ( const struct attestation_requester *attestation, uint8_t device_eid) { struct spdm_get_measurements_response *rsp = (struct spdm_get_measurements_response*) attestation->state->txn.msg_buffer; // If Get Measurement request was not for all blocks, then only one block should be in response if ((attestation->state->txn.measurement_operation_requested != SPDM_MEASUREMENT_OPERATION_GET_ALL_BLOCKS) && !attestation->state->txn.device_discovery && (rsp->number_of_blocks != 1)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_NUM_MEASUREMENT_BLOCKS, device_eid, (1 << 8) | rsp->number_of_blocks); return ATTESTATION_UNEXPECTED_NUM_MEAS_BLOCKS; } if (rsp->slot_id != attestation->state->txn.slot_num) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_SLOT_NUM_IN_RSP, device_eid, (attestation->state->txn.slot_num << 8) | rsp->slot_id); return ATTESTATION_UNEXPECTED_SLOT_NUM; } if (attestation->state->txn.measurement_operation_requested == SPDM_MEASUREMENT_OPERATION_GET_NUM_BLOCKS) { attestation->state->txn.msg_buffer[0] = rsp->num_measurement_indices; attestation->state->txn.msg_buffer_len = 1; } return 0; } /** * Function to send SPDM request and wait for a response. This function assumes a pregenerated * request is in attestation_requester's spdm_msg_buffer. If request is not part of device * discovery, the request is added to the transcript hash. * * The MCTP header will be added to the message before sending the request. * * @param attestation Attestation requester instance to utilize. * @param request_len Length of request to send. * @param dest_addr SMBus address of destination device. * @param dest_eid MCTP EID of destination device. * @param crypto_timeout Flag indicating whether to use the crypto timeout with device. * @param command Requested command to send out. * * @return 0 if successful or error code otherwise */ static int attestation_requester_send_spdm_request_and_get_response ( const struct attestation_requester *attestation, size_t request_len, uint8_t dest_addr, uint8_t dest_eid, bool crypto_timeout, uint8_t command) { struct spdm_challenge_response *challenge_rsp = (struct spdm_challenge_response*) attestation->state->txn.msg_buffer; struct spdm_get_measurements_response *get_meas_rsp = (struct spdm_get_measurements_response*) attestation->state->txn.msg_buffer; size_t transcript_hash_len = hash_get_hash_length (attestation->state->txn.transcript_hash_type); size_t rsp_to_hash_len; int status; /* If performing device discovery, Get Measurements is not required to provide a signed response * thus transcript hashing is not necessary. */ if (!attestation->state->txn.device_discovery) { // Transcript hashing should not include the MCTP message header. status = attestation->secondary_hash->update (attestation->secondary_hash, attestation->state->spdm_msg_buffer, request_len); if (ROT_IS_ERROR (status)) { return status; } } spdm_populate_mctp_header (attestation->state->spdm_mctp); request_len += 1; status = attestation_requester_send_request_and_get_response (attestation, request_len, dest_addr, dest_eid, crypto_timeout, false, command); if (status != 0) { return status; } rsp_to_hash_len = attestation->state->txn.msg_buffer_len; switch (command) { case SPDM_REQUEST_GET_VERSION: status = attestation_requester_get_version_rsp_post_processing (attestation, dest_eid); if (status != 0) { device_manager_update_device_state_by_eid (attestation->device_mgr, dest_eid, DEVICE_MANAGER_ATTESTATION_INVALID_VERSION); } break; case SPDM_REQUEST_GET_CAPABILITIES: status = attestation_requester_get_capabilities_rsp_post_processing (attestation, dest_eid); if (status != 0) { device_manager_update_device_state_by_eid (attestation->device_mgr, dest_eid, DEVICE_MANAGER_ATTESTATION_INVALID_CAPS); } break; case SPDM_REQUEST_NEGOTIATE_ALGORITHMS: status = attestation_requester_negotiate_algorithms_rsp_post_processing (attestation, dest_eid); if (status != 0) { device_manager_update_device_state_by_eid (attestation->device_mgr, dest_eid, DEVICE_MANAGER_ATTESTATION_INVALID_ALGORITHM); } break; case SPDM_REQUEST_GET_DIGESTS: status = attestation_requester_get_digests_rsp_post_processing (attestation, dest_eid); if (status != 0) { device_manager_update_device_state_by_eid (attestation->device_mgr, dest_eid, DEVICE_MANAGER_ATTESTATION_INVALID_DIGESTS); } break; case SPDM_REQUEST_GET_CERTIFICATE: status = attestation_requester_get_certificate_rsp_post_processing (attestation, dest_eid); if (status != 0) { device_manager_update_device_state_by_eid (attestation->device_mgr, dest_eid, DEVICE_MANAGER_ATTESTATION_INVALID_CERTS); } break; case SPDM_REQUEST_CHALLENGE: status = attestation_requester_spdm_challenge_rsp_post_processing (attestation, dest_eid); if (status != 0) { device_manager_update_device_state_by_eid (attestation->device_mgr, dest_eid, DEVICE_MANAGER_ATTESTATION_INVALID_CHALLENGE); } rsp_to_hash_len = spdm_get_challenge_resp_length (challenge_rsp, transcript_hash_len, transcript_hash_len); break; case SPDM_REQUEST_GET_MEASUREMENTS: status = attestation_requester_spdm_get_measurements_rsp_post_processing (attestation, dest_eid); if (status != 0) { device_manager_update_device_state_by_eid (attestation->device_mgr, dest_eid, DEVICE_MANAGER_ATTESTATION_INVALID_MEASUREMENT); } rsp_to_hash_len = spdm_get_measurements_resp_length (get_meas_rsp); break; } if (status != 0) { return status; } if (attestation->state->txn.device_discovery) { return 0; } return attestation->secondary_hash->update (attestation->secondary_hash, attestation->state->txn.msg_buffer, rsp_to_hash_len); } #endif #ifdef ATTESTATION_SUPPORT_SPDM /** * Copy received response to internal buffer. * * @param observer The observer instance being notified. * @param response The response container received. * @param command Incoming command. */ static void attestation_requester_copy_spdm_response (const struct spdm_protocol_observer *observer, const struct cmd_interface_msg *response, uint8_t command) { const struct attestation_requester *attestation = TO_DERIVED_TYPE (observer, const struct attestation_requester, spdm_rsp_observer); if (attestation_requester_check_spdm_unexpected_rsp (attestation, command)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_RESPONSE_RECEIVED, response->source_eid, ((attestation->state->txn.protocol << 24) | (attestation->state->txn.requested_command << 16) | (ATTESTATION_PROTOCOL_DMTF_SPDM << 8) | command)); attestation->state->txn.request_status = ATTESTATION_REQUESTER_REQUEST_RSP_FAIL; return; } memcpy (attestation->state->txn.msg_buffer, response->payload, response->payload_length); attestation->state->txn.msg_buffer_len = response->payload_length; attestation->state->txn.request_status = ATTESTATION_REQUESTER_REQUEST_SUCCESSFUL; } /** * SPDM get version response observer function. The Get Version request/response interaction * determines the highest SPDM version both devices support, and then subsequent transactions with * the device uses the determined version. */ void attestation_requester_on_spdm_get_version_response ( const struct spdm_protocol_observer *observer, const struct cmd_interface_msg *response) { attestation_requester_copy_spdm_response (observer, response, SPDM_REQUEST_GET_VERSION); } /** * SPDM get capabilities response observer function. The Get Capabilities request/response * interaction allows both devices to know the level of support for non-required SPDM commands in * the other device, as well as timeout and message size capabilities. */ void attestation_requester_on_spdm_get_capabilities_response ( const struct spdm_protocol_observer *observer, const struct cmd_interface_msg *response) { attestation_requester_copy_spdm_response (observer, response, SPDM_REQUEST_GET_CAPABILITIES); } /** * SPDM negotiate algorithms response observer function. The Negotiate Algorithms request/response * interaction allows both devices to select common hashing and asymmetric cryptographic algorithms * to utilize in subsequent SPDM interactions. */ void attestation_requester_on_spdm_negotiate_algorithms_response ( const struct spdm_protocol_observer *observer, const struct cmd_interface_msg *response) { attestation_requester_copy_spdm_response (observer, response, SPDM_REQUEST_NEGOTIATE_ALGORITHMS); } /** * SPDM get digests response observer function. The Get Digests request/response interaction allows * the requester to retrieve certificate chain digests from the responder. The digests will then be * compared to the requesters certificate chain cache for the device being attested, and in case of * a mismatch, the requester will fetch new certificate chain from device. */ void attestation_requester_on_spdm_get_digests_response ( const struct spdm_protocol_observer *observer, const struct cmd_interface_msg *response) { attestation_requester_copy_spdm_response (observer, response, SPDM_REQUEST_GET_DIGESTS); } /** * SPDM get certificate response observer function. The Get Certificate request/response interaction * is used for the requester to retrieve certificate chain to be used for attestation from * responder. */ void attestation_requester_on_spdm_get_certificate_response ( const struct spdm_protocol_observer *observer, const struct cmd_interface_msg *response) { attestation_requester_copy_spdm_response (observer, response, SPDM_REQUEST_GET_CERTIFICATE); } /** * SPDM challenge response observer function. The Challenge request/response interaction allows the * requester to authenticate the responder through the challenge-response protocol by validating the * transcript signature and comparing the measurement summary hash to CFM contents. */ void attestation_requester_on_spdm_challenge_response ( const struct spdm_protocol_observer *observer, const struct cmd_interface_msg *response) { attestation_requester_copy_spdm_response (observer, response, SPDM_REQUEST_CHALLENGE); } /** * SPDM get measurements response observer function. The Get Measurements request/response * interaction allows the requester to retrieve measurement blocks from the responder, then * authenticate the responder through validating the transcript signature and comparing the * measurement blocks to CFM contents. */ void attestation_requester_on_spdm_get_measurements_response ( const struct spdm_protocol_observer *observer, const struct cmd_interface_msg *response) { attestation_requester_copy_spdm_response (observer, response, SPDM_REQUEST_GET_MEASUREMENTS); } /** * SPDM ResponseNotReady error observer function. If original request command code allows * ResponseNotReady, wait for RDT duration then issue RESPOND_IF_READY request. */ void attestation_requester_on_spdm_response_not_ready ( const struct spdm_protocol_observer *observer, const struct cmd_interface_msg *response) { const struct attestation_requester *attestation = TO_DERIVED_TYPE (observer, const struct attestation_requester, spdm_rsp_observer); struct spdm_error_response *rsp = (struct spdm_error_response*) response->payload; struct spdm_error_response_not_ready *rsp_not_ready = (struct spdm_error_response_not_ready*) spdm_get_spdm_error_rsp_optional_data (rsp); uint8_t rdt_exponent; if (attestation->state->txn.protocol != ATTESTATION_PROTOCOL_DMTF_SPDM) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_RESPONSE_RECEIVED, response->source_eid, ((attestation->state->txn.protocol << 24) | (attestation->state->txn.requested_command << 16) | (ATTESTATION_PROTOCOL_DMTF_SPDM << 8) | SPDM_RESPONSE_ERROR)); goto fail; } // DSP0274 SPDM spec indicates these commands cannot respond with ResponseNotReady if ((attestation->state->txn.requested_command == SPDM_REQUEST_GET_VERSION) || (attestation->state->txn.requested_command == SPDM_REQUEST_GET_CAPABILITIES) || (attestation->state->txn.requested_command == SPDM_REQUEST_NEGOTIATE_ALGORITHMS)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_ILLEGAL_RSP_NOT_READY, response->source_eid, attestation->state->txn.requested_command); goto fail; } if (rsp_not_ready->request_code != attestation->state->txn.requested_command) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_RQ_CODE_IN_RSP, response->source_eid, (attestation->state->txn.requested_command << 8) | rsp_not_ready->request_code); goto fail; } // TODO: Get maximum permitted sleep duration from PCD /* If the requested sleep duration is too large to store, then cap it at the maximum sleep * duration that can fit in sleep_duration_ms, which is roughly 25 days. If for some reason * responder requests a duration larger than that, then responder can respond to the * RESPOND_IF_READY request with another ResponseNotReady. */ if (rsp_not_ready->rdt_exponent > 41) { rdt_exponent = 41; } else { rdt_exponent = rsp_not_ready->rdt_exponent; } attestation->state->txn.sleep_duration_ms = 1 + ((1 << rdt_exponent) / 1000); attestation->state->txn.respond_if_ready_token = rsp_not_ready->token; attestation->state->txn.request_status = ATTESTATION_REQUESTER_REQUEST_SUCCESSFUL; return; fail: attestation->state->txn.request_status = ATTESTATION_REQUESTER_REQUEST_RSP_FAIL; } #endif #ifdef ATTESTATION_SUPPORT_CERBERUS_CHALLENGE /** * Cerberus Challenge get digest response observer function. The Get Digests request/response * interaction allows the requester to retrieve certificate chain digests from the responder. The * digests will then be compared to the requesters certificate chain cache for the device being * attested, and in case of a mismatch, the requester will fetch new certificate chain from device. */ void attestation_requester_on_cerberus_get_digest_response ( const struct cerberus_protocol_observer *observer, const struct cmd_interface_msg *response) { const struct attestation_requester *attestation = TO_DERIVED_TYPE (observer, const struct attestation_requester, cerberus_rsp_observer); struct cerberus_protocol_get_certificate_digest_response *rsp = (struct cerberus_protocol_get_certificate_digest_response*) response->payload; if (attestation_requester_check_cerberus_unexpected_rsp (attestation, CERBERUS_PROTOCOL_GET_DIGEST)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_RESPONSE_RECEIVED, response->source_eid, ((attestation->state->txn.protocol << 24) | (attestation->state->txn.requested_command << 16) | (ATTESTATION_PROTOCOL_CERBERUS << 8) | CERBERUS_PROTOCOL_GET_DIGEST)); attestation->state->txn.request_status = ATTESTATION_REQUESTER_REQUEST_RSP_FAIL; return; } memcpy (attestation->state->txn.msg_buffer, cerberus_protocol_certificate_digests (rsp), SHA256_HASH_LENGTH * rsp->num_digests); attestation->state->txn.msg_buffer_len = SHA256_HASH_LENGTH * rsp->num_digests; attestation->state->txn.request_status = ATTESTATION_REQUESTER_REQUEST_SUCCESSFUL; return; } /** * Cerberus Challenge get certificate response observer function. The Get Certificate * request/response interaction is used for the requester to retrieve certificate chain to be used * for attestation from responder. */ void attestation_requester_on_cerberus_get_certificate_response ( const struct cerberus_protocol_observer *observer, const struct cmd_interface_msg *response) { const struct attestation_requester *attestation = TO_DERIVED_TYPE (observer, const struct attestation_requester, cerberus_rsp_observer); struct cerberus_protocol_get_certificate_response *rsp = (struct cerberus_protocol_get_certificate_response*) response->payload; size_t cert_portion_len; if (attestation_requester_check_cerberus_unexpected_rsp (attestation, CERBERUS_PROTOCOL_GET_CERTIFICATE)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_RESPONSE_RECEIVED, response->source_eid, ((attestation->state->txn.protocol << 24) | (attestation->state->txn.requested_command << 16) | (ATTESTATION_PROTOCOL_CERBERUS << 8) | CERBERUS_PROTOCOL_GET_CERTIFICATE)); goto fail; } if (rsp->slot_num != attestation->state->txn.slot_num) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_SLOT_NUM_IN_RSP, response->source_eid, (attestation->state->txn.slot_num << 8) | rsp->slot_num); goto fail; } cert_portion_len = cerberus_protocol_get_certificate_response_cert_length (response->payload_length); /* TODO: Is there a better error that could be reported here? */ if (cert_portion_len > sizeof (attestation->state->txn.msg_buffer)) { goto fail; } memcpy (attestation->state->txn.msg_buffer, cerberus_protocol_certificate (rsp), cert_portion_len); attestation->state->txn.msg_buffer_len = cert_portion_len; attestation->state->txn.request_status = ATTESTATION_REQUESTER_REQUEST_SUCCESSFUL; return; fail: attestation->state->txn.request_status = ATTESTATION_REQUESTER_REQUEST_RSP_FAIL; } /** * Cerberus Challenge challenge response observer function. The Challenge request/response * interaction allows the requester to authenticate the responder by validating the response * signature and comparing the device PMR0 to CFM contents. */ void attestation_requester_on_cerberus_challenge_response ( const struct cerberus_protocol_observer *observer, const struct cmd_interface_msg *response) { const struct attestation_requester *attestation = TO_DERIVED_TYPE (observer, const struct attestation_requester, cerberus_rsp_observer); struct cerberus_protocol_challenge_response *rsp = (struct cerberus_protocol_challenge_response*) response->payload; if (attestation_requester_check_cerberus_unexpected_rsp (attestation, CERBERUS_PROTOCOL_ATTESTATION_CHALLENGE)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_RESPONSE_RECEIVED, response->source_eid, ((attestation->state->txn.protocol << 24) | (attestation->state->txn.requested_command << 16) | (ATTESTATION_PROTOCOL_CERBERUS << 8) | CERBERUS_PROTOCOL_ATTESTATION_CHALLENGE)); goto fail; } if (rsp->challenge.slot_num != attestation->state->txn.slot_num) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_SLOT_NUM_IN_RSP, response->source_eid, (attestation->state->txn.slot_num << 8) | rsp->challenge.slot_num); } else if (rsp->challenge.digests_size != SHA256_HASH_LENGTH) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_HASH_LEN_IN_RSP, response->source_eid, (rsp->challenge.digests_size << 8) | SHA256_HASH_LENGTH); } else if ((rsp->challenge.min_protocol_version > CERBERUS_PROTOCOL_PROTOCOL_VERSION) || (rsp->challenge.max_protocol_version < CERBERUS_PROTOCOL_PROTOCOL_VERSION)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_CERBERUS_PROTOCOL_VER_UNSUPPORTED, response->source_eid, (rsp->challenge.max_protocol_version << 16) | (rsp->challenge.min_protocol_version << 8) | CERBERUS_PROTOCOL_PROTOCOL_VERSION); } else { memcpy (attestation->state->txn.msg_buffer, response->payload, response->payload_length); attestation->state->txn.msg_buffer_len = response->payload_length; attestation->state->txn.request_status = ATTESTATION_REQUESTER_REQUEST_SUCCESSFUL; return; } fail: attestation->state->txn.request_status = ATTESTATION_REQUESTER_REQUEST_RSP_FAIL; } /** * Cerberus Challenge get capabilities response observer function. The Get Capabilities * request/response interaction allows both devices to determine device functionalities, including * timeout and message size capabilities. */ void attestation_requester_on_cerberus_device_capabilities_response ( const struct cerberus_protocol_observer *observer, const struct cmd_interface_msg *response) { const struct attestation_requester *attestation = TO_DERIVED_TYPE (observer, const struct attestation_requester, cerberus_rsp_observer); if (attestation_requester_check_cerberus_unexpected_rsp (attestation, CERBERUS_PROTOCOL_GET_DEVICE_CAPABILITIES)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_RESPONSE_RECEIVED, response->source_eid, ((attestation->state->txn.protocol << 24) | (attestation->state->txn.requested_command << 16) | (ATTESTATION_PROTOCOL_CERBERUS << 8) | CERBERUS_PROTOCOL_GET_DEVICE_CAPABILITIES)); attestation->state->txn.request_status = ATTESTATION_REQUESTER_REQUEST_RSP_FAIL; } else { attestation->state->txn.request_status = ATTESTATION_REQUESTER_REQUEST_SUCCESSFUL; } } #endif #ifdef ATTESTATION_SUPPORT_DEVICE_DISCOVERY /** * MCTP control protocol get message type response observer function. The Get Message Type * request/response interaction allows requester to determine MCTP message types supported by * responder. This function is used during device discovery to determine SPDM and Cerberus Challenge * support. */ void attestation_requester_on_mctp_get_message_type_response ( const struct mctp_control_protocol_observer *observer, const struct cmd_interface_msg *response) { const struct attestation_requester *attestation = TO_DERIVED_TYPE (observer, const struct attestation_requester, mctp_rsp_observer); if ((attestation->state->txn.requested_command != MCTP_CONTROL_PROTOCOL_GET_MESSAGE_TYPE)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_RESPONSE_RECEIVED, response->source_eid, ((attestation->state->txn.protocol << 24) | (attestation->state->txn.requested_command << 16) | (255 << 8) | MCTP_CONTROL_PROTOCOL_GET_MESSAGE_TYPE)); attestation->state->txn.request_status = ATTESTATION_REQUESTER_REQUEST_RSP_FAIL; } else { attestation->state->txn.request_status = ATTESTATION_REQUESTER_REQUEST_SUCCESSFUL; } // msg_buffer is sized to hold maximum response lengths memcpy (attestation->state->txn.msg_buffer, response->payload, response->payload_length); attestation->state->txn.msg_buffer_len = response->payload_length; } /** * MCTP control protocol set EID request observer function. Incoming set EID requests are monitored * since they are used by the MCTP bridge to alert device of routing table updates. */ void attestation_requester_on_mctp_set_eid_request ( const struct mctp_control_protocol_observer *observer) { const struct attestation_requester *attestation = TO_DERIVED_TYPE (observer, const struct attestation_requester, mctp_rsp_observer); attestation->state->get_routing_table = true; attestation->state->mctp_bridge_wait = false; platform_semaphore_post (&attestation->state->next_action); } /** * MCTP control protocol get routing table entries response observer function. The Get Routing Table * Entries request/response interaction is used by the requester to fetch routing table from MCTP * bridge. */ void attestation_requester_on_mctp_get_routing_table_entries_response ( const struct mctp_control_protocol_observer *observer, const struct cmd_interface_msg *response) { const struct attestation_requester *attestation = TO_DERIVED_TYPE (observer, const struct attestation_requester, mctp_rsp_observer); if ((attestation->state->txn.requested_command != MCTP_CONTROL_PROTOCOL_GET_ROUTING_TABLE_ENTRIES)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_RESPONSE_RECEIVED, response->source_eid, ((attestation->state->txn.protocol << 24) | (attestation->state->txn.requested_command << 16) | (255 << 8) | MCTP_CONTROL_PROTOCOL_GET_MESSAGE_TYPE)); attestation->state->txn.request_status = ATTESTATION_REQUESTER_REQUEST_RSP_FAIL; } else { attestation->state->txn.request_status = ATTESTATION_REQUESTER_REQUEST_SUCCESSFUL; } // msg_buffer is sized to hold maximum response lengths memcpy (attestation->state->txn.msg_buffer, response->payload, response->payload_length); attestation->state->txn.msg_buffer_len = response->payload_length; } /** * CFM activation request observer function. CFM activation requests are used to communicate to * device that component attestation states should be reset. */ void attestation_requester_on_cfm_activation_request (const struct cfm_observer *observer) { const struct attestation_requester *attestation = TO_DERIVED_TYPE (observer, const struct attestation_requester, cfm_observer); device_manager_reset_authenticated_devices (attestation->device_mgr); platform_semaphore_post (&attestation->state->next_action); } #endif /** * Initialize an attestation requester instance. * * @param attestation Attestation requester instance to initialize. * @param state Variable context for the attestation requester to utilize. * @param mctp MCTP interface instance to utilize. * @param channel Command channel instance to utilize. * @param primary_hash The hash engine to utilize for most attestation hashing operations. This * must be a dedicated hash engine that is not shared with any other component. * @param secondary_hash The hash engine to utilize for SPDM transcript hashes. This must be a * dedicated hash engine that is not not shared with any other component. * @param ecc The ECC engine to utilize. * @param rsa The RSA engine to utilize. Optional, can be set to NULL if not utilized. * @param x509 The x509 engine to utilize. * @param rng The RNG engine to utilize. * @param riot RIoT key manager. * @param device_mgr Device manager instance to utilize. * @param cfm_manager CFM manager to utilize. * * @return Initialization status, 0 if success or an error code. */ int attestation_requester_init (struct attestation_requester *attestation, struct attestation_requester_state *state, const struct mctp_interface *mctp, const struct cmd_channel *channel, const struct hash_engine *primary_hash, const struct hash_engine *secondary_hash, const struct ecc_engine *ecc, const struct rsa_engine *rsa, const struct x509_engine *x509, const struct rng_engine *rng, const struct riot_key_manager *riot, struct device_manager *device_mgr, const struct cfm_manager *cfm_manager) { if ((attestation == NULL) || (state == NULL) || (mctp == NULL) || (channel == NULL) || (primary_hash == NULL) || (ecc == NULL) || (x509 == NULL) || (rng == NULL) || (riot == NULL) || (device_mgr == NULL) || (cfm_manager == NULL)) { return ATTESTATION_INVALID_ARGUMENT; } memset (attestation, 0, sizeof (struct attestation_requester)); attestation->state = state; attestation->mctp = mctp; attestation->channel = channel; attestation->primary_hash = primary_hash; attestation->secondary_hash = secondary_hash; attestation->ecc = ecc; attestation->rsa = rsa; attestation->x509 = x509; attestation->rng = rng; attestation->riot = riot; attestation->device_mgr = device_mgr; attestation->cfm_manager = cfm_manager; #ifdef ATTESTATION_SUPPORT_SPDM attestation->spdm_rsp_observer.on_spdm_get_version_response = attestation_requester_on_spdm_get_version_response; attestation->spdm_rsp_observer.on_spdm_get_capabilities_response = attestation_requester_on_spdm_get_capabilities_response; attestation->spdm_rsp_observer.on_spdm_negotiate_algorithms_response = attestation_requester_on_spdm_negotiate_algorithms_response; attestation->spdm_rsp_observer.on_spdm_get_digests_response = attestation_requester_on_spdm_get_digests_response; attestation->spdm_rsp_observer.on_spdm_get_certificate_response = attestation_requester_on_spdm_get_certificate_response; attestation->spdm_rsp_observer.on_spdm_challenge_response = attestation_requester_on_spdm_challenge_response; attestation->spdm_rsp_observer.on_spdm_get_measurements_response = attestation_requester_on_spdm_get_measurements_response; attestation->spdm_rsp_observer.on_spdm_response_not_ready = attestation_requester_on_spdm_response_not_ready; #endif #ifdef ATTESTATION_SUPPORT_CERBERUS_CHALLENGE attestation->cerberus_rsp_observer.on_get_digest_response = attestation_requester_on_cerberus_get_digest_response; attestation->cerberus_rsp_observer.on_get_certificate_response = attestation_requester_on_cerberus_get_certificate_response; attestation->cerberus_rsp_observer.on_challenge_response = attestation_requester_on_cerberus_challenge_response; attestation->cerberus_rsp_observer.on_device_capabilities = attestation_requester_on_cerberus_device_capabilities_response; #endif #ifdef ATTESTATION_SUPPORT_DEVICE_DISCOVERY attestation->mctp_rsp_observer.on_get_message_type_response = attestation_requester_on_mctp_get_message_type_response; attestation->mctp_rsp_observer.on_set_eid_request = attestation_requester_on_mctp_set_eid_request; attestation->mctp_rsp_observer.on_get_routing_table_entries_response = attestation_requester_on_mctp_get_routing_table_entries_response; attestation->cfm_observer.on_cfm_activation_request = attestation_requester_on_cfm_activation_request; #endif return attestation_requester_init_state (attestation); } /** * Initialize only the variable state for an attestation responder instance. The rest of the * instance is assumed to have already been initialized. * * This would generally be used with a statically initialized instance. * * @param attestation Attestation requester instance that contains state to initialize. * * @return Initialization status, 0 if success or an error code. */ int attestation_requester_init_state (const struct attestation_requester *attestation) { if (attestation == NULL) { return ATTESTATION_INVALID_ARGUMENT; } memset (attestation->state, 0, sizeof (struct attestation_requester_state)); /* Set up buffer pointers for generating SPDM requests. */ attestation->state->spdm_mctp = (struct spdm_protocol_mctp_header*) attestation->state->txn.msg_buffer; attestation->state->spdm_msg_buffer = &attestation->state->txn.msg_buffer[sizeof (struct spdm_protocol_mctp_header)]; attestation->state->mctp_bridge_wait = true; return platform_semaphore_init (&attestation->state->next_action); } /** * Release an attestation requester instance. * * @param attestation Attestation requester instance to release. */ void attestation_requester_deinit (const struct attestation_requester *attestation) { if (attestation != NULL) { platform_semaphore_free (&attestation->state->next_action); } } #ifdef ATTESTATION_SUPPORT_CERBERUS_CHALLENGE /** * Retrieve and verify the certificate chain from the device using Cerberus challenge protocol. If * the certs are valid, store the alias key of the device. * * @param attestation Attestation requester instance to utilize. * @param device_addr Physical address of the device to query for certificates. * @param eid EID of the device to query for certificates. * @param active_cfm Active CFM to utilize. * @param component_id The component ID of the device. * * @return 0 if completed successfully, or an error code */ static int attestation_requester_verify_and_load_leaf_key_cerberus ( const struct attestation_requester *attestation, uint8_t device_addr, uint8_t eid, const struct cfm *active_cfm, uint32_t component_id) { struct x509_ca_certs certs_chain; struct x509_certificate cert; size_t transcript_hash_len = hash_get_hash_length (attestation->state->txn.transcript_hash_type); size_t cert_idx; int status; /* TODO: Cert chain digest calculation seems like it might be wrong for Cerberus now relative * to how GET_DIGESTS is handled. */ for (cert_idx = 0; cert_idx < attestation->state->txn.num_certs; ++cert_idx) { status = cerberus_protocol_generate_get_certificate_request (attestation->state->txn.slot_num, cert_idx, attestation->state->txn.msg_buffer, sizeof (attestation->state->txn.msg_buffer), 0, 0); if (ROT_IS_ERROR (status)) { attestation->primary_hash->cancel (attestation->primary_hash); return status; } status = attestation_requester_send_request_and_get_response (attestation, status, device_addr, eid, false, false, CERBERUS_PROTOCOL_GET_CERTIFICATE); if (status != 0) { attestation->primary_hash->cancel (attestation->primary_hash); return status; } if (cert_idx == 0) { /* Handle the Root CA. */ status = attestation_requester_verify_and_load_root_cert (attestation, eid, active_cfm, component_id, attestation->state->txn.msg_buffer, attestation->state->txn.msg_buffer_len, NULL, 0, &certs_chain); } else { /* Handle any other cert. */ status = attestation_requester_verify_and_load_certificate (attestation, eid, attestation->state->txn.msg_buffer, attestation->state->txn.msg_buffer_len, ((cert_idx + 1) == attestation->state->txn.num_certs), &cert, &certs_chain); } if (status != 0) { return status; } } return attestation_requester_update_alias_key (attestation, eid, transcript_hash_len, &cert); } /** * Perform an attestation cycle on a provided device using Cerberus Protocol. * * @param attestation Attestation requester instance to utilize. * @param eid EID of device to attest. * @param device_addr Slave address of device. * @param active_cfm Active CFM to utilize. * @param component_id The component ID of the device. * * @return Completion status, 0 if success or an error code otherwise. */ static int attestation_requester_attest_device_cerberus_protocol ( const struct attestation_requester *attestation, uint8_t eid, int device_addr, const struct cfm *active_cfm, uint32_t component_id) { struct cerberus_protocol_challenge *challenge_rq = (struct cerberus_protocol_challenge*) attestation->state->txn.msg_buffer; struct cerberus_protocol_challenge_response *challenge_rsp = (struct cerberus_protocol_challenge_response*) attestation->state->txn.msg_buffer; const struct device_manager_key *alias_key; uint8_t digest[SHA256_HASH_LENGTH]; int challenge_rq_len; int status; attestation->state->txn.protocol = ATTESTATION_PROTOCOL_CERBERUS; // TODO Get Cerberus Protocol version using the MCTP control Get VDM Support command status = cerberus_protocol_generate_get_device_capabilities_request (attestation->device_mgr, attestation->state->txn.msg_buffer, sizeof (attestation->state->txn.msg_buffer)); if (ROT_IS_ERROR (status)) { return status; } status = attestation_requester_send_request_and_get_response (attestation, status, device_addr, eid, false, false, CERBERUS_PROTOCOL_GET_DEVICE_CAPABILITIES); if (status != 0) { return status; } status = cerberus_protocol_generate_get_certificate_digest_request (attestation->state->txn.slot_num, ATTESTATION_ECDHE_KEY_EXCHANGE, attestation->state->txn.msg_buffer, sizeof (attestation->state->txn.msg_buffer)); if (ROT_IS_ERROR (status)) { return status; } status = attestation_requester_send_request_and_get_response (attestation, status, device_addr, eid, true, false, CERBERUS_PROTOCOL_GET_DIGEST); if (status != 0) { return status; } status = attestation->primary_hash->calculate_sha256 (attestation->primary_hash, attestation->state->txn.msg_buffer, attestation->state->txn.msg_buffer_len, digest, sizeof (digest)); if (status != 0) { return status; } status = attestation_requester_update_cert_chain_digest (attestation, eid, digest, SHA256_HASH_LENGTH); if (status != 0) { return status; } attestation->state->txn.num_certs = attestation->state->txn.msg_buffer_len / SHA256_HASH_LENGTH; alias_key = device_manager_get_alias_key (attestation->device_mgr, eid); // If certificate chain digest retrieved does not match cached certificate, refresh chain if (alias_key == NULL) { status = attestation_requester_verify_and_load_leaf_key_cerberus (attestation, device_addr, eid, active_cfm, component_id); if (status != 0) { goto clear_cert_chain; } } status = attestation->primary_hash->start_sha256 (attestation->primary_hash); if (status != 0) { return status; } challenge_rq_len = cerberus_protocol_generate_challenge_request (attestation->rng, eid, attestation->state->txn.slot_num, attestation->state->txn.msg_buffer, sizeof (attestation->state->txn.msg_buffer)); if (ROT_IS_ERROR (challenge_rq_len)) { status = challenge_rq_len; goto hash_cancel; } status = attestation->primary_hash->update (attestation->primary_hash, (uint8_t*) &challenge_rq->challenge, sizeof (struct attestation_challenge)); if (ROT_IS_ERROR (status)) { goto hash_cancel; } status = attestation_requester_send_request_and_get_response (attestation, challenge_rq_len, device_addr, eid, true, false, CERBERUS_PROTOCOL_ATTESTATION_CHALLENGE); if (status != 0) { goto hash_cancel; } status = attestation->primary_hash->update (attestation->primary_hash, (uint8_t*) &challenge_rsp->challenge, sizeof (struct attestation_response) + challenge_rsp->challenge.digests_size); if (status != 0) { goto hash_cancel; } status = attestation_requester_verify_signature (attestation, attestation->primary_hash, eid, cerberus_protocol_challenge_get_signature (challenge_rsp), cerberus_protocol_challenge_get_signature_len (challenge_rsp, attestation->state->txn.msg_buffer_len), NULL); if (status != 0) { goto hash_cancel; } memmove (attestation->state->txn.msg_buffer, cerberus_protocol_challenge_get_pmr (challenge_rsp), SHA256_HASH_LENGTH); attestation->state->txn.msg_buffer_len = SHA256_HASH_LENGTH; status = attestation_requester_verify_pmr (attestation, active_cfm, component_id, eid, 0); /* TODO Implement additional Cerberus Challenge Protocol attestation flows * 1) PMR(n) attestation using the Get PMR command * 2) PMR measurement attestation using the Get Log and Get PMR commands * 3) PMR measurement data attestation using the Get Attestation Data, Get Log, and Get PMR * commands * 4) Manifest IDs attestation using the Get Config IDs command */ hash_cancel: if (!attestation->state->txn.hash_finish) { attestation->primary_hash->cancel (attestation->primary_hash); } return status; clear_cert_chain: device_manager_clear_alias_key (attestation->device_mgr, eid); device_manager_clear_cert_chain_digest (attestation->device_mgr, eid); return status; } #endif #ifdef ATTESTATION_SUPPORT_SPDM /** * Get SPDM device version and capabilities, and perform algorithms negotiation. Since the VCA * commands are included in SPDM Challenges and Measurement (for SPDM v1.2+) signatures, this * command is run before every challenge and get measurement (SPDM v1.2+) transaction if signatures * are requested in response. * * @param attestation Attestation requester instance to utilize. * @param eid EID of device to utilize. * @param device_addr Slave address of device * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_setup_spdm_device (const struct attestation_requester *attestation, uint8_t eid, int device_addr) { uint32_t base_asym_algo = SPDM_TPM_ALG_ECDSA_ECC_NIST_P256; uint32_t base_hash_algo; int rq_len; int status; rq_len = spdm_generate_get_version_request (attestation->state->spdm_msg_buffer, ATTESTATION_REQUESTER_MAX_SPDM_REQUEST); if (ROT_IS_ERROR (rq_len)) { return rq_len; } status = attestation_requester_send_spdm_request_and_get_response (attestation, rq_len, device_addr, eid, false, SPDM_REQUEST_GET_VERSION); if (status != 0) { return status; } rq_len = spdm_generate_get_capabilities_request (attestation->state->spdm_msg_buffer, ATTESTATION_REQUESTER_MAX_SPDM_REQUEST, attestation->state->txn.spdm_minor_version); if (ROT_IS_ERROR (rq_len)) { return rq_len; } status = attestation_requester_send_spdm_request_and_get_response (attestation, rq_len, device_addr, eid, false, SPDM_REQUEST_GET_CAPABILITIES); if (status != 0) { return status; } /* If doing device discovery, show support for all hashing algorithms. Otherwise, only show * support for hashing algorithm that CFM selects for attestation with this device. */ if (attestation->state->txn.device_discovery) { base_hash_algo = SPDM_TPM_ALG_SHA_256; #ifdef HASH_ENABLE_SHA384 base_hash_algo |= SPDM_TPM_ALG_SHA_384; #endif #ifdef HASH_ENABLE_SHA512 base_hash_algo |= SPDM_TPM_ALG_SHA_512; #endif } else { switch (attestation->state->txn.transcript_hash_type) { case HASH_TYPE_SHA256: base_hash_algo = SPDM_TPM_ALG_SHA_256; break; #ifdef HASH_ENABLE_SHA384 case HASH_TYPE_SHA384: base_hash_algo = SPDM_TPM_ALG_SHA_384; break; #endif #ifdef HASH_ENABLE_SHA512 case HASH_TYPE_SHA512: base_hash_algo = SPDM_TPM_ALG_SHA_512; break; #endif default: return ATTESTATION_UNSUPPORTED_ALGORITHM; } if (attestation->state->txn.transcript_hash_type != attestation->state->txn.measurement_hash_type) { switch (attestation->state->txn.measurement_hash_type) { case HASH_TYPE_SHA256: base_hash_algo |= SPDM_TPM_ALG_SHA_256; break; #ifdef HASH_ENABLE_SHA384 case HASH_TYPE_SHA384: base_hash_algo |= SPDM_TPM_ALG_SHA_384; break; #endif #ifdef HASH_ENABLE_SHA512 case HASH_TYPE_SHA512: base_hash_algo |= SPDM_TPM_ALG_SHA_512; break; #endif default: return ATTESTATION_UNSUPPORTED_ALGORITHM; } } } #if ECC_MAX_KEY_LENGTH >= ECC_KEY_LENGTH_384 base_asym_algo |= SPDM_TPM_ALG_ECDSA_ECC_NIST_P384; #endif #if ECC_MAX_KEY_LENGTH >= ECC_KEY_LENGTH_521 base_asym_algo |= SPDM_TPM_ALG_ECDSA_ECC_NIST_P521; #endif rq_len = spdm_generate_negotiate_algorithms_request (attestation->state->spdm_msg_buffer, ATTESTATION_REQUESTER_MAX_SPDM_REQUEST, base_asym_algo, base_hash_algo, attestation->state->txn.spdm_minor_version); if (ROT_IS_ERROR (rq_len)) { return rq_len; } status = attestation_requester_send_spdm_request_and_get_response (attestation, rq_len, device_addr, eid, false, SPDM_REQUEST_NEGOTIATE_ALGORITHMS); if (status != 0) { return status; } return 0; } /** * SPDM get measurements response processing function. For Get Measurements responses outside of * device discovery, validating the transcript signature. * * @param attestation Attestation requester instance to utilize. * @param eid EID of device to utilize. * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_spdm_process_get_measurements_response ( const struct attestation_requester *attestation, uint8_t device_eid) { struct spdm_get_measurements_response *rsp = (struct spdm_get_measurements_response*) attestation->state->txn.msg_buffer; struct spdm_measurements_measurement_block *block; size_t offset = sizeof (struct spdm_get_measurements_response); size_t measurement_size; uint8_t number_of_blocks = rsp->number_of_blocks; uint8_t i_block; int status; if (attestation->state->txn.measurement_operation_requested == SPDM_MEASUREMENT_OPERATION_GET_NUM_BLOCKS) { return 0; } if (!attestation->state->txn.device_discovery) { status = attestation_requester_verify_signature (attestation, attestation->secondary_hash, device_eid, spdm_get_measurements_resp_signature (rsp), spdm_get_measurements_resp_signature_length (rsp, attestation->state->txn.msg_buffer_len), SPDM_GET_MEASUREMENTS_SIGNATURE_CONTEXT_STR); if (status != 0) { return status; } } attestation->state->txn.msg_buffer_len = 0; for (i_block = 0; i_block < number_of_blocks; ++i_block) { block = (struct spdm_measurements_measurement_block*) &attestation->state->txn.msg_buffer[offset]; offset += sizeof (struct spdm_measurements_measurement_block); // If a specific block was requested, make sure response only includes that block if ((attestation->state->txn.measurement_operation_requested != SPDM_MEASUREMENT_OPERATION_GET_ALL_BLOCKS) && (block->index != attestation->state->txn.measurement_operation_requested)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_NUM_MEASUREMENT_BLOCKS, device_eid, (1 << 16) | (attestation->state->txn.measurement_operation_requested << 8) | block->index); return ATTESTATION_GET_MEAS_OPERATION_UNEXPECTED; } // If block was requested in digest form and response is in raw form, fail if (!attestation->state->txn.raw_bitstream_requested && block->dmtf.raw_bit_stream) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_MEASUREMENT_BLOCK_RAW, device_eid, (attestation->state->txn.measurement_operation_requested << 8) | block->index); return ATTESTATION_GET_MEAS_RSP_NOT_DIGEST; } // If block was requested in raw form and response was in digest form, fail else if (attestation->state->txn.raw_bitstream_requested && !block->dmtf.raw_bit_stream) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_UNEXPECTED_MEASUREMENT_BLOCK_DIGEST, device_eid, block->index); return ATTESTATION_GET_MEAS_RSP_NOT_RAW; } else { if ((block->dmtf.measurement_value_size + attestation->state->txn.msg_buffer_len) > sizeof (attestation->state->txn.msg_buffer)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_MEASUREMENT_DATA_TOO_LARGE, (device_eid << 8) | block->index, block->dmtf.measurement_value_size + attestation->state->txn.msg_buffer_len); return ATTESTATION_GET_MEAS_BLOCKS_TOO_LARGE; } measurement_size = block->dmtf.measurement_value_size; memmove (&attestation->state->txn.msg_buffer[attestation->state->txn.msg_buffer_len], &attestation->state->txn.msg_buffer[offset], measurement_size); attestation->state->txn.msg_buffer_len += measurement_size; } offset += measurement_size; } return 0; } /** * Generate and send SPDM get measurements request, then wait for response. * * @param attestation Attestation requester instance to utilize. * @param eid EID of device being attested. * @param device_addr Slave address of device. * @param measurement_operation Measurement operation requested. * @param raw_bitstream_requested Flag indicating whether to request raw form of measurement blocks. * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_send_and_receive_spdm_get_measurements ( const struct attestation_requester *attestation, uint8_t eid, int device_addr, uint8_t measurement_operation, bool raw_bitstream_requested) { uint8_t nonce[SPDM_NONCE_LEN]; int rq_len; int status; if (!attestation->state->txn.device_discovery) { if (!attestation->state->txn.hash_finish) { attestation->secondary_hash->cancel (attestation->secondary_hash); } attestation->state->txn.hash_finish = true; status = hash_start_new_hash (attestation->secondary_hash, attestation->state->txn.transcript_hash_type); if (status != 0) { return status; } attestation->state->txn.hash_finish = false; } /* In 1.2+, every Get Measurement transaction with a signature requested in response requires a * a transcript that includes VDM. */ if (((attestation->state->txn.protocol != ATTESTATION_PROTOCOL_CERBERUS) && (attestation->state->txn.spdm_minor_version > ATTESTATION_PROTOCOL_DMTF_SPDM_1_1)) || attestation->state->txn.device_discovery) { status = attestation_requester_setup_spdm_device (attestation, eid, device_addr); if (status != 0) { return status; } } // No signature or nonce needed when getting device ID measurement block in device discovery if (!attestation->state->txn.device_discovery) { if (attestation->state->txn.cert_supported) { status = attestation->rng->generate_random_buffer (attestation->rng, SPDM_NONCE_LEN, nonce); if (status != 0) { return status; } } rq_len = spdm_generate_get_measurements_request (attestation->state->spdm_msg_buffer, ATTESTATION_REQUESTER_MAX_SPDM_REQUEST, attestation->state->txn.slot_num, measurement_operation, attestation->state->txn.cert_supported ? true : false, raw_bitstream_requested, attestation->state->txn.cert_supported ? nonce : NULL, attestation->state->txn.spdm_minor_version); if (ROT_IS_ERROR (rq_len)) { return rq_len; } } else { /* SPDM 1.1.x requires all measurement blocks to be contiguous. This means that device * might not be able to support the dedicated 0xEF block and be compliant, so instead * device IDs are placed in the last measurement block on the device. Cerberus will first * get the number of measurement blocks, then update measurement_operation to the index of * the last measurement block on the device. Starting from SPDM 1.2.x, measurement blocks * no longer have the contiguity requirement so instead use index 0xEF which is dedicated to * device IDs. */ if ((attestation->state->txn.protocol != ATTESTATION_PROTOCOL_CERBERUS) && (attestation->state->txn.spdm_minor_version <= ATTESTATION_PROTOCOL_DMTF_SPDM_1_1)) { rq_len = spdm_generate_get_measurements_request (attestation->state->spdm_msg_buffer, ATTESTATION_REQUESTER_MAX_SPDM_REQUEST, attestation->state->txn.slot_num, SPDM_MEASUREMENT_OPERATION_GET_NUM_BLOCKS, false, raw_bitstream_requested, NULL, attestation->state->txn.spdm_minor_version); if (ROT_IS_ERROR (rq_len)) { return rq_len; } attestation->state->txn.raw_bitstream_requested = raw_bitstream_requested; attestation->state->txn.measurement_operation_requested = SPDM_MEASUREMENT_OPERATION_GET_NUM_BLOCKS; status = attestation_requester_send_spdm_request_and_get_response (attestation, rq_len, device_addr, eid, true, SPDM_REQUEST_GET_MEASUREMENTS); if (status != 0) { return status; } status = attestation_requester_spdm_process_get_measurements_response (attestation, eid); if (status != 0) { device_manager_update_device_state_by_eid (attestation->device_mgr, eid, DEVICE_MANAGER_ATTESTATION_INVALID_MEASUREMENT); return status; } measurement_operation = attestation->state->txn.msg_buffer[0]; } rq_len = spdm_generate_get_measurements_request (attestation->state->spdm_msg_buffer, ATTESTATION_REQUESTER_MAX_SPDM_REQUEST, attestation->state->txn.slot_num, measurement_operation, false, raw_bitstream_requested, NULL, attestation->state->txn.spdm_minor_version); if (ROT_IS_ERROR (rq_len)) { return rq_len; } } attestation->state->txn.raw_bitstream_requested = raw_bitstream_requested; attestation->state->txn.measurement_operation_requested = measurement_operation; status = attestation_requester_send_spdm_request_and_get_response (attestation, rq_len, device_addr, eid, true, SPDM_REQUEST_GET_MEASUREMENTS); if (status != 0) { return status; } status = attestation_requester_spdm_process_get_measurements_response (attestation, eid); if (status != 0) { device_manager_update_device_state_by_eid (attestation->device_mgr, eid, DEVICE_MANAGER_ATTESTATION_INVALID_MEASUREMENT); } return status; } /** * If PMR0 digest checking is in CFM for device being attested using SPDM, get all measurement * blocks from device then combine into single digest and compare with allowable PMR0 digests. * * @param attestation Attestation requester instance to utilize. * @param eid EID of device being attested. * @param device_addr Slave address of device. * @param active_cfm Active CFM to utilize. * @param component_id The component ID of the device. * * @return Completion status, 0 if success, CFM_PMR_DIGEST_NOT_FOUND if no PMR0 digest checking * defined in CFM for this device, or an error code otherwise */ static int attestation_requester_get_and_verify_all_spdm_measurement_blocks ( const struct attestation_requester *attestation, uint8_t eid, int device_addr, const struct cfm *active_cfm, uint32_t component_id) { uint8_t digest[HASH_MAX_HASH_LEN]; struct cfm_pmr_digest pmr_digest; int status; status = active_cfm->get_component_pmr_digest (active_cfm, component_id, 0, &pmr_digest); if (status != 0) { return status; } status = attestation_requester_send_and_receive_spdm_get_measurements (attestation, eid, device_addr, SPDM_MEASUREMENT_OPERATION_GET_ALL_BLOCKS, false); if (status != 0) { goto free_pmr_digest; } // TODO: If device responds with raw blocks, hash them here instead of reporting error status = hash_calculate (attestation->primary_hash, attestation->state->txn.measurement_hash_type, attestation->state->txn.msg_buffer, attestation->state->txn.msg_buffer_len, digest, sizeof (digest)); if (ROT_IS_ERROR (status)) { goto free_pmr_digest; } status = attestation_requester_verify_digest_in_allowable_list (attestation, &pmr_digest.digests, digest, attestation->state->txn.measurement_hash_type); if (status != 0) { device_manager_update_device_state_by_eid (attestation->device_mgr, eid, DEVICE_MANAGER_ATTESTATION_MEASUREMENT_MISMATCH); } free_pmr_digest: active_cfm->free_component_pmr_digest (active_cfm, &pmr_digest); return status; } /** * Get corresponding the SPDM measurement block for a measurement entry from the CFM, then compare * to allowable values. * * @param attestation Attestation requester instance to utilize. * @param measurement CFM measurement entry. * @param eid EID of device being attested. * @param device_addr Slave address of device. * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_get_and_verify_spdm_measurement_block ( const struct attestation_requester *attestation, struct cfm_measurement_digest *measurement, uint8_t eid, int device_addr) { size_t i_allowable_digests; int status = 0; status = attestation_requester_send_and_receive_spdm_get_measurements (attestation, eid, device_addr, measurement->measurement_id, false); if (status != 0) { return status; } for (i_allowable_digests = 0; i_allowable_digests < measurement->allowable_digests_count; ++i_allowable_digests) { /* If device version set selected, and allowable digest has a non-zero version set which * does not match that of device, then digest not permitted for this device in its current * state. If there are no allowable digests with matching version sets to device, then * measurement comparison is not applicable to device in its current state and will be * skipped without failing attestation. */ if (attestation_requester_is_version_set_selected (attestation) && (measurement->allowable_digests[i_allowable_digests].version_set != 0) && (measurement->allowable_digests[i_allowable_digests].version_set != attestation->state->txn.device_version_set)) { continue; } /* If device version set not selected, then this should be a measurement used for version * set selection with only allowable digests unique to a single version set. */ if (!attestation_requester_is_version_set_selected (attestation) && (measurement->allowable_digests[i_allowable_digests].version_set == 0)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_CFM_VERSION_SET_SELECTOR_INVALID, ((eid << 16) | (measurement->pmr_id << 8) | (measurement->measurement_id)), i_allowable_digests); return ATTESTATION_CFM_VERSION_SET_SELECTOR_INVALID; } status = attestation_requester_verify_digest_in_allowable_list (attestation, &measurement->allowable_digests[i_allowable_digests].digests, NULL, attestation->state->txn.measurement_hash_type); if (status == 0) { // If device version set still not selected, then set it if (!attestation_requester_is_version_set_selected (attestation)) { attestation->state->txn.device_version_set = measurement->allowable_digests[i_allowable_digests].version_set; } break; } } // If device version set not selected, then report error if (!attestation_requester_is_version_set_selected (attestation)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_VERSION_SET_SELECTION_FAILED, ((eid << 16) | (measurement->pmr_id << 8) | (measurement->measurement_id)), status); if (status == 0) { return ATTESTATION_FAILED_TO_SELECT_VERSION_SET; } } return status; } /** * Perform check requested on data compared to expected data. * * @param check Checking method requested. * @param actual Actual data to utilize. * @param expected Expected data to utilize. * @param length Length of both actual and expected data. * @param bitmask Buffer with bitmask to use during comparison. Can be set to NULL if not needed. * @param big_endian Bool flag indicating if multi-byte data values are in big endian. * * @return 0 if data matches or 1 otherwise */ static int attestation_requester_compare_data (enum cfm_check check, const uint8_t *actual, const uint8_t *expected, size_t length, const uint8_t *bitmask, bool big_endian) { uint8_t actual_value; uint8_t expected_value; size_t i_comp; size_t i_data; int direction; int result; if (big_endian) { i_data = 0; direction = 1; } else { // If data is in little endian, reverse loop traversal. i_data = length - 1; direction = -1; } for (i_comp = 0; i_comp < length; ++i_comp, i_data += direction) { actual_value = actual[i_data]; expected_value = expected[i_data]; if (bitmask != NULL) { actual_value &= bitmask[i_data]; expected_value &= bitmask[i_data]; } result = actual_value - expected_value; if (result != 0) { break; } } if (result == 0) { if ((check == CFM_CHECK_EQUAL) || (check == CFM_CHECK_LESS_THAN_OR_EQUAL) || (check == CFM_CHECK_GREATER_THAN_OR_EQUAL)) { return 0; } } else if (result > 0) { if ((check == CFM_CHECK_GREATER_THAN) || (check == CFM_CHECK_GREATER_THAN_OR_EQUAL) || (check == CFM_CHECK_NOT_EQUAL)) { return 0; } } else { if ((check == CFM_CHECK_LESS_THAN) || (check == CFM_CHECK_LESS_THAN_OR_EQUAL) || (check == CFM_CHECK_NOT_EQUAL)) { return 0; } } return 1; } /** * Check if data in msg_buffer matches all checks in allowable data list from CFM entry. * * @param attestation Attestation requester instance to utilize. * @param check List of allowable data checks to utilize. * @param num_check Number of allowable data checks. * @param pmr_id PMR ID for CFM measurement data entry. * @param measurement_id Measurement ID for CFM measurement data entry. * @param eid EID of device being attested. * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_verify_data_in_allowable_list ( const struct attestation_requester *attestation, struct cfm_allowable_data *check, size_t num_check, uint8_t pmr_id, uint8_t measurement_id, uint8_t eid) { size_t i_checks_in_version_set = 0; size_t i_check; size_t i_data; int status = ATTESTATION_CFM_INVALID_ATTESTATION; for (i_check = 0; i_check < num_check; ++i_check, ++check) { for (i_data = 0; i_data < check->data_count; ++i_data) { /* If device version set selected, and allowable data entry has a non-zero version set * which does not match that of device, then data not permitted for this device in its * current state. */ if (attestation_requester_is_version_set_selected (attestation) && (check->allowable_data[i_data].version_set != 0) && (check->allowable_data[i_data].version_set != attestation->state->txn.device_version_set)) { continue; } /* If device version set not selected, then this should be a measurement data used for * version set selection with only allowable data entries unique to a single version * set. */ if (!attestation_requester_is_version_set_selected (attestation) && (check->allowable_data[i_data].version_set == 0)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_CFM_VERSION_SET_SELECTOR_INVALID, ((eid << 16) | (pmr_id << 8) | (measurement_id)), i_data); return ATTESTATION_CFM_VERSION_SET_SELECTOR_INVALID; } if ((check->allowable_data[i_data].data_len != attestation->state->txn.msg_buffer_len)) { return ATTESTATION_CFM_INVALID_ATTESTATION; } /* For a single version set, only a single allowable data entry is permitted unless a * "equal" or "not equal" check. */ if ((i_checks_in_version_set != 0) && (check->check != CFM_CHECK_EQUAL) && (check->check != CFM_CHECK_NOT_EQUAL)) { return ATTESTATION_CFM_INVALID_ATTESTATION; } ++i_checks_in_version_set; status = attestation_requester_compare_data (check->check, attestation->state->txn.msg_buffer, check->allowable_data[i_data].data, check->allowable_data[i_data].data_len, check->bitmask, check->big_endian); if (status == 0) { // If device version set still not selected, then set it if (!attestation_requester_is_version_set_selected (attestation)) { attestation->state->txn.device_version_set = check->allowable_data[i_data].version_set; } //For the equal check, at least one comparison must succeed if (check->check == CFM_CHECK_EQUAL) { break; } } else { status = ATTESTATION_CFM_ATTESTATION_RULE_FAIL; //Except for the equal check, all comparisons must succeed if (check->check != CFM_CHECK_EQUAL) { break; } } } } /* If there are no allowable data entries with matching version sets to device, then measurement * data comparison is not applicable to device in its current state and will be skipped without * failing attestation. */ if (i_checks_in_version_set == 0) { return 0; } return status; } /** * Get corresponding the SPDM measurement block for a measurement data entry from the CFM, then * compare to allowable values. * * @param attestation Attestation requester instance to utilize. * @param measurement CFM measurement data entry. * @param eid EID of device being attested. * @param device_addr Slave address of device. * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_get_and_verify_spdm_measurement_data_block ( const struct attestation_requester *attestation, struct cfm_measurement_data *data, uint8_t eid, int device_addr) { int status; status = attestation_requester_send_and_receive_spdm_get_measurements (attestation, eid, device_addr, data->measurement_id, true); if (status != 0) { return status; } status = attestation_requester_verify_data_in_allowable_list (attestation, data->data_checks, data->data_checks_count, data->pmr_id, data->measurement_id, eid); // If device version set not selected, then report error if (!attestation_requester_is_version_set_selected (attestation)) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_VERSION_SET_SELECTION_FAILED, ((eid << 16) | (data->pmr_id << 8) | (data->measurement_id)), status); if (status == 0) { return ATTESTATION_FAILED_TO_SELECT_VERSION_SET; } } return status; } /** * For each measurement or measurement data entry in CFM, get corresponding SPDM measurement block * and compare to allowable values. * * @param attestation Attestation requester instance to utilize. * @param eid EID of device being attested. * @param device_addr Slave address of device. * @param active_cfm Active CFM to utilize. * @param component_id The component ID of the device. * * @return Completion status, 0 if success or no measurement entries in CFM, or an error code * otherwise */ static int attestation_requester_get_and_verify_cfm_contents ( const struct attestation_requester *attestation, uint8_t eid, int device_addr, const struct cfm *active_cfm, uint32_t component_id) { struct cfm_measurement_container container; bool first = true; int status = 0; while (status == 0) { status = active_cfm->get_next_measurement_or_measurement_data (active_cfm, component_id, &container, first); if (status == 0) { if (container.measurement_type == CFM_MEASUREMENT_TYPE_DIGEST) { status = attestation_requester_get_and_verify_spdm_measurement_block (attestation, &container.measurement.digest, eid, device_addr); } else { status = attestation_requester_get_and_verify_spdm_measurement_data_block (attestation, &container.measurement.data, eid, device_addr); } first = false; } } active_cfm->free_measurement_container (active_cfm, &container); if (status == CFM_ENTRY_NOT_FOUND) { return 0; } if (status != 0) { device_manager_update_device_state_by_eid (attestation->device_mgr, eid, DEVICE_MANAGER_ATTESTATION_MEASUREMENT_MISMATCH); } return status; } /** * Process incoming SPDM challenge response. * * @param attestation Attestation requester instance to utilize. * @param eid EID of device to utilize. * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_spdm_process_challenge_response ( const struct attestation_requester *attestation, uint8_t device_eid) { struct spdm_challenge_response *rsp = (struct spdm_challenge_response*) attestation->state->txn.msg_buffer; size_t transcript_hash_len = hash_get_hash_length (attestation->state->txn.transcript_hash_type); int status; status = attestation_requester_verify_signature (attestation, attestation->secondary_hash, device_eid, spdm_get_challenge_resp_signature (rsp, transcript_hash_len, transcript_hash_len), spdm_get_challenge_resp_signature_length (rsp, transcript_hash_len, attestation->state->txn.msg_buffer_len, transcript_hash_len), SPDM_CHALLENGE_SIGNATURE_CONTEXT_STR); if (status != 0) { return status; } // msg_buffer is sized to hold maximum response lengths memmove (attestation->state->txn.msg_buffer, spdm_get_challenge_resp_measurement_summary_hash (rsp, transcript_hash_len), transcript_hash_len); attestation->state->txn.msg_buffer_len = transcript_hash_len; return 0; } /** * Retrieve a specified portion of the complete certificate chain data. * * @param attestation Attestation requester instance to utilize. * @param eid EID of device that sent certificate chain. * @param device_addr Slave address of device. * @param offset Offset within the certificate chain of the requested data. * @param length Total length of data to retrieve. * @param portion_data Output for the received certificate chain data. * * @return 0 if completed successfully, or an error code */ static int attestation_requester_retrieve_spdm_certificate_chain_portion ( const struct attestation_requester *attestation, uint8_t eid, int device_addr, uint32_t offset, size_t length, uint8_t **portion_data) { struct spdm_get_certificate_response *rsp = (struct spdm_get_certificate_response*) attestation->state->txn.msg_buffer; uint8_t *cert_buffer = NULL; size_t rx_data = 0; int rq_len; int status; /* Assume that no single certificate will be larger than the the total size of the message * buffer. */ if (length > sizeof (attestation->state->txn.msg_buffer)) { device_manager_update_device_state_by_eid (attestation->device_mgr, eid, DEVICE_MANAGER_ATTESTATION_INVALID_CERTS); return ATTESTATION_BUFFER_OVERRUN; } while (length > 0) { rq_len = spdm_generate_get_certificate_request (attestation->state->spdm_msg_buffer, ATTESTATION_REQUESTER_MAX_SPDM_REQUEST, attestation->state->txn.slot_num, offset + rx_data, length, attestation->state->txn.spdm_minor_version); if (ROT_IS_ERROR (rq_len)) { status = rq_len; goto exit; } status = attestation_requester_send_spdm_request_and_get_response (attestation, rq_len, device_addr, eid, true, SPDM_REQUEST_GET_CERTIFICATE); if (status != 0) { goto exit; } /* if portion_len is 0, or portion_len is greater than length, then the response is invalid */ if ((rsp->portion_len == 0) || (rsp->portion_len > length)) { device_manager_update_device_state_by_eid (attestation->device_mgr, eid, DEVICE_MANAGER_ATTESTATION_INVALID_CERTS); status = ATTESTATION_INVALID_CERT_CHAIN; goto exit; } if (cert_buffer == NULL) { /* If all the requested data did not get returned in a single request, allocate a memory * buffer to accumulate the data. */ if (rsp->portion_len < length) { cert_buffer = platform_malloc (length); if (cert_buffer == NULL) { return ATTESTATION_NO_MEMORY; } } } /* Handle the response data, copying as necessary. Extra data beyond what was requested * will be discarded. */ if (cert_buffer != NULL) { rx_data += buffer_copy (spdm_get_certificate_resp_cert_chain (rsp), rsp->portion_len, NULL, &length, &cert_buffer[rx_data]); } else { /* All the requested data is in the message buffer. */ *portion_data = spdm_get_certificate_resp_cert_chain (rsp); length = 0; } } if (cert_buffer != NULL) { /* It is guaranteed that the message buffer is large enough for the accumulated data based * on the length check made before requesting the data. */ memcpy (attestation->state->txn.msg_buffer, cert_buffer, rx_data); *portion_data = attestation->state->txn.msg_buffer; } exit: if (cert_buffer != NULL) { platform_free (cert_buffer); } return status; } /** * Retrieve a single certificate from an SPDM certificate chain. * * @param attestation Attestation requester instance to utilize. * @param dest_addr SMBus address of destination device. * @param dest_eid MCTP EID of destination device. * @param offset Offset within the certificate chain for the requested certificate. * @param cert_data Output for the retrieved certificate data. * @param cert_len Output for the total length of the certificate that it retrieved from response. * * @return 0 if completed successfully, or an error code */ static int attestation_requester_retrieve_individual_spdm_certificate ( const struct attestation_requester *attestation, uint8_t dest_addr, uint8_t dest_eid, uint32_t offset, uint8_t **cert_data, size_t *cert_len) { uint8_t *portion_data; size_t portion_size; uint8_t cert_header[ATTESTATION_REQUESTER_CERT_ASN1_HEADER_LEN]; int status; /* First determine the size of the next certificate at the specified offset by retrieving just * the ASN.1 header of the X.509 certificate. */ portion_size = ATTESTATION_REQUESTER_CERT_ASN1_HEADER_LEN; status = attestation_requester_retrieve_spdm_certificate_chain_portion (attestation, dest_eid, dest_addr, offset, portion_size, &portion_data); if (status != 0) { return status; } memcpy (cert_header, portion_data, portion_size); status = asn1_get_der_item_len (portion_data, portion_size); if (ROT_IS_ERROR (status)) { return status; } /* Assume that no single certificate will be larger than the the total size of the message * buffer. */ if ((size_t) (status + sizeof (struct spdm_get_certificate_response)) > sizeof (attestation->state->txn.msg_buffer)) { device_manager_update_device_state_by_eid (attestation->device_mgr, dest_eid, DEVICE_MANAGER_ATTESTATION_INVALID_CERTS); return ATTESTATION_CERT_TOO_LARGE; } /* Then issue a request to retrieve the rest of the certificate. As specified in SPDM spec, * offset must be monotonic after the first request for a particular certificate chain. */ if ((size_t) status < portion_size) { portion_size = 0; } else { offset += portion_size; portion_size = status - portion_size; status = attestation_requester_retrieve_spdm_certificate_chain_portion (attestation, dest_eid, dest_addr, offset, portion_size, &portion_data); if (status != 0) { return status; } memmove (portion_data + ATTESTATION_REQUESTER_CERT_ASN1_HEADER_LEN, portion_data, portion_size); memcpy (portion_data, cert_header, ATTESTATION_REQUESTER_CERT_ASN1_HEADER_LEN); } *cert_data = portion_data; *cert_len = portion_size + ATTESTATION_REQUESTER_CERT_ASN1_HEADER_LEN; return 0; } /** * Retrieve and verify the certificate chain from the device using SPDM protocol. If the certs are * valid, store the alias key of the device. * * @param attestation Attestation requester instance to utilize. * @param eid EID of device that sent certificate chain. * @param active_cfm Active CFM to utilize. * @param component_id The component ID of the device. * * @return 0 if completed successfully, or an error code */ static int attestation_requester_verify_and_load_leaf_key_spdm ( const struct attestation_requester *attestation, uint8_t dest_addr, uint8_t eid, const struct cfm *active_cfm, uint32_t component_id) { struct x509_ca_certs certs_chain; struct x509_certificate cert; uint8_t cert_chain_header[sizeof (struct spdm_certificate_chain) + HASH_MAX_HASH_LEN]; struct spdm_certificate_chain *spdm_cert_chain; size_t transcript_hash_len = hash_get_hash_length (attestation->state->txn.transcript_hash_type); size_t cert_offset = 0; uint8_t *cert_data; size_t cert_len; int status; /* Determine the length of the SPDM certificate header and request just the header data. */ cert_len = sizeof (struct spdm_certificate_chain) + transcript_hash_len; status = attestation_requester_retrieve_spdm_certificate_chain_portion (attestation, eid, dest_addr, cert_offset, cert_len, &cert_data); if (status != 0) { return status; } memcpy (cert_chain_header, cert_data, cert_len); spdm_cert_chain = (struct spdm_certificate_chain*) cert_chain_header; /* Get root CA certificate. */ cert_offset = cert_len; status = attestation_requester_retrieve_individual_spdm_certificate (attestation, dest_addr, eid, cert_offset, &cert_data, &cert_len); if (status != 0) { return status; } status = attestation_requester_verify_and_load_root_cert (attestation, eid, active_cfm, component_id, cert_data, cert_len, cert_chain_header, cert_offset, &certs_chain); if (status != 0) { return status; } cert_offset += cert_len; while (cert_offset < spdm_cert_chain->length) { /* Retrieve, hash, and validate each certificate in the chain. */ status = attestation_requester_retrieve_individual_spdm_certificate (attestation, dest_addr, eid, cert_offset, &cert_data, &cert_len); if (status != 0) { attestation->primary_hash->cancel (attestation->primary_hash); return status; } cert_offset += cert_len; status = attestation_requester_verify_and_load_certificate (attestation, eid, cert_data, cert_len, (cert_offset >= spdm_cert_chain->length), &cert, &certs_chain); if (status != 0) { return status; } } return attestation_requester_update_alias_key (attestation, eid, transcript_hash_len, &cert); } /** * Perform an attestation cycle on a provided device using SPDM. * * @param attestation Attestation requester instance to utilize. * @param eid EID of device to attest. * @param device_addr Slave address of device. * @param active_cfm Active CFM to utilize. * @param component_id The component ID of the device. * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_attest_device_spdm ( const struct attestation_requester *attestation, uint8_t eid, int device_addr, const struct cfm *active_cfm, uint32_t component_id) { const struct device_manager_key *alias_key; uint8_t nonce[SPDM_NONCE_LEN]; int rq_len; int status; if (!hash_is_alg_supported (attestation->state->txn.transcript_hash_type) || !hash_is_alg_supported (attestation->state->txn.measurement_hash_type)) { return ATTESTATION_UNSUPPORTED_ALGORITHM; } status = hash_start_new_hash (attestation->secondary_hash, attestation->state->txn.transcript_hash_type); if (status != 0) { return status; } // Start off assuming 1.0 then update based on response from device to the Get Version request attestation->state->txn.protocol = ATTESTATION_PROTOCOL_DMTF_SPDM; attestation->state->txn.spdm_minor_version = ATTESTATION_PROTOCOL_DMTF_SPDM_1_0; status = attestation_requester_setup_spdm_device (attestation, eid, device_addr); if (status != 0) { goto hash_cancel; } if (attestation->state->txn.cert_supported) { rq_len = spdm_generate_get_digests_request (attestation->state->spdm_msg_buffer, ATTESTATION_REQUESTER_MAX_SPDM_REQUEST, attestation->state->txn.spdm_minor_version); if (ROT_IS_ERROR (rq_len)) { status = rq_len; goto hash_cancel; } status = attestation_requester_send_spdm_request_and_get_response (attestation, rq_len, device_addr, eid, true, SPDM_REQUEST_GET_DIGESTS); if (status != 0) { goto hash_cancel; } // If certificate chain digest retrieved does not match cached certificate, refresh chain alias_key = device_manager_get_alias_key (attestation->device_mgr, eid); if (alias_key == NULL) { status = attestation_requester_verify_and_load_leaf_key_spdm (attestation, device_addr, eid, active_cfm, component_id); if (status != 0) { goto clear_cert_chain; } } } /* Perform PMR0 check. If device supports Challenge command, then use that. Otherwise, get all * measurement blocks which make up PMR0 using the Get Measurement command */ if (attestation->state->txn.challenge_supported) { status = attestation->rng->generate_random_buffer (attestation->rng, SPDM_NONCE_LEN, nonce); if (status != 0) { goto hash_cancel; } rq_len = spdm_generate_challenge_request (attestation->state->spdm_msg_buffer, ATTESTATION_REQUESTER_MAX_SPDM_REQUEST, attestation->state->txn.slot_num, SPDM_MEASUREMENT_SUMMARY_HASH_ALL, nonce, attestation->state->txn.spdm_minor_version); if (ROT_IS_ERROR (rq_len)) { status = rq_len; goto hash_cancel; } status = attestation_requester_send_spdm_request_and_get_response (attestation, rq_len, device_addr, eid, true, SPDM_REQUEST_CHALLENGE); if (status != 0) { goto hash_cancel; } status = attestation_requester_spdm_process_challenge_response (attestation, eid); if (status != 0) { goto hash_cancel; } status = attestation_requester_verify_pmr (attestation, active_cfm, component_id, eid, 0); if ((status != 0) && (status != CFM_PMR_DIGEST_NOT_FOUND)) { goto hash_cancel; } } else { status = attestation_requester_get_and_verify_all_spdm_measurement_blocks (attestation, eid, device_addr, active_cfm, component_id); if ((status != 0) && (status != CFM_PMR_DIGEST_NOT_FOUND)) { goto hash_cancel; } } /* If PMR0 entry exists in CFM, then by getting here device has valid PMR0. Since PMR0 includes * all measurement blocks, we dont have to check rest of the attestation rules. */ if (status == CFM_PMR_DIGEST_NOT_FOUND) { status = attestation_requester_get_and_verify_cfm_contents (attestation, eid, device_addr, active_cfm, component_id); if (status != 0) { goto hash_cancel; } } hash_cancel: if (!attestation->state->txn.hash_finish) { attestation->secondary_hash->cancel (attestation->secondary_hash); } return status; clear_cert_chain: device_manager_clear_alias_key (attestation->device_mgr, eid); device_manager_clear_cert_chain_digest (attestation->device_mgr, eid); if (!attestation->state->txn.hash_finish) { attestation->secondary_hash->cancel (attestation->secondary_hash); } return status; } #endif /** * Perform an attestation cycle on a provided device using requested protocol. * * @param attestation Attestation requester instance to utilize. * @param eid EID of device to attest. * * @return Completion status, 0 if success or an error code otherwise */ int attestation_requester_attest_device (const struct attestation_requester *attestation, uint8_t eid) { struct cfm_component_device component_device; const struct cfm *active_cfm; uint32_t component_id; enum cfm_attestation_type attestation_protocol; int device_addr; int device_state; int status; if (attestation == NULL) { return ATTESTATION_INVALID_ARGUMENT; } if (attestation->state->get_routing_table) { return ATTESTATION_REFRESH_ROUTING_TABLE; } memset (&attestation->state->txn, 0, sizeof (struct attestation_requester_transaction_state)); device_addr = device_manager_get_device_addr_by_eid (attestation->device_mgr, eid); if (ROT_IS_ERROR (device_addr)) { return device_addr; } status = device_manager_get_component_id (attestation->device_mgr, eid, &component_id); if (status != 0) { return status; } active_cfm = attestation->cfm_manager->get_active_cfm (attestation->cfm_manager); if (active_cfm == NULL) { status = device_manager_update_device_state_by_eid (attestation->device_mgr, eid, DEVICE_MANAGER_ATTESTATION_INVALID_CFM); if (status != 0) { return status; } return ATTESTATION_NO_CFM; } status = active_cfm->get_component_device (active_cfm, component_id, &component_device); if (status != 0) { device_manager_update_device_state_by_eid (attestation->device_mgr, eid, DEVICE_MANAGER_ATTESTATION_INVALID_CFM); goto free_cfm; } attestation->state->txn.slot_num = component_device.cert_slot; attestation->state->txn.transcript_hash_type = component_device.transcript_hash_type; attestation->state->txn.measurement_hash_type = component_device.measurement_hash_type; attestation_protocol = component_device.attestation_protocol; active_cfm->free_component_device (active_cfm, &component_device); /* update previous attestation state in device manager attestation event */ device_manager_update_attestation_summary_prev_state_by_eid (attestation->device_mgr, eid); device_state = device_manager_get_device_state_by_eid (attestation->device_mgr, eid); if (!(device_state == DEVICE_MANAGER_AUTHENTICATED) || (device_state == DEVICE_MANAGER_AUTHENTICATED_WITHOUT_CERTS)) { status = device_manager_update_device_state_by_eid (attestation->device_mgr, eid, DEVICE_MANAGER_READY_FOR_ATTESTATION); if (status != 0) { goto free_cfm; } } switch (attestation_protocol) { #ifdef ATTESTATION_SUPPORT_CERBERUS_CHALLENGE case CFM_ATTESTATION_CERBERUS_PROTOCOL: status = attestation_requester_attest_device_cerberus_protocol (attestation, eid, device_addr, active_cfm, component_id); break; #endif #ifdef ATTESTATION_SUPPORT_SPDM case CFM_ATTESTATION_DMTF_SPDM: if (attestation->secondary_hash == NULL) { status = ATTESTATION_UNSUPPORTED_OPERATION; } else { status = attestation_requester_attest_device_spdm (attestation, eid, device_addr, active_cfm, component_id); } break; #endif default: status = ATTESTATION_UNSUPPORTED_PROTOCOL; } free_cfm: attestation->cfm_manager->free_cfm (attestation->cfm_manager, active_cfm); if (status == 0) { if (attestation_protocol == CFM_ATTESTATION_DMTF_SPDM) { device_manager_update_device_state_by_eid (attestation->device_mgr, eid, attestation->state->txn.cert_supported ? DEVICE_MANAGER_AUTHENTICATED : DEVICE_MANAGER_AUTHENTICATED_WITHOUT_CERTS); } else { device_manager_update_device_state_by_eid (attestation->device_mgr, eid, DEVICE_MANAGER_AUTHENTICATED); } } else { device_state = device_manager_get_device_state_by_eid (attestation->device_mgr, eid); if ((device_state == DEVICE_MANAGER_NEVER_ATTESTED) || (device_state == DEVICE_MANAGER_READY_FOR_ATTESTATION)) { device_manager_update_device_state_by_eid (attestation->device_mgr, eid, DEVICE_MANAGER_ATTESTATION_FAILED); } } device_manager_update_attestation_summary_event_counters_by_eid (attestation->device_mgr, eid); return status; } #ifdef ATTESTATION_SUPPORT_DEVICE_DISCOVERY #ifdef ATTESTATION_SUPPORT_CERBERUS_CHALLENGE /** * Perform discovery on a provided device using the Cerberus protocol. * * @param attestation Attestation requester instance to utilize. * @param eid EID of device to discover. * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_discover_device_cerberus_protocol ( const struct attestation_requester *attestation, uint8_t eid) { UNUSED (attestation); UNUSED (eid); // TODO: Implement Cerberus protocol device discovery return 0; } #endif #ifdef ATTESTATION_SUPPORT_SPDM /** * Perform discovery on a provided device using the SPDM protocol. * * @param attestation Attestation requester instance to utilize. * @param eid EID of device to discover. * @param device_addr Slave address of device. * * @return Completion status, 0 if success or an error code otherwise */ static int attestation_requester_discover_device_spdm_protocol ( const struct attestation_requester *attestation, uint8_t eid, uint8_t device_addr) { struct spdm_discovery_device_id_block *block = (struct spdm_discovery_device_id_block*) attestation->state->txn.msg_buffer; struct spdm_discovery_device_id_descriptor *descriptor; uint16_t pci_vid = 0; uint16_t pci_device_id = 0; uint16_t pci_sub_vid = 0; uint16_t pci_sub_id = 0; uint16_t *id; size_t offset = sizeof (struct spdm_discovery_device_id_block); uint8_t found = 0; int i_descriptor; int device_num; int status; attestation->state->txn.protocol = ATTESTATION_PROTOCOL_DMTF_SPDM; attestation->state->txn.spdm_minor_version = ATTESTATION_PROTOCOL_DMTF_SPDM_1_0; attestation->state->txn.transcript_hash_type = HASH_TYPE_SHA256; attestation->state->txn.measurement_hash_type = HASH_TYPE_SHA256; status = attestation_requester_send_and_receive_spdm_get_measurements (attestation, eid, device_addr, SPDM_MEASUREMENT_OPERATION_GET_DEVICE_ID, true); if (status != 0) { return status; } if (block->completion_code != SPDM_DISCOVERY_DEVICE_ID_BLOCK_CC_SUCCESS) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_GET_DEVICE_ID_FAILED, ((eid << 8) | attestation->state->txn.spdm_minor_version), block->completion_code); return ATTESTATION_GET_DEVICE_ID_FAIL; } // Only PCI descriptors are supported, so ensure the 4 PCI descriptors are included in response if ((block->descriptor_count < 4) || (block->device_id_len < ((sizeof (struct spdm_discovery_device_id_descriptor) + sizeof (uint16_t)) * 4))) { return 0; } for (i_descriptor = 0; i_descriptor < block->descriptor_count; ++i_descriptor) { descriptor = (struct spdm_discovery_device_id_descriptor*) &attestation->state->txn.msg_buffer[offset]; offset += sizeof (struct spdm_discovery_device_id_descriptor); id = (uint16_t*) &attestation->state->txn.msg_buffer[offset]; offset += descriptor->descriptor_len; if (descriptor->descriptor_len != sizeof (uint16_t)) { continue; } switch (descriptor->descriptor_type) { case SPDM_DISCOVERY_DEVICE_ID_PCI_VID: pci_vid = *id; found |= 1; break; case SPDM_DISCOVERY_DEVICE_ID_PCI_DEVICE_ID: pci_device_id = *id; found |= (1 << 1); break; case SPDM_DISCOVERY_DEVICE_ID_PCI_SUBSYSTEM_VID: pci_sub_vid = *id; found |= (1 << 2); break; case SPDM_DISCOVERY_DEVICE_ID_PCI_SUBSYSTEM_ID: pci_sub_id = *id; found |= (1 << 3); break; default: continue; } } if (found == 0x0F) { device_num = device_manager_get_device_num_by_device_ids (attestation->device_mgr, pci_vid, pci_device_id, pci_sub_vid, pci_sub_id); if (ROT_IS_ERROR (device_num)) { return 0; } status = device_manager_update_device_state (attestation->device_mgr, device_num, DEVICE_MANAGER_NEVER_ATTESTED); if (status != 0) { return status; } return device_manager_update_device_eid (attestation->device_mgr, device_num, eid); } return 0; } #endif /** * Perform discovery on a provided device. * * @param attestation Attestation requester instance to utilize. * @param eid EID of device to discover. * * @return Completion status, 0 if success or an error code otherwise */ int attestation_requester_discover_device (const struct attestation_requester *attestation, uint8_t eid) { struct mctp_control_get_message_type_response *msg_type_rsp; uint8_t *msg_type; uint8_t i_type; int device_addr; int status; if (attestation == NULL) { return ATTESTATION_INVALID_ARGUMENT; } if (attestation->state->get_routing_table) { return ATTESTATION_REFRESH_ROUTING_TABLE; } memset (&attestation->state->txn, 0, sizeof (struct attestation_requester_transaction_state)); device_addr = device_manager_get_device_addr (attestation->device_mgr, DEVICE_MANAGER_MCTP_BRIDGE_DEVICE_NUM); if (ROT_IS_ERROR (device_addr)) { return device_addr; } attestation->state->txn.device_discovery = true; status = mctp_control_protocol_generate_get_message_type_support_request ( attestation->state->txn.msg_buffer, sizeof (attestation->state->txn.msg_buffer)); if (ROT_IS_ERROR (status)) { return status; } status = attestation_requester_send_request_and_get_response (attestation, status, device_addr, eid, false, true, MCTP_CONTROL_PROTOCOL_GET_MESSAGE_TYPE); if (status != 0) { goto done; } msg_type_rsp = (struct mctp_control_get_message_type_response*) attestation->state->txn.msg_buffer; for (i_type = 0; i_type < msg_type_rsp->message_type_count; ++i_type) { msg_type = attestation->state->txn.msg_buffer + sizeof (struct mctp_control_get_message_type_response) + i_type; switch (*msg_type) { #ifdef ATTESTATION_SUPPORT_CERBERUS_CHALLENGE case MCTP_BASE_PROTOCOL_MSG_TYPE_VENDOR_DEF: status = attestation_requester_discover_device_cerberus_protocol (attestation, eid); goto done; #endif #ifdef ATTESTATION_SUPPORT_SPDM case MCTP_BASE_PROTOCOL_MSG_TYPE_SPDM: status = attestation_requester_discover_device_spdm_protocol (attestation, eid, device_addr); goto done; #endif default: continue; } } done: if (status != 0) { device_manager_unidentified_device_timed_out (attestation->device_mgr, eid); return status; } return device_manager_remove_unidentified_device (attestation->device_mgr, eid); } /** * Check to see if routing table should be retrieved from the MCTP bridge, and fetch it if so. * * @param attestation Attestation requester instance to utilize. * * @return Completion status, 0 if success or an error code otherwise */ int attestation_requester_get_mctp_routing_table (const struct attestation_requester *attestation) { struct mctp_control_get_routing_table_entries_response *routing_table_rsp; struct mctp_control_routing_table_entry *entry; uint8_t entry_handle = 0; uint8_t i_entry; uint8_t i_eid; int bridge_addr; int bridge_eid; int status; if (attestation == NULL) { return ATTESTATION_INVALID_ARGUMENT; } if (!attestation->state->get_routing_table) { return 0; } device_manager_clear_unidentified_devices (attestation->device_mgr); bridge_addr = device_manager_get_device_addr (attestation->device_mgr, DEVICE_MANAGER_MCTP_BRIDGE_DEVICE_NUM); if (ROT_IS_ERROR (bridge_addr)) { return bridge_addr; } bridge_eid = device_manager_get_device_eid (attestation->device_mgr, DEVICE_MANAGER_MCTP_BRIDGE_DEVICE_NUM); if (ROT_IS_ERROR (bridge_eid)) { return bridge_eid; } while (entry_handle != 0xFF) { status = mctp_control_protocol_generate_get_routing_table_entries_request (entry_handle, attestation->state->txn.msg_buffer, sizeof (attestation->state->txn.msg_buffer)); if (ROT_IS_ERROR (status)) { return status; } status = attestation_requester_send_request_and_get_response (attestation, status, bridge_addr, bridge_eid, false, true, MCTP_CONTROL_PROTOCOL_GET_ROUTING_TABLE_ENTRIES); if (status != 0) { return status; } routing_table_rsp = (struct mctp_control_get_routing_table_entries_response*) attestation->state->txn. msg_buffer; entry = mctp_control_get_routing_table_entries_response_get_entries (routing_table_rsp); for (i_entry = 0; i_entry < routing_table_rsp->num_entries; ++i_entry, ++entry) { if (device_manager_is_device_unattestable (attestation->device_mgr, entry->starting_eid)) { continue; } for (i_eid = 0; i_eid < entry->eid_range_size; ++i_eid) { status = device_manager_add_unidentified_device (attestation->device_mgr, entry->starting_eid + i_eid); if (status != 0) { return status; } } } entry_handle = routing_table_rsp->next_entry_handle; } attestation->state->get_routing_table = false; attestation->state->mctp_bridge_wait = false; return 0; } #endif /** * Check to see if routing table should be retrieved from the MCTP bridge, and fetch it if so. * * @param attestation Attestation requester instance to utilize. * @param pcr PCR store instance to utilize. * @param measurement The measurement ID for attestation results. * @param measurement_version The version associated with the measurement data. */ void attestation_requester_discovery_and_attestation_loop ( const struct attestation_requester *attestation, struct pcr_store *pcr, uint16_t measurement, uint8_t measurement_version) { const uint8_t *attestation_status; int eid = 0; int status; if ((attestation == NULL) || (pcr == NULL)) { return; } #ifdef ATTESTATION_SUPPORT_DEVICE_DISCOVERY while (eid != DEVICE_MGR_NO_DEVICES_AVAILABLE) { eid = device_manager_get_eid_of_next_device_to_discover (attestation->device_mgr); if (!ROT_IS_ERROR (eid)) { status = attestation_requester_discover_device (attestation, eid); if (status != 0) { if (status == ATTESTATION_REFRESH_ROUTING_TABLE) { goto get_routing_table; } debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_DEVICE_FAILED_DISCOVERY, attestation->state->txn.requested_command, status); } } else if (eid != DEVICE_MGR_NO_DEVICES_AVAILABLE) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_NEXT_DEVICE_DISCOVERY_ERROR, eid, 0); } } #endif eid = 0; while (eid != DEVICE_MGR_NO_DEVICES_AVAILABLE) { eid = device_manager_get_eid_of_next_device_to_attest (attestation->device_mgr); if (!ROT_IS_ERROR (eid)) { status = attestation_requester_attest_device (attestation, eid); if (status != 0) { if (status == ATTESTATION_REFRESH_ROUTING_TABLE) { goto get_routing_table; } debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_DEVICE_FAILED_ATTESTATION, ((eid << 16) | (attestation->state->txn.protocol << 8) | attestation->state->txn.requested_command), status); } } else if (eid != DEVICE_MGR_NO_DEVICES_AVAILABLE) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_NEXT_DEVICE_ATTESTATION_ERROR, eid, 0); } } status = device_manager_get_attestation_status (attestation->device_mgr, &attestation_status); if (!ROT_IS_ERROR (status)) { status = pcr_store_update_versioned_buffer (pcr, attestation->primary_hash, measurement, attestation_status, status, true, measurement_version); if (status != 0) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_PCR_UPDATE_ERROR, measurement, status); } } else { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_GET_ATTESTATION_STATUS_ERROR, status, 0); } get_routing_table: #ifdef ATTESTATION_SUPPORT_DEVICE_DISCOVERY status = attestation_requester_get_mctp_routing_table (attestation); if (status != 0) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_GET_MCTP_ROUTING_TABLE_ERROR, status, 0); } #endif return; } /** * On an MCTP bridge reset event, this function should be called to indicate that the attestation * requester needs to refresh the routing table and rediscover any remote devices. * * @param attestation Attestation requester instance to utilize. * * @return Completion status, 0 if success or an error code otherwise */ int attestation_requestor_mctp_bridge_was_reset (const struct attestation_requester *attestation) { if (attestation == NULL) { return ATTESTATION_INVALID_ARGUMENT; } attestation->state->mctp_bridge_wait = true; return 0; } /** * Indicate that MCTP routing table should be queried from the MCTP bridge. * attestation_requestor_mctp_bridge_was_reset must be called prior to sending this request. Even * then, MCTP bridge will not be queried for its routing table if routing table has already been * refreshed due to processing of a set EID request from the MCTP bridge. * * @param attestation Attestation requester instance to utilize. */ void attestation_requester_refresh_routing_table (const struct attestation_requester *attestation) { if (attestation == NULL) { return; } if (attestation->state->mctp_bridge_wait) { attestation->state->get_routing_table = true; debug_log_create_entry (DEBUG_LOG_SEVERITY_INFO, DEBUG_LOG_COMPONENT_ATTESTATION, ATTESTATION_LOGGING_BRIDGE_RESET_TRIGGERED_ROUTING_TABLE_SYNC, 0, 0); platform_semaphore_post (&attestation->state->next_action); } } /** * This call will block until the attestation requester has a pending action to perform. * * @param attestation Attestation requester instance to utilize. */ void attestation_requestor_wait_for_next_action (const struct attestation_requester *attestation) { uint32_t duration_ms; if (attestation == NULL) { return; } duration_ms = device_manager_get_time_till_next_action (attestation->device_mgr); if (duration_ms != 0) { platform_semaphore_wait (&attestation->state->next_action, duration_ms); } }