Prototypes to analyze neuroimaging data in trusted hardware (SGX).
The current very small prototype is based on gramine's Python examples. You may find it useful to refer to it at https://github.com/gramineproject/gramine/tree/master/CI-Examples/python.
Note: For Debian-based systems, the gramine examples expect Python packages to be installed under
/usr/lib/python3/dist-packages
. The examples in this repository try to use packages installed viapip
instead.
Warning: Only tested on Ubuntu (should work on Debian), and with the out-of-tree (OOT) SGX driver.
To try the "mvp" (minimal viable prototype), you need:
- An SGX enabled machine
- To Install gramine
To perform remote attestation:
- Obtain an Unlinkable subscription key (SPID) for IAS.
TODO: minimal instructions on installing gramine
-
Clone this repository.
-
Create a virual environment, e.g.:
python3.10 -m venv ~/.venvs/nifti
- Install
dicom2nifti
in the virtual environment, e.g.:
source ~/.venvs/nifti/bin/activate \
&& pip install dicom2nifti \
&& deactivate
- From the root of this repo, copy the
data
directory under/opt
and give set file permissions to your user, e.g.:
chown -R `id -un`:`id -gn` /opt/data
- From the
mvp
directory, generate thepython.manifest.sgx
file:
cd mvp
make SGX=1 PYTHON=python3.10 VENV_PATH=~/.venvs/nifti \
RA_CLIENT_SPID=12345678901234567890123456789012 RA_CLIENT_LINKABLE=0
Note: In the above the
RA_CLIENT_SPID
is set to a dummy value, and you can ignore it for the sake of this brief demo. See the Remote Attestation section to see how to perform the attestation phase.
- Convert the dicom images to nifti:
gramine-sgx ./python scripts/test-dicom2nifti.py
- Check that the output file is there:
ls -l /opt/data/out/nifti/
- Slice a nifti image:
gramine-sgx ./python scripts/test-nibabel.py
- Check the output:
ls -l /opt/data/out/nifti/
If you haven't done so already, subsribe to IAS to get an SPID and API key.
Set the RA_CLIENT_SPID
and IAS_PRIMARY_KEY
enviroment variables:
export RA_CLIENT_SPID=<YOUR_SPID>
export IAS_PRIMARY_KEY=<YOUR_IAS_PRIMARY_KEY>
Run the following command, making sure to provide your own SPID.
make SGX=1 PYTHON=python3.10 VENV_PATH=~/.venvs/nifti \
RA_CLIENT_SPID=$RA_CLIENT_SPID RA_CLIENT_LINKABLE=0
Notice the measurement or mrenclave in the output, e.g.:
Measurement:
554052c3730934ec0868eea6796f75b9a97d248d029803c35ad78cd0bbb9cd83
gramine-sgx-get-token --output python.token --sig python.sig Attributes:
mr_enclave: 554052c3730934ec0868eea6796f75b9a97d248d029803c35ad78cd0bbb9cd83
mr_signer: 5d9fd619b25ed0de75ea71d0d211522c80e14d4eb417fa8869babf62656c5b84
...
Let's set an environemnt variable for the MRENCLAVE so that we can easily refer to it later when parsing the report from IAS.
export MRENCLAVE=554052c3730934ec0868eea6796f75b9a97d248d029803c35ad78cd0bbb9cd83
Generate a quote:
gramine-sgx ./python scripts/sgx-quote.py
Notice the QUOTE base 64
output, e.g.:
QUOTE base 64:
AgAAAEsMAAANAA0AAAAAAFOrdeScwC/lZP1RWReIG+hfJ7BJNls6vpHvqy2dOXqEExMCB/+ABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAHAAAAAAAAAFVAUsNzCTTsCGjupnlvdbmpfSSNApgDw1rXjNC7uc2DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABdn9YZsl7Q3nXqcdDSEVIsgOFNTrQX+ohpur9iZWxbhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAIAACjTKbgsgOCw9A4v58WRhDDbuGvbuB4BdDPIVetcyWIlIFSGuLxIDAqukAGYTfvG3xCB5hHTqJkg86/P2TF6NTf7V4wN+PHXB1Csqm3chHBjNdOPRBcDIYyWIrWAhNb17+RafZsGW524lOfyZJUEs6GG0rAe1EK1VmEeqUpDdyWcXo9jTtOHMHh517kF7hSRSnBon2/aO6JdIu2Acc0rEEWI60S8ySolFeLYn825yUW+drMV+Zi++g9y5i7qPBtdJDRaCfHBzZlDAjiTISrabV1lOyw/SkkGofy7WUrND0rdhOD50LsyngJuCglzd5f5Uexo2iFjKrTSAuwFfRZIU+VUWmklmqcLHKhHNZS1mANmZpqGIMghuqe9W2En3csOJOzmpYQaUUEOkGxU7GgBAABeyPrHVEYDJCNc6MNE3bqxeMGNsgMYHt8OOgR7jR/lVNjBs0E47PrMndMqFQ13etccv7/+h4qcyXfsMqRKcKlR9QB1EQT1ha+JcOOiBMx9m5/VceAx/Pj0JgNweEnT22vSaJtAmfANajUc7dIpnjWWnAmZHTrLgbNg8i2rclic9ZNHOdAP1HCx7y8RycJYwITFZW3d40Q2Snhk27aqZLswXho4EZ0jbJESv24oy6x4NYcbiXPQ5FoGF+VUtNg3WxSRfQNOgwI/TGT1g3wZzst7yKaXkYPPDK2VeV8jOAcO62Jd16z+wQ/oaqs2YHWzo4Ldmjr2el5MEqjA+3UysRs7NNuSAItlHsGL67cHOQ5BgYvv4Yxvh1o4wwcYlXiYcbvOlFau4sO3Gct2hwYckReT+R0eXHmboldpl70peG1NCRWJ/+s/Gw+zAt1byzGTZ9zfQOL8hGRY80OmFaAJysg6EP0eyocGkXnsJ73UUDRdHd6lcZduVYAs
Here's a simple example of sending out the quote to IAS for verification using Python's
requests
library:
Copy-paste the quote to send to Intel for verification. In a Python shell:
quote = "AgAAAEsMAAANAA0AAAAAAFOrdeScwC/lZP1RWReIG+hfJ7BJNls6vpHvqy2dOXqEExMCB/+ABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAHAAAAAAAAAFVAUsNzCTTsCGjupnlvdbmpfSSNApgDw1rXjNC7uc2DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABdn9YZsl7Q3nXqcdDSEVIsgOFNTrQX+ohpur9iZWxbhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAIAACjTKbgsgOCw9A4v58WRhDDbuGvbuB4BdDPIVetcyWIlIFSGuLxIDAqukAGYTfvG3xCB5hHTqJkg86/P2TF6NTf7V4wN+PHXB1Csqm3chHBjNdOPRBcDIYyWIrWAhNb17+RafZsGW524lOfyZJUEs6GG0rAe1EK1VmEeqUpDdyWcXo9jTtOHMHh517kF7hSRSnBon2/aO6JdIu2Acc0rEEWI60S8ySolFeLYn825yUW+drMV+Zi++g9y5i7qPBtdJDRaCfHBzZlDAjiTISrabV1lOyw/SkkGofy7WUrND0rdhOD50LsyngJuCglzd5f5Uexo2iFjKrTSAuwFfRZIU+VUWmklmqcLHKhHNZS1mANmZpqGIMghuqe9W2En3csOJOzmpYQaUUEOkGxU7GgBAABeyPrHVEYDJCNc6MNE3bqxeMGNsgMYHt8OOgR7jR/lVNjBs0E47PrMndMqFQ13etccv7/+h4qcyXfsMqRKcKlR9QB1EQT1ha+JcOOiBMx9m5/VceAx/Pj0JgNweEnT22vSaJtAmfANajUc7dIpnjWWnAmZHTrLgbNg8i2rclic9ZNHOdAP1HCx7y8RycJYwITFZW3d40Q2Snhk27aqZLswXho4EZ0jbJESv24oy6x4NYcbiXPQ5FoGF+VUtNg3WxSRfQNOgwI/TGT1g3wZzst7yKaXkYPPDK2VeV8jOAcO62Jd16z+wQ/oaqs2YHWzo4Ldmjr2el5MEqjA+3UysRs7NNuSAItlHsGL67cHOQ5BgYvv4Yxvh1o4wwcYlXiYcbvOlFau4sO3Gct2hwYckReT+R0eXHmboldpl70peG1NCRWJ/+s/Gw+zAt1byzGTZ9zfQOL8hGRY80OmFaAJysg6EP0eyocGkXnsJ73UUDRdHd6lcZduVYAs"
json = {"isvEnclaveQuote": quote}
Set the request headers. You need your unlinkable subscription key from the Intel SGX Attestation Service Utilizing Enhanced Privacy ID (EPID).
import os
headers = {
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-Key': os.environ['IAS_PRIMARY_KEY'],
}
Using the requests
library post the quote to IAS:
import requests
url = 'https://api.trustedservices.intel.com/sgx/dev/attestation/v4/report'
res = requests.post(url, json=json, headers=headers)
Check the response status:
res.ok
Check the response body:
>>> res.json()
{'id': '121855069149404306319130152257933697178',
'timestamp': '2022-10-06T03:24:59.639030',
'version': 4,
'advisoryURL': 'https://security-center.intel.com',
'advisoryIDs': ['INTEL-SA-00334'],
'isvEnclaveQuoteStatus': 'SW_HARDENING_NEEDED',
'isvEnclaveQuoteBody': 'AgAAAEsMAAANAA0AAAAAAFOrdeScwC/lZP1RWReIG+hfJ7BJNls6vpHvqy2dOXqEExMCB/+ABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAHAAAAAAAAAFVAUsNzCTTsCGjupnlvdbmpfSSNApgDw1rXjNC7uc2DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABdn9YZsl7Q3nXqcdDSEVIsgOFNTrQX+ohpur9iZWxbhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'}
Getting the MRENCLAVE, MRSIGNER and REPORT DATA out of the report requires to know the structure of a quote:
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 report body is the structure that contains the MRENCLAVE:
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;
Extract the report body:
import base64
isv_enclave_quote_body = res.json()['isvEnclaveQuoteBody']
report_body = base64.b64decode(isv_enclave_quote_body)[48:432]
Check the MRENCLAVE:
Recall that after running the make
command above there was a measurement
output also known as MRENCLAVE. If you haven't done so, set an environment
variable MRENCLAVE
to the value that was output after the build command
with make
, e.g.:
export MRENCLAVE=554052c3730934ec0868eea6796f75b9a97d248d029803c35ad78cd0bbb9cd83
Now, let's compare the expected MRENCLAVE
with what we have in the report:
>>> report_body[64:96].hex() == os.environ['MRENCLAVE']
True
If it is False
, it means that the attestation phase reported a different
measurement of the code, which would mean that the code that was loaded in
SGX differs from the code that was built.
Extract the REPORT DATA:
report_body[320:384].hex() # report data
- Encrypt the files before processing them in SGX. Input files should only be decrypted within SGX.
- Provide a Dockerfile for easier setup.
- ...