/* * Copyright (C) 2011-2018 Intel Corporation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * Neither the name of Intel Corporation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "utility.h" #include "session_mgr.h" #include "pse_op_t.h" #include "sgx_dh.h" // ISV enclave <-> pse-op sessions static pse_session_t g_session[SESSION_CONNECTION]; static uint32_t g_session_count = 0; // ephemeral session global variables static uint8_t g_nonce_r_pse[EPH_SESSION_NONCE_SIZE] = {0}; // nonce R(PSE) for ephemeral session establishment static uint8_t g_nonce_r_cse[EPH_SESSION_NONCE_SIZE] = {0}; // nonce R(CSE) for ephemeral session establishment static pairing_data_t g_pairing_data; // unsealed pairing data eph_session_t g_eph_session; // ephemeral session information /** * @brief Check the status of the ephemeral session * * @return true if the ephemeral session is active. Otherwise false. */ bool is_eph_session_active() { if(SESSION_ACTIVE != g_eph_session.state) { return false; } return true; } /** * @brief Get session from session ID * * @param sid Session ID * * @return Session */ pse_session_t* sid2session(uint32_t sid) { for(int index = 0; index < SESSION_CONNECTION; index++) { if(g_session[index].state != SESSION_CLOSE && g_session[index].sid == sid) { return &g_session[index]; } } return NULL; } /** * @brief Free the memory of a session * * @param session */ void free_session(pse_session_t* session) { if (session) { memset_s(session, sizeof(pse_session_t), 0, sizeof(pse_session_t)); } } /** * @brief Check active state of a session and its sequence number * * @param session * * @return true if the session is active and sequence number doesn't overflow */ bool is_isv_session_valid(pse_session_t* session) { //if session not exists or established, return error if (!session || session->state != SESSION_ACTIVE) { return false; } // check sequence number if(session->active.counter >= (UINT32_MAX-2)) { //close session which contains AEK memset_s(session, sizeof(pse_session_t), 0, sizeof(pse_session_t)); return false; } return true; } /** * @brief Get the sequence number of a session * * @param session * * @return Sequence number */ uint32_t get_session_seq_num(pse_session_t* session) { return session->active.counter; } /** * @brief Set the sequence number of a session * * @param session * @param seq_num Sequence number */ void set_session_seq_num(pse_session_t* session, uint32_t seq_num) { session->active.counter = seq_num; } /** * @brief Update the tick counter of a session * * @param session * @param new_tick_count */ void update_session_tick_count(pse_session_t* session, uint64_t new_tick_count) { session->isv_attributes.tick_count = new_tick_count; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// /** * @brief * Here describe how to find the idle session * 1. find the one which exceed session quote, and if two enclave has * same instances number, find the one has idle for the longest time. * two arrays: * instances: record each enclave's instances number * time_stamp: for each enclave's instance, record the instance that * idle for the longest time, it's a index to g_session. * the two array items are corresponding to each other * for instance: * instances: * | 1 | 32 | 9 | 8 | 1 | 32 |.... * ---------------------------.... * time stamp * | 0 | 2 | 5 | 10 | 19 | 6 |.... * ---------------------------.... * [0]: * enclave has 1 instance, and the longest idle one is g_session[0] * [1]: * enclave has 32 instances, among them the g_session[2] is the longest * idle one. * [2]: * enclave has 9 instances, among them the g_session[5] is the longest * idle one. * * @param tick * * @return Session */ pse_session_t* find_idle_session(uint64_t tick) { pse_session_t* session = NULL; uint8_t instances[SESSION_CONNECTION]; uint8_t time_stamp[SESSION_CONNECTION]; uint8_t end_index = 0; uint8_t max_instance = 0; memset(instances, 0, SESSION_CONNECTION); memset(time_stamp, 0, SESSION_CONNECTION); for(uint8_t i = 0; i < SESSION_CONNECTION; i++) { pse_session_t* s1 = &g_session[i]; // if one session has not established, no need to calculate the quote // as no enclave information has obtain. if (s1->state != SESSION_ACTIVE) { continue; } uint8_t j = 0; for(; j < end_index; j++) { pse_session_t* s2 = &g_session[time_stamp[j]]; if (!memcmp(&s1->isv_attributes.mr_enclave, &s2->isv_attributes.mr_enclave, sizeof(s1->isv_attributes.mr_enclave)) && !memcmp(&s1->isv_attributes.mr_signer, &s2->isv_attributes.mr_signer, sizeof(s1->isv_attributes.mr_signer)) && s1->isv_attributes.isv_prod_id == s2->isv_attributes.isv_prod_id) { if (s1->isv_attributes.tick_count < s2->isv_attributes.tick_count) { time_stamp[j] = i; } ++instances[j]; if (instances[j] > max_instance) { max_instance = instances[j]; } break; } } if (j == end_index) { time_stamp[end_index] = i; instances[end_index] = 1; if (instances[end_index] > max_instance) { max_instance = instances[end_index]; } end_index++; } } if (max_instance > MAX_INST_PER_ENCLAVE) { for (uint8_t i = 0; i < end_index; i++) { if (instances[i] == max_instance) { pse_session_t* s1 = &g_session[time_stamp[i]]; if (!session) { session = s1; } else if (session->isv_attributes.tick_count > s1->isv_attributes.tick_count) { session = s1; } } } } if(session) { return session; } //if no enclave instance exceed quote, //iterate all enclave to find the one no-active for long time uint64_t elapse = 0; //find the least active session for(uint8_t index = 0; index < SESSION_CONNECTION; index++) { uint64_t temp = tick - g_session[index].isv_attributes.tick_count; if(temp > tick) { // temp underflow return NULL; } if(temp > elapse) { elapse = temp; session = &g_session[index]; } } //if the least active session is not exceed predefined time, // no idle session find if(elapse < SESSION_IDLE_TIME) { session = NULL; } return session; } /** * @brief Select a session slot based on the defined rules. * * @param tick * * @return */ pse_session_t* open_session(uint64_t tick) { int index = 0; pse_session_t* session = NULL; //if the session id reached the limitation, reset all sessions if(g_session_count == SESSION_COUNTER_MAX) { for(index = 0; index < SESSION_CONNECTION; index++) { free_session(&g_session[index]); } g_session_count = 0; } for(index = 0; index < SESSION_CONNECTION; index++) { if(g_session[index].state == SESSION_CLOSE) { break; } } if(index == SESSION_CONNECTION) { //if no session slot available, find one idle session for reuse session = find_idle_session(tick); if (session) { free_session(session); } } else { session = &g_session[index]; } return session; } /** * @brief Initialize a DH session. * * @param tick Tick counter * @param id Session ID * @param dh_msg1 DH Message1 * * @return OP_SUCCESS */ pse_op_error_t pse_create_session(uint64_t tick, uint32_t &id, pse_dh_msg1_t &dh_msg1) { pse_op_error_t status = OP_SUCCESS; pse_session_t* session = NULL; sgx_status_t se_ret; sgx_dh_session_t sgx_dh_session; se_ret = sgx_dh_init_session(SGX_DH_SESSION_RESPONDER, &sgx_dh_session); if(SGX_SUCCESS != se_ret) { return OP_ERROR_INTERNAL; } session = open_session(tick); if(!session) { //if no available session found, return error to ISV enclave id = INVADE_SESSION_ID; return OP_ERROR_MAX_NUM_SESSION_REACHED; } else { session->state = SESSION_IN_PROGRESS; session->sid = g_session_count++; // addition overflow already checked in open_session id = session->sid; } do { //Generate Message1 se_ret = sgx_dh_responder_gen_msg1((sgx_dh_msg1_t*)&dh_msg1, &sgx_dh_session); if(SGX_SUCCESS != se_ret) { status = OP_ERROR_INTERNAL; break; } memcpy(&session->in_progress.dh_session, &sgx_dh_session, sizeof(sgx_dh_session_t)); // clear secret memset_s(&sgx_dh_session, sizeof(sgx_dh_session_t), 0, sizeof(sgx_dh_session_t)); //update tick count to record the last session activation tick count session->isv_attributes.tick_count = tick; session->isv_attributes_len = sizeof(isv_attributes_t); }while(0); if(status != OP_SUCCESS) { pse_close_session(id); id = INVADE_SESSION_ID; } return status; } /** * @brief Exchange M2 and M3 * * @param tick * @param sid * @param dh_msg2 * @param dh_msg3 * * @return */ pse_op_error_t pse_exchange_report(uint64_t tick, uint32_t sid, const sgx_dh_msg2_t &dh_msg2, pse_dh_msg3_t &dh_msg3) { pse_op_error_t status = OP_SUCCESS; sgx_dh_session_t sgx_dh_session; sgx_key_128bit_t aek; sgx_dh_session_enclave_identity_t initiator_identity; cse_sec_prop_t * pcse_sec = NULL; secu_info_t* psec_info = NULL; pse_session_t* session = sid2session(sid); if(!session || session->state != SESSION_IN_PROGRESS) { return OP_ERROR_INVALID_SESSION; } do { memcpy(&sgx_dh_session, &session->in_progress.dh_session, sizeof(sgx_dh_session_t)); dh_msg3.additional_prop_length = sizeof(cse_sec_prop_t); // set secu_info_t pcse_sec = &dh_msg3.cse_sec_prop; pcse_sec->gid_cse = g_pairing_data.plaintext.cse_sec_prop.ps_hw_gid; pcse_sec->prvrl_version = g_pairing_data.plaintext.cse_sec_prop.ps_hw_privkey_rlversion; pcse_sec->sigrl_version = g_pairing_data.plaintext.cse_sec_prop.ps_hw_sig_rlversion; pcse_sec->sec_info_type = 0; memcpy(pcse_sec->ca_id_cse, g_pairing_data.plaintext.cse_sec_prop.ps_hw_CA_id, sizeof(pcse_sec->ca_id_cse)); psec_info = (secu_info_t*)(pcse_sec->sec_info); psec_info->jom_task_id = 8; //psec_info->psda_svn = *(uint32_t*)&g_pairing_data.plaintext.cse_sec_prop.ps_hw_sec_info[12]; psec_info->psda_svn = g_pairing_data.plaintext.cse_sec_prop.ps_hw_sec_info.psdaSvn; psec_info->psda_id = 1; psec_info->reserved = 0; memset_s(psec_info->reserved2, sizeof(psec_info->reserved2), 0, sizeof(psec_info->reserved2)); sgx_status_t se_ret = sgx_dh_responder_proc_msg2(&dh_msg2, (sgx_dh_msg3_t*)&dh_msg3, &sgx_dh_session, &aek, &initiator_identity); // clear secret memset_s(&sgx_dh_session, sizeof(sgx_dh_session_t), 0, sizeof(sgx_dh_session_t)); if (SGX_ERROR_KDF_MISMATCH == se_ret) { status = OP_ERROR_KDF_MISMATCH; break; } else if(SGX_SUCCESS != se_ret) { status = OP_ERROR_INTERNAL; break; } // clear secret before changing session status memset_s(&session->in_progress.dh_session, sizeof(sgx_dh_session_t), 0, sizeof(sgx_dh_session_t)); session->state = SESSION_ACTIVE; memcpy(&session->active.AEK, &aek, sizeof(sgx_key_128bit_t)); session->active.counter = 0; session->isv_attributes_len = sizeof(isv_attributes_t); memcpy(&session->isv_attributes.attribute, &initiator_identity.attributes, sizeof(sgx_attributes_t)); memcpy(&session->isv_attributes.isv_prod_id, &initiator_identity.isv_prod_id, sizeof(sgx_prod_id_t)); memcpy(&session->isv_attributes.isv_svn, &initiator_identity.isv_svn, sizeof(sgx_isv_svn_t)); //record mr_signer memcpy(&session->isv_attributes.mr_signer, &initiator_identity.mr_signer, sizeof(sgx_measurement_t)); //record mr_enclave memcpy(&session->isv_attributes.mr_enclave, &initiator_identity.mr_enclave, sizeof(sgx_measurement_t)); //update tick count session->isv_attributes.tick_count = tick; }while(0); if(status != OP_SUCCESS) { pse_close_session(sid); } return status; } pse_op_error_t pse_close_session(uint32_t sid) { if (sid != (uint32_t)INVADE_SESSION_ID) { pse_session_t* session = sid2session(sid); if(session) { free_session(session); } return OP_SUCCESS; } return OP_ERROR_INVALID_SESSION; } /** * @brief Exchange message2 and message3 between CSE and PSE * * @param sealed_blob Sealed long term pairing blob. * @param pse_cse_msg2 Message2 * @param pse_cse_msg3 Message3 * * @return OP_SUCCESS for successful message exchange. All other values indicate FAIL. */ pse_op_error_t ephemeral_session_m2m3( pairing_blob_t* sealed_blob, const pse_cse_msg2_t &pse_cse_msg2, pse_cse_msg3_t &pse_cse_msg3) { sgx_status_t se_ret = SGX_SUCCESS; uint32_t msg_len = 0; pse_op_error_t op_ret = OP_SUCCESS; Nonce128_t zero_nonce; sgx_report_t report; sgx_isv_svn_t sealed_isv_svn; sgx_cpu_svn_t* p_sealed_cpu_svn = NULL; // decrypt sealed blob if (UnsealPairingBlob(sealed_blob, &g_pairing_data) != AE_SUCCESS) { // unseal error op_ret = OP_ERROR_UNSEAL_PAIRING_BLOB; goto error; } // If the pairing blob was sealed by a different ISV SVN, we need to // tell AESM to redo long term pairing memset_s(&report, sizeof(report), 0, sizeof(report)); se_ret = sgx_create_report(NULL, NULL, &report); if(SGX_SUCCESS != se_ret) { op_ret = OP_ERROR_INTERNAL; goto error; } sealed_isv_svn = ((sgx_sealed_data_t*)sealed_blob)->key_request.isv_svn; p_sealed_cpu_svn = &((sgx_sealed_data_t*)sealed_blob)->key_request.cpu_svn; if (sealed_isv_svn != report.body.isv_svn || memcmp(p_sealed_cpu_svn, &report.body.cpu_svn, sizeof(sgx_cpu_svn_t)) != 0) { op_ret = OP_ERROR_LTPB_SEALING_OUT_OF_DATE; goto error; } // Reset ephemeral session memset(&g_eph_session, 0 , sizeof(eph_session_t)); // verify pairingNonce, must be non-zero. memset(&zero_nonce, 0, sizeof(zero_nonce)); if(0 == memcmp(&g_pairing_data.secret_data.pairingNonce, &zero_nonce, sizeof(Nonce128_t))) { op_ret = OP_ERROR_INVALID_PAIRING_BLOB; goto error; } // clear secret data memset_s(&g_pairing_data.secret_data.VerifierPrivateKey, sizeof(EcDsaPrivKey), 0, sizeof(EcDsaPrivKey)); memset_s(&g_pairing_data.secret_data.pairingID, sizeof(SIGMA_SECRET_KEY), 0, sizeof(SIGMA_SECRET_KEY)); // verify IDcse if (memcmp(g_pairing_data.secret_data.Id_cse, pse_cse_msg2.id_cse, sizeof(pse_cse_msg2.id_cse)) != 0) { op_ret = OP_ERROR_EPH_SESSION_ESTABLISHMENT_INTEGRITY_ERROR; goto error; } // save nonce R_cse memcpy(g_nonce_r_cse, pse_cse_msg2.nonce_r_cse, sizeof(g_nonce_r_cse)); // generate Message3 se_ret = sgx_read_rand((uint8_t*)&g_nonce_r_pse, sizeof(g_nonce_r_pse)); if (se_ret != SGX_SUCCESS) { op_ret = OP_ERROR_INTERNAL; goto error; } memcpy(pse_cse_msg3.id_cse, g_pairing_data.secret_data.Id_cse, sizeof(pse_cse_msg3.id_cse)); memcpy(pse_cse_msg3.id_pse, g_pairing_data.secret_data.Id_pse, sizeof(pse_cse_msg3.id_pse)); memcpy(pse_cse_msg3.nonce_r_pse, g_nonce_r_pse, sizeof(pse_cse_msg3.nonce_r_pse)); memcpy(pse_cse_msg3.nonce_r_cse, pse_cse_msg2.nonce_r_cse, sizeof(pse_cse_msg3.nonce_r_cse)); // compute HMAC over IDcse||IDpse||Rpse||Rcse msg_len = static_cast(sizeof(pse_cse_msg3.id_cse) + sizeof(pse_cse_msg3.id_pse) + sizeof(pse_cse_msg3.nonce_r_pse) + sizeof(pse_cse_msg3.nonce_r_cse)); if (ippsHMAC_Message((uint8_t*)&pse_cse_msg3, msg_len, g_pairing_data.secret_data.mk, sizeof(g_pairing_data.secret_data.mk), pse_cse_msg3.mac, SGX_SHA256_HASH_SIZE, IPP_ALG_HASH_SHA256) != ippStsNoErr) { op_ret = OP_ERROR_INTERNAL; goto error; } g_eph_session.state = SESSION_IN_PROGRESS; return OP_SUCCESS; error: memset_s(&g_pairing_data.secret_data, sizeof(se_secret_pairing_data_t), 0, sizeof(se_secret_pairing_data_t)); return op_ret; } /** * @brief Verify message4 which is generated by CSE. If verification * is passed, ephemeral session will be established. * * @param pse_cse_msg4 Message4 * * @return OP_SUCCESS for successful session establishment. All other values indicate FAIL. */ pse_op_error_t ephemeral_session_m4( const pse_cse_msg4_t &pse_cse_msg4) { uint32_t msg_len; uint8_t* msg_buf = NULL; uint8_t mac_buf[SGX_SHA256_HASH_SIZE]; pse_op_error_t op_ret = OP_SUCCESS; // check session state if (g_eph_session.state != SESSION_IN_PROGRESS) { op_ret = OP_ERROR_INVALID_EPH_SESSION; goto error; } // verify message4 if (memcmp(g_nonce_r_pse, pse_cse_msg4.nonce_r_pse, sizeof(pse_cse_msg4.nonce_r_pse)) != 0 || memcmp(g_pairing_data.secret_data.Id_cse, pse_cse_msg4.id_cse, sizeof(pse_cse_msg4.id_cse)) != 0) { op_ret = OP_ERROR_EPH_SESSION_ESTABLISHMENT_INTEGRITY_ERROR; goto error; } // verify MAC value if (!verify_hmac_sha256(g_pairing_data.secret_data.mk, sizeof(g_pairing_data.secret_data.mk), (const uint8_t*)&pse_cse_msg4, CSE_ID_SIZE + EPH_SESSION_NONCE_SIZE, pse_cse_msg4.mac)) { op_ret = OP_ERROR_EPH_SESSION_ESTABLISHMENT_INTEGRITY_ERROR; goto error; } // Deriving Transient Session Key msg_len = EPH_SESSION_NONCE_SIZE * 2; msg_buf = (uint8_t*)malloc(msg_len); if (msg_buf == NULL) { op_ret = OP_ERROR_MALLOC; goto error; } // calculate session key memcpy(msg_buf, g_nonce_r_pse, EPH_SESSION_NONCE_SIZE); memcpy(msg_buf + EPH_SESSION_NONCE_SIZE, g_nonce_r_cse, EPH_SESSION_NONCE_SIZE); // TSK := HMAC-SHA256sk(Rpse || Rcse) if (ippsHMAC_Message(msg_buf, msg_len, g_pairing_data.secret_data.sk, sizeof(g_pairing_data.secret_data.sk), mac_buf, SGX_SHA256_HASH_SIZE, IPP_ALG_HASH_SHA256) != ippStsNoErr) { SAFE_FREE(msg_buf); op_ret = OP_ERROR_INTERNAL; goto error; } memcpy(g_eph_session.TSK, mac_buf, EPH_SESSION_TSK_SIZE); memcpy(g_eph_session.TMK, mac_buf + EPH_SESSION_TSK_SIZE, EPH_SESSION_TMK_SIZE); // free buffer SAFE_FREE(msg_buf); // mark ephemeral session as established g_eph_session.state = SESSION_ACTIVE; // reset global variables that will be never used anymore memset_s(g_nonce_r_cse, sizeof(g_nonce_r_cse), 0, sizeof(g_nonce_r_cse)); memset_s(g_nonce_r_pse, sizeof(g_nonce_r_pse), 0, sizeof(g_nonce_r_pse)); // keep the CSE security property information and the pairing nonce in the enclave memory memset_s(&g_pairing_data.secret_data.sk, sizeof(SIGMA_SECRET_KEY), 0, sizeof(SIGMA_SECRET_KEY)); memset_s(&g_pairing_data.secret_data.mk, sizeof(SIGMA_MAC_KEY), 0, sizeof(SIGMA_MAC_KEY)); memset_s(&g_pairing_data.secret_data.Id_pse, sizeof(SHA256_HASH), 0, sizeof(SHA256_HASH)); memset_s(&g_pairing_data.secret_data.Id_cse, sizeof(SHA256_HASH), 0, sizeof(SHA256_HASH)); return OP_SUCCESS; error: memset_s(&g_pairing_data.secret_data, sizeof(se_secret_pairing_data_t), 0, sizeof(se_secret_pairing_data_t)); memset(&g_eph_session, 0, sizeof(g_eph_session)); return op_ret; } bool copy_global_pairing_nonce(uint8_t* target_buffer) { // g_pairing_data availability is ensured by checking ephemeral session state at the ECALL layer // but we check again here if (!is_eph_session_active()) { return false; } memcpy(target_buffer, g_pairing_data.secret_data.pairingNonce, sizeof(Nonce128_t)); return true; } void copy_pse_instance_id(uint8_t* pse_instance_id) { memcpy(pse_instance_id, g_pairing_data.plaintext.pse_instance_id, SW_INSTANCE_ID_SIZE); }