From 35ec1237decd78cf79d7c52e251bc3067d7ad8bb Mon Sep 17 00:00:00 2001 From: Gordon King Date: Tue, 10 Oct 2023 10:32:41 -0700 Subject: [PATCH] [Integrations/ita] Add RA-TLS/SecretProv libs for Intel Trust Authority (ITA) This commit adds the sources, documentation and tests for `libra_tls_verify_ita.so` and `libsecret_prov_verify_ita.so` libraries, used for integration of Gramine with ITA. Signed-off-by: Scott Raynor Signed-off-by: Gordon King --- Integrations/ita/ra_tls_ita/.gitignore | 7 + Integrations/ita/ra_tls_ita/README.md | 185 +++ .../helpers/gramine-v1.5-ci-examples.patch | 177 +++ Integrations/ita/ra_tls_ita/meson.build | 114 ++ Integrations/ita/ra_tls_ita/src/ra_tls.map | 5 + .../ita/ra_tls_ita/src/ra_tls_verify_ita.c | 1291 +++++++++++++++++ .../ita/ra_tls_ita/src/secret_prov.map | 4 + .../ra_tls_ita/src/secret_prov_verify_ita.c | 21 + .../ita/ra_tls_ita/subprojects/.gitignore | 5 + .../ra_tls_ita/subprojects/cJSON-1.7.12.wrap | 7 + .../ra_tls_ita/subprojects/curl-7.84.0.wrap | 7 + .../subprojects/mbedtls-mbedtls-3.4.0.wrap | 11 + .../packagefiles/cJSON/meson.build | 6 + .../packagefiles/curl-7.84.0/compile.sh | 89 ++ .../packagefiles/curl-7.84.0/meson.build | 23 + .../packagefiles/mbedtls/compile-curl.sh | 23 + .../packagefiles/mbedtls/enforce-aes-ni.patch | 35 + .../mbedtls/include/mbedtls/config-min.h | 45 + .../packagefiles/mbedtls/meson.build | 33 + 19 files changed, 2088 insertions(+) create mode 100644 Integrations/ita/ra_tls_ita/.gitignore create mode 100644 Integrations/ita/ra_tls_ita/README.md create mode 100644 Integrations/ita/ra_tls_ita/helpers/gramine-v1.5-ci-examples.patch create mode 100644 Integrations/ita/ra_tls_ita/meson.build create mode 100644 Integrations/ita/ra_tls_ita/src/ra_tls.map create mode 100644 Integrations/ita/ra_tls_ita/src/ra_tls_verify_ita.c create mode 100644 Integrations/ita/ra_tls_ita/src/secret_prov.map create mode 100644 Integrations/ita/ra_tls_ita/src/secret_prov_verify_ita.c create mode 100644 Integrations/ita/ra_tls_ita/subprojects/.gitignore create mode 100644 Integrations/ita/ra_tls_ita/subprojects/cJSON-1.7.12.wrap create mode 100644 Integrations/ita/ra_tls_ita/subprojects/curl-7.84.0.wrap create mode 100644 Integrations/ita/ra_tls_ita/subprojects/mbedtls-mbedtls-3.4.0.wrap create mode 100644 Integrations/ita/ra_tls_ita/subprojects/packagefiles/cJSON/meson.build create mode 100644 Integrations/ita/ra_tls_ita/subprojects/packagefiles/curl-7.84.0/compile.sh create mode 100644 Integrations/ita/ra_tls_ita/subprojects/packagefiles/curl-7.84.0/meson.build create mode 100644 Integrations/ita/ra_tls_ita/subprojects/packagefiles/mbedtls/compile-curl.sh create mode 100644 Integrations/ita/ra_tls_ita/subprojects/packagefiles/mbedtls/enforce-aes-ni.patch create mode 100644 Integrations/ita/ra_tls_ita/subprojects/packagefiles/mbedtls/include/mbedtls/config-min.h create mode 100644 Integrations/ita/ra_tls_ita/subprojects/packagefiles/mbedtls/meson.build diff --git a/Integrations/ita/ra_tls_ita/.gitignore b/Integrations/ita/ra_tls_ita/.gitignore new file mode 100644 index 0000000..f999774 --- /dev/null +++ b/Integrations/ita/ra_tls_ita/.gitignore @@ -0,0 +1,7 @@ +/build +/gramine +.history/ +.vscode/ +*.diff +*.patch +.cache/ diff --git a/Integrations/ita/ra_tls_ita/README.md b/Integrations/ita/ra_tls_ita/README.md new file mode 100644 index 0000000..ca1a84a --- /dev/null +++ b/Integrations/ita/ra_tls_ita/README.md @@ -0,0 +1,185 @@ +## Intel Trust Authority (ITA) + +**NOTE**: This document assumes familiarity with SGX attestation protocols and +RA-TLS/Secret Provisioning libraries shipped with Gramine. Please refer to +Gramine documentation for details: +- https://gramine.readthedocs.io/en/stable/attestation.html +- https://gramine.readthedocs.io/en/stable/glossary.html +- https://gramine.readthedocs.io/en/stable/sgx-intro.html#sgx-terminology + +**DISCLAIMER**: This version was tested with Gramine v1.5 and ITA REST API version +`v1.0.0`. + +--- + +[Intel Trust Authority](https://trustauthority.intel.com/) is a suite of trust +and security services that provides customers with assurance that their apps and +data are protected on the platform of their choice, including multiple cloud, +edge, and on-premises environments. + +Similarly to the Intel-developed EPID protocol, the remote verifier based +on the ITA protocol needs to contact the ITA 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 ITA attestation provider to check +the enclave's validity and receive back the set of claims describing this +enclave. + +For more information on ITA, refer to official documentation: +- https://www.intel.com/content/www/us/en/security/trust-authority.html +- https://portal.trustauthority.intel.com/workspace/documentation + +### Gramine manifest file + +Because ITA attestation uses DCAP-formatted SGX quotes, the manifest in Gramine +must contain the following line: +``` +sgx.remote_attestation = "dcap" +``` + +### RA-TLS library: `ra_tls_verify_ita.so` + +Similarly to e.g. `ra_tls_verify_epid.so`, the `ra_tls_verify_ita.so` 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 Intel Trust Authority (ITA) +provider and retrieving the attestation response (the JWT) from it. This +library is *not* thread-safe. + +Note that the JWT's signature can be verified using one of the set of JSON Web +Keys (JWKs). The RA-TLS library retrieves this set of JWKs together with +retrieving the JWT (this eager fetching of JWKs guarantees the freshness of +these keys). + +The library verifies the following set of claims in the received JWT: +- `ver` (JWT schema version, expected to be "1.0.0") +- `attester_type` (expected to be "SGX") +- `exp` (expiration time of JWT) +- `nbf` (not-before time of JWT) +- `sgx_is_debuggable` (verified using `RA_TLS_ALLOW_DEBUG_ENCLAVE_INSECURE`) +- `sgx_mrenclave` (verified against `RA_TLS_MRENCLAVE`) +- `sgx_mrsigner` (verified against `RA_TLS_MRSIGNER`) +- `sgx_isvprodid` (verified against `RA_TLS_ISV_PROD_ID`) +- `sgx_isvsvn` (verified against `RA_TLS_ISV_SVN`) +- `sgx_report_data` (verified to contain the hash of the RA-TLS public key) + +The library uses the same [SGX-specific environment variables as +`ra_tls_verify_epid.so`](https://gramine.readthedocs.io/en/stable/attestation.html#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 ITA-specific environment variables: + +- `RA_TLS_ITA_PROVIDER_URL` (mandatory) -- URL for ITA provider's REST API + endpoints. +- `RA_TLS_ITA_PROVIDER_API_VERSION` (optional) -- version of the ITA + provider's REST API `attest` endpoint. If not specified, the default + hard-coded version `v1` is used. +- `RA_TLS_ITA_API_KEY` (mandatory) -- API key for ITA provider's API endpoint + access. +- `RA_TLS_ITA_PORTAL_URL` (mandatory) -- URL for ITA provider's JWKs download. + +Note that the library does *not* use the following SGX-enclave-status +environment variables: `RA_TLS_ALLOW_OUTDATED_TCB_INSECURE`, +`RA_TLS_ALLOW_HW_CONFIG_NEEDED` and `RA_TLS_ALLOW_SW_HARDENING_NEEDED`. This is +because ITA will only generate a JWT for the SGX enclave if the enclave's TCB +level matches the "TCB baseline" specified in the used ITA policy. In other +words, ITA takes the responsibility away from the Gramine user and decides about +the allowed security status of the SGX enclave based on its policy and not based +on the aforementioned RA-TLS environment variables. + +However, the library uses the `RA_TLS_ALLOW_DEBUG_ENCLAVE_INSECURE` environment +variable because typically ITA policies allow debug enclaves. + +The library sets the following ITA-specific environment variables: + +- `RA_TLS_ITA_JWT` -- contains the raw ITA JWT (JSON object). +- `RA_TLS_ITA_SET_OF_JWKS` -- contains the raw set of JWKs (JSON object). + +### Secret Provisioning library: `secret_prov_verify_ita.so` + +Similarly to `secret_prov_verify_epid.so`, this library is used in +secret-provisioning services. The only difference is that this library uses ITA +based RA-TLS flows underneath. + +The library sets the same environment variables as `ra_tls_verify_ita.so`, +namely `RA_TLS_ITA_JWT` and `RA_TLS_ita_SET_OF_JWKS`. + +### Building ITA libraries + +The only prerequisite is that Gramine v1.5 must be installed on the system. + +To build the `ra_tls_verify_ita.so` and `secret_prov_verify_ita.so` libraries, +we use the meson build system: +```sh +meson setup build/ --buildtype=release +ninja -C build/ +sudo ninja -C build/ install +``` + +This installs the two libraries under a system-wide path. This path should be +added to the manifest file of an application that wishes to use ITA libraries +(typically manifests already contain this path). + +Similarly to EPID- and DCAP-based RA-TLS/Secret Prov libraries shipped with +Gramine, the ITA-based libraries are minimized and statically linked with all +dependencies except libc ones. + +### Testing ITA libraries + +The ITA libraries can be tested with the +[`ra-tls-mbedtls`](https://github.com/gramineproject/gramine/tree/master/CI-Examples/ra-tls-mbedtls) +and +[`ra-tls-secret-prov`](https://github.com/gramineproject/gramine/tree/master/CI-Examples/ra-tls-secret-prov) +examples available in Gramine. + +To be able to run these tests, the machine must run on the SGX enabled host, with +access to the ITA attestation provider service. + +For this, we provide a patch that should be applied on top of Gramine v1.5 repo: +```sh +git clone --depth 1 --branch v1.5 https://github.com/gramineproject/gramine.git +cd gramine/ +git apply ../helpers/gramine-v1.5-ci-examples.patch +``` + +To test the `ra-tls-mbedtls` example, cd to its directory and run: +```sh +make clean +make app ita RA_TYPE=dcap + +gramine-sgx ./server & + +RA_TLS_ALLOW_DEBUG_ENCLAVE_INSECURE=1 \ +RA_TLS_ITA_PROVIDER_URL="https://api.trustauthority.intel.com" \ +RA_TLS_ITA_PORTAL_URL="https://portal.trustauthority.intel.com" \ +RA_TLS_ITA_API_KEY= \ +RA_TLS_MRENCLAVE= \ +RA_TLS_MRSIGNER= \ +RA_TLS_ISV_PROD_ID= \ +RA_TLS_ISV_SVN= \ +./client ita + +# client will successfully connect to the server via RA-TLS/ITA flows +kill %% +``` + +To test the `ra-tls-secret-prov` example, cd to its directory and run: +```sh +make clean +make app ita RA_TYPE=dcap + +# test encrypted files client (other examples can be tested similarly) +cd secret_prov_pf + +RA_TLS_ALLOW_DEBUG_ENCLAVE_INSECURE=1 \ +RA_TLS_ITA_PROVIDER_URL="https://api.trustauthority.intel.com" \ +RA_TLS_ITA_PORTAL_URL="https://portal.trustauthority.intel.com" \ +RA_TLS_ITA_API_KEY= \ +./server_ita wrap_key & + +gramine-sgx ./client + +kill %% +``` diff --git a/Integrations/ita/ra_tls_ita/helpers/gramine-v1.5-ci-examples.patch b/Integrations/ita/ra_tls_ita/helpers/gramine-v1.5-ci-examples.patch new file mode 100644 index 0000000..99affdb --- /dev/null +++ b/Integrations/ita/ra_tls_ita/helpers/gramine-v1.5-ci-examples.patch @@ -0,0 +1,177 @@ +diff --git a/CI-Examples/ra-tls-mbedtls/Makefile b/CI-Examples/ra-tls-mbedtls/Makefile +index 4adc0ee..81b2d6f 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 + .PHONY: dcap + dcap: client_dcap.manifest.sgx client_dcap.sig + ++.PHONY: ita ++ita: client_ita.manifest.sgx client_ita.sig ++ + ############################# SSL DATA DEPENDENCY ############################# + + # SSL data: key and x.509 self-signed certificate +@@ -107,6 +110,23 @@ sgx_sign_client_epid: client_epid.manifest client + --manifest $< \ + --output $<.sgx + ++########################### CLIENT (ita) MANIFEST ############################# ++ ++client_ita.manifest: client.manifest.template ++ gramine-manifest \ ++ -Dlog_level=$(GRAMINE_LOG_LEVEL) \ ++ -Darch_libdir=$(ARCH_LIBDIR) \ ++ $< >$@ ++ ++client_ita.manifest.sgx client_ita.sig: sgx_sign_client_ita ++ @: ++ ++.INTERMEDIATE: sgx_sign_client_ita ++sgx_sign_client_ita: client_ita.manifest client ++ gramine-sgx-sign \ ++ --manifest $< \ ++ --output $<.sgx ++ + ############################### SGX CHECKS FOR CI ############################# + + .PHONY: check_epid +@@ -149,6 +169,26 @@ check_dcap_fail: app dcap + ./client dcap && exit 1 || echo "[ Success 1/1 ]"; \ + kill -9 $$SERVER_ID + ++.PHONY: check_ita ++check_ita: app ita ++ gramine-sgx server >/dev/null & SERVER_ID=$$!; \ ++ ../../scripts/wait_for_server 60 127.0.0.1 4433; \ ++ ./client ita > OUTPUT; \ ++ ./client ita 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_ita_fail ++check_ita_fail: app ita ++ gramine-sgx server --test-malicious-quote >/dev/null & SERVER_ID=$$!; \ ++ ../../scripts/wait_for_server 60 127.0.0.1 4433; \ ++ ./client ita && exit 1 || echo "[ Success 1/1 ]"; \ ++ kill -9 $$SERVER_ID ++ + ################################## CLEANUP #################################### + + .PHONY: clean +diff --git a/CI-Examples/ra-tls-mbedtls/src/client.c b/CI-Examples/ra-tls-mbedtls/src/client.c +index 6b7d056..b8decbd 100644 +--- a/CI-Examples/ra-tls-mbedtls/src/client.c ++++ b/CI-Examples/ra-tls-mbedtls/src/client.c +@@ -165,9 +165,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], "ita"))) { ++ mbedtls_printf("USAGE: %s native|epid|dcap|ita [SGX measurements]\n", argv[0]); + return 1; + } + +@@ -210,6 +210,13 @@ int main(int argc, char** argv) { + return 1; + } + } ++ } else if (!strcmp(argv[1], "ita")) { ++ ra_tls_verify_lib = dlopen("libra_tls_verify_ita.so", RTLD_LAZY); ++ if (!ra_tls_verify_lib) { ++ mbedtls_printf("%s\n", dlerror()); ++ mbedtls_printf("User requested RA-TLS verification with ita but cannot find lib\n"); ++ return 1; ++ } + } + + if (ra_tls_verify_lib) { +diff --git a/CI-Examples/ra-tls-secret-prov/.gitignore b/CI-Examples/ra-tls-secret-prov/.gitignore +index cc4e300..a125fa6 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_ita + /secret_prov_minimal/client + /secret_prov_minimal/server_epid + /secret_prov_minimal/server_dcap ++/secret_prov_minimal/server_ita + /secret_prov_pf/client + /secret_prov_pf/server_epid + /secret_prov_pf/server_dcap ++/secret_prov_pf/server_ita + /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 2780651..96a6094 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: ita ++ita: ssl/server.crt secret_prov_minimal/server_ita secret_prov/server_ita secret_prov_pf/server_ita \ ++ secret_prov_pf/wrap_key secret_prov_pf/enc_files/input.txt ++ + ############################# SSL DATA DEPENDENCY ############################# + + # SSL data: key and x.509 self-signed certificate +@@ -60,6 +64,9 @@ LDFLAGS += -Wl,--enable-new-dtags $(shell pkg-config --libs secret_prov_gramine) + %/server_dcap: %/server.c + $(CC) $< $(CFLAGS) $(LDFLAGS) -Wl,--no-as-needed -lsgx_urts -lsecret_prov_verify_dcap -pthread -o $@ + ++%/server_ita: %/server.c ++ $(CC) $< $(CFLAGS) $(LDFLAGS) -lsecret_prov_verify_ita -pthread -o $@ ++ + secret_prov/client: secret_prov/client.c + $(CC) $< $(CFLAGS) $(LDFLAGS) -lsecret_prov_attest -o $@ + +@@ -213,6 +220,35 @@ check_dcap: app dcap + + @rm OUTPUT + ++.PHONY: check_ita ++check_ita: app ita ++ # secret_prov_minimal ++ cd secret_prov_minimal; \ ++ ./server_ita >/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_ita >/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_ita 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/Integrations/ita/ra_tls_ita/meson.build b/Integrations/ita/ra_tls_ita/meson.build new file mode 100644 index 0000000..25f7751 --- /dev/null +++ b/Integrations/ita/ra_tls_ita/meson.build @@ -0,0 +1,114 @@ +# +# Project configuration, options, modules, scripts +# + +project( + 'ra-tls-ita', + 'c', 'cpp', + version: '0.1', + license: 'LGPLv3+', + + meson_version: '>=0.56', + + default_options: [ + 'c_std=c11', + ], +) + +pkgconfig = find_program('pkg-config') + +cc = meson.get_compiler('c') + +add_project_arguments( + '-Wa,--noexecstack', + + '-Wall', + '-Wextra', + + '-Wmissing-prototypes', + '-Wstrict-prototypes', + '-Wwrite-strings', + + cc.get_supported_arguments( + '-Wtrampolines', + '-Wnull-dereference', + ), + + language: 'c') + +release_mode = get_option('buildtype') == 'release' +if release_mode + add_project_arguments('-DNDEBUG', language: 'c') +endif + +curl_proj = subproject('curl-7.84.0') +libcurl_dep = curl_proj.get_variable('curl_minimal_dep') + +cjson_proj = subproject('cJSON-1.7.12') +cjson_dep = cjson_proj.get_variable('cjson_dep') + +threads_dep = dependency('threads') + +mbedtls_dep = dependency('mbedtls_gramine', static: true) + +# RA-TLS with ITA + +ra_tls_map = meson.current_source_dir() / 'src' / 'ra_tls.map' +ra_tls_link_args = [ + '-Wl,--version-script=@0@'.format(ra_tls_map), +] + +ra_tls_dep = dependency('ra_tls_gramine') +ra_tls_libdir = ra_tls_dep.get_variable(pkgconfig: 'libdir') +ra_tls_includedir = ra_tls_dep.get_variable(pkgconfig: 'includedir') + '/gramine' + +ra_tls_verify_lib = cc.find_library('ra_tls_verify', dirs: ra_tls_libdir) +ra_tls_util_lib = cc.find_library('sgx_util', dirs: ra_tls_libdir) + +libra_tls_verify_ita = shared_library('ra_tls_verify_ita', + 'src' / 'ra_tls_verify_ita.c', + + include_directories: ra_tls_includedir, + link_args: ra_tls_link_args, + dependencies: [ + cjson_dep, + libcurl_dep, + mbedtls_dep, + ra_tls_util_lib, + ra_tls_verify_lib, + ], + install: true, + install_rpath: get_option('prefix') / get_option('libdir'), +) + +# Secret Prov with ITA + +secret_prov_map = meson.current_source_dir() / 'src' / 'secret_prov.map' +secret_prov_link_args = [ + '-Wl,--version-script=@0@'.format(secret_prov_map), +] + +secret_prov_dep = dependency('secret_prov_gramine') +secret_prov_libdir = secret_prov_dep.get_variable(pkgconfig: 'libdir') +secret_prov_includedir = secret_prov_dep.get_variable(pkgconfig: 'includedir') + '/gramine' + +secret_prov_verify_lib = cc.find_library('secret_prov_verify', dirs: secret_prov_libdir) + +libsecret_prov_verify_ita = shared_library('secret_prov_verify_ita', + 'src' / 'secret_prov_verify_ita.c', + 'src' / 'ra_tls_verify_ita.c', + + include_directories: secret_prov_includedir, + link_args: secret_prov_link_args, + dependencies: [ + cjson_dep, + libcurl_dep, + mbedtls_dep, + ra_tls_util_lib, + ra_tls_verify_lib, + secret_prov_verify_lib, + threads_dep, + ], + install: true, + install_rpath: get_option('prefix') / get_option('libdir'), +) diff --git a/Integrations/ita/ra_tls_ita/src/ra_tls.map b/Integrations/ita/ra_tls_ita/src/ra_tls.map new file mode 100644 index 0000000..552b52b --- /dev/null +++ b/Integrations/ita/ra_tls_ita/src/ra_tls.map @@ -0,0 +1,5 @@ +RA_TLS { + global: ra_tls_set_measurement_callback; ra_tls_verify_callback_der; ra_tls_verify_callback_extended_der; ra_tls_create_key_and_crt_der; + local: *; +}; + diff --git a/Integrations/ita/ra_tls_ita/src/ra_tls_verify_ita.c b/Integrations/ita/ra_tls_ita/src/ra_tls_verify_ita.c new file mode 100644 index 0000000..d98ccf8 --- /dev/null +++ b/Integrations/ita/ra_tls_ita/src/ra_tls_verify_ita.c @@ -0,0 +1,1291 @@ +/* SPDX-License-Identifier: LGPL-3.0-or-later */ +/* Copyright (C) 2023 Intel Corporation */ + +/*! + * \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 Intel Trust Authority (ITA) + * for ITA-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 ITA 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 ITA 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}/appraisal/{apiVersion}/attest + * + * The HTTPS "Get set of JWKs" request is sent to the URL in the format: + * GET {instanceUrl}/certs/ + * + * {instanceUrl} is the attestation provider URL, e.g. `api.trustauthority.intel.com`. + * + * 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 + +#include +#include +#include +#include + +#include + +#include "ra_tls.h" +#include "ra_tls_common.h" +#include "sgx_arch.h" +#include "sgx_attest.h" + +extern verify_measurements_cb_t g_verify_measurements_cb; + +#define ERROR(fmt, ...) \ + do { \ + fprintf(stderr, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__); \ + } while (0) + +#define WARN(fmt, ...) \ + do { \ + fprintf(stderr, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__); \ + } while (0) + +#define JWT_ISSUER_NAME "Intel Trust Authority" +#define RA_TLS_ITA_PROVIDER_URL "RA_TLS_ITA_PROVIDER_URL" +#define RA_TLS_ITA_PORTAL_URL "RA_TLS_ITA_PORTAL_URL" +#define RA_TLS_ITA_API_KEY "RA_TLS_ITA_API_KEY" +#define RA_TLS_ITA_PROVIDER_API_VERSION "RA_TLS_ITA_PROVIDER_API_VERSION" + +#define ITA_URL_MAX_SIZE 256 + +/** ITA "Attest SGX Enclave" API endpoint. */ +#define ITA_URL_ATTEST_ENDPOINT "appraisal/%s/attest" + +/** ITA "Get Signing Certificates" API endpoint. */ +#define ITA_URL_CERTS_ENDPOINT "certs" + +/** Default API version for ITA API endpoints. */ +#define DEFAULT_ITA_PROVIDER_API_VERSION "v1" + +/* Environment variables exposed by successful RA-TLS verification API */ +#define RA_TLS_ITA_JWT "RA_TLS_ita_JWT" +#define RA_TLS_ITA_SET_OF_JWKS "RA_TLS_ita_SET_OF_JWKS" + +static char* g_ita_base_url = NULL; +static char* g_ita_api_key = NULL; +static char* g_ita_portal_url = NULL; +static char* g_ita_api_version = NULL; + +/*! Context used in ita_*() calls */ +struct ita_context { + bool curl_global_init_done; + CURL* curl; /*!< CURL context for this session */ + struct curl_slist* headers; /*!< Request headers sent to ITA attestation provider */ +}; + +/*! ITA response (JWT token for `attest/` API, set of Signing keys for `certs/` API) */ +struct ita_response { + char* data; /*!< response (JSON string) */ + size_t data_size; /*!< size of \a data string */ +}; + +/* Parse hex string to buffer; returns 0 on success, otherwise -1 */ +static int parse_hex(const char* hex, void* buffer, size_t buffer_size) { + if (!hex || !buffer || buffer_size == 0 || strlen(hex) != buffer_size * 2) + return -1; + + for (size_t i = 0; i < buffer_size; i++) { + if (!isxdigit(hex[i * 2]) || !isxdigit(hex[i * 2 + 1])) { + return -1; + } + sscanf(hex + i * 2, "%02hhx", &((uint8_t*)buffer)[i]); + } + return 0; +} + +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) { + 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; +} + +static int verify_quote_body_enclave_attributes(sgx_quote_body_t* quote_body, + bool allow_debug_enclave) { + if (!allow_debug_enclave && (quote_body->report_body.attributes.flags & SGX_FLAGS_DEBUG)) { + ERROR("Quote: DEBUG bit in enclave attributes is set\n"); + return -1; + } + /* sanity checks: 64-bit enclave, initialized, must not have provision/EINIT token key */ + if (!(quote_body->report_body.attributes.flags & SGX_FLAGS_MODE64BIT) || + !(quote_body->report_body.attributes.flags & SGX_FLAGS_INITIALIZED) || + (quote_body->report_body.attributes.flags & SGX_FLAGS_PROVISION_KEY) || + (quote_body->report_body.attributes.flags & SGX_FLAGS_LICENSE_KEY)) { + return -1; + } + return 0; +} + +/*! + * \brief Parse response headers of the ITA 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 ita_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 ita_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 ita_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 ita_response* response) { + free(response->data); + free(response); +} + +static void ita_cleanup(struct ita_context* context) { + if (!context) + return; + + curl_slist_free_all(context->headers); + 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 ita_init(struct ita_context** out_context) { + int ret = -1; + const char* api_key_hdr_start = "x-api-key: "; + char* api_key_hdr = NULL; + size_t api_key_hdr_size; + + struct ita_context* 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; + } + + api_key_hdr_size = strlen(api_key_hdr_start) + strlen(g_ita_api_key) + 1; + api_key_hdr = malloc(api_key_hdr_size); + if (!api_key_hdr) + goto out; + snprintf(api_key_hdr, api_key_hdr_size, "%s%s", api_key_hdr_start, g_ita_api_key); + + 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; + } + + // set ITA API key in headers + context->headers = curl_slist_append(context->headers, api_key_hdr); + context->headers = curl_slist_append(context->headers, "Accept: application/json"); + 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) { + ita_cleanup(context); + } + return ret; +} + +/*! Send GET request (empty) to ITA attestation provider's `certs/` API endpoint and save the + * resulting set of JWKs in \a out_set_of_jwks; caller is responsible for its cleanup */ +static int ita_get_signing_certs(struct ita_context* context, char** out_set_of_jwks) { + int ret; + + char* request_url = NULL; + struct ita_response* response = NULL; + + /* prepare sending "GET certs" to ITA and receiving a response (using Curl) */ + response = calloc(1, sizeof(*response)); + if (!response) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + request_url = malloc(ITA_URL_MAX_SIZE); + if (!request_url) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + ret = snprintf(request_url, ITA_URL_MAX_SIZE, "%s/%s", g_ita_portal_url, ITA_URL_CERTS_ENDPOINT); + if (ret < 0 || (size_t)ret >= ITA_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 ITA \"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("ITA \"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("ITA \"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 ITA attestation provider's `attest/` API endpoint + * and save response in \a out_ita_response; caller is responsible for its cleanup */ +static int ita_send_request(struct ita_context* context, const void* quote, size_t quote_size, + const void* runtime_data, size_t runtime_data_size, + struct ita_response** out_ita_response) { + int ret; + + char* quote_b64 = NULL; + char* runtime_data_b64 = NULL; + char* request_json = NULL; + char* request_url = NULL; + + struct ita_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 ITA */ + const char* request_json_fmt = "{\"quote\": \"%s\"}"; + + 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 ITA and receiving a response (using Curl) */ + response = calloc(1, sizeof(*response)); + if (!response) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + request_url = malloc(ITA_URL_MAX_SIZE); + if (!request_url) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + ret = snprintf(request_url, ITA_URL_MAX_SIZE, "%s/" ITA_URL_ATTEST_ENDPOINT "", + g_ita_base_url, g_ita_api_version); + if (ret < 0 || (size_t)ret >= ITA_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 ITA 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("ITA 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("ITA Attestation response doesn't have the JSON Web Token (JWT)\n"); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + *out_ita_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 ITA (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 ita_verify_response_output_quote(struct ita_response* response, const char* set_of_jwks, + sgx_quote_body_t** out_quote_body) { + int ret; + + sgx_quote_body_t* quote_body = NULL; + + char* ita_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("ITA 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("ITA 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("ITA 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("ITA 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("ITA 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("ITA 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("ITA 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("ITA 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 PS384/RSASSA-PSS signing */ + if (!cJSON_IsString(token_header_alg) || strcmp(token_header_alg->valuestring, "PS384") || + !cJSON_IsString(token_header_typ) || strcmp(token_header_typ->valuestring, "JWT") || + !cJSON_IsString(token_header_kid)) { + ERROR("ITA 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 */ + ita_certs_url = malloc(ITA_URL_MAX_SIZE); + if (!ita_certs_url) { + ret = MBEDTLS_ERR_X509_ALLOC_FAILED; + goto out; + } + + ret = snprintf(ita_certs_url, ITA_URL_MAX_SIZE, "%s/%s", g_ita_portal_url, + ITA_URL_CERTS_ENDPOINT); + if (ret < 0 || (size_t)ret >= ITA_URL_MAX_SIZE) { + ret = MBEDTLS_ERR_X509_BUFFER_TOO_SMALL; + goto out; + } + + if (!cJSON_IsString(token_header_jku) || strcmp(token_header_jku->valuestring, ita_certs_url)) { + ERROR("ITA JWT header's `jku` field contains an unexpected URL (got `%s`, expected `%s`)\n", + token_header_jku->valuestring, ita_certs_url); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + + json_token_payload = cJSON_Parse(token_payload); + if (!json_token_payload) { + ERROR("ITA 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("ITA 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("ITA 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("ITA 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("ITA 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("ITA 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 ITA\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("ITA 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("ITA JWK's certificate is incorrectly formatted (not a proper X.509 cert)\n"); + goto out; + } + + /* we verified the header and the signature of the received JWT, can trust its payload */ + cJSON *iss = cJSON_GetObjectItem(json_token_payload, "iss"); + cJSON *ver = cJSON_GetObjectItem(json_token_payload, "ver"); + + cJSON* expiration_time = cJSON_GetObjectItem(json_token_payload, "exp"); + cJSON* not_before_time = cJSON_GetObjectItem(json_token_payload, "nbf"); + + cJSON *attester_type = cJSON_GetObjectItem(json_token_payload, "attester_type"); + cJSON *sgx_is_debuggable = cJSON_GetObjectItem(json_token_payload, "sgx_is_debuggable"); + cJSON *sgx_mrenclave = cJSON_GetObjectItem(json_token_payload, "sgx_mrenclave"); + cJSON *sgx_mrsigner = cJSON_GetObjectItem(json_token_payload, "sgx_mrsigner"); + cJSON *sgx_product_id = cJSON_GetObjectItem(json_token_payload, "sgx_isvprodid"); + cJSON *sgx_svn = cJSON_GetObjectItem(json_token_payload, "sgx_isvsvn"); + cJSON *sgx_report_data = cJSON_GetObjectItem(json_token_payload, "sgx_report_data"); + // cJSON *sgx_tcb_status = cJSON_GetObjectItem(json_token_payload, "attester_tcb_status"); + + if (!cJSON_IsString(iss) || strcmp(iss->valuestring, "Intel Trust Authority")) { + ERROR("The JWT isn't issued by ITA"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + + if (!cJSON_IsString(ver) || strcmp(ver->valuestring, "1.0.0") || + !cJSON_IsString(attester_type) || strcmp(attester_type->valuestring, "SGX")) { + ERROR("Intel Trust Authority JWT payload's `ver` and/or `attester_type` fields " + "contain unrecognized values\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + + /* expiration_time (exp) and not_before_time (nbf) are represented as JSON numbers with a + * NumericDate value -- the number of seconds from 1970-01-01 UTC (Seconds Since the Epoch). + * Verify against the current time, otherwise this JWT must not be accepted. */ + if (!cJSON_IsNumber(expiration_time) || !cJSON_IsNumber(not_before_time)) { + ERROR("ITA JWT payload's `exp` and/or `nbf` fields have incorrect types\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + time_t curr_time = time(NULL); + if (curr_time == (time_t)-1) { + ERROR("Failed to fetch current time (to compare against `exp` and `nbf` of ITA JWT)\n"); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + if (!((time_t)not_before_time->valueint <= curr_time + && curr_time <= (time_t)expiration_time->valueint)) { + WARN("ITA JWT is expired (nbf=%d, exp=%d in 'Seconds Since the Epoch' format)\n", + not_before_time->valueint, expiration_time->valueint); + } + + 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("ITA JWT payload's `sgx_is_debuggable`, `sgx_mrenclave`, " + "`sgx_mrsigner`, `sgx_isvprodid`, `sgx_isvsvn` and/or " + "`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 taken 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)); + if (ret < 0) { + ERROR("ITA JWT payload's `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)); + if (ret < 0) { + ERROR("ITA JWT payload's `sgx_mrsigner` field is not hex encoded\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + + static_assert(sizeof(quote_body->report_body.isv_prod_id) == 2); /* uint16_t */ + if (sgx_product_id->valueint == INT_MAX || sgx_product_id->valueint == INT_MIN) { + ERROR("ITA JWT payload's `sgx_isvprodid` field is not an integer\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + if (sgx_product_id->valueint < 0 || sgx_product_id->valueint > USHRT_MAX) { + ERROR("ITA JWT payload's `sgx_isvprodid` field is not uint16_t\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + quote_body->report_body.isv_prod_id = sgx_product_id->valueint; + + static_assert(sizeof(quote_body->report_body.isv_svn) == 2); /* uint16_t */ + if (sgx_svn->valueint == INT_MAX || sgx_svn->valueint == INT_MIN) { + ERROR("ITA JWT payload's `sgx_isvsvn` field is not an integer\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + if (sgx_svn->valueint < 0 || sgx_svn->valueint > USHRT_MAX) { + ERROR("ITA JWT payload's `sgx_isvsvn` field is not uint16_t\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)); + if (ret < 0) { + ERROR("ITA JWT payload's `sgx_report_data` field is not hex encoded\n"); + ret = MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT; + goto out; + } + +#if 0 + ERROR("--- JWT is ```%s``` ---\n", token_b64->valuestring); + ERROR("--- set_of_jwks is ```%s``` ---\n", set_of_jwks); +#endif + + /* + * Expose JWT (as base64-formatted string) and "set of JWKs" (as JSON string) in envvars; + * at this point we know that JWT and "set of JWKs" are correctly formatted strings. + * + * NOTE: manipulations with envvars are not thread-safe. + */ + if (getenv(RA_TLS_ITA_JWT)) { + ERROR("ITA JWT cannot be exposed through RA_TLS_ita_JWT envvar because this envvar is " + "already used (you must unsetenv before calling RA-TLS verification)\n"); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + ret = setenv(RA_TLS_ITA_JWT, token_b64->valuestring, /*overwrite=*/1); + if (ret < 0) { + ERROR("ITA JWT cannot be exposed through RA_TLS_ita_JWT envvar because setenv() failed " + "with error %d\n", errno); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + if (getenv(RA_TLS_ITA_SET_OF_JWKS)) { + ERROR("ITA \"Set of JWKs\" cannot be exposed through RA_TLS_ita_SET_OF_JWKS envvar because " + "this envvar is already used (you must unsetenv before calling RA-TLS " + "verification)\n"); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + goto out; + } + + ret = setenv(RA_TLS_ITA_SET_OF_JWKS, set_of_jwks, /*overwrite=*/1); + if (ret < 0) { + ERROR("ITA \"Set of JWKs\" cannot be exposed through RA_TLS_ita_SET_OF_JWKS envvar because " + "setenv() failed with error %d\n", errno); + ret = MBEDTLS_ERR_X509_FATAL_ERROR; + 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(ita_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 out_pk_der */ +static int parse_pk(mbedtls_pk_context* pk, uint8_t* out_pk_der, size_t* out_pk_der_size) { + /* 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 < 0) + return pk_der_size_byte; + + /* 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); + *out_pk_der_size = pk_der_size_byte; + return 0; +} + +int ra_tls_verify_callback(void* data, mbedtls_x509_crt* crt, int depth, uint32_t* flags) { + struct ra_tls_verify_callback_results* results = (struct ra_tls_verify_callback_results*)data; + + int ret; + + struct ita_context* context = NULL; + struct ita_response* response = NULL; + char* set_of_jwks = NULL; + + sgx_quote_body_t* quote_from_ita = NULL; + + if (results) { + /* TODO: when ITA becomes standard, add RA_TLS_ATTESTATION_SCHEME_ita to core RA-TLS lib */ + results->attestation_scheme = RA_TLS_ATTESTATION_SCHEME_UNKNOWN; + results->err_loc = AT_INIT; + } + + 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_ita_base_url, RA_TLS_ITA_PROVIDER_URL, /*default_val=*/NULL); + if (ret < 0) { + ERROR("Failed to read the environment variable RA_TLS_ITA_PROVIDER_URL\n"); + goto out; + } + + ret = init_from_env(&g_ita_api_key, RA_TLS_ITA_API_KEY, /*default_val=*/NULL); + if (ret < 0) { + ERROR("Failed to read the environment variable RA_TLS_ITA_API_KEY\n"); + goto out; + } + + ret = init_from_env(&g_ita_portal_url, RA_TLS_ITA_PORTAL_URL, /*default_val=*/NULL); + if (ret < 0) { + ERROR("Failed to read the environment variable RA_TLS_ITA_PORTAL_URL\n"); + goto out; + } + + ret = init_from_env(&g_ita_api_version, RA_TLS_ITA_PROVIDER_API_VERSION, + DEFAULT_ITA_PROVIDER_API_VERSION); + if (ret < 0) { + ERROR("Failed to read the environment variable RA_TLS_ITA_PROVIDER_API_VERSION\n"); + goto out; + } + + if (results) + results->err_loc = AT_EXTRACT_QUOTE; + + /* extract SGX quote from "quote" OID extension from crt */ + sgx_quote_t* quote; + size_t quote_size; + ret = find_oid_in_cert_extensions(crt->v3_ext.p, crt->v3_ext.len, g_quote_oid, g_quote_oid_size, + (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 (ITA 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}; + size_t pk_der_size; + ret = parse_pk(&crt->pk, pk_der, &pk_der_size); + if (ret < 0) + goto out; + + /* TODO: when ITA becomes standard, use results->ita. to expose more info on error */ + if (results) + results->err_loc = AT_VERIFY_EXTERNAL; + + /* initialize the ITA context, get the set of JWKs from the `certs/` ita API endpoint, send the + * SGX quote to the `attest/` ITA API endpoint, and finally receive and verify the attestation + * response (JWT) */ + ret = ita_init(&context); + if (ret < 0) { + goto out; + } + + /* a set of JWKs may change over time, so we better get them every time */ + ret = ita_get_signing_certs(context, &set_of_jwks); + if (ret < 0) { + goto out; + } + + ret = ita_send_request(context, quote, quote_size, pk_der, pk_der_size, &response); + if (ret < 0 || !response || !response->data) { + goto out; + } + + /* 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 = ita_verify_response_output_quote(response, set_of_jwks, "e_from_ita); + if (ret < 0) { + ret = MBEDTLS_ERR_X509_CERT_VERIFY_FAILED; + goto out; + } + + /* verify that the SGX quote sent to ITA has the same measurements as the constructed from the + * ITA's JWT payload -- just for sanity */ + sgx_report_body_t* orig_body = "e->body.report_body; + sgx_report_body_t* ita_body = "e_from_ita->report_body; + if (memcmp(&orig_body->report_data, &ita_body->report_data, sizeof(orig_body->report_data)) || + memcmp(&orig_body->mr_enclave, &ita_body->mr_enclave, sizeof(orig_body->mr_enclave)) || + memcmp(&orig_body->mr_signer, &ita_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; + } + + if (results) + results->err_loc = AT_VERIFY_ENCLAVE_ATTRS; + + /* verify enclave attributes from the SGX quote body, including the user-supplied verification + * parameter "allow debug enclave"; NOTE: "allow outdated TCB", "allow HW config needed", "allow + * SW hardening needed" parameters are not used in ITA */ + ret = verify_quote_body_enclave_attributes(quote_from_ita, 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; + } + + if (results) + results->err_loc = AT_VERIFY_ENCLAVE_MEASUREMENTS; + + /* 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_ita->report_body.mr_enclave, + (const char*)"e_from_ita->report_body.mr_signer, + (const char*)"e_from_ita->report_body.isv_prod_id, + (const char*)"e_from_ita->report_body.isv_svn); + } else { + /* use default logic to verify measurements */ + ret = verify_quote_body_against_envvar_measurements(quote_from_ita); + } + if (ret < 0) { + ERROR("Failed verification of JWT's SGX measurements\n"); + ret = MBEDTLS_ERR_X509_CERT_VERIFY_FAILED; + goto out; + } + + if (results) + results->err_loc = AT_NONE; + ret = 0; +out: + if (context) + ita_cleanup(context); + + if (response) + response_cleanup(response); + + free(set_of_jwks); + free(quote_from_ita); + return ret; +} diff --git a/Integrations/ita/ra_tls_ita/src/secret_prov.map b/Integrations/ita/ra_tls_ita/src/secret_prov.map new file mode 100644 index 0000000..244dd36 --- /dev/null +++ b/Integrations/ita/ra_tls_ita/src/secret_prov.map @@ -0,0 +1,4 @@ +SECRET_PROV { + global: secret_provision_write; secret_provision_read; secret_provision_close; secret_provision_get; secret_provision_start; secret_provision_start_server; + local: *; +}; diff --git a/Integrations/ita/ra_tls_ita/src/secret_prov_verify_ita.c b/Integrations/ita/ra_tls_ita/src/secret_prov_verify_ita.c new file mode 100644 index 0000000..ca4ad25 --- /dev/null +++ b/Integrations/ita/ra_tls_ita/src/secret_prov_verify_ita.c @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-3.0-or-later */ +/* Copyright (C) 2023 Intel Corporation */ + +/*! + * \file + * + * This file contains the dummy references to secret_prov_verify exported functions (this is to + * force symbols from libsecret_prov_verify.a to be exported in our ITA version; note that by + * default unused symbols from a linked static library are *not* exported). + */ + +#include + +#include "secret_prov.h" + +static __attribute__((used)) void* dummy_secret_prov_symbols_table[] = { + secret_provision_start_server, + secret_provision_read, + secret_provision_write, + secret_provision_close, +}; diff --git a/Integrations/ita/ra_tls_ita/subprojects/.gitignore b/Integrations/ita/ra_tls_ita/subprojects/.gitignore new file mode 100644 index 0000000..01f951b --- /dev/null +++ b/Integrations/ita/ra_tls_ita/subprojects/.gitignore @@ -0,0 +1,5 @@ +/packagecache + +/cJSON-*/ +/curl-*/ +/mbedtls-*/ diff --git a/Integrations/ita/ra_tls_ita/subprojects/cJSON-1.7.12.wrap b/Integrations/ita/ra_tls_ita/subprojects/cJSON-1.7.12.wrap new file mode 100644 index 0000000..cfda2f6 --- /dev/null +++ b/Integrations/ita/ra_tls_ita/subprojects/cJSON-1.7.12.wrap @@ -0,0 +1,7 @@ +[wrap-file] +directory = cJSON-1.7.12 +source_url = https://github.com/DaveGamble/cJSON/archive/v1.7.12.tar.gz +source_fallback_url = https://packages.gramineproject.io/distfiles/cJSON-1.7.12.tar.gz +source_filename = cJSON-1.7.12.tar.gz +source_hash = 760687665ab41a5cff9c40b1053c19572bcdaadef1194e5cba1b5e6f824686e7 +patch_directory = cJSON diff --git a/Integrations/ita/ra_tls_ita/subprojects/curl-7.84.0.wrap b/Integrations/ita/ra_tls_ita/subprojects/curl-7.84.0.wrap new file mode 100644 index 0000000..c7fcfe8 --- /dev/null +++ b/Integrations/ita/ra_tls_ita/subprojects/curl-7.84.0.wrap @@ -0,0 +1,7 @@ +[wrap-file] +directory = curl-7.84.0 +source_url = https://github.com/curl/curl/releases/download/curl-7_84_0/curl-7.84.0.tar.gz +source_fallback_url = https://packages.gramineproject.io/distfiles/curl-7.84.0.tar.gz +source_filename = curl-7.84.0.tar.gz +source_hash = 3c6893d38d054d4e378267166858698899e9d87258e8ff1419d020c395384535 +patch_directory = curl-7.84.0 diff --git a/Integrations/ita/ra_tls_ita/subprojects/mbedtls-mbedtls-3.4.0.wrap b/Integrations/ita/ra_tls_ita/subprojects/mbedtls-mbedtls-3.4.0.wrap new file mode 100644 index 0000000..0021061 --- /dev/null +++ b/Integrations/ita/ra_tls_ita/subprojects/mbedtls-mbedtls-3.4.0.wrap @@ -0,0 +1,11 @@ +[wrap-file] +directory = mbedtls-mbedtls-3.4.0 +source_url = https://github.com/ARMmbed/mbedtls/archive/mbedtls-3.4.0.tar.gz +source_fallback_url = https://packages.gramineproject.io/distfiles/mbedtls-3.4.0.tar.gz +source_filename = mbedtls-3.4.0.tar.gz +source_hash = a5dac98592b1ac2232de0aed8f4ee62dffaa99e843e6f41dca2958095c737afd + +patch_directory = mbedtls + +# this unpacks the sources to `mbedtls-mbedtls-3.4.0/mbedtls-mbedtls-3.4.0` +lead_directory_missing = true diff --git a/Integrations/ita/ra_tls_ita/subprojects/packagefiles/cJSON/meson.build b/Integrations/ita/ra_tls_ita/subprojects/packagefiles/cJSON/meson.build new file mode 100644 index 0000000..3b59382 --- /dev/null +++ b/Integrations/ita/ra_tls_ita/subprojects/packagefiles/cJSON/meson.build @@ -0,0 +1,6 @@ +project('cJSON', 'c') + +cjson_dep = declare_dependency( + sources: files('cJSON.c', 'cJSON.h'), + include_directories: include_directories('.'), +) diff --git a/Integrations/ita/ra_tls_ita/subprojects/packagefiles/curl-7.84.0/compile.sh b/Integrations/ita/ra_tls_ita/subprojects/packagefiles/curl-7.84.0/compile.sh new file mode 100644 index 0000000..23ed82f --- /dev/null +++ b/Integrations/ita/ra_tls_ita/subprojects/packagefiles/curl-7.84.0/compile.sh @@ -0,0 +1,89 @@ +#!/bin/sh + +set -e + +log() { + echo "curl: $*" +} + +CURRENT_SOURCE_DIR="$1" +CURRENT_BUILD_DIR="$2" +PRIVATE_DIR="$3" +SUBPROJ_ROOT="$4" +shift 4 + +BUILD_LOG=$(realpath "$CURRENT_BUILD_DIR/curl-build.log") +rm -f "$BUILD_LOG" + +log "see $BUILD_LOG for full build log" + +log "preparing sources..." + +rm -rf "$PRIVATE_DIR" +cp -ar "$CURRENT_SOURCE_DIR" "$PRIVATE_DIR" + +( + cd "$PRIVATE_DIR" + + log "running configure..." + # The list of configure options is selected based on: + # https://github.com/curl/curl/blob/curl-7_84_0/docs/INSTALL.md#reducing-size + ./configure \ + --disable-alt-svc \ + --disable-ares \ + --disable-cookies \ + --disable-crypto-auth \ + --disable-dateparse \ + --disable-dict \ + --disable-dnsshuffle \ + --disable-doh \ + --disable-file \ + --disable-ftp \ + --disable-get-easy-options \ + --disable-gopher \ + --disable-hsts \ + --disable-http-auth \ + --disable-imap \ + --disable-ldap \ + --disable-ldaps \ + --disable-libcurl-option \ + --disable-manual \ + --disable-mqtt \ + --disable-netrc \ + --disable-ntlm-wb \ + --disable-pop3 \ + --disable-progress-meter \ + --disable-proxy \ + --disable-pthreads \ + --disable-rtsp \ + --disable-shared \ + --disable-smb \ + --disable-smtp \ + --disable-socketpair \ + --disable-telnet \ + --disable-tftp \ + --disable-threaded-resolver \ + --disable-tls-srp \ + --disable-unix-sockets \ + --disable-verbose \ + --disable-versioned-symbols \ + --with-mbedtls="$SUBPROJ_ROOT"/mbedtls-curl \ + --without-brotli \ + --without-libidn2 \ + --without-libpsl \ + --without-librtmp \ + --without-nghttp2 \ + --without-ngtcp2 \ + --without-zlib \ + --without-zstd \ + >>"$BUILD_LOG" 2>&1 + + log "running make..." + + # The curl executable is not needed so we only build libcurl here. + cd lib; make -j"$(nproc)" >>"$BUILD_LOG" 2>&1 +) + +cp -r "$PRIVATE_DIR"/lib/.libs/* "$CURRENT_BUILD_DIR"/ + +log "done" diff --git a/Integrations/ita/ra_tls_ita/subprojects/packagefiles/curl-7.84.0/meson.build b/Integrations/ita/ra_tls_ita/subprojects/packagefiles/curl-7.84.0/meson.build new file mode 100644 index 0000000..e164c04 --- /dev/null +++ b/Integrations/ita/ra_tls_ita/subprojects/packagefiles/curl-7.84.0/meson.build @@ -0,0 +1,23 @@ +project('curl', 'c', version: '7.84.0') + +curl_libs_output = [ + 'libcurl.a', +] + +curl = custom_target('curl', + command: [ + find_program('compile.sh'), + '@CURRENT_SOURCE_DIR@', + meson.current_build_dir(), + '@PRIVATE_DIR@', + meson.build_root() / 'subprojects', + ], + + depends: subproject('mbedtls-mbedtls-3.4.0').get_variable('mbedtls_curl_libs'), + output: curl_libs_output, +) + +curl_minimal_dep = declare_dependency( + link_with: curl, + include_directories: 'include', +) diff --git a/Integrations/ita/ra_tls_ita/subprojects/packagefiles/mbedtls/compile-curl.sh b/Integrations/ita/ra_tls_ita/subprojects/packagefiles/mbedtls/compile-curl.sh new file mode 100644 index 0000000..0ef2ebe --- /dev/null +++ b/Integrations/ita/ra_tls_ita/subprojects/packagefiles/mbedtls/compile-curl.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +set -ex + +CURRENT_SOURCE_DIR="$1" +VENDOR_SOURCE_DIR="$2" +CURRENT_BUILD_DIR="$3" +PRIVATE_DIR="$4" +SUBPROJ_ROOT="$5" +shift 5 + +rm -rf "$PRIVATE_DIR" + +cp -ar "$VENDOR_SOURCE_DIR" "$PRIVATE_DIR" +cp "$CURRENT_SOURCE_DIR"/include/mbedtls/*.h "$PRIVATE_DIR"/include/mbedtls/ +patch -p1 --directory "$PRIVATE_DIR" <"$CURRENT_SOURCE_DIR"/enforce-aes-ni.patch + +make -C "$PRIVATE_DIR" lib SUFFIX="''" install DESTDIR="$SUBPROJ_ROOT"/mbedtls-curl + +for output in $@ +do + cp -a "$PRIVATE_DIR"/library/"$(basename "$output")" "$output" +done diff --git a/Integrations/ita/ra_tls_ita/subprojects/packagefiles/mbedtls/enforce-aes-ni.patch b/Integrations/ita/ra_tls_ita/subprojects/packagefiles/mbedtls/enforce-aes-ni.patch new file mode 100644 index 0000000..160d4f6 --- /dev/null +++ b/Integrations/ita/ra_tls_ita/subprojects/packagefiles/mbedtls/enforce-aes-ni.patch @@ -0,0 +1,35 @@ +diff --git a/library/aes.c b/library/aes.c +index 69da5828ac619ed837559df5c54b6bb1e11076b0..ca54bfa0b086c495d47a7cba9b256ec9cae4ae16 100644 +--- a/library/aes.c ++++ b/library/aes.c +@@ -582,6 +582,9 @@ int mbedtls_aes_setkey_enc(mbedtls_aes_context *ctx, const unsigned char *key, + if (mbedtls_aesni_has_support(MBEDTLS_AESNI_AES)) { + return mbedtls_aesni_setkey_enc((unsigned char *) RK, key, keybits); + } ++ else { ++ return MBEDTLS_ERR_PLATFORM_FEATURE_UNSUPPORTED; ++ } + #endif + + #if defined(MBEDTLS_AESCE_C) && defined(MBEDTLS_HAVE_ARM64) +@@ -687,6 +690,10 @@ int mbedtls_aes_setkey_dec(mbedtls_aes_context *ctx, const unsigned char *key, + (const unsigned char *) (cty.buf + cty.rk_offset), ctx->nr); + goto exit; + } ++ else { ++ ret = MBEDTLS_ERR_PLATFORM_FEATURE_UNSUPPORTED; ++ goto exit; ++ } + #endif + + #if defined(MBEDTLS_AESCE_C) && defined(MBEDTLS_HAVE_ARM64) +@@ -1013,6 +1020,9 @@ int mbedtls_aes_crypt_ecb(mbedtls_aes_context *ctx, + if (mbedtls_aesni_has_support(MBEDTLS_AESNI_AES)) { + return mbedtls_aesni_crypt_ecb(ctx, mode, input, output); + } ++ else { ++ return MBEDTLS_ERR_PLATFORM_FEATURE_UNSUPPORTED; ++ } + #endif + + #if defined(MBEDTLS_AESCE_C) && defined(MBEDTLS_HAVE_ARM64) diff --git a/Integrations/ita/ra_tls_ita/subprojects/packagefiles/mbedtls/include/mbedtls/config-min.h b/Integrations/ita/ra_tls_ita/subprojects/packagefiles/mbedtls/include/mbedtls/config-min.h new file mode 100644 index 0000000..12f4a78 --- /dev/null +++ b/Integrations/ita/ra_tls_ita/subprojects/packagefiles/mbedtls/include/mbedtls/config-min.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: LGPL-3.0-or-later */ +/* Copyright (C) 2017 Fortanix, Inc. + * Copyright (C) 2021 Intel Corp. + */ + +/* This mbedTLS config is for v3.4.0 and assumes Intel x86-64 CPU with AESNI and SSE2 support */ + +#pragma once + +#define MBEDTLS_AESNI_C +#define MBEDTLS_AES_C +#define MBEDTLS_BASE64_C +#define MBEDTLS_BIGNUM_C +#define MBEDTLS_CIPHER_C +#define MBEDTLS_CMAC_C +#define MBEDTLS_CTR_DRBG_C +#define MBEDTLS_DHM_C +#define MBEDTLS_ENTROPY_C +#define MBEDTLS_ENTROPY_HARDWARE_ALT +#define MBEDTLS_ERROR_C +#define MBEDTLS_GCM_C +#define MBEDTLS_GENPRIME +#define MBEDTLS_HAVE_ASM +#define MBEDTLS_HAVE_SSE2 +#if defined(__x86_64__) +#define MBEDTLS_HAVE_X86_64 +#endif +#define MBEDTLS_HKDF_C +#define MBEDTLS_KEY_EXCHANGE_PSK_ENABLED +#define MBEDTLS_MD_C +#define MBEDTLS_NET_C +#define MBEDTLS_NO_PLATFORM_ENTROPY +#define MBEDTLS_NO_UDBL_DIVISION +#define MBEDTLS_OID_C +#define MBEDTLS_PKCS1_V15 +#define MBEDTLS_PLATFORM_C +#define MBEDTLS_PLATFORM_ZEROIZE_ALT +#define MBEDTLS_RSA_C +#define MBEDTLS_SHA256_C +#define MBEDTLS_SSL_CIPHERSUITES MBEDTLS_TLS_PSK_WITH_AES_128_GCM_SHA256 +#define MBEDTLS_SSL_CLI_C +#define MBEDTLS_SSL_CONTEXT_SERIALIZATION +#define MBEDTLS_SSL_PROTO_TLS1_2 +#define MBEDTLS_SSL_SRV_C +#define MBEDTLS_SSL_TLS_C diff --git a/Integrations/ita/ra_tls_ita/subprojects/packagefiles/mbedtls/meson.build b/Integrations/ita/ra_tls_ita/subprojects/packagefiles/mbedtls/meson.build new file mode 100644 index 0000000..aa89992 --- /dev/null +++ b/Integrations/ita/ra_tls_ita/subprojects/packagefiles/mbedtls/meson.build @@ -0,0 +1,33 @@ +project('mbedtls', 'c', version: '3.4.0') + +mbedtls_curl_libs = custom_target('mbedtls_curl', + command: [ + find_program('compile-curl.sh'), + '@CURRENT_SOURCE_DIR@', + '@CURRENT_SOURCE_DIR@/mbedtls-mbedtls-3.4.0', + meson.current_build_dir(), + '@PRIVATE_DIR@', + meson.build_root() / 'subprojects', + '@OUTPUT@', + ], + + input: ['mbedtls-mbedtls-3.4.0/Makefile'], + + output: [ + 'libmbedcrypto.a', + 'libmbedtls.a', + 'libmbedx509.a', + ], + + build_by_default: true, +) + +mbedtls_inc = include_directories('include', 'mbedtls-mbedtls-3.4.0/include') + +mbedtls_curl_dep = declare_dependency( + # HACK: Apparently Meson considers the `mbedtls_curl_libs` to be "not linkable", because it has + # multiple outputs; however, it allows picking the outputs one by one. + link_with: [mbedtls_curl_libs[0], mbedtls_curl_libs[1], mbedtls_curl_libs[2]], + include_directories: mbedtls_inc, + compile_args: '-DMBEDTLS_CONFIG_FILE="mbedtls/config-min.h"', +)