/* 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 #include #include #include "quote/generated-cacert.h" /* * Graphene's simple remote attestation feature: * * This feature is for verifying the SGX hardware platforms for executing applications * in Graphene, to a remote trusted entity. The whole remote attestation process requires * interaction with the Intel Attestation Service (IAS) and Intel PSW enclave for * generating the remote and local quotes. Once the platform is fully verified, Graphene * will continue to initialize the library OS and the application; otherwise the execution * should terminate. * * Graphene's remote attestation process is meant to be transparent to the application; * that is, no change is required to the source code or binary of the application. The * remote attestation feature is enabled if "sgx.ra_client_spid" and "sgx.ra_client_key" * are specified in the manifest. To obtain the SPID and the subscription key, register in * the Intel API Portal: https://api.portal.trustedservices.intel.com/EPID-attestation. * * The remote attestation process contains four steps: * * (1) Initialization: * * +-------------------+ +-----------+ +---------+ * | Intel PSW Enclave | target info (PSW) | Untrusted | target info (PSW) | Enclave | * | (AESMD) |------------------->| PAL |-------------------->| PAL | * +-------------------+ +-----------+ +---------+ * * Before the enclave is created, Graphene contacts the AESMD to retrieve the target info * of the Intel PSW enclave. The target info is used for generating local report later. * * (2) OCALL + Local attestation: * * +---------+ +-----------+ +-------------------+ * | Enclave | OCALL(GET_ATTESTATION) | Untrusted | Report (PAL->PSW) | Intel PSW Enclave | * | PAL |----------------------->| PAL |------------------>| (AESMD) | * +---------+ +-----------+ +-------------------+ * * The enclave PAL uses ENCLU[EREPORT] to generate a local report for the PSW enclave to * verify that the two enclaves are on the same platform. The enclave PAL then issues an * OCALL(GET_ATTESTATION), along with the report, the SPID, and the subscription key. * The report is given to the PSW enclave to generate a local quote. The PSW enclave will * verify the report, decide whether to trust the Graphene enclave, and then sign the * local quote with a PSW-only attestation key. * * (3) Contact the IAS for platform report: * * The local quote from the PSW enclave needs to be verified by the IAS. Different from the * Intel SDK model, Graphene does not rely on a third party to contact the IAS. * Graphene contacts the IAS as part of its remote attestation process. * * +-----------+ +--------------+ +---------------+ * | Untrusted | fork + execve | HTTPS client | HTTPS (quote, SPID, key) | Intel Attest. | * | PAL |-------------->| (CURL) |--------------------------->| Service | * +-----------+ +--------------+ +---------------+ * * Graphene now uses a commodity HTTPS client (CURL) to contact the IAS. This is not fully * compliant to the TOS of the IAS, because the SPID and the key is not protected from * the untrusted host. The verification of the SGX platform does not require secrecy of the * SPID/key, because the SPID/key is only used by the IAS to identify the clients. Even if * an attacker has obtained the SPID/key, the attacker cannot tamper the quote and the IAS * attestation report. * * (4) Checking the IAS report: * * +---------------+ +-----------+ HTTPS resp, Certs, +---------+ * | Intel Attest. | HTTPS resp, Certs | Untrusted | Report (PSW->PAL) | Enclave | * | Service |------------------>| PAL |-------------------->| PAL | * +---------------+ +-----------+ +---------+ * * Finally, Graphene returns the HTTPS response and a certificate chain from the IAS * back to the enclave PAL, along with the local report from the PSW enclave. Graphene * then verifies the attestation result based on the following criteria: * * - The HTTPS response needs to be signed by the certificate chain, including the first * certificate to generate the signature, and all the following certificates to sign * the previous certificates. * - The last certificate in the chain will be signed by a known IAS root CA, hard-coded * in the enclave PAL. * - The report from the PSW enclave needs to be verified. This will establish the mutual * trust between the enclave PAL and the PSW enclave. * - The HTTPS response from the IAS needs to contain the same quote generated from the * PSW enclave, the same mr_enclave, attributes, and 64-byte report data. * - The HTTPS response needs to have an acceptable status, which is "OK" by default, or * "GROUP_OUT_OF_DATE" if "sgx.ra_accept_group_out_of_date = 1" is in the manifest, or * "CONFIGURATION_NEEDED" if "sgx.ra_accept_configuration_needed = 1" is in the manifest. * If you obtain a status besides OK, please see the SECURITY ADVISORIES in README.md. */ /* * Perform the initial attestation procedure if "sgx.ra_client.spid" is specified in the manifest * file. */ int init_trusted_platform(void) { char spid_hex[sizeof(sgx_spid_t) * 2 + 1]; ssize_t len = get_config(pal_state.root_config, "sgx.ra_client_spid", spid_hex, sizeof(spid_hex)); if (len <= 0) { SGX_DBG(DBG_E, "*** No client info specified in the manifest. " "Graphene will not perform remote attestation ***\n"); return 0; } if (len != sizeof(sgx_spid_t) * 2) { SGX_DBG(DBG_E, "Malformed sgx.ra_client_spid value in the manifest: %s\n", spid_hex); return -PAL_ERROR_INVAL; } sgx_spid_t spid; for (ssize_t i = 0; i < len; i++) { int8_t val = hex2dec(spid_hex[i]); if (val < 0) { SGX_DBG(DBG_E, "Malformed sgx.ra_client_spid value in the manifest: %s\n", spid_hex); return -PAL_ERROR_INVAL; } spid[i / 2] = spid[i / 2] * 16 + (uint8_t)val; } char subkey[CONFIG_MAX]; len = get_config(pal_state.root_config, "sgx.ra_client_key", subkey, sizeof(subkey)); if (len <= 0) { SGX_DBG(DBG_E, "No sgx.ra_client_key in the manifest\n"); return -PAL_ERROR_INVAL; } char buf[2]; len = get_config(pal_state.root_config, "sgx.ra_client_linkable", buf, sizeof(buf)); bool linkable = (len == 1 && buf[0] == '1'); len = get_config(pal_state.root_config, "sgx.ra_accept_group_out_of_date", buf, sizeof(buf)); bool accept_group_out_of_date = (len == 1 && buf[0] == '1'); len = get_config(pal_state.root_config, "sgx.ra_accept_configuration_needed", buf, sizeof(buf)); bool accept_configuration_needed = (len == 1 && buf[0] == '1'); sgx_quote_nonce_t nonce; int ret = _DkRandomBitsRead(&nonce, sizeof(nonce)); if (ret < 0) return ret; char* status; char* timestamp; ret = sgx_verify_platform(&spid, subkey, &nonce, (sgx_report_data_t*)&pal_enclave_state, linkable, accept_group_out_of_date, accept_configuration_needed, /*ret_attestation=*/NULL, &status, ×tamp); if (ret < 0) return ret; // If the attestation is successful, update the control block __pal_control.attestation_status = status; __pal_control.attestation_timestamp = timestamp; return ret; } /* * A simple function to parse a X509 certificate for only the certificate body, the signature, * and the public key. * * TODO: Currently no verification of the X509 certificate. * * @cert: The certificate to parse (DER format). * @cert_len: The length of cert. * @body: The certificate body (the signed part). * @body_len: The length of body. * @sig: The certificate signature. * @sig_len: The length of sig. * @pubkey: The RSA public key from the certificate. */ static int parse_x509(uint8_t* cert, size_t cert_len, uint8_t** body, size_t* body_len, uint8_t** sig, size_t* sig_len, LIB_RSA_KEY* pubkey) { uint8_t* ptr = cert; uint8_t* end = cert + cert_len; enum asn1_tag tag; bool is_cons; uint8_t* buf; size_t buf_len; int ret; // X509Certificate := SEQUENCE { // Body CertificateBody, // SignatureAlgorithm AlgorithmDescriptor, // Signature BIT STRING } ret = lib_ASN1GetSerial(&ptr, end, &tag, &is_cons, &buf, &buf_len); if (ret < 0) return ret; if (tag != ASN1_SEQUENCE || !is_cons) return -PAL_ERROR_INVAL; uint8_t* cert_signed = ptr = buf; uint8_t* cert_body; uint8_t* cert_sig; size_t cert_body_len, cert_sig_len; end = buf + buf_len; ret = lib_ASN1GetSerial(&ptr, end, &tag, &is_cons, &cert_body, &cert_body_len); if (ret < 0) return ret; if (tag != ASN1_SEQUENCE || !is_cons) return -PAL_ERROR_INVAL; size_t cert_signed_len = ptr - cert_signed; // Skip SignatureAlgorithm ret = lib_ASN1GetSerial(&ptr, end, &tag, &is_cons, &buf, &buf_len); if (ret < 0) return ret; ret = lib_ASN1GetBitstring(&ptr, end, &cert_sig, &cert_sig_len); if (ret < 0) return ret; // CertficateBody := SEQUENCE { // Version CONSTANT, // SerialNumber INTEGER, // Signature AlgorithmDiscriptor, // Issuer Name, // Validity ValidityTime, // Subject Name, // SubjectPublicKeyInfo PublicKeyInfo, // (optional fields) } ptr = cert_body; end = cert_body + cert_body_len; // Skip Version, SerialNumber, Signature, Issuer, Validity, and Subject for (int i = 0; i < 6; i++) { ret = lib_ASN1GetSerial(&ptr, end, &tag, &is_cons, &buf, &buf_len); if (ret < 0) return ret; } // Get SubjectPublicKeyInfo ret = lib_ASN1GetSerial(&ptr, end, &tag, &is_cons, &buf, &buf_len); if (ret < 0) return ret; if (tag != ASN1_SEQUENCE || !is_cons) return -PAL_ERROR_INVAL; // PublickKeyInfo := SEQUENCE { // PublicKeyAlgorithm AlgorithmDescriptor, // PublicKey BIT STRING } ptr = buf; end = buf + buf_len; // Skip PublicKeyAlgorithm ret = lib_ASN1GetSerial(&ptr, end, &tag, &is_cons, &buf, &buf_len); if (ret < 0) return ret; // Get PublicKey uint8_t* pkey_bits; size_t pkey_bits_len; ret = lib_ASN1GetBitstring(&ptr, end, &pkey_bits, &pkey_bits_len); if (ret < 0) return ret; // RSAPublicKey := SEQUENCE { // Modulus Integer, // PublicExponent Integer } ptr = pkey_bits; end = pkey_bits + pkey_bits_len; ret = lib_ASN1GetSerial(&ptr, end, &tag, &is_cons, &buf, &buf_len); if (ret < 0) return ret; uint8_t* mod; uint8_t* exp; size_t mod_len, exp_len; ptr = buf; end = buf + buf_len; ret = lib_ASN1GetLargeNumberLength(&ptr, end, &mod_len); if (ret < 0) return ret; mod = ptr; ptr += mod_len; ret = lib_ASN1GetLargeNumberLength(&ptr, end, &exp_len); if (ret < 0) return ret; exp = ptr; ptr += exp_len; *body = malloc(cert_signed_len); *body_len = cert_signed_len; memcpy(*body, cert_signed, cert_signed_len); *sig = malloc(cert_sig_len); *sig_len = cert_sig_len; memcpy(*sig, cert_sig, cert_sig_len); ret = lib_RSAInitKey(pubkey); if (ret < 0) return ret; ret = lib_RSAImportPublicKey(pubkey, exp, exp_len, mod, mod_len); if (ret < 0) return ret; return 0; } /* * Same as parse_x509(), but parse the certificate in PEM format. * * @cert: The starting address for parsing the certificate. * @cert_end: Returns the end of certificate after parsing. * @body: The certificate body (the signed part). * @body_len: The length of body. * @sig: The certificate signature. * @sig_len: The length of sig. * @pubkey: The RSA public key from the certificate. */ static int parse_x509_pem(char* cert, char** cert_end, uint8_t** body, size_t* body_len, uint8_t** sig, size_t* sig_len, LIB_RSA_KEY* pubkey) { int ret; char* start = strchr(cert, '-'); if (!start) { // No more certificate *cert_end = NULL; return 0; } if (!strstartswith_static(start, "-----BEGIN CERTIFICATE-----")) return -PAL_ERROR_INVAL; start += static_strlen("-----BEGIN CERTIFICATE-----"); char* end = strchr(start, '-'); if (!strstartswith_static(end, "-----END CERTIFICATE-----")) return -PAL_ERROR_INVAL; size_t cert_der_len; ret = lib_Base64Decode(start, end - start, NULL, &cert_der_len); if (ret < 0) return ret; uint8_t* cert_der = __alloca(cert_der_len); ret = lib_Base64Decode(start, end - start, cert_der, &cert_der_len); if (ret < 0) return ret; ret = parse_x509(cert_der, cert_der_len, body, body_len, sig, sig_len, pubkey); if (ret < 0) return ret; *cert_end = end + static_strlen("-----END CERTIFICATE-----"); return 0; } /* * Perform the remote attestation to verify the current SGX platform. * * The remote attestation procedure verifies two primary properties: (1) The current execution runs * in an SGX enclave; and (2) The enclave is created on a genuine, up-to-date Intel CPU. This * procedure requires interaction with the Intel PSW quoting enclave (AESMD) and the Intel * Attestation Service (IAS). The quoting enclave verifies a local attestation report from the * target enclave, and then generates a quoting enclave (QE) report and a platform quote signed by * the platform's attestation key. The IAS then verifies the platform quote and issues a remote * attestation report, signed by a certificate chain attached to the report. * * TODO: currently no verification of the correctness of the IAS certificate * * @spid: The SPID registered for the Intel Attestation Service (IAS). * @subkey: SPID subscription key. * @nonce: A 16-byte nonce to be included in the quote. * @report_data: A 64-byte bytestring to be included in the local report and the quote. * @linkable: Specify whether the SPID is linkable. * @accept_group_out_of_date: Specify whether to accept GROUP_OUT_OF_DATE from IAS * @accept_configuration_needed: Specify whether to accept CONFIGURATION_NEEDED from IAS * @ret_attestation: Returns the retrieved attestation data. * @ret_ias_status: Returns a pointer to the attestation status (as a string) from the IAS. * @ret_ias_timestamp: Returns a pointer to the timestamp (as a string) from the IAS. * Timestamp format: %Y-%m-%dT%H:%M:%S.%f (Ex: 2019-08-01T12:30:00.123456) */ int sgx_verify_platform(sgx_spid_t* spid, const char* subkey, sgx_quote_nonce_t* nonce, sgx_report_data_t* report_data, bool linkable, bool accept_group_out_of_date, bool accept_configuration_needed, sgx_attestation_t* ret_attestation, char** ret_ias_status, char** ret_ias_timestamp) { SGX_DBG(DBG_S, "Request quote:\n"); SGX_DBG(DBG_S, " spid: %s\n", ALLOCA_BYTES2HEXSTR(*spid)); SGX_DBG(DBG_S, " type: %s\n", linkable ? "linkable" : "unlinkable"); SGX_DBG(DBG_S, " nonce: %s\n", ALLOCA_BYTES2HEXSTR(*nonce)); __sgx_mem_aligned sgx_report_t report; __sgx_mem_aligned sgx_target_info_t targetinfo = pal_sec.aesm_targetinfo; int ret = sgx_report(&targetinfo, report_data, &report); if (ret) { SGX_DBG(DBG_E, "Failed to get report for attestation\n"); return -PAL_ERROR_DENIED; } sgx_attestation_t attestation; ret = ocall_get_attestation(spid, subkey, linkable, &report, nonce, &attestation); if (ret < 0) { SGX_DBG(DBG_E, "Failed to get attestation\n"); return ret; } // First, verify the report from the quoting enclave ret = sgx_verify_report(&attestation.qe_report); if (ret < 0) { SGX_DBG(DBG_E, "Failed to verify QE report, ret = %d\n", ret); goto failed; } // Verify the IAS response against the certificate chain uint8_t* data_to_verify = (uint8_t*)attestation.ias_report; uint8_t* data_sig = attestation.ias_sig; size_t data_len = attestation.ias_report_len; size_t data_sig_len = attestation.ias_sig_len; // Attach the IAS signing chain with the hard-coded CA certificate const char* ca_cert = IAS_CA_CERT; size_t len1 = strlen(attestation.ias_certs); size_t len2 = static_strlen(IAS_CA_CERT); char* certs = malloc(len1 + len2 + 1); memcpy(certs, attestation.ias_certs, len1); memcpy(certs + len1, ca_cert, len2); certs[len1 + len2] = 0; free(attestation.ias_certs); attestation.ias_certs = certs; attestation.ias_certs_len = len1 + len2 + 1; // There can be multiple certificates in the chain. We need to use the public key from the // *first* certificate to verify the IAS response. For each certificate except the last one, we // need to use the public key from the *next* certificate to verify the certificate body. The // last certificate will be verified by the CA certificate (hard-coded in the binary) for (char* cert_start = attestation.ias_certs; cert_start < attestation.ias_certs + attestation.ias_certs_len && *cert_start;) { // Generate the message digest first (without RSA) LIB_SHA256_CONTEXT ctx; uint8_t hash[32]; if ((ret = lib_SHA256Init(&ctx)) < 0) goto failed; if ((ret = lib_SHA256Update(&ctx, data_to_verify, data_len)) < 0) goto failed; if ((ret = lib_SHA256Final(&ctx, hash)) < 0) goto failed; // Use the public key to verify the last signature uint8_t* cert_body; uint8_t* cert_sig; size_t cert_body_len; size_t cert_sig_len; LIB_RSA_KEY cert_key; ret = parse_x509_pem(cert_start, &cert_start, &cert_body, &cert_body_len, &cert_sig, &cert_sig_len, &cert_key); if (ret < 0) { SGX_DBG(DBG_E, "Failed to parse IAS certificate, rv = %d\n", ret); goto failed; } ret = lib_RSAVerifySHA256(&cert_key, hash, sizeof(hash), data_sig, data_sig_len); if (ret < 0) { SGX_DBG(DBG_E, "Failed to verify the report against the IAS certificates, rv = %d\n", ret); lib_RSAFreeKey(&cert_key); goto failed; } lib_RSAFreeKey(&cert_key); data_to_verify = cert_body; data_sig = cert_sig; data_len = cert_body_len; data_sig_len = cert_sig_len; } // Parse the IAS report in JSON format char* ias_status = NULL; char* ias_timestamp = NULL; sgx_quote_nonce_t* ias_nonce = NULL; sgx_quote_t* ias_quote = NULL; char* start = attestation.ias_report; if (start[0] == '{') start++; char* end = strchr(start, ','); while (end) { char* next_start = end + 1; // Retrieve the key and value separated by the colon (:) char* delim = strchr(start, ':'); if (!delim) break; char* key = start; char* val = delim + 1; size_t klen = delim - start; size_t vlen = end - val; // Remove quotation marks (") around the key and value if there are any if (key[0] == '"') { key++; klen--; } if (key[klen - 1] == '"') klen--; if (val[0] == '"') { val++; vlen--; } if (val[vlen - 1] == '"') vlen--; // Scan the fields in the IAS report. if (!memcmp(key, "isvEnclaveQuoteStatus", klen)) { // Parse "isvEnclaveQuoteStatus":"OK"|"GROUP_OUT_OF_DATE"|... ias_status = __alloca(vlen + 1); memcpy(ias_status, val, vlen); ias_status[vlen] = 0; } else if (!memcmp(key, "nonce", klen)) { // Parse "nonce":"{Hex representation of the initial nonce}" if (vlen != sizeof(sgx_quote_nonce_t) * 2) { SGX_DBG(DBG_E, "Malformed nonce in the IAS report\n"); goto failed; } ias_nonce = __alloca(sizeof(sgx_quote_nonce_t)); for (size_t i = 0; i < sizeof(sgx_quote_nonce_t); i++) { int8_t hi = hex2dec(val[i * 2]); int8_t lo = hex2dec(val[i * 2 + 1]); if (hi < 0 || lo < 0) { SGX_DBG(DBG_E, "Malformed nonce in the IAS report\n"); goto failed; } ((uint8_t*)ias_nonce)[i] = (uint8_t)hi * 16 + (uint8_t)lo; } } else if (!memcmp(key, "timestamp", klen)) { // Parse "timestamp":"{IAS timestamp (format: %Y-%m-%dT%H:%M:%S.%f)}" ias_timestamp = __alloca(vlen + 1); memcpy(ias_timestamp, val, vlen); ias_timestamp[vlen] = 0; } else if (!memcmp(key, "isvEnclaveQuoteBody", klen)) { // Parse "isvEnclaveQuoteBody":"{Quote body (in Base64 format)}" size_t ias_quote_len; ret = lib_Base64Decode(val, vlen, NULL, &ias_quote_len); if (ret < 0) { SGX_DBG(DBG_E, "Malformed quote in the IAS report\n"); goto failed; } ias_quote = __alloca(ias_quote_len); ret = lib_Base64Decode(val, vlen, (uint8_t*)ias_quote, &ias_quote_len); if (ret < 0) { SGX_DBG(DBG_E, "Malformed quote in the IAS report\n"); goto failed; } } start = next_start; end = strchr(start, ',') ?: strchr(start, '}'); } if (!ias_status || !ias_nonce || !ias_timestamp || !ias_quote) { SGX_DBG(DBG_E, "Missing important field(s) in the IAS report\n"); goto failed; } SGX_DBG(DBG_S, "Quote:\n"); SGX_DBG(DBG_S, " version: %04x\n", ias_quote->body.version); SGX_DBG(DBG_S, " sigtype: %04x\n", ias_quote->body.sigtype); SGX_DBG(DBG_S, " gid: %08x\n", ias_quote->body.gid); SGX_DBG(DBG_S, " isvsvn qe: %08x\n", ias_quote->body.isvsvn_qe); SGX_DBG(DBG_S, " isvsvn pce: %08x\n", ias_quote->body.isvsvn_pce); SGX_DBG(DBG_S, "IAS report: %s\n", attestation.ias_report); SGX_DBG(DBG_S, " status: %s\n", ias_status); SGX_DBG(DBG_S, " timestamp: %s\n", ias_timestamp); // Only accept status to be "OK" or "GROUP_OUT_OF_DATE" / "CONFIGURATION_NEEDED" (if // accept_out_of_date / accept_configuration_needed is true) if (strcmp_static(ias_status, "OK") && (!accept_group_out_of_date || strcmp_static(ias_status, "GROUP_OUT_OF_DATE")) && (!accept_configuration_needed || strcmp_static(ias_status, "CONFIGURATION_NEEDED"))) { SGX_DBG(DBG_E, "IAS returned invalid status: %s\n", ias_status); goto failed; } // Check if the nonce matches the IAS report if (memcmp(ias_nonce, nonce, sizeof(sgx_quote_nonce_t))) { SGX_DBG(DBG_E, "IAS returned the wrong nonce\n"); goto failed; } // Check if the quote matches the IAS report if (memcmp(&ias_quote->body, &attestation.quote->body, sizeof(sgx_quote_body_t)) || memcmp(&ias_quote->report_body, &report.body, sizeof(sgx_report_body_t))) { SGX_DBG(DBG_E, "IAS returned the wrong quote\n"); goto failed; } // Check if the quote has the right enclave report if (memcmp(&attestation.quote->report_body, &report.body, sizeof(sgx_report_body_t))) { SGX_DBG(DBG_E, "The returned quote contains the wrong enclave report\n"); goto failed; } // Succeeded!!! if (ret_ias_status) { size_t len = strlen(ias_status) + 1; *ret_ias_status = malloc(len); memcpy(*ret_ias_status, ias_status, len); } if (ret_ias_timestamp) { size_t len = strlen(ias_timestamp) + 1; *ret_ias_timestamp = malloc(len); memcpy(*ret_ias_timestamp, ias_timestamp, len); } if (ret_attestation) { memcpy(ret_attestation, &attestation, sizeof(sgx_attestation_t)); return 0; } ret = 0; free_attestation: if (attestation.quote) free(attestation.quote); if (attestation.ias_report) free(attestation.ias_report); if (attestation.ias_sig) free(attestation.ias_sig); if (attestation.ias_certs) free(attestation.ias_certs); return ret; failed: ret = -PAL_ERROR_DENIED; goto free_attestation; }