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
-
You need docker (version 19.03+) and docker-compose.
-
The docker-based development environment assumes it is running on an SGX-enabled processor. If you are not sure whether your computer supports SGX, and/or how to enable it, see https://github.com/ayeks/SGX-hardware#test-sgx.
-
Obtain an Unlinkable subscription key for the Intel SGX Attestation Service Utilizing Enhanced Privacy ID (EPID).
Quickstart
Set Environment Variables
Before starting a container, set the two following environment variables:
SGX_SPID
- used to create a quoteIAS_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()),
)