diff --git a/CI-Examples/busybox/README.md b/CI-Examples/busybox/README.md index f599683b26..45fe94dddc 100644 --- a/CI-Examples/busybox/README.md +++ b/CI-Examples/busybox/README.md @@ -17,6 +17,12 @@ a working DCAP setup. Then build the example as follows: make SGX=1 RA_TYPE=dcap ``` +If you run in Microsoft Azure cloud with a Confidential Computing offerring and +want to build the example for MAA attestation, build it as follows: +``` +make SGX=1 RA_TYPE=maa +``` + Otherwise, you will probably want to use EPID attestation. For this, you will additionally need to provide an SPID and specify whether it is set up for linkable quotes or not: diff --git a/CI-Examples/python/README.md b/CI-Examples/python/README.md index bcfb6c7bb8..b70602fe2c 100644 --- a/CI-Examples/python/README.md +++ b/CI-Examples/python/README.md @@ -30,6 +30,12 @@ a working DCAP setup. Then build the example as follows: make SGX=1 RA_TYPE=dcap ``` +If you run in Microsoft Azure cloud with a Confidential Computing offerring and +want to build the example for MAA attestation, build it as follows: +``` +make SGX=1 RA_TYPE=maa +``` + Otherwise, you will probably want to use EPID attestation. For this, you will additionally need to provide an SPID and specify whether it is set up for linkable quotes or not: diff --git a/CI-Examples/ra-tls-mbedtls/Makefile b/CI-Examples/ra-tls-mbedtls/Makefile index 5cfa6f6154..801889e79d 100644 --- a/CI-Examples/ra-tls-mbedtls/Makefile +++ b/CI-Examples/ra-tls-mbedtls/Makefile @@ -27,6 +27,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 @@ -127,6 +130,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 @@ -189,6 +212,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 >/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 --test-malicious-quote >/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 d1b2b98982..cfd39005ec 100644 --- a/CI-Examples/ra-tls-mbedtls/README.md +++ b/CI-Examples/ra-tls-mbedtls/README.md @@ -11,13 +11,23 @@ verification callback to verify the server RA-TLS certificate via `ra_tls_verify_callback_der()`. 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`). For DCAP attestation, the DCAP software infrastructure must be - installed and work correctly on the host. +`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`). + +For 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 (IAS) and ECDSA (DCAP) remote -attestation schemes. For more documentation, refer to +attestation schemes. 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 @@ -33,10 +43,10 @@ This is useful for testing 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 @@ -59,7 +69,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 +136,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 RA_TYPE=maa + +gramine-sgx ./server & + +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/MAA 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/src/client.c b/CI-Examples/ra-tls-mbedtls/src/client.c index 4d2f71ba87..9ae6a1744c 100644 --- a/CI-Examples/ra-tls-mbedtls/src/client.c +++ b/CI-Examples/ra-tls-mbedtls/src/client.c @@ -161,9 +161,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; } @@ -171,7 +171,8 @@ int main(int argc, char** argv) { ra_tls_verify_lib = dlopen("libra_tls_verify_epid.so", RTLD_LAZY); if (!ra_tls_verify_lib) { mbedtls_printf("%s\n", dlerror()); - mbedtls_printf("User requested RA-TLS verification with EPID but cannot find lib\n"); + mbedtls_printf("User requested RA-TLS verification with EPID but cannot find lib " + "libra_tls_verify_epid.so\n"); if (in_sgx) { mbedtls_printf("Please make sure that you are using client_epid.manifest\n"); } @@ -186,7 +187,8 @@ int main(int argc, char** argv) { ra_tls_verify_lib = dlopen("libra_tls_verify_dcap_gramine.so", RTLD_LAZY); if (!ra_tls_verify_lib) { mbedtls_printf("%s\n", dlerror()); - mbedtls_printf("User requested RA-TLS verification with DCAP inside SGX but cannot find lib\n"); + mbedtls_printf("User requested RA-TLS verification with DCAP inside SGX but cannot " + "find lib libra_tls_verify_dcap_gramine.so\n"); mbedtls_printf("Please make sure that you are using client_dcap.manifest\n"); return 1; } @@ -194,18 +196,35 @@ int main(int argc, char** argv) { 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 DCAP but cannot find helper" - " libsgx_urts.so lib\n"); + mbedtls_printf("User requested RA-TLS verification with DCAP but cannot find " + "helper libsgx_urts.so lib\n"); return 1; } ra_tls_verify_lib = dlopen("libra_tls_verify_dcap.so", RTLD_LAZY); if (!ra_tls_verify_lib) { mbedtls_printf("%s\n", dlerror()); - mbedtls_printf("User requested RA-TLS verification with DCAP but cannot find lib\n"); + mbedtls_printf("User requested RA-TLS verification with DCAP but cannot find lib " + "libra_tls_verify_dcap.so\n"); 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 " + "libra_tls_verify_maa.so\n"); + return 1; + } } if (ra_tls_verify_lib) { @@ -215,7 +234,8 @@ int main(int argc, char** argv) { return 1; } - ra_tls_set_measurement_callback_f = dlsym(ra_tls_verify_lib, "ra_tls_set_measurement_callback"); + ra_tls_set_measurement_callback_f = dlsym(ra_tls_verify_lib, + "ra_tls_set_measurement_callback"); if ((error = dlerror()) != NULL) { mbedtls_printf("%s\n", error); return 1; diff --git a/CI-Examples/ra-tls-mbedtls/src/server.c b/CI-Examples/ra-tls-mbedtls/src/server.c index d5c63ae87c..cb1095f2a2 100644 --- a/CI-Examples/ra-tls-mbedtls/src/server.c +++ b/CI-Examples/ra-tls-mbedtls/src/server.c @@ -118,7 +118,8 @@ int main(int argc, char** argv) { if (ret == -ENOENT || !strcmp(attestation_type_str, "none")) { ra_tls_attest_lib = NULL; ra_tls_create_key_and_crt_der_f = NULL; - } else if (!strcmp(attestation_type_str, "epid") || !strcmp(attestation_type_str, "dcap")) { + } else if (!strcmp(attestation_type_str, "epid") || !strcmp(attestation_type_str, "dcap") + || !strcmp(attestation_type_str, "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 cc4e300d31..a93d19078b 100644 --- a/CI-Examples/ra-tls-secret-prov/.gitignore +++ b/CI-Examples/ra-tls-secret-prov/.gitignore @@ -2,12 +2,15 @@ /secret_prov/client /secret_prov/server_epid /secret_prov/server_dcap +/secret_prov/server_maa /secret_prov_minimal/client /secret_prov_minimal/server_epid /secret_prov_minimal/server_dcap +/secret_prov_minimal/server_maa /secret_prov_pf/client /secret_prov_pf/server_epid /secret_prov_pf/server_dcap +/secret_prov_pf/server_maa /secret_prov_pf/wrap_key /secret_prov_pf/enc_files/input.txt diff --git a/CI-Examples/ra-tls-secret-prov/Makefile b/CI-Examples/ra-tls-secret-prov/Makefile index 8bd8a3f318..f59c05f67a 100644 --- a/CI-Examples/ra-tls-secret-prov/Makefile +++ b/CI-Examples/ra-tls-secret-prov/Makefile @@ -36,6 +36,10 @@ epid: ssl/server.crt secret_prov_minimal/server_epid secret_prov/server_epid sec dcap: ssl/server.crt secret_prov_minimal/server_dcap secret_prov/server_dcap secret_prov_pf/server_dcap \ secret_prov_pf/wrap_key secret_prov_pf/enc_files/input.txt +.PHONY: maa +maa: ssl/server.crt secret_prov_minimal/server_maa secret_prov/server_maa secret_prov_pf/server_maa \ + secret_prov_pf/wrap_key secret_prov_pf/enc_files/input.txt + ############################# SSL DATA DEPENDENCY ############################# # SSL data: key and x.509 self-signed certificate @@ -65,6 +69,9 @@ LDFLAGS += -Wl,--enable-new-dtags $(shell pkg-config --libs sgx_util) %/server_dcap: %/server.c $(CC) $< $(CFLAGS) $(LDFLAGS) -Wl,--no-as-needed -lsgx_urts -lsecret_prov_verify_dcap -pthread -o $@ +%/server_maa: %/server.c + $(CC) $< $(CFLAGS) $(LDFLAGS) -lsecret_prov_verify_maa -pthread -o $@ + secret_prov/client: secret_prov/client.c $(CC) $< $(CFLAGS) $(LDFLAGS) -lsecret_prov_attest -o $@ @@ -227,6 +234,35 @@ check_dcap: app dcap @rm OUTPUT +.PHONY: check_maa +check_maa: app maa + # secret_prov_minimal + cd secret_prov_minimal; \ + ./server_maa >/dev/null & SERVER_ID=$$!; \ + ../../../scripts/wait_for_server 60 127.0.0.1 4433; \ + gramine-sgx client > ../OUTPUT; \ + kill -9 $$SERVER_ID; + @grep -E "Received secret = 'A_SIMPLE_SECRET'" OUTPUT && echo "[ Success 1/4 ]" + + # secret_prov + cd secret_prov; \ + ./server_maa >/dev/null & SERVER_ID=$$!; \ + ../../../scripts/wait_for_server 60 127.0.0.1 4433; \ + gramine-sgx client > ../OUTPUT; \ + kill -9 $$SERVER_ID; + @grep -E "Received secret1 = 'FIRST_SECRET', secret2 = '42'" OUTPUT && echo "[ Success 2/4 ]" + + # secret_prov_pf + cd secret_prov_pf; \ + ./server_maa wrap_key >/dev/null & SERVER_ID=$$!; \ + ../../../scripts/wait_for_server 60 127.0.0.1 4433; \ + gramine-sgx client > ../OUTPUT; \ + kill -9 $$SERVER_ID; + @grep -E "\[parent\] Read from protected file: 'helloworld'" OUTPUT && echo "[ Success 3/4 ]" + @grep -E "\[child\] Read from protected file: 'helloworld'" OUTPUT && echo "[ Success 4/4 ]" + + @rm OUTPUT + ################################## CLEANUP #################################### .PHONY: clean diff --git a/CI-Examples/ra-tls-secret-prov/README.md b/CI-Examples/ra-tls-secret-prov/README.md index 16b4dd4cad..6deed06e85 100644 --- a/CI-Examples/ra-tls-secret-prov/README.md +++ b/CI-Examples/ra-tls-secret-prov/README.md @@ -5,13 +5,24 @@ examples of servers and clients written against the Secret Provisioning library. These examples use the Secret Provisioning library `secret_prov_attest.so` for -the clients and `secret_prov_verify_epid.so`/`secret_prov_verify_dcap.so` for -the servers. These libraries are installed together with Gramine (but for DCAP -version, you need `meson setup ... -Ddcap=enabled`). For DCAP attestation, the -DCAP software infrastructure must be installed and work correctly on the host. +the clients and `secret_prov_verify_epid.so` / `secret_prov_verify_dcap.so` / +`secret_prov_verify_maa.so` for the servers. These libraries are installed +together with Gramine (but for DCAP and MAA versions, you need `meson setup ... +-Ddcap=enabled`). + +For 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 (IAS) and ECDSA (DCAP) remote -attestation schemes. For more documentation, refer to +attestation schemes. 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 servers @@ -33,19 +44,20 @@ verify the client's RA-TLS certificate and the embedded SGX quote and, if verification succeeds, sends secrets back to the client (e.g. the master key for encrypted files in `secret_prov_pf` example). -There are two versions of each 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 each server: EPID, DCAP and MAA. Each of them links +against the corresponding EPID/DCAP/MAA 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`. +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 +121,19 @@ gramine-sgx ./client kill %% ``` + +- Secret Provisioning flows, Microsoft Azure Attestation (MAA) attestation: + +```sh +make app maa RA_TYPE=maa + +# test encrypted files client (other examples can be tested similarly) +cd secret_prov_pf +RA_TLS_ALLOW_DEBUG_ENCLAVE_INSECURE=1 \ +RA_TLS_MAA_PROVIDER_URL="https://sharedcus.cus.attest.azure.net" \ +./server_maa wrap_key & + +gramine-sgx ./client + +kill %% +``` diff --git a/Documentation/attestation.rst b/Documentation/attestation.rst index 8b5441bbf5..9155a23ece 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 ---------------------------------------- @@ -120,7 +140,7 @@ below pseudo-files: - ``/dev/attestation/attestation_type`` pseudo-file can be opened for read and contains the name of the attestation scheme used (currently one of ``none``, - ``epid`` and ``dcap``). + ``epid``, ``dcap`` or ``maa``). - ``/dev/attestation/user_report_data`` pseudo-file can be opened for read or write access. Typically, it is opened and written into before opening and @@ -186,7 +206,7 @@ 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 @@ -201,12 +221,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`): @@ -226,6 +257,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 -------------------------- @@ -264,8 +296,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:`tools/sgx/ra-tls/ra_tls.h`. @@ -286,10 +323,14 @@ 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 based attestation, the library expects instead: - ``sgx.remote_attestation = "dcap"`` -- DCAP remote attestation is enabled. +For MAA based attestation, the library expects instead: + +- ``sgx.remote_attestation = "maa"`` -- MAA remote attestation is enabled. + The library uses the following environment variables if available: - ``RA_TLS_CERT_TIMESTAMP_NOT_BEFORE`` -- the generated RA-TLS certificate uses @@ -364,7 +405,7 @@ The library uses the following EPID-specific environment variables if available: 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. Verifies the RA-TLS certificate and the SGX quote by +the TLS certificate. It verifies the RA-TLS certificate and the SGX quote by forwarding it to DCAP verification library (``libsgx_dcap_quoteverify.so``) and checking the result. This library is *not* thread-safe. @@ -377,6 +418,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 EPID 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 ``2022-08-01`` is used. + High-level Secret Provisioning interface ---------------------------------------- @@ -409,13 +473,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`` ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -510,6 +578,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/manifest-syntax.rst b/Documentation/manifest-syntax.rst index 6f3160b1c5..eabd81f8dd 100644 --- a/Documentation/manifest-syntax.rst +++ b/Documentation/manifest-syntax.rst @@ -762,7 +762,7 @@ Attestation and quotes :: - sgx.remote_attestation = "[none|epid|dcap]" + sgx.remote_attestation = "[none|epid|dcap|maa]" (Default: "none") sgx.ra_client_linkable = [true|false] @@ -780,6 +780,9 @@ filled with your registered Intel SGX EPID Attestation Service credentials For :term:`DCAP` based attestation, ``remote_attestation`` must be set to ``dcap``. ``ra_client_spid`` and ``ra_client_linkable`` are ignored. +For :term:`MAA` based attestation, ``remote_attestation`` must be set to +``maa``. ``ra_client_spid`` and ``ra_client_linkable`` are ignored. + Pre-heating enclave ^^^^^^^^^^^^^^^^^^^ @@ -987,5 +990,5 @@ Attestation and quotes (deprecated syntax) This syntax specified whether to enable SGX remote attestation. The boolean value has been replaced with the string value. The ``none`` value in the new syntax corresponds to the ``false`` boolean value in the deprecated syntax. The -explicit ``epid`` and ``dcap`` values in the new syntax replace the ambiguous -``true`` boolean value in the deprecated syntax. +explicit ``epid``, ``dcap``, ``maa`` values in the new syntax replace the +ambiguous ``true`` boolean value in the deprecated syntax. diff --git a/Documentation/sgx-intro.rst b/Documentation/sgx-intro.rst index 9f00c337ba..2d23e13dd9 100644 --- a/Documentation/sgx-intro.rst +++ b/Documentation/sgx-intro.rst @@ -267,6 +267,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 @@ -316,9 +319,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. @@ -413,6 +419,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/common_manifest_sgx_parser.c b/pal/src/host/linux-sgx/common_manifest_sgx_parser.c index 5269f30c1a..dfbe96311f 100644 --- a/pal/src/host/linux-sgx/common_manifest_sgx_parser.c +++ b/pal/src/host/linux-sgx/common_manifest_sgx_parser.c @@ -63,9 +63,11 @@ int parse_attestation_type(toml_table_t* manifest_root, attestation_type = SGX_ATTESTATION_EPID; } else if (!strcmp(sgx_attestation_type_str, "dcap")) { attestation_type = SGX_ATTESTATION_DCAP; + } else if (!strcmp(sgx_attestation_type_str, "maa")) { + attestation_type = SGX_ATTESTATION_MAA; } else { log_error("Unknown 'sgx.remote_attestation' type (recognized values are " - "\"none\", \"epid\" and \"dcap\")"); + "\"none\", \"epid\", \"dcap\", \"maa\")"); ret = -EINVAL; goto out; } @@ -76,8 +78,8 @@ int parse_attestation_type(toml_table_t* manifest_root, ret = toml_bool_in(manifest_root, "sgx.remote_attestation", /*defaultval=*/false, &sgx_remote_attestation_enabled); if (ret < 0) { - log_error("Cannot parse 'sgx.remote_attestation' (the value must be \"none\", \"epid\" " - "or \"dcap\", or in case of legacy syntax `true` or `false`)"); + log_error("Cannot parse 'sgx.remote_attestation' (value must be \"none\", \"epid\", " + "\"dcap\", \"maa\", or in case of legacy syntax `true` or `false`)"); ret = -EINVAL; goto out; } @@ -86,11 +88,12 @@ int parse_attestation_type(toml_table_t* manifest_root, if (sgx_ra_client_spid_str && strlen(sgx_ra_client_spid_str)) { attestation_type = SGX_ATTESTATION_EPID; } else { + /* note that it's impossible to specify MAA in legacy syntax, only DCAP */ attestation_type = SGX_ATTESTATION_DCAP; } } log_always("Detected deprecated syntax 'sgx.remote_attestation = true|false'; " - "consider using 'sgx.remote_attestation = \"none\"|\"epid\"|\"dcap\"'."); + "consider using 'sgx.remote_attestation = \"none\"|\"epid\"|\"dcap\"|\"maa\"'."); } *out_attestation_type = attestation_type; diff --git a/pal/src/host/linux-sgx/pal_linux.h b/pal/src/host/linux-sgx/pal_linux.h index 7ee4aafe5f..d7b1789d6f 100644 --- a/pal/src/host/linux-sgx/pal_linux.h +++ b/pal/src/host/linux-sgx/pal_linux.h @@ -44,7 +44,8 @@ extern struct pal_linuxsgx_state { enum sgx_attestation_type { SGX_ATTESTATION_NONE, SGX_ATTESTATION_EPID, - SGX_ATTESTATION_DCAP + SGX_ATTESTATION_DCAP, + SGX_ATTESTATION_MAA }; static inline const char* attestation_type_to_str(enum sgx_attestation_type attestation_type) { @@ -52,6 +53,7 @@ static inline const char* attestation_type_to_str(enum sgx_attestation_type atte case SGX_ATTESTATION_NONE: return "none"; case SGX_ATTESTATION_EPID: return "epid"; case SGX_ATTESTATION_DCAP: return "dcap"; + case SGX_ATTESTATION_MAA: return "maa"; default: BUG(); } } diff --git a/python/graminelibos/sgx_sign.py b/python/graminelibos/sgx_sign.py index aeda598760..3e3497e779 100644 --- a/python/graminelibos/sgx_sign.py +++ b/python/graminelibos/sgx_sign.py @@ -464,6 +464,8 @@ def get_mrenclave_and_manifest(manifest_path, libpal, verbose=False): print(' None') elif attestation_type == "dcap": print(' DCAP/ECDSA') + elif attestation_type == "maa": + print(' MAA (Microsoft Azure Attestation)') elif attestation_type == "epid": spid = manifest_sgx.get('ra_client_spid', '') linkable = manifest_sgx.get('ra_client_linkable', False) diff --git a/tools/sgx/ra-tls/meson.build b/tools/sgx/ra-tls/meson.build index 2677ec5f20..789fb6b065 100644 --- a/tools/sgx/ra-tls/meson.build +++ b/tools/sgx/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/tools/sgx/ra-tls/ra_tls.h b/tools/sgx/ra-tls/ra_tls.h index cb5a780506..3a8baa5440 100644 --- a/tools/sgx/ra-tls/ra_tls.h +++ b/tools/sgx/ra-tls/ra_tls.h @@ -23,6 +23,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/tools/sgx/ra-tls/ra_tls_verify_maa.c b/tools/sgx/ra-tls/ra_tls_verify_maa.c new file mode 100644 index 0000000000..8810df1507 --- /dev/null +++ b/tools/sgx/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=2022-08-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 "quote.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 "2022-08-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; +}