Skip to content

sbellem/sgx-iot

Repository files navigation

Key Provisioning, Secure Signing, and Verifiable Remote Attestation using Intel® SGX

This is an adaptation of the original code sample found at https://software.intel.com/content/www/us/en/develop/articles/code-sample-gateway-key-provisioning-and-secure-signing-using-intel-software-guard.html. For convenience, the original code is also under the branch download.

The focus of this adaptation is on combining the key generation with remote attestation such that the public key is included in the report data of a remote attestation report. Moreover the code samples demonstrate how the remote attestation verification report can be used to verify the reported MRENCLAVE against the trusted source code, using a docker & nix -based toolchain, managed by a small tool named auditee.

Prerequisites

Quickstart

Set Environment Variables

Before starting a container, set the two following environment variables:

  • SGX_SPID - used to create a quote
  • IAS_PRIMARY_KEY - used to access Intel's Attestation Service (IAS)
export SGX_SPID=<your-SPID>
export IAS_PRIMARY_KEY=<your-ias-primary-key>

Alternatively, you can use place the environment variables in a .env file, under the root of the repository. NOTE that the IAS_PRIMARY_KEY MUST be kept secret. Consequently, the file .env is not tracked by git, as it MUST NOT be uploaded to a public repository, such as on GitHub.

# .env sample
SGX_SPID=<your-SPID>
IAS_PRIMARY_KEY=<your-ias-primary-key>

Run the demo

The demo creates an asymmetric elliptic curve keypair. It seals both the private key and public key. The public key is sealed to protect it against tampering as it is included in the attestation report from which a quote is generated, and can be verified by sending it to Intel's Attestation Service. Upon a successful verification of the quote, the client checks that the MRENCLAVE contained in the reoprt matches that of the trusted source code. That is, when re-building the enclave from source, its MRENCLAVE is the trusted reference. If the MRENCLAVE matches the client extracts the public key out of the report data, and uses that public key to verify the signature generated by the enclave. If the signature is valid, the client can trust the sensor data.

$ docker-compose run --rm sgxiot ./run_demo_sgxra.sh
Starting sgx-iot_aesm_1 ... done
Creating sgx-iot_sgxiot_run ... done

Provisioning elliptic curve key:
--------------------------------
[GatewayApp]: Creating enclave
[GatewayApp]: Querying enclave for buffer sizes
TrustedApp: Sizes for sealed 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 & public keys were sealed.
[GatewayApp]: Saving enclave state - sealed priv key                                     
[GatewayApp]: Saving enclave state - sealed pub key   
[GatewayApp]: Destroying enclave                                 
[GatewayApp]: Deallocating buffers                                                
Key provisoning completed.                                                                                                    
                                                                                                                        
Generating quote for remote attestation:                                                                        
----------------------------------------
[GatewayApp]: Creating enclave
...
[GatewayApp]: Loading sealed public key
[GatewayApp]: Calling enclave to generate report
[[TrustedApp]]: Received the sealed public key.
[[TrustedApp]]: Calling enclave to generate attestation report
[[TrustedApp]]: Unsealed the sealed public key and created a report containing the public key in the report data.
[GatewayApp]: Call sgx_calc_quote_size() ...                                     
[GatewayApp]: Call sgx_get_quote() ...                                          
[GatewayApp]: status of sgx_get_quote(): success                                                   
[GatewayApp]: Saving quote
[GatewayApp]: Destroying enclave
[GatewayApp]: Deallocating buffers
Quote generation completed.

Signing sensor data:
--------------------
[GatewayApp]: Creating enclave
[GatewayApp]: Querying enclave for buffer sizes

TrustedApp: Sizes for sealed 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]: Destroying enclave
[GatewayApp]: Deallocating buffers
Sensor data signed.

Verifying quote and signature:
------------------------------
Reading quote from file ...
AgAAAFsLAAALAAoAAAAAAFOrdeScwC/lZP1RWReIG+gNExGhsiX172fOAXRJJfh8CRH//wECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAHAAAAAAAAALlNRyDzfz6RxY9YGjS9izaemohHd+emMc7mTcUIrSBKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACDurDbNIc2BjZH7kIqysRdpX+aLwKCWa1DLNlHRrcw2wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0gQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9qwK5AwOKcmy2Lchz/epYRd7yQrsy3RJJY/6JMGFaTq0WNTQ9ZXeFnu0L7nIHhn2oRQ+bTigevKk05LmUF+DaqAIAAEKqmwCJOHIX3gWYoKOVETx2BQNEGuuLXiSz202lEBbYk7/ZhpJhFiM59pkdXlM/aP3TI8ZgIL1JSfmD2E6ykCRnqPF5ZFOrv5F7DJJDdLQo+6/7fU1VNHotTY3sTD8xJXH58YXeJi7cXClkC+5pYai7Ui+GXu1o71J2gRKLrkxSn8LwEiRmws7Lk5AfhsfSygQaKJb+VFs1uumf8BxAY1K6hPu8zcOWw4LUBDHeSrONhc185ydiBJjF0HgeD/ApfQImoMQnHLP9BVMqfGji6O0gGzsBbIp74VUxv9ZNkso4/AMY2VL++bxxoqUuBPB24DvBMptLDgOsjMcCZPiDpxToxa4Zcwd9zagVuKVzHuRrtGXbLduIL+mLqR0C4wifLCduF5paUI88EbWzRWgBAACykbFLOGPTItXa2xFLdmCX1u6QIB2jp7xpAzuFr9cuM3X7RBUA466QiAE13IFtYTl3n05tKKt+AEgkrieQzayqwHgAuB+tlVRv+6BIGwstWXjqPBDNW5Tpy2QS3VT04j31quav05wp1DtukA7y8Ef/sMbNMThySOlTmq9Gzsi0jjR0zIXFfiqGQAVIwiqfc3u2kcbwjqa1hgoBdJf9SaC/+UEWy5BMGlD9WGI9sZHqOsnJCKu29NhZnMbgvb2bjJJi3Jit9veRUdv+jNH7LsgKVrJMRoUGUIacHe6L912rQDWT/i9dklrcRz2gqhN01aL7W1zufQy6PZR2OA5eXzU7u9yIFkQREsO5110uZ5W3QyY5sPR4v9WoPmbATk40NDVzR6hDlMtu1toDMuw8zivO54G5DwvZtkOHJBscLqld4HLaZW6iROsBsOuZ/qTrqBZIbWvldWTe5TzjT6ogzsvW9IEgjQvuxPW5/m0g6X8ygv94HrUXTodl

Sending quote to Intel's Attestation Service for verification ...
Attestation verification succeeded!

IAS response is: 
{
    "id": "182475002551792100629007094006738923356",
    "timestamp": "2021-06-03T03:53:09.602034",
    "version": 4,
    "advisoryURL": "https://security-center.intel.com",
    "advisoryIDs": [
        "INTEL-SA-00161",
        "INTEL-SA-00381",
        "INTEL-SA-00389",
        "INTEL-SA-00320",
        "INTEL-SA-00329",
        "INTEL-SA-00220",
        "INTEL-SA-00270",
        "INTEL-SA-00293"
    ],
    "isvEnclaveQuoteStatus": "GROUP_OUT_OF_DATE",
    "platformInfoBlob": "150200650400090000111102040101070000000000000000000B00000B000000020000000000000B5B551B76CD1668F3420C901F6195DDDB3330A40FE4A31A3DE6C982CD5420B077AA4D1FB5C9099DA24A0953745D5FC67415A63FFAC6E73BE48191709DEE4F391F36",
    "isvEnclaveQuoteBody": "AgAAAFsLAAALAAoAAAAAAFOrdeScwC/lZP1RWReIG+gNExGhsiX172fOAXRJJfh8CRH//wECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAHAAAAAAAAALlNRyDzfz6RxY9YGjS9izaemohHd+emMc7mTcUIrSBKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACDurDbNIc2BjZH7kIqysRdpX+aLwKCWa1DLNlHRrcw2wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0gQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9qwK5AwOKcmy2Lchz/epYRd7yQrsy3RJJY/6JMGFaTq0WNTQ9ZXeFnu0L7nIHhn2oRQ+bTigevKk05LmUF+Da"
}

Verify reported MRENCLAVE against trusted source code ...

Reproducibility Report
----------------------
- Signed enclave MRENCLAVE:                     b94d4720f37f3e91c58f581a34bd8b369e9a884777e7a631cee64dc508ad204a
- Built-from-source enclave MRENCLAVE:          b94d4720f37f3e91c58f581a34bd8b369e9a884777e7a631cee64dc508ad204a
- IAS report MRENCLAVE:                         b94d4720f37f3e91c58f581a34bd8b369e9a884777e7a631cee64dc508ad204a

MRENCLAVES match!

Report data
-----------
The following REPORT DATA contained in the remote attestation verification report CAN be trusted.
3dab02b903038a726cb62dc873fdea5845def242bb32dd124963fe8930615a4ead1635343d6577859eed0bee7207867da8450f9b4e281ebca934e4b99417e0da

Extracting public key from IAS report ...
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETlphMIn+Y0kS3TK7QvLeRVjq/XPI
LbZscooDA7kCqz3a4BeUueQ0qbweKE6bD0WofYYHcu4L7Z6Fd2U9NDUWrQ==
-----END PUBLIC KEY-----

Verifying signature:
--------------------
3044022063b906028557e59869367b913d77afb213d8c3924dd191f69886855fcb7e6ea202207d350c6aec38f38198831b881b5b8530066fce075e9bb62863387e5299a20450
for sensor data:
Sensor Data Message !
** Start of sensor data ***
123.456    78.9    xyz
111.456    78.9    xyz
222.456    78.9    xyz
333.456    78.9    xyz
444.456    78.9    xyz
555.456    78.9    xyz
*** End of sensor data ***

Signature verification successful!

The sections below, provide some details about some key steps of the demo.

Send the quote to Intel

Go into an ipython shell:

ipython

Copy the quote from the output, and assign it to a variable:

quote = {
    "isvEnclaveQuote":"AgAAAFsLAAALAAoAAAAAAFOrdeScwC/lZP1RWReIG+iRaFn0HiQK7vu+7g8BckAuCRH//wECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAHAAAAAAAAAKijCU12IXxd0KESasFCs23TT4hRSpm/jfyOqFLx+mI4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABnBOOv77LJPGq5rW5P2XqTpdBWpBwqmccBzKH18B98SwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0gQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC0gEAU6MLnODQoKJlw5fZz7sUJYj5Z6qx78ar7B4V4pEKKhfhEyl/krjOiPlIzno5hNeorr3jOEnuUOs6l2kboqAIAAHmlFCVS6cp4FM7WhB3jpxWVDukXLMRgBvaHshb1qfuRqR6twO+shb5XQkLd/bNftWeTtZC+IVyUWjqSFJhuMyt8lXiNureQxoTxHvTa2//lL0tMKGjgZBt01ucRhIeyIb9LnAvgLw7EXuRmit4RjRKfVpSgMjnYtu1ZDsO+qoSCGlfY2WDy4oHCBQvz/ErTGA4cX20luT3G+4V9rvbUFL1XcdrRzEIBeOYv1o3w3ZhmhLNrqBxlB8JJrndMTvRb+idI5CYK7AGGIMvO6XPzgzDKvm2T+4hpqwQrUoQQilvcIkAJ7els/y53psv/m/T6R07ygBGkSF0kHFgnP1o7gs510cI7E7s714smfwnf7+fQMmDIIqBuOUCcAwmVMTbkpYETLGZwfMaaCI2tvWgBAADt2TDVlkTLaf/Hi5xnk8NaA/PcdbEVNf1LGnuorB4qY5dvM83rF6ABEV7N6uF5pH73b/oZwY+F4AxJW1cb8zjSrGJVb+LlO2zOrtDs0mb1RfckXChrGEgZtj0Tx584YSDRhPuQp2mvQjQyVYrOGCfzhyIDyiuqbhbvjPXWVVVKIjBWh+QZxZ3b/YJBPm38/XQfWV/JotJUMB6rUUzGSZi91a/Eb/7hrNjRyKXAYboog4IHrKJWgLRwPSdNcjZAeZAjZKJK5OCEk25341G6FoG34H9k6LeXidTBk/VRXrzlBbbbBbLg8mMqGDLv0WVXZnaYgDf/Jidu8vp08vknfi28PtxcIWmqwXOazO00yHU7OvUfjynnOxh0t+REa2YYxdehPAfO0nsgVmUruuj3s2vwtbgiQ6il03O/Sw4CabOya3C8Evce6tTHoPBtYGVs24Tgn5mXM+NSRs+ad+eDKsSqHymzd/sVrYaJlmL87zlcvtC8y33FVEIJ"
}

To send the quote over to Intel, you need your API primary subscription key, which you should have set as an environment variable before starting the container. (See the prerequisite section if needed.)

import os

headers = {
    'Content-Type': 'application/json',
    'Ocp-Apim-Subscription-Key': os.environ["IAS_PRIMARY_KEY"],
}

send the quote for verification:

import requests

url = 'https://api.trustedservices.intel.com/sgx/dev/attestation/v4/report'

res = requests.post(url, json=quote, headers=headers)

If everything went well the res.status_code should be 200, or res.ok True. You can look at res.reason for more information if you got an error.

In [6]: res.json()
Out[6]: 
{'id': '241352371682676293259277452268094264738',
 'timestamp': '2021-05-20T04:51:10.638041',
 'version': 4,
 'advisoryURL': 'https://security-center.intel.com',
 'advisoryIDs': ['INTEL-SA-00161',
  'INTEL-SA-00381',
  'INTEL-SA-00389',
  'INTEL-SA-00320',
  'INTEL-SA-00329',
  'INTEL-SA-00220',
  'INTEL-SA-00270',
  'INTEL-SA-00293'],
 'isvEnclaveQuoteStatus': 'GROUP_OUT_OF_DATE',
 'platformInfoBlob': '150200650400090000111102040101070000000000000000000B00000B000000020000000000000B5B6DB2D012D7BBA9067D6818A3CCBDEDC2EA2250EF57A18F3F85B03FAA9A09E606FE0414651A88C4F5335A733BC0C521083D62358CD310BD088C9C62A07B29E9F5',
 'isvEnclaveQuoteBody': 'AgAAAFsLAAALAAoAAAAAAFOrdeScwC/lZP1RWReIG+iRaFn0HiQK7vu+7g8BckAuCRH//wECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAHAAAAAAAAAKijCU12IXxd0KESasFCs23TT4hRSpm/jfyOqFLx+mI4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABnBOOv77LJPGq5rW5P2XqTpdBWpBwqmccBzKH18B98SwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0gQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC0gEAU6MLnODQoKJlw5fZz7sUJYj5Z6qx78ar7B4V4pEKKhfhEyl/krjOiPlIzno5hNeorr3jOEnuUOs6l2kbo'}

With the above output, it's possible to check the MRENCLAVE, etc, and to extract the public key out of the report data.

NOTE: The res.headers are important to check signature and certificates to make sure the report is authentic, meaning that it was signed by Intel's key.

Verify the MRENCLAVE

TODO

Extract the Public Key

From the json of the response, we get the quote body, encoded in base 64.

quote_body = res.json()['isvEnclaveQuoteBody']

In order to extract the report data out of the quote, it's necessary to be aware of the structure of a quote (sgx_quote_t) and of a report (sgx_report_body_t).

typedef struct _quote_t
{
    uint16_t            version;        /* 0   */
    uint16_t            sign_type;      /* 2   */
    sgx_epid_group_id_t epid_group_id;  /* 4   */
    sgx_isv_svn_t       qe_svn;         /* 8   */
    sgx_isv_svn_t       pce_svn;        /* 10  */
    uint32_t            xeid;           /* 12  */
    sgx_basename_t      basename;       /* 16  */
    sgx_report_body_t   report_body;    /* 48  */
    uint32_t            signature_len;  /* 432 */
    uint8_t             signature[];    /* 436 */
} sgx_quote_t;

The repport data is at the end of the report_body, at offset 320:

typedef struct _report_body_t
{
    sgx_cpu_svn_t           cpu_svn;        /* (  0) Security Version of the CPU */
    sgx_misc_select_t       misc_select;    /* ( 16) Which fields defined in SSA.MISC */
    uint8_t                 reserved1[SGX_REPORT_BODY_RESERVED1_BYTES];  /* ( 20) */
    sgx_isvext_prod_id_t    isv_ext_prod_id;/* ( 32) ISV assigned Extended Product ID */
    sgx_attributes_t        attributes;     /* ( 48) Any special Capabilities the Enclave possess */
    sgx_measurement_t       mr_enclave;     /* ( 64) The value of the enclave's ENCLAVE measurement */
    uint8_t                 reserved2[SGX_REPORT_BODY_RESERVED2_BYTES];  /* ( 96) */
    sgx_measurement_t       mr_signer;      /* (128) The value of the enclave's SIGNER measurement */
    uint8_t                 reserved3[SGX_REPORT_BODY_RESERVED3_BYTES];  /* (160) */
    sgx_config_id_t         config_id;      /* (192) CONFIGID */
    sgx_prod_id_t           isv_prod_id;    /* (256) Product ID of the Enclave */
    sgx_isv_svn_t           isv_svn;        /* (258) Security Version of the Enclave */
    sgx_config_svn_t        config_svn;     /* (260) CONFIGSVN */
    uint8_t                 reserved4[SGX_REPORT_BODY_RESERVED4_BYTES];  /* (262) */
    sgx_isvfamily_id_t      isv_family_id;  /* (304) ISV assigned Family ID */
    sgx_report_data_t       report_data;    /* (320) Data provided by the user */
} sgx_report_body_t;

With the above information, we can decode the base 64 encoded quote, and access the report data in it.

import base64

report_data = base64.b64decode(quote_body)[368:432]

The original demo wrote the public key in PEM format under the file demo_sgx/secp256r1.pem. The public key we have in the report data should match the one in the .pem file. We'll use Python's cryptography library to verify this.

With Python's cryptography library, the public point can be used to instantiate a public key object, EllipticCurvePublicKey, from which it's possible to obtain other formats such as PEM and DER.

It's important to note that Python's cryptography library expects the point to be encoded as per Section 2.3.3 in https://www.secg.org/sec1-v2.pdf. The report data contains both x and y coordinates, in uncompressed form, and without the octet prefix 04. It's therefore necessary to add the octet prefix to the report data.

from cryptography.hazmat.primitives.asymmetric import ec

point = b"\x04" + report_data
pubkey = ec.EllipticCurvePublicKey.from_encoded_point(curve=ec.SECP256R1(), data=point)

Check that it matches the PEM data file:

from cryptography.hazmat.primitives import serialization

pem_from_report_data = pubkey.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo,
)

with open('demo_sgx/secp256r1.pem') as f:
    pem_file_data = f.read()

pem_from_report_data == pem_file_data.encode()
# True

Verify the Signed Data

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec

with open('demo_sgx/Sensor_Data.signature', 'rb') as f:
    signature = f.read()

with open('Sensor_Data') as f:
    sensor_data = f.read()

pubkey.verify(
    signature,
    sensor_data.encode(),
    signature_algorithm=ec.ECDSA(hashes.SHA256()),
)