Code Sample: Gateway Key Provisioning and Secure Signing using Intel® Software Guard Extensions

File(s):Download version 1.0
License:BSD-3-Clause
Optimized for... 
OS:Linux* with Intel® SGX for Linux* Driver
Hardware:Platforms based on 6th generation (or later) Intel® Core™ processors with BIOS support enabled for Intel® SGX
Software:
(Programming Language, tool, IDE, Framework)
C (ISO C99)
Prerequisites:Intel® SGX Driver, PSW (Platform Software), and SDK

Introduction

One issue that may arise in IoT scenarios involving sensor data is the trustworthiness of the data.  For example, are the sensor readings authentic and integrity-protected?  One way to provide integrity protection and prove authenticity is to use an IoT gateway at the edge to digitally sign the captured data, but then the validity of the digital signatures become dependent upon the uniqueness and confidentiality of the private key.  This code sample demonstrates the use of Intel® Software Guard Extensions (Intel® SGX) to protect the private key of an asymmetric elliptic curve keypair used to sign sensor data collected at the edge.

Prerequisites

This code sample requires Intel Software Guard Extensions, which in turn requires support from the hardware, firmware, operating system, and application libraries. The required software stacks should be installed and verified working using the bundle sample applications prior to attempting to build and run this code sample.

  • Hardware:
    • Non-production usage: This sample can be compiled and run in simulator mode even if the underlying hardware does not support Intel SGX. This is to support various development workflows.
    • Production usage: Intel SGX is available on platforms based on 6th generation (or later) Intel® Core™ processors. Consult the Intel product specification site to look up your particular processor. In particular, determine whether your process supports Intel SGX 1.x or Intel SGX 2.x, as the required kernel driver is version-specific.
  • Firmware: Intel SGX must be supported by the firmware / boot loader, and must be set to "Enabled" or "Software Controlled" state. ("Software Controlled" mode will require a reboot to enable Intel SGX.)
  • Operating System: Linux with in-tree or out-of-tree Intel SGX device driver,
  • Middleware: Intel SGX PSW (platform software). PSW includes the architectural enclave service manager (AESM), and the architectural enclaves themselves.  PSW is required at runtime as well as for development.
  • Development Tools: The code sample presumes that you have downloaded and installed the Intel® Software Guard Extensions SDK for Linux*. Please verify that your Linux* distribution is on the supported operating system list published in the SDK release notes. This example additionally requires C/C++ language build tools and OpenSSL development libraries to be installed. Development tools are not required at runtime.

Baseline: An OpenSSL Implementation

The baseline implementation of gateway key provisioning and secure signing is built with OpenSSL. The run_demo_openssl.sh script performs the following actions:

  1. Creates an elliptic curve key pair and saves it to disk.
  2. Simulates uploading the public key to a cloud.
  3. Signs some "sensor data."
  4. Simulates uploading the sensor data and signature to a cloud.
  5. Simulated cloud verifies the sensor data and detached signature.

Running the baseline

Run the following command to execute the baseline sample:

./run_demo_openssl.sh

The output should resemble the output below:

Provisioning private elliptic curve key...done
Generated public key:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoj59k3GsdyL6dP40BRiQ0Tz7CNBp
1LxQPHmbOkZW7j0L57bSQ5+CuSL8MJ7uOGSRnjkBwRWGi6t3Viawihtrbw==
-----END PUBLIC KEY-----
Registering publc key with server...done
Signing sensor data...done
Transmitting signature to server...done
Verifying signature:
Verified OK

Looking Inside the Baseline Implementation

At its core, the baseline implementation issues the following four OpenSSL commands.  These commands generate an EC keypair, an EC public key, sign some data with the EC private key, and verify the signature on the sensor data. (For simplicity, there is no actual cloud involved.)

openssl ecparam -name prime256v1 -genkey -out secp256r1-key.pem
openssl ec -in secp256r1-key.pem -pubout -out secp256r1.pem
openssl dgst -sign secp256r1-key.pem -out Sensor_Data.signature Sensor_Data
openssl dgst -verify secp256r1.pem -signature Sensor_Data.signature Sensor_Data

Critique of the Baseline Implementation

While the baseline implementation does the job, it suffers from well known-known weaknesses:

Specifically, the primary asset—the sensor data—is protected by a secondary asset—the EC private key—that is inadequately protected from theft. The EC private key can be stolen off of disk by online or offline attacks, and it can also be stolen out of memory while loaded into OpenSSL to sign the sensor data. These threats undermine the security objective of proving to the cloud that the sensor data is authentic.

Improving Upon the Baseline Using Intel SGX

Intel SGX provides two capabilities that are relevant to the sensor data signing use case:

  • Trusted execution environment: Intel SGX provides a trusted execution environment where the private key can be used safely to perform digital signing of the sensor data. This will protect the private key from both ring 0 though ring 3 memory scanning attacks that seek to steal the private key while it is in use.
  • Data sealing: Data sealing is used to encrypt enclave data using an encryption key that is derived from the CPU. Our example will use a MRSIGNER data sealing policy that will allow the data to be decrypted on the same system by any enclave signed with the same key. (This is useful, for example, to support software updates to the enclave.) This will protect the confidentiality and integrity of the private key at-rest. However, it does not stop any software from deleting it from disk.

Compiling the Sample

First, ensure that the prerequisites have been met.  Then, source the "environment" file in the SGX SDK and run the top-level makefile:

. $HOME/sgxsdk/environment
make

Alternatively, if you want to compile in simulator mode (for example, if you are developing on a virtual machine), pass the SGX_MODE=sim flag to make:

source $HOME/sgxsdk/environment
make SGX_MODE=sim

A lot of output will scroll by, but the sample takes less than a second to compile.

Running the Sample

Run the following command to execute the sample:

./run_demo_sgx.sh

The output should resemble the following:

Provisioning private elliptic curve key:
[GatewayApp]: Creating enclave
[GatewayApp]: Querying enclave for buffer sizes

TrustedApp: Sizes for public key, sealed private key and signature calculated successfully.
[GatewayApp]: Allocating buffers
[GatewayApp]: Calling enclave to generate key material

TrustedApp: Key pair generated and private key was sealed. Sent the public key and sealed private key back.
[GatewayApp]: Saving enclave state
[GatewayApp]: Saving public key
[GatewayApp]: Destroying enclave
[GatewayApp]: Deallocating buffers
Key provisoning completed.
Generated public key:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEL0OnNGfIawRX/OxtEM1d3BbJUC+2
IAVGU8AfweCTNAGLNDWmoXUr4VK8MfpjjEXQc4WtIla2Yk0b9z9uRHHKlQ==
-----END PUBLIC KEY-----
Registering publc key with server...done
Signing sensor data:
[GatewayApp]: Creating enclave
[GatewayApp]: Querying enclave for buffer sizes

TrustedApp: Sizes for public key, sealed private key and signature calculated successfully.
[GatewayApp]: Allocating buffers
[GatewayApp]: Loading enclave state
[GatewayApp]: Loading input file
[GatewayApp]: Calling enclave to generate key material

TrustedApp: Received sensor data and the sealed private key.

TrustedApp: Unsealed the sealed private key, signed sensor data with this private key and then, sent the signature back.
[GatewayApp]: Saving enclave state
[GatewayApp]: Destroying enclave
[GatewayApp]: Deallocating buffers
Sensor data signed.
Transmitting signature to server...done
Verifying signature:
Verified OK

The output of the SGX sample substantially resembles the output of the OpenSSL-based sample. What is different is that the SGX example logs its progress as execution flows through the untrusted host application as well as the trusted enclave side of the application.

In both cases, verification of the sensor data is done using the "openssl dgst -verify" command.  Digital signature verification does not involve use of secrets, and it is useful to show that SGX-based cryptography is compatible with standard open-source tools.

Recap

Presuming the implementation doesn't accidentally leak secrets (for example, by logging the key or through side-channel attacks), moving key generation and signing to the SGX enclave largely mitigates the theft of the EC private key. An entire tree of weaknesses has been eliminated:

Note that we have not eliminated all weaknesses.

In the Intel SGX implementation, the EC private key from the baseline example has been replaced with an encrypted key blob that is bound to the SGX enclave on the device. It is true that the keyblob—if stolen—cannot be used on any other machine other than the one that generated it.  However, this does not preclude an attacker from simply deleting the keyblob and causing a denial of service against the gateway.  Techniques such as proper file system permissions, secure boot, and full disk encryption could be used to mitigate attacks that seek to cause a denial of service by deleting the encrypted keyblob—a discussion of these techniques is beyond the scope of this article.

The remainder of this article describes the file organization of the sample and details about the implementation.

How the Sample is Organized

The sample is broken into three subdirectories, each with a particular function:

Host Application (app)

The host application is an untrusted application responsible for the majority of the application processing and preparation of the enclave and invoking functions within it. The sample application provided either runs the enclave in key generation mode or runs it in signing mode to sign sensor data.

Trusted Application (enclave)

The trusted application is the "trusted" part of the application that runs in the SGX enclave. The enclave consists of functions that are invoked through the SGX ECALL mechanism. There are two primary functions of the sample enclave:

  • Key generation: This is a one-time event that causes the enclave to generate an elliptic curve key pair inside of the enclave. The private key of this key pair is additionally sealed using AES-GCM algorithm and a unique key which is derived from the silicon and the enclave’s SIGNER measurement register. It can only be unsealed again in the same enclave on the same machine that created it.
  • Signing: The host application can request the enclave to perform signing of sensor data using the sealed key generated in key generation stage. The enclave first unseals the private key generated in the key generation call, and then uses it to calculate a digital signature on the sensor data that is then returned to the caller.

Interface Library (interface)

The interface library defines the inbound (ECALL) and outbound (OCALL) interfaces to the enclave. The interface is defined using Enclave Definition Language (EDL). The EDL is compiled into stub libraries that are linked into both the untrusted and trusted applications to facilitate procedure calls across the protection boundary.

Code Walkthrough

Provisioning

main()

The main provisioning flow is coded in app.c's main() function.  In pseudocode, it looks like this:

create_enclave(opt_enclave_path) &&
enclave_get_buffer_sizes() &&
allocate_buffers() &&
enclave_generate_key() &&
save_enclave_state(opt_statefile) &&
save_public_key(opt_public_key_file);

destroy_enclave();
cleanup_buffers();

An explanation of what each of these subroutines are doing:

  • create_enclave() loads the compiled enclave file using sgx_create_enclave()
  • enclave_get_buffer_sizes() makes an ECALL to the enclave to determine sizes of required output buffers.
  • allocate_buffers() allocates the output buffers.  For the provisioning flow, buffers are needed to return the sealed private key and the public key.
  • enclave_generate_key()  makes an ECALL to the enclave to generate a EC key pair, passing the previous allocated buffers.
  • save_enclave_state() persists the sealed EC private key to disk.
  • save_public_key() persists the EC public key to disk after converting it to PEM format.
  • destroy_enclave() terminates the enclave.
  • cleanup_buffers() releases the allocated buffers prior to program termination.

enclave_generate_key()

Key generation is done via an ECALL to the enclave. There are no input parameters, but three output parameters:

  • The return code from the enclave function
  • The public key
  • A sealed data buffer containing the private key.
    sgx_lasterr = ecall_key_gen_and_seal(enclave_id,
                                         &ecall_retval,
                                         (char *)public_key_buffer,
                                         public_key_buffer_size,
                                         (char *)sealed_data_buffer,
                                         sealed_data_buffer_size);

If the ECALL success, ecall_retval contains the return value of the enclave function.

ecall_key_gen_and_seal()

Key generation is done inside the enclave in ecall_key_gen_and_seal():

sgx_status_t ecall_key_gen_and_seal(char *pubkey, size_t pubkey_size, char *sealedprivkey, size_t sealedprivkey_size)
{
  // Step 1: Open Context.
  sgx_status_t ret = SGX_ERROR_UNEXPECTED;
  sgx_ecc_state_handle_t p_ecc_handle = NULL;

  if ((ret = sgx_ecc256_open_context(&p_ecc_handle)) != SGX_SUCCESS)
  {
    print("\nTrustedApp: sgx_ecc256_open_context() failed !\n");
    goto cleanup;
  }

  // Step 2: Create Key Pair.
  sgx_ec256_private_t p_private;
  if ((ret = sgx_ecc256_create_key_pair(&p_private, (sgx_ec256_public_t *)pubkey, p_ecc_handle)) != SGX_SUCCESS)
  {
    print("\nTrustedApp: sgx_ecc256_create_key_pair() failed !\n");
    goto cleanup;
  }

  // Step 3: Calculate sealed data size.
  if (sealedprivkey_size >= sgx_calc_sealed_data_size(0U, sizeof(p_private)))
  {
    if ((ret = sgx_seal_data(0U, NULL, sizeof(p_private), (uint8_t *)&p_private, sealedprivkey_size, (sgx_sealed_data_t *)sealedprivkey)) != SGX_SUCCESS)
    {
      print("\nTrustedApp: sgx_seal_data() failed !\n");
      goto cleanup;
    }
  }
  else
  {
    print("\nTrustedApp: Size allocated for sealedprivkey by untrusted app is less than the required size !\n");
    ret = SGX_ERROR_INVALID_PARAMETER;
    goto cleanup;
  }

  print("\nTrustedApp: Key pair generated and private key was sealed. Sent the public key and sealed private key back.\n");
  ret = SGX_SUCCESS;

cleanup:
  // Step 4: Close Context.
  if (p_ecc_handle != NULL)
  {
    sgx_ecc256_close_context(p_ecc_handle);
  }

  return ret;
}

The key steps in the above flow are:

The sgx_seal_data() by default uses the MRSIGNER data sealing policy that will allow the data to be decrypted on the same system by any enclave signed with the same key.  sgx_seal_data_ex() can be used to specify MRENCLAVE policy if the developer wishes to.  Please note that the MRENCLAVE policy does not allow for software updates as discussed earlier.

Signing

main()

The main signing flow is coded in app.c's main() function.  In pseudocode, it looks like this:

create_enclave(opt_enclave_path) &&
enclave_get_buffer_sizes() &&
allocate_buffers() &&
load_enclave_state(opt_statefile) &&
load_input_file(opt_input_file) &&
enclave_sign_data() &&
save_enclave_state(opt_statefile) &&
save_signature(opt_signature_file);

destroy_enclave();
cleanup_buffers();

An explanation of what each of these subroutines are doing:

  • create_enclave() loads the compiled enclave file using sgx_create_enclave()
  • enclave_get_buffer_sizes() makes an ECALL to the enclave to determine sizes of required output buffers.
  • allocate_buffers() allocates the output buffers.  For the provisioning flow, buffers are needed to return the sealed private key and the public key.
  • load_enclave_state()  reads the sealed EC private key on disk into a buffer.
  • load_input_file() reads the data to be sealed into a buffer.
  • enclave_sign_data() passes the sealed EC private key and input buffer to the enclave and receives a signature.
  • save_enclave_state() persists the sealed EC private key to disk (not strictly needed as signing does not change the state in this example).
  • save_signature() persists the returned signature to disk after DER-encoding the output.
  • destroy_enclave() terminates the enclave.
  • cleanup_buffers() releases the allocated buffers prior to program termination.

enclave_sign_data()

Key generation is done via an ECALL to the enclave. There are two input and two output parameters:

  • The input buffer (content to be signed)
  • The sealed data buffer (from the earlier keygen call)
  • The return code from the enclave function (output)
  • The signature response buffer.
    sgx_lasterr = ecall_unseal_and_sign(enclave_id,
                                        &ecall_retval,
                                        (uint8_t *)input_buffer,
                                        (uint32_t)input_buffer_size,
                                        (char *)sealed_data_buffer,
                                        sealed_data_buffer_size,
                                        (char *)signature_buffer,
                                        signature_buffer_size);

If the ECALL success, ecall_retval contains the return value of the enclave function.

ecall_unseal_and_sign()

Signing is also done entirely inside the enclave in the ecall_unsign_and_sign() function:

sgx_status_t ecall_unseal_and_sign(uint8_t *msg, uint32_t msg_size, char *sealed, size_t sealed_size, char *signature, size_t signature_size)
{
  sgx_status_t ret = SGX_ERROR_UNEXPECTED;
  sgx_ecc_state_handle_t p_ecc_handle = NULL;

  print("\nTrustedApp: Received sensor data and the sealed private key.\n");

  // Step 1: Calculate sealed/encrypted data length.
  uint32_t unsealed_data_size = sgx_get_encrypt_txt_len((const sgx_sealed_data_t *)sealed);
  uint8_t * const unsealed_data = (uint8_t *)malloc(unsealed_data_size); // Check malloc return;
  if (unsealed_data == NULL)
  {
    print("\nTrustedApp: malloc(unsealed_data_size) failed !\n");
    goto cleanup;
  }

  // Step 2: Unseal data.
  if ((ret = sgx_unseal_data((sgx_sealed_data_t *)sealed, NULL, NULL, unsealed_data, &unsealed_data_size)) != SGX_SUCCESS)
  {
    print("\nTrustedApp: sgx_unseal_data() failed !\n");
    goto cleanup;
  }

  // Step 3: Open Context.
  if ((ret = sgx_ecc256_open_context(&p_ecc_handle)) != SGX_SUCCESS)
  {
    print("\nTrustedApp: sgx_ecc256_open_context() failed !\n");
    goto cleanup;
  }

  // Step 4: Perform ECDSA Signing.
  if ((ret = sgx_ecdsa_sign(msg, msg_size, (sgx_ec256_private_t *)unsealed_data, (sgx_ec256_signature_t *)signature, p_ecc_handle)) != SGX_SUCCESS)
  {
    print("\nTrustedApp: sgx_ecdsa_sign() failed !\n");
    goto cleanup;
  }

  print("\nTrustedApp: Unsealed the sealed private key, signed sensor data with this private key and then, sent the signature back.\n");

  ret = SGX_SUCCESS;

cleanup:
  // Step 5: Close Context, release memory
  if (p_ecc_handle != NULL)
  {
    sgx_ecc256_close_context(p_ecc_handle);
  }
  if (unsealed_data != NULL)
  {
    free(unsealed_data);
  }

  return ret;
}

The key steps in the above flow are:

Note that the result of the signing operation produces a raw signature. The next section explains how the raw key and raw signature are transformed into a portable representation that can be processed with open source tools such as OpenSSL.

Signature Verification

Because digital signature verification does not require secrets and to avoid the need to write a separate C program to do signature verification, this sample uses the "openssl dgst -verify" command to perform signature verification on the sensor data. This decision means that the host application must output the public key and digital signature in PEM- and DER-encoded forms, respectively.  This is interesting because the cryptographic primitives in the enclave work on raw bytes, and conversion is necessary to put the data into a form that can be consumed by the openssl command line.

Preparing the public key

The most efficient representation of the EC public key to DER- and PEM-encode the following ASN.1 sequence, where algorithm is id-ecPublickey, and parameters is secp256r1.

SubjectPublicKeyInfo  ::=  SEQUENCE  {
       algorithm         AlgorithmIdentifier,
       subjectPublicKey  BIT STRING
}

AlgorithmIdentifier  ::=  SEQUENCE  {
  algorithm   OBJECT IDENTIFIER,
  parameters  ANY DEFINED BY algorithm OPTIONAL
}

id-ecPublicKey OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) ansi-X9-62(10045) keyType(2) 1 }

secp256r1 OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) ansi-X9-62(10045) curves(3) prime(1) 7 }

The algorithm for doing so is implemented in save_public_key().  This function is broken up into several utility functions, but the overall algorithm, is pseduocode, is as follows:

EC_KEY *key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY_set_asn1_flag(key, OPENSSL_EC_NAMED_CURVE);
bn_x = BN_bin2bn(ENDIAN_SWAP(x));
bn_y = BN_bin2bn(ENDIAN_SWAP(y));
EC_KEY_set_public_key_affine_coordinates(key, bn_x, bn_y)
PEM_write_EC_PUBKEY(file, key);

The key points to note about the above algorithm are:

  • One must set the OPENSSL_EC_NAMED_CURVE parameter to select the proper output encoding.
  • It is necessary to endian-swap the x and y coordinates because the SGX crypto library computes in little-endian form. (openssl libraries use network-byte-order/big-endian form.)

The resulting PEM file can be read and processed by the standard openssl utilities.

Preparing the signature

The signature value is easier to prepare.  An ECDSA signature consists of the pair (r, s).  This must be DER-encoded using the following ASN.1 syntax:

ECDSA-Sig-Value ::= SEQUENCE {
  r  INTEGER,
  s  INTEGER
}

The algorithm for doing so is implemented in save_signature().  The overall algorithm, is pseduocode, is as follows:

/* signature_buffer contains r and s as concatenated 32-byte values */
ecdsa_sig = ECDSA_SIG_new();
ecdsa_sig->r = ENDIAN_SWAP(signature_buffer, 32);
ecdsa_sig->s = ENDIAN_SWAP(signature_buffer+32, 32);
i2d_ECDSA_SIG(ecdsa_sig, &sig_buffer);
/* Write sig_buffer to disk in binary form */
ECDSA_SIG_free(ecdsa_sig); 

The resulting DER-encoded binary file can be read and processed by the standard openssl utilities.

Verifying the signature

After that, verifying the signature on the sensor data is simple:

openssl dgst -verify secp256r1.pem -signature Sensor_Data.signature Sensor_Data

How the Sample is Built

The sample is built using a set of hierarchical makefiles. To aid in understanding the makefile contents, we will first review some makefile concepts:

  • rule is an entry in a makefile that describes the commands needed to create a target from a set of prerequisites. Make will build a target's prerequisites prior to building the target.
  • An implicit rule is a rule that matches file naming patterns. Make comes with an extensive collection of built-in rules for compiling C programs. These are written as implicit rules.
  • There are a set of predefined makefile variables that have special meaning for the built-in rules:
    • CPPFLAGS: These are options that are passed to the C preprocessor.  This variable is used for preprocessor defines and specifying include paths. 
    • CFLAGS: Specifies compiler options passed to the C and C++ compilers.  This variable is used to enable or disable compiler features and control code generation.
    • CXXFLAGS: Specifies compiler options passed to the C++ compiler. This variable is similar to CFLAGS, but these options are passed only to the C++ compiler.
    • LDFLAGS: Specifies flags passed to the linker when producing executables or shared libraries to control how the final image is linked.
    • LDLIBS: Specifies link libraries that the linker uses for undefined symbol resolution. Order is important.

The primary reason for a hierarchical make structure is that the host application and the enclave itself must be compiled using conflicting compiler and linker options. Be aware of this point if you later write your own non-recursive makefiles.

Enclave Makefile

The enclave makefile starts including two files that pre-define useful makefile variables and a set of implicit rules to help us build the enclave.

include ../common/common.mk
include ../common/rules.mk

The second section of this makefile sets compiler and linker options to the pre-defined values imported above. It tells the C preprocessor to look for stub header files and generated stub code in the interface folder, slightly modifies the compiler warning flags, and set other local options.  It tells the linker to statically link the ECALL stubs into the final executable.

CFLAGS += $(SGX_ENCLAVE_CFLAGS) -std=c99 -Wno-unused-parameter
CXXFLAGS += $(SGX_ENCLAVE_CXXFLAGS)
CPPFLAGS += $(SGX_ENCLAVE_CPPFLAGS) -I../interface
LDFLAGS += $(SGX_ENCLAVE_LDFLAGS)
LDLIBS += -L ../interface -lenclave_stub_t $(SGX_ENCLAVE_LDLIBS)

The last section of the makefile leverages the pre-defined implicit rules to compile and link the enclave. Other than the command to dynamically generate the enclave signing key, this section of the makefile only specifies dependencies.  The ultimate target of this makefile is enclave.signed.so which is constructed by signing enclave.unsigned.so with enclave.key.pem.  The enclave.unsigned.so binary is constructed using the specified object code, and the link libraries defined above. The reference to libenclave_stub_t.a triggers the implicit rule to create the trusted-side interface stubs.

all: ../interface/libenclave_stub_t.a enclave.signed.so

../interface/libenclave_stub_t.a: ../interface/enclave.edl

enclave.unsigned.so: calcbuffsize.o keygen.o print.o sign.o

enclave.signed.so: enclave.key.pem

enclave.key.pem:
	openssl genrsa -3 -out $@ 3072

An SGX enclave owes many of its security properties to a minimal trusted computing base (TCB).  One implication of this is that the SGX enclave must be entirely self-contained.  All of the code running in the enclave must be part of the enclave binary.  To achieve this, the code must be linked as position-independent executable (there is no runtime linker/loader), certain code re-writing features must be disabled (-fno-builtin), and the code cannot reference the standard C runtime library.  Only calls to the trusted SGX runtime and the trusted C/C++ runtime libraries are permitted.

SGX_ENCLAVE_CFLAGS := $(SGX_COMMON_CFLAGS) -nostdinc -fvisibility=hidden -fpie -ffunction-sections -fdata-sections -fno-builtin
SGX_ENCLAVE_CPPFLAGS := -I$(SGX_SDK)/include -I$(SGX_SDK)/include/tlibc -I$(SGX_SDK)/include/libcxx

The linker options and libraries are equally interesting. The linker options below disable linking of the standard C runtime and the normal entrypoint code that exists in a user-mode executable. The entire contents of the trusted runtime services (TRTS) library are included into the enclave, and referenced functions from the trusted C/C++ runtime and crypto libraries are pulled in as needed. 

SGX_ENCLAVE_LDFLAGS := \
    -nostdlib \
	-nodefaultlibs \
	-nostartfiles \
	-Wl,-Bstatic \
	-Wl,-Bsymbolic \
	-Wl,--no-undefined \
	-Wl,-pie,-eenclave_entry \
	-Wl,--export-dynamic \
	-Wl,--defsym,__ImageBase=0 \
	-Wl,--gc-sections \
	-Wl,--version-script=enclave.lds

SGX_ENCLAVE_LDLIBS := -L$(SGX_LIBRARY_PATH) \
	-Wl,--whole-archive -l$(SGX_TRTS_LIB) -Wl,--no-whole-archive \
	-Wl,--start-group -lsgx_tstdc -lsgx_tcxx -lsgx_tcrypto -l$(SGX_TSERVICE_LIB) -Wl,--end-group

The final result is a standalone binary that more resembles a firmware image than a traditional Unix* shared library.

Application Makefile

The host application makefile includes the standard boilerplate header.

include ../common/common.mk
include ../common/rules.mk

The compiler and linker flags use a set of recommend flags for host (untrusted) applications.  Other than that, this makefile produces a normal executable.  LDLIBS includes the stub code for the untrusted side of the enclave, and because this sample calls functions in the OpenSSL library (to facilitate DER-encoding the enclave output), those libraries are linked as well.

CFLAGS += $(SGX_HOST_CFLAGS) -std=c99
CXXFLAGS += $(SGX_HOST_CXXFLAGS)
CPPFLAGS += $(SGX_HOST_CPPFLAGS) -I../interface
LDFLAGS += $(SGX_HOST_LDFLAGS)
LDLIBS += -L ../interface -lenclave_stub_u $(SGX_HOST_LDLIBS) -lcrypto -ldl

The targets section of the makefile triggers an implicit rule to create the untrusted interface stubs, and then specifies which .o files comprise the executable. 

all: ../interface/libenclave_stub_u.a app

../interface/libenclave_stub_u.a: ../interface/enclave.edl

app: app.o globals.o ocall_print_string.o decode_sgx_status.o enclave.o buffers.o enclave_state.o endianswap.o readfile.o ecall_buffer.o keygen.o sign.o open_file.o

The predefined SGX_HOST_LDLIBS include the untrusted host support libraries. Shared library linkage is used by default.

SGX_HOST_LDFLAGS :=
SGX_HOST_LDLIBS := -L$(SGX_LIBRARY_PATH) -l$(SGX_URTS_LIB) -l$(SGX_UAE_SERVICE_LIB) -lpthread

Conclusion

Intel SGX provides a trusted execution environment (TEE) with performant cryptography and built-in support for key generation and for sealing of private data to the enclave. These capabilities can be used on an Intel® IoT Gateway to provide secure key generation and secure signing of IoT sensor data since both of these happen within the enclave. The resulting digitally-signed sensor data can be sent to the cloud and processed and verified using readily-available open-source tools.

Disclaimer

This is only a sample. When using this sample code as a baseline for a real use-case, please take into consideration the following points.  This list in no way strives to be a complete or exhaustive:

  • End-to-end authenticity and integrity protection: A real-world use case will need to secure the data path between the sensors and the gateway to prevent the IoT gateway from signing fake data.
  • Availability attacks: Neither Intel SGX nor the sample application protect against availability attacks such as deletion of sealed data blobs or the enclave itself.
  • Denial-of-service attacks: Neither Intel SGX nor the sample application prevent denial-of-service attacks such as overloading the enclave page cache (EPC) by launching the same enclave in a loop until the EPC is exhausted.
  • Key rotation: Consideration should be given to key rotation as suggested by section 5.3 of NIST SP800-57 which this code sample does not implement.
  • Other weaknesses: The Intel SGX trusted computing base (TCB) is the silicon package itself. When building a real-world use case be sure the threat model the entire system, not just the part that leverages Intel SGX.

To report a vulnerability with an Intel branded protect or technology such as Intel SGX please follow the Intel vulnerability reporting process.  To report a vulnerability in this open source code sample, please contact secure-opensource@01.org.

Feedback

Please contact iot-security-samples@intel.com if you have feedback or wish to report issues with this sample.

 

References

 

Export Classification: 5D002TSU

For more complete information about compiler optimizations, see our Optimization Notice.