sealing

2022-04-14 ยท 9 min read

Deriving some Key Material #

While an enclave is running, the enclave application code has access to the EGETKEY instruction. Enclaves use this to derive Seal Key material.

  1. Enclave application code calls EREPORT to obtain the CPU SVN and ISV SVN.
  2. The enclave application prepares a KEYREQUEST struct with appropriate fields.
    1. The keyname=4, for Seal Key.
    2. Choose a Key Request Policy.
    3. Choose a keyid. This should be a hashed Domain Separation Value, like you would input to a normal KDF. Some places also seem to use a random value, which you prepend in plaintext next to the ciphertext? Not sure when that's necessary. Is this also functioning like a nonce or something? Need to investigate...
  3. User code calls the EGETKEY instruction with the KEYREQUEST.
  4. The instruction derives key material according to the policy defined by the KEYREQUEST and returns 128-bits key material to the enclave code.
    1. IF the Keyrequest.ISVSVN > Enclave.ISVSVN or Keyrequest.CPUSVN > Enclave.CPUSVN, THEN the key request fails.

You can also view the full EGETKEY flow and some real code in oasis-core that calls EGETKEY.

Key Request Policy #

The enclave has two options

  1. The enclave can request a key derived from the Enclave Measurement (MRENCLAVE), which pins a derived key to the exact (1) TCB version, (2) enclave code, and (3) CPU product ID.
  2. The key can also be derived from the Enclave Signer's Identity, which allows any enclave signed with this identity to derive the same keys. Newer enclave versions can also successfully derive keys created on older versions, but older versions will fail to derive keys created on newer versions. See Preventing Version Downgrade Attacks.

Preventing Version Downgrade Attacks #

Older versions of an enclave should not be able to derive keys or read sealed data from a newer enclave version. To prevent these downgrading attacks, the KEYREQUEST also contains the current CPU SVN and ISV SVN. The EGETKEY intrinsic will then verify that the keyrequest CPU SVN and ISV SVN are not beyond the current CPU SVN and enclave ISV SVN.

Sealing Pattern #

Sealed data should effectively include the SGX lingo > KEYREQUEST used to seal the data. When an enclave wants to decrypt some sealed data, it just passes this attached KEYREQUEST to EGETKEY. An enclave should then reseal the data, but with a freshly generated Seal Key. This will ratchet any old secure platform versions forward.

Migrating data between enclaves #

  1. Same platform, same enclave, different instances
    1. Offline migration is always possible. Both enclaves will simply rederive the same keys.
  2. Same platform, different enclave versions
    1. If the enclave has a newer security version for the CPU SVN + ISV SVN and the Keypolicy is set to MRSIGNER
      1. Then Offline migration is possible. Both enclaves will derive the same keys.
    2. Otherwise, online migration is necessary.
  3. Different platform, same or different enclave
    1. Online migration is necessary.

Offline migration #

We say offline migration is available when no coordination is required for two enclaves to read each other's sealed data.

Online migration #

In contrast, online migration requires both enclaves to coordinate. Both enclaves usually need to be online and able to communicate. The enclaves then establish a secure channel using remote attestation and migrate secrets or other unsealed data over the secure channel.

Another approach is to delegate key distribution to a shared enclave service. This is what oasis does with their oasis > Key Manager Enclaves. Here compute enclaves store their machine-specific keys with the key manager enclaves, along with a policy for which other enclaves can read the keys. This way enclaves don't need to communicate with or attest each other directly and can instead just query the local key manager enclave.

Examples #

oasis #

See: oasis > Sealing

intel/linux-sgx #

https://github.com/intel/linux-sgx/blob/26c458905b72e66db7ac1feae04b43461ce1b76f/sdk/tseal/tSeal.cpp

extern "C" sgx_status_t sgx_seal_data_ex(
	const uint16_t key_policy,
	const sgx_attributes_t attribute_mask,
	const sgx_misc_select_t misc_mask,
	const uint32_t additional_MACtext_length,
	const uint8_t *p_additional_MACtext,
	const uint32_t text2encrypt_length,
	const uint8_t *p_text2encrypt,
	const uint32_t sealed_data_size,
	sgx_sealed_data_t *p_sealed_data
) {
    sgx_status_t err = SGX_ERROR_UNEXPECTED;
    sgx_key_id_t keyID;
    sgx_key_request_t tmp_key_request;
    uint8_t payload_iv[SGX_SEAL_IV_SIZE];
    memset(&payload_iv, 0, sizeof(payload_iv));


    uint32_t sealedDataSize = sgx_calc_sealed_data_size(additional_MACtext_length,text2encrypt_length);
    // Check for overflow
    if (sealedDataSize == UINT32_MAX)
    {
        return SGX_ERROR_INVALID_PARAMETER;
    }

    //
    // Check parameters
    //
    // check key_request->key_policy:
    //  1. Reserved bits are not set
    //  2. Either MRENCLAVE or MRSIGNER is set
    if ((key_policy & ~(SGX_KEYPOLICY_MRENCLAVE | SGX_KEYPOLICY_MRSIGNER | (KEY_POLICY_KSS) | SGX_KEYPOLICY_NOISVPRODID)) ||
        (key_policy & (SGX_KEYPOLICY_MRENCLAVE | SGX_KEYPOLICY_MRSIGNER)) == 0)
    {
        return SGX_ERROR_INVALID_PARAMETER;
    }
    if ( !(attribute_mask.flags & SGX_FLAGS_INITTED)
      || !(attribute_mask.flags & SGX_FLAGS_DEBUG) )
    {
        return SGX_ERROR_INVALID_PARAMETER;
    }
    if ((additional_MACtext_length > 0) && (p_additional_MACtext == NULL))
    {
        return SGX_ERROR_INVALID_PARAMETER;
    }
    if ((text2encrypt_length == 0) || (p_text2encrypt == NULL) || (!sgx_is_within_enclave(p_text2encrypt,text2encrypt_length)))
    {
        return SGX_ERROR_INVALID_PARAMETER;
    }
    // Ensure sealed data blob is within an enclave during the sealing process
    if ((p_sealed_data == NULL) || (!sgx_is_within_enclave(p_sealed_data,sealed_data_size)))
    {
        return SGX_ERROR_INVALID_PARAMETER;
    }
    // Ensure aad data does not cross enclave boundary
    if ((additional_MACtext_length > 0) &&
        (!(sgx_is_within_enclave(p_additional_MACtext,additional_MACtext_length) || sgx_is_outside_enclave(p_additional_MACtext, additional_MACtext_length))))
    {
        return SGX_ERROR_INVALID_PARAMETER;
    }
    if (sealedDataSize != sealed_data_size)
    {
        return SGX_ERROR_INVALID_PARAMETER;
    }
    memset(p_sealed_data, 0, sealedDataSize);
    memset(&keyID, 0, sizeof(sgx_key_id_t));
    memset(&tmp_key_request, 0, sizeof(sgx_key_request_t));

    // Get the report to obtain isv_svn and cpu_svn
    const sgx_report_t *report = sgx_self_report();

    // Get a random number to populate the key_id of the key_request
    err = sgx_read_rand(reinterpret_cast<uint8_t *>(&keyID), sizeof(sgx_key_id_t));
    if (err != SGX_SUCCESS)
    {
        goto clear_return;
    }

    memcpy(&(tmp_key_request.cpu_svn), &(report->body.cpu_svn), sizeof(sgx_cpu_svn_t));
    memcpy(&(tmp_key_request.isv_svn), &(report->body.isv_svn), sizeof(sgx_isv_svn_t));
    tmp_key_request.config_svn = report->body.config_svn;
    tmp_key_request.key_name = SGX_KEYSELECT_SEAL;
    tmp_key_request.key_policy = key_policy;
    tmp_key_request.attribute_mask.flags = attribute_mask.flags;
    tmp_key_request.attribute_mask.xfrm = attribute_mask.xfrm;
    memcpy(&(tmp_key_request.key_id), &keyID, sizeof(sgx_key_id_t));
    tmp_key_request.misc_mask = misc_mask;

    err = random_stack_advance<0x400>(sgx_seal_data_iv, additional_MACtext_length, p_additional_MACtext,
        text2encrypt_length, p_text2encrypt, payload_iv, &tmp_key_request, p_sealed_data);

    if (err == SGX_SUCCESS)
    {
        // Copy data from the temporary key request buffer to the sealed data blob
        memcpy(&(p_sealed_data->key_request), &tmp_key_request, sizeof(sgx_key_request_t));
    }
clear_return:
    // Clear temp state
    memset_s(&keyID, sizeof(sgx_key_id_t), 0, sizeof(sgx_key_id_t));
    return err;
}

openenclave #

https://github.com/openenclave/openenclave/blob/master/enclave/core/seal.c#L93


oe_result_t oe_seal(
    const oe_uuid_t* plugin_id,
    const oe_seal_setting_t* settings,
    size_t settings_count,
    const uint8_t* plaintext,
    size_t plaintext_size,
    const uint8_t* additional_data,
    size_t additional_data_size,
    uint8_t** blob,
    size_t* blob_size)
{
    oe_result_t result;
    const oe_seal_plugin_definition_t* plugin;
    size_t i;

    if (blob == NULL || blob_size == NULL)
        return OE_INVALID_PARAMETER;

    *blob = NULL;
    *blob_size = 0;

    if ((settings == NULL) != (settings_count == 0) ||
        !oe_is_within_enclave(settings, settings_count * sizeof(*settings)) ||
        (plaintext == NULL) != (plaintext_size == 0) ||
        !oe_is_within_enclave(plaintext, plaintext_size) ||
        (additional_data == NULL) != (additional_data_size == 0) ||
        ((additional_data != NULL) &&
         !oe_is_within_enclave(additional_data, additional_data_size)))
        return OE_INVALID_PARAMETER;

    for (i = 0; i < settings_count; ++i)
    {
        if (settings[i].type < 0 || settings[i].type >= OE_SEAL_SETTING_MAX)
            return OE_INVALID_PARAMETER;
        if (settings[i].size > 0 &&
            !oe_is_within_enclave(settings[i].value.p, settings[i].size))
            return OE_INVALID_PARAMETER;
    }

    if (oe_mutex_lock(&_plugins_lock) != OE_OK)
        return OE_UNEXPECTED;

    plugin = _find_plugin(plugin_id);
    if (plugin == NULL)
        OE_RAISE(OE_NOT_FOUND);

    result = plugin->seal(
        settings,
        settings_count,
        plaintext,
        plaintext_size,
        additional_data,
        additional_data_size,
        blob,
        blob_size);

done:
    oe_mutex_unlock(&_plugins_lock);
    return result;
}

https://github.com/openenclave/openenclave/blob/master/include/openenclave/bits/sgx/sgxtypes.h#L1073

/* Enclave MISCSELECT Flags Bit Masks, additional information to an SSA frame */
/* If set, then the enclave page fault and general protection exception are
 * reported */
#define SGX_MISC_FLAGS_PF_GP_EXIT_INFO 0x0000000000000001ULL

/* Enclave Flags Bit Masks */
/* If set, then the enclave is initialized */
#define SGX_FLAGS_INITTED 0x0000000000000001ULL
/* If set, then the enclave is debug */
#define SGX_FLAGS_DEBUG 0x0000000000000002ULL
/* If set, then the enclave is 64 bit */
#define SGX_FLAGS_MODE64BIT 0x0000000000000004ULL
/* If set, then the enclave has access to provision key */
#define SGX_FLAGS_PROVISION_KEY 0x0000000000000010ULL
/* If set, then the enclave has access to EINITTOKEN key */
#define SGX_FLAGS_EINITTOKEN_KEY 0x0000000000000020ULL
#define SGX_FLAGS_RESERVED                                         \
    (~(SGX_FLAGS_INITTED | SGX_FLAGS_DEBUG | SGX_FLAGS_MODE64BIT | \
       SGX_FLAGS_PROVISION_KEY | SGX_FLAGS_EINITTOKEN_KEY))

/* Set the bits which have no security implications to 0 for sealed data
 migration */
/* Bits which have no security implications in attributes.flags:
 *    Reserved bit[55:6]  - 0xFFFFFFFFFFFFC0ULL
 *    SGX_FLAGS_MODE64BIT
 *    SGX_FLAGS_PROVISION_KEY
 *    SGX_FLAGS_EINITTOKEN_KEY */
#define SGX_FLAGS_NON_SECURITY_BITS                                        \
    (0xFFFFFFFFFFFFC0ULL | SGX_FLAGS_MODE64BIT | SGX_FLAGS_PROVISION_KEY | \
     SGX_FLAGS_EINITTOKEN_KEY)

/* bit[27:0]: have no security implications */
#define SGX_MISC_NON_SECURITY_BITS 0x0FFFFFFFU

/* OE seal key default flag masks*/
#define OE_SEALKEY_DEFAULT_FLAGSMASK (~SGX_FLAGS_NON_SECURITY_BITS)
#define OE_SEALKEY_DEFAULT_MISCMASK (~SGX_MISC_NON_SECURITY_BITS)
#define OE_SEALKEY_DEFAULT_XFRMMASK (0X0ULL)

https://github.com/openenclave/openenclave/blob/master/enclave/sealing/sgx/seal_gcmaes.c

static oe_result_t _init_key_request(sgx_key_request_t* keyrequest)
{
    sgx_report_t report = {{{0}}};

    oe_result_t result = OE_OK;

    OE_CHECK(
        oe_memset_s(keyrequest, sizeof(*keyrequest), 0, sizeof(*keyrequest)));

    OE_CHECK(sgx_create_report(NULL, 0, NULL, 0, &report));
    OE_CHECK(oe_memcpy_s(
        &keyrequest->cpu_svn,
        sizeof(keyrequest->cpu_svn),
        &report.body.cpusvn,
        sizeof(report.body.cpusvn)));

    keyrequest->key_name = SGX_KEYSELECT_SEAL;
    keyrequest->key_policy = SGX_KEYPOLICY_MRSIGNER;
    keyrequest->isv_svn = report.body.isvsvn;
    keyrequest->attribute_mask.flags = OE_SEALKEY_DEFAULT_FLAGSMASK;
    keyrequest->attribute_mask.xfrm = OE_SEALKEY_DEFAULT_XFRMMASK;
    keyrequest->misc_attribute_mask = OE_SEALKEY_DEFAULT_MISCMASK;

done:
    return result;
}

static oe_result_t _seal(
    const oe_seal_setting_t* settings,
    size_t settings_count,
    const uint8_t* plaintext,
    size_t plaintext_size,
    const uint8_t* additional_data,
    size_t additional_data_size,
    uint8_t** blob,
    size_t* blob_size)
{
    oe_result_t result = OE_OK;
    struct _sealed_blob_header* header;
    uint8_t* key = NULL;
    size_t size;
    oe_entropy_kind_t kind;
    size_t i;

    size = sizeof(*header);
    if (plaintext_size > OE_UINT32_MAX - size)
        return OE_INTEGER_OVERFLOW;
    size += plaintext_size;
    if (additional_data_size > OE_UINT32_MAX - size)
        return OE_INTEGER_OVERFLOW;
    // Please note that blob will NOT include additional_data

    *blob_size = size;
    *blob = (uint8_t*)oe_calloc(1, size);
    if (*blob == NULL)
        return OE_OUT_OF_MEMORY;

    header = (struct _sealed_blob_header*)*blob;
    OE_CHECK(_init_key_request(&header->keyrequest));
    OE_CHECK(oe_get_entropy(
        header->keyrequest.key_id, sizeof(header->keyrequest.key_id), &kind));
    header->ciphertext_size = (uint32_t)plaintext_size;
    header->total_size = (uint32_t)(plaintext_size + additional_data_size);
    OE_CHECK(oe_get_entropy(header->iv, sizeof(header->iv), &kind));

    for (i = 0; i < settings_count; ++i)
        switch (settings[i].type)
        {
            case OE_SEAL_SETTING_POLICY:
                switch (settings[i].value.w)
                {
                    case OE_SEAL_POLICY_UNIQUE:
                        header->keyrequest.key_policy = SGX_KEYPOLICY_MRENCLAVE;
                        break;
                    case OE_SEAL_POLICY_PRODUCT:
                        header->keyrequest.key_policy = SGX_KEYPOLICY_MRSIGNER;
                        break;
                    default:
                        OE_RAISE(OE_INVALID_PARAMETER);
                }
                break;

            case OE_SEAL_SETTING_IV:
                if (settings[i].size != sizeof(header->iv))
                    OE_RAISE(OE_INVALID_PARAMETER);
                else
                    OE_CHECK(oe_memcpy_s(
                        header->iv,
                        sizeof(header->iv),
                        settings[i].value.p,
                        settings[i].size));
                break;

            case OE_SEAL_SETTING_SGX_KEYNAME:
                header->keyrequest.key_name = settings[i].value.w;
                break;

            case OE_SEAL_SETTING_SGX_ISVSVN:
                header->keyrequest.isv_svn = settings[i].value.w;
                break;

            case OE_SEAL_SETTING_SGX_CPUSVN:
                if (settings[i].size != sizeof(header->keyrequest.cpu_svn))
                    OE_RAISE(OE_INVALID_PARAMETER);
                OE_CHECK(oe_memcpy_s(
                    header->keyrequest.cpu_svn,
                    sizeof(header->keyrequest.cpu_svn),
                    settings[i].value.p,
                    settings[i].size));
                break;

            case OE_SEAL_SETTING_SGX_FLAGSMASK:
                header->keyrequest.attribute_mask.flags = settings[i].value.q;
                break;

            case OE_SEAL_SETTING_SGX_XFRMMASK:
                header->keyrequest.attribute_mask.xfrm = settings[i].value.q;
                break;

            case OE_SEAL_SETTING_SGX_MISCMASK:
                header->keyrequest.misc_attribute_mask = settings[i].value.d;
                break;

            case OE_SEAL_SETTING_SGX_CONFIGSVN:
                header->keyrequest.config_svn = settings[i].value.w;
                break;

            case OE_SEAL_SETTING_ADDITIONAL_CONTEXT:
                // Custom OE_SEAL_SETTING_ADDITIONAL_CONTEXT not supported

            case OE_SEAL_SETTING_SGX_CET_ATTRIBUTES_MASK:
                // OE_SEAL_SETTING_SGX_CET_ATTRIBUTES_MASK not supported

            default:
                OE_RAISE(OE_UNSUPPORTED);
        }

    OE_CHECK(oe_get_seal_key(
        (uint8_t*)&header->keyrequest,
        sizeof(header->keyrequest),
        &key,
        &size));

    OE_STATIC_ASSERT(sizeof(header->tag) >= 16);
    OE_CHECK(oe_aes_gcm_encrypt(
        key,
        size,
        header->iv,
        sizeof(header->iv),
        additional_data,
        additional_data_size,
        plaintext,
        plaintext_size,
        (uint8_t*)(header + 1),
        plaintext_size,
        header->tag));

done:
    oe_free_key(key, size, NULL, 0);

    if (result != OE_OK)
    {
        oe_free(*blob);
        *blob = NULL;
    }

    return result;
}