Skip to content

Latest commit

 

History

History
844 lines (741 loc) · 34.1 KB

InitTimeConfigurationInterface.md

File metadata and controls

844 lines (741 loc) · 34.1 KB

Open Enclave Init-time Configuration Interface

In this paper, we present the interface design for Init-time Configuration support in Open Enclave. Open Enclave Init-time Configuration feature supports configurable enclave functionality beyond the static functionality implemented by the enclave code and initialized data inside the enclave at the enclave initialization time.

SGX CONFIGID and CONFIGSVN Overview

Intel's 10th-gen Core processor and 3rd-gen Xeon-SP processor support the SGX Key Separation and Sharing (KSS) feature, including CONFIGID and CONFIGSVN, which are new fields defined in SECS, set by the host side SW at Enclave initialization time. CONFIGID and CONFIGSVN enable configurable enclave functionality, in contrast to the static functionality decided by the enclave code and initialized data, which are reflected in SGX MRENCLAVE and can not change without re-signing the enclave. For example, an enclave app might configure the enclave with a public key of an external service the enclave will trust, instead of relying on a hardcoded value. Another form of configurable enclave functionality is loading additional code/data into the enclave post enclave initialization. Enclave functionality change based on the configuration data might have security implication, therefore, the configuration data must be captured in the enclave attestation. CONFIGID and CONFIGSVN are part of the enclave identity produced by the CPU, reflecting the configuration committed at the enclave initialization time.

The exact usage of CONFIGID and CONFIGSVN depends on the enclave implementation. For the use case of loading additional code/data into the enclave, CONFIGID might be used to reflect the hash of additional code/data to be accepted by the enclave post enclave initialization, or the hash of a metadata to be loaded into the enclave and used by the enclave code to verify extra code/data to load. CONFIGSVN might be used in case CONFIGID does not fully reflect the identity of the additional content. For example, CONFIGID can be set as the hash of the signing key or cert to verify the metadata, and CONFIGSVN can be set as the version number of the signed metadata.

The scheme of committing the Enclave configuration data into the enclave identity produced by the CPU at the enclave initialization time is critical for SGX. For the use case of loading additional code, CONFIGID and CONFIGSVN reflect the identity of the additional code allowed to be loaded into the enclave. As SGX Technology does not inherently provide privilege isolation among SW modules within the enclave, the identity must be immutable at enclave run time. Otherwise, the loaded code might be able to produce the attestation evidence and spoof its own identity, For instance, consider an enclave containing a JavaScript interpreter that executes user scripts that originate from the untrusted host in shared enclave memory, and which has access to the oe_get_evidence API. It is impossible to know which script has been executed by such an enclave based on traditional evidence, even if the hash of the script is supposed to be included in the REPORT.report_data, because a malicious script can set REPORT.report_data and obtain valid evidence via oe_get_evidence. Similarly, if an enclave loads and executes arbitrary assembly code from the host, this assembly code can use the SGX EREPORT instruction to create valid evidence reporting a spoofed identity of the loaded code. With CONFIGID and CONFIGSVN set at enclave initialization time, and accessible to the enclave code, the static code of the enclave makes sure the user script or assembly code loaded into the enclave match the identity captured in CONFIGID and CONFIGSVN, before transferring the execution control to the loaded code. Even if the loaded code is malicious, the code has no ability to spoof its own identity.

Enclave Creation Interface

The configurable enclave functionality concept is not limited to SGX TEE. Other TEEs can potentially support this concept, including the aspect of immutable identity of additional code/code allowed to be loaded into the enclave post enclave initialization. But at the time of the writing, only SGX with KSS feature supports Init-time Configuration Data.

An OE SGX application passes the Init-time Configuration Data to the enclave loader through the OE_SGX_CONFIGURATION_DATA enclave setting context:

/**
 * Types of settings passed into **oe_create_enclave**
 */
typedef enum _oe_enclave_setting_type
{
    OE_ENCLAVE_SETTING_CONTEXT_SWITCHLESS = 0xdc73a628,
#ifdef OE_WITH_EXPERIMENTAL_EEID
    OE_EXTENDED_ENCLAVE_INITIALIZATION_DATA = 0x976a8f66,
#endif
    /** Enclave configuration data committed at Enclave initialization time,
     *  reflected in TEE-produced enclave identity evidence. Currently only
     *  supported by SGX Enclaves with KSS feature enabled.
     */
    OE_SGX_ENCLAVE_CONFIG_DATA = 0x78b5b41d
} oe_enclave_setting_type_t;

typedef struct _oe_sgx_enclave_setting_configuration_data
{
    uint8_t config_id[64];
    uint16_t config_svn;
    bool ignore_if_unsupported;

}
oe_sgx_enclave_setting_configuration_data;

For SGX, based on whether the OE_SGX_ENCLAVE_CONFIG_DATA setting is provided and whether SGX-KSS feature is supported, the SGX enclave loader sets SECS.CONFIGID and SECS.CONFIGSVN according to the table below. Developer can set the ignore_if_unsupported field to true to ignore the provided configuration data if the SGX-KSS feature is not supported.

SGX KSS Feature OE_SGX_ENCLAVE_CONFIG_DATA ignore_if_unsupported Behavior
Supported Provided true/false The enclave loader sets SECS.CONFIGID and SECS.CONFIGSVN to config_id and config_svn
Supported Not Provided - No action
Not supported Provided true Enclave creation fails
Not supported Provided false No action (the enclave loader ignores the provided config_id and config_svn)
Not supported Not Provided - No action

The enclave developer is responsible for the host side code that produces the Enclave Configuration Data and pass the data to the enclave loader. The code snippet below demonstrates an example of how a developer can provide configuration data and pass it to an enclave.

    const uint8_t configuration_id_data[64] = {
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,  12,  13,  143, 153,
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,  12,  134, 14,  154,
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,  125, 13,  14,  155,
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 116, 12,  13,  14,  156};

    const uint16_t configuration_svn = 65535;
    oe_sgx_enclave_setting_config_data mandatory_configuration_data_setting = {
        {0}, configuration_svn, false /* ignore_if_unsupported */};
    memcpy(
        mandatory_configuration_data_setting.config_id,
        configuration_id_data,
        sizeof(mandatory_configuration_data_setting.config_id));
    oe_enclave_setting_t mandatory_settings;
    mandatory_settings.setting_type = OE_SGX_ENCLAVE_CONFIG_DATA;
    mandatory_settings.u.configuration_data = &mandatory_configuration_data_setting;
    result = oe_create_config_id_enclave(
            argv[1],
            OE_ENCLAVE_TYPE_SGX,
            flags,
            &mandatory_settings,
            1,
            &enclave);
    OE_TEST(result == OE_OK);

As KSS feature is not avaiable on CoffeeLake machines, user can make config_id/configsv setting as optional as shown below.

    const uint8_t configuration_id_data[64] = {
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,  12,  13,  143, 153,
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,  12,  134, 14,  154,
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,  125, 13,  14,  155,
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 116, 12,  13,  14,  156};

    const uint16_t configuration_svn = 65535;
    oe_sgx_enclave_setting_config_data optional_configuration_data_setting  = {
        {0}, configuration_svn, true /* ignore_if_unsupported */};
    memcpy(
        optional_configuration_data_setting.config_id,
        configuration_id_data,
        sizeof(optional_configuration_data_setting.config_id));
    oe_enclave_setting_t optional_settings;
    optional_settings.setting_type = OE_SGX_ENCLAVE_CONFIG_DATA;
    optional_settings.u.configuration_data = &optional_configuration_data_setting;
    result = oe_create_config_id_enclave(
            argv[1],
            OE_ENCLAVE_TYPE_SGX,
            flags,
            &optional_settings,
            1,
            &enclave);
    OE_TEST(result == OE_OK);

The enclave developer should also implement an explicit function to load the additional content into the enclave memory post enclave initialization, and to verify the loaded content against the Enclave Configuration Data, if the Configuration Data is used to covey the identity of the extra content. The exact relationship between the extra content and the Enclave Configuration Data is defined by the enclave developer.

On SGX CPUs supporting the KSS feature, CONFIGID and CONFIGSVN are available in the SGX REPORT. The OE SDK libs will provide an API to retrieve config_id and config_svn within the enclave. Below code snippet demonstrates how users can retrieve CONFIGID and CONFIGSVN on enclave side.

    uint8_t* evidence = NULL;
    size_t evidence_size = 0;
    oe_claim_t* claims = NULL;
    size_t claims_length = 0;

    OE_CHECK(oe_attester_initialize());
    oe_uuid_t selected_format;
    oe_attester_select_format(&_ecdsa_uuid, 1, &selected_format);

    OE_CHECK(oe_get_evidence(
        &selected_format,
        OE_EVIDENCE_FLAGS_EMBED_FORMAT_ID,
        NULL,
        0,
        NULL,
        0,
        &evidence,
        &evidence_size,
        NULL,
        0));

    OE_CHECK(oe_verifier_initialize());

    OE_CHECK(oe_verify_evidence(
        NULL,
        evidence,
        evidence_size,
        NULL,
        0,
        NULL,
        0,
        &claims,
        &claims_length));

    uint8_t* configuration_id;
    uint16_t* configuration_svn;

    configuration_id =
        (uint8_t*)_find_claim(claims, claims_length, OE_CLAIM_SGX_CONFIG_ID);

    configuration_svn =
        (uint16_t*)_find_claim(claims, claims_length, OE_CLAIM_SGX_CONFIG_SVN);

    oe_free_evidence(evidence);
    oe_free_claims(claims, claims_length);
    oe_attester_shutdown();
    oe_verifier_shutdown();

In the future, when more TEEs support Init-time Configuration Data, the interface might be expanded to support TEE-agnostic Init-time Configuration Data.

Attester and Verifier Plugin support

The Enclave Attestation Attester and Verifier plugins will include the Enclave Init-time Configuration Data as base claims and may include the additional content as custom claims. For a TEE environment that does not support Enclave Init-time Configuration, for example, a SGX enclave running on a SGX CPU that does not support KSS, the Enclave Init-time Configuration Data claims should be 0.

Currently, the oe_result_t oe_get_evidence(...) function each Attester Plugin must support specifies the custom_claims_buffer as a variable length byte-array, whose relationship with other base claims are defined by each Attester/Verifier plugin. Typically, the plugins define the customer claims as "run-time" claims made by Enclave code, and protect the integrity of the data by binding the data with certain base claims produced by the TEE environment. For example, SGX Attester Plugin implementations set the ReportData field of the SGX REPORT produced by the CPU as the SHA256 hash of the custom_claims_buffer. SGX Enclave code can generate a RSA key pair, and include the RSA public key in the custom claims, to be used by a remote entity to wrap secrets to be delivered to the enclave, after verification of the attestation.

The extra content that can be loaded into the enclave post enclave initialization, identified by the Enclave Init-time Configuration Data, can be considered "init-time" claims. Combining the "init-time" claims and the "run-time" claims in the custom_claims_buffer field as a single variable length byte-array is possible, but would either require the caller to be aware of plugin-specific implementation of the internal structure of the combined custom_claims_buffer field, or the Verifier to be aware of the caller defined internal structure.

Plugin API change with explicit init-time custom claim buffer support

One solution is to explicitly support the optional "run-time" custom claim buffer and "init-time" custom claim buffer. Similar to the handling of the "run-time" customer claim buffer, the placement of the "init-time" customer claim buffer within the evidence buffer is plugin-specific, but all plugin implementations supporting Enclave Init-time Configuration should include the the "init-time" customer claim buffer in the evidence buffer. Different from the handling of the "run-time" customer claim buffer, the Attester Plugin does not bind the data with certain base claims produced by the TEE environment, as it's the enclave developer's responsibility to do so.

As the relationship between the "init-time" custom claim buffer and the Enclave Init-time Configuration Data is defined by the enclave developer, a single implementation of Verifier Plugin can not accommodate all possible definitions of Enclave Init-time Configuration Data. The Verifier Plugin should support a default definition of Enclave Init-time Configuration Data, where the first 32 bytes of the Enclave Init-time Configuration Data is defined as the SHA256 hash of the inittime_custom_claims_buffer content, and verify the integrity of the content. As defined below, the inittime_custom_claims_buffer contains an integrity algorithm ID, with ID 0 as the default definition each Verifier Plugin should support if the Plugin supports inittime_custom_claims_buffer. A Verifier Plugin might support other integrity algorithms. If the caller of the oe_result_t oe_get_evidence(...) function sets an integrity algorithm ID not supported by the Verifier Plugin, the Verifier Plugin should output the inittime_custom_claims_buffer as it is, as an unverified init-time claim. In that case, the consumer of the outputted claims is responsible to verify the integrity of the inittime_custom_claims_buffer claim using the base claim of config_id/config_svn.

/**
 * oe_get_evidence
 *
 * Generates the evidence for the given format id.
 * This function is only available in the enclave.
 *
 * @param[in] format_id The format ID of the evidence to be generated.
 * @param[in] flags A bit-wise parameter. Currently there is one bit
 * defined: OE_EVIDENCE_FLAGS_EMBED_FORMAT_ID. If this bit is set,
 * the evidence and endorsements will be wrapped with a header containing
 * the format ID.
 * @param[in] runtime_custom_claims_buffer The optional runtime custom claims
 * buffer. When provided, the content of the buffer is included in the
 * evidence_buffer, with integrity protection. Depending on the underlining TEE
 * and the plugin implementation, the content might or might not be further
 * encrypted.
 * @param[in] runtime_custom_claims_buffer_size The number of bytes in the
 * runtime custom claims buffer.
 * @param[in] inittime_custom_claims_buffer The optional inittime custom claims
 * buffer. When provided, the content of the buffer is included in the
 * evidence_buffer as plaintext. The integrity protection of the content is
 * provided by the underlying TEE and the enclave SW outside the plugin.
 * @param[in] inittime_custom_claims_buffer_size The number of bytes in the
 * inittime custom claims buffer.
 * @param[in] optional_parameters The optional format-specific input parameters.
 * @param[in] optional_parameters_size The size of optional_parameters in bytes.
 * @param[out] evidence_buffer An output pointer that will be assigned the
 * address of the dynamically allocated evidence buffer.
 * @param[out] evidence_buffer_size A pointer that points to the size of the
 * evidence buffer in bytes.
 * @param[out] endorsements_buffer If not NULL, an output pointer that will be
 * assigned the address of the dynamically allocated endorsements buffer.
 * @param[out] endorsements_buffer_size A pointer that points to the size of the
 * endorsements buffer in bytes.
 * @retval OE_OK The function succeeded.
 * @retval OE_INVALID_PARAMETER At least one of the parameters is invalid.
 * @retval OE_NOT_FOUND The input evidence format id is not supported.
 * @retval other appropriate error code.
 */
oe_result_t oe_get_evidence(
    const oe_uuid_t* format_id,
    uint32_t flags,
    const void* runtime_custom_claims_buffer,
    size_t runtime_custom_claims_buffer_size,
    const oe_inittime_custom_claim_buffer_t* inittime_custom_claims_buffer,
    size_t inittime_custom_claims_buffer_size,
    const void* optional_parameters,
    size_t optional_parameters_size,
    uint8_t** evidence_buffer,
    size_t* evidence_buffer_size,
    uint8_t** endorsements_buffer,
    size_t* endorsements_buffer_size);

typedef struct _oe_inittime_custom_claim_buffer_t
{
    /* ID of integrity algorithm to be used to verify the buffer content.
     *   0: config_id = SHA256(buffer[]);
     *   others: undefined;
     */
    uint32_t integrity_algorithm_id;
    /* Variable length buffer */
    char buffer[];
} oe_inittime_custom_claim_buffer_t;

The following pseudocode demonstrates how the proposed interface can be used.

    /// Untrusted Code of the OE Application
    ///

    // Set up extra content and config_id
    rsa3072_public_key_t  pubkey = {...};
    oe_sgx_enclave_setting_config_data configuration_data =
    {
        SHA256(pubkey), // config_id
        0, // config_svn
        false //ignore if unspported
    };
    oe_enclave_setting_t settings[] =
    {
        {
            setting_type = OE_SGX_ENCLAVE_CONFIG_DATA,
            u.oe_sgx_configuration_data_t = &configuration_data
        }
    };
    // Create enclave
    oe_create_enclave(..., settings, ...);

    /// Enclave code of the OE Application
    ///

    // Load the pubkey into enclave memory and verify it against
    // config_id
    ...

    // Set up init-time claims for attestation
    uint8 claim_buffer[sizeof(uint32_t)+sizeof(rsa3072_public_key_t)];
    oe_inittime_custom_claim_buffer_t *p_inittime_custom_claims_buffer
                                          = claim_buffer;

    p_inittime_custom_claims_buffer->integrity_algorithm_id = 0;
    memcpy(p_inittime_custom_claims_buffer->buffer, pubkey,
           sizeof(rsa3072_public_key_t));
  
    size_t inittime_custom_claims_buffer_size =
        sizeof(claim_buffer);

    // Generate evidence #1
    oe_get_evidence(...,
        runtime_custom_claims_buffer_1,
        runtime_custom_claims_buffer_size_1,
        p_inittime_custom_claims_buffer,
        inittime_custom_claims_buffer_size,
        evidence_buffer_1,
        ...);
    // Send evidence to Verifier
    send_evidence_to_REST_URL(..., evidence_buffer_1, evidence_buffer_size_1,...);
    ...

    // Generate evidence #2
    oe_get_evidence(...,
        runtime_custom_claims_buffer_2,
        runtime_custom_claims_buffer_size_2,
        p_inittime_custom_claims_buffer,
        inittime_custom_claims_buffer_size,
        evidence_buffer_2,
        ...);

    // Send evidence to Verifier
    send_evidence_to_REST_URL(..., evidence_buffer_2, evidence_buffer_size_2,...);


    /// Verifier Plugin
    ///
    oe_result_t oe_verify_evidence(
    const oe_uuid_t* format_id,
    const uint8_t* evidence_buffer,
    size_t evidence_buffer_size,
    const uint8_t* endorsements_buffer,
    size_t endorsements_buffer_size,
    const oe_policy_t* policies,
    size_t policies_size,
    oe_claim_t** claims,
    size_t* claims_length)
    {
        ...
        //extract the init_time_claims_buffer and verify it
        inittime_claims_buffer = _find_init_time_claim_buffer(evidence_buffer,
                                   evidence_buffer_size);
        inittime_claims_buffer_size = _find_init_time_claim_buffer_size(
                                        evidence_buffer, evidence_buffer_size);
        configuration_id = _find_config_id(evidence_buffer, evidence_buffer_size);
        if (inittime_claim_buffer.integrity_algorithm_id ! = 0 )
            return error;
        if (SHA256(inittime_claim_buffer.buffer, inittime_claims_buffer_size)
              != configuration_id)
            return error;
        //output init_time claims
        ...
    }



    /// Verifier
    ///

    // Verify evidence
    oe_verify_evidence(...,
            evidence_buffer,
            ...,
            &claims,
            &claims_length);
    // Check claims against policy, including init_time claims
    // reported by the plugin.
    ...

Alternative Design with no Plugin API change

Alternatively, the responsibility of "init-time" claims packaging and verification can be removed from the Attester/Verifier plugin and left to the upper level SW to implement. The upper level SW can append the "init-time" claim buffer to the evidence buffer returned by oe_get_evidence and send to the the Verifier, which is expected to verify the integrity of the "init-time" claim buffer received using the Enclave Init-time Configuration Data base claim. In this approach, the upper level SW within the attesting Enclave app and the Verifier should establish an agreement on the implementation-specific integrity algorithm. In this solution, the oe_get_evidence API does not require any change other than clarification on the custom_claims_buffer definition.

/**
 * oe_get_evidence
 *
 * Generates the evidence for the given format id.
 * This function is only available in the enclave.
 *
 * @param[in] format_id The format ID of the evidence to be generated.
 * @param[in] flags A bit-wise parameter. Currently there is one bit
 * defined: OE_EVIDENCE_FLAGS_EMBED_FORMAT_ID. If this bit is set,
 * the evidence and endorsements will be wrapped with a header containing
 * the format ID.
 * @param[in] custom_claims_buffer The optional runtime custom claims
 * buffer. When provided, the content of the buffer is included in the
 * evidence_buffer, with integrity protection. Depending on the underlining TEE
 * and the plugin implementation, the content might or might not be further
 * encrypted.
 * @param[in] custom_claims_buffer_size The number of bytes in the
 * runtime custom claims buffer.
 * @param[in] optional_parameters The optional format-specific input parameters.
 * @param[in] optional_parameters_size The size of optional_parameters in bytes.
 * @param[out] evidence_buffer An output pointer that will be assigned the
 * address of the dynamically allocated evidence buffer.
 * @param[out] evidence_buffer_size A pointer that points to the size of the
 * evidence buffer in bytes.
 * @param[out] endorsements_buffer If not NULL, an output pointer that will be
 * assigned the address of the dynamically allocated endorsements buffer.
 * @param[out] endorsements_buffer_size A pointer that points to the size of the
 * endorsements buffer in bytes.
 * @retval OE_OK The function succeeded.
 * @retval OE_INVALID_PARAMETER At least one of the parameters is invalid.
 * @retval OE_NOT_FOUND The input evidence format id is not supported.
 * @retval other appropriate error code.
 */
oe_result_t oe_get_evidence(
    const oe_uuid_t* format_id,
    uint32_t flags,
    const void* custom_claims_buffer,
    size_t custom_claims_buffer_size,
    const void* optional_parameters,
    size_t optional_parameters_size,
    uint8_t** evidence_buffer,
    size_t* evidence_buffer_size,
    uint8_t** endorsements_buffer,
    size_t* endorsements_buffer_size);

The following pseudocode demonstrates how the proposed interface can be used.

    /// Untrusted Code of the OE Application
    ///

    // Set up extra content and config_id
    rsa3072_public_key_t  pubkey = {...};
    oe_sgx_enclave_setting_config_data configuration_data =
    {
        SHA256(pubkey), // config_id
        0, // config_svn
        false //ignore if unspported
    };
    oe_enclave_setting_t settings[] =
    {
        {
            setting_type = OE_SGX_ENCLAVE_CONFIG_DATA,
            u.oe_sgx_configuration_data_t = &configuration_data
        }
    };
    // Create enclave
    oe_create_enclave(..., settings, ...);

    /// Enclave code of the OE Application
    ///

    // Load the pubkey into enclave memory and verify it against
    // config_id
    ...

    // Set up init-time claims for attestation
    uint8 claim_buffer[sizeof(uint32_t)+sizeof(rsa3072_public_key_t);
    oe_inittime_custom_claim_buffer_t *p_inittime_custom_claims_buffer
                                                        = claim_buffer;

    p_inittime_custom_claims_buffer->integrity_algorithm_id = 0;
    memcpy(p_inittime_custom_claims_buffer->buffer, pubkey,
           sizeof(rsa3072_public_key_t));
  
    size_t inittime_custom_claims_buffer_size =
        sizeof(claim_buffer);

    // Generate evidence #1
    oe_get_evidence(...,
        runtime_custom_claims_buffer_1,
        runtime_custom_claims_buffer_size_1,
        evidence_buffer_1,
        ...);

    //Assemble the data buffer with evidence and inittime_claim
    ...
    data_buffer->evidence_buffer_size = evidence_buffer_size_1;
    data_buffer->inittime_claims_buffer_size = inittime_custom_claims_buffer_size;
    memcpy(data_buffer->data, evidence_buffer_1, evidence_buffer_size_1);
    memcpy(data_buffer->data + evidence_buffer_size_1;
       p_inittime_custom_claims_buffer, inittime_custom_claims_buffer_size);
    ...

    // Send data_buffer to Verifier
    send_evidence_to_REST_URL(..., data_buffer, data_buffer_size, ....);
    ...

    // Generate evidence #2
    oe_get_evidence(...,
        runtime_custom_claims_buffer_2,
        runtime_custom_claims_buffer_size_2,
        evidence_buffer_2,
        ...);

    //Assemble the data buffer with evidence and inittime_claim
    ...
    data_buffer->evidence_buffer_size = evidence_buffer_size_2;
    data_buffer->inittime_claims_buffer_size = inittime_custom_claims_buffer_size;
    memcpy(data_buffer->data, evidence_buffer_2, evidence_buffer_size_2);
    memcpy(data_buffer->data + evidence_buffer_size_2;
      p_inittime_custom_claims_buffer, inittime_custom_claims_buffer_size);
    ...

    // Send evidence to Verifier
    send_evidence_to_REST_URL(..., data_buffer, data_buffer_size,...);

    /// Verifier Plugin
    ///
    oe_result_t oe_verify_evidence(
    const oe_uuid_t* format_id,
    const uint8_t* evidence_buffer,
    size_t evidence_buffer_size,
    const uint8_t* endorsements_buffer,
    size_t endorsements_buffer_size,
    const oe_policy_t* policies,
    size_t policies_size,
    oe_claim_t** claims,
    size_t* claims_length)
    {
        ...
        //extract the customer_claims_buffer and verify it
        custom_claims_buffer = _find_custom_claim_buffer(evidence_buffer,
                               evidence_buffer_size);
        custom_claims_buffer_size = _find_custom_claim_buffer_size(
                                    evidence_buffer, evidence_buffer_size);
        report_data = _find_report_data(evidence_buffer, evidence_buffer_size);
        if (SHA256(custom_claims_buffer, custom_claims_buffer_size) != report_data)
            return error;
        // has to output the custom claim buffer as a whole, as the buffer
        // internal structure is opaque to the Plugin.
        oe_add_claim(claims, custom_claims_buffer, custom_claims_buffer_size,
                     OE_SGX_CUSTOM_CLAIM_BUFFER)
        ...
    }


    /// Verifier
    ///

    // Verify evidence
    oe_verify_evidence(...,
            data_buffer->data,
            data_buffer->evidence_buffer_size,
            ...,
            &claims,
            &claims_length);
    // Check claims provided by Verifier Plugin against policy,
    ...
    // Verifier has to validate init-time claims buffer
    configuration_id = _find_claim(claims, claims_length, OE_SGX_CONFIG_ID);
    // check init-time claim  
    p_init_time_custom_claims_buffer = data_buffer->data + evidence_buffer_size;
    if (p_inittime_claim_buffer->integrity_algorithm_id ! = 0 )
            return error;
    if (SHA256(p_inittime_claim_buffer->buffer,
        data_buffer->inittime_claims_buffer_size -
        sizeof(p_inittime_claim_buffer->integrity_algorithm_id)) != configuration_id)
            return error;
    ...
    // Check p_inittime_claim_buffer->buffer against policy.
    ...

In the alternative design, the caller of oe_get_evidence is not supposed to include the init-time customer claim buffer inside the custom_claim_buffer input to the API. If the caller does so indeed, the Verifier logic would have to change. The following pseudocode shows the difference.

    /// Untrusted Code of the OE Application
    ///

    // Set up extra content and config_id
    rsa3072_public_key_t  pubkey = {...};
    oe_sgx_enclave_setting_config_data configuration_data =
    {
        SHA256(pubkey), // config_id
        0, // config_svn
        false //ignore if unspported
    };
    oe_enclave_setting_t settings[] =
    {
        {
            setting_type = OE_SGX_ENCLAVE_CONFIG_DATA,
            u.oe_sgx_configuration_data_t = &configuration_data
        }
    };

    // Create enclave
    oe_create_enclave(..., settings, ...);

    /// Enclave code of the OE Application
    ///

    // Load the pubkey into enclave memory and verify it against
    // config_id
    ...

    // Set up init-time claims for attestation
    uint8 claim_buffer[sizeof(uint32_t)+sizeof(rsa3072_public_key_t);
    oe_inittime_custom_claim_buffer_t *p_inittime_custom_claims_buffer
                                                        = claim_buffer;

    p_inittime_custom_claims_buffer->integrity_algorithm_id = 0;
    memcpy(p_inittime_custom_claims_buffer->buffer, pubkey,
           sizeof(rsa3072_public_key_t));
  
    size_t inittime_custom_claims_buffer_size =
        sizeof(claim_buffer);

    // Generate evidence #1
    //Assemble the runtime claim and initime claim into a single custom claim buffer
    ...
    claim_buffer->runtime_claims_buffer_size = runtime_custom_claims_buffer_size_1;
    claim_buffer->inittime_claims_buffer_size = inittime_custom_claims_buffer_size;
    memcpy(claim_buffer->data, runtime_custom_claims_buffer_1,
           runtime_custom_claims_buffer_size_1);
    memcpy(data_buffer->data + runtime_custom_claims_buffer_size_1;
           p_inittime_custom_claims_buffer, inittime_custom_claims_buffer_size);
    ...
    oe_get_evidence(...,
        claim_buffer,
        sizeof(claim_buffer)
            + claim_buffer->runtime_claims_buffer_size
            + claim_buffer->inittime_claims_buffer_size,
        evidence_buffer_1,
        ...);

    // Send evidence to Verifier
    send_evidence_to_REST_URL(..., evidence_buffer_1, evidence_buffer_size_1, ....);
    ...

    // Generate evidence #2
    //Assemble the runtime claim and initime claim into a single custom claim buffer
    ...
    claim_buffer->runtime_claims_buffer_size = runtime_custom_claims_buffer_size_2;
    claim_buffer->inittime_claims_buffer_size = inittime_custom_claims_buffer_size;
    memcpy(claim_buffer->data, runtime_custom_claims_buffer_2,
           runtime_custom_claims_buffer_size_2);
    memcpy(data_buffer->data + runtime_custom_claims_buffer_size_2;
           p_inittime_custom_claims_buffer, inittime_custom_claims_buffer_size);
    ...
    oe_get_evidence(...,
        claim_buffer,
        sizeof(claim_buffer)
            + claim_buffer->runtime_claims_buffer_size
            + claim_buffer->inittime_claims_buffer_size,
        evidence_buffer_2,
        ...);
    ...
    // Send evidence to Verifier
    send_evidence_to_REST_URL(..., evidence_buffer_2, evidence_buffer_size_2,...);

    /// Verifier Plugin
    ///
    oe_result_t oe_verify_evidence(
    const oe_uuid_t* format_id,
    const uint8_t* evidence_buffer,
    size_t evidence_buffer_size,
    const uint8_t* endorsements_buffer,
    size_t endorsements_buffer_size,
    const oe_policy_t* policies,
    size_t policies_size,
    oe_claim_t** claims,
    size_t* claims_length)
    {
        ...
        //extract the customer_claims_buffer and verify it
        custom_claims_buffer = _find_custom_claim_buffer(evidence_buffer,
                                evidence_buffer_size);
        custom_claims_buffer_size = _find_custom_claim_buffer_size(
                                     evidence_buffer, evidence_buffer_size);
        report_data = _find_report_data(evidence_buffer, evidence_buffer_size);
        if (SHA256(custom_claims_buffer, custom_claims_buffer_size) != report_data)
            return error;
        // has to output the custom claim buffer as a whole, as the buffer
        // internal structure is opaque to the Plugin.
        oe_add_claim(claims, custom_claims_buffer, custom_claims_buffer_size,
                     OE_SGX_CUSTOM_CLAIM_BUFFER);
        ...
    }


    /// Verifier
    ///

    // Verify evidence
    oe_verify_evidence(...,
            evidence_buffer,
            evidence_buffer_size,
            ...,
            &claims,
            &claims_length);
    // Check claims provided by Verifier Plugin against policy,
    ...
    // Verifier has to validate init-time claims buffer within the
    // OE_SGX_CUSTOM_CLAIM_BUFFER claim
    configuration_id = _find_claim(claims, claims_length, OE_SGX_CONFIG_ID);
    data_buffer = _find_claim(claims, claims_length, OE_SGX_CUSTOM_CLAIM_BUFFER);
    // check init-time claim buffer within the data_buffer
    p_init_time_custom_claims_buffer = data_buffer->data +
        data_buffer->runtime_claims_buffer_size;
    if (p_inittime_claim_buffer->integrity_algorithm_id ! = 0 )
            return error;
    if (SHA256(p_inittime_claim_buffer->buffer,
               data_buffer->inittime_claims_buffer_size
                 - sizeof(p_inittime_claim_buffer->integrity_algorithm_id))
        != configuration_id)
            return error;
    ...
    // Check p_inittime_claim_buffer->buffer against policy.
    ...

Recommendation

The alternative design where the caller of the Plugins is responsible for "init-time" claims packaging and verification is chosen, for its flexibility and clear designation of the responsibilities between the Plugin developers and Application developers.

It's suggested that the Application developers concatenate "init-time" claims buffer and the evidence buffer returned by the Attester Plugin based on their own protocol.

Authors

Bo Zhang [email protected].