diff --git a/CI-Examples/ra-tls-mbedtls/Makefile b/CI-Examples/ra-tls-mbedtls/Makefile index 4a09f67448..a5f29b4353 100644 --- a/CI-Examples/ra-tls-mbedtls/Makefile +++ b/CI-Examples/ra-tls-mbedtls/Makefile @@ -31,6 +31,9 @@ epid: client_epid.manifest.sgx client_epid.sig client_epid.token .PHONY: dcap dcap: client_dcap.manifest.sgx client_dcap.sig client_dcap.token +.PHONY: maa +maa: client_maa.manifest.sgx client_maa.sig client_maa.token + ############################# SSL DATA DEPENDENCY ############################# # SSL data: key and x.509 self-signed certificate @@ -131,6 +134,26 @@ sgx_sign_client_dcap: client_dcap.manifest client client_dcap.token: client_dcap.sig gramine-sgx-get-token --output $@ --sig $< +########################### CLIENT (MAA) MANIFEST ############################# + +client_maa.manifest: client.manifest.template + gramine-manifest \ + -Dlog_level=$(GRAMINE_LOG_LEVEL) \ + -Darch_libdir=$(ARCH_LIBDIR) \ + $< >$@ + +client_maa.manifest.sgx client_maa.sig: sgx_sign_client_maa + @: + +.INTERMEDIATE: sgx_sign_client_maa +sgx_sign_client_maa: client_maa.manifest client + gramine-sgx-sign \ + --manifest $< \ + --output $<.sgx + +client_maa.token: client_maa.sig + gramine-sgx-get-token --output $@ --sig $< + ########################### CLIENT (EPID) MANIFEST ############################ client_epid.manifest: client.manifest.template @@ -193,6 +216,26 @@ check_dcap_fail: app dcap ./client dcap && exit 1 || echo "[ Success 1/1 ]"; \ kill -9 $$SERVER_ID +.PHONY: check_maa +check_maa: app maa + gramine-sgx server maa >/dev/null & SERVER_ID=$$!; \ + sleep 30; \ + ./client maa > OUTPUT; \ + ./client maa 0 0 0 0 >> OUTPUT; \ + kill -9 $$SERVER_ID + @grep -q "using default SGX-measurement verification callback" OUTPUT && echo "[ Success 1/4 ]" + @grep -q "using our own SGX-measurement verification callback" OUTPUT && echo "[ Success 2/4 ]" + @grep -q "Verifying peer X.509 certificate... ok" OUTPUT && echo "[ Success 3/4 ]" + @(exit `grep -c "failed" "OUTPUT"`) && echo "[ Success 4/4 ]" + @rm OUTPUT + +.PHONY: check_maa_fail +check_maa_fail: app maa + gramine-sgx server maa dummy-option >/dev/null & SERVER_ID=$$!; \ + sleep 30; \ + ./client maa && exit 1 || echo "[ Success 1/1 ]"; \ + kill -9 $$SERVER_ID + ################################## CLEANUP #################################### .PHONY: clean diff --git a/CI-Examples/ra-tls-mbedtls/README.md b/CI-Examples/ra-tls-mbedtls/README.md index 034ee7c920..784960466d 100644 --- a/CI-Examples/ra-tls-mbedtls/README.md +++ b/CI-Examples/ra-tls-mbedtls/README.md @@ -5,28 +5,39 @@ minimal server and client written against the mbedTLS library. The server and client are based on `ssl_server.c` and `ssl_client1.c` example programs shipped with mbedTLS. We modified them to allow using RA-TLS flows if -the programs are given the command-line argument `epid`/`dcap`. In particular, -the server uses a self-signed RA-TLS cert with the SGX quote embedded in it via -`ra_tls_create_key_and_crt()`. The client uses an RA-TLS verification callback -to verify the server RA-TLS certificate via `ra_tls_verify_callback()`. +the programs are given the command-line argument `epid`/`dcap`/`maa`. In +particular, the server uses a self-signed RA-TLS cert with the SGX quote +embedded in it via `ra_tls_create_key_and_crt()`. The client uses an RA-TLS +verification callback to verify the server RA-TLS certificate via +`ra_tls_verify_callback()`. This example uses the RA-TLS libraries `ra_tls_attest.so` for server and -`ra_tls_verify_epid.so`/ `ra_tls_verify_dcap.so` for client. These libraries are -installed together with Gramine (for DCAP version, you need `meson setup ... --Ddcap=enabled`). Additionally, mbedTLS libraries are required to correctly run -RA-TLS, the client, and the server. For ECDSA/DCAP attestation, the DCAP -software infrastructure must be installed and work correctly on the host. - -The current example works with both EPID (IAS) and ECDSA (DCAP) remote -attestation schemes. For more documentation, refer to +`ra_tls_verify_epid.so` / `ra_tls_verify_dcap.so` / `ra_tls_verify_maa.so` for +client. These libraries are installed together with Gramine (for DCAP and MAA +versions, you need `meson setup ... -Ddcap=enabled`). Additionally, mbedTLS +libraries are required. + +For ECDSA/DCAP attestation, the DCAP software infrastructure must be installed +and work correctly on the host. + +For MAA attestation, the server must run on the Azure cloud, and the client must +have internet access to the MAA attestation provider service. + +The current example works with both EPID and ECDSA/DCAP SGX quote formats. The +current example supports: +- the IAS-based attestation scheme for EPID quotes, +- the DCAP-based attestation scheme for DCAP quotes, +- the MAA-based attestation scheme for DCAP quotes. + +For more documentation, refer to https://gramine.readthedocs.io/en/latest/attestation.html. ## RA-TLS server The server is supposed to run in the SGX enclave with Gramine and RA-TLS dlopen-loaded. If RA-TLS library `ra_tls_attest.so` is not requested by user via -`epid`/`dcap` command-line argument, the server falls back to using normal X.509 -PKI flows (specified as `native` command-line argument). +`epid`/`dcap`/`maa` command-line argument, the server falls back to using normal +X.509 PKI flows (specified as `native` command-line argument). If server is run with more command-line arguments (the only important thing is to have at least one additional argument), then the server will maliciously @@ -36,10 +47,10 @@ purposes. ## RA-TLS client The client is supposed to run on a trusted machine (*not* in an SGX enclave). If -RA-TLS library `ra_tls_verify_epid.so` or `ra_tls_verify_dcap.so` is not -requested by user via `epid` or `dcap` command-line arguments respectively, the -client falls back to using normal X.509 PKI flows (specified as `native` -command-line argument). +RA-TLS library `ra_tls_verify_epid.so`, `ra_tls_verify_dcap.so` or +`ra_tls_verify_maa.so` is not requested by user via `epid`, `dcap` or `maa` +command-line arguments respectively, the client falls back to using normal X.509 +PKI flows (specified as `native` command-line argument). It is also possible to run the client in an SGX enclave. This will create a secure channel between two Gramine SGX processes, possibly running on different @@ -60,7 +71,8 @@ Moreover, we set `RA_TLS_ALLOW_OUTDATED_TCB_INSECURE=1`, to allow performing the tests when some of Intel's security advisories haven't been addressed (for example, when the microcode or architectural enclaves aren't fully up-to-date). As the name of this setting suggests, this is not secure and likewise should not -be used in production. +be used in production. Note that this setting is irrelevant for the MAA +attestation scheme because MAA always expects enclave TCB to be up-to-date. # Quick Start @@ -125,6 +137,27 @@ RA_TLS_ISV_SVN= \ kill %% ``` +- RA-TLS flows with SGX and with Gramine, Microsoft Azure Attestation (MAA) + attestation: + +```sh +make clean +make app maa + +gramine-sgx ./server maa & + +RA_TLS_ALLOW_DEBUG_ENCLAVE_INSECURE=1 \ +RA_TLS_MAA_PROVIDER_URL="https://sharedcus.cus.attest.azure.net" \ +RA_TLS_MRENCLAVE= \ +RA_TLS_MRSIGNER= \ +RA_TLS_ISV_PROD_ID= \ +RA_TLS_ISV_SVN= \ +./client maa + +# client will successfully connect to the server via RA-TLS/DCAP flows +kill %% +``` + - RA-TLS flows with SGX and with Gramine, client with its own verification callback: ```sh diff --git a/CI-Examples/ra-tls-mbedtls/client.manifest.template b/CI-Examples/ra-tls-mbedtls/client.manifest.template index a6676dec38..d50298cb20 100644 --- a/CI-Examples/ra-tls-mbedtls/client.manifest.template +++ b/CI-Examples/ra-tls-mbedtls/client.manifest.template @@ -1,4 +1,4 @@ -# Client manifest file (both for EPID and DCAP) +# Client manifest file (same for EPID, DCAP and MAA) loader.entrypoint = "file:{{ gramine.libos }}" libos.entrypoint = "client" diff --git a/CI-Examples/ra-tls-mbedtls/src/client.c b/CI-Examples/ra-tls-mbedtls/src/client.c index 99c643b11e..b240013c27 100644 --- a/CI-Examples/ra-tls-mbedtls/src/client.c +++ b/CI-Examples/ra-tls-mbedtls/src/client.c @@ -155,9 +155,9 @@ int main(int argc, char** argv) { mbedtls_x509_crt_init(&cacert); mbedtls_entropy_init(&entropy); - if (argc < 2 || - (strcmp(argv[1], "native") && strcmp(argv[1], "epid") && strcmp(argv[1], "dcap"))) { - mbedtls_printf("USAGE: %s native|epid|dcap [SGX measurements]\n", argv[0]); + if (argc < 2 || (strcmp(argv[1], "native") && strcmp(argv[1], "epid") && + strcmp(argv[1], "dcap") && strcmp(argv[1], "maa"))) { + mbedtls_printf("USAGE: %s native|epid|dcap|maa [SGX measurements]\n", argv[0]); return 1; } @@ -200,6 +200,21 @@ int main(int argc, char** argv) { return 1; } } + } else if (!strcmp(argv[1], "maa")) { + void* helper_sgx_urts_lib = dlopen("libsgx_urts.so", RTLD_NOW | RTLD_GLOBAL); + if (!helper_sgx_urts_lib) { + mbedtls_printf("%s\n", dlerror()); + mbedtls_printf("User requested RA-TLS verification with MAA but cannot find helper" + " libsgx_urts.so lib\n"); + return 1; + } + + ra_tls_verify_lib = dlopen("libra_tls_verify_maa.so", RTLD_LAZY); + if (!ra_tls_verify_lib) { + mbedtls_printf("%s\n", dlerror()); + mbedtls_printf("User requested RA-TLS verification with MAA but cannot find lib\n"); + return 1; + } } if (ra_tls_verify_lib) { diff --git a/CI-Examples/ra-tls-mbedtls/src/server.c b/CI-Examples/ra-tls-mbedtls/src/server.c index 32b9926909..02724a5da8 100644 --- a/CI-Examples/ra-tls-mbedtls/src/server.c +++ b/CI-Examples/ra-tls-mbedtls/src/server.c @@ -99,13 +99,13 @@ int main(int argc, char** argv) { mbedtls_debug_set_threshold(DEBUG_LEVEL); #endif - if (argc < 2 || - (strcmp(argv[1], "native") && strcmp(argv[1], "epid") && strcmp(argv[1], "dcap"))) { - mbedtls_printf("USAGE: %s native|epid|dcap [SGX measurements]\n", argv[0]); + if (argc < 2 || (strcmp(argv[1], "native") && strcmp(argv[1], "epid") && + strcmp(argv[1], "dcap") && strcmp(argv[1], "maa"))) { + mbedtls_printf("USAGE: %s native|epid|dcap|maa [SGX measurements]\n", argv[0]); return 1; } - if (!strcmp(argv[1], "epid") || !strcmp(argv[1], "dcap")) { + if (!strcmp(argv[1], "epid") || !strcmp(argv[1], "dcap") || !strcmp(argv[1], "maa")) { ra_tls_attest_lib = dlopen("libra_tls_attest.so", RTLD_LAZY); if (!ra_tls_attest_lib) { mbedtls_printf("User requested RA-TLS attestation but cannot find lib\n"); diff --git a/CI-Examples/ra-tls-secret-prov/.gitignore b/CI-Examples/ra-tls-secret-prov/.gitignore index 8e58fd6fcb..4227bab2f7 100644 --- a/CI-Examples/ra-tls-secret-prov/.gitignore +++ b/CI-Examples/ra-tls-secret-prov/.gitignore @@ -6,5 +6,6 @@ /secret_prov_pf_client /secret_prov_server_dcap /secret_prov_server_epid +/secret_prov_server_maa /ssl/ca.* /ssl/server.* diff --git a/CI-Examples/ra-tls-secret-prov/Makefile b/CI-Examples/ra-tls-secret-prov/Makefile index d195138dd3..f75e8c164b 100644 --- a/CI-Examples/ra-tls-secret-prov/Makefile +++ b/CI-Examples/ra-tls-secret-prov/Makefile @@ -38,6 +38,9 @@ epid: secret_prov_server_epid .PHONY: dcap dcap: secret_prov_server_dcap +.PHONY: maa +maa: secret_prov_server_maa + ############################# SSL DATA DEPENDENCY ############################# # SSL data: key and x.509 self-signed certificate @@ -63,6 +66,9 @@ secret_prov_server_epid: src/secret_prov_server.c secret_prov_server_dcap: src/secret_prov_server.c $(CC) $< $(CFLAGS) $(LDFLAGS) -Wl,--no-as-needed -lsgx_urts -lsecret_prov_verify_dcap -pthread -o $@ +secret_prov_server_maa: src/secret_prov_server.c + $(CC) $< $(CFLAGS) $(LDFLAGS) -lsecret_prov_verify_maa -pthread -o $@ + secret_prov_client: src/secret_prov_client.c $(CC) $< $(CFLAGS) $(LDFLAGS) -lsecret_prov_attest -o $@ @@ -175,13 +181,27 @@ check_dcap: app dcap files/input.txt @grep "\[child\] Read from protected file: 'helloworld'" OUTPUT && echo "[ Success 4/4 ]" @rm OUTPUT +.PHONY: check_maa +check_maa: app maa files/input.txt + ./secret_prov_server_maa >/dev/null & SERVER_ID=$$!; \ + sleep 3; \ + gramine-sgx ./secret_prov_min_client > OUTPUT; \ + gramine-sgx ./secret_prov_client >> OUTPUT; \ + gramine-sgx ./secret_prov_pf_client >> OUTPUT; \ + kill -9 $$SERVER_ID; + @grep "Received secret = 'ffeeddccbbaa99887766554433221100'" OUTPUT && echo "[ Success 1/4 ]" + @grep "Received secret1 = 'ffeeddccbbaa99887766554433221100', secret2 = '42'" OUTPUT && echo "[ Success 2/4 ]" + @grep "\[parent\] Read from protected file: 'helloworld'" OUTPUT && echo "[ Success 3/4 ]" + @grep "\[child\] Read from protected file: 'helloworld'" OUTPUT && echo "[ Success 4/4 ]" + @rm OUTPUT + ################################## CLEANUP #################################### .PHONY: clean clean: $(RM) -r \ *.token *.sig *.manifest.sgx *.manifest *.so *.so.* OUTPUT \ - secret_prov_server_epid secret_prov_server_dcap \ + secret_prov_server_epid secret_prov_server_dcap secret_prov_server_maa \ secret_prov_client secret_prov_min_client secret_prov_pf_client .PHONY: distclean diff --git a/CI-Examples/ra-tls-secret-prov/README.md b/CI-Examples/ra-tls-secret-prov/README.md index 1b693bd62f..f742fd9098 100644 --- a/CI-Examples/ra-tls-secret-prov/README.md +++ b/CI-Examples/ra-tls-secret-prov/README.md @@ -4,14 +4,24 @@ This directory contains the Makefile, the template client manifests, and the minimal server and clients written against the Secret Provisioning library. This example uses the Secret Provisioning libraries `secret_prov_attest.so` for -clients and `secret_prov_verify_epid.so`/`secret_prov_verify_dcap.so` for -server. These libraries are installed together with Gramine (for DCAP version, -you need `meson setup ... -Ddcap=enabled`). Additionally, mbedTLS libraries are -required. For ECDSA/DCAP attestation, the DCAP software infrastructure must be -installed and work correctly on the host. - -The current example works with both EPID (IAS) and ECDSA (DCAP) remote -attestation schemes. For more documentation, refer to +clients and `secret_prov_verify_epid.so` / `secret_prov_verify_dcap.so` / +`secret_prov_verify_maa.so` for server. These libraries are installed together +with Gramine (for DCAP and MAA versions, you need `meson setup ... +-Ddcap=enabled`). Additionally, mbedTLS libraries are required. + +For ECDSA/DCAP attestation, the DCAP software infrastructure must be installed +and work correctly on the host. + +For MAA attestation, the server must run on the Azure cloud, and the client must +have internet access to the MAA attestation provider service. + +The current example works with both EPID and ECDSA/DCAP SGX quote formats. The +current example supports: +- the IAS-based attestation scheme for EPID quotes, +- the DCAP-based attestation scheme for DCAP quotes, +- the MAA-based attestation scheme for DCAP quotes. + +For more documentation, refer to https://gramine.readthedocs.io/en/latest/attestation.html. ## Secret Provisioning server @@ -23,20 +33,19 @@ verification succeeds, sends the first secret back to the client (the master key for encrypted files, read from `files/wrap-key`). If the client requests a second secret, the server sends the dummy string `42` as the second secret. -There are two versions of the server: the EPID one and the DCAP one. Each of -them links against the corresponding EPID/DCAP secret-provisioning library at -build time. +There are three versions of the server: EPID, DCAP and MAA. Each of them links +against the corresponding secret-provisioning library at build time. -Because this example builds and uses debug SGX enclaves (`sgx.debug` is set -to `true`), we use environment variable `RA_TLS_ALLOW_DEBUG_ENCLAVE_INSECURE=1`. -Note that in production environments, -you must *not* use this option! +Because this example builds and uses debug SGX enclaves (`sgx.debug` is set to +`true`), we use environment variable `RA_TLS_ALLOW_DEBUG_ENCLAVE_INSECURE=1`. +Note that in production environments, you must *not* use this option! Moreover, we set `RA_TLS_ALLOW_OUTDATED_TCB_INSECURE=1`, to allow performing the tests when some of Intel's security advisories haven't been addressed (for example, when the microcode or architectural enclaves aren't fully up-to-date). As the name of this setting suggests, this is not secure and likewise should not -be used in production. +be used in production. Note that this setting is irrelevant for the MAA +attestation scheme because MAA always expects enclave TCB to be up-to-date. ## Secret Provisioning clients @@ -109,3 +118,24 @@ gramine-sgx ./secret_prov_pf_client kill %% ``` + +- Secret Provisioning flows, Microsoft Azure Attestation (MAA) attestation: + +```sh +make app maa files/input.txt + +RA_TLS_ALLOW_DEBUG_ENCLAVE_INSECURE=1 \ +RA_TLS_MAA_PROVIDER_URL="https://sharedcus.cus.attest.azure.net" \ +./secret_prov_server_maa & + +# test minimal client +gramine-sgx ./secret_prov_min_client + +# test feature-rich client +gramine-sgx ./secret_prov_client + +# test encrypted-files client +gramine-sgx ./secret_prov_pf_client + +kill %% +``` diff --git a/Documentation/attestation.rst b/Documentation/attestation.rst index 1841a365cc..6289830d17 100644 --- a/Documentation/attestation.rst +++ b/Documentation/attestation.rst @@ -27,7 +27,8 @@ Gramine provides support for all three levels of attestation flows: application via ``/dev/attestation`` pseudo-filesystem. SGX local attestation in Gramine relies on the ``EREPORT`` hardware instruction. SGX remote attestation uses the Intel SGX PSW's AESM service and the Intel IAS service - (for EPID flows) or DCAP libraries (for ECDSA/DCAP flows) under the hood. + (for EPID flows), DCAP libraries (for ECDSA/DCAP flows) or MAA attestation + provier service (for MAA flows) under the hood. #. :term:`Secure Channel` is constructed using the RA-TLS libraries. :term:`RA-TLS` uses raw ``/dev/attestation`` pseudo-files under the hood. @@ -47,18 +48,20 @@ micro-services in the public cloud. Please refer to :ref:`third_party_solutions` for specific examples. -Remote Attestation flows for EPID and DCAP ------------------------------------------- +Remote Attestation flows for EPID, DCAP and MAA +----------------------------------------------- -Remote attestation in Intel SGX comes in two flavours: :term:`EPID` and -:term:`DCAP`. The former is used in client machines whereas the latter is used -in data center environments. The details of these flows will be described in the -following sections. Here we give a high-level description of both of these -remote attestation schemes. +Remote attestation in Intel SGX comes in the form of three attestation schemes: +:term:`EPID`, :term:`DCAP` and :term:`MAA`. + +The EPID scheme is used in client machines. The DCAP scheme is used in data +center environments. The MAA scheme is used in the Microsoft Azure public cloud. +The details of these flows will be described in the following sections. Here we +give a high-level description of these remote attestation schemes. .. image:: ./img/epid.svg :target: ./img/epid.svg - :alt: Figure: EPID based remote attestation in Intel SGX + :alt: Figure: EPID remote attestation scheme in Intel SGX The diagram above shows EPID based remote attestation. The user application runs in an SGX enclave on a remote untusted machine, whereas the end user waits for @@ -90,7 +93,7 @@ untrusted machine and start sending inputs/receiving enclave outputs. .. image:: ./img/dcap.svg :target: ./img/dcap.svg - :alt: Figure: DCAP based remote attestation in Intel SGX + :alt: Figure: DCAP remote attestation scheme in Intel SGX The diagram above shows DCAP based remote attestation. The DCAP flows are very similar to EPID flows, but rather than using the EPID keys and consulting the @@ -109,6 +112,23 @@ local machine (preliminary step 0). When the SGX quote arrives, the user compares the certificates embedded in the quote against these cached certificates (step 9). +.. image:: ./img/maa.svg + :target: ./img/maa.svg + :alt: Figure: MAA remote attestation scheme in Intel SGX + +The diagram above shows MAA based remote attestation. The MAA flows are very +similar to EPID flows, but rather than communicating the Intel Attestation +Service, the MAA flows instead communicate with the MAA attestation provider +service. + +MAA attestation uses DCAP-formatted SGX quotes, so the steps 1-8 retrieve the +SGX quote similarly to the DCAP attestation. However, then the step 9 forwards +the SGX quote to the MAA attestation provider (in a so-called Attestation +request), and the MAA attestation provider replies with the Attestation +response. The attestation response embeds the JSON Web Token (JWT) that +contains a set of claims about the SGX quote. The remote user can verify the +enclave measurements contained in the JWT claims against the expected values. + Low-level ``/dev/attestation`` interface ---------------------------------------- @@ -182,7 +202,7 @@ the remote attestation flow may look like in your application:: The remote user should receive this attestation quote and verify it. In case of Intel SGX, this verification flow depends on whether the SGX remote attestation -is EPID based or DCAP/ECDSA based: +scheme is EPID, DCAP or MAA: - :term:`EPID` based quote verification is done with the help of the Intel Attestation Service (:term:`IAS`). In particular, the remote user should @@ -197,12 +217,23 @@ is EPID based or DCAP/ECDSA based: :term:`Intel Provisioning Certification Service`, caching these certificates in the Provisioning Certificate Caching Service, etc.). +- :term:`MAA` based quote verification is done with the help of the Microsoft + Azure Attestation (MAA) provider. In particular, the remote user should + forward the received SGX quote to the well-known MAA REST endpoint via a + secure internet connection and get the MAA attestation response (that embeds + the JSON Web Token, or JWT) back. The user then should verify the signature of + the JWT (using a JWK obtained from the MAA provider separately) and examine + the contents of the JWT and decide whether to trust the remote SGX enclave or + not. + Gramine does *not* provide any pseudo-files under ``/dev/attestation`` for verification of the attestation quote. Instead, the remote user is encouraged to use the :program:`quote_dump`, :program:`ias_request` and -:program:`verify_ias_report` tools shipped together with Gramine (for -EPID based quote verification) or to use the Intel DCAP libraries and tools (for -DCAP based quote verification). +:program:`verify_ias_report` tools shipped together with Gramine (for the EPID +attestation scheme) or to use the Intel DCAP libraries and tools (for the DCAP +attestation scheme). Gramine provides the RA-TLS and Secret Provisioning +libraries for the MAA attestation scheme; no separate tools are currently +provided. The ``/dev/attestation`` pseudo-filesystem also exposes pseudo-files to set the encryption keys (see also :doc:`manifest-syntax`): @@ -222,6 +253,7 @@ encryption keys (see also :doc:`manifest-syntax`): 32-character hex value, and the new files (``/dev/attestation/keys/``) use a 16-byte raw binary value. + Mid-level RA-TLS interface -------------------------- @@ -260,8 +292,13 @@ Also, notice how the SGX report's REPORTDATA field contains the secure hash of the ephemeral public key generated by the enclavized application -- this is how this RA-TLS certificate is tied to the enclavized application that generated it. -RA-TLS is shipped as three libraries: ``ra_tls_attest.so``, EPID based -``ra_tls_verify_epid.so`` and DCAP/ECDSA based ``ra_tls_verify_dcap.so``. +RA-TLS is shipped as four libraries: + +- ``ra_tls_attest.so`` for the attesting side, +- three versions of the verification library for the verifying side: EPID based + ``ra_tls_verify_epid.so``, DCAP based ``ra_tls_verify_dcap.so`` and MAA based + ``ra_tls_verify_maa.so``. + The interfaces exposed by these libraries can be found in the following header: :file:`Pal/src/host/Linux-SGX/tools/ra-tls/ra_tls.h`. @@ -282,7 +319,7 @@ attestation: - ``sgx.ra_client_spid`` -- client SPID for EPID remote attestation. - ``sgx.ra_client_linkable`` -- client linkable/unlinkable attestation mode. -For DCAP/ECDSA based attestation, the library expects instead: +For DCAP/MAA based attestation, the library expects instead: - ``sgx.remote_attestation = true`` -- remote attestation is enabled. - ``sgx.ra_client_spid = ""`` -- hints that this is a DCAP attestation, *not* @@ -375,6 +412,29 @@ SGX measurements may be verified via a user-specified callback registered via The library expects all the DCAP infrastructure to be installed and working correctly on the host. +``ra_tls_verify_maa.so`` +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Similarly to ``ra_tls_verify_epid.so``, this library contains the verification +callback that should be registered with the TLS library during verification of +the TLS certificate. It verifies the RA-TLS certificate and the SGX quote by +sending it to the Microsoft Azure Attestation (MAA) provider and retrieving the +attestation response (the JWT) from it. This library is *not* thread-safe. + +The library uses the same SGX-specific environment variables as +``ra_tls_verify_epid.so`` and ignores the EPID-specific environment variables. +Similarly to the MAA version, instead of using environment variables, the four +SGX measurements may be verified via a user-specified callback registered via +``ra_tls_set_measurement_callback()``. + +The library uses the following MAA-specific environment variables if available: + +- ``RA_TLS_MAA_PROVIDER_URL`` (mandatory) -- URL for MAA provider's REST API + endpoints. +- ``RA_TLS_MAA_PROVIDER_API_VERSION`` (optional) -- version of the MAA + provider's REST API ``attest/`` endpoint. If not specified, the default + hard-coded version ``2020-10-01`` is used. + High-level Secret Provisioning interface ---------------------------------------- @@ -407,13 +467,17 @@ The established TLS channel may be either closed after provisioning these initial secrets or may be further used by both parties for continued secure communication. -Secret Provisioning is shipped as three libraries: ``secret_prov_attest.so``, -EPID based ``secret_prov_verify_epid.so`` and DCAP/ECDSA based -``secret_prov_verify_dcap.so``. +Secret Provisioning is shipped as four libraries: + +- ``secret_prov_attest.so`` for the attesting side, +- three versions of the verification library for the verifying side: EPID based + ``secret_prov_verify_epid.so``, DCAP based ``secret_prov_verify_dcap.so`` and + MAA based ``secret_prov_verify_maa.so``. -The examples of using RA-TLS can be found under ``CI-Examples/ra-tls-secret-prov``. -The examples include minimalistic provisioning of constant-string secrets as -well as provisioning of an encryption key and its later use for encrypted files. +The examples of using the Secret Provisioning library can be found under +``CI-Examples/ra-tls-secret-prov``. The examples include minimalistic +provisioning of constant-string secrets as well as provisioning of an encryption +key and its later use for encrypted files. ``secret_prov_attest.so`` ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -491,6 +555,17 @@ The library uses the same SGX-specific environment variables as variables. The library expects all the DCAP infrastructure to be installed and working correctly on the host. +``secret_prov_verify_maa.so`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Similarly to ``secret_prov_verify_epid.so``, this library is used in +secret-provisioning services. The only difference is that this library uses +MAA based RA-TLS flows underneath. + +The library uses the same SGX-specific environment variables as +``secret_prov_verify_epid.so``, ignores the EPID-specific environment +variables and expects instead the MAA-specific environment variables. + .. _third_party_solutions: diff --git a/Documentation/img/maa.svg b/Documentation/img/maa.svg new file mode 100644 index 0000000000..a15c12adff --- /dev/null +++ b/Documentation/img/maa.svg @@ -0,0 +1 @@ +User appGramine LibOSUntrusted MS Azure VMTrusted machineSGX Quote VerifierExpected measure-ments/dev/attestation/user_report_dataquoteEREPORTQuoting EnclaveProvisioning Enclave(already done by MS Azure)(already done byMS Azure)Microsoft Azure Attestation (MAA) providerAttestation certificatesIntel® Provisioning Certification Service (Intel PCS) \ No newline at end of file diff --git a/Documentation/sgx-intro.rst b/Documentation/sgx-intro.rst index d7c9a7436c..121fcbaa7d 100644 --- a/Documentation/sgx-intro.rst +++ b/Documentation/sgx-intro.rst @@ -265,6 +265,9 @@ SGX terminology A |~| way to launch enclaves with Intel's infrastructure, intended for client machines. + :term:`MAA` + A |~| way to launch enclaves in the Microsoft Azure public cloud. + Enclave .. todo:: TBD @@ -314,9 +317,12 @@ SGX terminology .. seealso:: :term:`DCAP` - A way to launch enclaves without relying on the Intel's + A |~| way to launch enclaves without relying on the Intel's infrastructure. + :term:`MAA` + A |~| way to launch enclaves in the Microsoft Azure public cloud. + :term:`SPID` An identifier one can obtain from Intel, required to make use of EPID attestation. @@ -380,6 +386,28 @@ SGX terminology Provisioning Certification Service, another Internet service provided by Intel. + Microsoft Azure Attestation + MAA + + MAA is the attestation protocol (attestation scheme) developed by + Microsoft and available in the Microsoft Azure public cloud. Similarly to + :term:`EPID`, the remote verifier making user of the MAA protocol needs to + contact the MAA attestation provider each time it wishes to attest an + enclave. An enclave sends DCAP-formatted SGX quotes to the client/verifier + who will forward them to the MAA attestation provider to check the + enclave's validity and receive back the set of claims describing this + enclave. + + .. seealso:: + + :term:`DCAP` + A |~| way to launch enclaves without relying on the Intel's + infrastructure. + + :term:`EPID` + A |~| way to launch enclaves with Intel's infrastructure, intended + for client machines. + Memory Encryption Engine MEE diff --git a/Pal/src/host/Linux-SGX/tools/ra-tls/meson.build b/Pal/src/host/Linux-SGX/tools/ra-tls/meson.build index 82997ed833..def7320b8f 100644 --- a/Pal/src/host/Linux-SGX/tools/ra-tls/meson.build +++ b/Pal/src/host/Linux-SGX/tools/ra-tls/meson.build @@ -151,4 +151,52 @@ if dcap 'ln -sf ../../../libsecret_prov_verify_dcap.so ' + '"$MESON_INSTALL_DESTDIR_PREFIX"/@0@/gramine/runtime/glibc/'.format( get_option('libdir'))) + + # Microsoft Azure Attestation (MAA) scheme uses DCAP SGX quotes, so it is + # located under the `dcap` Meson config + libra_tls_verify_maa = shared_library('ra_tls_verify_maa', + 'ra_tls_verify_maa.c', + 'ra_tls_verify_common.c', + 'ra_tls.h', + + c_args: ra_tls_args, + include_directories: sgx_inc, + dependencies: [ + cjson_dep, + libcurl_dep, + sgx_util_dep, + mbedtls_static_dep, + ], + install: true, + install_rpath: join_paths(get_option('prefix'), get_option('libdir')), + ) + meson.add_install_script('/bin/sh', '-c', + 'ln -sf ../../../libra_tls_verify_maa.so ' + + '"$MESON_INSTALL_DESTDIR_PREFIX"/@0@/gramine/runtime/glibc/'.format( + get_option('libdir'))) + + libsecret_prov_verify_maa = shared_library('secret_prov_verify_maa', + 'ra_tls_verify_maa.c', + 'ra_tls_verify_common.c', + 'secret_prov_verify.c', + 'secret_prov_common.c', + 'ra_tls.h', + 'secret_prov.h', + + c_args: ra_tls_args, + include_directories: sgx_inc, + dependencies: [ + threads_dep, + cjson_dep, + libcurl_dep, + sgx_util_dep, + mbedtls_static_dep, + ], + install: true, + install_rpath: join_paths(get_option('prefix'), get_option('libdir')), + ) + meson.add_install_script('/bin/sh', '-c', + 'ln -sf ../../../libsecret_prov_verify_maa.so ' + + '"$MESON_INSTALL_DESTDIR_PREFIX"/@0@/gramine/runtime/glibc/'.format( + get_option('libdir'))) endif diff --git a/Pal/src/host/Linux-SGX/tools/ra-tls/ra_tls.h b/Pal/src/host/Linux-SGX/tools/ra-tls/ra_tls.h index 45f8bc3ed0..c2e1135f8f 100644 --- a/Pal/src/host/Linux-SGX/tools/ra-tls/ra_tls.h +++ b/Pal/src/host/Linux-SGX/tools/ra-tls/ra_tls.h @@ -21,6 +21,9 @@ #define RA_TLS_IAS_REPORT_URL "RA_TLS_IAS_REPORT_URL" #define RA_TLS_IAS_SIGRL_URL "RA_TLS_IAS_SIGRL_URL" +#define RA_TLS_MAA_PROVIDER_URL "RA_TLS_MAA_PROVIDER_URL" +#define RA_TLS_MAA_PROVIDER_API_VERSION "RA_TLS_MAA_PROVIDER_API_VERSION" + #define RA_TLS_CERT_TIMESTAMP_NOT_BEFORE "RA_TLS_CERT_TIMESTAMP_NOT_BEFORE" #define RA_TLS_CERT_TIMESTAMP_NOT_AFTER "RA_TLS_CERT_TIMESTAMP_NOT_AFTER" diff --git a/Pal/src/host/Linux-SGX/tools/ra-tls/ra_tls_verify_maa.c b/Pal/src/host/Linux-SGX/tools/ra-tls/ra_tls_verify_maa.c new file mode 100644 index 0000000000..7d87d9ad17 --- /dev/null +++ b/Pal/src/host/Linux-SGX/tools/ra-tls/ra_tls_verify_maa.c @@ -0,0 +1,1138 @@ +/* SPDX-License-Identifier: LGPL-3.0-or-later */ +/* Copyright (C) 2022 Intel Labs */ + +/*! + * \file + * + * This file contains the implementation of a verification callback for TLS libraries. The callback + * verifies the correctness of a self-signed RA-TLS certificate with an SGX quote embedded in it. + * The callback accesses a specific attestation provider of the Microsoft Azure Attestation (MAA) + * for MAA-based attestation as part of the verification process. In particular, the callback sends + * the Attestation request (JSON string that embeds the SGX quote + Enclave Held Data) to MAA via + * HTTPS and receives an Attestation response (a JSON Web Token, or JWT, with claims). To ensure + * authenticity of the Attestation response, the callback also obtains a set of JSON Web Keys, or + * JWKs, from MAA and verifies the signature of JWT with the corresponding JWK's public key. + * + * The HTTPS Attestation request is sent to the URL in the format: + * POST {instanceUrl}/attest/SgxEnclave?api-version=2020-10-01 + * + * The HTTPS "Get set of JWKs" request is sent to the URL in the format: + * POST {instanceUrl}/certs/ + * + * {instanceUrl} is the attestation provider URL, e.g. `shareduks.uks.attest.azure.net`. + * + * This file is part of the RA-TLS verification library which is typically linked into client + * applications. This library is *not* thread-safe. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include +#include +#include + +#ifdef HAVE_INTERNAL_CJSON +/* here we -I the cJSON's repo root, which directly contains the header */ +#include +#else +#include +#endif + +#include "attestation.h" +#include "ra_tls.h" +#include "sgx_arch.h" +#include "sgx_attest.h" +#include "util.h" + +extern verify_measurements_cb_t g_verify_measurements_cb; + +#define MAA_URL_MAX_SIZE 256 + +/** MAA "Attest SGX Enclave" API endpoint. */ +#define MAA_URL_ATTEST_ENDPOINT "attest/SgxEnclave" + +/** MAA "Get Signing Certificates" API endpoint. */ +#define MAA_URL_CERTS_ENDPOINT "certs" + +/** Default API version for MAA API endpoints. */ +#define DEFAULT_MAA_PROVIDER_API_VERSION "2020-10-01" + +static char* g_maa_base_url = NULL; +static char* g_maa_api_version = NULL; + +/*! Context used in maa_*() calls */ +struct maa_context_t { + bool curl_global_init_done; + CURL* curl; /*!< CURL context for this session */ + struct curl_slist* headers; /*!< Request headers sent to MAA attestation provider */ +}; + +/*! MAA response (JWT token for `attest/` API, set of Signing keys for `certs/` API) */ +struct maa_response { + char* data; /*!< response (JSON string) */ + size_t data_size; /*!< size of \a token string */ +}; + +static void replace_char(uint8_t* buf, size_t buf_size, char find, char replace) { + while (*buf && buf_size > 0) { + if (*buf == find) + *buf = replace; + buf++; + buf_size--; + } +} + +/* mbedTLS currently doesn't implement base64url but only base64, so we introduce helpers */ +static int mbedtls_base64url_encode(uint8_t* dst, size_t dlen, size_t* olen, const uint8_t* src, + size_t slen) { + int ret = mbedtls_base64_encode(dst, dlen, olen, src, slen); + if (ret < 0 || dlen == 0) + return ret; + + /* dst contains base64-encoded string; replace `+` -> `-`, `/` -> `_`, `=` -> `\0` */ + replace_char(dst, dlen, '+', '-'); + replace_char(dst, dlen, '/', '_'); + replace_char(dst, dlen, '=', '\0'); + return 0; +} + +static int mbedtls_base64url_decode(uint8_t* dst, size_t dlen, size_t* olen, const uint8_t* src, + size_t slen) { + if (!src || slen == 0) { + /* that's what mbedtls_base64_decode() does in this case */ + *olen = 0; + return 0; + } + + size_t copied_slen = slen + (3 - (slen - 1) % 4); /* account for 4-byte padding */ + uint8_t* copied_src = calloc(1, copied_slen + 1); + memcpy(copied_src, src, slen); + + /* src contains base64url-encoded string; replace `-` -> `+`, `_` -> `/` and pad with `=` */ + replace_char(copied_src, copied_slen, '-', '+'); + replace_char(copied_src, copied_slen, '_', '/'); + memset(copied_src + slen, '=', copied_slen - slen); + + int ret = mbedtls_base64_decode(dst, dlen, olen, copied_src, copied_slen); + free(copied_src); + return ret; +} + +static int init_from_env(char** ptr, const char* env_name, const char* default_val) { + assert(ptr == &g_maa_base_url || ptr == &g_maa_api_version); + + if (*ptr) { + /* already initialized */ + return 0; + } + + char* env_val = getenv(env_name); + if (!env_val) { + if (!default_val) + return MBEDTLS_ERR_X509_BAD_INPUT_DATA; + + *ptr = strdup(default_val); + if (!*ptr) + return MBEDTLS_ERR_X509_ALLOC_FAILED; + + return 0; + } + + size_t env_val_size = strlen(env_val) + 1; + *ptr = malloc(env_val_size); + if (!*ptr) + return MBEDTLS_ERR_X509_ALLOC_FAILED; + + memcpy(*ptr, env_val, env_val_size); + return 0; +} + +/*! + * \brief Parse response headers of the MAA attestation response (currently none). + * + * \param[in] buffer Single HTTP header. + * \param[in] size Together with \a count a size of \a buffer. + * \param[in] count Size of \a buffer, in \a size units. + * \param[in] context User data pointer (of type struct maa_response). + * + * \returns \a size * \a count + * + * \details See cURL documentation at + * https://curl.haxx.se/libcurl/c/CURLOPT_HEADERFUNCTION.html + */ +static size_t header_callback(char* buffer, size_t size, size_t count, void* context) { + /* unused callback, always return success */ + (void)buffer; + (void)context; + return size * count; +} + +/*! + * \brief Add HTTP body chunk to internal buffer (contains JSON string). + * + * \param[in] buffer Chunk containing HTTP body. + * \param[in] size Together with \a count a size of \a buffer. + * \param[in] count Size of \a buffer, in \a size units. + * \param[in] context User data pointer (of type struct maa_response). + * + * \returns \a size * \a count + * + * \details See cURL documentation at + * https://curl.haxx.se/libcurl/c/CURLOPT_WRITEFUNCTION.html + */ +static size_t body_callback(char* buffer, size_t size, size_t count, void* context) { + size_t total_size = size * count; + + struct maa_response* response = context; + assert(response); + + /* make space for the data, plus terminating \0 */ + response->data = realloc(response->data, response->data_size + total_size + 1); + if (!response->data) { + exit(-ENOMEM); // no way to gracefully recover + } + + /* append the data (buffer) to response->data */ + memcpy(response->data + response->data_size, buffer, total_size); + response->data_size += total_size; + + /* add terminating `\0`, but don't count it in response->data_size to ease appending a next + * chunk (if any) */ + response->data[response->data_size] = '\0'; + + return total_size; +} + +static void response_cleanup(struct maa_response* response) { + free(response->data); + free(response); +} + +static void maa_cleanup(struct maa_context_t* context) { + if (!context) + return; + + if (context->headers) + curl_slist_free_all(context->headers); + + if (context->curl) + curl_easy_cleanup(context->curl); + + /* every curl_global_init() must have a corresponding curl_global_cleanup() */ + if (context->curl_global_init_done) + curl_global_cleanup(); + + free(context); +} + +static int maa_init(struct maa_context_t** out_context) { + int ret; + + struct maa_context_t* context = calloc(1, sizeof(*context)); + if (!context) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + /* can be called multiple times */ + CURLcode curl_ret = curl_global_init(CURL_GLOBAL_ALL); + if (curl_ret != CURLE_OK) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + context->curl_global_init_done = true; + + context->curl = curl_easy_init(); + if (!context->curl) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + curl_ret = curl_easy_setopt(context->curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); + if (curl_ret != CURLE_OK) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + curl_ret = curl_easy_setopt(context->curl, CURLOPT_SSL_VERIFYPEER, 1L); + if (curl_ret != CURLE_OK) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + context->headers = curl_slist_append(context->headers, "Content-Type: application/json"); + if (!context->headers) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + curl_ret = curl_easy_setopt(context->curl, CURLOPT_HTTPHEADER, context->headers); + if (curl_ret != CURLE_OK) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + curl_ret = curl_easy_setopt(context->curl, CURLOPT_HEADERFUNCTION, header_callback); + if (curl_ret != CURLE_OK) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + curl_ret = curl_easy_setopt(context->curl, CURLOPT_WRITEFUNCTION, body_callback); + if (curl_ret != CURLE_OK) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + *out_context = context; + ret = 0; +out: + if (ret < 0) { + maa_cleanup(context); + } + return ret; +} + +/*! Send GET request (empty) to MAA attestation provider's `certs/` API endpoint and save the + * resulting set of JWKs \a out_set_of_jwks; caller is responsible for its cleanup */ +static int maa_get_signing_certs(struct maa_context_t* context, char** out_set_of_jwks) { + int ret; + + char* request_url = NULL; + struct maa_response* response = NULL; + + /* prepare sending "GET certs" to MAA and receiving a response (using Curl) */ + response = calloc(1, sizeof(*response)); + if (!response) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + request_url = malloc(MAA_URL_MAX_SIZE); + if (!request_url) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + ret = snprintf(request_url, MAA_URL_MAX_SIZE, "%s/%s/", g_maa_base_url, MAA_URL_CERTS_ENDPOINT); + if (ret < 0 || (size_t)ret >= MAA_URL_MAX_SIZE) { + ret = MBEDTLS_ERR_X509_BUFFER_TOO_SMALL; + goto out; + } + + CURLcode curl_ret; + curl_ret = curl_easy_setopt(context->curl, CURLOPT_URL, request_url); + if (curl_ret != CURLE_OK) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + curl_ret = curl_easy_setopt(context->curl, CURLOPT_HTTPGET, 1); + if (curl_ret != CURLE_OK) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + curl_ret = curl_easy_setopt(context->curl, CURLOPT_HEADERDATA, response); + if (curl_ret != CURLE_OK) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + curl_ret = curl_easy_setopt(context->curl, CURLOPT_WRITEDATA, response); + if (curl_ret != CURLE_OK) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + /* send the "GET certs" request, callbacks will store results in `response` */ + curl_ret = curl_easy_perform(context->curl); + if (curl_ret != CURLE_OK) { + ERROR("Failed to send the MAA \"GET certs\" request to `%s`\n", request_url); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + long response_code; + curl_ret = curl_easy_getinfo(context->curl, CURLINFO_RESPONSE_CODE, &response_code); + if (curl_ret != CURLE_OK) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + if (response_code != 200) { + ERROR("MAA \"GET certs\" request failed with code %ld and message `%s`\n", response_code, + response->data); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + if (!response->data) { + ERROR("MAA \"GET certs\" response doesn't have the set of JSON Web Keys (JWKs)\n"); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + char* set_of_jwks = strdup(response->data); + if (!set_of_jwks) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + *out_set_of_jwks = set_of_jwks; + ret = 0; +out: + response_cleanup(response); + free(request_url); + return ret; +} + +/*! Send request (with \a quote embedded in it) to MAA attestation provider's `attest/` API endpoint + * and save response in \a out_maa_response; caller is responsible for its cleanup */ +static int maa_send_request(struct maa_context_t* context, const void* quote, size_t quote_size, + const void* runtime_data, size_t runtime_data_size, + struct maa_response** out_maa_response) { + int ret; + + char* quote_b64 = NULL; + char* runtime_data_b64 = NULL; + char* request_json = NULL; + char* request_url = NULL; + + struct maa_response* response = NULL; + + /* get needed base64url buffer size for quote, allocate it and encode the quote */ + size_t quote_b64_size = 0; + ret = mbedtls_base64url_encode(/*dest=*/NULL, /*dlen=*/0, "e_b64_size, quote, quote_size); + if (ret != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) { + goto out; + } + + quote_b64 = malloc(quote_b64_size); + if (!quote_b64) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + ret = mbedtls_base64url_encode((uint8_t*)quote_b64, quote_b64_size, "e_b64_size, quote, + quote_size); + if (ret < 0) { + goto out; + } + + /* get needed base64url buffer size for runtime data, allocate it and encode the runtime data */ + size_t runtime_data_b64_size = 0; + ret = mbedtls_base64url_encode(/*dest=*/NULL, /*dlen=*/0, &runtime_data_b64_size, runtime_data, + runtime_data_size); + if (ret != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) { + goto out; + } + + runtime_data_b64 = malloc(runtime_data_b64_size); + if (!runtime_data_b64) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + ret = mbedtls_base64url_encode((uint8_t*)runtime_data_b64, runtime_data_b64_size, + &runtime_data_b64_size, runtime_data, runtime_data_size); + if (ret < 0) { + goto out; + } + + /* construct JSON string with the attestation request to MAA */ + const char* request_json_fmt = "{\"quote\": \"%s\", \"runtimeData\": " + " {\"data\": \"%s\", \"dataType\": \"Binary\"} }"; + + size_t request_json_size = strlen(request_json_fmt) + 1 + quote_b64_size + + runtime_data_b64_size; + request_json = malloc(request_json_size); + if (!request_json) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + ret = snprintf(request_json, request_json_size, request_json_fmt, quote_b64, runtime_data_b64); + if (ret < 0 || (size_t)ret >= request_json_size) { + ret = MBEDTLS_ERR_X509_BUFFER_TOO_SMALL; + goto out; + } + + /* prepare sending attestation request to MAA and receiving a response (using Curl) */ + response = calloc(1, sizeof(*response)); + if (!response) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + request_url = malloc(MAA_URL_MAX_SIZE); + if (!request_url) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + ret = snprintf(request_url, MAA_URL_MAX_SIZE, "%s/" MAA_URL_ATTEST_ENDPOINT "?api-version=%s", + g_maa_base_url, g_maa_api_version); + if (ret < 0 || (size_t)ret >= MAA_URL_MAX_SIZE) { + ret = MBEDTLS_ERR_X509_BUFFER_TOO_SMALL; + goto out; + } + + CURLcode curl_ret; + curl_ret = curl_easy_setopt(context->curl, CURLOPT_URL, request_url); + if (curl_ret != CURLE_OK) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + curl_ret = curl_easy_setopt(context->curl, CURLOPT_POST, 1); + if (curl_ret != CURLE_OK) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + curl_ret = curl_easy_setopt(context->curl, CURLOPT_POSTFIELDS, request_json); + if (curl_ret != CURLE_OK) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + curl_ret = curl_easy_setopt(context->curl, CURLOPT_HEADERDATA, response); + if (curl_ret != CURLE_OK) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + curl_ret = curl_easy_setopt(context->curl, CURLOPT_WRITEDATA, response); + if (curl_ret != CURLE_OK) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + /* send the attestation request, callbacks will store results in `response` */ + curl_ret = curl_easy_perform(context->curl); + if (curl_ret != CURLE_OK) { + ERROR("Failed to send the MAA Attestation request to `%s`\n", request_url); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + long response_code; + curl_ret = curl_easy_getinfo(context->curl, CURLINFO_RESPONSE_CODE, &response_code); + if (curl_ret != CURLE_OK) { + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + if (response_code != 200) { + ERROR("MAA Attestation request failed with code %ld and message `%s`\n", response_code, + response->data); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + if (!response->data) { + ERROR("MAA Attestation response doesn't have the JSON Web Token (JWT)\n"); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + *out_maa_response = response; + ret = 0; + +out: + if (ret < 0 && response) { + response_cleanup(response); + } + free(quote_b64); + free(runtime_data_b64); + free(request_json); + free(request_url); + return ret; +} + +/*! Verify the attestation response from MAA (the JWT token) and create a dummy SGX quote populated + * with the SGX-enclave measurements from this response in \a out_quote_body; caller is responsible + * for its cleanup */ +static int maa_verify_response_output_quote(struct maa_response* response, const char* set_of_jwks, + sgx_quote_body_t** out_quote_body) { + int ret; + + sgx_quote_body_t* quote_body = NULL; + + char* maa_certs_url = NULL; + + cJSON* json_response = NULL; + cJSON* json_token_header = NULL; + cJSON* json_token_payload = NULL; + cJSON* json_jwks = NULL; + + char* token_b64_header = NULL; + char* token_b64_payload = NULL; + char* token_b64_signature = NULL; + + char* token_header = NULL; + char* token_payload = NULL; + char* token_signature = NULL; + + char* token_signing_x509cert_b64 = NULL; /* not allocated, so no need to free it */ + char* token_signing_x509cert = NULL; + + mbedtls_md_context_t md_context; + mbedtls_md_init(&md_context); + + mbedtls_x509_crt token_signing_crt; + mbedtls_x509_crt_init(&token_signing_crt); + + json_response = cJSON_Parse(response->data); + if (!json_response) { + ERROR("MAA Attestation response is not proper JSON\n"); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + cJSON* token_b64 = cJSON_GetObjectItem(json_response, "token"); + if (!cJSON_IsString(token_b64)) { + ERROR("MAA Attestation response doesn't contain the `token` string key (JWT)\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + + /* JWT tokens are strings in the format: xxx.yyy.zzz where xxx, yyy, zzz are the header, the + * payload, and the signature correspondingly (each base64url encoded) */ + char* header_begin_in_token_b64 = token_b64->valuestring; + char* header_end_in_token_b64 = strchr(header_begin_in_token_b64, '.'); + if (!header_end_in_token_b64) { + ERROR("MAA JWT is incorrectly formatted (cannot find the header)\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + + token_b64_header = calloc(1, header_end_in_token_b64 - header_begin_in_token_b64 + 1); + if (!token_b64_header) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + memcpy(token_b64_header, header_begin_in_token_b64, + header_end_in_token_b64 - header_begin_in_token_b64); + + char* payload_begin_in_token_b64 = header_end_in_token_b64 + 1; + char* payload_end_in_token_b64 = strchr(payload_begin_in_token_b64, '.'); + if (!payload_end_in_token_b64) { + ERROR("MAA JWT is incorrectly formatted (cannot find the payload)\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + + token_b64_payload = calloc(1, payload_end_in_token_b64 - payload_begin_in_token_b64 + 1); + if (!token_b64_payload) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + memcpy(token_b64_payload, payload_begin_in_token_b64, + payload_end_in_token_b64 - payload_begin_in_token_b64); + + char* signature_begin_in_token_b64 = payload_end_in_token_b64 + 1; + token_b64_signature = strdup(signature_begin_in_token_b64); + if (!token_b64_signature) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + size_t token_header_size; + ret = mbedtls_base64url_decode(/*dest=*/NULL, /*dlen=*/0, &token_header_size, + (const uint8_t*)token_b64_header, strlen(token_b64_header)); + if (ret != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) { + goto out; + } + + token_header = calloc(1, token_header_size + 1); + if (!token_header) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + ret = mbedtls_base64url_decode((uint8_t*)token_header, token_header_size, &token_header_size, + (const uint8_t*)token_b64_header, strlen(token_b64_header)); + if (ret < 0) { + ERROR("MAA JWT is incorrectly formatted (the header is not Base64Url encoded)\n"); + goto out; + } + + size_t token_payload_size; + ret = mbedtls_base64url_decode(/*dest=*/NULL, /*dlen=*/0, &token_payload_size, + (const uint8_t*)token_b64_payload, strlen(token_b64_payload)); + if (ret != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) { + goto out; + } + + token_payload = calloc(1, token_payload_size + 1); + if (!token_payload) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + ret = mbedtls_base64url_decode((uint8_t*)token_payload, token_payload_size, &token_payload_size, + (const uint8_t*)token_b64_payload, strlen(token_b64_payload)); + if (ret < 0) { + ERROR("MAA JWT is incorrectly formatted (the payload is not Base64Url encoded)\n"); + goto out; + } + + size_t token_signature_size; + ret = mbedtls_base64url_decode(/*dest=*/NULL, /*dlen=*/0, &token_signature_size, + (const uint8_t*)token_b64_signature, + strlen(token_b64_signature)); + if (ret != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) { + goto out; + } + + token_signature = calloc(1, token_signature_size + 1); + if (!token_signature) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + ret = mbedtls_base64url_decode((uint8_t*)token_signature, token_signature_size, + &token_signature_size, (const uint8_t*)token_b64_signature, + strlen(token_b64_signature)); + if (ret < 0) { + ERROR("MAA JWT is incorrectly formatted (the signature is not Base64Url encoded)\n"); + goto out; + } + + /* at this point, we parsed JWT into three decoded strings: token_header, token_payload, + * token_signature; the first two are JSON strings */ + json_token_header = cJSON_Parse(token_header); + if (!json_token_header) { + ERROR("MAA JWT is incorrectly formatted (the header is not proper JSON)\n"); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + cJSON* token_header_alg = cJSON_GetObjectItem(json_token_header, "alg"); + cJSON* token_header_kid = cJSON_GetObjectItem(json_token_header, "kid"); + cJSON* token_header_typ = cJSON_GetObjectItem(json_token_header, "typ"); + cJSON* token_header_jku = cJSON_GetObjectItem(json_token_header, "jku"); + + /* currently only support JWTs with RSA-SHA256 signing */ + if (!cJSON_IsString(token_header_alg) || strcmp(token_header_alg->valuestring, "RS256") || + !cJSON_IsString(token_header_typ) || strcmp(token_header_typ->valuestring, "JWT") || + !cJSON_IsString(token_header_kid)) { + ERROR("MAA JWT header's `alg`, `typ` and/or `kid` fields contain unrecognized values\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + + /* verify that we got the set of JWKs from the same endpoint as contained in `jku`; note that + * `jku` field doesn't have the trailing slash */ + maa_certs_url = malloc(MAA_URL_MAX_SIZE); + if (!maa_certs_url) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + ret = snprintf(maa_certs_url, MAA_URL_MAX_SIZE, "%s/%s", g_maa_base_url, + MAA_URL_CERTS_ENDPOINT); + if (ret < 0 || (size_t)ret >= MAA_URL_MAX_SIZE) { + ret = MBEDTLS_ERR_X509_BUFFER_TOO_SMALL; + goto out; + } + + if (!cJSON_IsString(token_header_jku) || strcmp(token_header_jku->valuestring, maa_certs_url)) { + ERROR("MAA JWT header's `jku` field contains an unexpected URL (got `%s`, expected `%s`)\n", + token_header_jku->valuestring, maa_certs_url); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + + json_token_payload = cJSON_Parse(token_payload); + if (!json_token_payload) { + ERROR("MAA JWT is incorrectly formatted (the payload is not proper JSON)\n"); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + /* json_token_header["kid"] contains an ID that should be found in `set_of_jwks`, so let's parse + * the latter, find the corresponding array item and extract the X.509 cert from `x5c` field */ + json_jwks = cJSON_Parse(set_of_jwks); + if (!json_jwks) { + ERROR("MAA set of JWKs is incorrectly formatted (the set is not proper JSON)\n"); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + cJSON* keys_json_array = cJSON_GetObjectItem(json_jwks, "keys"); + if (!cJSON_IsArray(keys_json_array)) { + ERROR("MAA set of JWKs doesn't contain the `keys` JSON array\n"); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + token_signing_x509cert_b64 = NULL; /* for sanity */ + const cJSON* key_json = NULL; + cJSON_ArrayForEach(key_json, keys_json_array) { + /* in practice, the `certs/` API endpoint doesn't have `use` and `alg` fields */ + cJSON* key_kty = cJSON_GetObjectItem(key_json, "kty"); + cJSON* key_kid = cJSON_GetObjectItem(key_json, "kid"); + cJSON* key_x5c = cJSON_GetObjectItem(key_json, "x5c"); + + /* currently only support RSA keys */ + if (!cJSON_IsString(key_kty) || strcmp(key_kty->valuestring, "RSA")) { + ERROR("MAA JWK's `kty` field contains an unexpected value (got `%s`, expected `%s`)\n", + key_kty->valuestring, "RSA"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + + if (!cJSON_IsString(key_kid) || !cJSON_IsArray(key_x5c) || !cJSON_GetArraySize(key_x5c)) { + ERROR("MAA JWK's `kid` and/or `x5c` fields have incorrect types\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + + /* compare kid from the set of JWKs with the one in JWT */ + if (!strcmp(key_kid->valuestring, token_header_kid->valuestring)) { + cJSON* key_first_x509cert = cJSON_GetArrayItem(key_x5c, 0); + if (!cJSON_IsString(key_first_x509cert)) { + ERROR("MAA JWK's `x5c` is not an array of string-value X.509 certificates\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + + token_signing_x509cert_b64 = key_first_x509cert->valuestring; + break; + } + } + + if (!token_signing_x509cert_b64) { + ERROR("Failed to find a corresponding JWK for the JWT received from MAA\n"); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + /* note that "x5c" field is *not* base64url encoded */ + size_t token_signing_x509cert_size = 0; + ret = mbedtls_base64_decode(/*dest=*/NULL, /*dlen=*/0, &token_signing_x509cert_size, + (const uint8_t*)token_signing_x509cert_b64, + strlen(token_signing_x509cert_b64)); + if (ret != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) { + goto out; + } + + token_signing_x509cert = malloc(token_signing_x509cert_size); + if (!token_signing_x509cert) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + ret = mbedtls_base64_decode((uint8_t*)token_signing_x509cert, token_signing_x509cert_size, + &token_signing_x509cert_size, + (const uint8_t*)token_signing_x509cert_b64, + strlen(token_signing_x509cert_b64)); + if (ret < 0) { + ERROR("MAA JWK's certificate is incorrectly formatted (not Base64 encoded)\n"); + goto out; + } + + ret = mbedtls_x509_crt_parse(&token_signing_crt, (const uint8_t*)token_signing_x509cert, + token_signing_x509cert_size); + if (ret < 0) { + ERROR("MAA JWK's certificate is incorrectly formatted (not a proper X.509 cert)\n"); + goto out; + } + + /* perform signature verification of attestation token using the public key from the self-signed + * certificate obtained from `certs/` MAA API endpoint */ + uint8_t md_sha256[32]; + mbedtls_md_setup(&md_context, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), /*hmac=*/0); + mbedtls_md_starts(&md_context); + mbedtls_md_update(&md_context, (const uint8_t*)token_b64_header, strlen(token_b64_header)); + mbedtls_md_update(&md_context, (const uint8_t*)".", 1); + mbedtls_md_update(&md_context, (const uint8_t*)token_b64_payload, + strlen(token_b64_payload)); + mbedtls_md_finish(&md_context, md_sha256); + + ret = mbedtls_pk_verify(&token_signing_crt.pk, MBEDTLS_MD_SHA256, md_sha256, sizeof(md_sha256), + (const uint8_t*)token_signature, token_signature_size); + if (ret < 0) { + ERROR("Failed signature verification of JWT using the JWK's certificate\n"); + goto out; + } + + /* we verified the header and the signature of the received JWT, can trust its payload */ + cJSON* x_ms_ver = cJSON_GetObjectItem(json_token_payload, "x-ms-ver"); + cJSON* x_ms_type = cJSON_GetObjectItem(json_token_payload, "x-ms-attestation-type"); + + cJSON* sgx_is_debuggable = cJSON_GetObjectItem(json_token_payload, "x-ms-sgx-is-debuggable"); + cJSON* sgx_mrenclave = cJSON_GetObjectItem(json_token_payload, "x-ms-sgx-mrenclave"); + cJSON* sgx_mrsigner = cJSON_GetObjectItem(json_token_payload, "x-ms-sgx-mrsigner"); + cJSON* sgx_product_id = cJSON_GetObjectItem(json_token_payload, "x-ms-sgx-product-id"); + cJSON* sgx_svn = cJSON_GetObjectItem(json_token_payload, "x-ms-sgx-svn"); + cJSON* sgx_report_data = cJSON_GetObjectItem(json_token_payload, "x-ms-sgx-report-data"); + + /* XXX: we currently do not use/verify the following fields: x-ms-sgx-ehd, x-ms-sgx-config-id, + * x-ms-sgx-config-svn, x-ms-sgx-isv-extended-product-id, x-ms-sgx-isv-family-id, + * x-ms-sgx-collateral, x-ms-policy-hash */ + + if (!cJSON_IsString(x_ms_ver) || strcmp(x_ms_ver->valuestring, "1.0") || + !cJSON_IsString(x_ms_type) || strcmp(x_ms_type->valuestring, "sgx")) { + ERROR("MAA JWT payload's `x-ms-ver` and/or `x-ms-attestation-type` fields contain " + "unrecognized values\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + + if (!cJSON_IsBool(sgx_is_debuggable) || !cJSON_IsString(sgx_mrenclave) || + !cJSON_IsString(sgx_mrsigner) || !cJSON_IsNumber(sgx_product_id) || + !cJSON_IsNumber(sgx_svn) || !cJSON_IsString(sgx_report_data)) { + ERROR("MAA JWT payload's `x-ms-sgx-is-debuggable`, `x-ms-sgx-mrenclave`, " + "`x-ms-sgx-mrsigner`, `x-ms-sgx-product-id`, `x-ms-sgx-svn` and/or " + "`x-ms-sgx-report-data` fields have incorrect types\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + + /* construct a dummy SGX quote (body) with contents takes from the JWT payload; this is for + * convenience because other functions in RA-TLS library operate on an SGX quote */ + quote_body = calloc(1, sizeof(*quote_body)); + if (!quote_body) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + quote_body->version = 3; /* DCAP; not strictly needed, just for sanity */ + + quote_body->report_body.attributes.flags = SGX_FLAGS_INITIALIZED | SGX_FLAGS_MODE64BIT; + if (cJSON_IsTrue(sgx_is_debuggable)) + quote_body->report_body.attributes.flags |= SGX_FLAGS_DEBUG; + + ret = parse_hex(sgx_mrenclave->valuestring, "e_body->report_body.mr_enclave, + sizeof(quote_body->report_body.mr_enclave), /*mask=*/NULL); + if (ret < 0) { + ERROR("MAA JWT payload's `x-ms-sgx-mrenclave` field is not hex encoded\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + + ret = parse_hex(sgx_mrsigner->valuestring, "e_body->report_body.mr_signer, + sizeof(quote_body->report_body.mr_signer), /*mask=*/NULL); + if (ret < 0) { + ERROR("MAA JWT payload's `x-ms-sgx-mrsigner` field is not hex encoded\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + + if (sgx_product_id->valueint == INT_MAX || sgx_product_id->valueint == INT_MIN) { + ERROR("MAA JWT payload's `x-ms-sgx-product-id` field is not an integer\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + quote_body->report_body.isv_prod_id = sgx_product_id->valueint; + + if (sgx_svn->valueint == INT_MAX || sgx_svn->valueint == INT_MIN) { + ERROR("MAA JWT payload's `x-ms-sgx-svn` field is not an integer\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + quote_body->report_body.isv_svn = sgx_svn->valueint; + + ret = parse_hex(sgx_report_data->valuestring, "e_body->report_body.report_data, + sizeof(quote_body->report_body.report_data), /*mask=*/NULL); + if (ret < 0) { + ERROR("MAA JWT payload's `x-ms-sgx-report-data` field is not hex encoded\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + + *out_quote_body = quote_body; + ret = 0; +out: + if (ret < 0) { + free(quote_body); + } + + if (json_response) + cJSON_Delete(json_response); + if (json_token_header) + cJSON_Delete(json_token_header); + if (json_token_payload) + cJSON_Delete(json_token_payload); + if (json_jwks) + cJSON_Delete(json_jwks); + + free(token_b64_header); + free(token_b64_payload); + free(token_b64_signature); + + free(token_header); + free(token_payload); + free(token_signature); + + free(maa_certs_url); + free(token_signing_x509cert); + mbedtls_x509_crt_free(&token_signing_crt); + mbedtls_md_free(&md_context); + return ret; +} + +/*! parse the public key \p pk into DER format and copy it into \p pk_der */ +static int parse_pk(mbedtls_pk_context* pk, uint8_t* out_pk_der) { + /* below function writes data at the end of the buffer */ + int pk_der_size_byte = mbedtls_pk_write_pubkey_der(pk, out_pk_der, PUB_KEY_SIZE_MAX); + if (pk_der_size_byte != RSA_PUB_3072_KEY_DER_LEN) + return MBEDTLS_ERR_PK_INVALID_PUBKEY; + + /* move the data to the beginning of the buffer, to avoid pointer arithmetic later */ + memmove(out_pk_der, out_pk_der + PUB_KEY_SIZE_MAX - pk_der_size_byte, pk_der_size_byte); + return 0; +} + +int ra_tls_verify_callback(void* data, mbedtls_x509_crt* crt, int depth, uint32_t* flags) { + (void)data; + + int ret; + + struct maa_context_t* context = NULL; + struct maa_response* response = NULL; + char* set_of_jwks = NULL; + + sgx_quote_body_t* quote_from_maa = NULL; + + if (depth != 0) { + /* the cert chain in RA-TLS consists of single self-signed cert, so we expect depth 0 */ + return MBEDTLS_ERR_X509_INVALID_FORMAT; + } + + if (flags) { + /* mbedTLS sets flags to signal that the cert is not to be trusted (e.g., it is not + * correctly signed by a trusted CA; since RA-TLS uses self-signed certs, we don't care + * what mbedTLS thinks and ignore internal cert verification logic of mbedTLS */ + *flags = 0; + } + + ret = init_from_env(&g_maa_base_url, RA_TLS_MAA_PROVIDER_URL, /*default_val=*/NULL); + if (ret < 0) { + ERROR("Failed to read the environment variable RA_TLS_MAA_PROVIDER_URL\n"); + goto out; + } + + ret = init_from_env(&g_maa_api_version, RA_TLS_MAA_PROVIDER_API_VERSION, + DEFAULT_MAA_PROVIDER_API_VERSION); + if (ret < 0) { + ERROR("Failed to read the environment variable RA_TLS_MAA_PROVIDER_API_VERSION\n"); + goto out; + } + + /* extract SGX quote from "quote" OID extension from crt */ + sgx_quote_t* quote; + size_t quote_size; + ret = find_oid(crt->v3_ext.p, crt->v3_ext.len, quote_oid, quote_oid_len, (uint8_t**)"e, + "e_size); + if (ret < 0) + goto out; + + if (quote_size < sizeof(*quote)) { + ret = MBEDTLS_ERR_X509_INVALID_EXTENSIONS; + goto out; + } + + /* compare public key's hash from cert against quote's report_data */ + ret = cmp_crt_pk_against_quote_report_data(crt, quote); + if (ret < 0) + goto out; + + /* parse the public key of the received certificate into DER format -- it should be put into the + * Attestation request's `runtimeData` field (MAA will take a SHA256 hash over it and verify + * against the first 32 bytes of the SGX quote's report_data field) */ + uint8_t pk_der[PUB_KEY_SIZE_MAX] = {0}; + ret = parse_pk(&crt->pk, pk_der); + if (ret < 0) + goto out; + + /* initialize the MAA context, get the set of JWKs from the `certs/` MAA API endpoint, send the + * SGX quote to the `attest/` MAA API endpoint, and finally receive and verify the attestation + * response (JWT) */ + ret = maa_init(&context); + if (ret < 0) { + goto out; + } + + /* a set of JWKs may change over time, so we better get them every time */ + ret = maa_get_signing_certs(context, &set_of_jwks); + if (ret < 0) { + goto out; + } + + ret = maa_send_request(context, quote, quote_size, pk_der, RSA_PUB_3072_KEY_DER_LEN, &response); + if (ret < 0) { + goto out; + } + assert(response && response->data); + + /* The attestation response is JWT -- we need to verify its signature using one of the set of + * JWKs, as well as verify its header and payload, and construct an SGX quote from the + * JWT-payload values to be used in further `verify_*` functions */ + ret = maa_verify_response_output_quote(response, set_of_jwks, "e_from_maa); + if (ret < 0) { + ret = MBEDTLS_ERR_X509_CERT_VERIFY_FAILED; + goto out; + } + + /* verify that the SGX quote sent to MAA has the same measurements as the constructed from the + * MAA's JWT payload -- just for sanity */ + sgx_report_body_t* orig_body = "e->body.report_body; + sgx_report_body_t* maa_body = "e_from_maa->report_body; + if (memcmp(&orig_body->report_data, &maa_body->report_data, sizeof(orig_body->report_data)) || + memcmp(&orig_body->mr_enclave, &maa_body->mr_enclave, sizeof(orig_body->mr_enclave)) || + memcmp(&orig_body->mr_signer, &maa_body->mr_signer, sizeof(orig_body->mr_signer))) { + ERROR("Failed verification of JWT's SGX measurements against the original SGX quote's " + "measurements (for sanity)\n"); + ret = MBEDTLS_ERR_X509_CERT_VERIFY_FAILED; + goto out; + } + + /* verify enclave attributes from the SGX quote body, including the user-supplied verification + * parameter "allow debug enclave"; NOTE: "allow outdated TCB" parameter is not used in MAA */ + ret = verify_quote_body_enclave_attributes(quote_from_maa, getenv_allow_debug_enclave()); + if (ret < 0) { + ERROR("Failed verification of JWT's SGX enclave attributes\n"); + ret = MBEDTLS_ERR_X509_CERT_VERIFY_FAILED; + goto out; + } + + /* verify other relevant enclave information from the SGX quote */ + if (g_verify_measurements_cb) { + /* use user-supplied callback to verify measurements */ + ret = g_verify_measurements_cb((const char*)"e_from_maa->report_body.mr_enclave, + (const char*)"e_from_maa->report_body.mr_signer, + (const char*)"e_from_maa->report_body.isv_prod_id, + (const char*)"e_from_maa->report_body.isv_svn); + } else { + /* use default logic to verify measurements */ + ret = verify_quote_body_against_envvar_measurements(quote_from_maa); + } + if (ret < 0) { + ERROR("Failed verification of JWT's SGX measurements\n"); + ret = MBEDTLS_ERR_X509_CERT_VERIFY_FAILED; + goto out; + } + + ret = 0; +out: + if (context) + maa_cleanup(context); + + if (response) + response_cleanup(response); + + free(set_of_jwks); + free(quote_from_maa); + return ret; +}