From d65bf516729ea424ba3ad7ca9702b069d79624a9 Mon Sep 17 00:00:00 2001 From: wanghouqi Date: Thu, 12 Jan 2023 14:38:36 +0800 Subject: [PATCH] dkey rotation Signed-off-by: wanghouqi --- Makefile | 2 +- core/App/ehsm_provider.cpp | 83 +- core/Enclave/enclave_hsm.cpp | 16 + core/Enclave/key_factory.cpp | 4 +- core/Test/function_test.cpp | 22 +- dkeycache/App/la_server.cpp | 231 ++- dkeycache/App/main.cpp | 50 + dkeycache/Enclave/enclave.edl | 3 + dkeycache/Enclave/enclave_la.cpp | 2 +- dkeycache/Enclave/enclave_ra.cpp | 307 +++- dkeycache/Makefile | 3 +- dkeyserver/App/base64.cpp | 124 ++ dkeyserver/App/base64.h | 42 + dkeyserver/App/couchdb_curl.cpp | 142 ++ dkeyserver/App/couchdb_curl.h | 57 + dkeyserver/App/main.cpp | 526 +++++- dkeyserver/Enclave/domainkey_factory.cpp | 267 +++ dkeyserver/Enclave/domainkey_factory.h | 70 + dkeyserver/Enclave/enclave.config.xml | 2 +- dkeyserver/Enclave/enclave.cpp | 1163 ++++++++++--- dkeyserver/Enclave/enclave.edl | 32 +- dkeyserver/Makefile | 12 +- dkeyserver/dkeyrotation/App/auto_version.h | 6 + dkeyserver/dkeyrotation/App/enclave_u.c | 443 +++++ dkeyserver/dkeyrotation/App/enclave_u.h | 136 ++ dkeyserver/dkeyrotation/App/enclave_u.o | Bin 0 -> 159608 bytes dkeyserver/dkeyrotation/App/main.cpp | 169 ++ dkeyserver/dkeyrotation/App/main.o | Bin 0 -> 333712 bytes .../dkeyrotation/Enclave/enclave.config.xml | 11 + dkeyserver/dkeyrotation/Enclave/enclave.edl | 67 + dkeyserver/dkeyrotation/Enclave/enclave.lds | 9 + .../Enclave/enclave_private_test.pem | 39 + .../dkeyrotation/Enclave/enclave_ra.cpp | 332 ++++ dkeyserver/dkeyrotation/Enclave/enclave_ra.o | Bin 0 -> 490816 bytes dkeyserver/dkeyrotation/Enclave/enclave_t.c | 1539 +++++++++++++++++ dkeyserver/dkeyrotation/Enclave/enclave_t.h | 66 + dkeyserver/dkeyrotation/Enclave/enclave_t.o | Bin 0 -> 27568 bytes dkeyserver/dkeyrotation/Makefile | 208 +++ include/datatypes.h | 58 +- include/json_utils.h | 2 +- utils/log4cplus/elog_utils.h | 2 +- utils/log4cplus/ulog_utils.h | 4 +- utils/sgx_socket/tls/common.h | 24 +- 43 files changed, 5840 insertions(+), 435 deletions(-) create mode 100644 dkeyserver/App/base64.cpp create mode 100644 dkeyserver/App/base64.h create mode 100644 dkeyserver/App/couchdb_curl.cpp create mode 100644 dkeyserver/App/couchdb_curl.h create mode 100644 dkeyserver/Enclave/domainkey_factory.cpp create mode 100644 dkeyserver/Enclave/domainkey_factory.h create mode 100644 dkeyserver/dkeyrotation/App/auto_version.h create mode 100644 dkeyserver/dkeyrotation/App/enclave_u.c create mode 100644 dkeyserver/dkeyrotation/App/enclave_u.h create mode 100644 dkeyserver/dkeyrotation/App/enclave_u.o create mode 100644 dkeyserver/dkeyrotation/App/main.cpp create mode 100644 dkeyserver/dkeyrotation/App/main.o create mode 100644 dkeyserver/dkeyrotation/Enclave/enclave.config.xml create mode 100644 dkeyserver/dkeyrotation/Enclave/enclave.edl create mode 100644 dkeyserver/dkeyrotation/Enclave/enclave.lds create mode 100644 dkeyserver/dkeyrotation/Enclave/enclave_private_test.pem create mode 100644 dkeyserver/dkeyrotation/Enclave/enclave_ra.cpp create mode 100644 dkeyserver/dkeyrotation/Enclave/enclave_ra.o create mode 100644 dkeyserver/dkeyrotation/Enclave/enclave_t.c create mode 100644 dkeyserver/dkeyrotation/Enclave/enclave_t.h create mode 100644 dkeyserver/dkeyrotation/Enclave/enclave_t.o create mode 100644 dkeyserver/dkeyrotation/Makefile diff --git a/Makefile b/Makefile index 8f0da189..faaf740b 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ include buildenv.mk -SUB_DIR := utils/tkey_exchange utils/ukey_exchange core dkeycache dkeyserver enroll_app +SUB_DIR := utils/tkey_exchange utils/ukey_exchange core dkeycache dkeyserver dkeyserver/dkeyrotation enroll_app SSL_DIR := third_party/intel-sgx-ssl export DESTDIR = ${OPENSSL_PATH} diff --git a/core/App/ehsm_provider.cpp b/core/App/ehsm_provider.cpp index 7dd2f36d..c0551e59 100644 --- a/core/App/ehsm_provider.cpp +++ b/core/App/ehsm_provider.cpp @@ -42,6 +42,8 @@ #include #include #include +#include +#include #include "enclave_hsm_u.h" #include "ehsm_provider.h" @@ -93,6 +95,48 @@ sgx_ra_context_t g_context = INT_MAX; sgx_enclave_id_t g_enclave_id; +#define UNIX_DOMAIN (std::string(RUNTIME_FOLDER) + "dkeyprovision.sock").c_str() + +bool g_ready_flag = true; + +int server_sock_fd; + +void recv_msg() +{ + int byte_num; + _response_header_t *res_msg = (_response_header_t*)malloc(sizeof(_response_header_t)); + + do + { + uint32_t sgxStatus; + sgx_status_t ret; + + byte_num = recv(server_sock_fd, reinterpret_cast(res_msg), sizeof(_response_header_t), 0); + if (byte_num > 0) + { + if (res_msg->type == MSG_ROTATE_END) + { + ret = enclave_la_message_exchange(g_enclave_id, &sgxStatus); + if (ret != SGX_SUCCESS || sgxStatus != SGX_SUCCESS) + { + log_e("test_message_exchange Ecall failed: ECALL return 0x%x, error code is 0x%x.\n", ret, sgxStatus); + return; + } + log_i("update dk\n"); + + g_ready_flag = true; + log_i("ready flag change to %s\n", g_ready_flag == true ? "true" : "false"); + } + else if (res_msg->type == MSG_ROTATE_START) + { + g_ready_flag = false; + log_i("ready flag change to %s\n", g_ready_flag == true ? "true" : "false"); + } + } + + } while (1); +} + static ehsm_status_t SetupSecureChannel(sgx_enclave_id_t eid) { uint32_t sgxStatus; @@ -117,13 +161,30 @@ static ehsm_status_t SetupSecureChannel(sgx_enclave_id_t eid) log_i("Succeed to exchange secure message...\n"); // close ECDH session - ret = enclave_la_close_session(eid, &sgxStatus); - if (ret != SGX_SUCCESS || sgxStatus != SGX_SUCCESS) + // ret = enclave_la_close_session(eid, &sgxStatus); + // if (ret != SGX_SUCCESS || sgxStatus != SGX_SUCCESS) + // { + // log_e("test_close_session Ecall failed: ECALL return 0x%x, error code is 0x%x.\n", ret, sgxStatus); + // return EH_LA_CLOSE_ERROR; + // } + // log_i("Succeed to close Session...\n"); + + server_sock_fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (server_sock_fd == -1) { - log_e("test_close_session Ecall failed: ECALL return 0x%x, error code is 0x%x.\n", ret, sgxStatus); - return EH_LA_CLOSE_ERROR; + log_e("socket error"); + return EH_FUNCTION_FAILED; } - log_i("Succeed to close Session...\n"); + + struct sockaddr_un server_addr; + server_addr.sun_family = AF_UNIX; + strcpy(server_addr.sun_path, UNIX_DOMAIN); + + if (connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) >= 0) + log_w("socket connect ok"); + + std::thread thread(recv_msg); + thread.detach(); return EH_OK; } @@ -209,7 +270,17 @@ uint32_t EHSM_FFI_CALL(const char *reqJson, char *respJson) RetJsonObj retJsonObj; uint32_t action = -1; JsonObj payloadJson; - if(respJson == NULL){ + + if (g_ready_flag == false) + { + retJsonObj.setCode(retJsonObj.CODE_FAILED); + retJsonObj.setMessage("rotating."); + retJsonObj.toChar(respJson); + return EH_GENERAL_ERROR; + } + + if (respJson == NULL) + { retJsonObj.setCode(retJsonObj.CODE_FAILED); retJsonObj.setMessage("Argument bad."); retJsonObj.toChar(respJson); diff --git a/core/Enclave/enclave_hsm.cpp b/core/Enclave/enclave_hsm.cpp index e0297f17..1fc4bd51 100644 --- a/core/Enclave/enclave_hsm.cpp +++ b/core/Enclave/enclave_hsm.cpp @@ -31,10 +31,13 @@ #include "enclave_hsm_t.h" #include "openssl/rand.h" +#include "openssl/sha.h" #include "datatypes.h" #include "key_factory.h" #include "key_operation.h" +extern sgx_aes_gcm_256bit_key_t g_domain_key; + using namespace std; // Used to store the secret passed by the SP in the sample code. @@ -109,6 +112,17 @@ static size_t get_signature_length(ehsm_keyspec_t keyspec) } } +void compute_dk_hash(ehsm_keyblob_t *cmk) +{ + SHA256_CTX ctx; + SHA256_Init(&ctx); + unsigned int len = SGX_DOMAIN_KEY_SIZE; + unsigned char result[SHA256_DIGEST_LENGTH] = {0}; + SHA256_Update(&ctx, g_domain_key, len); + SHA256_Final(result, &ctx); + memcpy(cmk->metadata.dk_hashcode, result, SHA256_DIGEST_LENGTH); +} + sgx_status_t enclave_create_key(ehsm_keyblob_t *cmk, size_t cmk_size) { sgx_status_t ret = SGX_ERROR_UNEXPECTED; @@ -120,6 +134,8 @@ sgx_status_t enclave_create_key(ehsm_keyblob_t *cmk, size_t cmk_size) return SGX_ERROR_INVALID_PARAMETER; } + compute_dk_hash(cmk); + switch (cmk->metadata.keyspec) { case EH_AES_GCM_128: diff --git a/core/Enclave/key_factory.cpp b/core/Enclave/key_factory.cpp index cfcd2092..1d577d23 100644 --- a/core/Enclave/key_factory.cpp +++ b/core/Enclave/key_factory.cpp @@ -115,7 +115,7 @@ sgx_status_t ehsm_create_keyblob(uint8_t *plaintext, if (SGX_SUCCESS != ret) { log_e("gcm encrypting failed.\n"); - } + } else { keyblob_data->ciphertext_size = plaintext_size; @@ -331,7 +331,7 @@ sgx_status_t ehsm_create_rsa_key(ehsm_keyblob_t *cmk) if (bio) BIO_free(bio); if (e) - BN_free(e); + BN_free(e); SAFE_MEMSET(pem_keypair, key_size, 0, key_size); SAFE_FREE(pem_keypair); diff --git a/core/Test/function_test.cpp b/core/Test/function_test.cpp index 140c9f81..1384415e 100644 --- a/core/Test/function_test.cpp +++ b/core/Test/function_test.cpp @@ -1376,27 +1376,27 @@ void function_test() { test_symmertric_encrypt_decrypt(); - test_symmertric_encrypt_decrypt_without_aad(); + // test_symmertric_encrypt_decrypt_without_aad(); - test_RSA_encrypt_decrypt(); + // test_RSA_encrypt_decrypt(); - test_RSA_sign_verify(); + // test_RSA_sign_verify(); - test_sm2_sign_verify(); + // test_sm2_sign_verify(); - test_ec_sign_verify(); + // test_ec_sign_verify(); - test_SM2_encrypt_decrypt(); + // test_SM2_encrypt_decrypt(); - test_generate_AES_datakey(); + // test_generate_AES_datakey(); - test_generate_SM4_datakey(); + // test_generate_SM4_datakey(); - test_export_datakey(); + // test_export_datakey(); - test_GenerateQuote_and_VerifyQuote(); + // test_GenerateQuote_and_VerifyQuote(); - test_Enroll(); + // test_Enroll(); log_i("All of tests done. %d/%d success\n", success_number, case_number); } \ No newline at end of file diff --git a/dkeycache/App/la_server.cpp b/dkeycache/App/la_server.cpp index 51794061..4d301210 100644 --- a/dkeycache/App/la_server.cpp +++ b/dkeycache/App/la_server.cpp @@ -28,7 +28,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ - + #include #include #include @@ -38,179 +38,257 @@ #include #include #include - + #include "la_server.h" #include "ulog_utils.h" - -#define BACKLOG 5 -#define CONCURRENT_MAX 32 +#include "enclave_u.h" +#include "datatypes.h" + +#define BACKLOG 5 +#define CONCURRENT_MAX 32 #define SERVER_PORT 8888 #define BUFFER_SIZE 1024 - - +#define RECONNECT_TIMES 3 +#define SLEEP_INTV 3 + +extern bool g_is_ready; +extern FdPool g_client_resrved_fds[CONCURRENT_MAX]; + /* Function Description: * This is server initialization routine, it creates TCP sockets and listen on a port. * In Linux, it would listen on domain socket named '/var/run/ehsm/dkeyprovision.sock' - * In Windows, it would listen on port 8888, which is for demonstration purpose + * In Windows, it would listen on port 8888, which is for demonstration purpose * */ + +static void *HeartbeatToClientHandler(void *args) +{ + int bytes_written = 0; + _response_header_t *heart_beat = nullptr; + heart_beat = (_response_header_t *)malloc(sizeof(_response_header_t)); + if (heart_beat == nullptr) + { + log_d("getDomainkey malloc failed\n"); + goto out; + } + heart_beat->type = MSG_HEARTBEAT; + while (true) + { + ocall_sleep(SLEEP_INTV); + for (int i = 0; i < CONCURRENT_MAX; i++) + { + if (g_client_resrved_fds[i].fd != 0) + { + if (g_client_resrved_fds[i].errorCnt < RECONNECT_TIMES) + { + bytes_written = send(g_client_resrved_fds[i].fd, reinterpret_cast(heart_beat), sizeof(_response_header_t), MSG_NOSIGNAL); + if (bytes_written<= 0) + { + g_client_resrved_fds[i].errorCnt++; + } + else + { + g_client_resrved_fds[i].errorCnt = 0; + } + } + else + { + close(g_client_resrved_fds[i].fd); + g_client_resrved_fds[i].fd = 0; + g_client_resrved_fds[i].errorCnt = 0; + } + } + } + } +out: + SAFE_FREE(heart_beat); + pthread_exit((void *)-1); +} + int LaServer::init() { - log_i("Initializing ProtocolHandler [\"socket: %s\"]", UNIX_DOMAIN); + log_i("Initializing ProtocolHandler [\"socket: %s\"]", UNIX_DOMAIN); struct sockaddr_un srv_addr; - + m_server_sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); if (m_server_sock_fd == -1) { log_d("socket initiazation error\n"); return -1; } - + srv_addr.sun_family = AF_UNIX; - strncpy(srv_addr.sun_path, UNIX_DOMAIN, sizeof(srv_addr.sun_path)-1); + strncpy(srv_addr.sun_path, UNIX_DOMAIN, sizeof(srv_addr.sun_path) - 1); unlink(UNIX_DOMAIN); - - int bind_result = bind(m_server_sock_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)); + + int bind_result = bind(m_server_sock_fd, (struct sockaddr *)&srv_addr, sizeof(srv_addr)); if (bind_result == -1) { log_d("bind error\n"); close(m_server_sock_fd); return -1; } - + if (listen(m_server_sock_fd, BACKLOG) == -1) { log_d("listen error\n"); close(m_server_sock_fd); return -1; } - + m_shutdown = 0; - - log_i("Starting ProtocolHandler [\"socket: %s\"]", UNIX_DOMAIN); + + log_i("Starting ProtocolHandler [\"socket: %s\"]", UNIX_DOMAIN); + return 0; } - + /* Function Description: * This function is server's major routine, it uses select() to accept new connection and receive messages from clients. * When it receives clients' request messages, it would put the message to task queue and wake up worker thread to process the requests. * */ void LaServer::doWork() { - int client_fds[CONCURRENT_MAX] = {0}; fd_set server_fd_set; int max_fd = -1; - struct timeval tv; + struct timeval tv; char input_msg[BUFFER_SIZE]; char recv_msg[BUFFER_SIZE]; - + pthread_t HeartbeatToClient_thread; + if (pthread_create(&HeartbeatToClient_thread, NULL, HeartbeatToClientHandler, NULL) < 0) + { + log_d("could not create thread\n"); + // error handler + } while (!m_shutdown) { - // set 20s timeout for select() + // set 20s timeout for select() tv.tv_sec = 20; tv.tv_usec = 0; FD_ZERO(&server_fd_set); - + FD_SET(STDIN_FILENO, &server_fd_set); - if (max_fd 0) { + if (g_is_ready == false) + { + close(client_sock_fd); + client_sock_fd = 0; + } + + if (client_sock_fd > 0) + { // add new connection to connection pool if it's not full int index = -1; - for(int i = 0; i < CONCURRENT_MAX; i++) { - if(client_fds[i] == 0) { + for (int i = 0; i < CONCURRENT_MAX; i++) + { + if (g_client_resrved_fds[i].fd == 0) + { index = i; - client_fds[i] = client_sock_fd; + g_client_resrved_fds[i].fd = client_sock_fd; break; } } - - if(index < 0) { + + if (index < 0) + { log_i("server reach maximum connection!\n"); bzero(input_msg, BUFFER_SIZE); strcpy(input_msg, "server reach maximum connection\n"); send(client_sock_fd, input_msg, BUFFER_SIZE, 0); } - }else if (client_sock_fd < 0) { - log_d("server: accept() return failure, %s, would exit.\n", strerror(errno)); + } + else if (client_sock_fd < 0) + { + log_d("server: accept() return failure, %s, would exit.\n", strerror(errno)); close(m_server_sock_fd); break; } } - - for(int i =0; i < CONCURRENT_MAX; i++) { - if ((client_fds[i] !=0) - && (FD_ISSET(client_fds[i], &server_fd_set))) + + for (int i = 0; i < CONCURRENT_MAX; i++) + { + if ((g_client_resrved_fds[i].fd != 0) && (FD_ISSET(g_client_resrved_fds[i].fd, &server_fd_set))) { // there is request messages from client connectsions - FIFO_MSG * msg; - + FIFO_MSG *msg; + bzero(recv_msg, BUFFER_SIZE); - long byte_num = recv(client_fds[i], recv_msg, BUFFER_SIZE, 0); - if (byte_num > 0) { - if(byte_num > BUFFER_SIZE) - byte_num = BUFFER_SIZE; - + long byte_num = recv(g_client_resrved_fds[i].fd, recv_msg, BUFFER_SIZE, 0); + if (byte_num > 0) + { + if (byte_num > BUFFER_SIZE) + byte_num = BUFFER_SIZE; + recv_msg[byte_num] = '\0'; - + msg = (FIFO_MSG *)malloc(byte_num); - if (!msg) { + if (!msg) + { log_e("memory allocation failure\n"); continue; } memset(msg, 0, byte_num); - + memcpy(msg, recv_msg, byte_num); - - msg->header.sockfd = client_fds[i]; - + + msg->header.sockfd = g_client_resrved_fds[i].fd; + // put request message to event queue m_cptask->puttask(msg); } - else if(byte_num < 0) { - log_d("failed to receive message.\n"); - } else { + else if (byte_num < 0) + { + log_d("failed to receive message.\n"); + } + else + { // client connect is closed - FD_CLR(client_fds[i], &server_fd_set); - close(client_fds[i]); - client_fds[i] = 0; + FD_CLR(g_client_resrved_fds[i].fd, &server_fd_set); + close(g_client_resrved_fds[i].fd); + g_client_resrved_fds[i].fd = 0; } } } } } - + /* Function Description: * This function is to shutdown server. It's called when process exits. * */ @@ -219,7 +297,6 @@ void LaServer::shutDown() log_i("Server would shutdown...\n"); m_shutdown = 1; m_cptask->shutdown(); - + close(m_server_sock_fd); -} - +} \ No newline at end of file diff --git a/dkeycache/App/main.cpp b/dkeycache/App/main.cpp index 397ab496..ee9f01d3 100644 --- a/dkeycache/App/main.cpp +++ b/dkeycache/App/main.cpp @@ -28,8 +28,14 @@ #define ENCLAVE_PATH "libenclave-ehsm-dkeycache.signed.so" #include +#define CONCURRENT_MAX 32 + +FdPool g_client_resrved_fds[CONCURRENT_MAX] = {{0, 0}}; + sgx_enclave_id_t g_enclave_id; +bool g_is_ready = false; // dkeycache get latest domainkey the status is ready or (ROTATE_START connect failed) status is not ready + void ocall_print_string(uint32_t log_level, const char *str, const char *filename, uint32_t line) { switch (log_level) @@ -46,11 +52,54 @@ void ocall_print_string(uint32_t log_level, const char *str, const char *filenam } } +void ocall_update_rotate_flag(const bool *rotate_flag) +{ + _response_header_t *rotate_msg = nullptr; + rotate_msg = (_response_header_t *)malloc(sizeof(_response_header_t)); + if (rotate_msg == nullptr) + { + log_d("getDomainkey malloc failed\n"); + } + if (*rotate_flag) + { + rotate_msg->type = MSG_ROTATE_START; + g_is_ready = false; + log_i("dkeycache is ready is set to false\n"); + } + else + { + rotate_msg->type = MSG_ROTATE_END; + g_is_ready = true; + log_i("dkeycache is ready is set to true\n"); + } + + for (int i = 0; i < CONCURRENT_MAX; i++) + { + if (g_client_resrved_fds[i].fd != 0) + { + // error is handled by heartbeat; + send(g_client_resrved_fds[i].fd, reinterpret_cast(rotate_msg), sizeof(_response_header_t), MSG_NOSIGNAL); + } + } + SAFE_FREE(rotate_msg) +} + +void ocall_update_is_ready(const bool *is_ready) +{ + g_is_ready = *is_ready; + log_i("is ready set to %s\n", g_is_ready == true ? "true" : "false"); +} + int ocall_close(int fd) { return close(fd); } +void ocall_sleep(int second) +{ + sleep(second); +} + void ocall_get_current_time(uint64_t *p_current_time) { time_t rawtime; @@ -224,6 +273,7 @@ int main(int argc, char *argv[]) // register signal handler so to respond to user interception signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); + signal(SIGPIPE, SIG_IGN); g_la_task->start(); diff --git a/dkeycache/Enclave/enclave.edl b/dkeycache/Enclave/enclave.edl index 87440231..176e81b7 100644 --- a/dkeycache/Enclave/enclave.edl +++ b/dkeycache/Enclave/enclave.edl @@ -55,6 +55,9 @@ enclave { void ocall_get_current_time([out] uint64_t *p_current_time); int ocall_socket (int domain, int type, int protocol) propagate_errno; int ocall_connect (int fd, [in, size=len] const struct sockaddr *addr, socklen_t len) propagate_errno; + void ocall_sleep(int second); + void ocall_update_rotate_flag([in] const bool *rotate_flag); + void ocall_update_is_ready([in] const bool *is_ready); }; trusted { diff --git a/dkeycache/Enclave/enclave_la.cpp b/dkeycache/Enclave/enclave_la.cpp index 6332b97f..07e70eb5 100644 --- a/dkeycache/Enclave/enclave_la.cpp +++ b/dkeycache/Enclave/enclave_la.cpp @@ -47,7 +47,7 @@ #include "sgx_tcrypto.h" -void log_printf(uint32_t log_level, const char* filename, uint32_t line, const char *fmt, ...); +extern void log_printf(uint32_t log_level, const char* filename, uint32_t line, const char *fmt, ...); #define MAX_SESSION_COUNT 16 diff --git a/dkeycache/Enclave/enclave_ra.cpp b/dkeycache/Enclave/enclave_ra.cpp index 8e28e54e..97469cd3 100644 --- a/dkeycache/Enclave/enclave_ra.cpp +++ b/dkeycache/Enclave/enclave_ra.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -40,14 +41,26 @@ #include #include #include -#include "sgx_trts.h" + #include "openssl_utility.h" #include "enclave_t.h" #include "elog_utils.h" #include "datatypes.h" +#define RECONNECT_TIMES 3 + +#define ROTATE_START true + +#define ROTATE_END false + uint8_t g_domain_key[SGX_DOMAIN_KEY_SIZE] = {0}; +SSL *g_ssl_session = nullptr; + +std::string g_server_name; + +uint16_t g_server_port = 0; + int verify_callback(int preverify_ok, X509_STORE_CTX *ctx); void log_printf(uint32_t log_level, const char* filename, uint32_t line, const char *fmt, ...) @@ -240,10 +253,79 @@ int create_socket(const char *server_name, uint16_t server_port) return sockfd; } -int enclave_launch_tls_client(const char *server_name, uint16_t server_port) +static bool SendAll(const void *data, int32_t data_size) { - log_d(TLS_CLIENT " called launch tls client\n"); + const char *data_ptr = (const char *)data; + int32_t bytes_sent; + int error = 0; + + while ((bytes_sent = SSL_write(g_ssl_session, data_ptr, data_size)) <= 0) + { + error = SSL_get_error(g_ssl_session, bytes_sent); + if (error == SSL_ERROR_WANT_WRITE) + continue; + log_d(TLS_SERVER "Failed! SSL_write returned %d\n", error); + return false; + } + log_d(TLS_SERVER "%d bytes sent\n", bytes_sent); + return true; +} + +static bool RecvAll(void *data, int32_t data_size) +{ + char *data_ptr = (char *)data; + int32_t bytes_recv; + int error = 0; + + while (true) + { + bytes_recv = SSL_read(g_ssl_session, data_ptr, data_size); + if (bytes_recv <= 0) + { + error = SSL_get_error(g_ssl_session, bytes_recv); + if (error == SSL_ERROR_WANT_READ) + continue; + log_d(TLS_SERVER "Failed! SSL_read returned error=%d\n", error); + return false; + } + log_d(TLS_SERVER "%d bytes recv\n", bytes_recv); + return true; + } + return true; +} + +int SendGetDomainkeyReq() +{ + int ret = 1; + + _request_header_t *req = nullptr; + _response_header_t *resp = nullptr; + + log_d(TLS_CLIENT "-----> Write getdomainkey cmd to server:\n"); + + req = (_request_header_t *)malloc(sizeof(_request_header_t)); + resp = (_response_header_t *)malloc(sizeof(_response_header_t)); + + if (req == nullptr || resp == nullptr) + { + log_d(TLS_CLIENT "getDomainkey malloc failed\n"); + goto out; + } + req->cmd = GET_DOMAINKEY; + + if (!SendAll(req, sizeof(_request_header_t))) + { + goto out; + } + ret = 0; + +out: + SAFE_FREE(req); + return ret; +} +int enclave_connect_and_get_domainkey() +{ int ret = -1; SSL_CTX *ssl_client_ctx = nullptr; @@ -255,11 +337,7 @@ int enclave_launch_tls_client(const char *server_name, uint16_t server_port) int client_socket = -1; int error = 0; - if (server_name == NULL) - { - log_d("Starting" TLS_CLIENT "failed: server name unavailable.\n"); - goto done; - } + log_d("\nStarting" TLS_CLIENT "\n\n\n"); if ((ssl_client_ctx = SSL_CTX_new(TLS_client_method())) == nullptr) @@ -290,7 +368,7 @@ int enclave_launch_tls_client(const char *server_name, uint16_t server_port) goto done; } - if ((ssl_session = SSL_new(ssl_client_ctx)) == nullptr) + if ((g_ssl_session = SSL_new(ssl_client_ctx)) == nullptr) { log_d(TLS_CLIENT "Unable to create a new SSL connection state object\n"); @@ -298,21 +376,21 @@ int enclave_launch_tls_client(const char *server_name, uint16_t server_port) } log_d(TLS_CLIENT "new ssl connection getting created\n"); - client_socket = create_socket(server_name, server_port); + client_socket = create_socket(g_server_name.c_str(), g_server_port); if (client_socket == -1) { log_d( TLS_CLIENT "create a socket and initiate a TCP connect to server: %s:%d " "(errno=%d)\n", - server_name, - server_port, + g_server_name, + g_server_port, errno); goto done; } // set up ssl socket and initiate TLS connection with TLS server - if (SSL_set_fd(ssl_session, client_socket) != 1) + if (SSL_set_fd(g_ssl_session, client_socket) != 1) { log_d(TLS_CLIENT "ssl set fd error.\n"); } @@ -321,23 +399,22 @@ int enclave_launch_tls_client(const char *server_name, uint16_t server_port) log_d(TLS_CLIENT "ssl set fd succeed.\n"); } - if ((error = SSL_connect(ssl_session)) != 1) + if ((error = SSL_connect(g_ssl_session)) != 1) { log_d( TLS_CLIENT "Error: Could not establish a TLS session ret2=%d " "SSL_get_error()=%d\n", error, - SSL_get_error(ssl_session, error)); + SSL_get_error(g_ssl_session, error)); goto done; } log_d( TLS_CLIENT "successfully established TLS channel:%s\n", - SSL_get_version(ssl_session)); + SSL_get_version(g_ssl_session)); - // start the client server communication - if ((error = communicate_with_server(ssl_session)) != 0) + if ((error = SendGetDomainkeyReq()) != 0) { - log_d(TLS_CLIENT "Failed: communicate_with_server (ret=%d)\n", error); + log_d(TLS_CLIENT "Failed: get domainkey (ret=%d)\n", error); goto done; } @@ -345,23 +422,6 @@ int enclave_launch_tls_client(const char *server_name, uint16_t server_port) ret = 0; done: - if (client_socket != -1) - { - int closeRet; - ocall_close(&closeRet, client_socket); - if (closeRet != 0) - { - log_d(TLS_CLIENT "OCALL: error close socket\n"); - ret = -1; - } - } - - if (ssl_session) - { - SSL_shutdown(ssl_session); - SSL_free(ssl_session); - } - if (cert) X509_free(cert); @@ -373,7 +433,182 @@ int enclave_launch_tls_client(const char *server_name, uint16_t server_port) if (ssl_confctx) SSL_CONF_CTX_free(ssl_confctx); + if (ret == -1) + { + SSL_shutdown(g_ssl_session); + if (g_ssl_session) + SSL_free(g_ssl_session); + } + return ret; +} + +static void *HeartbeatToServerHandler(void *arg) +{ + int numberOfErrors = 0; + int res = -1; + bool is_ready = false; + _response_header_t *heart_beat = nullptr; + heart_beat = (_response_header_t *)malloc(sizeof(_response_header_t)); + + if (heart_beat == nullptr) + { + log_d(TLS_CLIENT "HeartbeatToServer malloc failed\n"); + goto out; + } + heart_beat->type = MSG_HEARTBEAT; + + while (true) + { + if (g_ssl_session != nullptr) + { + while (numberOfErrors < RECONNECT_TIMES) + { + + if (!SendAll(heart_beat, sizeof(_response_header_t))) + { + numberOfErrors++; + } + else + { + numberOfErrors = 0; + } + ocall_sleep(5); // sleep 5s. + } + // send failed reach max time then reconnect + if (g_ssl_session) + { + SSL_shutdown(g_ssl_session); + SSL_free(g_ssl_session); + ocall_close(&res, SSL_get_fd(g_ssl_session)); + g_ssl_session = nullptr; + } + if (0 != enclave_connect_and_get_domainkey()) + { + log_d(TLS_CLIENT "Failed: reconnect_with_server\n"); + ocall_update_is_ready(&is_ready); + } + numberOfErrors = 0; + } + } +out: + SAFE_FREE(heart_beat); + pthread_exit((void *)-1); +} + +int UpdateRotateFlag(bool rotate_flag) +{ + int ret = 0; + if (ocall_update_rotate_flag(&rotate_flag) != SGX_SUCCESS) + { + ret = 1; + log_e("OCALL status failed.\n"); + } + return ret; +} + +static void *RecvMsgHandler(void *args) +{ + int ret = 1; + _response_header_t *recv_msg = nullptr; + recv_msg = (_response_header_t *)malloc(sizeof(_response_header_t)); + if (recv_msg == nullptr) + { + log_d(TLS_CLIENT "getDomainkey malloc failed\n"); + } + + while (true) + { + if (g_ssl_session == nullptr) + { + continue; + } + + memset(recv_msg, 0, sizeof(_response_header_t)); + RecvAll(recv_msg, sizeof(_response_header_t)); + + switch (recv_msg->type) + { + case MSG_ROTATE_START: + { + if (UpdateRotateFlag(ROTATE_START)) + { + log_d(TLS_CLIENT "Failed: update rotate flag\n"); + ret = -1; + goto done; + } + } + break; + case MSG_ROTATE_END: + { + if ((ret = SendGetDomainkeyReq()) != 0) + { + log_d(TLS_CLIENT "Failed: get send get domainkey req\n"); + goto done; + } + } + break; + case MSG_DOMAINKEY: + { + ret = 0; + memcpy(g_domain_key, recv_msg->domainKey, SGX_DOMAIN_KEY_SIZE); + log_i("Successfully received the DomainKey from deploy server.\n"); + for (unsigned long int i = 0; i < SGX_DOMAIN_KEY_SIZE; i++) + { + log_d("domain_key[%u]=%2u\n", i, g_domain_key[i]); + } + int retval = 0; + if (UpdateRotateFlag(ROTATE_END)) + { + log_d(TLS_CLIENT "Failed: update rotate flag\n"); + ret = -1; + goto done; + } + } + break; + default: + break; + } + } +done: + SAFE_FREE(recv_msg); + pthread_exit((void *)-1); +} +int enclave_launch_tls_client(const char *server_name, uint16_t server_port) +{ + log_d(TLS_CLIENT " called launch tls client\n"); + + int ret = -1; + if (server_name == nullptr) + { + log_d(TLS_CLIENT "Failed: null server_name"); + goto done; + } + + g_server_name = server_name; + g_server_port = server_port; + if (0 != enclave_connect_and_get_domainkey()) + { + log_d(TLS_CLIENT "Failed: reconnect_with_server\n"); + goto done; + } + + pthread_t heartbeat_to_server_thread, recvmsg_thread; + if (pthread_create(&heartbeat_to_server_thread, NULL, HeartbeatToServerHandler, NULL) < 0) + { + log_d("could not create thread\n"); + goto done; + } + + if (pthread_create(&recvmsg_thread, NULL, RecvMsgHandler, NULL) < 0) + { + log_d("could not create thread\n"); + goto done; + } + + // Free the structures we don't need anymore + ret = 0; +done: log_d(TLS_CLIENT " %s\n", (ret == 0) ? "success" : "failed"); return ret; } diff --git a/dkeycache/Makefile b/dkeycache/Makefile index 0a8f43d0..4e7359e7 100644 --- a/dkeycache/Makefile +++ b/dkeycache/Makefile @@ -49,7 +49,8 @@ App_Include_Paths := \ -IApp \ -I$(LOG_DIR) \ -I$(SGX_SDK)/include \ - -I$(TOPDIR)/include + -I$(TOPDIR)/include \ + -I$(TOPDIR)/utils/sgx_socket/tls App_C_Flags := $(SGX_COMMON_FLAGS) -fPIC -Wno-attributes $(App_Include_Paths) -DRUNTIME_FOLDER=\"$(RUNTIME_FOLDER)\" diff --git a/dkeyserver/App/base64.cpp b/dkeyserver/App/base64.cpp new file mode 100644 index 00000000..60bb85c1 --- /dev/null +++ b/dkeyserver/App/base64.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2021-2022 Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. 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 "base64.h" + + +static const std::string encode_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static inline bool is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +std::string base64_encode(const uint8_t *bytes_to_encode, uint32_t in_len) { + std::string encode_str; + uint32_t i = 0; + uint32_t j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(i = 0; (i <4) ; i++) + encode_str += encode_table[char_array_4[i]]; + i = 0; + } + } + + if (i) { + for(j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) + encode_str += encode_table[char_array_4[j]]; + + while((i++ < 3)) + encode_str += '='; + } + + return encode_str; + +} + +std::string base64_decode(const std::string &encoded_string) { + uint32_t in_len = encoded_string.size(); + uint32_t i = 0; + uint32_t j = 0; + uint32_t in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string decode_str; + + while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; in_++; + if (i ==4) { + for (i = 0; i <4; i++) + char_array_4[i] = static_cast(encode_table.find(char_array_4[i])); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) + decode_str += char_array_3[i]; + i = 0; + } + } + + if (i) { + for (j = i; j <4; j++) + char_array_4[j] = 0; + + for (j = 0; j <4; j++) + char_array_4[j] = static_cast(encode_table.find(char_array_4[j])); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (j = 0; (j < i - 1); j++) + decode_str += char_array_3[j]; + } + + return decode_str; +} + + diff --git a/dkeyserver/App/base64.h b/dkeyserver/App/base64.h new file mode 100644 index 00000000..9b5ef6eb --- /dev/null +++ b/dkeyserver/App/base64.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021-2022 Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. 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. + * + */ + +#ifndef _BASE64_H_ +#define _BASE64_H_ + +#include + +std::string base64_encode(const uint8_t *bytes_to_encode, uint32_t in_len); + +std::string base64_decode(const std::string &encoded_string); + +#endif + diff --git a/dkeyserver/App/couchdb_curl.cpp b/dkeyserver/App/couchdb_curl.cpp new file mode 100644 index 00000000..baa63326 --- /dev/null +++ b/dkeyserver/App/couchdb_curl.cpp @@ -0,0 +1,142 @@ +#include "couchdb_curl.h" + +using namespace std; + +size_t getUrlResponse(void *buffer, size_t size, size_t count, void *response) +{ + string *str = (string *)response; + (*str).append((char *)buffer, size * count); + + return size * count; +} + +string setRequest(string url, string data) +{ + string response = ""; + + CURL *curl = NULL; + struct curl_slist *headers = NULL; + curl_global_init(CURL_GLOBAL_ALL); + + curl = curl_easy_init(); + + headers = curl_slist_append(headers, "Accept: application/json"); + headers = curl_slist_append(headers, "Content-Type: application/json"); + headers = curl_slist_append(headers, "charsets: utf-8"); + + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); + + // curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); + // curl_easy_setopt(curl, CURLOPT_HEADER, 0L); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &getUrlResponse); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + + int res = curl_easy_perform(curl); + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return response; +} + +string getRequest(string url) +{ + string response = ""; + + CURL *curl = NULL; + struct curl_slist *headers = NULL; + curl_global_init(CURL_GLOBAL_ALL); + + curl = curl_easy_init(); + + headers = curl_slist_append(headers, "Accept: application/json"); + headers = curl_slist_append(headers, "Content-Type: application/json"); + headers = curl_slist_append(headers, "charsets: utf-8"); + + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET"); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &getUrlResponse); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + + curl_easy_perform(curl); + + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return response; +} + +void print_json(JsonValue data, vector &keylist, string req_data) +{ + JsonValue::Members mem = data.getMemberNames(); + + for (auto iter = mem.begin(); iter != mem.end(); iter++) + { + if (data[*iter].type() == Json::objectValue) + { + print_json(data[*iter], keylist, req_data); + } + else if (data[*iter].type() == Json::arrayValue) + { + auto cnt = data[*iter].size(); + for (auto i = 0; i < cnt; i++) + { + print_json(data[*iter][i], keylist, req_data); + } + } + else if (data[*iter].type() == Json::stringValue) + { + string str = data[*iter].asString(); + if (string::npos != str.find(req_data)) + if (keylist.at(keylist.size() - 1).compare(data[*iter].asString())) + keylist.push_back(data[*iter].asString()); + } + } +} + +int couchdb_get(vector &data, string req_data, string url) +{ + auto res = getRequest(url + "/ehsm_kms_db/_all_docs"); + + JsonReader reader; + JsonValue value; + if (!reader.parse(res, value)) + return -1; + + vector _ids; + _ids.push_back(""); + print_json(value, _ids, req_data); + _ids.erase(_ids.begin()); + + for (auto id : _ids) + { + auto res = getRequest(url + "/ehsm_kms_db/" + id); + JsonObj temp; + temp.parse(res); + cout<<"couchdb_get in="< +#include +#include +#include +#include +#include +#include + +#include "json_utils.h" +#include "auto_version.h" + +using namespace std; + +typedef Json::Reader JsonReader; +typedef Json::Value JsonValue; + +int couchdb_get(vector &data, + string req_data, + string url); + +int couchdb_put(JsonObj data, string url); + +#endif diff --git a/dkeyserver/App/main.cpp b/dkeyserver/App/main.cpp index e32f4f4a..96116c48 100644 --- a/dkeyserver/App/main.cpp +++ b/dkeyserver/App/main.cpp @@ -1,6 +1,7 @@ #include #include #include "sgx_urts.h" +#include #include "auto_version.h" #include "ulog_utils.h" @@ -10,38 +11,93 @@ #include #include #include +#include #include #include #include #include #include #include - +#include #include +#include +#include + +#include "base64.h" +#include "couchdb_curl.h" +#include "datatypes.h" +#include "json_utils.h" #define ENCLAVE_PATH "libenclave-ehsm-dkeyserver.signed.so" #define ROLE_WORKER "worker" #define ROLE_ROOT "root" +#define CMK_DB "cmk:" +#define USER_INFO_DB "user_info:" char s_port[] = "8888"; #define FILE_NAME (std::string(RUNTIME_FOLDER) + "dkey.bin").c_str() -sgx_enclave_id_t g_enclave_id; +#define CMK_INFO 0 +#define USER_INFO 1 + +#define KEYBLOB 0 +#define CMK 1 +#define SM_DEFAULT_CMK 2 + +sgx_enclave_id_t g_enclave_id = 0; +std::string g_couchdb_url; using namespace std; +errno_t memcpy_s( + void *dest, + size_t numberOfElements, + const void *src, + size_t count) +{ + if (numberOfElements < count) + return -1; + memcpy(dest, src, count); + return 0; +} + +int ocall_select(int fd) +{ + fd_set server_fd_set; + FD_ZERO(&server_fd_set); + FD_SET(fd, &server_fd_set); + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 5000; + + int ret = select(fd + 1, &server_fd_set, NULL, NULL, &tv); + if (ret > 0) + { + if (FD_ISSET(fd, &server_fd_set)) + { + log_d("%d\n", ret); + return 1; + } + } + + sleep(1); + + return 0; +} + void ocall_print_string(uint32_t log_level, const char *str, const char *filename, uint32_t line) { - switch (log_level) + switch (log_level) { - case LOG_INFO: - case LOG_DEBUG: - case LOG_ERROR: - case LOG_WARN: - log_c(log_level, str, filename, line); - break; - default: - log_c(LOG_ERROR, "log system error in ocall print.\n", filename, line); - break; + case LOG_INFO: + case LOG_DEBUG: + case LOG_ERROR: + case LOG_WARN: + log_c(log_level, str, filename, line); + break; + default: + log_c(LOG_ERROR, "log system error in ocall print.\n", filename, line); + break; } } @@ -60,6 +116,11 @@ void ocall_get_current_time(uint64_t *p_current_time) *p_current_time = (uint64_t)rawtime; } +void ocall_sleep(int second) +{ + sleep(second); +} + int ocall_set_dkeyserver_done() { return (system("touch /tmp/dkeyserver_isready.status")); @@ -71,39 +132,77 @@ static inline bool file_exists(const std::string &name) return (stat(name.c_str(), &buffer) == 0); } -int ocall_read_domain_key(uint8_t *cipher_dk, uint32_t cipher_dk_len) +int ocall_read_domain_key(uint8_t *cipher_dk, + uint32_t cipher_dk_len, + uint64_t *create_time, + uint8_t *dk_hash, + uint32_t dk_hash_size) { if (!file_exists(FILE_NAME)) { - log_e("ocall_read_domain_key file does not exist.\n"); + log_e("ocall_read_domain_key: file does not exist.\n"); return -2; } fstream file; + JsonReader reader; + JsonValue value; + uint64_t temp_time = 0; + file.open(FILE_NAME, ios::in | ios::binary); if (!file) { - log_e("Failed to open file...\n"); + log_e("ocall_read_domain_key: failed to open file\n"); return -1; } file.seekg(0, std::ios::end); size_t size = file.tellg(); file.seekg(0); - if (size != cipher_dk_len) - { - log_e("mismatched length: %ld:%d.\n", size, cipher_dk_len); - return -1; - } - uint8_t tmp[size] = {0}; - if (file.read((char *)&tmp, size)) + uint8_t tmp_buf[size] = {0}; + uint8_t last_domainkey[cipher_dk_len] = {0}; + + if (file.read((char *)&tmp_buf, size)) { - memcpy(cipher_dk, tmp, cipher_dk_len); + if (!reader.parse((char *)tmp_buf, value)) + return -1; + + JsonValue::Members mem = value.getMemberNames(); + + JsonObj last_domainkey_json; + last_domainkey_json.addData_uint64("createDate", 0); + + for (auto iter = mem.begin(); iter != mem.end(); iter++) + { + for (auto iter = mem.begin(); iter != mem.end(); iter++) + { + if (value[*iter].type() == Json::objectValue) + { + JsonObj temp; + temp.setJson(value[*iter]); + uint64_t time = temp.readData_uint64("createDate"); + if (time > last_domainkey_json.readData_uint64("createDate")) + { + last_domainkey_json = temp; + } + } + } + } + + *create_time = last_domainkey_json.readData_uint64("createDate"); + memcpy(dk_hash, + base64_decode(last_domainkey_json.readData_string("dk_hashcode").c_str()).c_str(), + 32); + + log_i("base64 hash=%s", last_domainkey_json.readData_string("dk_hashcode").c_str()); + log_i("createDate=%d", last_domainkey_json.readData_uint64("createDate")); + + last_domainkey_json.readData_uint8Array("dkey", cipher_dk); } else { - log_e("Failed to read data from file...\n"); + log_e("ocall_read_domain_key: Failed to read data from file...\n"); return -1; } @@ -112,22 +211,72 @@ int ocall_read_domain_key(uint8_t *cipher_dk, uint32_t cipher_dk_len) return 0; } -int ocall_store_domain_key(uint8_t *cipher_dk, uint32_t cipher_dk_len) +int ocall_store_domain_key(uint8_t *cipher_dk, + uint32_t cipher_dk_len, + uint8_t *dk_hash, + uint32_t dk_hash_size) { + fstream file; + + string dk_hash_base64 = base64_encode(dk_hash, dk_hash_size); + + if (!file_exists(FILE_NAME)) + { + log_d("domain key file does not exist.\n"); + file.open(FILE_NAME, ios::out | ios::binary | ios::trunc); + if (!file) + { + log_e("Failed to create file...\n"); + return -1; + } + file.write("{}", 3); + file.close(); + } uint8_t tmp[cipher_dk_len]; + JsonObj domainkey_json; + JsonObj storeJson; + time_t createdate; + + time(&createdate); memcpy(tmp, cipher_dk, cipher_dk_len); - fstream file; - file.open(FILE_NAME, ios::out | ios::binary | ios::trunc); + file.open(FILE_NAME, ios::in | ios::binary); if (!file) { - log_e("Failed to create file...\n"); + log_e("store_domain_key: Failed to open file...\n"); return -1; } + file.seekg(0, std::ios::end); + size_t size = file.tellg(); + file.seekg(0); + char tmp_buf[size + 1] = {0}; - file.write((char *)&tmp, cipher_dk_len); + if (file.read(tmp_buf, size)) + storeJson.parse(tmp_buf); + else + { + log_e("store_domain_key: Failed to read data from file...\n"); + return -1; + } file.close(); + if (!storeJson.hasOwnProperty(dk_hash_base64)) + { + domainkey_json.addData_uint8Array("dkey", tmp, cipher_dk_len); + domainkey_json.addData_uint64("createDate", (uint64_t)createdate); + domainkey_json.addData_string("dk_hashcode", dk_hash_base64); + + storeJson.addData_JsonValue(dk_hash_base64, domainkey_json.getJson()); + // log_d("storeJson=> %s", storeJson.toString().c_str()); + + file.open(FILE_NAME, ios::out | ios::binary | ios::trunc); + file.write(storeJson.toString().c_str(), strlen(storeJson.toString().c_str())); + file.close(); + } + + log_i("new dkey base64 hash=%s", dk_hash_base64.c_str()); + log_i("new dkey createDate=%d", createdate); + return 0; } @@ -138,6 +287,11 @@ int ocall_socket(int domain, int type, int protocol) return socket(domain, type, protocol); } +int ocall_send(int fd, const char *msg, uint32_t msg_size, int flag) +{ + return send(fd, msg, msg_size, flag); +} + int ocall_bind(int fd, const struct sockaddr *addr, socklen_t len) { return bind(fd, addr, len); @@ -191,18 +345,192 @@ int ocall_connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen return -1; } +std::vector couchdb_data; + +int ocall_load_couchdb(int db_type) +{ + int db_size; + couchdb_data.clear(); + + switch (db_type) + { + case CMK_INFO: + db_size = couchdb_get(couchdb_data, CMK_DB, g_couchdb_url); + break; + case USER_INFO: + db_size = couchdb_get(couchdb_data, USER_INFO_DB, g_couchdb_url); + break; + default: + return 0; + } + + if (db_size == 0) + { + log_d("no cmkdb data\n"); + return 0; + } + return db_size; +} + +int update_CMK_by_dbName(std::string dbName, + std::string keyName, + uint8_t *cipher_cmk_dk, + uint32_t cipher_cmk_dk_len, + uint8_t *dk_hash) +{ + int error = 0; + int ret; + fstream file; + JsonObj dkey_storeJson; + JsonObj domainkeyJson; + string cmk_dk_hash_base64; + + std::string cmk_str; + string new_cmk; + size_t cmk_size = 0; + size_t new_cmk_size = 0; + string new_dk_hash_base64 = base64_encode(dk_hash, 32); + ehsm_keyblob_t *cmk = NULL; + + file.open(FILE_NAME, ios::in | ios::binary); + if (!file) + { + log_e("update_CMK_by_dbName: Failed to open file...\n"); + return -1; + } + file.seekg(0, std::ios::end); + size_t size = file.tellg(); + file.seekg(0); + char tmp_buf[size] = {0}; + + if (file.read(tmp_buf, size)) + dkey_storeJson.parse(tmp_buf); + else + { + log_e("update_CMK_by_dbName: Failed to read data from file...\n"); + return -1; + } + file.close(); + + auto iter = couchdb_data.at(0); + log_i("iter0=%s", iter.toString().c_str()); + + if (iter.hasOwnProperty("error")) + { + log_e("iter:\n%s", iter.toString().c_str()); + goto out; + } + if (iter.hasOwnProperty("creationDate")) + { + log_e("iter has creationDate"); + } + + cmk_str = base64_decode(iter.readData_string(keyName)); + cmk_size = cmk_str.size(); + cmk = (ehsm_keyblob_t *)malloc(cmk_size); + if (cmk == NULL) + { + log_e("cmk = NULL"); + goto out; + } +log_i("iter1=%s", iter.toString().c_str()); + memcpy(cmk, (uint8_t *)cmk_str.data(), cmk_size); + cmk_dk_hash_base64 = base64_encode(cmk->metadata.dk_hashcode, 32); + if (strncmp(cmk_dk_hash_base64.c_str(), new_dk_hash_base64.c_str(), new_dk_hash_base64.size()) == 0) + { + SAFE_FREE(cmk); + log_i("this cmk has been updated"); + goto out; + } +log_i("iter2=%s", iter.toString().c_str()); + domainkeyJson.setJson(dkey_storeJson.readData_JsonValue(cmk_dk_hash_base64)); + domainkeyJson.readData_uint8Array("dkey", cipher_cmk_dk); + log_i("ecall_reencrypt_cmk in"); + error = ecall_reencrypt_cmk(g_enclave_id, &ret, cipher_cmk_dk, cipher_cmk_dk_len, cmk, APPEND_SIZE_TO_KEYBLOB_T(cmk->keybloblen)); + if (ret != SGX_SUCCESS || error != SGX_SUCCESS) + { + SAFE_FREE(cmk); + log_e("store new domain key failed (%d)(%d)", error, ret); + goto out; + } + log_i("ecall_reencrypt_cmk out"); + sleep(1); + + new_cmk_size = APPEND_SIZE_TO_KEYBLOB_T(cmk->keybloblen); + new_cmk = base64_encode((uint8_t *)cmk, new_cmk_size); + log_i("iter3=%s", iter.toString().c_str()); + if (new_cmk.size() > 0) + { + iter.addData_string(keyName, new_cmk); + SAFE_FREE(cmk); + } + else + { + SAFE_FREE(cmk); + goto out; + } + if (couchdb_put(iter, g_couchdb_url) < 0) + { + goto out; + } + + couchdb_data.erase(couchdb_data.begin()); + return 0; +out: + couchdb_data.erase(couchdb_data.begin()); + return -1; +} + +int ocall_update_CMK(uint8_t *cipher_dk, + uint32_t cipher_dk_len, + uint8_t *dk_hash, + uint32_t dk_hash_size, + int key_type) +{ + int ret = 0; + + std::string key; + std::string db; + switch (key_type) + { + case KEYBLOB: + key = "keyBlob"; + db = CMK_DB; + break; + case CMK: + key = "cmk"; + db = USER_INFO_DB; + break; + case SM_DEFAULT_CMK: + key = "sm_default_cmk"; + db = USER_INFO_DB; + break; + default: + return -1; + } + // update cmk_db data + ret = update_CMK_by_dbName(db, key, cipher_dk, cipher_dk_len, dk_hash); + if (ret == -1) + return -1; + + return ret; +} + void print_usage(int code) { log_i("Usage: ehsm-dkeyserver " "-r [ server role ] " + "-w [ password ] " + "-t [ period ] " + "-u [ couchdb_url ] " "-i [ target server ip ] " - "-u [ target server url ] " "-p [target server port]\n"); log_i("-h Print usage information and quit.\n" "-r Set the role of this machine as root or worker in server cluster.\n" "-i Set the ip address of target server.\n" - "-u Set the url of target server.\n" - "-p Set the port of target server.\n"); + "-w Set the password of root server.\n" + "-u Set the url to connect to couchdb, if you want use roration dk function, you must set couchdb_url. eg:http:// + user + : + password + @ + ip + : + port\n" + "-t Set the period of root server.\n"); exit(code); } @@ -210,18 +538,21 @@ static void parse_args(int argc, char *argv[], string &server_role, string &target_ip_addr, - uint16_t *target_port) + string &couchdb_url, + uint16_t *target_port, + size_t *password, + int *period) { int opt; int oidx = 0; - string host; - struct hostent *hptr; - static const char *_sopts = "r:i:u:p:h"; + static const char *_sopts = "r:i:p:h:w:P:u:"; static const struct option _lopts[] = {{"role", required_argument, NULL, 'r'}, {"ip", optional_argument, NULL, 'i'}, - {"url", optional_argument, NULL, 'u'}, {"port", optional_argument, NULL, 'p'}, + {"password", optional_argument, NULL, 'w'}, + {"period", optional_argument, NULL, 'P'}, {"help", no_argument, NULL, 'h'}, + {"couchdb_url", optional_argument, NULL, 'u'}, {0, 0, 0, 0}}; while ((opt = getopt_long(argc, argv, _sopts, _lopts, &oidx)) != -1) { @@ -239,26 +570,41 @@ static void parse_args(int argc, target_ip_addr = strdup(optarg); break; case 'u': - host = strdup(optarg); - hptr = gethostbyname(host.c_str()); - if (hptr == NULL || hptr->h_addr == NULL) + couchdb_url = strdup(optarg); + break; + case 'p': + try { - log_e("can't parse hostname [%s].", host.c_str()); + *target_port = std::stoi(strdup(optarg)); } - else + catch (...) { - char *ip = NULL; - target_ip_addr = inet_ntoa(*(struct in_addr *)hptr->h_addr_list[0]); + log_e("[-p %s] port must be a number.", optarg); } break; - case 'p': + case 'w': try { - *target_port = std::stoi(strdup(optarg)); + *password = std::stoi(strdup(optarg)); } catch (...) { - log_e("[-p %s] port must be a number.", optarg); + log_e("[-w %s] password must be a number.", optarg); + } + break; + case 'P': + try + { + *period = std::stoi(strdup(optarg)); + if (*period <= 30 || *period >= 365) + { + log_e("the period must greater than 30 days and less than 365 days."); + print_usage(EXIT_FAILURE); + } + } + catch (...) + { + log_e("[-t %s] period time must be a number.", optarg); } break; case 'h': @@ -273,7 +619,10 @@ static void parse_args(int argc, int validate_parameter(string server_role, string target_ip_addr, - uint16_t target_port) + string couchdb_url, + uint16_t target_port, + size_t password, + int period) { if (server_role[0] == '\0') { @@ -292,6 +641,37 @@ int validate_parameter(string server_role, log_e("please set correct target server ip and port.\n"); return -1; } + if (server_role == ROLE_WORKER && password != 0 && period != -1) + { + log_e("worker server cannot set password and period\n"); + return -1; + } + if (server_role == ROLE_ROOT && couchdb_url[0] == '\0') + { + log_e("root server must set couchdb_url\n"); + return -1; + } + return 0; +} + +int initialize_enclave(const sgx_uswitchless_config_t *us_config) +{ + sgx_status_t ret = SGX_ERROR_UNEXPECTED; + + /* Call sgx_create_enclave to initialize an enclave instance */ + /* Debug Support: set 2nd parameter to 1 */ + + const void *enclave_ex_p[32] = {0}; + + enclave_ex_p[SGX_CREATE_ENCLAVE_EX_SWITCHLESS_BIT_IDX] = (const void *)us_config; + + ret = sgx_create_enclave_ex(ENCLAVE_PATH, SGX_DEBUG_FLAG, NULL, NULL, &g_enclave_id, NULL, SGX_CREATE_ENCLAVE_EX_SWITCHLESS, enclave_ex_p); + if (ret != SGX_SUCCESS) + { + // print_error_message(ret); + return -1; + } + return 0; } @@ -306,30 +686,46 @@ int main(int argc, char *argv[]) return -1; } } + if (initLogger("dkeyserver.log") < 0) return -1; log_i("Service name:\t\tDomainKey Provisioning Service %s", EHSM_VERSION); log_i("Service built:\t\t%s", EHSM_DATE); log_i("Service git_sha:\t\t%s", EHSM_GIT_SHA); log_i("Runtime folder:\t%s", RUNTIME_FOLDER); + + signal(SIGPIPE, SIG_IGN); + string server_role; string target_ip_addr; + string couchdb_url; uint16_t target_port = 0; + size_t root_password = 0; + int root_period = -1; - sgx_status_t sgxStatus = SGX_ERROR_UNEXPECTED; parse_args(argc, argv, server_role, target_ip_addr, - &target_port); - - int ret = validate_parameter(server_role, target_ip_addr, target_port); + couchdb_url, + &target_port, + &root_password, + &root_period); + + int ret = validate_parameter(server_role, + target_ip_addr, + couchdb_url, + target_port, + root_password, + root_period); if (ret != 0) { log_i("Usage: ehsm-dkeyserver " "-r [server role] " "-i [target server ip] " - "-u [target server url] " + "-w [set root password] " + "-t [set period, eg:40(days)] " + "-u [set couchdb_url eg:http:// + user + : + password + @ + ip + : + port] " "-p [target server port]\n"); return -1; } @@ -343,25 +739,31 @@ int main(int argc, char *argv[]) log_i("Target Server:\t%s:%d", target_ip_addr.c_str(), target_port); } - ret = sgx_create_enclave(ENCLAVE_PATH, - SGX_DEBUG_FLAG, - NULL, NULL, - &g_enclave_id, NULL); - if (SGX_SUCCESS != ret) + sgx_uswitchless_config_t us_config = SGX_USWITCHLESS_CONFIG_INITIALIZER; + us_config.num_uworkers = 2; + us_config.num_tworkers = 2; + + /* Initialize the enclave */ + if (initialize_enclave(&us_config) < 0) { - log_e("failed(%d) to create enclave.\n", ret); + printf("Error: enclave initialization failed\n"); return -1; } + g_couchdb_url = couchdb_url; + + int sgxStatus = -1; ret = sgx_set_up_tls_server(g_enclave_id, - &ret, + &sgxStatus, s_port, server_role.c_str(), target_ip_addr.c_str(), - target_port); + target_port, + root_password, + root_period); if (ret != SGX_SUCCESS || sgxStatus != SGX_SUCCESS) { - log_d("Host: setup_tls_server failed\n"); + log_d("Host: setup_tls_server failed(%d)(%d)\n", ret, sgxStatus); } logger_shutDown(); diff --git a/dkeyserver/Enclave/domainkey_factory.cpp b/dkeyserver/Enclave/domainkey_factory.cpp new file mode 100644 index 00000000..8ed33cb7 --- /dev/null +++ b/dkeyserver/Enclave/domainkey_factory.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2020-2022 Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. 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 "elog_utils.h" +#include "sgx_tseal.h" + +#include +#include +#include +#include + +#include "sgx_report.h" +#include "sgx_utils.h" +#include "sgx_tkey_exchange.h" + +#include "datatypes.h" +#include "domainkey_factory.h" +#include "enclave_t.h" + +extern void log_printf(uint32_t log_level, const char* filename, uint32_t line, const char *fmt, ...); + +bool ehsm_get_symmetric_key_size(ehsm_keyspec_t key_spec, uint32_t &key_size) +{ + switch (key_spec) + { + case EH_AES_GCM_128: + case EH_SM4_CTR: + case EH_SM4_CBC: + key_size = 16; + break; + case EH_AES_GCM_192: + key_size = 24; + break; + case EH_AES_GCM_256: + key_size = 32; + break; + default: + return false; + } + return true; +} + +uint32_t ehsm_get_gcm_ciphertext_size(const sgx_aes_gcm_data_ex_t *gcm_data) +{ + if (NULL == gcm_data) + return UINT32_MAX; + + return gcm_data->ciphertext_size; +} + +// https://github.com/openssl/openssl/blob/master/test/aesgcmtest.c#L38 +sgx_status_t aes_gcm_encrypt(uint8_t *key, + uint8_t *cipherblob, + const EVP_CIPHER *block_mode, + uint8_t *plaintext, + uint32_t plaintext_len, + uint8_t *aad, + uint32_t aad_len, + uint8_t *iv, + uint32_t iv_len, + uint8_t *tag, + uint32_t tag_len) +{ + sgx_status_t ret = SGX_ERROR_UNEXPECTED; + int temp_len = 0; + EVP_CIPHER_CTX *pctx = NULL; + + // Create and init ctx + if (!(pctx = EVP_CIPHER_CTX_new())) + goto out; + + if (1 != EVP_EncryptInit_ex(pctx, block_mode, NULL, NULL, NULL)) + goto out; + + if (iv_len != SGX_AESGCM_IV_SIZE) + if (1 != EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL)) + goto out; + + // Initialise encrypt/decrpty, key and IV + if (1 != EVP_EncryptInit_ex(pctx, NULL, NULL, key, iv)) + goto out; + + // Provide AAD data if exist + if (aad != NULL && aad_len > 0) + if (1 != EVP_EncryptUpdate(pctx, NULL, &temp_len, aad, aad_len)) + goto out; + + if (plaintext != NULL && plaintext_len > 0) + { + // Provide the message to be encrypted, and obtain the encrypted output. + if (1 != EVP_EncryptUpdate(pctx, cipherblob, &temp_len, plaintext, plaintext_len)) + goto out; + } + else + { + ret = SGX_ERROR_INVALID_PARAMETER; + goto out; + } + + // Finalise the encryption/decryption + if (1 != EVP_EncryptFinal_ex(pctx, cipherblob + temp_len, &temp_len)) + goto out; + + // Get tag + if (1 != EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_GET_TAG, tag_len, tag)) + goto out; + + ret = SGX_SUCCESS; + +out: + EVP_CIPHER_CTX_free(pctx); + return ret; +} + +sgx_status_t aes_gcm_decrypt(uint8_t *key, + uint8_t *plaintext, + const EVP_CIPHER *block_mode, + uint8_t *ciphertext, + uint32_t ciphertext_len, + uint8_t *aad, + uint32_t aad_len, + uint8_t *iv, + uint32_t iv_len, + uint8_t *tag, + uint32_t tag_len) +{ + sgx_status_t ret = SGX_ERROR_UNEXPECTED; + + int temp_len = 0; + EVP_CIPHER_CTX *pctx = NULL; + // Create and initialise the context + if (!(pctx = EVP_CIPHER_CTX_new())) + goto out; + + if (1 != EVP_EncryptInit_ex(pctx, block_mode, NULL, NULL, NULL)) + goto out; + + if (iv_len != SGX_AESGCM_IV_SIZE) + if (1 != EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL)) + goto out; + + // Initialise decrypt, key and IV + if (!EVP_DecryptInit_ex(pctx, NULL, NULL, key, iv)) + goto out; + + if (aad != NULL && aad_len > 0) + if (!EVP_DecryptUpdate(pctx, NULL, &temp_len, aad, aad_len)) + goto out; + + // Decrypt message, obtain the plaintext output + if (ciphertext != NULL && ciphertext_len > 0) + { + if (!EVP_DecryptUpdate(pctx, plaintext, &temp_len, ciphertext, ciphertext_len)) + goto out; + } + else + { + ret = SGX_ERROR_INVALID_PARAMETER; + goto out; + } + + // Update expected tag value + if (!EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_SET_TAG, tag_len, tag)) + goto out; + + // Finalise the decryption. A positive return value indicates success, + // anything else is a failure - the plaintext is not trustworthy. + if (EVP_DecryptFinal_ex(pctx, plaintext + temp_len, &temp_len) <= 0) + { + ret = SGX_ERROR_MAC_MISMATCH; + goto out; + } + + ret = SGX_SUCCESS; + +out: + EVP_CIPHER_CTX_free(pctx); + return ret; +} + +// use the g_domain_key to decrypt the cmk and get it plaintext +sgx_status_t ehsm_parse_keyblob(uint8_t *plaintext, + sgx_aes_gcm_data_ex_t *keyblob_data, + uint8_t *domainkey) +{ + if (NULL == keyblob_data || NULL == plaintext) + return SGX_ERROR_INVALID_PARAMETER; + + sgx_status_t ret = aes_gcm_decrypt(domainkey, + plaintext, EVP_aes_256_gcm(), + keyblob_data->payload, + keyblob_data->ciphertext_size, + NULL, + 0, + keyblob_data->iv, + SGX_AESGCM_IV_SIZE, + keyblob_data->mac, + SGX_AESGCM_MAC_SIZE); + + if (SGX_SUCCESS != ret) + log_e("gcm decrypting failed.\n"); + + return ret; +} + +// use the g_domain_key to encrypt the cmk and get it ciphertext +sgx_status_t ehsm_create_keyblob(uint8_t *plaintext, + uint32_t plaintext_size, + sgx_aes_gcm_data_ex_t *keyblob_data, + uint8_t *domainkey) +{ + if (keyblob_data == NULL || plaintext == NULL) + return SGX_ERROR_INVALID_PARAMETER; + + sgx_status_t ret = sgx_read_rand(keyblob_data->iv, sizeof(keyblob_data->iv)); + if (ret != SGX_SUCCESS) + { + log_d("error generating iv.\n"); + return ret; + } + + ret = aes_gcm_encrypt(domainkey, + keyblob_data->payload, EVP_aes_256_gcm(), + plaintext, plaintext_size, + NULL, 0, + keyblob_data->iv, SGX_AESGCM_IV_SIZE, + keyblob_data->mac, SGX_AESGCM_MAC_SIZE); + + if (SGX_SUCCESS != ret) + { + log_e("gcm encrypting failed.\n"); + } + else + { + keyblob_data->ciphertext_size = plaintext_size; + keyblob_data->aad_size = 0; + } + + return ret; +} diff --git a/dkeyserver/Enclave/domainkey_factory.h b/dkeyserver/Enclave/domainkey_factory.h new file mode 100644 index 00000000..9c31f51f --- /dev/null +++ b/dkeyserver/Enclave/domainkey_factory.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2020-2022 Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. 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 "elog_utils.h" +#include "sgx_tseal.h" + +#include +#include +#include +#include + +#include "openssl/evp.h" +#include "openssl/pem.h" +#include "openssl/bio.h" +#include "openssl/err.h" + +#include "sgx_report.h" +#include "sgx_utils.h" +#include "sgx_tkey_exchange.h" + +typedef struct _aes_gcm_data_ex_t +{ + uint32_t ciphertext_size; + uint32_t aad_size; + uint8_t reserve1[8]; + uint8_t iv[SGX_AESGCM_IV_SIZE]; + uint8_t reserve2[4]; + uint8_t mac[SGX_AESGCM_MAC_SIZE]; + uint8_t payload[]; /* ciphertext + aad */ +} sgx_aes_gcm_data_ex_t; + +// use the g_domain_key to encrypt the cmk and get it ciphertext +sgx_status_t ehsm_parse_keyblob(uint8_t *plaintext, + sgx_aes_gcm_data_ex_t *keyblob_data, + uint8_t *domainkey); + +// use the g_domain_key to decrypt the cmk and get it plaintext +sgx_status_t ehsm_create_keyblob(uint8_t *plaintext, uint32_t plaintext_size, + sgx_aes_gcm_data_ex_t *keyblob_data, uint8_t *domainkey); + +bool ehsm_get_symmetric_key_size(ehsm_keyspec_t key_spec, uint32_t &key_size); + +uint32_t ehsm_get_gcm_ciphertext_size(const sgx_aes_gcm_data_ex_t *gcm_data); \ No newline at end of file diff --git a/dkeyserver/Enclave/enclave.config.xml b/dkeyserver/Enclave/enclave.config.xml index 12b5df99..947a0f76 100644 --- a/dkeyserver/Enclave/enclave.config.xml +++ b/dkeyserver/Enclave/enclave.config.xml @@ -3,7 +3,7 @@ 0 0x40000 0xA00000 - 8 + 10 1 0 0 diff --git a/dkeyserver/Enclave/enclave.cpp b/dkeyserver/Enclave/enclave.cpp index ff50790f..59cd14f2 100644 --- a/dkeyserver/Enclave/enclave.cpp +++ b/dkeyserver/Enclave/enclave.cpp @@ -42,27 +42,57 @@ #include #include #include +#include + +#include +#include +#include + #include "openssl/evp.h" #include "openssl/ssl.h" +#include "openssl/sha.h" #include "sys/socket.h" #include "netinet/in.h" #include "byteswap.h" #include "openssl_utility.h" #include "datatypes.h" +#include "domainkey_factory.h" -#define CLIENT_MAX_NUM 20 #define ROLE_WORKER "worker" #define ROLE_ROOT "root" +std::string g_server_name; +uint16_t g_server_port; +std::string g_server_role; + +SSL *g_ssl_session = nullptr; +int g_socket_fd = -1; + +dkey_server_domainkey g_domainkey; + +uint64_t g_nextRotationTime = 0; +int g_period = -1; + +size_t g_password = 0; +bool g_ready_flag = false; + +typedef struct g_sessionPoolStruct +{ + SSL *ssl_session; + int errorCnt; +} g_sessionPoolStruct; + +g_sessionPoolStruct g_client_session[CONCURRENT_MAX] = {{NULL, 0}}; + typedef struct SocketMsgHandlerParam { - int client_socket_fd; + int socket_fd; SSL_CTX *ssl_server_ctx; SSL *ssl_session; uint8_t *domainkey; } SocketMsgHandlerParam; -void log_printf(uint32_t log_level, const char* filename, uint32_t line, const char *fmt, ...) +void log_printf(uint32_t log_level, const char *filename, uint32_t line, const char *fmt, ...) { char buf[BUFSIZ] = {'\0'}; va_list ap; @@ -72,11 +102,36 @@ void log_printf(uint32_t log_level, const char* filename, uint32_t line, const c ocall_print_string(log_level, buf, filename, line); } +int update_couch_db(int db_type, int key_type) +{ + int db_size; + ocall_load_couchdb(&db_size, db_type); + log_i("db_size=%d", db_size); + + for (int i = 0; i < db_size; i++) + { + int ret; + + uint32_t dk_cipher_len = sgx_calc_sealed_data_size(0, SGX_DOMAIN_KEY_SIZE); + uint8_t dk_cipher[dk_cipher_len] = {0}; + + ocall_update_CMK(&ret, dk_cipher, dk_cipher_len, g_domainkey.dk_hash, DOMAINKEY_HASH_SIZE, key_type); + if (ret == -1) + return -1; + } + return 0; +} + void t_time(time_t *current_t) { ocall_get_current_time((uint64_t *)current_t); } +void t_sleep(int second) +{ + ocall_sleep(second); +} + uint32_t htonl(uint32_t n) { union @@ -177,133 +232,419 @@ int setsockopt( return ret; } -int verify_callback(int preverify_ok, X509_STORE_CTX *ctx); - -static void *SocketMsgHandler(void *arg) +int create_listener_socket(int port, int &server_socket) { - if (arg == NULL) - { - log_d(TLS_SERVER - "arg cannot be obtained\n"); - return ((void *)0); - } - SSL *ssl_session = nullptr; - SocketMsgHandlerParam handler_ctx = *(SocketMsgHandlerParam *)arg; - int test_error = 1; int ret = -1; - // create a new SSL structure for a connection - if ((ssl_session = SSL_new(handler_ctx.ssl_server_ctx)) == nullptr) + const int reuse = 1; + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + server_socket = socket(AF_INET, SOCK_STREAM, 0); + if (server_socket < 0) { - log_d(TLS_SERVER - "Unable to create a new SSL connection state object\n"); + log_d(TLS_SERVER "socket creation failed\n"); goto exit; } - if (SSL_set_fd(ssl_session, handler_ctx.client_socket_fd) != 1) + if (setsockopt( + server_socket, + SOL_SOCKET, + SO_REUSEADDR, + (const void *)&reuse, + sizeof(reuse)) < 0) { - log_d(TLS_SERVER - "SSL set fd failed\n"); + log_d(TLS_SERVER "setsocket failed \n"); goto exit; } - // wait for a TLS/SSL client to initiate a TLS/SSL handshake - log_i(TLS_SERVER "initiating a passive connect SSL_accept\n"); - test_error = SSL_accept(ssl_session); - if (test_error <= 0) + if (bind(server_socket, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - log_d(TLS_SERVER " SSL handshake failed, error(%d)(%d)\n", - test_error, SSL_get_error(ssl_session, test_error)); + log_d(TLS_SERVER "Unable to bind socket to the port\n"); goto exit; } - log_d(TLS_SERVER "<---- Read from client:\n"); - if (read_from_session_peer( - ssl_session, CLIENT_PAYLOAD, CLIENT_PAYLOAD_SIZE) != 0) + if (listen(server_socket, CLIENT_MAX_NUM) < 0) { - log_d(TLS_SERVER " Read from client failed\n"); + log_d(TLS_SERVER "Unable to open socket for listening\n"); goto exit; } + ret = 0; +exit: + return ret; +} - for (unsigned long int i = 0; i < SGX_DOMAIN_KEY_SIZE; i++) +int verify_callback(int preverify_ok, X509_STORE_CTX *ctx); + +std::mutex mtx; + +static bool RecvAll(SSL *ssl_session, void *data, int32_t data_size) +{ + char *data_ptr = (char *)data; + int32_t bytes_recv; + int error = 0; + + while (true) { - log_d("domain_key[%u]=%2u", i, handler_ctx.domainkey[i]); + // mtx.lock(); + + bytes_recv = SSL_read(ssl_session, data_ptr, data_size); + + // log_i("bytes_recv=%d\n", bytes_recv); + // mtx.unlock(); + + if (bytes_recv <= 0) + { + error = SSL_get_error(ssl_session, bytes_recv); + if (error == SSL_ERROR_WANT_READ) + { + log_i("SSL_ERROR_WANT_READ"); + continue; + } + + // log_d(TLS_SERVER "Failed! SSL_read returned error=%d\n", error); + return false; + } + + // t_sleep(1); + // log_d(TLS_SERVER "%d bytes recv\n", bytes_recv); + return true; } - log_d(TLS_SERVER "<---- Write to client:\n"); - if (write_to_session_peer( - ssl_session, handler_ctx.domainkey, SGX_DOMAIN_KEY_SIZE) != 0) + return true; +} + +static bool SendAll(SSL *ssl_session, const void *data, int32_t data_size) +{ + // mtx.lock(); + + const char *data_ptr = (const char *)data; + int32_t bytes_sent; + int error = 0; + + while (true) { - log_d(TLS_SERVER " Write to client failed\n"); - goto exit; + bytes_sent = SSL_write(ssl_session, data_ptr, data_size); + + if (bytes_sent <= 0) + { + error = SSL_get_error(ssl_session, bytes_sent); + if (error == SSL_ERROR_WANT_WRITE) + { + continue; + } + else + { + // mtx.unlock(); + + return false; + } + } + else + { + break; + } } - if (handler_ctx.client_socket_fd > 0) + + // mtx.unlock(); + + return true; +} + +int SocketDispatchCmd(_request_header_t *req, SSL *ssl_session) +{ + int bytes_written = 0; + _response_header_t *server_res = NULL; + + if (req->password != g_password && req->cmd != GET_DOMAINKEY && g_password != 0) { - ocall_close(&ret, handler_ctx.client_socket_fd); - if (ret != 0) + if (!SendAll(ssl_session, PASSWORD_WRONG, PASSWORD_WRONG_SIZE)) { - log_d(TLS_SERVER "OCALL: error closing client socket before starting a new TLS session.\n"); - goto exit; + log_d("failed to send PASSWORD_WRONG datas\n"); } + return -1; + } + + switch (req->cmd) + { + case GET_DOMAINKEY: + { + log_d(TLS_SERVER "<---- Write domainkey to client:\n"); + + server_res = (_response_header_t *)malloc(sizeof(_response_header_t)); + memcpy_s(server_res->domainKey, SGX_DOMAIN_KEY_SIZE, g_domainkey.domainkey, SGX_DOMAIN_KEY_SIZE); + server_res->type = MSG_DOMAINKEY; + + if (!SendAll(ssl_session, server_res, sizeof(_response_header_t))) + { + log_d("failed to send domainkey data\n"); + SAFE_FREE(server_res); + return -1; + } + SAFE_FREE(server_res); + return 0; + } + case STOP_AUTO_ROTATION: + { + g_period = -1; + g_nextRotationTime = g_period; + if (!SendAll(ssl_session, STOP_AUTO_ROTATION_MSG, STOP_AUTO_ROTATION_MSG_SIZE)) + { + log_d("failed to send STOP_AUTO_ROTATION_MSG datas\n"); + return -1; + } + return 0; + } + case START_ROTATION: + { + log_i("START_ROTATION!"); + t_time((time_t *)&g_nextRotationTime); + if (!SendAll(ssl_session, START_ROTATION_MSG, START_ROTATION_MSG_SIZE)) + { + log_d("failed to send START_ROTATION_MSG datas\n"); + return -1; + } + return 0; + } + case SET_PERIOD: + { + std::string set_period_msg; + uint32_t set_period_msg_size = 0; + if (req->period <= 30 || req->period >= 365) + { + log_d("the period must greater than 30 days and less than 365 days"); + set_period_msg = SET_PERIOD_FAILED_MSG; + set_period_msg_size = SET_PERIOD_FAILED_MSG_SIZE; + } + else + { + g_period = req->period; + g_nextRotationTime = g_period * 24 * 60 * 60 + g_domainkey.createTime; + set_period_msg = SET_PERIOD_SUCCESS_MSG; + set_period_msg_size = SET_PERIOD_SUCCESS_MSG_SIZE; + } + if (!SendAll(ssl_session, set_period_msg.c_str(), set_period_msg_size)) + { + log_d("failed to send SET_PERIOD_MSG datas\n"); + return -1; + } + return 0; + } + case GET_PERIOD: + { + std::string get_period_msg = "The period time is " + std::to_string(g_period) + " days."; + if (!SendAll(ssl_session, get_period_msg.c_str(), get_period_msg.size() + 1)) + { + log_d("failed to send get_period_msg datas\n"); + return -1; + } + return 0; + } + case GET_NEXT_ROTATION_DATETIME: + { + std::string get_next_rotation_datetime_msg = "The next retation time is " + + std::to_string(g_nextRotationTime) + "."; + if (!SendAll(ssl_session, get_next_rotation_datetime_msg.c_str(), get_next_rotation_datetime_msg.size() + 1)) + { + log_d("failed to send get_next_rotation_datetime_msg datas\n"); + return -1; + } + return 0; + } + case UPDATE_CMK: + { + uint32_t dk_cipher_len = sgx_calc_sealed_data_size(0, SGX_DOMAIN_KEY_SIZE); + uint8_t dk_cipher[dk_cipher_len] = {0}; + std::string update_cmk_msg; + + int ret; + ret = update_couch_db(CMK_INFO, KEYBLOB); + if (ret == -1) + { + update_cmk_msg = "Update CMK failed"; + } + + ret = update_couch_db(USER_INFO, CMK); + if (ret == -1) + { + update_cmk_msg = "Update CMK failed"; + } + + ret = update_couch_db(USER_INFO, SM_DEFAULT_CMK); + if (ret == -1) + { + update_cmk_msg = "Update CMK failed"; + } + + if (ret == 1) + { + update_cmk_msg = "Update CMK done"; + log_i("UPDATE_CMK ok"); + } + + if (!SendAll(ssl_session, update_cmk_msg.c_str(), strlen(update_cmk_msg.c_str()))) + log_e("failed to send update_cmk_msg datas\n"); + } + default: + return -1; } -exit: - SSL_free(ssl_session); - log_i("write domainkey to clent success\n"); - return ((void *)0); } -int create_listener_socket(int port, int &server_socket) +static void *Server_heart(void *args) { - int ret = -1; - const int reuse = 1; - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - addr.sin_addr.s_addr = htonl(INADDR_ANY); + (void)args; + log_d("in Server_heart"); - server_socket = socket(AF_INET, SOCK_STREAM, 0); - if (server_socket < 0) + int bytes_written = 0; + int test_error = 0; + + _response_header_t *server_res = NULL; + server_res = (_response_header_t *)malloc(sizeof(_response_header_t)); + server_res->type = MSG_HEARTBEAT; + + while (true) { - log_d(TLS_SERVER "socket creation failed\n"); - goto exit; + t_sleep(10); + for (int i = 0; i < CONCURRENT_MAX; i++) + { + if (g_client_session[i].ssl_session != NULL) + { + if (g_client_session[i].errorCnt < MAX_RECONNECT) + { + log_i("Server->client heart start index = %d", i); + int r = SendAll(g_client_session[i].ssl_session, server_res, sizeof(_response_header_t)); + if (!r) + g_client_session[i].errorCnt++; + else + g_client_session[i].errorCnt = 0; + continue; + } + + int client_fd = SSL_get_fd(g_client_session[i].ssl_session); + if (client_fd > 0) + { + int closeRet; + ocall_close(&closeRet, client_fd); + } + + SSL_shutdown(g_client_session[i].ssl_session); + SSL_free(g_client_session[i].ssl_session); + + g_client_session[i].ssl_session = nullptr; + g_client_session[i].errorCnt = 0; + + log_i("remove session index=%d", i); + } + } } +} - if (setsockopt( - server_socket, - SOL_SOCKET, - SO_REUSEADDR, - (const void *)&reuse, - sizeof(reuse)) < 0) +static void *SocketMsgHandler(void *arg) +{ + if (arg == NULL) { - log_d(TLS_SERVER "setsocket failed \n"); + log_d(TLS_SERVER + "arg cannot be obtained\n"); + return ((void *)0); + } + + int index = -1; + _request_header_t *client_req = NULL; + client_req = (_request_header_t *)malloc(sizeof(_request_header_t)); + + // add new connection to connection pool if it's not full + for (int i = 0; i < CONCURRENT_MAX; i++) + { + if (g_client_session[i].ssl_session == NULL) + { + index = i; + log_i("create session index=%d", index); + break; + } + } + if (index < 0) + { + log_d(TLS_SERVER "The connection pool was full.\n"); + return ((void *)0); + } + + SocketMsgHandlerParam handler_ctx = *(SocketMsgHandlerParam *)arg; + int test_error = 1; + + // create a new SSL structure for a connection + if ((g_client_session[index].ssl_session = SSL_new(handler_ctx.ssl_server_ctx)) == nullptr) + { + log_d(TLS_SERVER + "Unable to create a new SSL connection state object\n"); goto exit; } - if (bind(server_socket, (struct sockaddr *)&addr, sizeof(addr)) < 0) + if (SSL_set_fd(g_client_session[index].ssl_session, handler_ctx.socket_fd) != 1) { - log_d(TLS_SERVER "Unable to bind socket to the port\n"); + log_d(TLS_SERVER + "SSL set fd failed\n"); goto exit; } - if (listen(server_socket, CLIENT_MAX_NUM) < 0) + // wait for a TLS/SSL client to initiate a TLS/SSL handshake + log_i(TLS_SERVER "initiating a passive connect SSL_accept\n"); + test_error = SSL_accept(g_client_session[index].ssl_session); + if (test_error <= 0) { - log_d(TLS_SERVER "Unable to open socket for listening\n"); + log_d(TLS_SERVER " SSL handshake failed, error(%d)(%d)\n", + test_error, SSL_get_error(g_client_session[index].ssl_session, test_error)); goto exit; } - ret = 0; + + while (true) + { + if (g_client_session[index].ssl_session == NULL) + continue; + + // log_d(TLS_SERVER "<---- Read cmd from client:\n"); + + memset(client_req, 0, sizeof(client_req)); + + if (!RecvAll(g_client_session[index].ssl_session, client_req, sizeof(_request_header_t))) + { + // log_d("failed to get res data\n"); + t_sleep(1); + continue; + } + + // log_i("\ntype=%d\n", client_req->cmd); + + test_error = SocketDispatchCmd(client_req, g_client_session[index].ssl_session); + // t_sleep(20); + if (test_error < 0) + { + // log_d("parse cmd failed\n"); + } + } + exit: - return ret; + if (client_req) + SAFE_FREE(client_req); + return ((void *)0); } int handle_communication_until_done( int &server_socket_fd, int &client_socket_fd, - SSL_CTX *&ssl_server_ctx, - uint8_t *domainkey) + SSL_CTX *&ssl_server_ctx) { int ret = -1; // waiting_for_connection_request: struct sockaddr_in addr; uint len = sizeof(addr); + pthread_t heart_thread; + + if (pthread_create(&heart_thread, NULL, Server_heart, NULL) < 0) + { + log_d("could not create thread\n"); + goto exit; + } // reset ssl_session and client_socket_fd to prepare for the new TLS // connection @@ -314,6 +655,16 @@ int handle_communication_until_done( client_socket_fd = accept(server_socket_fd, (struct sockaddr *)&addr, &len); + if (g_ready_flag == false) + { + if (client_socket_fd != -1) + { + int closeRet; + ocall_close(&closeRet, client_socket_fd); + } + goto waiting_for_connection_request; + } + if (client_socket_fd < 0) { log_d(TLS_SERVER "Unable to accept the client request\n"); @@ -321,9 +672,8 @@ int handle_communication_until_done( } SocketMsgHandlerParam param; - param.client_socket_fd = client_socket_fd; + param.socket_fd = client_socket_fd; param.ssl_server_ctx = ssl_server_ctx; - param.domainkey = domainkey; pthread_t sniffer_thread; if (pthread_create(&sniffer_thread, NULL, SocketMsgHandler, (void *)¶m) < 0) @@ -375,7 +725,6 @@ int create_socket(const char *server_name, uint16_t server_port) goto out; } - dest_sock.sin_family = AF_INET; dest_sock.sin_port = htons(server_port); dest_sock.sin_addr.s_addr = inet_addr(server_name); @@ -402,28 +751,36 @@ int create_socket(const char *server_name, uint16_t server_port) return sockfd; } -sgx_status_t get_domainkey_from_target(uint8_t *domain_key, - const char *target_server_name, - uint16_t target_server_port) +sgx_status_t store_domain_key(uint8_t *domain_key) { sgx_status_t ret = SGX_ERROR_UNEXPECTED; + uint32_t dk_cipher_len = sgx_calc_sealed_data_size(0, SGX_DOMAIN_KEY_SIZE); + uint8_t dk_cipher[dk_cipher_len] = {0}; + time_t current_time; + int retstatus; - if (target_server_name[0] == '\0') - return ret; + SHA256(domain_key, SGX_DOMAIN_KEY_SIZE, g_domainkey.dk_hash); - SSL_CTX *ssl_client_ctx = nullptr; - SSL *ssl_session = nullptr; + ret = sgx_seal_data(0, NULL, SGX_DOMAIN_KEY_SIZE, domain_key, dk_cipher_len, (sgx_sealed_data_t *)dk_cipher); + if (ret != SGX_SUCCESS) + return SGX_ERROR_UNEXPECTED; + + ret = ocall_store_domain_key(&retstatus, dk_cipher, dk_cipher_len, g_domainkey.dk_hash, DOMAINKEY_HASH_SIZE); + if (ret != SGX_SUCCESS || retstatus != 0) + return SGX_ERROR_UNEXPECTED; + + t_time((time_t *)&g_domainkey.createTime); + + return ret; +} +int generate_ssl_session() +{ + SSL_CTX *ssl_client_ctx = nullptr; X509 *cert = nullptr; EVP_PKEY *pkey = nullptr; SSL_CONF_CTX *ssl_confctx = SSL_CONF_CTX_new(); - - int client_socket = -1; - int error = 0; - unsigned char buf[200]; - int len = 0; - int bytes_written = 0; - int bytes_read = 0; + int ret = -1; if ((ssl_client_ctx = SSL_CTX_new(TLS_client_method())) == nullptr) { @@ -446,15 +803,69 @@ sgx_status_t get_domainkey_from_target(uint8_t *domain_key, goto out; } - if ((ssl_session = SSL_new(ssl_client_ctx)) == nullptr) + if ((g_ssl_session = SSL_new(ssl_client_ctx)) == nullptr) + { + log_d(TLS_SERVER "Unable to create a new SSL connection state object\n"); + goto out; + } + + ret = 0; + +out: + if (cert) + X509_free(cert); + + if (pkey) + EVP_PKEY_free(pkey); + + if (ssl_client_ctx) + SSL_CTX_free(ssl_client_ctx); + + if (ssl_confctx) + SSL_CONF_CTX_free(ssl_confctx); + + return ret; +} + +static int req_domainkey(SSL *ssl_session) +{ + if (ssl_session == NULL) + return -1; + + _request_header_t *client_req = NULL; + + log_d(TLS_SERVER "-----> Write getdomainkey cmd to server:\n"); + + client_req = (_request_header_t *)malloc(sizeof(_request_header_t)); + client_req->cmd = GET_DOMAINKEY; + + if (!SendAll(ssl_session, client_req, sizeof(_request_header_t))) + { + log_d("failed to send req data\n"); + SAFE_FREE(client_req); + return -1; + } + + SAFE_FREE(client_req); + return 0; +} + +static int connect_target_server(const char *target_server_name, + uint16_t target_server_port) +{ + int error = 0; + int ret = SGX_ERROR_UNEXPECTED; + + if (generate_ssl_session() < 0) { - log_e(TLS_SERVER "Unable to create a new SSL connection state object\n"); + log_d(TLS_SERVER "generate ssl session in get_domainkey_from_target failed\n"); goto out; } log_d(TLS_SERVER "New ssl connection getting created\n"); - client_socket = create_socket(target_server_name, target_server_port); - if (client_socket == -1) + g_socket_fd = create_socket(target_server_name, target_server_port); + + if (g_socket_fd == -1) { log_e( TLS_SERVER "Create a socket and initiate a TCP connect to target server: %s:%d " @@ -466,116 +877,289 @@ sgx_status_t get_domainkey_from_target(uint8_t *domain_key, } // set up ssl socket and initiate TLS connection with TLS target server - if (SSL_set_fd(ssl_session, client_socket) != 1) + if (SSL_set_fd(g_ssl_session, g_socket_fd) != 1) { log_e(TLS_SERVER "Ssl set fd error.\n"); goto out; } - if ((error = SSL_connect(ssl_session)) != 1) + if ((error = SSL_connect(g_ssl_session)) != 1) { log_e(TLS_SERVER "Error: Could not establish a TLS session ret2=%d " "SSL_get_error()=%d\n", error, - SSL_get_error(ssl_session, error)); + SSL_get_error(g_ssl_session, error)); goto out; } + log_d(TLS_SERVER "successfully established TLS channel:%s\n", - SSL_get_version(ssl_session)); + SSL_get_version(g_ssl_session)); - // start the communication - // Write an GET request to the target server - log_d(TLS_SERVER "-----> Write to server:\n"); - len = snprintf((char *)buf, sizeof(buf) - 1, CLIENT_PAYLOAD); + ret = SGX_SUCCESS; +out: + if (ret != SGX_SUCCESS) + g_ready_flag = false; + log_i("ready flag change to %s\n", g_ready_flag == true ? "true" : "false"); + return ret; +} - while ((bytes_written = SSL_write(ssl_session, buf, (size_t)len)) <= 0) - { - error = SSL_get_error(ssl_session, bytes_written); - if (error == SSL_ERROR_WANT_WRITE) - continue; - log_e(TLS_SERVER "Failed! SSL_write returned %d\n", error); - goto out; - } +static void *ClientSocketMsgHandler(void *args) +{ + (void)args; - log_d(TLS_SERVER "%d bytes written\n", bytes_written); + _response_header_t *server_res = NULL; + server_res = (_response_header_t *)malloc(sizeof(_response_header_t)); - // Read the HTTP response from target server - log_d(TLS_SERVER "<---- Read from server:\n"); - do + while (true) { - len = sizeof(buf) - 1; - memset_s(buf, sizeof(buf), 0, sizeof(buf)); - bytes_read = SSL_read(ssl_session, buf, (size_t)len); + if (g_ssl_session == nullptr || g_socket_fd == -1) + { + t_sleep(1); + continue; + } + + memset(server_res, 0, sizeof(_response_header_t)); - if (bytes_read <= 0) + if (!RecvAll(g_ssl_session, server_res, sizeof(_response_header_t))) { - int error = SSL_get_error(ssl_session, bytes_read); - if (error == SSL_ERROR_WANT_READ) - continue; + log_d("failed to get res data\n"); - log_e(TLS_SERVER "Failed! SSL_read returned error=%d\n", error); - goto out; + continue; } - log_d(TLS_SERVER " %d bytes read\n", bytes_read); + log_i("server_res->type=%d\n", server_res->type); - if (bytes_read != SGX_DOMAIN_KEY_SIZE) + switch (server_res->type) { - log_e( - TLS_SERVER "ERROR: expected reading %lu bytes but only " - "received %d bytes\n", - SGX_DOMAIN_KEY_SIZE, - bytes_read); - goto out; + case MSG_DOMAINKEY: + log_i("in masg_domainkey type"); + memcpy_s(g_domainkey.domainkey, SGX_DOMAIN_KEY_SIZE, server_res->domainKey, SGX_DOMAIN_KEY_SIZE); + + for (unsigned long int i = 0; i < SGX_DOMAIN_KEY_SIZE; i++) + { + log_d("new domain_key from root[%u]=%2u\n", i, g_domainkey.domainkey[i]); + } + + if (store_domain_key(g_domainkey.domainkey) != SGX_SUCCESS) + { + log_d("store_domain_key failed\n"); + // SAFE_FREE(server_res); + continue; + } + + log_d(TLS_SERVER "new domainkey received succeed:\n"); + + //=========== + memset(server_res, 0, sizeof(_response_header_t)); + server_res->type = MSG_ROTATE_END; + + g_ready_flag = true; + log_i("ready flag change to %s\n", g_ready_flag == true ? "true" : "false"); + + for (int i = 0; i < CONCURRENT_MAX; i++) + { + if (g_client_session[i].ssl_session != NULL) + { + if (!SendAll(g_client_session[i].ssl_session, + server_res, + sizeof(_response_header_t))) + { + log_d("failed to send ROTATION_START datas\n"); + continue; + } + } + } + break; + + case MSG_ROTATE_START: + g_ready_flag = false; + log_i("ready flag change to %s\n", g_ready_flag == true ? "true" : "false"); + + for (int i = 0; i < CONCURRENT_MAX; i++) + { + if (g_client_session[i].ssl_session != NULL) + { + if (!SendAll(g_client_session[i].ssl_session, + server_res, + sizeof(_response_header_t))) + { + log_d("failed to send ROTATION_START datas\n"); + continue; + } + } + } + break; + + case MSG_ROTATE_END: + if (req_domainkey(g_ssl_session) < 0) + { + log_d("send get_domainkey cmd failed\n"); + } + + break; + + default: + break; } - else + + if (strncmp(g_server_role.c_str(), ROLE_ROOT, strlen(g_server_role.c_str())) == 0 && + server_res->type == MSG_DOMAINKEY) { - memcpy_s(domain_key, SGX_DOMAIN_KEY_SIZE, buf, SGX_DOMAIN_KEY_SIZE); - memset_s(buf, SGX_DOMAIN_KEY_SIZE, 0, SGX_DOMAIN_KEY_SIZE); - log_i(TLS_SERVER "domainkey received succeed:\n"); + if (g_socket_fd != -1) + { + int closeRet; + ocall_close(&closeRet, g_socket_fd); + } + + if (g_ssl_session) + { + SSL_shutdown(g_ssl_session); + SSL_free(g_ssl_session); + } break; } - } while (1); + } + + SAFE_FREE(server_res); - for (unsigned long int i = 0; i < SGX_DOMAIN_KEY_SIZE; i++) + return ((void *)0); +} + +static void *Client_Heart(void *args) +{ + (void)args; + + int len = 0; + int error = 0; + int bytes_written = 0; + int numberOfErrors = 0; + + _response_header_t *server_res = NULL; + server_res = (_response_header_t *)malloc(sizeof(_response_header_t)); + server_res->type = MSG_HEARTBEAT; + + // t_sleep(10); + + while (true) { - log_d("domain_key[%u]=%2u", i, domain_key[i]); + if (g_ssl_session == nullptr || g_socket_fd == -1) + { + t_sleep(1); + log_i("2"); + continue; + } + + while (numberOfErrors <= MAX_RECONNECT) + { + log_i("client->server heart start"); + if (!SendAll(g_ssl_session, server_res, sizeof(_response_header_t))) + numberOfErrors++; + else + numberOfErrors = 0; + log_i("client->server heart end"); + t_sleep(10); + } + while (true) + { + // Empty g_socket_fd and g_ssl_session + if (g_socket_fd != -1) + { + int closeRet; + ocall_close(&closeRet, g_socket_fd); + if (closeRet != 0) + { + log_d(TLS_CLIENT "OCALL: error close socket\n"); + continue; + } + } + if (g_ssl_session) + { + SSL_shutdown(g_ssl_session); + SSL_free(g_ssl_session); + } + // Reconnect to the target server + if (connect_target_server(g_server_name.c_str(), g_server_port) < 0) + { + log_d(TLS_SERVER "connect to target failed\n"); + continue; + } + numberOfErrors = 0; + if (req_domainkey(g_ssl_session) < 0) + { + log_d("send get_domainkey cmd failed\n"); + continue; + } + break; + } } + return ((void *)0); +} - ret = SGX_SUCCESS; +sgx_status_t get_domainkey_from_target(const char *target_server_name, + uint16_t target_server_port) +{ + sgx_status_t ret = SGX_ERROR_UNEXPECTED; -out: + if (target_server_name[0] == '\0') + return ret; + + pthread_t sniffer_thread; + pthread_t heart_thread; + + _response_header_t *server_res = NULL; - if (client_socket != -1) + if (connect_target_server(target_server_name, target_server_port) < 0) { - int closeRet; - ocall_close(&closeRet, client_socket); - if (closeRet != 0) + log_d(TLS_SERVER "connect to target failed\n"); + goto out; + } + + // start heart thread + if (strncmp(g_server_role.c_str(), ROLE_WORKER, strlen(g_server_role.c_str())) == 0) + { + log_d("create Client_Heart thread\n"); + if (pthread_create(&heart_thread, NULL, Client_Heart, NULL) < 0) { - log_e(TLS_CLIENT "OCALL: error close socket\n"); - ret = SGX_ERROR_UNEXPECTED; + log_d("could not create Client_Heart thread\n"); + goto out; } } - if (ssl_session) + // start the communication + // Read the HTTP response from target server + if (pthread_create(&sniffer_thread, NULL, ClientSocketMsgHandler, NULL) < 0) { - SSL_shutdown(ssl_session); - SSL_free(ssl_session); + log_d("could not create sniffer thread\n"); + goto out; } - if (cert) - X509_free(cert); + // Write an GET request to the target server + if (req_domainkey(g_ssl_session) < 0) + { + log_d("send get_domainkey cmd failed\n"); + goto out; + } - if (pkey) - EVP_PKEY_free(pkey); + ret = SGX_SUCCESS; - if (ssl_client_ctx) - SSL_CTX_free(ssl_client_ctx); +out: + log_d(TLS_SERVER "get domain key from target server %s\n", (ret == SGX_SUCCESS) ? "success" : "failed"); + return ret; +} - if (ssl_confctx) - SSL_CONF_CTX_free(ssl_confctx); +sgx_status_t create_new_domainkey(uint8_t *domainkey) +{ + sgx_status_t ret = SGX_ERROR_UNEXPECTED; + + ret = sgx_read_rand(domainkey, SGX_DOMAIN_KEY_SIZE); + if (ret != SGX_SUCCESS) + return ret; + + log_i("start store domain key to disk"); + ret = store_domain_key(domainkey); + if (ret != SGX_SUCCESS) + return ret; - log_i(TLS_SERVER "get domain key from target server %s\n", (ret == SGX_SUCCESS) ? "success" : "failed"); return ret; } @@ -591,7 +1175,12 @@ sgx_status_t get_domainkey_from_local(uint8_t *domain_key) uint8_t dk_cipher[dk_cipher_len] = {0}; uint8_t tmp[SGX_DOMAIN_KEY_SIZE] = {0}; - ret = ocall_read_domain_key(&retstatus, dk_cipher, dk_cipher_len); + ret = ocall_read_domain_key(&retstatus, + dk_cipher, + dk_cipher_len, + &g_domainkey.createTime, + g_domainkey.dk_hash, + DOMAINKEY_HASH_SIZE); if (ret != SGX_SUCCESS) return ret; @@ -607,7 +1196,8 @@ sgx_status_t get_domainkey_from_local(uint8_t *domain_key) else if (retstatus == -2) { log_d("enclave file does not exist.\n"); - ret = sgx_read_rand(tmp, SGX_DOMAIN_KEY_SIZE); + + ret = create_new_domainkey(tmp); if (ret != SGX_SUCCESS) return ret; } @@ -616,67 +1206,209 @@ sgx_status_t get_domainkey_from_local(uint8_t *domain_key) memcpy_s(domain_key, SGX_DOMAIN_KEY_SIZE, tmp, SGX_DOMAIN_KEY_SIZE); memset_s(tmp, SGX_DOMAIN_KEY_SIZE, 0, SGX_DOMAIN_KEY_SIZE); + g_ready_flag = true; + log_i("ready flag change to %s\n", g_ready_flag == true ? "true" : "false"); return ret; } -sgx_status_t store_domain_key(uint8_t *domain_key) +sgx_status_t sgx_get_domainkey(const char *target_server_name, + uint16_t target_server_port) { sgx_status_t ret = SGX_ERROR_UNEXPECTED; - uint32_t dk_cipher_len = sgx_calc_sealed_data_size(0, SGX_DOMAIN_KEY_SIZE); - uint8_t dk_cipher[dk_cipher_len] = {0}; - int retstatus; + int errorNumber = 0; - ret = sgx_seal_data(0, NULL, SGX_DOMAIN_KEY_SIZE, domain_key, dk_cipher_len, (sgx_sealed_data_t *)dk_cipher); - if (ret != SGX_SUCCESS) - return SGX_ERROR_UNEXPECTED; + log_i("start get domain key from target server. \n"); + ret = get_domainkey_from_target(target_server_name, target_server_port); + if (strncmp(g_server_role.c_str(), ROLE_WORKER, strlen(g_server_role.c_str())) == 0 && ret != SGX_SUCCESS) + { + log_e("worker get domain key from target failed. \n"); + return ret; + } - ret = ocall_store_domain_key(&retstatus, dk_cipher, dk_cipher_len); - if (ret != SGX_SUCCESS || retstatus != 0) - return SGX_ERROR_UNEXPECTED; + t_sleep(10); + if (g_ready_flag == false && strncmp(g_server_role.c_str(), ROLE_ROOT, strlen(g_server_role.c_str())) == 0) + { + log_i("start get domain key from disk\n"); + ret = get_domainkey_from_local(g_domainkey.domainkey); + } return ret; } -sgx_status_t sgx_get_domainkey(uint8_t *domain_key, - const char *server_role, - const char *target_server_name, - uint16_t target_server_port) +int ecall_reencrypt_cmk(uint8_t *cipher_dk, + uint32_t cipher_dk_len, + ehsm_keyblob_t *cmk, + size_t cmk_size) { - sgx_status_t ret = SGX_ERROR_UNEXPECTED; - if (domain_key == NULL) - { - log_e("domain key is null. \n"); - return ret; - } + uint8_t *key = NULL; + uint8_t tmp[SGX_DOMAIN_KEY_SIZE] = {0}; + uint32_t keyblob_size = 0; + int ret = SGX_ERROR_UNEXPECTED; - log_i("start get domain key from target server. \n"); - ret = get_domainkey_from_target(domain_key, target_server_name, target_server_port); - if (strncmp(server_role, ROLE_WORKER, strlen(server_role)) == 0 && ret != SGX_SUCCESS) + uint32_t dk_len = sgx_get_encrypt_txt_len((const sgx_sealed_data_t *)cipher_dk); + if (sgx_unseal_data((const sgx_sealed_data_t *)cipher_dk, NULL, 0, tmp, &dk_len) != SGX_SUCCESS) + return SGX_ERROR_UNEXPECTED; + + // if (cmk->metadata.keyspec == EH_AES_GCM_128 || cmk->metadata.keyspec == EH_AES_GCM_192 || + // cmk->metadata.keyspec == EH_AES_GCM_256 || cmk->metadata.keyspec == EH_SM4_CBC || + // cmk->metadata.keyspec == EH_SM4_CTR) + // { + // uint32_t keysize = 0; + // if (!ehsm_get_symmetric_key_size(cmk->metadata.keyspec, keysize)) + // return SGX_ERROR_UNEXPECTED; + + uint32_t key_size = ehsm_get_gcm_ciphertext_size((sgx_aes_gcm_data_ex_t *)cmk->keyblob); + // if (key_size == UINT32_MAX || key_size != keysize) + // { + // log_d("key_size size:%d is not expected: %lu.\n", key_size, keysize); + // return SGX_ERROR_INVALID_PARAMETER; + // } + keyblob_size = key_size; + log_i("keyblob_size=%d", key_size); + // } + // else + // { + // keyblob_size = cmk->keybloblen; + // } + + SHA256(g_domainkey.domainkey, SGX_DOMAIN_KEY_SIZE, cmk->metadata.dk_hashcode); + + key = (uint8_t *)malloc(keyblob_size); + if (key == NULL) + return SGX_ERROR_OUT_OF_MEMORY; + + if (SGX_SUCCESS != ehsm_parse_keyblob(key, (sgx_aes_gcm_data_ex_t *)cmk->keyblob, tmp)) { - log_i("worker get domain key from target failed. \n"); - return ret; + log_d("ehsm_parse_keyblob failed\n"); + goto out; } - if (strncmp(server_role, ROLE_ROOT, strlen(server_role)) == 0 && ret != SGX_SUCCESS) + if (SGX_SUCCESS != ehsm_create_keyblob(key, keyblob_size, (sgx_aes_gcm_data_ex_t *)cmk->keyblob, g_domainkey.domainkey)) { - log_i("start get domain key from disk\n"); - ret = get_domainkey_from_local(domain_key); + log_d("ehsm_create_keyblob failed\n"); + goto out; } + ret = SGX_SUCCESS; - if (ret == SGX_SUCCESS) +out: + if (key) { - log_i("start store domain key to disk\n"); - ret = store_domain_key(domain_key); + memset_s(key, keyblob_size, 0, keyblob_size); + free(key); } return ret; } +static void *rotationTimerListener(void *arg) +{ + log_i("rotation thread start."); + log_i("g_nextRotationTime %d.", g_nextRotationTime); + time_t current_time; + int bytes_written; + int test_error; + int failed_number; + uint32_t dk_cipher_len = sgx_calc_sealed_data_size(0, SGX_DOMAIN_KEY_SIZE); + uint8_t dk_cipher[dk_cipher_len] = {0}; + int ret; + _response_header_t *server_req = (_response_header_t *)malloc(sizeof(_response_header_t)); + + while (true) + { + t_time(¤t_time); + if (current_time >= g_nextRotationTime && g_nextRotationTime > 0) + { + g_ready_flag = false; + log_i("ready flag change to %s\n", g_ready_flag == true ? "true" : "false"); + server_req->type = MSG_ROTATE_START; + for (int i = 0; i < CONCURRENT_MAX; i++) + { + if (g_client_session[i].ssl_session != NULL) + { + if (!SendAll(g_client_session[i].ssl_session, + server_req, + sizeof(_response_header_t))) + { + log_d("failed to send ROTATION_START datas"); + continue; + } + else + { + log_i("send to fd=%d", SSL_get_fd(g_client_session[i].ssl_session)); + } + } + } + log_i("server sleep 60s for client change flag."); + t_sleep(30); + log_i("rotation start."); + + uint8_t new_domainkey[SGX_DOMAIN_KEY_SIZE] = {0}; + if (create_new_domainkey(new_domainkey) != SGX_SUCCESS) + continue; + + memcpy_s(g_domainkey.domainkey, SGX_DOMAIN_KEY_SIZE, new_domainkey, SGX_DOMAIN_KEY_SIZE); + + int ret; + + ret = update_couch_db(CMK_INFO, KEYBLOB); + if (ret == -1) + { + log_i("goto out"); + goto out; + } + + ret = update_couch_db(USER_INFO, CMK); + if (ret == -1) + { + log_i("goto out"); + goto out; + } + + ret = update_couch_db(USER_INFO, SM_DEFAULT_CMK); + if (ret == -1) + { + log_i("goto out"); + goto out; + } + + log_i("UPDATE_CMK ok"); + server_req->type = MSG_ROTATE_END; + g_ready_flag = true; + log_i("ready flag change to %s\n", g_ready_flag == true ? "true" : "false"); + + for (int i = 0; i < CONCURRENT_MAX; i++) + { + if (g_client_session[i].ssl_session != NULL) + { + if (!SendAll(g_client_session[i].ssl_session, + server_req, + sizeof(_response_header_t))) + { + log_d("failed to send ROTATION_END datas\n"); + continue; + } + } + } + + out: + // always + if (g_period == -1) + g_nextRotationTime = -1; + else + g_nextRotationTime = g_domainkey.createTime + g_period * 24 * 60 * 60; + } + t_sleep(5); + } + log_i("rotation thread end."); +} + int sgx_set_up_tls_server(char *server_port, const char *server_role, const char *target_server_name, - uint16_t target_server_port) + uint16_t target_server_port, + size_t root_password, + int root_period) { int ret = -1; int server_socket_fd; @@ -688,7 +1420,18 @@ int sgx_set_up_tls_server(char *server_port, EVP_PKEY *pkey = nullptr; SSL_CONF_CTX *ssl_confctx = SSL_CONF_CTX_new(); SSL_CTX *ssl_server_ctx = nullptr; - uint8_t domain_key[SGX_DOMAIN_KEY_SIZE]; + pthread_t sniffer_thread; + + if (root_period >= 0) + { + log_d("set period to %dd\n", root_period); + g_period = root_period; + } + + g_password = root_password; + g_server_role = server_role; + g_server_name = target_server_name; + g_server_port = target_server_port; if (server_port == NULL) { @@ -723,10 +1466,7 @@ int sgx_set_up_tls_server(char *server_port, } // get domainkey - if (sgx_get_domainkey(domain_key, - server_role, - target_server_name, - target_server_port) != SGX_SUCCESS) + if (sgx_get_domainkey(target_server_name, target_server_port) != SGX_SUCCESS) { log_e("Failed to get domain key.\n"); goto exit; @@ -743,10 +1483,19 @@ int sgx_set_up_tls_server(char *server_port, goto exit; } + if (strncmp(g_server_role.c_str(), ROLE_ROOT, strlen(g_server_role.c_str())) == 0) + { + if (g_period < 0) + g_nextRotationTime = g_period; + else + g_nextRotationTime = g_domainkey.createTime + root_period * 24 * 60 * 60; + pthread_create(&sniffer_thread, NULL, rotationTimerListener, NULL); + } + server_port_number = (unsigned int)atoi(server_port); // convert to char* to int if (create_listener_socket(server_port_number, server_socket_fd) != 0) { - log_e(TLS_SERVER " unable to create listener socket on the server\n "); + log_e(TLS_SERVER "unable to create listener socket on the server\n "); goto exit; } @@ -754,8 +1503,7 @@ int sgx_set_up_tls_server(char *server_port, ret = handle_communication_until_done( server_socket_fd, client_socket_fd, - ssl_server_ctx, - domain_key); + ssl_server_ctx); if (ret != 0) { log_e(TLS_SERVER "server communication error %d\n", ret); @@ -763,19 +1511,6 @@ int sgx_set_up_tls_server(char *server_port, } exit: - int closeRet; - ocall_close(&closeRet, client_socket_fd); // close the socket connections - if (closeRet != 0) - { - log_e(TLS_SERVER "OCALL: error closing client socket\n"); - ret = -1; - } - ocall_close(&closeRet, server_socket_fd); - if (closeRet != 0) - { - log_e(TLS_SERVER "OCALL: error closing server socket\n"); - ret = -1; - } if (ssl_server_ctx) SSL_CTX_free(ssl_server_ctx); if (ssl_confctx) diff --git a/dkeyserver/Enclave/enclave.edl b/dkeyserver/Enclave/enclave.edl index f4807a92..7b8963e5 100644 --- a/dkeyserver/Enclave/enclave.edl +++ b/dkeyserver/Enclave/enclave.edl @@ -34,6 +34,8 @@ enclave { from "sgx_tsgxssl.edl" import *; from "sgx_pthread.edl" import *; from "sgx_ttls.edl" import *; + from "sgx_tstdc.edl" import *; + from "sgx_tswitchless.edl" import *; include "sgx_key_exchange.h" include "sgx_quote.h" @@ -44,26 +46,42 @@ enclave { include "sys/select.h" include "netdb.h" include "poll.h" + include "datatypes.h" - untrusted { + untrusted { void ocall_print_string(uint32_t log_level, [in, string] const char *str, [in, string] const char *filename, uint32_t line); int ocall_close(int fd); void ocall_get_current_time([out] uint64_t *p_current_time); - int ocall_set_dkeyserver_done(); + void ocall_sleep(int second); + + int ocall_select(int fd); + int ocall_send(int fd, [in, size = msg_size]const char* msg, uint32_t msg_size, int flag); - int ocall_read_domain_key([out, size=cipher_dk_len] uint8_t* cipher_dk, uint32_t cipher_dk_len); - int ocall_store_domain_key([in, size=cipher_dk_len] uint8_t* cipher_dk, uint32_t cipher_dk_len); + int ocall_set_dkeyserver_done(); + int ocall_update_CMK([out, size=cipher_dk_len] uint8_t* cipher_dk, uint32_t cipher_dk_len, + [in, size=dk_hash_size] uint8_t* dk_hash, uint32_t dk_hash_size, + int key_type) transition_using_threads; + int ocall_load_couchdb(int db_type); + + int ocall_read_domain_key([out, size=cipher_dk_len] uint8_t* cipher_dk, uint32_t cipher_dk_len, + [out]uint64_t* create_time, + [out, size=dk_hash_size] uint8_t* dk_hash, uint32_t dk_hash_size); + int ocall_store_domain_key([in, size=cipher_dk_len] uint8_t* cipher_dk, uint32_t cipher_dk_len, + [in, size=dk_hash_size] uint8_t* dk_hash, uint32_t dk_hash_size); + int ocall_socket (int domain, int type, int protocol) propagate_errno; int ocall_bind (int fd, [in, size=len] const struct sockaddr *addr, socklen_t len) propagate_errno; int ocall_listen (int fd, int n) propagate_errno; int ocall_accept (int fd, [out, size=addrlen_in] struct sockaddr *addr, socklen_t addrlen_in, [out] socklen_t *addrlen_out) propagate_errno; - int ocall_connect (int fd, [in, size=len] const struct sockaddr *addr, socklen_t len) propagate_errno; + int ocall_connect (int fd, [in, size=len] const struct sockaddr *addr, socklen_t len) propagate_errno; int ocall_setsockopt (int fd, int level, int optname, [in, size=optlen] const void *optval, socklen_t optlen) propagate_errno; }; - + trusted { public int sgx_set_up_tls_server([in, string] char* port, [in, string] const char* server_role, [in, string] const char* server_name, - uint16_t server_port); + uint16_t server_port, size_t root_password, int root_period); + public int ecall_reencrypt_cmk([in, size=cipher_dk_len] uint8_t* cipher_dk, uint32_t cipher_dk_len, + [in, out, size=cmk_size] ehsm_keyblob_t* cmk, size_t cmk_size) transition_using_threads; }; }; diff --git a/dkeyserver/Makefile b/dkeyserver/Makefile index 561bea7d..152a6baf 100644 --- a/dkeyserver/Makefile +++ b/dkeyserver/Makefile @@ -65,12 +65,15 @@ else App_C_Flags += -DNDEBUG -UEDEBUG -UDEBUG endif -App_Cpp_Flags := $(App_C_Flags) -std=c++11 +App_Cpp_Flags := $(App_C_Flags) -std=c++11 -g App_Link_Flags := -L$(SGX_LIBRARY_PATH) -l$(Urts_Library_Name) \ -lpthread \ -lsgx_dcap_quoteverify \ -ldl \ + -ljsoncpp \ + -lcurl \ -llog4cplus \ + -Wl,--whole-archive -lsgx_uswitchless -Wl,--no-whole-archive \ -lsgx_utls -lsgx_dcap_ql -lsgx_dcap_quoteverify -lcrypto\ -lra_ukey_exchange -L$(TOPDIR)/$(OUTLIB_DIR) \ -L$(OPENSSL_LIBRARY_PATH) -l$(SGXSSL_Untrusted_Library_Name) @@ -97,7 +100,8 @@ Enclave_C_Flags := \ -nostdinc -fvisibility=hidden \ -fpie -ffunction-sections \ -fdata-sections \ - $(MITIGATION_CFLAGS) + $(MITIGATION_CFLAGS) \ + -g CC_BELOW_4_9 := $(shell expr "`$(CC) -dumpversion`" \< "4.9") ifeq ($(CC_BELOW_4_9), 1) @@ -121,7 +125,7 @@ Enclave_Security_Link_Flags := -Wl,-z,relro,-z,now,-z,noexecstack Enclave_Link_Flags := $(MITIGATION_LDFLAGS) $(Enclave_Security_Link_Flags) \ $(SgxSSL_Link_Libraries) \ -Wl,--no-undefined -nostdlib -nodefaultlibs -nostartfiles -L$(SGX_TRUSTED_LIBRARY_PATH) \ - -Wl,--whole-archive -l$(Trts_Library_Name) -Wl,--no-whole-archive \ + -Wl,--whole-archive -lsgx_tswitchless -l$(Trts_Library_Name) -Wl,--no-whole-archive \ -Wl,--start-group -lsgx_tstdc -lsgx_tcxx -lsgx_tcrypto -lsgx_dcap_tvl -lsgx_ttls -l$(Service_Library_Name) -lra_tkey_exchange -L$(TOPDIR)/$(OUTLIB_DIR) -Wl,--end-group \ -Wl,-Bstatic -Wl,-Bsymbolic -Wl,--no-undefined \ -Wl,-pie,-eenclave_entry -Wl,--export-dynamic \ @@ -201,7 +205,7 @@ Enclave/%.o: Enclave/%.cpp Enclave/enclave_t.h @echo "CXX <= $<" $(Enclave_Name): Enclave/enclave_t.o $(Enclave_Cpp_Objects) - @$(CXX) $^ -o $@ $(Enclave_Link_Flags) + @$(CXX) $^ -o $@ $(Enclave_Link_Flags) -g @echo "LINK => $@" $(Signed_Enclave_Name): $(Enclave_Name) diff --git a/dkeyserver/dkeyrotation/App/auto_version.h b/dkeyserver/dkeyrotation/App/auto_version.h new file mode 100644 index 00000000..f987679e --- /dev/null +++ b/dkeyserver/dkeyrotation/App/auto_version.h @@ -0,0 +1,6 @@ +#ifndef EHSM_DKEYCACHE_AUTO_VERSION_H +#define EHSM_DKEYCACHE_AUTO_VERSION_H +#define EHSM_VERSION "0.3.2" +#define EHSM_DATE "2023.01.14 0:04" +#define EHSM_GIT_SHA "fd59274" +#endif //EHSM_DKEYCACHE_AUTO_VERSION_H diff --git a/dkeyserver/dkeyrotation/App/enclave_u.c b/dkeyserver/dkeyrotation/App/enclave_u.c new file mode 100644 index 00000000..7108e042 --- /dev/null +++ b/dkeyserver/dkeyrotation/App/enclave_u.c @@ -0,0 +1,443 @@ +#include "enclave_u.h" +#include + +typedef struct ms_enclave_launch_tls_client_t { + int ms_retval; + const char* ms_server_name; + size_t ms_server_name_len; + uint16_t ms_server_port; + uint32_t ms_key; + const char* ms_action; + size_t ms_action_len; +} ms_enclave_launch_tls_client_t; + +typedef struct ms_sgx_ra_get_ga_t { + sgx_status_t ms_retval; + sgx_ra_context_t ms_context; + sgx_ec256_public_t* ms_g_a; +} ms_sgx_ra_get_ga_t; + +typedef struct ms_sgx_ra_proc_msg2_trusted_t { + sgx_status_t ms_retval; + sgx_ra_context_t ms_context; + const sgx_ra_msg2_t* ms_p_msg2; + const sgx_target_info_t* ms_p_qe_target; + sgx_report_t* ms_p_report; + sgx_quote_nonce_t* ms_p_nonce; +} ms_sgx_ra_proc_msg2_trusted_t; + +typedef struct ms_sgx_ra_get_msg3_trusted_t { + sgx_status_t ms_retval; + sgx_ra_context_t ms_context; + uint32_t ms_quote_size; + sgx_report_t* ms_qe_report; + sgx_ra_msg3_t* ms_p_msg3; + uint32_t ms_msg3_size; +} ms_sgx_ra_get_msg3_trusted_t; + +typedef struct ms_ocall_printf_t { + const char* ms_str; +} ms_ocall_printf_t; + +typedef struct ms_ocall_close_t { + int ms_retval; + int ms_fd; +} ms_ocall_close_t; + +typedef struct ms_ocall_sleep_t { + int ms_sec; +} ms_ocall_sleep_t; + +typedef struct ms_ocall_get_current_time_t { + uint64_t* ms_p_current_time; +} ms_ocall_get_current_time_t; + +typedef struct ms_ocall_socket_t { + int ms_retval; + int ocall_errno; + int ms_domain; + int ms_type; + int ms_protocol; +} ms_ocall_socket_t; + +typedef struct ms_ocall_connect_t { + int ms_retval; + int ocall_errno; + int ms_fd; + const struct sockaddr* ms_addr; + socklen_t ms_len; +} ms_ocall_connect_t; + +typedef struct ms_sgx_oc_cpuidex_t { + int* ms_cpuinfo; + int ms_leaf; + int ms_subleaf; +} ms_sgx_oc_cpuidex_t; + +typedef struct ms_sgx_thread_wait_untrusted_event_ocall_t { + int ms_retval; + const void* ms_self; +} ms_sgx_thread_wait_untrusted_event_ocall_t; + +typedef struct ms_sgx_thread_set_untrusted_event_ocall_t { + int ms_retval; + const void* ms_waiter; +} ms_sgx_thread_set_untrusted_event_ocall_t; + +typedef struct ms_sgx_thread_setwait_untrusted_events_ocall_t { + int ms_retval; + const void* ms_waiter; + const void* ms_self; +} ms_sgx_thread_setwait_untrusted_events_ocall_t; + +typedef struct ms_sgx_thread_set_multiple_untrusted_events_ocall_t { + int ms_retval; + const void** ms_waiters; + size_t ms_total; +} ms_sgx_thread_set_multiple_untrusted_events_ocall_t; + +typedef struct ms_u_sgxssl_ftime_t { + void* ms_timeptr; + uint32_t ms_timeb_len; +} ms_u_sgxssl_ftime_t; + +typedef struct ms_u_sgxssl_write_t { + size_t ms_retval; + int ms_fd; + const void* ms_buf; + size_t ms_n; +} ms_u_sgxssl_write_t; + +typedef struct ms_u_sgxssl_read_t { + size_t ms_retval; + int ms_fd; + void* ms_buf; + size_t ms_count; +} ms_u_sgxssl_read_t; + +typedef struct ms_u_sgxssl_close_t { + int ms_retval; + int ms_fd; +} ms_u_sgxssl_close_t; + +typedef struct ms_sgx_tls_get_qe_target_info_ocall_t { + quote3_error_t ms_retval; + sgx_target_info_t* ms_p_target_info; + size_t ms_target_info_size; +} ms_sgx_tls_get_qe_target_info_ocall_t; + +typedef struct ms_sgx_tls_get_quote_size_ocall_t { + quote3_error_t ms_retval; + uint32_t* ms_p_quote_size; +} ms_sgx_tls_get_quote_size_ocall_t; + +typedef struct ms_sgx_tls_get_quote_ocall_t { + quote3_error_t ms_retval; + sgx_report_t* ms_p_report; + size_t ms_report_size; + uint8_t* ms_p_quote; + uint32_t ms_quote_size; +} ms_sgx_tls_get_quote_ocall_t; + +typedef struct ms_sgx_tls_get_supplemental_data_size_ocall_t { + quote3_error_t ms_retval; + uint32_t* ms_p_supplemental_data_size; +} ms_sgx_tls_get_supplemental_data_size_ocall_t; + +typedef struct ms_sgx_tls_verify_quote_ocall_t { + quote3_error_t ms_retval; + const uint8_t* ms_p_quote; + uint32_t ms_quote_size; + time_t ms_expiration_check_date; + sgx_ql_qv_result_t* ms_p_quote_verification_result; + sgx_ql_qe_report_info_t* ms_p_qve_report_info; + size_t ms_qve_report_info_size; + uint8_t* ms_p_supplemental_data; + uint32_t ms_supplemental_data_size; +} ms_sgx_tls_verify_quote_ocall_t; + +typedef struct ms_pthread_wait_timeout_ocall_t { + int ms_retval; + unsigned long long ms_waiter; + unsigned long long ms_timeout; +} ms_pthread_wait_timeout_ocall_t; + +typedef struct ms_pthread_create_ocall_t { + int ms_retval; + unsigned long long ms_self; +} ms_pthread_create_ocall_t; + +typedef struct ms_pthread_wakeup_ocall_t { + int ms_retval; + unsigned long long ms_waiter; +} ms_pthread_wakeup_ocall_t; + +static sgx_status_t SGX_CDECL enclave_ocall_printf(void* pms) +{ + ms_ocall_printf_t* ms = SGX_CAST(ms_ocall_printf_t*, pms); + ocall_printf(ms->ms_str); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_ocall_close(void* pms) +{ + ms_ocall_close_t* ms = SGX_CAST(ms_ocall_close_t*, pms); + ms->ms_retval = ocall_close(ms->ms_fd); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_ocall_sleep(void* pms) +{ + ms_ocall_sleep_t* ms = SGX_CAST(ms_ocall_sleep_t*, pms); + ocall_sleep(ms->ms_sec); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_ocall_get_current_time(void* pms) +{ + ms_ocall_get_current_time_t* ms = SGX_CAST(ms_ocall_get_current_time_t*, pms); + ocall_get_current_time(ms->ms_p_current_time); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_ocall_socket(void* pms) +{ + ms_ocall_socket_t* ms = SGX_CAST(ms_ocall_socket_t*, pms); + ms->ms_retval = ocall_socket(ms->ms_domain, ms->ms_type, ms->ms_protocol); + ms->ocall_errno = errno; + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_ocall_connect(void* pms) +{ + ms_ocall_connect_t* ms = SGX_CAST(ms_ocall_connect_t*, pms); + ms->ms_retval = ocall_connect(ms->ms_fd, ms->ms_addr, ms->ms_len); + ms->ocall_errno = errno; + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_sgx_oc_cpuidex(void* pms) +{ + ms_sgx_oc_cpuidex_t* ms = SGX_CAST(ms_sgx_oc_cpuidex_t*, pms); + sgx_oc_cpuidex(ms->ms_cpuinfo, ms->ms_leaf, ms->ms_subleaf); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_sgx_thread_wait_untrusted_event_ocall(void* pms) +{ + ms_sgx_thread_wait_untrusted_event_ocall_t* ms = SGX_CAST(ms_sgx_thread_wait_untrusted_event_ocall_t*, pms); + ms->ms_retval = sgx_thread_wait_untrusted_event_ocall(ms->ms_self); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_sgx_thread_set_untrusted_event_ocall(void* pms) +{ + ms_sgx_thread_set_untrusted_event_ocall_t* ms = SGX_CAST(ms_sgx_thread_set_untrusted_event_ocall_t*, pms); + ms->ms_retval = sgx_thread_set_untrusted_event_ocall(ms->ms_waiter); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_sgx_thread_setwait_untrusted_events_ocall(void* pms) +{ + ms_sgx_thread_setwait_untrusted_events_ocall_t* ms = SGX_CAST(ms_sgx_thread_setwait_untrusted_events_ocall_t*, pms); + ms->ms_retval = sgx_thread_setwait_untrusted_events_ocall(ms->ms_waiter, ms->ms_self); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_sgx_thread_set_multiple_untrusted_events_ocall(void* pms) +{ + ms_sgx_thread_set_multiple_untrusted_events_ocall_t* ms = SGX_CAST(ms_sgx_thread_set_multiple_untrusted_events_ocall_t*, pms); + ms->ms_retval = sgx_thread_set_multiple_untrusted_events_ocall(ms->ms_waiters, ms->ms_total); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_u_sgxssl_ftime(void* pms) +{ + ms_u_sgxssl_ftime_t* ms = SGX_CAST(ms_u_sgxssl_ftime_t*, pms); + u_sgxssl_ftime(ms->ms_timeptr, ms->ms_timeb_len); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_u_sgxssl_write(void* pms) +{ + ms_u_sgxssl_write_t* ms = SGX_CAST(ms_u_sgxssl_write_t*, pms); + ms->ms_retval = u_sgxssl_write(ms->ms_fd, ms->ms_buf, ms->ms_n); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_u_sgxssl_read(void* pms) +{ + ms_u_sgxssl_read_t* ms = SGX_CAST(ms_u_sgxssl_read_t*, pms); + ms->ms_retval = u_sgxssl_read(ms->ms_fd, ms->ms_buf, ms->ms_count); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_u_sgxssl_close(void* pms) +{ + ms_u_sgxssl_close_t* ms = SGX_CAST(ms_u_sgxssl_close_t*, pms); + ms->ms_retval = u_sgxssl_close(ms->ms_fd); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_sgx_tls_get_qe_target_info_ocall(void* pms) +{ + ms_sgx_tls_get_qe_target_info_ocall_t* ms = SGX_CAST(ms_sgx_tls_get_qe_target_info_ocall_t*, pms); + ms->ms_retval = sgx_tls_get_qe_target_info_ocall(ms->ms_p_target_info, ms->ms_target_info_size); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_sgx_tls_get_quote_size_ocall(void* pms) +{ + ms_sgx_tls_get_quote_size_ocall_t* ms = SGX_CAST(ms_sgx_tls_get_quote_size_ocall_t*, pms); + ms->ms_retval = sgx_tls_get_quote_size_ocall(ms->ms_p_quote_size); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_sgx_tls_get_quote_ocall(void* pms) +{ + ms_sgx_tls_get_quote_ocall_t* ms = SGX_CAST(ms_sgx_tls_get_quote_ocall_t*, pms); + ms->ms_retval = sgx_tls_get_quote_ocall(ms->ms_p_report, ms->ms_report_size, ms->ms_p_quote, ms->ms_quote_size); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_sgx_tls_get_supplemental_data_size_ocall(void* pms) +{ + ms_sgx_tls_get_supplemental_data_size_ocall_t* ms = SGX_CAST(ms_sgx_tls_get_supplemental_data_size_ocall_t*, pms); + ms->ms_retval = sgx_tls_get_supplemental_data_size_ocall(ms->ms_p_supplemental_data_size); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_sgx_tls_verify_quote_ocall(void* pms) +{ + ms_sgx_tls_verify_quote_ocall_t* ms = SGX_CAST(ms_sgx_tls_verify_quote_ocall_t*, pms); + ms->ms_retval = sgx_tls_verify_quote_ocall(ms->ms_p_quote, ms->ms_quote_size, ms->ms_expiration_check_date, ms->ms_p_quote_verification_result, ms->ms_p_qve_report_info, ms->ms_qve_report_info_size, ms->ms_p_supplemental_data, ms->ms_supplemental_data_size); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_pthread_wait_timeout_ocall(void* pms) +{ + ms_pthread_wait_timeout_ocall_t* ms = SGX_CAST(ms_pthread_wait_timeout_ocall_t*, pms); + ms->ms_retval = pthread_wait_timeout_ocall(ms->ms_waiter, ms->ms_timeout); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_pthread_create_ocall(void* pms) +{ + ms_pthread_create_ocall_t* ms = SGX_CAST(ms_pthread_create_ocall_t*, pms); + ms->ms_retval = pthread_create_ocall(ms->ms_self); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL enclave_pthread_wakeup_ocall(void* pms) +{ + ms_pthread_wakeup_ocall_t* ms = SGX_CAST(ms_pthread_wakeup_ocall_t*, pms); + ms->ms_retval = pthread_wakeup_ocall(ms->ms_waiter); + + return SGX_SUCCESS; +} + +static const struct { + size_t nr_ocall; + void * table[23]; +} ocall_table_enclave = { + 23, + { + (void*)enclave_ocall_printf, + (void*)enclave_ocall_close, + (void*)enclave_ocall_sleep, + (void*)enclave_ocall_get_current_time, + (void*)enclave_ocall_socket, + (void*)enclave_ocall_connect, + (void*)enclave_sgx_oc_cpuidex, + (void*)enclave_sgx_thread_wait_untrusted_event_ocall, + (void*)enclave_sgx_thread_set_untrusted_event_ocall, + (void*)enclave_sgx_thread_setwait_untrusted_events_ocall, + (void*)enclave_sgx_thread_set_multiple_untrusted_events_ocall, + (void*)enclave_u_sgxssl_ftime, + (void*)enclave_u_sgxssl_write, + (void*)enclave_u_sgxssl_read, + (void*)enclave_u_sgxssl_close, + (void*)enclave_sgx_tls_get_qe_target_info_ocall, + (void*)enclave_sgx_tls_get_quote_size_ocall, + (void*)enclave_sgx_tls_get_quote_ocall, + (void*)enclave_sgx_tls_get_supplemental_data_size_ocall, + (void*)enclave_sgx_tls_verify_quote_ocall, + (void*)enclave_pthread_wait_timeout_ocall, + (void*)enclave_pthread_create_ocall, + (void*)enclave_pthread_wakeup_ocall, + } +}; +sgx_status_t enclave_launch_tls_client(sgx_enclave_id_t eid, int* retval, const char* server_name, uint16_t server_port, uint32_t key, const char* action) +{ + sgx_status_t status; + ms_enclave_launch_tls_client_t ms; + ms.ms_server_name = server_name; + ms.ms_server_name_len = server_name ? strlen(server_name) + 1 : 0; + ms.ms_server_port = server_port; + ms.ms_key = key; + ms.ms_action = action; + ms.ms_action_len = action ? strlen(action) + 1 : 0; + status = sgx_ecall(eid, 0, &ocall_table_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + +sgx_status_t sgx_ra_get_ga(sgx_enclave_id_t eid, sgx_status_t* retval, sgx_ra_context_t context, sgx_ec256_public_t* g_a) +{ + sgx_status_t status; + ms_sgx_ra_get_ga_t ms; + ms.ms_context = context; + ms.ms_g_a = g_a; + status = sgx_ecall(eid, 1, &ocall_table_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + +sgx_status_t sgx_ra_proc_msg2_trusted(sgx_enclave_id_t eid, sgx_status_t* retval, sgx_ra_context_t context, const sgx_ra_msg2_t* p_msg2, const sgx_target_info_t* p_qe_target, sgx_report_t* p_report, sgx_quote_nonce_t* p_nonce) +{ + sgx_status_t status; + ms_sgx_ra_proc_msg2_trusted_t ms; + ms.ms_context = context; + ms.ms_p_msg2 = p_msg2; + ms.ms_p_qe_target = p_qe_target; + ms.ms_p_report = p_report; + ms.ms_p_nonce = p_nonce; + status = sgx_ecall(eid, 2, &ocall_table_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + +sgx_status_t sgx_ra_get_msg3_trusted(sgx_enclave_id_t eid, sgx_status_t* retval, sgx_ra_context_t context, uint32_t quote_size, sgx_report_t* qe_report, sgx_ra_msg3_t* p_msg3, uint32_t msg3_size) +{ + sgx_status_t status; + ms_sgx_ra_get_msg3_trusted_t ms; + ms.ms_context = context; + ms.ms_quote_size = quote_size; + ms.ms_qe_report = qe_report; + ms.ms_p_msg3 = p_msg3; + ms.ms_msg3_size = msg3_size; + status = sgx_ecall(eid, 3, &ocall_table_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + diff --git a/dkeyserver/dkeyrotation/App/enclave_u.h b/dkeyserver/dkeyrotation/App/enclave_u.h new file mode 100644 index 00000000..a13333c9 --- /dev/null +++ b/dkeyserver/dkeyrotation/App/enclave_u.h @@ -0,0 +1,136 @@ +#ifndef ENCLAVE_U_H__ +#define ENCLAVE_U_H__ + +#include +#include +#include +#include +#include "sgx_edger8r.h" /* for sgx_status_t etc. */ + +#include "sgx_ttls.h" +#include "sgx_key_exchange.h" +#include "sgx_quote.h" +#include "sgx_trts.h" +#include "stdbool.h" +#include "datatypes.h" +#include "dh_session_protocol.h" +#include "sys/socket.h" +#include "sys/select.h" +#include "netdb.h" +#include "poll.h" +#include "sgx_report.h" +#include "sgx_qve_header.h" +#include "sgx_ql_lib_common.h" +#include "sgx_ql_quote.h" + +#include /* for size_t */ + +#define SGX_CAST(type, item) ((type)(item)) + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef OCALL_PRINTF_DEFINED__ +#define OCALL_PRINTF_DEFINED__ +void SGX_UBRIDGE(SGX_NOCONVENTION, ocall_printf, (const char* str)); +#endif +#ifndef OCALL_CLOSE_DEFINED__ +#define OCALL_CLOSE_DEFINED__ +int SGX_UBRIDGE(SGX_NOCONVENTION, ocall_close, (int fd)); +#endif +#ifndef OCALL_SLEEP_DEFINED__ +#define OCALL_SLEEP_DEFINED__ +void SGX_UBRIDGE(SGX_NOCONVENTION, ocall_sleep, (int sec)); +#endif +#ifndef OCALL_GET_CURRENT_TIME_DEFINED__ +#define OCALL_GET_CURRENT_TIME_DEFINED__ +void SGX_UBRIDGE(SGX_NOCONVENTION, ocall_get_current_time, (uint64_t* p_current_time)); +#endif +#ifndef OCALL_SOCKET_DEFINED__ +#define OCALL_SOCKET_DEFINED__ +int SGX_UBRIDGE(SGX_NOCONVENTION, ocall_socket, (int domain, int type, int protocol)); +#endif +#ifndef OCALL_CONNECT_DEFINED__ +#define OCALL_CONNECT_DEFINED__ +int SGX_UBRIDGE(SGX_NOCONVENTION, ocall_connect, (int fd, const struct sockaddr* addr, socklen_t len)); +#endif +#ifndef SGX_OC_CPUIDEX_DEFINED__ +#define SGX_OC_CPUIDEX_DEFINED__ +void SGX_UBRIDGE(SGX_CDECL, sgx_oc_cpuidex, (int cpuinfo[4], int leaf, int subleaf)); +#endif +#ifndef SGX_THREAD_WAIT_UNTRUSTED_EVENT_OCALL_DEFINED__ +#define SGX_THREAD_WAIT_UNTRUSTED_EVENT_OCALL_DEFINED__ +int SGX_UBRIDGE(SGX_CDECL, sgx_thread_wait_untrusted_event_ocall, (const void* self)); +#endif +#ifndef SGX_THREAD_SET_UNTRUSTED_EVENT_OCALL_DEFINED__ +#define SGX_THREAD_SET_UNTRUSTED_EVENT_OCALL_DEFINED__ +int SGX_UBRIDGE(SGX_CDECL, sgx_thread_set_untrusted_event_ocall, (const void* waiter)); +#endif +#ifndef SGX_THREAD_SETWAIT_UNTRUSTED_EVENTS_OCALL_DEFINED__ +#define SGX_THREAD_SETWAIT_UNTRUSTED_EVENTS_OCALL_DEFINED__ +int SGX_UBRIDGE(SGX_CDECL, sgx_thread_setwait_untrusted_events_ocall, (const void* waiter, const void* self)); +#endif +#ifndef SGX_THREAD_SET_MULTIPLE_UNTRUSTED_EVENTS_OCALL_DEFINED__ +#define SGX_THREAD_SET_MULTIPLE_UNTRUSTED_EVENTS_OCALL_DEFINED__ +int SGX_UBRIDGE(SGX_CDECL, sgx_thread_set_multiple_untrusted_events_ocall, (const void** waiters, size_t total)); +#endif +#ifndef U_SGXSSL_FTIME_DEFINED__ +#define U_SGXSSL_FTIME_DEFINED__ +void SGX_UBRIDGE(SGX_NOCONVENTION, u_sgxssl_ftime, (void* timeptr, uint32_t timeb_len)); +#endif +#ifndef U_SGXSSL_WRITE_DEFINED__ +#define U_SGXSSL_WRITE_DEFINED__ +size_t SGX_UBRIDGE(SGX_NOCONVENTION, u_sgxssl_write, (int fd, const void* buf, size_t n)); +#endif +#ifndef U_SGXSSL_READ_DEFINED__ +#define U_SGXSSL_READ_DEFINED__ +size_t SGX_UBRIDGE(SGX_NOCONVENTION, u_sgxssl_read, (int fd, void* buf, size_t count)); +#endif +#ifndef U_SGXSSL_CLOSE_DEFINED__ +#define U_SGXSSL_CLOSE_DEFINED__ +int SGX_UBRIDGE(SGX_NOCONVENTION, u_sgxssl_close, (int fd)); +#endif +#ifndef SGX_TLS_GET_QE_TARGET_INFO_OCALL_DEFINED__ +#define SGX_TLS_GET_QE_TARGET_INFO_OCALL_DEFINED__ +quote3_error_t SGX_UBRIDGE(SGX_NOCONVENTION, sgx_tls_get_qe_target_info_ocall, (sgx_target_info_t* p_target_info, size_t target_info_size)); +#endif +#ifndef SGX_TLS_GET_QUOTE_SIZE_OCALL_DEFINED__ +#define SGX_TLS_GET_QUOTE_SIZE_OCALL_DEFINED__ +quote3_error_t SGX_UBRIDGE(SGX_NOCONVENTION, sgx_tls_get_quote_size_ocall, (uint32_t* p_quote_size)); +#endif +#ifndef SGX_TLS_GET_QUOTE_OCALL_DEFINED__ +#define SGX_TLS_GET_QUOTE_OCALL_DEFINED__ +quote3_error_t SGX_UBRIDGE(SGX_NOCONVENTION, sgx_tls_get_quote_ocall, (sgx_report_t* p_report, size_t report_size, uint8_t* p_quote, uint32_t quote_size)); +#endif +#ifndef SGX_TLS_GET_SUPPLEMENTAL_DATA_SIZE_OCALL_DEFINED__ +#define SGX_TLS_GET_SUPPLEMENTAL_DATA_SIZE_OCALL_DEFINED__ +quote3_error_t SGX_UBRIDGE(SGX_NOCONVENTION, sgx_tls_get_supplemental_data_size_ocall, (uint32_t* p_supplemental_data_size)); +#endif +#ifndef SGX_TLS_VERIFY_QUOTE_OCALL_DEFINED__ +#define SGX_TLS_VERIFY_QUOTE_OCALL_DEFINED__ +quote3_error_t SGX_UBRIDGE(SGX_NOCONVENTION, sgx_tls_verify_quote_ocall, (const uint8_t* p_quote, uint32_t quote_size, time_t expiration_check_date, sgx_ql_qv_result_t* p_quote_verification_result, sgx_ql_qe_report_info_t* p_qve_report_info, size_t qve_report_info_size, uint8_t* p_supplemental_data, uint32_t supplemental_data_size)); +#endif +#ifndef PTHREAD_WAIT_TIMEOUT_OCALL_DEFINED__ +#define PTHREAD_WAIT_TIMEOUT_OCALL_DEFINED__ +int SGX_UBRIDGE(SGX_CDECL, pthread_wait_timeout_ocall, (unsigned long long waiter, unsigned long long timeout)); +#endif +#ifndef PTHREAD_CREATE_OCALL_DEFINED__ +#define PTHREAD_CREATE_OCALL_DEFINED__ +int SGX_UBRIDGE(SGX_CDECL, pthread_create_ocall, (unsigned long long self)); +#endif +#ifndef PTHREAD_WAKEUP_OCALL_DEFINED__ +#define PTHREAD_WAKEUP_OCALL_DEFINED__ +int SGX_UBRIDGE(SGX_CDECL, pthread_wakeup_ocall, (unsigned long long waiter)); +#endif + +sgx_status_t enclave_launch_tls_client(sgx_enclave_id_t eid, int* retval, const char* server_name, uint16_t server_port, uint32_t key, const char* action); +sgx_status_t sgx_ra_get_ga(sgx_enclave_id_t eid, sgx_status_t* retval, sgx_ra_context_t context, sgx_ec256_public_t* g_a); +sgx_status_t sgx_ra_proc_msg2_trusted(sgx_enclave_id_t eid, sgx_status_t* retval, sgx_ra_context_t context, const sgx_ra_msg2_t* p_msg2, const sgx_target_info_t* p_qe_target, sgx_report_t* p_report, sgx_quote_nonce_t* p_nonce); +sgx_status_t sgx_ra_get_msg3_trusted(sgx_enclave_id_t eid, sgx_status_t* retval, sgx_ra_context_t context, uint32_t quote_size, sgx_report_t* qe_report, sgx_ra_msg3_t* p_msg3, uint32_t msg3_size); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/dkeyserver/dkeyrotation/App/enclave_u.o b/dkeyserver/dkeyrotation/App/enclave_u.o new file mode 100644 index 0000000000000000000000000000000000000000..4b55c0e257947fc1be2b81d6650c2520abaeeb9d GIT binary patch literal 159608 zcmdSB1yogC+b+B}u;~U#>6Y$J0YMrBDJc;_LAsIdl#r4VQ9_ZH?(UZER63Lp^sF8F zJn#E_<9zS=&OiQt{A;l1b=`}#?s?67-ZR!>tDl0ZA`%!3`h9>wQlP)A-)BJ}5lW;# zh763!fV9AxushuV+a35o@ACjYo(S#*pf`g10QelieF5}CaDM;;5IhjTAOsHvFa*Iv z0SrU%Z~!9^JQBbt1dj$V2Ek(ij6?7j0LCMD0)UAKo&?}a1WyJq1;JAROhfQ=0AC?^ z27s9eo&{hwg69DE8o_e`e1qV50KP@=cL3%icmaTg2wnu>dj$UgU@?M!1h53bO93oH z@Nxhv5WEt=Dg^%oU^Rl*09cFQbpX~Qcmsfq2;Kx>GlI7O*oxq70JbA|2Y{Uj-UVPc zg7*N}i{O0#_9OTJfP)A=1mG}&j{rD|;9~%eBlrY>lL-D9z$pZu25<(!X91i;@Oc0i z5PT89B?Mmva0S6v0bE1ybpSUId=tPe1m6bm3xag zAHf9x6hv?#0EH1;1VB*)7Xwfn!6g8cL~tnpr4d{PKv@Kr1MoJ2%LAwYd`|GFFLjk7 zHk5*PX_X?LZ!1M9Y%4`5oC64aKuQta(2N2>haIT5{eO3Llne^uVx=hE^9ZE> z?*9K!j3z#`1yM@aFpxoy82T4+utr6}u8Gh@`A?*3lEFiw1a~ZhuJnKJ5-v^&9TDt^ zq5p$$D@8#izVa}{eQ^K z|6p_gHN$f4e;)V$L54&r(ZaLwckmqkrs4mMY?F_Yk%24l5P_mUI1~MU66^n9Gr}3)Kj z!aQ(cIB<3uxGx-jA7K7h_iZc1=t64&kK(4E$wH6lWxA?SGn$Q2*(lml|X zh=N_66M-M4(4Z8hc5bP}j}6o70)b7x|FWED0`2Sp+SwAJ+n^MoR{C#^16)KXtOJ#o z0#czumH`f2|3?V0l>JS}e9-fGxR4f@5a`t{P$58T|Mx;*0j~jslt$=&0EFbIMku5L z5q|-Azz6CM|A(*-5egH)FM$qHm~0nPz_{|H9<7r_HT&j;Xw zOJRbcSC>Ks5Bzt6cO8Jp{T3XdRsiS)G^?flmpXyYgR6w9g{p+A-3D3~q3~a6ME<|h z2;25@oUeYH?~p!wiI zlHr{?W2&7GvBW#r&P@l^!%bUt1J`R;->aJse(c%%{qP)m{?>3jkm5QwFpKqvZ+~vB zlZ=A`ExtKAQZ9&qd8AfCLNIwxZl>sKxz`hVvahwyuRc)NXgv1I)>X6mbc=>#=1pH1 z-;9$V+soUd4yQh!7J5A2Hm9cVzx?JhYN7p8PdACUL}z1c)xOZ_-MRU3F-ph{c{K27 zPDuhzAgK;9%i}dCYunQp|972A=rM7&Ou;;CZpj~LC(6uZ3m((qybcJti=Jpu5IWM+ zMux7VUl7LgGK3jjjbk8yCn!W6o!_A5MjMB>x%Rl8T4RSbswpmUK=skgMb@ZNBy@tUJGAP_L%If@R0s z=7%>i#yY~WRjzuedPOFe+0MzA^M{6MrEgzFAMnirr{5SAvb5m^ukNQ@R89V}0z3MXy;=(LzU$Q|Gdf0Py!Vh|=+ zq5QSFKs_O$3>pfU&VL$-$R8ISNzR{#7AYLb0#ru152C^c@?&*4l`;Ncn+9#2A{Lh& z8>=?7c$)D&Gh>R~ho*31ZEM!R8$UXjcV53ZjZ;(eRZq)ld=z11GZt$mmm>Ud+s|f< z%l=)-yoAdgrOVwDpLhLa$5uweb*`U3@3}wmbPoMwS>4%CD`rq(@MzIzhm&u|;LS!A z&hx0^)I-U*QaP`x9hOG37^M}q@;3{#_{nZ^?91`j%B#%}#FKukRkmpQ)|tiKlHRHi zU8(R4u*0l1X4PLYdV0Y?)Qs-^6o+B-APfX+AUtToKSr~!(!AV7@(Vk*<$03lCJwIf zLEHYcv?%Dv^!}{0s6EJJe+3f6ib4#I1S|XFD1pcP31!%5ky(*N{GDVVIfOjuJY+m5 zJj6WMJU2BV1*p-WXq0HAd87jH1d<|h7pM!V3*3dw3}Qz53i{@c+F~igV1{XS%Z$QI zEC+)JnTKcuI~8IL9zd-`+5~MPiGi`eQJ@F@Fa2LNx7*&`o%|)J`WTIq;RBy@px|t# zx?YKuy3=AGmqLYl^+S`|R-2>bM~O4mqrFdxM+XXraI@P58g)elM4zzpJSM61ct38t z=|u3d%3pLia=mx2+jv=%OxxPoIH<7bl;CLh>u z&JN_2CNQ)ubQ^EH(B%!DqpCCN;VRzz*~?YDlGU$2bxgMW)nc)g!ua-p>xWj(Hsj%I z@f6F~-81r4NMopBLSOuogq)fJwP)8=of$Ay3a30rzELu~6{mVqrTi{rSA-LF-A#j< zuAqv!~u^3m=!PJkSyaN4+{* zp4m^nJRd@NZ}Q9Wb5U>Is-WfI0PoY!el|VGtNhh3l;f^mIn;kRDhRJLRsCnM;{_N3j^XRFGIx0QlSxf8^ z?PBQC?ee|NP3zNDN*+gXn=G53nfK?wwv~j{eEYfc8b;Sz7e?1sj63-+TVbc0zaAcJ z`TAJ-^?mcJI>ExQpMFm+T^+{rbMTqfRomhhiL&_2BBo6Hx`EFaac|^VVLV-Q z;lxV}#nc{eO*fo66|i|yA$4M)^F{9}QsAY58vE0t`N*evg?&%nm2YA`h+Z%ex%VR7 z>+^`Hi6X@<>y4*gpPq)~J3Qrk#CdODX`CztbFGHrkdMOQY_tu-t6ipzi&AL<4VCa4 z8ZyS^h)kdq-;0{3s7#n@P5o*K(qZ8uRejdw@izn#Jq(%iJI>YmFE?jRI7MHE&{{om zG7P7GUzsp?%lCZX?r!g8Wht$_Tkp(k_Cp&Ta7%t;_-%g;G^{L*#|1t2xLbm1iVTHQ z(p-{0IAsU171S59e_eU9?`+oMODl( z9Gs+KCa#uyURL#&Z)anC6zV@d89hajl;t+4EH57wJuM2kBQB_?c00lN0}Jb3<@wDg z{xk`jM&Kr$vB1*`KQ&>tol2?a{AhLUVqq7hTWx#F3t9_|Ur@fNuTuoS$>;Q9*=ppJ zdsK51yf9hfKprrYvo$WYtm)o7-x_aQCi+4{9OY6l^8L^n7gi`1eYP9+>?7!kIBzTG=zh(XwV%E>XDp&=`m-^2lF3*y=Vuejo~F3- zsdiOG>ZhvPwMEenZfr5MQNLVfX#1#ksP`-`?nKK%BOF>pF2!qPTX|@|P^xENF^|Y+ z`gwO%FrpGoZ+t%`Bk11S$c9v9wS0@Nk5MPBmQ&pqX62NtK0GstYe1p1>O)(j&m50; z%iAym?^ZqA5ZcRyn_EGBSvP>c9D_vO7Lf${| zG8c6%O)Qc6qA)PSW^eO_>ze!HKFO^xMW!bLCIMIpr+Ii<7?-rlORXY)_r&{eJ3{IO(}We)J6Pn4_FhAcg4MJ zAXCoVd_f7Mjk4_QYZ{d`T2>~}>J=N)mg-^0w)Q!EkB`$I(B&xD8YwP5>hC$JGHi%` zyg2b|>StuhQ`se=rAZE7z2l0A>~o7Z7&0Jy6nU^H&6I%8voW{0&&vh9awF{4vL%{d zZlqfZXPVx5oIl^+v|$`MQ4hJaEf!5V*+z{ywvWCj`YHL)pszpZ#Frxug;P#aZG@QV z?b(pLv*R0<9i@hoFsCQj8+a8P#}rbxwLWg}-4RwqDK<0w#3Hw{oAQW2edJjLcRxMh zn`;^!J$W2Vh-tFIK#oWs?RpuWNmX#G)FhcF!Bn=YCj&R3UeuEbfiFgd#2<2X_oMOT zSWA0veI3#;pJF2Ry=8ZE1woqiOX7{^%LC4v|Ha2N%1^!i-`D5@l?_< z*|}0hnU`O(SHm~&M?!Qzy_-_P-kcs0b`gEU%5p3#s7P0lT=Vj~i4%_8W=Ue{L}3tq zJGCv4sWAVm-%$a3r^`-vWL>X@WV?psxWXi5t)}!KgC$DU) zr0(GiL}(gdI$vj;;B(o1x@CIuEzuxd=so?K=cgZQyGkRv`in;j`M#1n6+K_Ir5^ZZ zcZy1>oNAGONhDeL4Bl{lzx0^RhjzXzFlFS?Wh6(d)6N5hLmcT=%tYb@GbSN$b+bH5 zx~`X>VrSDVU%$k#;OJC^z|%66ykV-#JLH1NJH;D)`C*hV+ED$9gz4STUrlpx&Uk%O zO|zJ@rMhoxbY<&)G1Jsrru?Z`)OPMPi%)Q=+UP#xyN~ zRAU8x?N2{fPax(C%)IsTbOl+U{xq)t-mT`R3-7G=&&DP2Ss-?i+3%Ila-@c|UX4-j z2yR?FZ{9Ll^s)r~xT3tgje28U>J+&aFW@rllKBqgTllw#+{J@@-+QQ#Y^m$OE&e+a zR(l+01GwkUskv!Gu($5a7hrEiO#6l26G7WU>z|BwVm;BUJsW;2GVq`!U*d?-9lS)_ zsWb3-eRDgGVo$SqnY-k44+Bg+`fIZQ{3F6u3o?B`v!0(=JkxoiL(v9N{YGsd+>5Id*f0CVW*A?yP)0JY?h#< zwtKta=#SXFqC}|^rT1sw-bYTlY<_vJQ5ae88Ty^--i;qNNME1F6-8!`U*wE8II z#1$Y#{$05>tZ4B}qyDRR^^?`40&7+}NgPfad_TWuSNZp@-Uc-|$3vJ~xG3geUu|6uh3(;kba`&FXPw`Z1;oNcvSe-sG(m25$;M|4o4fS!d|wZB zR0}CHD<$os9`)yGSea)?|6(?<`8aO-orFVfMo<&VmgQ~NoQXgo}A>`}-L zM}(iIRHjdqrlojUQx#3CP(SI@-MV_Me(&{uzGn4{aZKJ5*DS?qzV2HqFStgk7c&Jb zcV#9d@kbviOk}E5-V;f=p`}FNx=tv_mMeQ(A{UppTcc7Fn~o~n`V6e~lxx&Yp;DHh zYLjsA#%&?i+&ec$PXp?{C(x&JHEpt_({(lFgFiQ1|PkjC+?UbT6Q!6bG6461a0GjXEg zGm^-S4Y1y(Rp+r2ZvXqvS2y~>oyTn;NwGKjCOy6Vh3n*EnQ}&a)7ldM` zQ%X*O%kH;=Tui(wnz!2OvF}QVm7q}1LkG?bLj@h2@qnH=;@BKP6fmd>QB^^|wB zV_di#!b2WYUT0o3uz;yCq$=GGLERrba;e^1>3o!IcN~DLc!=HZO`8V4mW8B=#%*+A=#znQnkLe3R1`&yX z(*Im6caz0`G%Hi~IOkxUu|F z!K5bJ(ZW*xCYShM&(RluRBUAoG+QzERxf>dblVZ?l@ zb52uM%!NZ}!{{r6+A7?WaOB=o=!O$Cyy#h07;wGY?5N+b>27LagEK3wcGx{+N3-GDEmDD#uusQp_Axx8Tv_qpFe0Zak7;# z`87%*y`*@V!;yCn8U1?ah0EQf7dVHlB~BE?Pal{wO@AZ%lo-)LOL%)nm;G~9z}>e$ zWxW`tvI-w2Ep!x=8U%c*_@tR%eTjW?ETp%$FjT;2*^zV4X)wuok(XTn-^f-QGMG$J zsH4NmisKx?peT>wYW`Ku>)zXuhe{h-?$JimME8`{W;kEix6d#2QW1Y^oG+~VsX?GP zYcnSvJ?eIZ&8zQ5N?JKrEQsgL8fkVNhm<0_`*tzrB$atSMX8OnOaoGN*M~6B;Ei3x zbwjPr{>%J==1#ET^oT6d+uH(P-Nr``&5;uC^u$x-(fPi-Gx}hfEbY?F=6lIOYSQO| zu7|~40!n4_v0q;?qQs7S>~@+9X5V+uYA zjjvmFcgLAJg5lxFkD@-b8CZFvfUhy_qilR)c<1>gTZunqv^x>$=`$l8C*z<@%t~ z4KA$w_=5Vd;0c=2{U8ss=s~)@urA$@&v$MGi@J@GyrrohPYZCwsNiY6T+n#IeUAp^ zN8xhF#^WU~=IAMECw`whId*#f9_mipsYH_#fnbc+s2D3&81b|_-fKx_yAVnR6EQ9m z(WR+CaUSjg;oF^ME99IX8&-VsPOp$%^G%>%>-aNXZeslVczFnnuScK&EvKZLOm}(_ z5U*TL7mp2UTSR+>0(_x?2znEQ8CG(^N-<~&0)B@Ie1P9Upd~6QjKhZ{^gh^IatJx} zEaLb*P6wcWCjgG$wnqk2fA3v6N3yP!@f_iiL7~Tw5`_r$M~m3epHMQswRtO4C@bJU zR)b|&^Zq=m!h^=wuZcgEvGDc!FBQD**pAq)*v{B)^$zte^-inHK$`(G5`zJAYApQ@ zLZ_aG)Vf$F?G7`MTLe% z#rwKRKkc~-v;ML@GN5_YI5+@8`QT1F@c`WwPi`Sh6OV3!0sR|^Tw{=?8V7A8 z7-X1;-VODP3B4bJ1uNnHabO%)3j`0w@nIzatR#e$u)Kt_KRQ%l)?YSUht4Aqk{%9z zuNAr#Wzfu~*(avdJrt{Y{~2ntPo$|-=vHV!N~j<^2W05iUtq`MfB0eQ?8X7ng4$bX zTWcmTw!@2MBWR?sMB=iseAbDwFWTEh7xa5a(Brp% zj|{2~QQSZo5U7Q79d9BP^rTW2+=_Lhq_3l+BjJ~yTqc_pu^e$&HtW%JA^%pE$Jewi zlbXXZ_LPst-g{le!i!#8jd=S$r@fEQn$EwzI`BRJlCt#E!P zwpDy55=mL`qYt!U5_)B!uC_V;^mkd`1m1d5@FswHXjG1pYJPnQMk!wMD6 zxjd=qc^977uk3GV&Z&1uMAyqAIEhPR=h03SK4OfwDY_{j6SOzfX+r^_D>2QaV< z8P_vMQTzuWnHBmCP2>K*JR2$CU28SS)N zFZC}~L6WD#ILM>Czac|W;5)oAnK66+mWSBa=4FWec1{>rG1V*-qBm_8hAed$qBBCu z?Gl2H-SI*O6R(Fh4C!t`4k}hmj?sC*5i9yE=2ujQbn;JtEL3>{&iuBa>qR|*KP|ER=oXscXqg&{Uwj0HnkKg7Na(;K&i4US@mIB- zUh_qZ6Xy8h@=6XZ7^L%u=-IDjsT9)BF@BXx^=JlP<5XhhKvn#Gor2ml(AprhusQ)7 z^%bmI)Gatnz(k{l!X6@p9=@28z?}bSpCa7+ zW5e@M*AkewfINH$wt5<%OvO`(#r&-$51cB1lGtT7hAqzr0x7>hVupI9gF1jPAl&@% z^UwUI>JTRc;p5AK9%0T@Qy&$)fewEE>x8Cl>w8SxYJa3*6g$nW;Ty(#Lr+4hpsom#^4X!B$W@_Gw7 z2`UmEXG)mVn=p^R{p}ZeJt#H6)$)Yq7W+PWztPH+&m+V&ucs? z(oCEZXw%@cD{Qv?_w+#|Ieg~@y%zR2cL)beAk>D>60%Sm0?!xNSb&WZ2yEJbsQtP4 zZ#P3qVa%c<}W@c!$Bo4O( zzSu2{91Yb47XZ@?oYgf5R!f?1IBANFK(*?DC9`maO#E9 z2h<=eCF7Qyinp)Qsw@aFD2UW-l4JwlrIOFJG8x~dUw>|Y?(3P%>(+Vo`2J+N;eE;{ zI(CJ2Rd3vM@9Y(RoV}0sRr4FB1*QtR85-~I3J7C<1+3xGn#bycZjNjezKl-3&chxd z%3%h1iY_m@nz@WksY4P8LfEYm@G?UacnKHq|2hD&!aLJz_mSh2<|$NZxqN2ejV|DW!+^r zuYguJ5_HES@Ucg1ibu$lN02*Nr?;v(36_4>t2;e>#Ts)BBu${ZrCQ~FSm1f_?>iK(6ReERLAwiamKA~HjT{>JNX(3xtY zo$lpQxLV8b&qC{ZL(0qeq601uS?(7oZD`;E+VAkRhVNUUHf%nEP(iyLv1x+R-}(Dr zkEfT=`I(J}4UKuDH4iEE9!laZGi@Bzq9A1|V6zEg#%awXfDS0_dq{{K*KdEYQT;dn z@cI6wIpEKerx1l%w>1wnbpo6iYBol!YUr=5!eawrE|z6P2I+i6didYznzZ^e&Rg=N zQUySXie_U}s;GgzJUrw$%nw^Ek)X2tej&#~8wni0bveV$KQ>&~4d}dq!()TZJlL9t zlj?$!_{?mKQWe#|SBZxliAX@?CyHKBXFh7u1ICR3H_qX%^ww~Ylg~R-)C676E0x5CU zOdC!0YoPLT9vcu)k0dF09$F84^?^-d5D{1dpl4y#^pt;xnfHFyO)QR4ix5E}kK19KZqsX2G$PAHcQOt` z?BfQ~6;4J!q?cHYUxi3Nh^;pYS1K1@rj? zR@%c#2UzI@D`7nVafNc=_?<%k92d}aUJ!WR{jASJj>f#!YRQ*607|SiE$a#x;0FmqK)o=~a_QsR)=7(}%$uos_)9FsY*1rVmg%=cxePs?z3Mx$`1!_}sv zIj1A-remdcWu)CKNKs2Ey?Mgvb~~kZyTLa*=x%lq-0bAN*^Pm{#;Cp`Ftp4uv?9T^ zOwYB#r|1B=?}#(yh%D@gRqcrOrY6d}CXSa#d<3_OVNYLtbal2cc_d*r&F$mc%F5)? z^4So#!Ur}}voUV(7i`XEgT5C?+6>P|eJ^UXIhYOmUijQ*X*TYAf3&ojm_R->y70k8 z4~+*rMd35vzs6@BZvM$1XuM=GWn{?wN3#d6@yunFaa(Zm*mM#Zl@l0k5@K)$&xE)M zwI0z*>uRat*zj2vzR5zRu*pp$F|>J;i7WPn{*JKZsrJoF!n!icwA0)66>En&%@5fMgiX6`{kFCg9lQ=P6rpGe2i0?OExiwWYg!9ZP zYHogo>#Sy&uwP=CF3w|sDC;=-M%GE7u1R?p`)AKV)K?8FoChAmXi~=!!#*qQOVbvw z4z8A2nk_zK{J{Nz@dIZk^g84l$5&*7LvnFw9N_yh*dql5_SYc9at+@S{LcS3|3&`W za082)|B-Zm*ioNguN^x1D~3BgU4*ZW+YI-g8eX#2PGKsrzEu0DDP&bqCC{yIH>5`C zowe0@Qcr*$9E|!A3uA3XBJj>JEA^oLF_~{v_pu1|Fp&qn#%ixbNdB=v){y-Pc2;l2 zIaSu6vj^Ak+C_su>2ZBSIOz$YN#DT{pUDvW`E!%r_a1_NElY08^+;~9 zPdqJ&&69hakD=mNR;n+(lVe^q}Q(rDeZ~2FwBA-M-gA|+#E@?}hPnJe*miK-8 zxMvcV%DK^w@8E7I{p^z#O(qh3$uF9MRh#r{j0#(F3@d>B+DZoq>+d*rBgk(X$eZp3 zD_|Z8J^H16pI-m78!7H`+i0z!u>S8LHoUS(pPa4pMhbeIb zdmbG&D*$KW`HikE7a+ zk=U8a+^HK&eC!$D)X*mrdd$q=nSytb#7*8W|L&NRKdoObxjcc!pufcS5sUXlV$jVE z##b-!0{O>(CYLh zLb_!_JZdP6Z^h$BbUX<8{K&fZSH{ZK&4;!_R~4R(Ii%kbOg^)F>6UzPU6`cw(m6X# zUi>WXwH%J2YL4oxFN3b4-%24=8bSp;>fRA)CFgPop_F$BLzAEjMKL)H#q*&H!LHLg z&B2qxHbZA1!#Y9Mab$(Y=(o;)KEF_Pl!1ZwKq#$PJ_P6z81$!0H{Aj;5)>qpMV!sI z`eMZKvKl;2E_RSKzaUZn@ssqAI8$}dA<4*qK&4pwr0KNif&cPzF7c_9F*r3;V<4m6 z;Y(i?W%d5N_u+PL1!>7*!;ix+{ngpO4v{{4qm}=g-_QSML)UjBk^oKMeYv;#v)2i7 z+@4I|n>$i3Qq|CTF{F9LTsMo|7scII#?bCd)w!)D4mZ`fZY>7gF_pa7tf%q4+?cWF z{(1gG`U8(1a>8G6(fh}|N!0;cM?oV?XjDyN{8|QEN(L`?chkzNZHJorYkzt-oUKfv z$QB5l6BRi(QqFHP3)goRKXabmi>+zTj1}9gsh{7YKC2tPrasIeqIMc%!}IQVLw!;+ zJ|wb(u_f_^O~bpZBI~5##N;qHrD69*iPzxH;^DjMhP^;{W0&6g#gisHpKS)3A07Ep zC$-!@U)WQg?@=yY4xRZPzRAAYC2hVOJotH--)wwT?Pq)~eGVzIp)B~fc)i1hpZMMX z9vLzB2c0Mt@J)KDyW2kH^)Ro$c##CwVXM&YtjRz@f}j=TF1O)Cwts%&R+&EbN)Jrgcl?w7nV+EoA#_cJN?#*#MwGtEF0FntntInM$ z82T?6C}Swvd`jY7>kvy(CUg0W8tw!TD~Xv?e0h0$!@+C)ua@6P~y-8?1?d4OZArE9XgzdOw^Q7F;skQ!=_A z`nV&$opn|5%~wr(*%T3t;ZA@tE-y2&HJkBN~;m|CmWCt&_3UN z_2g16bbeoo?)50ar#o~O1;s>DRxVdKRatj?_oWa z9^@L~XrIUxLh;1tT*XL71LWwepMZG^*+FF)Pac?{e`UMwac62Zd^pI2Ab+P$m9VS zu3o80ZSyo48)|0c+M>#B)6DtzCKDSN!(wW;GBEliOxox5L(_Q2+I#Z}l$l;WHBwB# z=ZJaC#GcU=*i%=cCYh*%JJp)zhk6Kch?8%sSWdsB>mFD9vB@)?jy8`}k((&4 z;G_0%Cr7F6k-=T^7;+WtS8wp2JaiT*T~8OAAe7fZElgD7WA$}afBPUKMa;@pQCY@- zr&ouAO+`TsOs2)7vE|BFn-_80jQ{>{hFuA6oizoz*-aE&z9Gi;OBA%Bq`CJqT_nJl zy^5^H-oBo8U8la6jZs%bHs!u}_qso70Tq8+YrN4Gg>F(@iosY-@Zl(m7c;m|J~Kwk zYf?Ftzj{UB@J!Zn>BfXf7hzp-ni2`4ofE-FpTd-Pkxx04Rd2HI>1x;ea*9|qlhjvD zSL90LLJD2<7JTul8r0`1q>Y-Z47c}?T!jlx2&;U3qGK^#{DLn1UJq4H;F%q16icv< zEc6l1FGBhkq(`)(Q3+k0aF*CckRiXugO}JeX^h=}eV#3q(P-ITaw2V(A^qs9m!qNk z!u0O4azK+c=uv}0q2#@Oe6!J>T%->Qt{?9}#GayD<;dI?4he&Jp?LjhmyM9%8e#d^ z=?%G5GVqbE37(KWAl>e8wfSn)-Tu87MAfLhJo41Ono&$EZfWIxBfqTsN^Ro_vY7U; z{b-Yyt)_;gh0Cz@0cErI$0hUel49z;wc8H`WViZMbK|`v?qrfxNzGkjB!cuSxfb_`Zld%v=^noG;4zK+UF+ZUx`J$j;4gjK#^?r)J#$PM*ya%(_lU$Qe;LU6MkgptT^OCX1sfIobXF zVo$@EPp1%TQquQ&BU(~toX< z%_(s`E4TbwiI!>mm2sTo=+|mmW`LtHP16UwnP(R@q1A(LLpB-+e4uqM*(xjsAOFUP}LQmGihs2He>?tb_^Y43ae+5U@9lis-Z7BYDN`V_Vx&Iwjh=>j zCc8-LHii1}v~$<_JvSUv(!mAmUBTE_acUKs=1T4US)UR$>~}ldWWlOkx;KGT` zLW_}F&1%RUHD9vDK$eJK{O(sZyx*2kwZ^e?t2Za|`G3-B=8K0X5R+OE#_ITOD!g+O zj|wdFX{0d`4Ui7a45SPl+BR(vae8yXxu2qtI$%y-?QpwOi{jDH#DG0T+IbRQ6zjKg z8>?enyv(#I{T0OyQwHkNLDXW`Y3`vOEIxfjqUoPAMv0~db(#TqsI;#}Ha~T69VMb? zRdX1#Jqd^*=`9v(un~ycw6RFue}4meXE5qEOUnY9IIj8z@h9|~iPEpo76-7*buP8g zyI56=sX2BX#RQ9~jiPKf=PmWvPo8a-{+KJJsaS7sBTvLw3#pySjAUjH&#j%=C!4YH zJg<>z74w>Y%+kPD=Kb~~D#yF4EmQ9yCT{8MxVM#o3p_Y2CVh!~$WN8sF|E*5Z~E*Y z30p0<2DEUk(TpRDlR?17*3;iJlfO55#3$6QwbjzZA(h&ZXYZzyju5%vEDMrVe z&ihdOGUk~MH?N)07r)pm9_KbA37XQ96YiUyq=%|M3)LM6(tL{Mgk~O{>q}r&TPE{2 zcpV1M=?f0N6gacYIdt)I;LA7?5gK)nu-mWDaclOS-u--bn8qNjmKTskl8pD^5F(1j zW38TUm9l8J85fhkdvs3l$?-cVyV>(RT~gveFMrS%4a@7=$&kiv#_2~tA;+dwbqIGr z*ktTr@_F(g<}Sn=S-7D$m;LC+zmc zqFj`MlX>}KxzgY0{y`J@X}QNuak@G#JhQ-sy@14K^j6a@YAOx`$T5E9y@&SL-|>rk z=JMNrR@$d~%;n9w6n2je1dK;bB`?mBsPL=jmzvc*cX7Y;68rFFQPLNA?={AU2c>Ao z@-K(R+mVKe&9EHCT&Ok_b`7ZNdrbu+B<jkDnf4D{g@6|?7R4oe*&IG|># zSl4SvZU{^ndOm!e>5_NymoW<)JbgsHD}i{YM@fv6gS{HlIFm8Z5y4n35eby524ltgT2#9eJte5jv{JjTzn&cb{AmXK)3EiG*%-6#FVX_NTppcF zmP*SYJ%`XwhZqjA``^z@DQDVsh*HGgFnT;Y@oc<~+#<^IILjZ1<16&mK5# z-&uG*e#KowHte72w~{|7o!V?KaR55YRndQL*!Eh8{U8|cY9(w_v;*rSmu!po6^h>K z{)KFAfx>n)Qk?+tT+36Y9Qxo&rE6M(!yYVVOOPa53HPpEx6hQW+3EQalek?Rxjwy% zn6&hP2GVN^Yw;D<(H!cOnf7DHitF@^cYS6>7;V;fo1p!4_F63V zyR2DkW%;G1eXs+rm0E!27%Q2g#U<{dvhzd^%w>&DgWW}dI~FZ zz+1|8h$?}U)xHld4GHpbJ64-p$Oq|+^LD}%i|>ww_A16;6nDPI>+Rix*}+1imM*P$;#?*y#9Ihs%2ww(@g1$_EefX#L z$uMR-w`P-s4@I7JIaiFcWj#e%#Q$zcv5q?aqiMVx&EML)@q5lwJk*7imh#KuSV3!d z)Xa~Ql(P%p@b+4Rhb2RbXOvg+>HDKhs53LS=M=XNEq-na<2|zXTJv&_=D`0oig#F!HMMuT zeE*!zz+GY|^#OW$l!)QLii429yJJM)Lz~7A7*C`m1diz2tXlMYUU?s>1|OC-AW1i% zl$zo;3!>vH?)rKfQuMlj(WHbrP3NOkgh-LNRzFo4Hmc(dVJ*k{z9%Ky9tFi^5@Rb& zgoLXsRi^iSl3HVyrmnW!#t|tQ<~E!~5fZWcwt~O-imweK&*EPzw}D(=_z0zZ z@)%=z^pX{<)r4tjR_y=2!H}=Vp;2Umli8%tmmji>H6$EbZ1=rB)SZeGf358!ujCdI zj~clZ@;cVCwZ^M%SBQFxIYW|vII-OA$ZoR;nfVBI5{ct>w~M22EcQsV_;#isrPsQA z&mE+Cj#33#BC}-rw}Rtm_EuTUc|?wo!^?TsK+Thm`KtlPL$#UUh8AgtflCjwGhXVr z)+Rb)Vl8q*J%_ACG-nwMi6PvoIeKdIY_fCySY8s2VoxAAN+vV7ASB6T~4WZN> zWyxMdnUl&)AX#K#c5PfIKKc=cHt53HqWGlx3+>87qVV08x2kj+iw zMEdY=W9V`p4u!t=KyY#JD`r6FoeTUiKaLXp^xG`FIks zxatvsrFU21SyGgi0Zv+OYyT9{kK_L1QrVV-%+GNea~sNY8CmWaTWoJ#NNlSdZw^+Z z2~&dmGA}*SS7gtPYbY$g3y}I}xvZLUSlq=62sr67vl1QkPE$yXm??{*M6(T(Mn=AC z&SOM`&iW*Q;#QNq&WWLqD7nrP+%ipzCef_#-PudlsA$gIQXdSC@uMm$8x*mogUoua zf@K4cO}_@r7|?zUS!;QVlaK0)kxARfvdwbpTX8F6M6Y?0ZOf_kr7F^;TCadQR#~#) zkZ)7^b*&e%n^9BHA+F)A8?t74q*}rz?k)!O8##o}@E_7;466tG%3oSzTd`$fG@F5a zOLtdt#&;_BhCn__&4QJ4&%LLqD)(9)tiOHue)%9@+y)tyiT^1Q%Hh~6m!$c0p%0wV z>SUeK58R&>83pIBmIW!BV$Yb3cPxgLiH>j$4usIW6ty%cpKeZl=lU#c&{i})`>`>| zSh-v5zA#&-@C#9136kLxRL;4tZUaKkFeJ@i99&5lkViIz&REY~Qwx zw_kaad`GeosRq(w+%75h%@S1mi?&3#ANxs^(4 zRC7OxEU4LX)pusTggnPg{bsyyi->WWKXnf|?WywP6ZWZ&N>C=s2}(Rs@T=gc7Rxlf zGh?qwfsPcduUJPZ19PL_-c_Q%i$;I9bs@kbNsz63=x9a$Ai$BpY2>EpCc3 zT}?!nE8T1_F$1omW-nar-bl$$c=U?NH1fY21un(HV_}%zQb*NcN%=$Gd+xR(r_z&3_=i zAt3b{z4RN^me=A)Va<#Cx*CbG3620{w6qe)w@F#G)U ze78jhi05S#ED-HLh%ndg7^P3<5UndWFZeO=z5v)%U6&&zvH*6!uNg+iQ?Y|L_YwM! z@`R6}vbSbqVE*2OxC|n)JU#qypFJ**p8-gLFB)IOm`mvyRG$UY2ypAUDYG3SA$we( zepx~!^Sl#DifsL5y*nLMiZ+MwRAQmJx;KKjqKl?!O!1;J^oca|XdM&qTI>ehnd4^o zuflM}9T#0p^kog#z>C8y^=248)3qS9&s2eoLsQ&79HwQsKO_YBWNPP9Y;tez$_s(z z%tgQ`E^#Jah8V~bb_h)NJPLgGR=_=f%&SXU7sT(DrBF-i=3oYHGpMxfp)ISBfCzXk zgIB^`WH$2qR3iIPCTKNV%UN0g!4$m!JdNU8j9h3JBit-z#^hR=$Nm+Vz6(#BE3p;% zlsPe6zNxICv473YJ-gOL4Z14$0B_3SDq?X}glY7CkKBqTKI;Oq&z~5GC$jAB_{;Js zqYTs%t`uh`8t@PN`7P~mu3bGh%|HFgg!h>LF!N_I(U3noC_l+hKQou1`XoNhPr~Om z4G!hK*r!r&&{*<;yyu~-IjsT7jxRYR+n>gkDFc84**K%CG%0Awf`8a>$a7ZxH4#0y@_1Xh? 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