core/attestation/pcr.c (788 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include <stdbool.h> #include <stdint.h> #include <string.h> #include "pcr.h" #include "platform_api.h" #include "common/common_math.h" #include "flash/flash.h" #include "flash/flash_util.h" /** * Initialize a PCR with support for a fixed number of measurements. Memory for managing the state * of each measurement will be dynamically allocated. * * @param pcr The PCR to initialize. * @param config The configuration for the PCR. If the PCR is configured to hold no measurements, * it will hold a single measurement that will be treated as an explict PCR value, meaning it will * be directly returned when computing the PCR without any additional operations performed. * * @return Completion status, 0 if success or an error code */ int pcr_init (struct pcr_bank *pcr, const struct pcr_config *config) { size_t num_measurements; int status; if ((pcr == NULL) || (config == NULL)) { return PCR_INVALID_ARGUMENT; } status = hash_get_hash_length (config->measurement_algo); if (status == HASH_ENGINE_UNKNOWN_HASH) { return PCR_UNSUPPORTED_ALGO; } if (!hash_is_alg_supported (config->measurement_algo) || (status > PCR_MAX_DIGEST_LENGTH)) { return PCR_UNSUPPORTED_ALGO; } /* Never support SHA-1 digests. */ if (config->measurement_algo == HASH_TYPE_SHA1) { return PCR_UNSUPPORTED_ALGO; } memset (pcr, 0, sizeof (struct pcr_bank)); status = platform_mutex_init (&pcr->lock); if (status != 0) { return status; } num_measurements = config->num_measurements; if (num_measurements == 0) { num_measurements = 1; pcr->explicit_measurement = true; } pcr->measurement_list = platform_calloc (num_measurements, sizeof (struct pcr_measurement)); if (pcr->measurement_list == NULL) { platform_mutex_free (&pcr->lock); return PCR_NO_MEMORY; } pcr->config.num_measurements = num_measurements; pcr->config.measurement_algo = config->measurement_algo; return 0; } /** * Release resources held by the PCR. * * @param pcr The PCR to release. */ void pcr_release (struct pcr_bank *pcr) { if (pcr != NULL) { platform_mutex_free (&pcr->lock); platform_free (pcr->measurement_list); } } /** * Retrieve number of measurements in the PCR. An explicit PCR value will report has having no * measurements. * * @param pcr The PCR to query. * * @return Number of measurements in PCR or an error code. */ int pcr_get_num_measurements (struct pcr_bank *pcr) { if (pcr == NULL) { return PCR_INVALID_ARGUMENT; } if (pcr->explicit_measurement) { return 0; } return pcr->config.num_measurements; } /** * Indicate if the measurement index is valid for the PCR. * * @param pcr The PCR to query. * @param measurement_index The measurement index to check. * * @return 0 if the measurement index is valid or an error code. */ int pcr_check_measurement_index (struct pcr_bank *pcr, uint8_t measurement_index) { if (pcr == NULL) { return PCR_INVALID_ARGUMENT; } if (measurement_index >= pcr->config.num_measurements) { return PCR_INVALID_INDEX; } return 0; } /** * Retrieve the hash algorithm used to generate measurements for the PCR. * * @param pcr The PCR to query. * * @return The hash algorithm used for the PCR. This will be HASH_TYPE_INVALID if the PCR is null. */ enum hash_type pcr_get_hash_algorithm (struct pcr_bank *pcr) { if (pcr == NULL) { return HASH_TYPE_INVALID; } return pcr->config.measurement_algo; } /** * Retrieve the digest length used for measurements for the PCR. * * @param pcr The PCR to query. * * @return Length of PCRs digests or an error code. */ int pcr_get_digest_length (struct pcr_bank *pcr) { if (pcr == NULL) { return PCR_INVALID_ARGUMENT; } return hash_get_hash_length (pcr->config.measurement_algo); } /** * Set the TCG event type for a measurement in the PCR. * * @param pcr PCR containing the measurement to update. * @param measurement_index The index of the measurement being updated. * @param event_type Event type to associate with measurement. * * @return Completion status, 0 if success or an error code. */ int pcr_set_tcg_event_type (struct pcr_bank *pcr, uint8_t measurement_index, uint32_t event_type) { if (pcr == NULL) { return PCR_INVALID_ARGUMENT; } if (measurement_index >= pcr->config.num_measurements) { return PCR_INVALID_INDEX; } platform_mutex_lock (&pcr->lock); pcr->measurement_list[measurement_index].event_type = event_type; platform_mutex_unlock (&pcr->lock); return 0; } /** * Get the TCG event type for a measurement in the PCR. * * @param pcr PCR containing the measurement to query. * @param measurement_index The index of the measurement being accessed. * @param event_type Output buffer to store the event type. * * @return Completion status, 0 if success or an error code. */ int pcr_get_tcg_event_type (struct pcr_bank *pcr, uint8_t measurement_index, uint32_t *event_type) { if ((pcr == NULL) || (event_type == NULL)) { return PCR_INVALID_ARGUMENT; } if (measurement_index >= pcr->config.num_measurements) { return PCR_INVALID_INDEX; } *event_type = pcr->measurement_list[measurement_index].event_type; return 0; } /** * Set the DMTF value type identifier for a measurement in the PCR. * * @param pcr PCR containing the measurement to update. * @param measurement_index The index of the measurement being updated. * @param value_type DMTF value type to associate with the measurement. * @param is_not_tcb Flag to indicate that a measurement should not be considered part of the TCB * when responding to SPDM requests. * * @return Completion status, 0 if success or an error code. */ int pcr_set_dmtf_value_type (struct pcr_bank *pcr, uint8_t measurement_index, enum pcr_dmtf_value_type value_type, bool is_not_tcb) { if (pcr == NULL) { return PCR_INVALID_ARGUMENT; } if (measurement_index >= pcr->config.num_measurements) { return PCR_INVALID_INDEX; } if (value_type >= PCR_DMTF_VALUE_TYPE_UNUSED) { return PCR_INVALID_VALUE_TYPE; } platform_mutex_lock (&pcr->lock); pcr->measurement_list[measurement_index].dmtf_type = value_type; pcr->measurement_list[measurement_index].spdm_not_tcb = is_not_tcb; platform_mutex_unlock (&pcr->lock); return 0; } /** * Get the DMTF value type identifier for a measurement in the PCR. * * @param pcr PCR containing the measurement to query. * @param measurement_index The index of the measurement being queried. * @param value_type Output buffer for the DMTF value type. * * @return Completion status, 0 if success or an error code. */ int pcr_get_dmtf_value_type (struct pcr_bank *pcr, uint8_t measurement_index, enum pcr_dmtf_value_type *value_type) { if ((pcr == NULL) || (value_type == NULL)) { return PCR_INVALID_ARGUMENT; } if (measurement_index >= pcr->config.num_measurements) { return PCR_INVALID_INDEX; } *value_type = pcr->measurement_list[measurement_index].dmtf_type; return 0; } /** * Determine if a measurement is part of the Trusted Computing Base (TCB) for the device. * * @param pcr PCR containing the measurement to query. * @param measurement_index The index of the measurement being queried. * * @return 1 if the measurement is part of the TCB, 0 if not, or an error code. */ int pcr_is_measurement_in_tcb (struct pcr_bank *pcr, uint8_t measurement_index) { if (pcr == NULL) { return PCR_INVALID_ARGUMENT; } if (measurement_index >= pcr->config.num_measurements) { return PCR_INVALID_INDEX; } return !pcr->measurement_list[measurement_index].spdm_not_tcb; } /** * Update the current digest for a single measurement. * * @param pcr PCR containing the measurement to update. * @param measurement_index The index of measurement being updated. * @param digest The digest that should be stored in the measurement. * @param digest_len Length of digest. This must match exactly the digest length for the PCR. * @param measurement_config Indicates what data included as part of the digest calculation. * @param version Optional version number that was include as part of the digest. * * @return 0 if successful or an error code. */ static int pcr_update_digest_common (struct pcr_bank *pcr, uint8_t measurement_index, const uint8_t *digest, size_t digest_len, uint8_t measurement_config, uint8_t version) { bool is_constant; int status = 0; if ((pcr == NULL) || (digest == NULL)) { return PCR_INVALID_ARGUMENT; } if (digest_len != (size_t) hash_get_hash_length (pcr->config.measurement_algo)) { return PCR_INCORRECT_DIGEST_LENGTH; } if (measurement_index >= pcr->config.num_measurements) { return PCR_INVALID_INDEX; } platform_mutex_lock (&pcr->lock); is_constant = !!(pcr->measurement_list[measurement_index].measurement_config & PCR_MEASUREMENT_FLAG_CONSTANT); if (!is_constant) { memcpy (pcr->measurement_list[measurement_index].digest, digest, digest_len); pcr->measurement_list[measurement_index].measurement_config = measurement_config; pcr->measurement_list[measurement_index].version = version; } else { status = PCR_CONSTANT_MEASUREMENT; } platform_mutex_unlock (&pcr->lock); return status; } /** * Store a pre-computed digest for a measurement in the PCR. * * @param pcr PCR containing the measurement to update. * @param measurement_index The index of measurement being updated. * @param digest The digest data that should be stored for the measurement. * @param digest_len Length of digest. This must match exactly the digest length for the PCR. * * @return 0 if successful or an error code. */ int pcr_update_digest (struct pcr_bank *pcr, uint8_t measurement_index, const uint8_t *digest, size_t digest_len) { return pcr_update_digest_common (pcr, measurement_index, digest, digest_len, 0, 0); } /** * Update a specified measurement in a PCR by computing the digest of a data buffer. Event type * and/or version details can be prepended to the data during the calculation. * * @param PCR containing the measurement to update. * @param hash Hashing engine to use for digest calculation. * @param measurement_index The index of the measurement being updated. * @param buf Buffer holding the data to measure. * @param buf_len Length of data buffer. * @param include_event Flag that indicates whether to include the event type in measurement * calculations. * @param include_version Flag that indicates whether to include the version in measurement * calculations. * @param is_constant Flag that indicates whether the measurement should prevent future updates. * @param version The version associated with the measurement data. * * @return Completion status, 0 if success or an error code */ static int pcr_update_buffer_common (struct pcr_bank *pcr, const struct hash_engine *hash, uint8_t measurement_index, const uint8_t *buf, size_t buf_len, bool include_event, bool include_version, bool is_constant, uint8_t version) { uint8_t digest[PCR_MAX_DIGEST_LENGTH]; uint8_t config = 0; int status; /* If there is an attempt to update buffer with 0 total bytes, using no data buffer and not * including event and version information, then fail due to invalid arguments */ if (!include_event && !include_version && ((buf == NULL) || (buf_len == 0))) { return PCR_INVALID_ARGUMENT; } status = hash_start_new_hash (hash, pcr->config.measurement_algo); if (status != 0) { return status; } if (include_event) { status = hash->update (hash, (uint8_t*) &pcr->measurement_list[measurement_index].event_type, sizeof (pcr->measurement_list[measurement_index].event_type)); if (status != 0) { goto hash_cancel; } config |= PCR_MEASUREMENT_FLAG_EVENT; } if (include_version) { status = hash->update (hash, &version, sizeof (version)); if (status != 0) { goto hash_cancel; } config |= PCR_MEASUREMENT_FLAG_VERSION; } else { version = 0; } if (is_constant) { config |= PCR_MEASUREMENT_FLAG_CONSTANT; } status = hash->update (hash, buf, buf_len); if (status != 0) { goto hash_cancel; } status = hash->finish (hash, digest, sizeof (digest)); if (status != 0) { goto hash_cancel; } return pcr_update_digest_common (pcr, measurement_index, digest, hash_get_hash_length (pcr->config.measurement_algo), config, version); hash_cancel: hash->cancel (hash); return status; } /** * Update a specified measurement in a PCR by computing the digest of a data buffer. * * @param pcr PCR containing the measurement to update. * @param hash Hashing engine to use for digest calculation. * @param measurement_index The index of the measurement being updated. * @param buf Buffer holding the data to measure. * @param buf_len Length of data buffer. * @param include_event Flag that indicates whether to include the event type in measurement * calculations. * * @return Completion status, 0 if success or an error code. */ int pcr_update_buffer (struct pcr_bank *pcr, const struct hash_engine *hash, uint8_t measurement_index, const uint8_t *buf, size_t buf_len, bool include_event) { if ((pcr == NULL) || (hash == NULL)) { return PCR_INVALID_ARGUMENT; } return pcr_update_buffer_common (pcr, hash, measurement_index, buf, buf_len, include_event, false, false, 0); } /** * Update a specified measurement in a PCR by computing the digest of a versioned data buffer. * * @param pcr PCR containing the measurement to update. * @param hash Hashing engine to use for digest calculation. * @param measurement_index The index of the measurement being updated. * @param buf Buffer holding the data to measure. * @param buf_len Length of data buffer. * @param include_event Flag that indicates whether to include the event type in measurement * calculations * @param version The version associated with the measurement data, which will prepended when * calculating the digest. * * @return Completion status, 0 if success or an error code. */ int pcr_update_versioned_buffer (struct pcr_bank *pcr, const struct hash_engine *hash, uint8_t measurement_index, const uint8_t *buf, size_t buf_len, bool include_event, uint8_t version) { if ((pcr == NULL) || (hash == NULL)) { return PCR_INVALID_ARGUMENT; } return pcr_update_buffer_common (pcr, hash, measurement_index, buf, buf_len, include_event, true, false, version); } /** * Store a pre-computed digest for a measurement in the PCR. The measurement will be locked from * any future modification. * * @param pcr PCR containing the measurement to update. * @param measurement_index The index of measurement being updated. * @param digest The digest data that should be stored for the measurement. * @param digest_len Length of digest. This must match exactly the digest length for the PCR. * * @return 0 if successful or an error code. */ int pcr_const_update_digest (struct pcr_bank *pcr, uint8_t measurement_index, const uint8_t *digest, size_t digest_len) { return pcr_update_digest_common (pcr, measurement_index, digest, digest_len, PCR_MEASUREMENT_FLAG_CONSTANT, 0); } /** * Update a specified measurement in a PCR by computing the digest of a data buffer. The * measurement will be locked from any future modification. * * @param pcr PCR containing the measurement to update. * @param hash Hashing engine to use for digest calculation. * @param measurement_index The index of the measurement being updated. * @param buf Buffer holding the data to measure. * @param buf_len Length of data buffer. * @param include_event Flag that indicates whether to include the event type in measurement * calculations. * * @return Completion status, 0 if success or an error code. */ int pcr_const_update_buffer (struct pcr_bank *pcr, const struct hash_engine *hash, uint8_t measurement_index, const uint8_t *buf, size_t buf_len, bool include_event) { if ((pcr == NULL) || (hash == NULL)) { return PCR_INVALID_ARGUMENT; } return pcr_update_buffer_common (pcr, hash, measurement_index, buf, buf_len, include_event, false, true, 0); } /** * Update a specified measurement in a PCR by computing the digest of a versioned data buffer. The * measurement will be locked from any future modification. * * @param pcr PCR containing the measurement to update. * @param hash Hashing engine to use for digest calculation. * @param measurement_index The index of the measurement being updated. * @param buf Buffer holding the data to measure. * @param buf_len Length of data buffer. * @param include_event Flag that indicates whether to include the event type in measurement * calculations * @param version The version associated with the measurement data, which will prepended when * calculating the digest. * * @return Completion status, 0 if success or an error code. */ int pcr_const_update_versioned_buffer (struct pcr_bank *pcr, const struct hash_engine *hash, uint8_t measurement_index, const uint8_t *buf, size_t buf_len, bool include_event, uint8_t version) { if ((pcr == NULL) || (hash == NULL)) { return PCR_INVALID_ARGUMENT; } return pcr_update_buffer_common (pcr, hash, measurement_index, buf, buf_len, include_event, true, true, version); } /** * Clear the currently stored digest for a measurement in the PCR. * * @param pcr The PCR containing the measurement to clear. * @param measurement_index The index of measurement being cleared. * * @return 0 if successful or an error code. */ int pcr_invalidate_measurement (struct pcr_bank *pcr, uint8_t measurement_index) { bool is_constant; int status = 0; if (pcr == NULL) { return PCR_INVALID_ARGUMENT; } if (measurement_index >= pcr->config.num_measurements) { return PCR_INVALID_INDEX; } platform_mutex_lock (&pcr->lock); is_constant = !!(pcr->measurement_list[measurement_index].measurement_config & PCR_MEASUREMENT_FLAG_CONSTANT); if (!is_constant) { memset (pcr->measurement_list[measurement_index].digest, 0, sizeof (pcr->measurement_list[measurement_index].digest)); } else { status = PCR_CONSTANT_MEASUREMENT; } platform_mutex_unlock (&pcr->lock); return status; } /** * Compute the PCR value based on the current state of the measurements. All measurements will be * included in the PCR calculation, even if they have not been updated with a value or if they have * been invalidated. * * @param pcr The PCR to calculate. * @param hash Hashing engine to use for the calculation. * @param lock true to acquire the PCR mutex during the calculation. If this is false, it is * expected that the lock is managed externally. * @param measurement Optional output buffer to return the PCR value. Setting this to null will * still refresh the measurement state. * @param length Size of the measurement output buffer. * * @return Length of the generated PCR value or an error code. Use ROT_IS_ERROR to check the return * status. */ int pcr_compute (struct pcr_bank *pcr, const struct hash_engine *hash, bool lock, uint8_t *measurement, size_t length) { uint8_t prev_measurement[PCR_MAX_DIGEST_LENGTH] = {0}; int hash_length; size_t i; int status = 0; if ((pcr == NULL) || (hash == NULL)) { return PCR_INVALID_ARGUMENT; } hash_length = hash_get_hash_length (pcr->config.measurement_algo); if ((measurement != NULL) && (length < (size_t) hash_length)) { return PCR_SMALL_OUTPUT_BUFFER; } if (lock) { platform_mutex_lock (&pcr->lock); } if (!pcr->explicit_measurement) { for (i = 0; i < pcr->config.num_measurements; ++i) { status = hash_start_new_hash (hash, pcr->config.measurement_algo); if (status != 0) { goto exit; } status = hash->update (hash, prev_measurement, hash_length); if (status != 0) { goto hash_cancel; } status = hash->update (hash, pcr->measurement_list[i].digest, hash_length); if (status != 0) { goto hash_cancel; } status = hash->finish (hash, prev_measurement, sizeof (prev_measurement)); if (status != 0) { goto hash_cancel; } memcpy (pcr->measurement_list[i].measurement, prev_measurement, sizeof (prev_measurement)); } if (measurement != NULL) { memcpy (measurement, prev_measurement, hash_length); } } else if (measurement != NULL) { memcpy (measurement, pcr->measurement_list[0].digest, hash_length); } status = hash_length; goto exit; hash_cancel: hash->cancel (hash); exit: if (lock) { platform_mutex_unlock (&pcr->lock); } return status; } /** * Retrieve details for a single measurement in a PCR. * * @param pcr The PCR containing the requested measurement. * @param measurement_index Index of measurement to get. * @param measurement Output buffer to return measurement. * * @return Length of the measurement digest or an error code. Use ROT_IS_ERROR to check the return * status. */ int pcr_get_measurement (struct pcr_bank *pcr, uint8_t measurement_index, struct pcr_measurement *measurement) { if ((pcr == NULL) || (measurement == NULL)) { return PCR_INVALID_ARGUMENT; } if (measurement_index >= pcr->config.num_measurements) { return PCR_INVALID_INDEX; } memcpy (measurement, &pcr->measurement_list[measurement_index], sizeof (struct pcr_measurement)); return hash_get_hash_length (pcr->config.measurement_algo); } /** * Retrieve a list of all measurements for the PCR. * * @param pcr The PCR containing the requested measurements. * @param measurement_list Output to for the list of PCR measurements. * * @return The digest length used for all measurements or an error code. Use ROT_IS_ERROR to check * the return status. */ int pcr_get_all_measurements (struct pcr_bank *pcr, const struct pcr_measurement **measurement_list) { if ((pcr == NULL) || (measurement_list == NULL)) { return PCR_INVALID_ARGUMENT; } *measurement_list = pcr->measurement_list; return hash_get_hash_length (pcr->config.measurement_algo); } /** * Indicate if a measurement has access to the raw data that was measured. * * @param pcr The PCR containing the measurement to query. * @param measurement_index Index of the measurement to query. * * @return 1 if the measurement has access to the raw data, 0 if not, or an error code. */ int pcr_is_measurement_data_available (struct pcr_bank *pcr, uint8_t measurement_index) { if (pcr == NULL) { return PCR_INVALID_ARGUMENT; } if (measurement_index >= pcr->config.num_measurements) { return PCR_INVALID_INDEX; } return (pcr->measurement_list[measurement_index].measured_data == NULL) ? 0 : 1; } /** * Set the raw data that is measured for a single measurement in the PCR. * * @param pcr The PCR containing the measurement to update. * @param measurement_index Index of measurement to set. * @param measurement_data Descriptor for the raw data associated with the measurement. * * @return Completion status, 0 if success or an error code. */ int pcr_set_measurement_data (struct pcr_bank *pcr, uint8_t measurement_index, const struct pcr_measured_data *measurement_data) { if (pcr == NULL) { return PCR_INVALID_ARGUMENT; } if (measurement_index >= pcr->config.num_measurements) { return PCR_INVALID_INDEX; } if (measurement_data != NULL) { switch (measurement_data->type) { case PCR_DATA_TYPE_1BYTE: case PCR_DATA_TYPE_2BYTE: case PCR_DATA_TYPE_4BYTE: case PCR_DATA_TYPE_8BYTE: case PCR_DATA_TYPE_MEMORY: break; case PCR_DATA_TYPE_FLASH: if (measurement_data->data.flash.flash == NULL) { return PCR_MEASURED_DATA_INVALID_FLASH_DEVICE; } break; case PCR_DATA_TYPE_CALLBACK: if (measurement_data->data.callback.get_data == NULL) { return PCR_MEASURED_DATA_INVALID_CALLBACK; } break; default: return PCR_INVALID_DATA_TYPE; } } platform_mutex_lock (&pcr->lock); pcr->measurement_list[measurement_index].measured_data = measurement_data; platform_mutex_unlock (&pcr->lock); return 0; } /** * Copy measurement data bytes into a destination buffer. Only bytes that fit into the destination * buffer will be copied. * * @param data Buffer storing the measurement data to be read. * @param data_len Size in bytes of the measurement data. * @param offset The offset index to start reading from. * @param buffer Output buffer yor the measured data. * @param buffer_len Maximum length of the buffer. * * @return Total number of bytes read. */ static int pcr_read_measurement_data_bytes (const uint8_t *data, size_t data_len, size_t offset, uint8_t *buffer, size_t buffer_len) { int bytes_read; if ((data == NULL) || (data_len == 0) || (buffer_len == 0) || (offset > (data_len - 1))) { return 0; } /* TODO: Can this be done with buffer_copy? */ bytes_read = ((data_len - offset) > buffer_len) ? buffer_len : (data_len - offset); memcpy (buffer, data + offset, bytes_read); return bytes_read; } /** * Internal function to retrieve the measured data for a single measurement. * * No data will be written and no error will be generated if the measurement does not have a * registered data descriptor. * * @param pcr The PCR containing the measurement to query. * @param measurement_index Index of the measurement to get. * @param offset The offset index to starting reading the data. * @param buffer Output buffer for the measured data. * @param length Maximum length of the buffer. * @param total_len Output containing the total length of the measurement data. This will contain * the total length of the measured data even if the data is only partially returned. * * @return Length of the buffer if the measured data was retrieved successfully or an error code. */ static int pcr_get_measurement_data_internal (struct pcr_bank *pcr, uint8_t measurement_index, size_t offset, uint8_t *buffer, size_t length, size_t *total_len) { const struct pcr_measured_data *measured_data; bool include_event; bool include_version; size_t total_bytes = 0; size_t bytes_read; uint32_t data_len; int status = 0; if (measurement_index >= pcr->config.num_measurements) { return PCR_INVALID_INDEX; } *total_len = 0; measured_data = pcr->measurement_list[measurement_index].measured_data; if (measured_data == NULL) { return 0; } include_event = pcr->measurement_list[measurement_index].measurement_config & PCR_MEASUREMENT_FLAG_EVENT; include_version = pcr->measurement_list[measurement_index].measurement_config & PCR_MEASUREMENT_FLAG_VERSION; if (include_event) { if (offset < 4) { bytes_read = pcr_read_measurement_data_bytes ( (uint8_t*) &pcr->measurement_list[measurement_index].event_type, 4, offset, buffer, length); offset = 0; length -= bytes_read; buffer = buffer + bytes_read; total_bytes += bytes_read; } else { offset -= 4; } *total_len += 4; } if (include_version) { if (offset < 1) { bytes_read = pcr_read_measurement_data_bytes (&pcr->measurement_list[measurement_index].version, 1, offset, buffer, length); offset = 0; length -= bytes_read; buffer = buffer + bytes_read; total_bytes += bytes_read; } else { offset -= 1; } *total_len += 1; } switch (measured_data->type) { case PCR_DATA_TYPE_1BYTE: bytes_read = pcr_read_measurement_data_bytes (&measured_data->data.value_1byte, 1, offset, buffer, length); status = bytes_read + total_bytes; *total_len += 1; break; case PCR_DATA_TYPE_2BYTE: bytes_read = pcr_read_measurement_data_bytes ((uint8_t*) &measured_data->data.value_2byte, 2, offset, buffer, length); status = bytes_read + total_bytes; *total_len += 2; break; case PCR_DATA_TYPE_4BYTE: bytes_read = pcr_read_measurement_data_bytes ((uint8_t*) &measured_data->data.value_4byte, 4, offset, buffer, length); status = bytes_read + total_bytes; *total_len += 4; break; case PCR_DATA_TYPE_8BYTE: bytes_read = pcr_read_measurement_data_bytes ((uint8_t*) &measured_data->data.value_8byte, 8, offset, buffer, length); status = bytes_read + total_bytes; *total_len += 8; break; case PCR_DATA_TYPE_MEMORY: bytes_read = pcr_read_measurement_data_bytes (measured_data->data.memory.buffer, measured_data->data.memory.length, offset, buffer, length); status = bytes_read + total_bytes; *total_len += measured_data->data.memory.length; break; case PCR_DATA_TYPE_FLASH: { const struct flash *flash_device = measured_data->data.flash.flash; size_t read_addr = measured_data->data.flash.addr + offset; if (offset > (measured_data->data.flash.length - 1)) { status = total_bytes; } else { bytes_read = (((measured_data->data.flash.length - offset) > length) ? length : (measured_data->data.flash.length - offset)); status = flash_device->read (flash_device, read_addr, buffer, bytes_read); if (status == 0) { status = bytes_read + total_bytes; } } *total_len += measured_data->data.flash.length; break; } case PCR_DATA_TYPE_CALLBACK: status = measured_data->data.callback.get_data (measured_data->data.callback.context, offset, buffer, length, &data_len); if (!ROT_IS_ERROR (status)) { status = status + total_bytes; } *total_len += data_len; break; default: status = PCR_INVALID_DATA_TYPE; break; } return status; } /** * Retrieve the raw data that was used to generate a measurement in the PCR. * * No data will be written and no error will be generated if the measurement has not been provided * with access to the raw data. * * @param pcr The PCR containing the measurement to query. * @param measurement_index Index of the measurement to get. * @param offset An offset into the measured data to start reading the data from. * @param buffer Output buffer for the measured data. * @param length Maximum length of the buffer. * @param total_len Output containing the total length of the measurement data. This will contain * the total length of the measured data even if the data is only partially returned. * * @return The amount of data that was written into the output buffer or an error code. Use * ROT_IS_ERROR to check the return value. */ int pcr_get_measurement_data (struct pcr_bank *pcr, uint8_t measurement_index, size_t offset, uint8_t *buffer, size_t length, size_t *total_len) { int status; if ((pcr == NULL) || (buffer == NULL) || (total_len == NULL)) { return PCR_INVALID_ARGUMENT; } platform_mutex_lock (&pcr->lock); status = pcr_get_measurement_data_internal (pcr, measurement_index, offset, buffer, length, total_len); platform_mutex_unlock (&pcr->lock); return status; } /** * Get the hash of the measurement data for a measurement in the PCR. * * If the requested hash algorithm matches the one used by the PCR, this will just return the * current measurement digest. If the requested hash algorithm is different, the digest will be * calculated from the raw data. * * @param pcr The PCR containing the measurement to hash. * @param measurement_index Index of the measurement to hash. * @param hash Hash engine to use for calculating the digest, if necessary. * @param hash_type The hash algorithm that should be used for digest calculation. * @param buffer Output buffer for the measurement hash. * @param length Size of the output buffer. * * @return Length of the hash that was generated for the measurement or an error code. Use * ROT_IS_ERROR to check the return value. */ int pcr_hash_measurement_data (struct pcr_bank *pcr, uint8_t measurement_index, const struct hash_engine *hash, enum hash_type hash_type, uint8_t *buffer, size_t length) { const struct pcr_measured_data *measured_data; int hash_length; bool include_event; bool include_version; int status = 0; if ((pcr == NULL) || (hash == NULL) || (buffer == NULL)) { return PCR_INVALID_ARGUMENT; } if (measurement_index >= pcr->config.num_measurements) { return PCR_INVALID_INDEX; } hash_length = hash_get_hash_length (hash_type); if (hash_length == HASH_ENGINE_UNKNOWN_HASH) { return hash_length; } if (length < (size_t) hash_length) { return PCR_SMALL_OUTPUT_BUFFER; } platform_mutex_lock (&pcr->lock); if (hash_type == pcr->config.measurement_algo) { /* No need to recalculate the hash since the requested algorithm matches the one used for * all measurements in the PCR. */ memcpy (buffer, pcr->measurement_list[measurement_index].digest, hash_length); } else { /* Calculate the digest from the raw measured data. */ measured_data = pcr->measurement_list[measurement_index].measured_data; if (measured_data == NULL) { /* There is no measured data for this measurement, so it's not possible to calculate the * hash */ status = PCR_MEASURED_DATA_NOT_AVIALABLE; goto exit; } include_event = pcr->measurement_list[measurement_index].measurement_config & PCR_MEASUREMENT_FLAG_EVENT; include_version = pcr->measurement_list[measurement_index].measurement_config & PCR_MEASUREMENT_FLAG_VERSION; status = hash_start_new_hash (hash, hash_type); if (status != 0) { goto exit; } if (include_event) { status = hash->update (hash, (uint8_t*) &pcr->measurement_list[measurement_index].event_type, 4); if (status != 0) { goto hash_done; } } if (include_version) { status = hash->update (hash, &pcr->measurement_list[measurement_index].version, 1); if (status != 0) { goto hash_done; } } switch (measured_data->type) { case PCR_DATA_TYPE_1BYTE: status = hash->update (hash, &measured_data->data.value_1byte, 1); break; case PCR_DATA_TYPE_2BYTE: status = hash->update (hash, (uint8_t*) &measured_data->data.value_2byte, 2); break; case PCR_DATA_TYPE_4BYTE: status = hash->update (hash, (uint8_t*) &measured_data->data.value_4byte, 4); break; case PCR_DATA_TYPE_8BYTE: status = hash->update (hash, (uint8_t*) &measured_data->data.value_8byte, 8); break; case PCR_DATA_TYPE_MEMORY: status = hash->update (hash, measured_data->data.memory.buffer, measured_data->data.memory.length); break; case PCR_DATA_TYPE_FLASH: status = flash_hash_update_contents (measured_data->data.flash.flash, measured_data->data.flash.addr, measured_data->data.flash.length, hash); break; case PCR_DATA_TYPE_CALLBACK: if (measured_data->data.callback.hash_data != NULL) { status = measured_data->data.callback.hash_data ( measured_data->data.callback.context, hash); } else { status = PCR_MEASURED_DATA_NO_HASH_CALLBACK; } break; default: status = PCR_INVALID_DATA_TYPE; break; } if (status != 0) { goto hash_done; } status = hash->finish (hash, buffer, length); } hash_done: if (status == 0) { status = hash_length; } else { hash->cancel (hash); } exit: platform_mutex_unlock (&pcr->lock); return status; } /** * Generate TCG formatted log entries for all measurements in the PCR. * * @param pcr The PCR to query. * @param pcr_num Number assigned to this PCR. * @param offset Offset within the PCR log to start reading data. * @param buffer Output buffer to populate with the requested log entries. * @param length Maximum number of bytes to read from the log. * @param total_len Total length of all log entries for the PCR. This is only valid if the call is * successful and 0 bytes are read from the log. This would happen if the offset was large enough * to skip over all the log data. * * @return The number of bytes read from the log or an error code. */ int pcr_get_tcg_log (struct pcr_bank *pcr, uint32_t pcr_num, size_t offset, uint8_t *buffer, size_t length, size_t *total_len) { union { struct pcr_tcg_event2_header header; struct pcr_tcg_event2_sha256 sha256; struct pcr_tcg_event2_sha384 sha384; struct pcr_tcg_event2_sha512 sha512; } entry; size_t num_bytes = 0; size_t i = 0; uint8_t *entry_digest; uint32_t *entry_event_size; size_t entry_digest_len; size_t entry_total_len; uint8_t *entry_ptr = NULL; size_t entry_len = 0; size_t entry_offset = 0; size_t event_size; int status = 0; if ((pcr == NULL) || (buffer == NULL) || (total_len == NULL)) { return PCR_INVALID_ARGUMENT; } *total_len = 0; if (pcr->explicit_measurement) { return 0; } entry.header.pcr_index = pcr_num; entry.header.digest_count = 1; switch (pcr->config.measurement_algo) { default: /* This isn't possible, since invalid hash types would be caught during init. * Fall-through to SHA-256. This is here mostly to keep compilers happy. */ case HASH_TYPE_SHA256: entry.header.digest_algorithm_id = PCR_TCG_SHA256_ALG_ID; entry_digest = entry.sha256.digest; entry_event_size = &entry.sha256.event_size; entry_digest_len = SHA256_HASH_LENGTH; entry_total_len = sizeof (entry.sha256); break; #if PCR_MAX_DIGEST_LENGTH >= SHA384_HASH_LENGTH case HASH_TYPE_SHA384: entry.header.digest_algorithm_id = PCR_TCG_SHA384_ALG_ID; entry_digest = entry.sha384.digest; entry_event_size = &entry.sha384.event_size; entry_digest_len = SHA384_HASH_LENGTH; entry_total_len = sizeof (entry.sha384); break; #endif #if PCR_MAX_DIGEST_LENGTH >= SHA512_HASH_LENGTH case HASH_TYPE_SHA512: entry.header.digest_algorithm_id = PCR_TCG_SHA512_ALG_ID; entry_digest = entry.sha512.digest; entry_event_size = &entry.sha512.event_size; entry_digest_len = SHA512_HASH_LENGTH; entry_total_len = sizeof (entry.sha512); break; #endif } platform_mutex_lock (&pcr->lock); while ((i < pcr->config.num_measurements) && (length > 0)) { entry.header.event_type = pcr->measurement_list[i].event_type; memcpy (entry_digest, pcr->measurement_list[i].digest, entry_digest_len); *total_len += entry_total_len; if (offset >= entry_total_len) { offset -= entry_total_len; } else if (length > 0) { entry_len = min (entry_total_len - offset, length); entry_offset = offset; entry_ptr = buffer; /* Do not write the entry yet because we don't know the entry size, but update the state * as if it was written to ensure everything ends up in the right place. */ num_bytes += entry_len; buffer += entry_len; length -= entry_len; offset = 0; } /* The entry event size is not word-aligned, so use a temp location that is aligned. */ status = pcr_get_measurement_data_internal (pcr, i, offset, buffer, length, &event_size); if (ROT_IS_ERROR (status)) { goto exit; } *entry_event_size = event_size; if (entry_ptr != NULL) { memcpy (entry_ptr, ((uint8_t*) &entry) + entry_offset, entry_len); entry_ptr = NULL; } *total_len += event_size; buffer += status; length -= status; if (status > 0) { offset = 0; num_bytes += status; } else { offset -= event_size; } i++; } exit: platform_mutex_unlock (&pcr->lock); if (ROT_IS_ERROR (status)) { return status; } else { return num_bytes; } } /** * Acquire the lock for accessing the PCR measurements. * * @param pcr The PCR to lock. * * @return Completion status, 0 if success or an error code. */ int pcr_lock (struct pcr_bank *pcr) { if (pcr) { return platform_mutex_lock (&pcr->lock); } else { return PCR_INVALID_ARGUMENT; } } /** * Release the lock for accessing the PCR measurements. * * @param pcr The PCR to unlock. * * @return Completion status, 0 if success or an error code. */ int pcr_unlock (struct pcr_bank *pcr) { if (pcr) { return platform_mutex_unlock (&pcr->lock); } else { return PCR_INVALID_ARGUMENT; } }