/* Copyright (C) 2019, Texas A&M University. This file is part of Graphene Library OS. Graphene Library OS is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Graphene Library OS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #include #include #include #include #include "sgx_internal.h" #include "sgx_arch.h" #include "sgx_enclave.h" #include "sgx_attest.h" #include "quote/aesm.pb-c.h" #include #include #include #define __USE_XOPEN2K8 #include /* * Connect to the AESM service to interact with the architectural enclave. Must reconnect * for each request to the AESM service. * * Some older versions of AESM service use a UNIX socket at "\0sgx_aesm_socket_base". * The latest AESM service binds the socket at "/var/run/aesmd/aesm.socket". This function * tries to connect to either of the paths to ensure connectivity. */ static int connect_aesm_service(void) { int sock = INLINE_SYSCALL(socket, 3, AF_UNIX, SOCK_STREAM, 0); if (IS_ERR(sock)) return -ERRNO(sock); struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; (void)strcpy_static(addr.sun_path, "\0sgx_aesm_socket_base", sizeof(addr.sun_path)); int ret = INLINE_SYSCALL(connect, 3, sock, &addr, sizeof(addr)); if (!IS_ERR(ret)) return sock; if (ERRNO(ret) != ECONNREFUSED) goto err; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; (void)strcpy_static(addr.sun_path, "/var/run/aesmd/aesm.socket", sizeof(addr.sun_path)); ret = INLINE_SYSCALL(connect, 3, sock, &addr, sizeof(addr)); if (!IS_ERR(ret)) return sock; err: INLINE_SYSCALL(close, 1, sock); return -ERRNO(ret); } /* * A wrapper for both creating a connection to the AESM service and submitting a request * to the service. Upon success, the function returns a response from the AESM service * back to the caller. */ static int request_aesm_service(Request* req, Response** res) { int aesm_socket = connect_aesm_service(); if (aesm_socket < 0) return aesm_socket; uint32_t req_len = (uint32_t) request__get_packed_size(req); uint8_t* req_buf = __alloca(req_len); request__pack(req, req_buf); int ret = INLINE_SYSCALL(write, 3, aesm_socket, &req_len, sizeof(req_len)); if (IS_ERR(ret)) goto err; ret = INLINE_SYSCALL(write, 3, aesm_socket, req_buf, req_len); if (IS_ERR(ret)) goto err; uint32_t res_len; ret = INLINE_SYSCALL(read, 3, aesm_socket, &res_len, sizeof(res_len)); if (IS_ERR(ret)) goto err; uint8_t* res_buf = __alloca(res_len); ret = INLINE_SYSCALL(read, 3, aesm_socket, res_buf, res_len); if (IS_ERR(ret)) goto err; *res = response__unpack(NULL, res_len, res_buf); ret = *res == NULL ? -EINVAL : 0; err: INLINE_SYSCALL(close, 1, aesm_socket); return -ERRNO(ret); } // Retrieve the targetinfo for the AESM enclave for generating the local attestation report. int init_aesm_targetinfo(sgx_target_info_t* aesm_targetinfo) { Request req = REQUEST__INIT; Request__InitQuoteRequest initreq = REQUEST__INIT_QUOTE_REQUEST__INIT; req.initquotereq = &initreq; Response* res = NULL; int ret = request_aesm_service(&req, &res); if (ret < 0) return ret; ret = -EPERM; if (!res->initquoteres) { SGX_DBG(DBG_E, "aesm_service returned wrong message\n"); goto failed; } Response__InitQuoteResponse* r = res->initquoteres; if (r->errorcode != 0) { SGX_DBG(DBG_E, "aesm_service returned error: %d\n", r->errorcode); goto failed; } if (r->targetinfo.len != sizeof(*aesm_targetinfo)) { SGX_DBG(DBG_E, "aesm_service returned invalid target info\n"); goto failed; } memcpy(aesm_targetinfo, r->targetinfo.data, sizeof(*aesm_targetinfo)); ret = 0; failed: response__free_unpacked(res, NULL); return ret; } /* * Contact to Intel Attestation Service and retrieve the signed attestation report * * @subkey: SPID subscription key. * @nonce: Random nonce generated in the enclave. * @quote: Platform quote retrieved from AESMD. * @attestation: Attestation data to be returned to the enclave. */ int contact_intel_attest_service(const char* subkey, const sgx_quote_nonce_t* nonce, const sgx_quote_t* quote, sgx_attestation_t* attestation) { size_t quote_len = sizeof(sgx_quote_t) + quote->sig_len; size_t quote_str_len; lib_Base64Encode((uint8_t*)quote, quote_len, NULL, "e_str_len); char* quote_str = __alloca(quote_str_len); int ret = lib_Base64Encode((uint8_t*)quote, quote_len, quote_str, "e_str_len); if (ret < 0) return ret; size_t nonce_str_len = sizeof(sgx_quote_nonce_t) * 2 + 1; char* nonce_str = __alloca(nonce_str_len); __bytes2hexstr((void *)nonce, sizeof(sgx_quote_nonce_t), nonce_str, nonce_str_len); // Create two temporary files for dumping the header and output of HTTPS request to IAS char https_header_path[] = "/tmp/gsgx-ias-header-XXXXXX"; char https_output_path[] = "/tmp/gsgx-ias-output-XXXXXX"; char* https_header = NULL; char* https_output = NULL; ssize_t https_header_len = 0; ssize_t https_output_len = 0; int header_fd = -1; int output_fd = -1; int fds[2] = {-1, -1}; header_fd = mkstemp(https_header_path); if (header_fd < 0) goto failed; output_fd = mkstemp(https_output_path); if (output_fd < 0) goto failed; ret = INLINE_SYSCALL(socketpair, 4, AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, &fds[0]); if (IS_ERR(ret)) goto failed; // Write HTTPS request in XML format size_t https_request_len = quote_str_len + nonce_str_len + HTTPS_REQUEST_MAX_LENGTH; char* https_request = __alloca(https_request_len); https_request_len = snprintf(https_request, https_request_len, "{\"isvEnclaveQuote\":\"%s\",\"nonce\":\"%s\"}", quote_str, nonce_str); ret = INLINE_SYSCALL(write, 3, fds[1], https_request, https_request_len); if (IS_ERR(ret)) goto failed; INLINE_SYSCALL(close, 1, fds[1]); fds[1] = -1; char subscription_header[64]; snprintf(subscription_header, 64, "Ocp-Apim-Subscription-Key: %s", subkey); // Start a HTTPS client (using CURL) const char* https_client_args[] = { "/usr/bin/curl", "-s", "--tlsv1.2", "-X", "POST", "-H", "Content-Type: application/json", "-H", subscription_header, "--data", "@-", "-o", https_output_path, "-D", https_header_path, IAS_REPORT_URL, NULL, }; int pid = ARCH_VFORK(); if (IS_ERR(pid)) goto failed; if (!pid) { INLINE_SYSCALL(dup2, 2, fds[0], 0); extern char** environ; INLINE_SYSCALL(execve, 3, https_client_args[0], https_client_args, environ); /* shouldn't get to here */ SGX_DBG(DBG_E, "unexpected failure of new process\n"); __asm__ volatile ("hlt"); return 0; } // Make sure the HTTPS client has exited properly int status; ret = INLINE_SYSCALL(wait4, 4, pid, &status, 0, NULL); if (IS_ERR(ret) || !WIFEXITED(status) || WEXITSTATUS(status) != 0) goto failed; // Read the HTTPS output ret = INLINE_SYSCALL(open, 2, https_output_path, O_RDONLY); if (IS_ERR(ret)) goto failed; INLINE_SYSCALL(close, 1, output_fd); output_fd = ret; https_output_len = INLINE_SYSCALL(lseek, 3, output_fd, 0, SEEK_END); if (IS_ERR(https_output_len) || !https_output_len) goto failed; https_output = (char*)INLINE_SYSCALL(mmap, 6, NULL, ALLOC_ALIGN_UP(https_output_len), PROT_READ, MAP_PRIVATE|MAP_FILE, output_fd, 0); if (IS_ERR_P(https_output)) goto failed; // Read the HTTPS headers ret = INLINE_SYSCALL(open, 2, https_header_path, O_RDONLY); if (IS_ERR(ret)) goto failed; INLINE_SYSCALL(close, 1, header_fd); header_fd = ret; https_header_len = INLINE_SYSCALL(lseek, 3, header_fd, 0, SEEK_END); if (IS_ERR(https_header_len) || !https_header_len) goto failed; https_header = (char*)INLINE_SYSCALL(mmap, 6, NULL, ALLOC_ALIGN_UP(https_header_len), PROT_READ, MAP_PRIVATE|MAP_FILE, header_fd, 0); if (IS_ERR_P(https_header)) goto failed; // Parse the HTTPS headers size_t ias_sig_len = 0; uint8_t* ias_sig = NULL; size_t ias_certs_len = 0; char* ias_certs = NULL; char* start = https_header; char* end = strchr(https_header, '\n'); while (end) { char* next_start = end + 1; // If the eol (\n) is preceded by a return (\r), move the end pointer. if (end > start + 1 && *(end - 1) == '\r') end--; if (strstartswith_static(start, "X-IASReport-Signature: ")) { start += static_strlen("X-IASReport-Signature: "); // Decode IAS report signature ret = lib_Base64Decode(start, end - start, NULL, &ias_sig_len); if (ret < 0) { SGX_DBG(DBG_E, "Malformed IAS signature\n"); goto failed; } ias_sig = (uint8_t*)INLINE_SYSCALL(mmap, 6, NULL, ALLOC_ALIGN_UP(ias_sig_len), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); if (IS_ERR_P(ias_sig)) { SGX_DBG(DBG_E, "Cannot allocate memory for IAS report signature\n"); goto failed; } ret = lib_Base64Decode(start, end - start, ias_sig, &ias_sig_len); if (ret < 0) { SGX_DBG(DBG_E, "Malformed IAS report signature\n"); goto failed; } } else if (strstartswith_static(start, "X-IASReport-Signing-Certificate: ")) { start += static_strlen("X-IASReport-Signing-Certificate: "); // Decode IAS signature chain ias_certs_len = end - start; ias_certs = (char*)INLINE_SYSCALL(mmap, 6, NULL, ALLOC_ALIGN_UP(ias_certs_len), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); if (IS_ERR_P(ias_certs)) { SGX_DBG(DBG_E, "Cannot allocate memory for IAS certificate chain\n"); goto failed; } /* * The value of x-iasreport-signing-certificate is a certificate chain which * consists of multiple certificates represented in the PEM format. The value * is escaped using the % character. For example, a %20 in the certificate * needs to be replaced as the newline ("\n"). The following logic iteratively * reads the character and converts the escaped characters into the buffer. */ size_t total_bytes = 0; // Convert escaped characters for (size_t i = 0; i < ias_certs_len; i++) { if (start[i] == '%') { int8_t hex1 = hex2dec(start[i + 1]), hex2 = hex2dec(start[i + 2]); if (hex1 < 0 || hex2 < 0) goto failed; char c = hex1 * 16 + hex2; if (c != '\n') ias_certs[total_bytes++] = c; i += 2; } else { ias_certs[total_bytes++] = start[i]; } } // Adjust certificate chain length ias_certs[total_bytes++] = '\0'; if (ALLOC_ALIGN_UP(total_bytes) < ALLOC_ALIGN_UP(ias_certs_len)) INLINE_SYSCALL(munmap, 2, ALLOC_ALIGN_UP(total_bytes), ALLOC_ALIGN_UP(ias_certs_len) - ALLOC_ALIGN_UP(total_bytes)); ias_certs_len = total_bytes; } start = next_start; end = strchr(start, '\n'); } if (!ias_sig) { SGX_DBG(DBG_E, "IAS returned invalid headers: no report signature\n"); goto failed; } if (!ias_certs) { SGX_DBG(DBG_E, "IAS returned invalid headers: no certificate chain\n"); goto failed; } // Now return the attestation data, including the IAS response, signature, and the // certificate chain back to the caller. attestation->ias_report = https_output; attestation->ias_report_len = https_output_len; attestation->ias_sig = ias_sig; attestation->ias_sig_len = ias_sig_len; attestation->ias_certs = ias_certs; attestation->ias_certs_len = ias_certs_len; https_output = NULL; // Don't free the HTTPS output ret = 0; done: if (https_header) INLINE_SYSCALL(munmap, 2, https_header, ALLOC_ALIGN_UP(https_header_len)); if (https_output) INLINE_SYSCALL(munmap, 2, https_output, ALLOC_ALIGN_UP(https_output_len)); if (fds[0] != -1) INLINE_SYSCALL(close, 1, fds[0]); if (fds[1] != -1) INLINE_SYSCALL(close, 1, fds[1]); if (header_fd != -1) { INLINE_SYSCALL(close, 1, header_fd); INLINE_SYSCALL(unlink, 1, https_header_path); } if (output_fd != -1) { INLINE_SYSCALL(close, 1, output_fd); INLINE_SYSCALL(unlink, 1, https_output_path); } return ret; failed: ret = -PAL_ERROR_DENIED; goto done; } /* * This wrapper function performs the whole attestation procedure outside the enclave (except * retrieving the local remote attestation and verification). The function first contacts * the AESM service to retrieve a quote of the platform and the report of the quoting enclave. * Then, the function submits the quote to the IAS through a HTTPS client (CURL) to exchange * for a remote attestation report signed by a Intel-approved certificate chain. Finally, the * function returns the QE report, the quote, and the response from the IAS back to the enclave * for verification. * * @spid: The client SPID registered with IAS. * @subkey: SPID subscription key. * @linkable: A boolean that represents whether the SPID is linkable. * @report: The local report of the target enclave. * @nonce: A 16-byte nonce randomly generated inside the enclave. * @attestation: A structure for storing the response from the AESM service and the IAS. */ int retrieve_verified_quote(const sgx_spid_t* spid, const char* subkey, bool linkable, const sgx_report_t* report, const sgx_quote_nonce_t* nonce, sgx_attestation_t* attestation) { int ret = connect_aesm_service(); if (ret < 0) return ret; Request req = REQUEST__INIT; Request__GetQuoteRequest getreq = REQUEST__GET_QUOTE_REQUEST__INIT; getreq.report.data = (uint8_t*) report; getreq.report.len = SGX_REPORT_ACTUAL_SIZE; getreq.quote_type = linkable ? SGX_LINKABLE_SIGNATURE : SGX_UNLINKABLE_SIGNATURE; getreq.spid.data = (uint8_t*) spid; getreq.spid.len = sizeof(*spid); getreq.has_nonce = true; getreq.nonce.data = (uint8_t*) nonce; getreq.nonce.len = sizeof(*nonce); getreq.buf_size = SGX_QUOTE_MAX_SIZE; getreq.has_qe_report = true; getreq.qe_report = true; req.getquotereq = &getreq; Response* res = NULL; ret = request_aesm_service(&req, &res); if (ret < 0) return ret; if (!res->getquoteres) { SGX_DBG(DBG_E, "aesm_service returned wrong message\n"); goto failed; } Response__GetQuoteResponse* r = res->getquoteres; if (r->errorcode != 0) { SGX_DBG(DBG_E, "aesm_service returned error: %d\n", r->errorcode); goto failed; } if (!r->has_quote || r->quote.len < sizeof(sgx_quote_t) || !r->has_qe_report || r->qe_report.len != SGX_REPORT_ACTUAL_SIZE) { SGX_DBG(DBG_E, "aesm_service returned invalid quote or report\n"); goto failed; } sgx_quote_t* quote = (sgx_quote_t*) INLINE_SYSCALL(mmap, 6, NULL, ALLOC_ALIGN_UP(r->quote.len), PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); if (IS_ERR_P(quote)) { SGX_DBG(DBG_E, "Failed to allocate memory for the quote\n"); goto failed; } memcpy(quote, r->quote.data, r->quote.len); attestation->quote = quote; attestation->quote_len = r->quote.len; ret = contact_intel_attest_service(subkey, nonce, (sgx_quote_t *) quote, attestation); if (ret < 0) { INLINE_SYSCALL(munmap, 2, quote, ALLOC_ALIGN_UP(r->quote.len)); goto failed; } memcpy(&attestation->qe_report, r->qe_report.data, sizeof(sgx_report_t)); response__free_unpacked(res, NULL); return 0; failed: response__free_unpacked(res, NULL); return -PAL_ERROR_DENIED; }