| /* |
| * Copyright (c) 2022-2024, Arm Limited. All rights reserved. |
| * Copyright (c) 2018-2023, Laurence Lundblade. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| * |
| */ |
| |
| #include <stdbool.h> |
| #include <stdint.h> |
| |
| #include "delegated_attest.h" |
| #include "measured_boot_api.h" |
| #include "psa/crypto.h" |
| #include "psa/initial_attestation.h" |
| #include "qcbor/qcbor.h" |
| #include "q_useful_buf.h" |
| #include "region_defs.h" |
| #include "t_cose_key.h" |
| #include "t_cose_standard_constants.h" |
| #include "tfm_attest_iat_defs.h" |
| #include "tfm_crypto_defs.h" |
| |
| |
| /** |
| * The size of X and Y coordinate in 2 parameter style EC public |
| * key. Format is as defined in [COSE (RFC 8152)] |
| * (https://tools.ietf.org/html/rfc8152) and [SEC 1: Elliptic Curve |
| * Cryptography](http://www.secg.org/sec1-v2.pdf). |
| * |
| * This size is well-known and documented in public standards. |
| */ |
| #define ECC_COORD_SIZE PSA_BITS_TO_BYTES(521) |
| |
| #define MAX_ENCODED_COSE_KEY_SIZE \ |
| 1 + /* 1 byte to encode map */ \ |
| 2 + /* 2 bytes to encode key type */ \ |
| 2 + /* 2 bytes to encode curve */ \ |
| 2 * /* the X and Y coordinates + encoding */ \ |
| (ECC_COORD_SIZE + 1 + 2) |
| |
| /* Delegated attestation key (DAK) identifier */ |
| static psa_key_id_t dak_key_id = PSA_KEY_ID_NULL; |
| /* Keeps track of whether DAK has been requested */ |
| static bool dak_requested = false; |
| /* Buffer to store the encoded platform boot state */ |
| static uint8_t boot_state_buffer[TFM_ATTEST_BOOT_RECORDS_MAX_SIZE]; |
| /* Hash algorithm that is used by the owner of the DAK for calculating the |
| * digest of the public part of the key (it serves as an input when creating |
| * the platform attestation token). |
| */ |
| static psa_algorithm_t dak_pub_hash_algo = PSA_ALG_NONE; |
| |
| /** |
| * \brief Static function to verify the hash of the public DAK |
| * |
| * \param[in] dak_pub_hash Pointer to buffer where the hash of the |
| * public DAK is stored. |
| * \param[in] dak_pub_hash_len Length of the hash value, in bytes. |
| * |
| * \return Returns error code as specified in \ref psa_status_t |
| */ |
| static psa_status_t |
| verify_dak_pub_hash(const uint8_t *dak_pub_hash, size_t dak_pub_hash_len) |
| { |
| psa_status_t status; |
| struct t_cose_key dak_key; |
| struct q_useful_buf_c cose_key; |
| enum t_cose_err_t cose_res; |
| /* Buffer large enough for P-521 public key */ |
| Q_USEFUL_BUF_MAKE_STACK_UB(cose_key_buf, MAX_ENCODED_COSE_KEY_SIZE); |
| |
| /* Verify the size of the hash */ |
| if (dak_pub_hash_len != PSA_HASH_LENGTH(dak_pub_hash_algo)) { |
| return PSA_ERROR_INVALID_ARGUMENT; |
| } |
| |
| /* Turns SEC1 encoding to a CBOR serialized COSE_Key object */ |
| dak_key.crypto_lib = T_COSE_CRYPTO_LIB_PSA; |
| dak_key.k.key_handle = dak_key_id; |
| cose_res = t_cose_key_encode(dak_key, |
| cose_key_buf, |
| &cose_key); |
| if (cose_res != T_COSE_SUCCESS) { |
| return PSA_ERROR_GENERIC_ERROR; |
| } |
| |
| /* Calculate and compare the hash of the public DAK */ |
| status = psa_hash_compare(dak_pub_hash_algo, |
| cose_key.ptr, cose_key.len, |
| dak_pub_hash, dak_pub_hash_len); |
| if (status != PSA_SUCCESS) { |
| return PSA_ERROR_INVALID_ARGUMENT; |
| } |
| |
| return status; |
| } |
| |
| /** |
| * \brief Static function to get a string representing the measurement algorithm |
| * |
| * String names according to |
| * https://www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xhtml |
| * |
| * \param[in] algorithm Algorithm identifier |
| * \param[out] measurement_desc Structure to carry the output string: |
| * pointer + string length, not including |
| * the null-terminator |
| * |
| * \return Returns error code as specified in \ref psa_status_t |
| */ |
| static inline psa_status_t |
| get_firmware_measurement_description(psa_algorithm_t algorithm, |
| struct q_useful_buf_c *measurement_desc) |
| { |
| switch (algorithm) { |
| case PSA_ALG_SHA_256: |
| measurement_desc->ptr = "sha-256"; |
| break; |
| case PSA_ALG_SHA_384: |
| measurement_desc->ptr = "sha-384"; |
| break; |
| case PSA_ALG_SHA_512: |
| measurement_desc->ptr = "sha-512"; |
| break; |
| default: |
| /* Algorithm not supported. */ |
| return PSA_ERROR_NOT_SUPPORTED; |
| } |
| measurement_desc->len = 7; /* Not including the null-terminator. */ |
| |
| return PSA_SUCCESS; |
| } |
| |
| /** |
| * \brief Encodes a firmware measurement and metadata as a map in CBOR |
| * |
| * \param[in] encode_ctx The encoding context to open the map in |
| * \param[in] signer_id Pointer to buffer which stores the signer ID |
| * \param[in] sw_version Pointer to buffer which stores the SW version |
| * \param[in] sw_type Pointer to buffer which stores the SW type |
| * \param[in] measurement_desc Pointer to buffer which stores the |
| * measurement description |
| * \param[in] measurement Pointer to buffer which stores the |
| * measurement value |
| * |
| * \return Returns error code as specified in \ref psa_status_t |
| */ |
| static psa_status_t |
| encode_firmware_measurement(QCBOREncodeContext *encode_ctx, |
| const struct q_useful_buf_c *signer_id, |
| const struct q_useful_buf_c *sw_version, |
| const struct q_useful_buf_c *sw_type, |
| const struct q_useful_buf_c *measurement_desc, |
| const struct q_useful_buf_c *measurement) |
| { |
| if (encode_ctx == NULL || signer_id == NULL || sw_version == NULL || |
| sw_type == NULL || measurement_desc == NULL || measurement == NULL) { |
| return PSA_ERROR_INVALID_ARGUMENT; |
| } |
| |
| QCBOREncode_OpenMap(encode_ctx); |
| |
| /* Encode signer ID as byte string. */ |
| QCBOREncode_AddBytesToMapN(encode_ctx, |
| IAT_SW_COMPONENT_SIGNER_ID, |
| *signer_id); |
| |
| /* Encode component version as text string. */ |
| QCBOREncode_AddTextToMapN(encode_ctx, |
| IAT_SW_COMPONENT_VERSION, |
| *sw_version); |
| |
| /* Encode software component type as text string. */ |
| QCBOREncode_AddTextToMapN(encode_ctx, |
| IAT_SW_COMPONENT_MEASUREMENT_TYPE, |
| *sw_type); |
| |
| /* Encode measurement description as text string. */ |
| QCBOREncode_AddTextToMapN(encode_ctx, |
| IAT_SW_COMPONENT_MEASUREMENT_DESC, |
| *measurement_desc); |
| |
| /* Encode measurement value as byte string. */ |
| QCBOREncode_AddBytesToMapN(encode_ctx, |
| IAT_SW_COMPONENT_MEASUREMENT_VALUE, |
| *measurement); |
| |
| QCBOREncode_CloseMap(encode_ctx); |
| |
| return PSA_SUCCESS; |
| } |
| |
| /** |
| * \brief Static function to return the boot state in CBOR encoded format |
| * |
| * The firmware measurements and associated firmware identity metadata together |
| * form the boot state. This function returns a CBOR encoded array if |
| * at least 1 firmware measurement is found, otherwise the \p boot_state_len |
| * parameter is set to 0 on return. |
| * |
| * \param[out] boot_state Pointer to the buffer where the encoded boot |
| * state information will be stored. |
| * \param[in,out] boot_state_len As an input it is the size of the allocated |
| * buffer for boot state in bytes. At return its |
| * values is updated with the exact size of the |
| * encoded boot state. |
| * |
| * \return Returns error code as specified in \ref psa_status_t |
| */ |
| static psa_status_t |
| get_platform_boot_state(uint8_t *boot_state, size_t *boot_state_len) |
| { |
| |
| psa_status_t status; |
| QCBOREncodeContext encode_context; |
| QCBORError encode_error; |
| struct q_useful_buf boot_state_buf = { |
| .ptr = boot_state, |
| .len = *boot_state_len |
| }; |
| struct q_useful_buf_c boot_state_buf_c = NULL_Q_USEFUL_BUF_C; |
| struct q_useful_buf_c measurement_desc = NULL_Q_USEFUL_BUF_C; |
| Q_USEFUL_BUF_MAKE_STACK_UB(measurement_buf, MEASUREMENT_VALUE_MAX_SIZE); |
| Q_USEFUL_BUF_MAKE_STACK_UB(signer_id_buf, SIGNER_ID_MAX_SIZE); |
| Q_USEFUL_BUF_MAKE_STACK_UB(sw_version_buf, VERSION_MAX_SIZE); |
| Q_USEFUL_BUF_MAKE_STACK_UB(sw_type_buf, SW_TYPE_MAX_SIZE); |
| uint32_t measurement_algo; |
| uint32_t measurement_cnt; |
| uint8_t slot_index; |
| bool is_locked; |
| |
| if (q_useful_buf_is_null_or_empty(boot_state_buf)) { |
| return PSA_ERROR_INVALID_ARGUMENT; |
| } |
| |
| /* Set up encoding context with output buffer. */ |
| QCBOREncode_Init(&encode_context, boot_state_buf); |
| |
| /* Retrieve all the measurements from the Measured Boot partition |
| * which are accessible to the Delegated Attestation partition. |
| */ |
| measurement_cnt = 0; |
| for (slot_index = 0; slot_index < NUM_OF_MEASUREMENT_SLOTS; slot_index++) { |
| status = tfm_measured_boot_read_measurement(slot_index, |
| signer_id_buf.ptr, |
| signer_id_buf.len, |
| &signer_id_buf.len, |
| sw_version_buf.ptr, |
| sw_version_buf.len, |
| &sw_version_buf.len, |
| &measurement_algo, |
| sw_type_buf.ptr, |
| sw_type_buf.len, |
| &sw_type_buf.len, |
| measurement_buf.ptr, |
| measurement_buf.len, |
| &measurement_buf.len, |
| &is_locked); |
| if (status != PSA_SUCCESS) { |
| continue; |
| } |
| |
| measurement_cnt++; |
| if (measurement_cnt == 1) { |
| /* Open array which stores the boot measurements. |
| * One measurement includes the measurement value of the |
| * firmware and associated metadata. |
| */ |
| QCBOREncode_OpenArray(&encode_context); |
| } |
| |
| /* Get the string representation of the measurement algorithm. */ |
| status = get_firmware_measurement_description( |
| (psa_algorithm_t)measurement_algo, |
| &measurement_desc); |
| if (status != PSA_SUCCESS) { |
| /* The boot state cannot be completed. */ |
| return PSA_ERROR_GENERIC_ERROR; |
| } |
| |
| status = encode_firmware_measurement( |
| &encode_context, |
| (struct q_useful_buf_c *)&signer_id_buf, |
| (struct q_useful_buf_c *)&sw_version_buf, |
| (struct q_useful_buf_c *)&sw_type_buf, |
| &measurement_desc, |
| (struct q_useful_buf_c *)&measurement_buf); |
| if (status != PSA_SUCCESS) { |
| return status; |
| } |
| } |
| |
| if (measurement_cnt != 0) { |
| /* Close array which stores the firmware measurements. */ |
| QCBOREncode_CloseArray(&encode_context); |
| } |
| |
| encode_error = QCBOREncode_Finish(&encode_context, &boot_state_buf_c); |
| /* Check for any encoding errors. */ |
| if (encode_error == QCBOR_ERR_BUFFER_TOO_SMALL) { |
| return PSA_ERROR_BUFFER_TOO_SMALL; |
| } else if (encode_error != QCBOR_SUCCESS) { |
| return PSA_ERROR_GENERIC_ERROR; |
| } |
| |
| *boot_state_len = boot_state_buf_c.len; |
| |
| return PSA_SUCCESS; |
| } |
| |
| /** |
| * \brief Derive a delegated attestation key using preprovisioned seed. |
| * |
| * \param[in] ecc_curve The type of the elliptic curve to which the |
| * requested key belongs. |
| * \param[in] key_bits The size of the requested key, in bits. |
| * \param[in] salt Salt for key derivation. |
| * \param[in] salt_len Size of the salt in bytes. |
| * \param[out] key_buf Pointer to the buffer where the delegated |
| * attestation key will be stored. |
| * \param[in,out] key_buf_len As an input it is the size of allocated buffer |
| * for the key, in bytes. At return its value is |
| * updated with the exact size of the derived |
| * attestation key. |
| * |
| * \return Returns error code as specified in \ref psa_status_t |
| */ |
| static psa_status_t |
| derive_delegated_attestation_key(psa_ecc_family_t ecc_curve, uint32_t key_bits, |
| uint8_t *salt, size_t salt_len, |
| uint8_t *key_buf, size_t *key_buf_len) |
| { |
| psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; |
| psa_key_derivation_operation_t op = PSA_KEY_DERIVATION_OPERATION_INIT; |
| psa_status_t status; |
| |
| if (salt == NULL || key_buf == NULL || |
| salt_len == 0 || key_buf_len == 0) { |
| return PSA_ERROR_INVALID_ARGUMENT; |
| } |
| |
| status = psa_destroy_key(dak_key_id); |
| if (status != PSA_SUCCESS && status != PSA_ERROR_INVALID_HANDLE) { |
| return status; |
| } |
| |
| /* Set the key attributes for the delegated attestation key */ |
| psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_EXPORT); |
| psa_set_key_type(&attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(ecc_curve)); |
| psa_set_key_bits(&attributes, key_bits); |
| |
| status = psa_key_derivation_setup(&op, PSA_ALG_HKDF(PSA_ALG_SHA_256)); |
| if (status != PSA_SUCCESS) { |
| return status; |
| } |
| |
| /* Set up a key derivation operation with DAK seed */ |
| status = psa_key_derivation_input_key(&op, PSA_KEY_DERIVATION_INPUT_SECRET, |
| TFM_BUILTIN_KEY_ID_DAK_SEED); |
| if (status != PSA_SUCCESS) { |
| goto err_release_op; |
| } |
| |
| /* Supply the secure boot state as input data to the key derivation */ |
| status = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_INFO, |
| salt, salt_len); |
| if (status != PSA_SUCCESS) { |
| goto err_release_op; |
| } |
| |
| /* Create the delegated attestation key from the key derivation operation */ |
| status = psa_key_derivation_output_key(&attributes, &op, |
| &dak_key_id); |
| if (status != PSA_SUCCESS) { |
| goto err_release_op; |
| } |
| |
| status = psa_export_key(dak_key_id, |
| key_buf, *key_buf_len, key_buf_len); |
| if (status != PSA_SUCCESS) { |
| goto err_release_key; |
| } |
| |
| /* Free resources associated with the key derivation operation */ |
| return psa_key_derivation_abort(&op); |
| |
| err_release_key: |
| (void)psa_destroy_key(dak_key_id); |
| |
| err_release_op: |
| (void)psa_key_derivation_abort(&op); |
| |
| return PSA_ERROR_GENERIC_ERROR; |
| } |
| |
| psa_status_t |
| delegated_attest_get_delegated_key(psa_ecc_family_t ecc_curve, |
| uint32_t key_bits, |
| uint8_t *key_buf, |
| size_t key_buf_size, |
| size_t *key_size, |
| psa_algorithm_t hash_algo) |
| { |
| psa_status_t status; |
| psa_hash_operation_t hash_op = psa_hash_operation_init(); |
| size_t boot_records_len = 0; |
| size_t key_len; |
| |
| /* Check the hash algorithm (input) if it is valid and supported. */ |
| status = psa_hash_setup(&hash_op, hash_algo); |
| (void)psa_hash_abort(&hash_op); |
| if (status != PSA_SUCCESS) { |
| return status; |
| } |
| |
| boot_records_len = sizeof(boot_state_buffer); |
| status = get_platform_boot_state(boot_state_buffer, |
| &boot_records_len); |
| if (status != PSA_SUCCESS) { |
| return status; |
| } |
| |
| key_len = key_buf_size; |
| status = derive_delegated_attestation_key( |
| ecc_curve, key_bits, |
| boot_state_buffer, boot_records_len, |
| key_buf, &key_len); |
| if (status != PSA_SUCCESS) { |
| return status; |
| } |
| |
| *key_size = key_len; |
| dak_requested = true; |
| /* Storing the hash algorithm for later use. */ |
| dak_pub_hash_algo = hash_algo; |
| |
| return PSA_SUCCESS; |
| } |
| |
| psa_status_t |
| delegated_attest_get_platform_token(const uint8_t *dak_pub_hash, |
| size_t dak_pub_hash_size, |
| uint8_t *token_buf, |
| size_t token_buf_size, |
| size_t *token_size) |
| { |
| psa_status_t status; |
| |
| if (!dak_requested) { |
| /* The platform attestation token cannot be created before a DAK has |
| * been successfully requested. |
| */ |
| return PSA_ERROR_INVALID_ARGUMENT; |
| } |
| |
| /* Verify the hash of the public part of the DAK received as input */ |
| status = verify_dak_pub_hash(dak_pub_hash, dak_pub_hash_size); |
| if (status != PSA_SUCCESS) { |
| return status; |
| } |
| |
| status = psa_initial_attest_get_token(dak_pub_hash, dak_pub_hash_size, |
| token_buf, token_buf_size, |
| token_size); |
| if (status != PSA_SUCCESS) { |
| return status; |
| } |
| |
| return PSA_SUCCESS; |
| } |