diff --git a/0072-use-memset-instead-of-explicit_bzero.patch b/0072-use-memset-instead-of-explicit_bzero.patch new file mode 100644 index 0000000000000000000000000000000000000000..94ebc29abdbbeff617f91434b78dd88baee94280 --- /dev/null +++ b/0072-use-memset-instead-of-explicit_bzero.patch @@ -0,0 +1,67 @@ +From 248f56df792c14421074a6049ac668464070a574 Mon Sep 17 00:00:00 2001 +From: zhengxiaoxiao +Date: Tue, 12 Mar 2024 16:53:22 +0800 +Subject: [PATCH] use memset instead of explicit_bzero + +Reference: https://gitee.com/openeuler/secGear/commit/248f56df792c14421074a6049ac668464070a574 +Conflict: NA +--- + src/enclave_src/gp/itrustee/itrustee_seal_data.c | 8 ++++---- + src/host_src/enclave.c | 4 ++-- + 2 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/src/enclave_src/gp/itrustee/itrustee_seal_data.c b/src/enclave_src/gp/itrustee/itrustee_seal_data.c +index cae1734..b074d6f 100644 +--- a/src/enclave_src/gp/itrustee/itrustee_seal_data.c ++++ b/src/enclave_src/gp/itrustee/itrustee_seal_data.c +@@ -139,13 +139,13 @@ TEE_Result itrustee_seal_data(uint8_t *seal_data, uint32_t seal_data_len, void * + result = data_copy(tmp_sealed_data, salt, nonce, mac_data, mac_data_len); + + error0: +- explicit_bzero(nonce, SEAL_DATA_NONCE_LEN); ++ memset(nonce, 0, SEAL_DATA_NONCE_LEN); + TEE_Free(nonce); + error1: +- explicit_bzero(salt, SEAL_KEY_SALT_LEN); ++ memset(salt, 0, SEAL_KEY_SALT_LEN); + TEE_Free(salt); + error2: +- explicit_bzero(key_buf, SEAL_KEY_LEN); ++ memset(key_buf, 0, SEAL_KEY_LEN); + TEE_Free(key_buf); + return result; + } +@@ -251,7 +251,7 @@ TEE_Result itrustee_unseal_data(void *sealed_data, uint8_t *decrypted_data, uint + } + + done: +- explicit_bzero(key_buf, SEAL_KEY_LEN); ++ memset(key_buf, 0, SEAL_KEY_LEN); + TEE_Free(key_buf); + return result; + } +diff --git a/src/host_src/enclave.c b/src/host_src/enclave.c +index d8b7d35..f13feec 100644 +--- a/src/host_src/enclave.c ++++ b/src/host_src/enclave.c +@@ -70,7 +70,7 @@ static void error_handle(cc_enclave_t *enclave, void *handle, p_tee_registered r + + if (enclave) { + pthread_rwlock_destroy(&enclave->rwlock); +- explicit_bzero(enclave, sizeof(cc_enclave_t)); ++ memset(enclave, 0, sizeof(cc_enclave_t)); + } + } + +@@ -310,7 +310,7 @@ cc_enclave_result_t cc_enclave_destroy(cc_enclave_t *context) + } + pthread_rwlock_unlock(&context->rwlock); + pthread_rwlock_destroy(&context->rwlock); +- explicit_bzero(context, sizeof(cc_enclave_t)); ++ memset(context, 0, sizeof(cc_enclave_t)); + + return CC_SUCCESS; + } +-- +2.33.0 + diff --git a/0073-memset-no-optimize.patch b/0073-memset-no-optimize.patch new file mode 100644 index 0000000000000000000000000000000000000000..3bb78f2f29b801718c897262e7073f0dc44c5ca1 --- /dev/null +++ b/0073-memset-no-optimize.patch @@ -0,0 +1,58 @@ +From c15207d44281663b32ad4a8ede998dd4c7bda6fd Mon Sep 17 00:00:00 2001 +From: zhengxiaoxiao +Date: Thu, 14 Mar 2024 20:20:34 +0800 +Subject: [PATCH] memset no optimize + +Reference:https://gitee.com/openeuler/secGear/commit/c0997efc6a69d465b286347285cb1508a9d9c24b +Conflict:NA +--- + src/enclave_src/gp/itrustee/itrustee_seal_data.c | 15 +++++++++++---- + 1 file changed, 11 insertions(+), 4 deletions(-) + +diff --git a/src/enclave_src/gp/itrustee/itrustee_seal_data.c b/src/enclave_src/gp/itrustee/itrustee_seal_data.c +index b074d6f..e23cb1e 100644 +--- a/src/enclave_src/gp/itrustee/itrustee_seal_data.c ++++ b/src/enclave_src/gp/itrustee/itrustee_seal_data.c +@@ -15,6 +15,13 @@ + #include "tee_crypto_api.h" + #include "dataseal_internal.h" + #include "tee_trusted_storage.h" ++ ++#define CC_OPTIMIZE_OFF __attribute__((optimize("O0"))) ++CC_OPTIMIZE_OFF static void *memset_no_optimize(void *ptr, int value, size_t num) ++{ ++ memset(ptr, 0, num); ++} ++ + uint32_t get_sealed_data_size_ex(uint32_t seal_data_len, uint32_t aad_len) + { + if (UINT32_MAX - aad_len <= seal_data_len) { +@@ -139,13 +146,13 @@ TEE_Result itrustee_seal_data(uint8_t *seal_data, uint32_t seal_data_len, void * + result = data_copy(tmp_sealed_data, salt, nonce, mac_data, mac_data_len); + + error0: +- memset(nonce, 0, SEAL_DATA_NONCE_LEN); ++ memset_no_optimize(nonce, 0, SEAL_DATA_NONCE_LEN); + TEE_Free(nonce); + error1: +- memset(salt, 0, SEAL_KEY_SALT_LEN); ++ memset_no_optimize(salt, 0, SEAL_KEY_SALT_LEN); + TEE_Free(salt); + error2: +- memset(key_buf, 0, SEAL_KEY_LEN); ++ memset_no_optimize(key_buf, 0, SEAL_KEY_LEN); + TEE_Free(key_buf); + return result; + } +@@ -249,7 +256,7 @@ TEE_Result itrustee_unseal_data(void *sealed_data, uint8_t *decrypted_data, uint + } + + done: +- memset(key_buf, 0, SEAL_KEY_LEN); ++ memset_no_optimize(key_buf, 0, SEAL_KEY_LEN); + TEE_Free(key_buf); + return result; + } +-- +2.33.0 + diff --git a/0074-add-codegen-compile-marco.patch b/0074-add-codegen-compile-marco.patch new file mode 100644 index 0000000000000000000000000000000000000000..97af32f9637cf365502aa46880f43334c8256d6e --- /dev/null +++ b/0074-add-codegen-compile-marco.patch @@ -0,0 +1,29 @@ +From 088eca103708b2d54c4fe46f6dc2da7a21f4f0da Mon Sep 17 00:00:00 2001 +From: houmingyong +Date: Thu, 7 Dec 2023 14:08:36 +0800 +Subject: [PATCH] add codegen compile marco + +Reference:https://gitee.com/openeuler/secGear/commit/088eca103708b2d54c4fe46f6dc2da7a21f4f0da +Conflict:Deleted the PL part from the patch. +--- + CMakeLists.txt | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 25e6381..8a6f22b 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -74,7 +74,10 @@ if(${ENCLAVE} STREQUAL "SGX") + set(CC_SGX ON) + endif() + +-add_subdirectory(tools/codegener) ++option(CODEGEN "default off" ON) ++if(CODEGEN) ++ add_subdirectory(tools/codegener) ++endif() + + add_subdirectory(src) + add_subdirectory(component) +-- +2.33.0 diff --git a/0075-Correct-the-error-in-the-comment.patch b/0075-Correct-the-error-in-the-comment.patch new file mode 100644 index 0000000000000000000000000000000000000000..4af968d5e68f2b0b72b8d34a464fd3ea313c71b8 --- /dev/null +++ b/0075-Correct-the-error-in-the-comment.patch @@ -0,0 +1,26 @@ +From 985be3c3b4947d1a304ff9171c74ca3fe77a86bf Mon Sep 17 00:00:00 2001 +From: zhengxiaoxiaoGitee +Date: Mon, 1 Apr 2024 17:05:10 +0800 +Subject: [PATCH] Correct the error in the comment. + +Reference:https://gitee.com/openeuler/secGear/commit/985be3c3b4947d1a304ff9171c74ca3fe77a86bf +Conflict:NA +--- + inc/host_inc/status.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/inc/host_inc/status.h b/inc/host_inc/status.h +index 7a7920b..c0ff6c7 100644 +--- a/inc/host_inc/status.h ++++ b/inc/host_inc/status.h +@@ -179,7 +179,7 @@ typedef enum _enclave_result_t + CC_CLIENT_INTR = 0xFFFF4000, /* Interrupted by CFC. Broken control flow is detected. */ + CC_ERROR_TIME_NOT_SET = 0xFFFF5000, /* *< 时间未设置 */ + CC_ERROR_TIME_NEEDS_RESET = 0xFFFF5001, /* *< 时间需要重置 */ +- CC_FAIL = 0xFFFF5002, /* *< 时间需要重置 */ ++ CC_FAIL = 0xFFFF5002, /* *< 操作失败 */ + CC_ERROR_TIMER = 0xFFFF6000, + CC_ERROR_TIMER_CREATE_FAILED, + CC_ERROR_TIMER_DESTORY_FAILED, +-- +2.33.0 diff --git a/0076-change-log-file-permission-0400.patch b/0076-change-log-file-permission-0400.patch new file mode 100644 index 0000000000000000000000000000000000000000..ebdd183d2f3dc1a9898cd8208b03f05457dcd6b3 --- /dev/null +++ b/0076-change-log-file-permission-0400.patch @@ -0,0 +1,24 @@ +From 1b2de0be8912fb1b705454011ed6190f52199f60 Mon Sep 17 00:00:00 2001 +From: zhengxiaoxiao +Date: Sat, 11 Jun 2022 12:17:18 +0800 +Subject: [PATCH] change log file permission 0400 + +--- + conf/logrotate.d/secgear | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/conf/logrotate.d/secgear b/conf/logrotate.d/secgear +index 92da41e..f88bb59 100644 +--- a/conf/logrotate.d/secgear ++++ b/conf/logrotate.d/secgear +@@ -5,4 +5,7 @@ + nocompress + copytruncate + size 2048k ++ lastaction ++ chmod 0400 /var/log/secgear/secgear.log.* ++ endscript + } +-- +2.27.0 + diff --git a/0077-support-CPU-core-binding.patch b/0077-support-CPU-core-binding.patch new file mode 100644 index 0000000000000000000000000000000000000000..e8205854905cb9be2c5800fadf9191ddf46508fd --- /dev/null +++ b/0077-support-CPU-core-binding.patch @@ -0,0 +1,67 @@ +From 3beeb13f9040b0c78aa5f246bedfa4d5475787ae Mon Sep 17 00:00:00 2001 +From: zhengxiaoxiao +Date: Tue, 13 Aug 2024 20:43:31 +0800 +Subject: [PATCH] support CPU core binding + +--- + inc/host_inc/secgear_uswitchless.h | 7 ++++++- + src/enclave_src/gp/itrustee/CMakeLists.txt | 2 ++ + src/enclave_src/gp/itrustee/itrustee_tswitchless.c | 1 + + 3 files changed, 9 insertions(+), 1 deletion(-) + +diff --git a/inc/host_inc/secgear_uswitchless.h b/inc/host_inc/secgear_uswitchless.h +index 2ea4691..b7c68d5 100644 +--- a/inc/host_inc/secgear_uswitchless.h ++++ b/inc/host_inc/secgear_uswitchless.h +@@ -41,7 +41,9 @@ + #ifndef SECGEAR_USWITCHLESS_H + #define SECGEAR_USWITCHLESS_H + ++#include + #include ++#include + + #ifdef __cplusplus + extern "C" { +@@ -85,9 +87,12 @@ typedef struct { + + /* Indicates whether to roll back to common invoking when asynchronous switchless invoking fails, only for GP */ + uint32_t rollback_to_common; ++ ++ /* Specifies the name of the bound core. */ ++ cpu_set_t tworkers_cores; + } cc_sl_config_t; + +-#define CC_USWITCHLESS_CONFIG_INITIALIZER {1, 1, 1, 16, 0, 0, WORKERS_POLICY_BUSY, 0} ++#define CC_USWITCHLESS_CONFIG_INITIALIZER {1, 1, 1, 16, 0, 0, WORKERS_POLICY_BUSY, 0, {{0}} } + + #ifdef __cplusplus + } +diff --git a/src/enclave_src/gp/itrustee/CMakeLists.txt b/src/enclave_src/gp/itrustee/CMakeLists.txt +index 14b3c64..c44fc38 100644 +--- a/src/enclave_src/gp/itrustee/CMakeLists.txt ++++ b/src/enclave_src/gp/itrustee/CMakeLists.txt +@@ -19,6 +19,8 @@ set(SOURCE_FILES ${SOURCE_FILES} ../gp.c ../gp_ocall.c itrustee_seal_data.c erro + set(ITRUSTEE_TEEDIR ${SDK_PATH}/) + set(ITRUSTEE_LIBC ${SDK_PATH}/thirdparty/open_source/musl/libc) + ++add_definitions(-D_GNU_SOURCE) ++ + if (CMAKE_COMPILER_IS_GNUCC) + execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpfullversion -dumpversion + OUTPUT_VARIABLE GCC_VERSION) +diff --git a/src/enclave_src/gp/itrustee/itrustee_tswitchless.c b/src/enclave_src/gp/itrustee/itrustee_tswitchless.c +index 3955ff9..4048507 100644 +--- a/src/enclave_src/gp/itrustee/itrustee_tswitchless.c ++++ b/src/enclave_src/gp/itrustee/itrustee_tswitchless.c +@@ -342,6 +342,7 @@ static pthread_t *tswitchless_init_workers(sl_task_pool_t *pool) + SLogError("Create tee thread failed, index:%u, ret:%d.", i, ret); + return NULL; + } ++ pthread_setaffinity_np(tids[i], sizeof(pool_cfg->tworkers_cores), &(pool_cfg->tworkers_cores)); + } + + CC_THREAD_ATTR_DESTROY(&attr); +-- +2.27.0 + diff --git a/0078-register-shared-memory-by-open-session.patch b/0078-register-shared-memory-by-open-session.patch new file mode 100644 index 0000000000000000000000000000000000000000..b547e535fa8068089e5e9f46300396d4b4248487 --- /dev/null +++ b/0078-register-shared-memory-by-open-session.patch @@ -0,0 +1,306 @@ +From d23874b11a5dc22f760b40f6fc0acf99fb088fbf Mon Sep 17 00:00:00 2001 +From: zhengxiaoxiao +Date: Mon, 12 Aug 2024 21:38:05 +0800 +Subject: [PATCH] register shared memory by open session + +Reference:https://gitee.com/openeuler/secGear/commit/d23874b11a5dc22f760b40f6fc0acf99fb088fbf +Conflict:NA +--- + inc/common_inc/gp/gp_shared_memory_defs.h | 1 + + src/enclave_src/gp/gp.c | 2 + + src/enclave_src/gp/itrustee/CMakeLists.txt | 3 +- + .../gp/itrustee/itrustee_shared_memory.c | 62 ++++++++++++++++ + .../gp/itrustee/itrustee_shared_memory.h | 28 +++++++ + src/host_src/gp/gp_enclave.c | 73 ++++++++++++++++++- + src/host_src/gp/gp_shared_memory.c | 20 ++++- + 7 files changed, 185 insertions(+), 4 deletions(-) + create mode 100644 src/enclave_src/gp/itrustee/itrustee_shared_memory.h + +diff --git a/inc/common_inc/gp/gp_shared_memory_defs.h b/inc/common_inc/gp/gp_shared_memory_defs.h +index 87c9a13..8af2411 100644 +--- a/inc/common_inc/gp/gp_shared_memory_defs.h ++++ b/inc/common_inc/gp/gp_shared_memory_defs.h +@@ -46,6 +46,7 @@ typedef struct { + bool is_registered; // the shared memory can be used only after being registered + void *enclave; // refer to cc_enclave_t + pthread_t register_tid; ++ void *reg_session; // register shared memory by open session + list_node_t node; + } gp_shared_memory_t; + +diff --git a/src/enclave_src/gp/gp.c b/src/enclave_src/gp/gp.c +index 57c280f..97d02ef 100644 +--- a/src/enclave_src/gp/gp.c ++++ b/src/enclave_src/gp/gp.c +@@ -16,6 +16,8 @@ + #include "tee_mem_mgmt_api.h" + #include "gp.h" + #include "caller.h" ++#include "tee_log.h" ++#include "itrustee/itrustee_shared_memory.h" + + #define PARAMNUM 4 + #define POS_IN 0 +diff --git a/src/enclave_src/gp/itrustee/CMakeLists.txt b/src/enclave_src/gp/itrustee/CMakeLists.txt +index 14b3c64..b194c41 100644 +--- a/src/enclave_src/gp/itrustee/CMakeLists.txt ++++ b/src/enclave_src/gp/itrustee/CMakeLists.txt +@@ -50,7 +50,8 @@ target_include_directories(${target_lib} PRIVATE + ${LOCAL_ROOT_PATH}/inc/enclave_inc/gp/itrustee + ${ITRUSTEE_TEEDIR}/include/CA + ${LOCAL_ROOT_PATH}/inc/common_inc +- ${LOCAL_ROOT_PATH}/inc/common_inc/gp) ++ ${LOCAL_ROOT_PATH}/inc/common_inc/gp ++ ${CMAKE_CURRENT_SOURCE_DIR}/) + + install(TARGETS ${target_lib} + ARCHIVE +diff --git a/src/enclave_src/gp/itrustee/itrustee_shared_memory.c b/src/enclave_src/gp/itrustee/itrustee_shared_memory.c +index 32b8d8e..d6f0913 100644 +--- a/src/enclave_src/gp/itrustee/itrustee_shared_memory.c ++++ b/src/enclave_src/gp/itrustee/itrustee_shared_memory.c +@@ -156,6 +156,41 @@ cc_enclave_result_t ecall_register_shared_memory(uint8_t *in_buf, + return CC_SUCCESS; + } + ++cc_enclave_result_t register_shared_memory_by_session(uint8_t *in_buf, uint8_t *registered_buf, void **sessionContext) ++{ ++ /* Parse input parameters from in_buf */ ++ size_t in_buf_offset = size_to_aligned_size(sizeof(gp_register_shared_memory_size_t)); ++ gp_register_shared_memory_size_t *args_size = (gp_register_shared_memory_size_t *)in_buf; ++ ++ uint8_t *host_buf_p = NULL; ++ uint8_t *host_buf_len_p = NULL; ++ uint8_t *is_control_buf_p = NULL; ++ SET_PARAM_IN_1(host_buf_p, size_t, host_buf, args_size->shared_buf_size); ++ SET_PARAM_IN_1(host_buf_len_p, size_t, host_buf_len, args_size->shared_buf_len_size); ++ SET_PARAM_IN_1(is_control_buf_p, bool, is_control_buf, args_size->is_control_buf_size); ++ ++ cc_enclave_result_t ret = CC_FAIL; ++ ++ shared_memory_block_t *shared_mem = create_shared_memory_block((void *)host_buf, host_buf_len, registered_buf); ++ if (shared_mem == NULL) { ++ return CC_ERROR_OUT_OF_MEMORY; ++ } ++ ++ if (is_control_buf) { ++ ret = tswitchless_init((void *)shared_mem->enclave_addr, &shared_mem->pool, &shared_mem->tid_arr); ++ if (ret != CC_SUCCESS) { ++ destroy_shared_memory_block(shared_mem); ++ return CC_ERROR_TSWITCHLESS_INIT_FAILED; ++ } ++ } ++ ++ add_shared_memory_block_to_list(shared_mem); ++ __atomic_store_n(&(((gp_shared_memory_t *)registered_buf)->is_registered), true, __ATOMIC_RELEASE); ++ *sessionContext = (void *)shared_mem->enclave_addr; ++ ++ return CC_SUCCESS; ++} ++ + size_t addr_host_to_enclave(size_t host_addr) + { + list_node_t *cur = NULL; +@@ -234,3 +269,30 @@ cc_enclave_result_t ecall_unregister_shared_memory(uint8_t *in_buf, + + return CC_SUCCESS; + } ++ ++void open_session_unregister_shared_memory(void *sessionContext) ++{ ++ list_node_t *cur = NULL; ++ shared_memory_block_t *mem_block = NULL; ++ ++ CC_RWLOCK_LOCK_WR(&g_shared_memory_list_lock); ++ ++ list_for_each(cur, &g_shared_memory_list) { ++ mem_block = list_entry(cur, shared_memory_block_t, node); ++ tlogi("[secGear] unregister shared_mem:%p, cur_mem:%p", sessionContext, mem_block->enclave_addr); ++ if (sessionContext == (void *)mem_block->enclave_addr) { ++ __atomic_store_n(&((GP_SHARED_MEMORY_ENTRY(mem_block->enclave_addr))->is_registered), ++ false, __ATOMIC_RELEASE); ++ ++ list_remove(&mem_block->node); ++ if ((GP_SHARED_MEMORY_ENTRY(mem_block->enclave_addr))->is_control_buf) { ++ tswitchless_fini(mem_block->pool, mem_block->tid_arr); ++ } ++ destroy_shared_memory_block(mem_block); ++ break; ++ } ++ } ++ CC_RWLOCK_UNLOCK(&g_shared_memory_list_lock); ++ ++ return; ++} +diff --git a/src/enclave_src/gp/itrustee/itrustee_shared_memory.h b/src/enclave_src/gp/itrustee/itrustee_shared_memory.h +new file mode 100644 +index 0000000..35ae829 +--- /dev/null ++++ b/src/enclave_src/gp/itrustee/itrustee_shared_memory.h +@@ -0,0 +1,28 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2020. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++#ifndef __ITRUSTEE_SHARED_MEMORY_H__ ++#define __ITRUSTEE_SHARED_MEMORY_H__ ++ ++#include "status.h" ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++cc_enclave_result_t register_shared_memory_by_session(uint8_t *in_buf, uint8_t *registered_buf, void **sessionContext); ++void open_session_unregister_shared_memory(void *sessionContext); ++ ++#ifdef __cplusplus ++} ++#endif ++#endif +diff --git a/src/host_src/gp/gp_enclave.c b/src/host_src/gp/gp_enclave.c +index 952d584..ad07c30 100644 +--- a/src/host_src/gp/gp_enclave.c ++++ b/src/host_src/gp/gp_enclave.c +@@ -698,6 +698,63 @@ cc_enclave_result_t handle_ecall_function_register_shared_memory(cc_enclave_t *e + + return CC_SUCCESS; + } ++/* TEEC_OpenSession 用户只能用param[0]和param[1], 2,3被底层默认占用了 */ ++static cc_enclave_result_t init_open_session_register_memory_oper(TEEC_Operation *operation, ++ cc_enclave_call_function_args_t *args) ++{ ++ const int input_pos = 0; ++ const int shared_pos = 1; ++ memset(operation, 0x00, sizeof(TEEC_Operation)); ++ operation->started = 1; ++ uint32_t paramtypes[] = { TEEC_NONE, TEEC_NONE, TEEC_NONE, TEEC_NONE }; ++ /* Fill input buffer */ ++ if (args->input_buffer_size) { ++ operation->params[input_pos].tmpref.buffer = (void *)args->input_buffer; ++ operation->params[input_pos].tmpref.size = (uint32_t)args->input_buffer_size; ++ paramtypes[input_pos] = TEEC_MEMREF_TEMP_INPUT; ++ } ++ ++ /* Fill shared buffer */ ++ gp_shared_memory_t *shared_mem = GP_SHARED_MEMORY_ENTRY(GET_HOST_BUF_FROM_INPUT_PARAMS(args->input_buffer)); ++ TEEC_SharedMemory *teec_shared_mem = (TEEC_SharedMemory *)(&shared_mem->shared_mem); ++ operation->params[shared_pos].memref.parent = teec_shared_mem; ++ operation->params[shared_pos].memref.size = teec_shared_mem->size; ++ paramtypes[shared_pos] = TEEC_MEMREF_REGISTER_INOUT; ++ ++ operation->paramTypes = TEEC_PARAM_TYPES(paramtypes[input_pos], paramtypes[shared_pos], TEEC_NONE, TEEC_NONE); ++ ++ return CC_SUCCESS; ++} ++ ++cc_enclave_result_t handle_open_session_register_shared_memory(cc_enclave_t *enclave, ++ cc_enclave_call_function_args_t *args, void *session) ++{ ++ if (args->function_id == fid_register_shared_memory) { ++ gp_context_t *gp = (gp_context_t *)(enclave->private_data); ++ uint32_t origin; ++ TEEC_Operation oper; ++ memset(&oper, 0, sizeof(oper)); ++ oper.started = 1; ++ oper.paramTypes = TEEC_PARAM_TYPES(TEEC_NONE, TEEC_NONE, TEEC_MEMREF_TEMP_INPUT, TEEC_MEMREF_TEMP_INPUT); ++ cc_enclave_result_t cc_res = init_open_session_register_memory_oper(&oper, args); ++ if (cc_res != CC_SUCCESS) { ++ print_error_term("Handle ecall with new session, failed to init operation, ret:%x\n", cc_res); ++ return CC_FAIL; ++ } ++ TEEC_Result result = TEEC_OpenSession(&gp->ctx, session, &gp->uuid, TEEC_LOGIN_IDENTIFY, ++ NULL, &oper, &origin); ++ if (result != TEEC_SUCCESS) { ++ print_error_term("Handle ecall with new session, failed to open session, ret:%x, origin:%x\n", ++ result, origin); ++ cc_res = conversion_res_status(result, enclave->type); ++ return cc_res; ++ } ++ } else { // shared_mem->reg_session close by unregister shared memory ++ TEEC_CloseSession(session); ++ } ++ ++ return CC_SUCCESS; ++} + + static cc_enclave_result_t handle_ecall_function(cc_enclave_t *enclave, cc_enclave_call_function_args_t *args) + { +@@ -706,7 +763,21 @@ static cc_enclave_result_t handle_ecall_function(cc_enclave_t *enclave, cc_encla + TEEC_Operation operation; + uint32_t origin; + gp_context_t *gp = (gp_context_t*)enclave->private_data; +- ++ if (args->function_id == fid_register_shared_memory || args->function_id == fid_unregister_shared_memory) { ++ gp_shared_memory_t *shared_mem = NULL; ++ if (args->function_id == fid_register_shared_memory) { ++ shared_mem = GP_SHARED_MEMORY_ENTRY(GET_HOST_BUF_FROM_INPUT_PARAMS(args->input_buffer)); ++ } else { ++ void *ptr = NULL; ++ (void)memcpy(&ptr, (char *)(args->input_buffer) + ++ size_to_aligned_size(sizeof(gp_unregister_shared_memory_size_t)), sizeof(void *)); ++ shared_mem = GP_SHARED_MEMORY_ENTRY(ptr); ++ } ++ TEEC_SharedMemory *teec_shared_mem = (TEEC_SharedMemory *)(&shared_mem->shared_mem); ++ if (teec_shared_mem->flags == TEEC_MEM_REGISTER_INOUT) { ++ return handle_open_session_register_shared_memory(enclave, args, shared_mem->reg_session); ++ } ++ } + if (args->function_id == fid_register_shared_memory) { + return handle_ecall_function_register_shared_memory(enclave, args); + } +diff --git a/src/host_src/gp/gp_shared_memory.c b/src/host_src/gp/gp_shared_memory.c +index b6a958d..cd1a4c5 100644 +--- a/src/host_src/gp/gp_shared_memory.c ++++ b/src/host_src/gp/gp_shared_memory.c +@@ -54,14 +54,26 @@ void *gp_malloc_shared_memory(cc_enclave_t *context, size_t size, bool is_contro + .is_control_buf = is_control_buf, + .is_registered = false, + .enclave = (void *) context, +- .register_tid = 0 ++ .register_tid = 0, ++ .reg_session = NULL + }; ++ gp_shared_mem.reg_session = malloc(sizeof(TEEC_Session)); ++ if (gp_shared_mem.reg_session == NULL) { ++ return NULL; ++ } + TEEC_SharedMemory *teec_shared_mem = (TEEC_SharedMemory *)(&gp_shared_mem.shared_mem); + teec_shared_mem->size = size + sizeof(gp_shared_memory_t); +- teec_shared_mem->flags = TEEC_MEM_SHARED_INOUT; ++ teec_shared_mem->flags = TEEC_MEM_REGISTER_INOUT; + + TEEC_Result result = TEEC_AllocateSharedMemory(&gp_context->ctx, teec_shared_mem); ++ if (result == TEEC_ERROR_BAD_PARAMETERS) { ++ print_warning("not support register type, try shared type again.\n"); ++ teec_shared_mem->flags = TEEC_MEM_SHARED_INOUT; ++ result = TEEC_AllocateSharedMemory(&gp_context->ctx, teec_shared_mem); ++ } ++ + if (result != TEEC_SUCCESS) { ++ free(gp_shared_mem.reg_session); + return NULL; + } + +@@ -106,6 +118,10 @@ cc_enclave_result_t gp_free_shared_memory(cc_enclave_t *enclave, void *ptr) + } + + gp_remove_shared_mem_from_list(GP_SHARED_MEMORY_ENTRY(ptr)); ++ if (GP_SHARED_MEMORY_ENTRY(ptr)->reg_session != NULL) { ++ free(GP_SHARED_MEMORY_ENTRY(ptr)->reg_session); ++ GP_SHARED_MEMORY_ENTRY(ptr)->reg_session = NULL; ++ } + + TEEC_SharedMemory sharedMem = *TEEC_SHARED_MEMORY_ENTRY(ptr); + TEEC_ReleaseSharedMemory(&sharedMem); +-- +2.27.0 + diff --git a/0079-Optimize-the-registration-shared-memory.patch b/0079-Optimize-the-registration-shared-memory.patch new file mode 100644 index 0000000000000000000000000000000000000000..9d9efa59ffed79327365588b56f665bc028628a1 --- /dev/null +++ b/0079-Optimize-the-registration-shared-memory.patch @@ -0,0 +1,225 @@ +From 2e22f45d9c20941823761fa858e1faa9ce050a2c Mon Sep 17 00:00:00 2001 +From: zhengxiaoxiao +Date: Tue, 20 Aug 2024 23:41:45 +0800 +Subject: [PATCH] Optimize the registration shared memory + +Reference:https://gitee.com/openeuler/secGear/commit/2e22f45d9c20941823761fa858e1faa9ce050a2c +Conflict:NA +--- + inc/host_inc/enclave_internal.h | 2 +- + src/host_src/gp/gp_enclave.c | 112 +++++++++++++++++++++++---- + src/host_src/gp/gp_shared_memory.c | 22 +++++- + src/host_src/gp/gp_shared_memory.h | 2 +- + src/host_src/secgear_shared_memory.c | 24 +++--- + src/host_src/sgx/sgx_shared_memory.c | 3 ++- + src/host_src/sgx/sgx_shared_memory.h | 2 +- + 7 files changed, 134 insertions(+), 33 deletions(-) + +diff --git a/inc/host_inc/enclave_internal.h b/inc/host_inc/enclave_internal.h +index fa0cbf4..ac88f46 100644 +--- a/inc/host_inc/enclave_internal.h ++++ b/inc/host_inc/enclave_internal.h +@@ -74,7 +74,7 @@ struct cc_enclave_ops { + cc_enclave_result_t (*cc_sl_async_ecall_get_result)(cc_enclave_t *enclave, int task_id, void *retval); + + /* shared memory */ +- void *(*cc_malloc_shared_memory)(cc_enclave_t *enclave, size_t size, bool is_control_buf); ++ void *(*cc_malloc_shared_memory)(cc_enclave_t *enclave, size_t size, bool is_control_buf, int try_cnt); + cc_enclave_result_t (*cc_free_shared_memory)(cc_enclave_t *enclave, void *ptr); + cc_enclave_result_t (*cc_register_shared_memory)(cc_enclave_t *enclave, void *ptr); + cc_enclave_result_t (*cc_unregister_shared_memory)(cc_enclave_t *enclave, void *ptr); +diff --git a/src/host_src/gp/gp_enclave.c b/src/host_src/gp/gp_enclave.c +index ad07c30..a2ff9f4 100644 +--- a/src/host_src/gp/gp_enclave.c ++++ b/src/host_src/gp/gp_enclave.c +@@ -377,27 +377,34 @@ cc_enclave_result_t init_uswitchless(cc_enclave_t *enclave, const enclave_featur + uswitchless_adjust_config(&cfg); + + size_t pool_buf_len = sl_get_pool_buf_len_by_config(&cfg); +- void *pool_buf = gp_malloc_shared_memory(enclave, pool_buf_len, true); +- if (pool_buf == NULL) { +- return CC_ERROR_OUT_OF_MEMORY; +- } +- (void)memset(pool_buf, 0, pool_buf_len); ++ cc_enclave_result_t ret; ++ sl_task_pool_t *pool; ++ for (int i = 0; i < 2; i++) { ++ void *pool_buf = gp_malloc_shared_memory(enclave, pool_buf_len, true, i); ++ if (pool_buf == NULL) { ++ return CC_ERROR_OUT_OF_MEMORY; ++ } ++ (void)memset(pool_buf, 0, pool_buf_len); + +- // Fill config +- (void)memcpy(pool_buf, &cfg, sizeof(cc_sl_config_t)); ++ // Fill config ++ (void)memcpy(pool_buf, &cfg, sizeof(cc_sl_config_t)); + +- // Layout task pool +- sl_task_pool_t *pool = uswitchless_create_task_pool(pool_buf, &cfg); +- if (pool == NULL) { +- (void)gp_free_shared_memory(enclave, pool_buf); +- return CC_ERROR_OUT_OF_MEMORY; +- } ++ // Layout task pool ++ pool = uswitchless_create_task_pool(pool_buf, &cfg); ++ if (pool == NULL) { ++ (void)gp_free_shared_memory(enclave, pool_buf); ++ return CC_ERROR_OUT_OF_MEMORY; ++ } + +- // Registering a task pool +- cc_enclave_result_t ret = gp_register_shared_memory(enclave, pool_buf); +- if (ret != CC_SUCCESS) { ++ // Registering a task pool ++ ret = gp_register_shared_memory(enclave, pool_buf); ++ if (ret == CC_SUCCESS) { ++ break; ++ } + free(pool); + (void)gp_free_shared_memory(enclave, pool_buf); ++ } ++ if (ret != CC_SUCCESS) { + return ret; + } + +diff --git a/src/host_src/gp/gp_shared_memory.c b/src/host_src/gp/gp_shared_memory.c +index cd1a4c5..232edbf 100644 +--- a/src/host_src/gp/gp_shared_memory.c ++++ b/src/host_src/gp/gp_shared_memory.c +@@ -47,7 +47,7 @@ static void gp_add_shared_mem_to_list(gp_shared_memory_t *shared_mem) + CC_RWLOCK_UNLOCK(&g_shared_mem_list_lock); + } + +-void *gp_malloc_shared_memory(cc_enclave_t *context, size_t size, bool is_control_buf) ++void *gp_malloc_shared_memory(cc_enclave_t *context, size_t size, bool is_control_buf, int try_cnt) + { + gp_context_t *gp_context = (gp_context_t *)context->private_data; + gp_shared_memory_t gp_shared_mem = { +@@ -63,7 +63,7 @@ void *gp_malloc_shared_memory(cc_enclave_t *context, size_t size, bool is_contro + } + TEEC_SharedMemory *teec_shared_mem = (TEEC_SharedMemory *)(&gp_shared_mem.shared_mem); + teec_shared_mem->size = size + sizeof(gp_shared_memory_t); +- teec_shared_mem->flags = TEEC_MEM_REGISTER_INOUT; ++ teec_shared_mem->flags = try_cnt == 0 ? TEEC_MEM_REGISTER_INOUT : TEEC_MEM_SHARED_INOUT; + + TEEC_Result result = TEEC_AllocateSharedMemory(&gp_context->ctx, teec_shared_mem); + if (result == TEEC_ERROR_BAD_PARAMETERS) { +diff --git a/src/host_src/gp/gp_shared_memory.h b/src/host_src/gp/gp_shared_memory.h +index 6914193..4659b4a 100644 +--- a/src/host_src/gp/gp_shared_memory.h ++++ b/src/host_src/gp/gp_shared_memory.h +@@ -31,7 +31,7 @@ extern "C" { + * is_control_buf: whether it is a control area buffer + * Return: A pointer to the allocated memory. On error, return NULL. + */ +-void *gp_malloc_shared_memory(cc_enclave_t *context, size_t size, bool is_control_buf); ++void *gp_malloc_shared_memory(cc_enclave_t *context, size_t size, bool is_control_buf, int try_cnt); + + /* + * Summary: Frees the memory space pointed to by ptr, which must have been returned by gp_malloc_shared_memory. +diff --git a/src/host_src/secgear_shared_memory.c b/src/host_src/secgear_shared_memory.c +index d7e8d35..258f329 100644 +--- a/src/host_src/secgear_shared_memory.c ++++ b/src/host_src/secgear_shared_memory.c +@@ -40,21 +40,27 @@ void *cc_malloc_shared_memory(cc_enclave_t *enclave, size_t size) + return NULL; + } + +- void *ptr = FUNC_CREATE_SHARED_MEM(enclave)(enclave, size, false); +- if (ptr == NULL) { +- CC_RWLOCK_UNLOCK(&enclave->rwlock); +- return NULL; ++ cc_enclave_result_t ret; ++ void *ptr; ++ for (int i = 0; i < 2; i++) { ++ ptr = FUNC_CREATE_SHARED_MEM(enclave)(enclave, size, false, i); ++ if (ptr == NULL) { ++ CC_RWLOCK_UNLOCK(&enclave->rwlock); ++ return NULL; ++ } ++ ++ ret = FUNC_REGISTER_SHARED_MEM(enclave)(enclave, ptr); ++ if (ret == CC_SUCCESS) { ++ break; ++ } ++ CC_IGNORE(FUNC_FREE_SHARED_MEM(enclave)(enclave, ptr)); + } + +- cc_enclave_result_t ret = FUNC_REGISTER_SHARED_MEM(enclave)(enclave, ptr); ++ CC_RWLOCK_UNLOCK(&enclave->rwlock); + if (ret != CC_SUCCESS) { +- CC_IGNORE(FUNC_FREE_SHARED_MEM(enclave)(enclave, ptr)); +- CC_RWLOCK_UNLOCK(&enclave->rwlock); + return NULL; + } + +- CC_RWLOCK_UNLOCK(&enclave->rwlock); +- + return ptr; + } + +diff --git a/src/host_src/sgx/sgx_shared_memory.c b/src/host_src/sgx/sgx_shared_memory.c +index b9ecf9a..2699580 100644 +--- a/src/host_src/sgx/sgx_shared_memory.c ++++ b/src/host_src/sgx/sgx_shared_memory.c +@@ -15,10 +15,11 @@ + #include + #include "secgear_defs.h" + +-void *sgx_malloc_shared_memory(cc_enclave_t *enclave, size_t size, bool is_control_buf) ++void *sgx_malloc_shared_memory(cc_enclave_t *enclave, size_t size, bool is_control_buf, int try_cnt) + { + CC_IGNORE(enclave); + CC_IGNORE(is_control_buf); ++ CC_IGNORE(try_cnt); + + return malloc(size); + } +diff --git a/src/host_src/sgx/sgx_shared_memory.h b/src/host_src/sgx/sgx_shared_memory.h +index 861cea7..b7f886a 100644 +--- a/src/host_src/sgx/sgx_shared_memory.h ++++ b/src/host_src/sgx/sgx_shared_memory.h +@@ -27,7 +27,7 @@ + * is_control_buf: whether it is a control area buffer + * Return: A pointer to the allocated memory. On error, return NULL. + */ +-void *sgx_malloc_shared_memory(cc_enclave_t *context, size_t size, bool is_control_buf); ++void *sgx_malloc_shared_memory(cc_enclave_t *context, size_t size, bool is_control_buf, int try_cnt); + + /* + * Summary: Frees the memory space pointed to by ptr, which must have been returned by sgx_malloc_shared_memory. +diff --git a/src/enclave_src/gp/gp.c b/src/enclave_src/gp/gp.c +index 3f30a16..4f3c3b2 100644 +--- a/src/enclave_src/gp/gp.c ++++ b/src/enclave_src/gp/gp.c +@@ -69,6 +69,13 @@ TEE_Result TA_OpenSessionEntryPoint(uint32_t paramTypes, + TEE_Result ret = TEE_SUCCESS; + SLogTrace("---- TA_OpenSessionEntryPoint -------- "); + ++ uint32_t param_in = 0; ++ uint32_t param_shared_mem = 1; ++ if (TEE_PARAM_TYPE_GET(paramTypes, param_shared_mem) == TEE_PARAM_TYPE_MEMREF_REGISTER_INOUT) { ++ ret = register_shared_memory_by_session(params[param_in].memref.buffer, ++ params[param_shared_mem].memref.buffer, sessionContext); ++ tlogi("[secGear]TA_OpenSessionEntryPoint register shared memory ret:%d, shared_mem:%p", ret, *sessionContext); ++ } + return ret; + } + +@@ -83,6 +90,12 @@ void TA_CloseSessionEntryPoint(void *sessionContext) + { + (void)sessionContext; /* -Wunused-parameter */ + SLogTrace("---- TA_CloseSessionEntryPoint ----- "); ++ ++ // find shared mem block by session, and destroy ++ if (sessionContext != NULL) { ++ tlogi("[secGear]TA_CloseSessionEntryPoint unregister shared_mem:%p", sessionContext); ++ open_session_unregister_shared_memory(sessionContext); ++ } + } + + /** +-- +2.27.0 + diff --git a/0080-add-attestation-service.patch b/0080-add-attestation-service.patch new file mode 100644 index 0000000000000000000000000000000000000000..93e64d055985e3d93a10155ddcb16f804c0d5387 --- /dev/null +++ b/0080-add-attestation-service.patch @@ -0,0 +1,4626 @@ +From 85f0bca3d385699ffca8d15c70ff9ac563d34512 Mon Sep 17 00:00:00 2001 +From: xuraoqing +Date: Thu, 22 Aug 2024 22:46:30 +0800 +Subject: [PATCH] add attestation service + +--- + service/attestation/.gitignore | 3 + + .../attestation/attestation-agent/Cargo.toml | 29 ++ + .../attestation/attestation-agent/README.md | 5 + + .../attestation-agent/agent/Cargo.toml | 47 ++ + .../agent/attestation-agent.conf | 7 + + .../agent/src/bin/aa-test/main.rs | 195 ++++++++ + .../agent/src/bin/generate-headers/main.rs | 14 + + .../attestation-agent/agent/src/lib.rs | 387 ++++++++++++++++ + .../attestation-agent/agent/src/main.rs | 67 +++ + .../agent/src/restapi/mod.rs | 140 ++++++ + .../attestation-agent/agent/src/result/mod.rs | 50 ++ + .../attestation-agent/attester/Cargo.toml | 18 + + .../attester/src/itrustee/itrustee.rs | 51 +++ + .../attester/src/itrustee/mod.rs | 130 ++++++ + .../attestation-agent/attester/src/lib.rs | 79 ++++ + .../attester/src/virtcca/mod.rs | 93 ++++ + .../attester/src/virtcca/virtcca.rs | 109 +++++ + .../attestation-agent/token/Cargo.toml | 13 + + .../attestation-agent/token/src/lib.rs | 114 +++++ + .../attestation-service/Cargo.toml | 42 ++ + .../attestation/attestation-service/README.md | 6 + + .../attestation-service/policy/Cargo.toml | 12 + + .../attestation-service/policy/src/lib.rs | 181 ++++++++ + .../policy/src/opa/default_itrustee.rego | 10 + + .../policy/src/opa/default_vcca.rego | 10 + + .../attestation-service/policy/src/opa/mod.rs | 167 +++++++ + .../policy/src/policy_engine.rs | 73 +++ + .../attestation-service/reference/Cargo.toml | 16 + + .../reference/src/extractor/mod.rs | 30 ++ + .../attestation-service/reference/src/lib.rs | 141 ++++++ + .../reference/src/local_fs/mod.rs | 87 ++++ + .../reference/src/reference/mod.rs | 147 ++++++ + .../reference/src/store/mod.rs | 19 + + .../attestation-service/service/Cargo.toml | 35 ++ + .../service/attestation-service.conf | 9 + + .../attestation-service/service/src/lib.rs | 204 +++++++++ + .../attestation-service/service/src/main.rs | 76 ++++ + .../service/src/restapi/mod.rs | 139 ++++++ + .../service/src/result/mod.rs | 55 +++ + .../service/src/session.rs | 58 +++ + .../attestation-service/tests/Cargo.toml | 9 + + .../attestation-service/tests/src/lib.rs | 166 +++++++ + .../attestation-service/token/Cargo.toml | 13 + + .../attestation-service/token/src/lib.rs | 115 +++++ + .../attestation-service/verifier/Cargo.toml | 27 ++ + .../verifier/src/itrustee/itrustee.rs | 53 +++ + .../verifier/src/itrustee/mod.rs | 76 ++++ + .../attestation-service/verifier/src/lib.rs | 80 ++++ + .../verifier/src/virtcca/ima.rs | 91 ++++ + .../verifier/src/virtcca/mod.rs | 427 ++++++++++++++++++ + .../attestation/attestation-types/Cargo.toml | 8 + + .../attestation/attestation-types/src/lib.rs | 52 +++ + 52 files changed, 4185 insertions(+) + create mode 100644 service/attestation/.gitignore + create mode 100644 service/attestation/attestation-agent/Cargo.toml + create mode 100644 service/attestation/attestation-agent/README.md + create mode 100644 service/attestation/attestation-agent/agent/Cargo.toml + create mode 100644 service/attestation/attestation-agent/agent/attestation-agent.conf + create mode 100644 service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs + create mode 100644 service/attestation/attestation-agent/agent/src/bin/generate-headers/main.rs + create mode 100644 service/attestation/attestation-agent/agent/src/lib.rs + create mode 100644 service/attestation/attestation-agent/agent/src/main.rs + create mode 100644 service/attestation/attestation-agent/agent/src/restapi/mod.rs + create mode 100644 service/attestation/attestation-agent/agent/src/result/mod.rs + create mode 100644 service/attestation/attestation-agent/attester/Cargo.toml + create mode 100644 service/attestation/attestation-agent/attester/src/itrustee/itrustee.rs + create mode 100644 service/attestation/attestation-agent/attester/src/itrustee/mod.rs + create mode 100644 service/attestation/attestation-agent/attester/src/lib.rs + create mode 100644 service/attestation/attestation-agent/attester/src/virtcca/mod.rs + create mode 100644 service/attestation/attestation-agent/attester/src/virtcca/virtcca.rs + create mode 100644 service/attestation/attestation-agent/token/Cargo.toml + create mode 100644 service/attestation/attestation-agent/token/src/lib.rs + create mode 100644 service/attestation/attestation-service/Cargo.toml + create mode 100644 service/attestation/attestation-service/README.md + create mode 100644 service/attestation/attestation-service/policy/Cargo.toml + create mode 100644 service/attestation/attestation-service/policy/src/lib.rs + create mode 100644 service/attestation/attestation-service/policy/src/opa/default_itrustee.rego + create mode 100644 service/attestation/attestation-service/policy/src/opa/default_vcca.rego + create mode 100644 service/attestation/attestation-service/policy/src/opa/mod.rs + create mode 100644 service/attestation/attestation-service/policy/src/policy_engine.rs + create mode 100644 service/attestation/attestation-service/reference/Cargo.toml + create mode 100644 service/attestation/attestation-service/reference/src/extractor/mod.rs + create mode 100644 service/attestation/attestation-service/reference/src/lib.rs + create mode 100644 service/attestation/attestation-service/reference/src/local_fs/mod.rs + create mode 100644 service/attestation/attestation-service/reference/src/reference/mod.rs + create mode 100644 service/attestation/attestation-service/reference/src/store/mod.rs + create mode 100644 service/attestation/attestation-service/service/Cargo.toml + create mode 100644 service/attestation/attestation-service/service/attestation-service.conf + create mode 100644 service/attestation/attestation-service/service/src/lib.rs + create mode 100644 service/attestation/attestation-service/service/src/main.rs + create mode 100644 service/attestation/attestation-service/service/src/restapi/mod.rs + create mode 100644 service/attestation/attestation-service/service/src/result/mod.rs + create mode 100644 service/attestation/attestation-service/service/src/session.rs + create mode 100644 service/attestation/attestation-service/tests/Cargo.toml + create mode 100644 service/attestation/attestation-service/tests/src/lib.rs + create mode 100644 service/attestation/attestation-service/token/Cargo.toml + create mode 100644 service/attestation/attestation-service/token/src/lib.rs + create mode 100644 service/attestation/attestation-service/verifier/Cargo.toml + create mode 100644 service/attestation/attestation-service/verifier/src/itrustee/itrustee.rs + create mode 100644 service/attestation/attestation-service/verifier/src/itrustee/mod.rs + create mode 100644 service/attestation/attestation-service/verifier/src/lib.rs + create mode 100644 service/attestation/attestation-service/verifier/src/virtcca/ima.rs + create mode 100644 service/attestation/attestation-service/verifier/src/virtcca/mod.rs + create mode 100644 service/attestation/attestation-types/Cargo.toml + create mode 100644 service/attestation/attestation-types/src/lib.rs + +diff --git a/service/attestation/.gitignore b/service/attestation/.gitignore +new file mode 100644 +index 0000000..8094f6e +--- /dev/null ++++ b/service/attestation/.gitignore +@@ -0,0 +1,3 @@ ++.vscode ++target ++Cargo.lock +diff --git a/service/attestation/attestation-agent/Cargo.toml b/service/attestation/attestation-agent/Cargo.toml +new file mode 100644 +index 0000000..bdc7b12 +--- /dev/null ++++ b/service/attestation/attestation-agent/Cargo.toml +@@ -0,0 +1,29 @@ ++[workspace] ++resolver = "2" ++members = [ ++ "agent", ++ "attester", ++ "token" ++] ++ ++[workspace.dependencies] ++anyhow = "1.0" ++config = "0.14.0" ++serde = { version = "1.0", features = ["derive"] } ++serde_json = "1.0" ++rand = "0.8.5" ++base64-url = "3.0.0" ++async-trait = "0.1.78" ++tokio = {version = "1.0", features = ["rt"]} ++log = "0.4.14" ++env_logger = "0.9" ++safer-ffi = {version = "0.1.8", features = ["alloc"]} ++futures = "0.3.30" ++reqwest = { version = "0.12", features = ["cookies", "json"] } ++jsonwebtoken = "9.3.0" ++thiserror = "1.0" ++actix-web = "4.5" ++clap = { version = "4.5.7", features = ["derive"] } ++ ++verifier = {path = "../attestation-service/verifier", default-features = false} ++attestation-types = {path = "../attestation-types"} +diff --git a/service/attestation/attestation-agent/README.md b/service/attestation/attestation-agent/README.md +new file mode 100644 +index 0000000..0157e59 +--- /dev/null ++++ b/service/attestation/attestation-agent/README.md +@@ -0,0 +1,5 @@ ++# Attestation Agent ++The Attestation Agent is deployed on the TEE node, provide get_evidence, get_token, verify_evidece interface, etc. ++ ++# Overview ++TODO +diff --git a/service/attestation/attestation-agent/agent/Cargo.toml b/service/attestation/attestation-agent/agent/Cargo.toml +new file mode 100644 +index 0000000..e29f89b +--- /dev/null ++++ b/service/attestation/attestation-agent/agent/Cargo.toml +@@ -0,0 +1,47 @@ ++[package] ++name = "attestation-agent" ++version = "0.1.0" ++edition = "2021" ++ ++[[bin]] ++name = "aa-test" ++ ++[[bin]] ++name = "generate-headers" ++required-features = ["headers"] ++ ++[lib] ++name = "attestation_agent" ++crate-type = ["lib", "cdylib"] ++ ++[features] ++no_as = [] ++itrustee-attester = ["attester/itrustee-attester"] ++virtcca-attester = ["attester/virtcca-attester"] ++all-attester = ["attester/itrustee-attester", "attester/virtcca-attester"] ++itrustee-verifier = ["verifier/itrustee-verifier"] ++virtcca-verifier = ["verifier/virtcca-verifier"] ++all-verifier = ["verifier/itrustee-verifier", "verifier/virtcca-verifier"] ++headers = ["safer-ffi/headers"] ++ ++[dependencies] ++anyhow.workspace = true ++config.workspace = true ++serde.workspace = true ++serde_json.workspace = true ++rand.workspace = true ++async-trait.workspace = true ++tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } ++log.workspace = true ++env_logger.workspace = true ++safer-ffi.workspace = true ++futures.workspace = true ++reqwest = { workspace = true, features = ["json"] } ++base64-url.workspace = true ++thiserror.workspace = true ++actix-web.workspace = true ++clap.workspace = true ++ ++attester = { path = "../attester" } ++token_verifier = { path = "../token" } ++verifier = { workspace = true, features = ["no_as"], optional = true } +diff --git a/service/attestation/attestation-agent/agent/attestation-agent.conf b/service/attestation/attestation-agent/agent/attestation-agent.conf +new file mode 100644 +index 0000000..0d68972 +--- /dev/null ++++ b/service/attestation/attestation-agent/agent/attestation-agent.conf +@@ -0,0 +1,7 @@ ++{ ++ "svr_url": "http://192.168.66.88:8888", ++ "token_cfg": { ++ "cert": "/home/cert/as_cert.pem", ++ "iss": "oeas" ++ } ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs b/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs +new file mode 100644 +index 0000000..58fc389 +--- /dev/null ++++ b/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs +@@ -0,0 +1,195 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! This is a test bin, test get evidence and verify ++//! on kunpeng platform, libqca has white ta lists, need copy target/debug/attestation-agent to /vendor/bin/ ++use tokio; ++use env_logger; ++use serde_json::json; ++use reqwest; ++ ++const TEST_THREAD_NUM: i64 = 1; // multi thread num ++ ++#[tokio::main] ++async fn main() { ++ env_logger::init(); ++ let mut handles = Vec::with_capacity(TEST_THREAD_NUM as usize); ++ for i in 0..TEST_THREAD_NUM { ++ let t = tokio::spawn(async move {aa_proc(i).await;}); ++ handles.push(t); ++ } ++ ++ for handle in handles { ++ let _ = tokio::join!(handle); ++ } ++ println!("main stop"); ++} ++ ++async fn aa_proc(i: i64) { ++ println!("attestation_proc {} start", i); ++ ++ // get challenge ++ let client = reqwest::Client::new(); ++ let challenge_endpoint = "http://127.0.0.1:8081/challenge"; ++ let res = client ++ .get(challenge_endpoint) ++ .header("Content-Type", "application/json") ++ .header("content-length", 0) ++ //.json(&request_body) ++ .send() ++ .await ++ .unwrap(); ++ ++ let challenge = match res.status() { ++ reqwest::StatusCode::OK => { ++ let respone = res.text().await.unwrap(); ++ println!("get challenge success, AA Response: {:?}", respone); ++ respone ++ } ++ status => { ++ println!("get challenge Failed, AA Response: {:?}", status); ++ return; ++ } ++ }; ++ ++ // get evidence ++ let request_body = json!({ ++ "challenge": challenge, ++ "uuid": String::from("f68fd704-6eb1-4d14-b218-722850eb3ef0"), ++ }); ++ ++ let attest_endpoint = "http://127.0.0.1:8081/evidence"; ++ let res = client ++ .get(attest_endpoint) ++ .header("Content-Type", "application/json") ++ .json(&request_body) ++ .send() ++ .await ++ .unwrap(); ++ ++ let evidence = match res.status() { ++ reqwest::StatusCode::OK => { ++ let respone = res.text().await.unwrap(); ++ println!("get evidence success, AA Response: {:?}", respone); ++ respone ++ } ++ status => { ++ println!("get evidence Failed, AA Response: {:?}", status); ++ return; ++ } ++ }; ++ // verify evidence with no challenge ++ #[cfg(not(feature = "no_as"))] ++ { ++ let request_body = json!({ ++ "challenge": "", ++ "evidence": evidence, ++ }); ++ ++ let res = client ++ .post(attest_endpoint) ++ .header("Content-Type", "application/json") ++ .json(&request_body) ++ .send() ++ .await ++ .unwrap(); ++ ++ match res.status() { ++ reqwest::StatusCode::OK => { ++ let respone = res.text().await.unwrap(); ++ println!("verify evidence with no challenge success, AA Response: {:?}", respone); ++ } ++ status => { ++ println!("verify evidence with no challenge Failed, AA Response: {:?}", status); ++ } ++ } ++ } ++ // verify evidence with challenge ++ let request_body = json!({ ++ "challenge": challenge, ++ "evidence": evidence, ++ }); ++ ++ let res = client ++ .post(attest_endpoint) ++ .header("Content-Type", "application/json") ++ .json(&request_body) ++ .send() ++ .await ++ .unwrap(); ++ ++ match res.status() { ++ reqwest::StatusCode::OK => { ++ let respone = res.text().await.unwrap(); ++ println!("verify evidence success, AA Response: {:?}", respone); ++ } ++ status => { ++ println!("verify evidence Failed, AA Response: {:?}", status); ++ } ++ } ++ ++ #[cfg(not(feature = "no_as"))] ++ { ++ // get token ++ let token_endpoint = "http://127.0.0.1:8081/token"; ++ let request_body = json!({ ++ "challenge": challenge, ++ "uuid": String::from("f68fd704-6eb1-4d14-b218-722850eb3ef0"), ++ }); ++ ++ let res = client ++ .get(token_endpoint) ++ .header("Content-Type", "application/json") ++ .json(&request_body) ++ .send() ++ .await ++ .unwrap(); ++ ++ let token = match res.status() { ++ reqwest::StatusCode::OK => { ++ let respone = res.text().await.unwrap(); ++ println!("get token success, AA Response: {:?}", respone); ++ respone ++ } ++ status => { ++ println!("get token Failed, AA Response: {:?}", status); ++ return; ++ } ++ }; ++ ++ // verify token ++ let request_body = json!({ ++ "token": token, ++ }); ++ ++ let res = client ++ .post(token_endpoint) ++ .header("Content-Type", "application/json") ++ .json(&request_body) ++ .send() ++ .await ++ .unwrap(); ++ ++ match res.status() { ++ reqwest::StatusCode::OK => { ++ let respone = res.text().await.unwrap(); ++ println!("verify token success, AA Response: {:?}", respone); ++ } ++ status => { ++ println!("verify token Failed, AA Response: {:?}", status); ++ } ++ } ++ } ++ ++ ++ println!("attestation_proc {} end", i); ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/agent/src/bin/generate-headers/main.rs b/service/attestation/attestation-agent/agent/src/bin/generate-headers/main.rs +new file mode 100644 +index 0000000..f3f62c9 +--- /dev/null ++++ b/service/attestation/attestation-agent/agent/src/bin/generate-headers/main.rs +@@ -0,0 +1,14 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++fn main() -> ::std::io::Result<()> { ++ attestation_agent::generate_headers() ++} +diff --git a/service/attestation/attestation-agent/agent/src/lib.rs b/service/attestation/attestation-agent/agent/src/lib.rs +new file mode 100644 +index 0000000..4ff9b58 +--- /dev/null ++++ b/service/attestation/attestation-agent/agent/src/lib.rs +@@ -0,0 +1,387 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! Attestation Agent ++//! ++//! This crate provides some APIs to get and verify the TEE evidence. ++//! Current supports kunpeng itrustee and virtcca TEE types. ++ ++use anyhow::{Result, bail, anyhow}; ++use log; ++use serde::{Serialize, Deserialize}; ++use async_trait::async_trait; ++use std::fs::File; ++use std::path::Path; ++use rand::RngCore; ++ ++use attester::{Attester, AttesterAPIs}; ++use token_verifier::{TokenVerifyConfig, TokenVerifier, TokenRawData}; ++ ++pub mod result; ++use result::Error; ++pub type TeeClaim = serde_json::Value; ++ ++#[cfg(feature = "no_as")] ++use verifier::{Verifier, VerifierAPIs}; ++ ++#[cfg(not(feature = "no_as"))] ++use {serde_json::json, reqwest, base64_url}; ++ ++pub use attester::EvidenceRequest; ++ ++pub type AsTokenClaim = TokenRawData; ++ ++pub const DEFAULT_AACONFIG_FILE: &str = "/etc/attestation/attestation-agent/attestation-agent.conf"; ++pub struct TokenRequest { ++ pub ev_req: EvidenceRequest, ++ pub policy_id: Option>, ++} ++ ++#[async_trait] ++pub trait AttestationAgentAPIs { ++ async fn get_challenge(&self) -> Result; ++ ++ /// `get_evidence`: get hardware TEE signed evidence due to given user_data, ++ /// such as input random challenge to prevent replay attacks ++ async fn get_evidence(&self, user_data: EvidenceRequest) -> Result>; ++ ++ /// `verify_evidence`: verify the integrity of TEE evidence and evaluate the ++ /// claims against the supplied reference values ++ async fn verify_evidence(&self, ++ challenge: &[u8], ++ evidence: &[u8], ++ policy_id: Option> ++ ) -> Result; ++ ++ //#[cfg(not(feature = "no_as"))] ++ async fn get_token(&self, user_data: TokenRequest) -> Result; ++ ++ async fn verify_token(&self, token: String) -> Result; ++} ++ ++#[async_trait] ++impl AttestationAgentAPIs for AttestationAgent { ++ // no_as generate by agent; has as generate by as ++ async fn get_challenge(&self) -> Result { ++ #[cfg(feature = "no_as")] ++ return self.generate_challenge_local().await; ++ ++ #[cfg(not(feature = "no_as"))] ++ return self.get_challenge_from_as().await; ++ } ++ async fn get_evidence(&self, user_data: EvidenceRequest) -> Result> { ++ Attester::default().tee_get_evidence(user_data).await ++ } ++ async fn verify_evidence(&self, ++ challenge: &[u8], ++ evidence: &[u8], ++ _policy_id: Option> ++ ) -> Result { ++ #[cfg(feature = "no_as")] ++ { ++ let ret = Verifier::default().verify_evidence(challenge, evidence).await; ++ match ret { ++ Ok(tee_claim) => Ok(tee_claim), ++ Err(e) => { ++ log::error!("attestation agent verify evidence with no as failed:{:?}", e); ++ Err(e) ++ }, ++ } ++ } ++ ++ #[cfg(not(feature = "no_as"))] ++ { ++ let ret = self.verify_evidence_by_as(challenge, evidence, _policy_id).await; ++ match ret { ++ Ok(token) => { self.token_to_teeclaim(token).await }, ++ Err(e) => { ++ log::error!("verify evidence with as failed:{:?}", e); ++ Err(e) ++ }, ++ } ++ } ++ } ++ ++ async fn get_token(&self, user_data: TokenRequest) -> Result { ++ #[cfg(feature = "no_as")] ++ { ++ return Ok("no as in not supprot get token".to_string()); ++ } ++ // todo token 有效期内,不再重新获取报告 ++ #[cfg(not(feature = "no_as"))] ++ { ++ let evidence = self.get_evidence(user_data.ev_req.clone()).await?; ++ let challenge = &user_data.ev_req.challenge; ++ let policy_id = user_data.policy_id; ++ // request as ++ return self.verify_evidence_by_as(challenge, &evidence, policy_id).await; ++ } ++ } ++ ++ async fn verify_token(&self, token: String) -> Result { ++ let verifier = TokenVerifier::new(self.config.token_cfg.clone())?; ++ let result = verifier.verify(&token); ++ match result { ++ Ok(raw_token) => Ok(raw_token as AsTokenClaim), ++ Err(e) => bail!("verify token failed {:?}", e), ++ } ++ } ++} ++ ++#[derive(Clone, Debug, Serialize, Deserialize)] ++struct AAConfig { ++ svr_url: String, // Attestation Service url ++ token_cfg: TokenVerifyConfig, ++} ++ ++impl Default for AAConfig { ++ fn default() -> Self { ++ Self { ++ svr_url: String::from("http://127.0.0.1:8080"), ++ token_cfg: TokenVerifyConfig::default(), ++ } ++ } ++} ++ ++impl TryFrom<&Path> for AAConfig { ++ /// Load `AAConfig` from a configuration file like: ++ /// { ++ /// "svr_url": "http://127.0.0.1:8080", ++ /// "token_cfg": { ++ /// "cert": "/etc/attestation/attestation-agent/as_cert.pem", ++ /// "iss": "oeas" ++ /// } ++ /// } ++ type Error = anyhow::Error; ++ fn try_from(config_path: &Path) -> Result { ++ let file = File::open(config_path).unwrap(); ++ serde_json::from_reader::(file).map_err(|e| anyhow!("invalid aaconfig {e}")) ++ } ++} ++ ++#[derive(Debug)] ++pub struct AttestationAgent { ++ config: AAConfig, ++ client: reqwest::Client, ++} ++ ++#[allow(dead_code)] ++impl AttestationAgent { ++ pub fn new(conf_path: Option) -> Result { ++ let config = match conf_path { ++ Some(conf_path) => { ++ log::info!("Attestation Agent config file:{conf_path}"); ++ AAConfig::try_from(Path::new(&conf_path))? ++ } ++ None => { ++ log::warn!("No Attestation Agent config file specified. Using a default config"); ++ AAConfig::default() ++ } ++ }; ++ let client = reqwest::ClientBuilder::new() ++ .cookie_store(true) ++ .user_agent("attestation-agent-client") ++ .build() ++ .map_err(|e| result::Error::AttestationAgentError(format!("build http client {e}")))?; ++ Ok(AttestationAgent { ++ config, ++ client, ++ }) ++ } ++ ++ #[cfg(not(feature = "no_as"))] ++ async fn verify_evidence_by_as(&self, ++ challenge: &[u8], ++ evidence: &[u8], ++ policy_id: Option> ++ ) -> Result { ++ let request_body = json!({ ++ "challenge": base64_url::encode(challenge), ++ "evidence": base64_url::encode(evidence), ++ "policy_id": policy_id, ++ }); ++ ++ let attest_endpoint = format!("{}/attestation", self.config.svr_url); ++ let res = self.client ++ .post(attest_endpoint) ++ .header("Content-Type", "application/json") ++ .json(&request_body) ++ .send() ++ .await?; ++ ++ match res.status() { ++ reqwest::StatusCode::OK => { ++ let token = res.text().await?; ++ log::debug!("Remote Attestation success, AS Response: {:?}", token); ++ Ok(token) ++ } ++ _ => { ++ bail!("Remote Attestation Failed, AS Response: {:?}", res.text().await?); ++ } ++ } ++ } ++ ++ #[cfg(not(feature = "no_as"))] ++ async fn token_to_teeclaim(&self, token: String) -> Result { ++ let ret = self.verify_token(token).await; ++ match ret { ++ Ok(token) => { ++ let token_claim: serde_json::Value = serde_json::from_slice(token.claim.as_bytes())?; ++ let tee_claim = json!({ ++ "tee": token_claim["tee"].clone(), ++ "payload" : token_claim["tcb_status"].clone(), ++ }); ++ Ok(tee_claim as TeeClaim) ++ }, ++ Err(e) => { ++ log::error!("token to teeclaim failed:{:?}", e); ++ Err(e) ++ }, ++ } ++ } ++ ++ async fn generate_challenge_local(&self) -> Result { ++ let mut nonce: [u8; 32] = [0; 32]; ++ rand::thread_rng().fill_bytes(&mut nonce); ++ Ok(base64_url::encode(&nonce)) ++ } ++ async fn get_challenge_from_as(&self) -> Result { ++ let challenge_endpoint = format!("{}/challenge", self.config.svr_url); ++ let res = self.client ++ .get(challenge_endpoint) ++ .header("Content-Type", "application/json") ++ .header("content-length", 0) ++ //.json(&request_body) ++ .send() ++ .await?; ++ let challenge = match res.status() { ++ reqwest::StatusCode::OK => { ++ let respone = res.json().await.unwrap(); ++ log::info!("get challenge success, AS Response: {:?}", respone); ++ respone ++ } ++ status => { ++ log::info!("get challenge Failed, AS Response: {:?}", status); ++ bail!("get challenge Failed") ++ } ++ }; ++ Ok(challenge) ++ } ++} ++ ++ ++// attestation agent c interface ++use safer_ffi::prelude::*; ++use futures::executor::block_on; ++use tokio::runtime::Runtime; ++ ++#[ffi_export] ++pub fn get_report(c_challenge: Option<&repr_c::Vec>, c_ima: &repr_c::TaggedOption) -> repr_c::Vec { ++ env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); ++ log::debug!("input challenge: {:?}, ima: {:?}", c_challenge, c_ima); ++ let ima = match c_ima { ++ repr_c::TaggedOption::None => false, ++ repr_c::TaggedOption::Some(ima) => *ima, ++ }; ++ let challenge = match c_challenge { ++ None => {log::error!("challenge is null"); return Vec::new().into();}, ++ Some(cha) => cha.clone().to_vec(), ++ }; ++ ++ let input: EvidenceRequest = EvidenceRequest { ++ uuid: "f68fd704-6eb1-4d14-b218-722850eb3ef0".to_string(), ++ challenge: challenge, ++ ima: Some(ima), ++ }; ++ ++ let fut = async { ++ AttestationAgent::new(Some(DEFAULT_AACONFIG_FILE.to_string())).unwrap().get_evidence(input).await ++ }; ++ let report: Vec = match block_on(fut) { ++ Ok(report) => report, ++ Err(e) => { ++ log::error!("get report failed {:?}", e); ++ Vec::new() ++ }, ++ }; ++ ++ report.into() ++} ++ ++#[ffi_export] ++pub fn verify_report(c_challenge: Option<&repr_c::Vec>, report: Option<&repr_c::Vec>) -> repr_c::String { ++ let challenge = match c_challenge { ++ None => { ++ log::error!("challenge is null"); ++ return "".to_string().into(); ++ }, ++ Some(cha) => cha.clone().to_vec(), ++ }; ++ let report = match report { ++ None => { ++ log::error!("report is null"); ++ return "".to_string().into(); ++ }, ++ Some(report) => report.clone().to_vec(), ++ }; ++ let rt = Runtime::new().unwrap(); ++ let fut = async {AttestationAgent::new(Some(DEFAULT_AACONFIG_FILE.to_string())).unwrap().verify_evidence( ++ &challenge, &report, None).await}; ++ let ret = rt.block_on(fut); ++ ++ let ret = match ret { ++ Ok(claim) => { ++ log::debug!("claim: {:?}", claim); ++ claim.to_string() ++ }, ++ Err(e) =>{ ++ log::error!("{e}"); ++ "".to_string() ++ }, ++ }; ++ ++ return ret.into(); ++} ++ ++#[ffi_export] ++pub fn free_rust_vec(vec: repr_c::Vec) { ++ drop(vec); ++} ++ ++// The following function is only necessary for the header generation. ++#[cfg(feature = "headers")] ++pub fn generate_headers() -> ::std::io::Result<()> { ++ ::safer_ffi::headers::builder() ++ .to_file("./c_header/rust_attestation_agent.h")? ++ .generate() ++} ++ ++ ++#[cfg(test)] ++mod tests { ++ use crate::*; ++ ++ #[test] ++ fn aa_new_no_conf_path() { ++ let aa = AttestationAgent::new(None).unwrap(); ++ assert_eq!(aa.config.svr_url, "http://127.0.0.1:8080"); ++ assert_eq!(aa.config.token_cfg.cert, "/etc/attestation/attestation-agent/as_cert.pem"); ++ assert_eq!(aa.config.token_cfg.iss, "openEulerAS"); ++ } ++ ++ #[test] ++ fn aa_new_with_example_conf() { ++ let aa = AttestationAgent::new(Some("attestation-agent.conf".to_string())).unwrap(); ++ assert_eq!(aa.config.token_cfg.cert, "/home/cert/as_cert.pem"); ++ assert_eq!(aa.config.token_cfg.iss, "oeas"); ++ } ++} +diff --git a/service/attestation/attestation-agent/agent/src/main.rs b/service/attestation/attestation-agent/agent/src/main.rs +new file mode 100644 +index 0000000..76e63dc +--- /dev/null ++++ b/service/attestation/attestation-agent/agent/src/main.rs +@@ -0,0 +1,67 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use attestation_agent::{AttestationAgent, DEFAULT_AACONFIG_FILE}; ++mod restapi; ++use restapi::{get_challenge, get_evidence, verify_evidence, get_token, verify_token}; ++ ++use anyhow::Result; ++use env_logger; ++use actix_web::{web, App, HttpServer, HttpResponse}; ++use std::{net::{SocketAddr, IpAddr, Ipv4Addr}, sync::Arc}; ++use tokio::sync::RwLock; ++use clap::{Parser, command, arg}; ++ ++const DEFAULT_SOCKETADDR: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081); ++ ++#[derive(Parser, Debug)] ++#[command(version, about, long_about = None)] ++struct Cli { ++ /// Socket address to listen on ++ #[arg(short, long, default_value_t = DEFAULT_SOCKETADDR)] ++ socketaddr: SocketAddr, ++ ++ /// Load `AAConfig` from a configuration file like: ++ /// { ++ /// "svr_url": "http://127.0.0.1:8080", ++ /// "token_cfg": { ++ /// "cert": "/etc/attestation/attestation-agent/as_cert.pem", ++ /// "iss": "oeas" ++ /// } ++ /// } ++ #[arg(short, long, default_value_t = DEFAULT_AACONFIG_FILE.to_string())] ++ config: String, ++} ++ ++#[actix_web::main] ++async fn main() -> Result<()> { ++ env_logger::init_from_env(env_logger::Env::new().default_filter_or("debug")); ++ ++ let cli = Cli::parse(); ++ let server = AttestationAgent::new(Some(cli.config)).unwrap(); ++ ++ let service = web::Data::new(Arc::new(RwLock::new(server))); ++ HttpServer::new(move || { ++ App::new() ++ .app_data(web::Data::clone(&service)) ++ .service(get_challenge) ++ .service(get_evidence) ++ .service(verify_evidence) ++ .service(get_token) ++ .service(verify_token) ++ .default_service(web::to(|| HttpResponse::NotFound())) ++ }) ++ .bind((cli.socketaddr.ip().to_string(), cli.socketaddr.port()))? ++ .run() ++ .await?; ++ ++ Ok(()) ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/agent/src/restapi/mod.rs b/service/attestation/attestation-agent/agent/src/restapi/mod.rs +new file mode 100644 +index 0000000..490242a +--- /dev/null ++++ b/service/attestation/attestation-agent/agent/src/restapi/mod.rs +@@ -0,0 +1,140 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use attestation_agent::{AttestationAgent, AttestationAgentAPIs, TokenRequest}; ++use attestation_agent::result::Result; ++ ++use actix_web::{ post, get, web, HttpResponse}; ++use attester::EvidenceRequest; ++use serde::{Deserialize, Serialize}; ++use std::sync::Arc; ++use tokio::sync::RwLock; ++use log; ++use base64_url; ++ ++#[derive(Deserialize, Serialize, Debug)] ++struct GetChallengeRequest {} ++ ++#[get("/challenge")] ++pub async fn get_challenge( ++ //_request: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ //let request = request.0; ++ log::debug!("get challenge request"); ++ let challenge = agent.read().await.get_challenge().await?; ++ ++ Ok(HttpResponse::Ok().body(challenge)) ++} ++ ++#[derive(Deserialize, Serialize, Debug)] ++struct GetEvidenceRequest { ++ challenge: String, ++ uuid: String, ++ ima: Option, ++} ++ ++#[get("/evidence")] ++pub async fn get_evidence( ++ request: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ let request = request.0; ++ log::debug!("get evidence request: {:?}", request); ++ let challenge = base64_url::decode(&request.challenge).expect("base64 decode challenge"); ++ let uuid = request.uuid; ++ let ima = request.ima; ++ let input = EvidenceRequest { ++ uuid: uuid, ++ challenge: challenge, ++ ima: ima, ++ }; ++ let evidence = agent.read().await.get_evidence(input).await?; ++ ++ ++ Ok(HttpResponse::Ok().body(evidence)) ++} ++ ++#[derive(Deserialize, Serialize, Debug)] ++struct VerifyEvidenceRequest { ++ challenge: String, ++ evidence: String, ++ policy_id: Option>, ++} ++#[post("/evidence")] ++pub async fn verify_evidence( ++ request: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ let request = request.0; ++ log::debug!("verify evidence request: {:?}", request); ++ let challenge = base64_url::decode(&request.challenge).expect("base64 decode challenge"); ++ let evidence = request.evidence; ++ let policy_id = request.policy_id; ++ ++ let claim = agent.read().await.verify_evidence(&challenge, evidence.as_bytes(), policy_id).await?; ++ let string_claim = serde_json::to_string(&claim)?; ++ ++ Ok(HttpResponse::Ok().body(string_claim)) ++} ++ ++#[derive(Deserialize, Serialize, Debug)] ++struct GetTokenRequest { ++ challenge: String, ++ uuid: String, ++ ima: Option, ++ policy_id: Option>, ++} ++ ++#[get("/token")] ++pub async fn get_token( ++ request: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ let request = request.0; ++ log::debug!("get token request: {:?}", request); ++ let challenge = base64_url::decode(&request.challenge).expect("base64 decode challenge"); ++ let uuid = request.uuid; ++ let ima = request.ima; ++ let policy_id = request.policy_id; ++ let ev = EvidenceRequest { ++ uuid: uuid, ++ challenge: challenge, ++ ima: ima, ++ }; ++ let input = TokenRequest { ++ ev_req: ev, ++ policy_id: policy_id, ++ }; ++ ++ let token = agent.read().await.get_token(input).await?; ++ ++ ++ Ok(HttpResponse::Ok().body(token)) ++} ++ ++#[derive(Deserialize, Serialize, Debug)] ++struct VerifyTokenRequest { ++ token: String, ++} ++#[post("/token")] ++pub async fn verify_token( ++ request: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ let request = request.0; ++ log::debug!("verify token request: {:?}", request); ++ ++ let claim = agent.read().await.verify_token(request.token).await?; ++ let string_claim = serde_json::to_string(&claim)?; ++ ++ Ok(HttpResponse::Ok().body(string_claim)) ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/agent/src/result/mod.rs b/service/attestation/attestation-agent/agent/src/result/mod.rs +new file mode 100644 +index 0000000..f06f064 +--- /dev/null ++++ b/service/attestation/attestation-agent/agent/src/result/mod.rs +@@ -0,0 +1,50 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use actix_web::{body::BoxBody, HttpResponse, ResponseError}; ++ ++pub type Result = std::result::Result; ++ ++/// libdevice error ++#[derive(Debug, thiserror::Error)] ++#[non_exhaustive] ++#[allow(missing_docs)] ++pub enum Error { ++ #[error("IO error: {source:?}")] ++ Io { ++ #[from] ++ source: std::io::Error, ++ }, ++ ++ #[error("Web error: {source:?}")] ++ Web { ++ #[from] ++ source: actix_web::error::Error, ++ }, ++ ++ #[error("Deserialize error: {source:?}")] ++ Deserialize { ++ #[from] ++ source: serde_json::Error, ++ }, ++ ++ #[error("Attestation Agent error:{0}")] ++ AttestationAgentError(String), ++ ++ #[error(transparent)] ++ Other(#[from] anyhow::Error), ++} ++ ++impl ResponseError for Error { ++ fn error_response(&self) -> actix_web::HttpResponse { ++ HttpResponse::InternalServerError().body(BoxBody::new(format!("{self:#?}"))) ++ } ++} +diff --git a/service/attestation/attestation-agent/attester/Cargo.toml b/service/attestation/attestation-agent/attester/Cargo.toml +new file mode 100644 +index 0000000..a7dae2a +--- /dev/null ++++ b/service/attestation/attestation-agent/attester/Cargo.toml +@@ -0,0 +1,18 @@ ++[package] ++name = "attester" ++version = "0.1.0" ++edition = "2021" ++ ++[features] ++itrustee-attester = [ "base64-url", "rand" ] ++virtcca-attester = [] ++ ++[dependencies] ++anyhow.workspace = true ++serde.workspace = true ++serde_json.workspace = true ++rand = { workspace = true, optional = true } ++base64-url = { workspace = true, optional = true } ++async-trait.workspace = true ++log.workspace = true ++attestation-types.workspace = true +diff --git a/service/attestation/attestation-agent/attester/src/itrustee/itrustee.rs b/service/attestation/attestation-agent/attester/src/itrustee/itrustee.rs +new file mode 100644 +index 0000000..9a711c2 +--- /dev/null ++++ b/service/attestation/attestation-agent/attester/src/itrustee/itrustee.rs +@@ -0,0 +1,51 @@ ++/* automatically generated by rust-bindgen 0.69.4 */ ++ ++#[repr(C)] ++#[derive(Debug, Copy, Clone)] ++pub struct ra_buffer_data { ++ pub size: ::std::os::raw::c_uint, ++ pub buf: *mut ::std::os::raw::c_uchar, ++} ++#[test] ++fn bindgen_test_layout_ra_buffer_data() { ++ const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); ++ let ptr = UNINIT.as_ptr(); ++ assert_eq!( ++ ::std::mem::size_of::(), ++ 16usize, ++ concat!("Size of: ", stringify!(ra_buffer_data)) ++ ); ++ assert_eq!( ++ ::std::mem::align_of::(), ++ 8usize, ++ concat!("Alignment of ", stringify!(ra_buffer_data)) ++ ); ++ assert_eq!( ++ unsafe { ::std::ptr::addr_of!((*ptr).size) as usize - ptr as usize }, ++ 0usize, ++ concat!( ++ "Offset of field: ", ++ stringify!(ra_buffer_data), ++ "::", ++ stringify!(size) ++ ) ++ ); ++ assert_eq!( ++ unsafe { ::std::ptr::addr_of!((*ptr).buf) as usize - ptr as usize }, ++ 8usize, ++ concat!( ++ "Offset of field: ", ++ stringify!(ra_buffer_data), ++ "::", ++ stringify!(buf) ++ ) ++ ); ++} ++ ++#[link(name = "qca")] ++extern "C" { ++ pub fn RemoteAttest( ++ in_: *mut ra_buffer_data, ++ out: *mut ra_buffer_data, ++ ) -> ::std::os::raw::c_uint; ++} +diff --git a/service/attestation/attestation-agent/attester/src/itrustee/mod.rs b/service/attestation/attestation-agent/attester/src/itrustee/mod.rs +new file mode 100644 +index 0000000..3fde5f7 +--- /dev/null ++++ b/service/attestation/attestation-agent/attester/src/itrustee/mod.rs +@@ -0,0 +1,130 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! itrustee tee plugin ++//! ++//! Call the hardware sdk or driver to get the specific evidence ++ ++use anyhow::*; ++use serde_json; ++use std::path::Path; ++use serde::{Serialize, Deserialize}; ++use base64_url; ++use log; ++ ++use crate::EvidenceRequest; ++ ++mod itrustee; ++ ++#[derive(Debug, Default)] ++pub struct ItrusteeAttester {} ++ ++impl ItrusteeAttester { ++ pub async fn tee_get_evidence(&self, user_data: EvidenceRequest) -> Result { ++ let ret = itrustee_provision(); ++ if ret.is_err() { ++ log::error!("itrustee attester provision failed"); ++ bail!("itrustee attester provision failed"); ++ } ++ ++ itrustee_get_evidence(user_data) ++ } ++} ++ ++pub fn detect_platform() -> bool { ++ Path::new("/usr/bin/tee").exists() ++} ++ ++#[derive(Serialize, Deserialize)] ++struct ReportInputPayload { ++ version: String, ++ nonce: String, ++ uuid: String, ++ hash_alg: String, ++ with_tcb: bool, ++ request_key: bool, ++} ++ ++#[derive(Serialize, Deserialize)] ++struct ItrusteeInput { ++ handler: String, ++ payload: ReportInputPayload, ++} ++ ++fn itrustee_get_evidence(user_data: EvidenceRequest) -> Result { ++ let payload = ReportInputPayload { ++ nonce: base64_url::encode(&user_data.challenge), ++ uuid: user_data.uuid, ++ with_tcb: false, ++ request_key: true, ++ version: String::from("TEE.RA.1.0"), ++ hash_alg: String::from("HS256"), ++ }; ++ ++ let itrustee_input: ItrusteeInput = ItrusteeInput { ++ handler: String::from("report-input"), ++ payload: payload, ++ }; ++ let mut buf = serde_json::to_string(&itrustee_input)?; ++ let mut input = itrustee::ra_buffer_data { ++ size: buf.len() as ::std::os::raw::c_uint, ++ buf: buf.as_mut_ptr() as *mut ::std::os::raw::c_uchar, ++ }; ++ ++ let mut report = Vec::new(); ++ report.resize(0x3000, b'\0'); ++ let mut output = itrustee::ra_buffer_data { ++ size: report.len() as ::std::os::raw::c_uint, ++ buf: report.as_mut_ptr() as *mut ::std::os::raw::c_uchar, ++ }; ++ ++ unsafe { ++ let ret = itrustee::RemoteAttest(&mut input, &mut output); ++ if ret != 0 { ++ log::error!("itrustee get report failed, ret:{}", ret); ++ bail!("itrustee get report failed, ret:{}", ret); ++ } ++ let out_len: usize = output.size.try_into()?; ++ report.set_len(out_len); ++ } ++ let str_report = String::from_utf8(report)?; ++ ++ Ok(str_report) ++} ++ ++fn itrustee_provision() -> Result<()> { ++ let json = r#"{"handler":"provisioning-input","payload":{"version":"TEE.RA.1.0","scenario":"sce_no_as","hash_alg":"HS256"}}"#; ++ ++ let provision_input: serde_json::Value = serde_json::from_str(json)?; ++ let mut provision_input = provision_input.to_string(); ++ ++ let mut input = itrustee::ra_buffer_data { ++ size: provision_input.len() as ::std::os::raw::c_uint, ++ buf: provision_input.as_mut_ptr() as *mut ::std::os::raw::c_uchar, ++ }; ++ ++ let mut report = Vec::new(); ++ report.resize(0x3000, b'\0'); ++ ++ let mut output = itrustee::ra_buffer_data { ++ size: report.len() as ::std::os::raw::c_uint, ++ buf: report.as_mut_ptr() as *mut ::std::os::raw::c_uchar, ++ }; ++ unsafe { ++ let ret = itrustee::RemoteAttest(&mut input, &mut output); ++ if ret != 0 { ++ log::error!("itrustee provision failed, ret:{}", ret); ++ bail!("itrustee provision failed, ret:{}", ret); ++ } ++ } ++ Ok(()) ++} +diff --git a/service/attestation/attestation-agent/attester/src/lib.rs b/service/attestation/attestation-agent/attester/src/lib.rs +new file mode 100644 +index 0000000..3c02946 +--- /dev/null ++++ b/service/attestation/attestation-agent/attester/src/lib.rs +@@ -0,0 +1,79 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! attester ++//! ++//! This crate provides unified APIs to get TEE evidence. ++ ++use anyhow::*; ++use async_trait::async_trait; ++use log; ++use attestation_types::{TeeType, Evidence}; ++ ++#[cfg(feature = "itrustee-attester")] ++mod itrustee; ++ ++#[cfg(feature = "virtcca-attester")] ++pub mod virtcca; ++ ++#[derive(Debug, Clone)] ++pub struct EvidenceRequest { ++ pub uuid: String, ++ pub challenge: Vec, ++ pub ima: Option, ++} ++ ++#[async_trait] ++pub trait AttesterAPIs { ++ /// Call tee plugin to get the hardware evidence. ++ /// Automatically detect the TEE type of the current running environment. ++ async fn tee_get_evidence(&self, user_data: EvidenceRequest) -> Result>; ++} ++ ++#[derive(Default)] ++pub struct Attester {} ++ ++ ++const MAX_CHALLENGE_LEN: usize = 64; ++ ++#[async_trait] ++impl AttesterAPIs for Attester { ++ async fn tee_get_evidence(&self, _user_data: EvidenceRequest) -> Result> { ++ let len = _user_data.challenge.len(); ++ if len <= 0 || len > MAX_CHALLENGE_LEN { ++ log::error!("challenge len is error, expecting 0 < len <= {}, got {}", MAX_CHALLENGE_LEN, len); ++ bail!("challenge len is error, expecting 0 < len <= {}, got {}", MAX_CHALLENGE_LEN, len); ++ } ++ #[cfg(feature = "itrustee-attester")] ++ if itrustee::detect_platform() { ++ let evidence = itrustee::ItrusteeAttester::default().tee_get_evidence(_user_data).await?; ++ let aa_evidence = Evidence { ++ tee: TeeType::Itrustee, ++ evidence: evidence, ++ }; ++ let evidence = serde_json::to_vec(&aa_evidence)?; ++ ++ return Ok(evidence); ++ } ++ #[cfg(feature = "virtcca-attester")] ++ if virtcca::detect_platform() { ++ let evidence = virtcca::VirtccaAttester::default().tee_get_evidence(_user_data).await?; ++ let aa_evidence = Evidence { ++ tee: TeeType::Virtcca, ++ evidence: evidence, ++ }; ++ let evidence = serde_json::to_vec(&aa_evidence)?; ++ return Ok(evidence); ++ } ++ bail!("unkown tee platform"); ++ } ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/attester/src/virtcca/mod.rs b/service/attestation/attestation-agent/attester/src/virtcca/mod.rs +new file mode 100644 +index 0000000..c981d91 +--- /dev/null ++++ b/service/attestation/attestation-agent/attester/src/virtcca/mod.rs +@@ -0,0 +1,93 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! virtcca tee plugin ++//! ++//! Call the hardware sdk or driver to get the specific evidence ++ ++use anyhow::{Result, bail}; ++use std::path::Path; ++use log; ++use attestation_types::VirtccaEvidence; ++ ++use crate::EvidenceRequest; ++use crate::virtcca::virtcca::tsi_free_ctx; ++use self::virtcca::{tsi_new_ctx, get_attestation_token, get_dev_cert}; ++ ++mod virtcca; ++ ++#[derive(Debug, Default)] ++pub struct VirtccaAttester {} ++ ++ ++impl VirtccaAttester { ++ pub async fn tee_get_evidence(&self, user_data: EvidenceRequest) -> Result { ++ let evidence = virtcca_get_token(user_data)?; ++ let evidence = serde_json::to_string(&evidence)?; ++ Ok(evidence) ++ } ++} ++ ++pub fn detect_platform() -> bool { ++ Path::new("/dev/tsi").exists() ++} ++ ++ ++fn virtcca_get_token(user_data: EvidenceRequest) -> Result { ++ unsafe { ++ let ctx = tsi_new_ctx(); ++ ++ let mut challenge = user_data.challenge.to_vec(); ++ let p_challenge = challenge.as_mut_ptr() as *mut ::std::os::raw::c_uchar; ++ let challenge_len = challenge.len() as usize; ++ let mut token = Vec::new(); ++ token.resize(4096, b'\0'); ++ let p_token = token.as_mut_ptr() as *mut ::std::os::raw::c_uchar; ++ let mut token_len = token.len(); ++ let p_token_len = &mut token_len as *mut usize; ++ let ret = get_attestation_token(ctx, p_challenge, challenge_len, p_token, p_token_len); ++ if ret != 0 { ++ log::error!("virtcca get attestation token failed {}", ret); ++ bail!("virtcca get attestation token failed {}", ret); ++ } ++ token.set_len(token_len); ++ ++ let mut dev_cert = Vec::new(); ++ dev_cert.resize(4096, b'\0'); ++ let p_dev_cert = dev_cert.as_mut_ptr() as *mut ::std::os::raw::c_uchar; ++ let mut dev_cert_len = dev_cert.len(); ++ let p_dev_cert_len = &mut dev_cert_len as *mut usize; ++ let ret = get_dev_cert(ctx, p_dev_cert, p_dev_cert_len); ++ if ret != 0 { ++ log::error!("get dev cert failed {}", ret); ++ bail!("get dev cert failed {}", ret); ++ } ++ dev_cert.set_len(dev_cert_len); ++ ++ let with_ima = match user_data.ima { ++ Some(ima) => ima, ++ None => false, ++ }; ++ let ima_log = match with_ima { ++ true => Some(std::fs::read("/sys/kernel/security/ima/binary_runtime_measurements").unwrap()), ++ false => None, ++ }; ++ ++ let evidence = VirtccaEvidence { ++ evidence: token, ++ dev_cert: dev_cert, ++ ima_log: ima_log, ++ }; ++ let _ = tsi_free_ctx(ctx); ++ Ok(evidence) ++ } ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/attester/src/virtcca/virtcca.rs b/service/attestation/attestation-agent/attester/src/virtcca/virtcca.rs +new file mode 100644 +index 0000000..33318c7 +--- /dev/null ++++ b/service/attestation/attestation-agent/attester/src/virtcca/virtcca.rs +@@ -0,0 +1,109 @@ ++/* automatically generated by rust-bindgen 0.69.4 */ ++#[allow(non_camel_case_types)] ++pub type wchar_t = ::std::os::raw::c_int; ++#[allow(dead_code)] ++#[repr(C)] ++#[repr(align(16))] ++#[derive(Debug, Copy, Clone)] ++pub struct max_align_t { ++ pub __clang_max_align_nonce1: ::std::os::raw::c_longlong, ++ pub __bindgen_padding_0: u64, ++ pub __clang_max_align_nonce2: u128, ++} ++#[test] ++fn bindgen_test_layout_max_align_t() { ++ const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); ++ let ptr = UNINIT.as_ptr(); ++ assert_eq!( ++ ::std::mem::size_of::(), ++ 32usize, ++ concat!("Size of: ", stringify!(max_align_t)) ++ ); ++ assert_eq!( ++ ::std::mem::align_of::(), ++ 16usize, ++ concat!("Alignment of ", stringify!(max_align_t)) ++ ); ++ assert_eq!( ++ unsafe { ::std::ptr::addr_of!((*ptr).__clang_max_align_nonce1) as usize - ptr as usize }, ++ 0usize, ++ concat!( ++ "Offset of field: ", ++ stringify!(max_align_t), ++ "::", ++ stringify!(__clang_max_align_nonce1) ++ ) ++ ); ++ assert_eq!( ++ unsafe { ::std::ptr::addr_of!((*ptr).__clang_max_align_nonce2) as usize - ptr as usize }, ++ 16usize, ++ concat!( ++ "Offset of field: ", ++ stringify!(max_align_t), ++ "::", ++ stringify!(__clang_max_align_nonce2) ++ ) ++ ); ++} ++#[repr(C)] ++#[derive(Debug, Copy, Clone)] ++pub struct tsi_ctx { ++ pub fd: wchar_t, ++} ++#[test] ++fn bindgen_test_layout_tsi_ctx() { ++ const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); ++ let ptr = UNINIT.as_ptr(); ++ assert_eq!( ++ ::std::mem::size_of::(), ++ 4usize, ++ concat!("Size of: ", stringify!(tsi_ctx)) ++ ); ++ assert_eq!( ++ ::std::mem::align_of::(), ++ 4usize, ++ concat!("Alignment of ", stringify!(tsi_ctx)) ++ ); ++ assert_eq!( ++ unsafe { ::std::ptr::addr_of!((*ptr).fd) as usize - ptr as usize }, ++ 0usize, ++ concat!( ++ "Offset of field: ", ++ stringify!(tsi_ctx), ++ "::", ++ stringify!(fd) ++ ) ++ ); ++} ++ ++#[link(name = "vccaattestation")] ++extern "C" { ++ pub fn tsi_new_ctx() -> *mut tsi_ctx; ++} ++extern "C" { ++ pub fn tsi_free_ctx(ctx: *mut tsi_ctx); ++} ++extern "C" { ++ #[allow(dead_code)] ++ pub fn get_version( ++ ctx: *mut tsi_ctx, ++ major: *mut wchar_t, ++ minor: *mut wchar_t, ++ ) -> wchar_t; ++} ++extern "C" { ++ pub fn get_attestation_token( ++ ctx: *mut tsi_ctx, ++ challenge: *mut ::std::os::raw::c_uchar, ++ challenge_len: usize, ++ token: *mut ::std::os::raw::c_uchar, ++ token_len: *mut usize, ++ ) -> wchar_t; ++} ++extern "C" { ++ pub fn get_dev_cert( ++ ctx: *mut tsi_ctx, ++ dev_cert: *mut ::std::os::raw::c_uchar, ++ dev_cert_len: *mut usize, ++ ) -> wchar_t; ++} +diff --git a/service/attestation/attestation-agent/token/Cargo.toml b/service/attestation/attestation-agent/token/Cargo.toml +new file mode 100644 +index 0000000..aa5cafc +--- /dev/null ++++ b/service/attestation/attestation-agent/token/Cargo.toml +@@ -0,0 +1,13 @@ ++[package] ++name = "token_verifier" ++version = "0.1.0" ++edition = "2021" ++ ++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html ++ ++[dependencies] ++jsonwebtoken.workspace = true ++serde.workspace = true ++serde_json.workspace = true ++anyhow.workspace = true ++attestation-types.workspace = true +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/token/src/lib.rs b/service/attestation/attestation-agent/token/src/lib.rs +new file mode 100644 +index 0000000..50a7a7a +--- /dev/null ++++ b/service/attestation/attestation-agent/token/src/lib.rs +@@ -0,0 +1,114 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use anyhow::{Result, bail}; ++use std::path::Path; ++use serde::{Deserialize, Serialize}; ++use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation }; ++use attestation_types::Claims; ++ ++#[derive(Clone, Debug, Serialize, Deserialize)] ++pub struct TokenVerifyConfig { ++ pub cert: String, // Attestation Service cert to verify jwt token signature ++ pub iss: String, // Attestation Service name ++ //pub root_cert: String, ++} ++ ++impl Default for TokenVerifyConfig { ++ fn default() -> Self { ++ TokenVerifyConfig { ++ cert: "/etc/attestation/attestation-agent/as_cert.pem".to_string(), ++ iss: "oeas".to_string(), ++ } ++ } ++} ++pub struct TokenVerifier ++{ ++ pub config: TokenVerifyConfig, ++} ++ ++impl Default for TokenVerifier ++{ ++ fn default() -> Self { ++ TokenVerifier { ++ config: TokenVerifyConfig::default(), ++ } ++ } ++} ++ ++// 返回token的原始数据 ++#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] ++pub struct TokenRawData { ++ pub header: String, ++ pub claim: String, ++} ++ ++impl TokenVerifier { ++ pub fn new(config: TokenVerifyConfig) -> Result { ++ Ok(TokenVerifier { config }) ++ } ++ fn support_rs(alg: &Algorithm) -> bool ++ { ++ if *alg == Algorithm::RS256 || *alg == Algorithm::RS384 || *alg == Algorithm::RS512{ ++ return true; ++ } ++ return false; ++ } ++ fn support_ps(alg: &Algorithm) -> bool ++ { ++ if *alg == Algorithm::PS256 || *alg == Algorithm::PS384 || *alg == Algorithm::PS512 { ++ return true; ++ } ++ return false; ++ } ++ pub fn verify( ++ &self, ++ token: &String ++ ) -> Result { ++ let header = match decode_header(&token) { ++ Ok(h) => h, ++ Err(e) => bail!("decode jwt header error {:?}", e), ++ }; ++ let alg: Algorithm = header.alg; ++ ++ if !Self::support_rs(&alg) && !Self::support_ps(&alg) { ++ bail!("unknown algrithm {:?}", alg); ++ } ++ if !Path::new(&self.config.cert).exists() { ++ bail!("token verfify failed, {:?} cert not exist", self.config.cert); ++ } ++ let cert = std::fs::read(&self.config.cert).unwrap(); ++ ++ /* 使用配置的公钥 */ ++ let key_value: DecodingKey = match DecodingKey::from_rsa_pem(&cert) ++ { ++ Ok(key) => key, ++ Err(e) => bail!("get key from pem error {:?}", e), ++ }; ++ ++ let mut validation = Validation::new(alg); ++ validation.set_issuer(&[self.config.iss.clone()]); ++ validation.validate_exp = true; ++ ++ let data = decode::(&token, &key_value, &validation); ++ match data { ++ Ok(d) => { ++ let header = d.header.clone(); ++ let claims = d.claims.clone(); ++ Ok(TokenRawData { ++ header: serde_json::to_string(&header).unwrap(), ++ claim: serde_json::to_string(&claims).unwrap(), ++ }) ++ } ++ Err(e) => bail!("verfiy jwt failed {:?}", e), ++ } ++ } ++} +diff --git a/service/attestation/attestation-service/Cargo.toml b/service/attestation/attestation-service/Cargo.toml +new file mode 100644 +index 0000000..cf0dd87 +--- /dev/null ++++ b/service/attestation/attestation-service/Cargo.toml +@@ -0,0 +1,42 @@ ++[workspace] ++resolver = "2" ++members = [ ++ "service", ++ "verifier", ++ "token", ++ "reference", ++ "policy", ++ "tests" ++] ++ ++[workspace.dependencies] ++anyhow = "1.0.80" ++serde = "1.0" ++serde_json = "1.0" ++async-trait = "0.1.78" ++cose-rust = "0.1.7" ++ciborium = "0.2.2" ++hex = "0.4" ++openssl = "0.10.64" ++log = "0.4.14" ++futures = "0.3.30" ++rand = "0.8.5" ++ima-measurements = "0.2.0" ++fallible-iterator = "0.2.0" ++ ++actix-web = "4.5" ++env_logger = "0.9" ++tokio = { version = "1", features = ["full"] } ++strum = { version = "0.25", features = ["derive"] } ++thiserror = "1.0" ++base64-url = "3.0.0" ++base64 = "0.22.0" ++jsonwebtoken = "9.3.0" ++clap = { version = "4.5.7", features = ["derive"] } ++regorus = "0.2.2" ++sled = "0.34.7" ++lazy_static = "1.5.0" ++uuid = { version = "1.2.2", features = ["serde", "v4"] } ++scc = "2.1" ++ ++attestation-types = {path = "../attestation-types"} +diff --git a/service/attestation/attestation-service/README.md b/service/attestation/attestation-service/README.md +new file mode 100644 +index 0000000..c64e6f1 +--- /dev/null ++++ b/service/attestation/attestation-service/README.md +@@ -0,0 +1,6 @@ ++# Attestation Service ++The Attestation Service verifies hardware TEE evidence. ++The first phase aims to support Kunpeng Trustzone, virtCCA and QingTian Enclave. In the future, it will support ARM CCA, Intel TDX, Hygon CSV etc. ++ ++# Overview ++TODO +diff --git a/service/attestation/attestation-service/policy/Cargo.toml b/service/attestation/attestation-service/policy/Cargo.toml +new file mode 100644 +index 0000000..87917a4 +--- /dev/null ++++ b/service/attestation/attestation-service/policy/Cargo.toml +@@ -0,0 +1,12 @@ ++[package] ++name = "policy" ++version = "0.1.0" ++edition = "2021" ++ ++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html ++ ++[dependencies] ++regorus.workspace = true ++base64.workspace = true ++tokio.workspace = true ++futures.workspace = true +diff --git a/service/attestation/attestation-service/policy/src/lib.rs b/service/attestation/attestation-service/policy/src/lib.rs +new file mode 100644 +index 0000000..0677f45 +--- /dev/null ++++ b/service/attestation/attestation-service/policy/src/lib.rs +@@ -0,0 +1,181 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++pub mod opa; ++pub mod policy_engine; ++ ++#[cfg(test)] ++mod tests { ++ use base64::Engine; ++ use std::fs; ++ ++ use crate::{ ++ opa::OPA, ++ policy_engine::{PolicyEngine, PolicyEngineError}, ++ }; ++ ++ #[tokio::test] ++ async fn test_new_policy_engine() { ++ let policy_dir = String::from("/etc/attestation/attestation-service/policy"); ++ let ret = OPA::new(&policy_dir).await; ++ assert!(ret.is_ok()); ++ } ++ ++ #[tokio::test] ++ async fn test_new_policy_engine_dir_exist() { ++ let policy_dir = String::from("/etc/attestation/attestation-service/policy"); ++ let _ = fs::create_dir_all(&policy_dir); ++ let ret = OPA::new(&policy_dir).await; ++ assert!(ret.is_ok()); ++ } ++ ++ #[tokio::test] ++ async fn test_new_policy_engine_dir_failed() { ++ let policy_dir = String::from("/sys/invalid_dir"); ++ let ret = OPA::new(&policy_dir).await; ++ assert!(ret.is_err()); ++ if let PolicyEngineError::CreatePolicyDirError(msg) = ret.err().unwrap() { ++ assert_eq!(msg, "policy dir create failed"); ++ } else { ++ panic!("Unexpected error type"); ++ } ++ } ++ ++ #[tokio::test] ++ async fn test_set_policy() { ++ let policy_dir = String::from("/etc/attestation/attestation-service/policy"); ++ let engine = OPA::new(&policy_dir).await; ++ ++ let policy_id = "test.rego".to_string(); ++ let policy = r#"package attestation ++import rego.v1 ++expect_keys := ["RIM", "RPV"] ++input_keys := object.keys(input) ++output[exist] := input[exist] if { ++ some exist in expect_keys ++ exist in input_keys ++} ++output[exist] := null if { ++ some exist in expect_keys ++ not exist in input_keys ++} ++output["Other"] := "other" if { ++ "test" in input_keys ++}"#; ++ let _ = ++ tokio::fs::remove_file("/etc/attestation/attestation-service/policy/test.rego").await; ++ ++ let ret = engine ++ .unwrap() ++ .set_policy( ++ &policy_id, ++ &base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(policy), ++ ) ++ .await; ++ assert!(ret.is_ok()); ++ } ++ ++ #[tokio::test] ++ async fn test_get_all_policy() { ++ let policy_dir = String::from("/etc/attestation/attestation-service/policy"); ++ let engine = OPA::new(&policy_dir).await; ++ let ret = engine.unwrap().get_all_policy().await; ++ println!("{:?}", ret); ++ assert!(ret.is_ok()); ++ } ++ ++ #[tokio::test] ++ async fn test_evaluate_by_default() { ++ let policy_dir = String::from("/etc/attestation/attestation-service/policy"); ++ let engine = OPA::new(&policy_dir).await.unwrap(); ++ let refs_from_report = String::from( ++ r#"{ ++ "RIM": "7d2e49c8d29f18b748e658e7243ecf26bc292e5fee93f72af11ad9da9810142a", ++ "RPV": "igliurbwjlkfxvr3wk2kqrttyz4gds42h9sdf72dgpcw8lspts1nnmxuvqzeqyq0", ++ "test": "u4eyoqgqsiju43aooetb02j0rymx6ijhhxs5oryj8344x7kehzjrwsi3vi7wqo2y" ++ }"#, ++ ); ++ let data = String::new(); ++ let policy_id: Vec = vec![]; ++ let result = engine.evaluate(&String::from("vcca"), &refs_from_report, &data, &policy_id).await; ++ println!("{:?}", result); ++ assert!(result.is_ok()); ++ match result { ++ Ok(ret) => { ++ for i in ret.keys() { ++ println!("{} : {}", i, ret[i]); ++ } ++ } ++ Err(err) => { ++ println!("{err}"); ++ } ++ } ++ } ++ ++ #[tokio::test] ++ async fn test_evaluate_use_specified_policy() { ++ // 先设置指定的策略 ++ let policy_dir = String::from("/etc/attestation/attestation-service/policy"); ++ let engine = OPA::new(&policy_dir).await.unwrap(); ++ ++ let policy_id = "test.rego".to_string(); ++ // 该策略提取期望的基线值,如果不存在则设置为null;同时包含“test”基线,则将Other设置为"other" ++ let policy = r#"package attestation ++import rego.v1 ++expect_keys := ["RIM", "RPV"] ++input_keys := object.keys(input) ++output[exist] := input[exist] if { ++ some exist in expect_keys ++ exist in input_keys ++} ++output[exist] := null if { ++ some exist in expect_keys ++ not exist in input_keys ++} ++output["Other"] := "other" if { ++ "test" in input_keys ++}"#; ++ // 删除已重复存在的policy ++ let _ = ++ tokio::fs::remove_file("/etc/attestation/attestation-service/policy/test.rego").await; ++ ++ let ret = engine ++ .set_policy( ++ &policy_id, ++ &base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(policy), ++ ) ++ .await; ++ assert!(ret.is_ok()); ++ ++ // 使用自定义的策略进行报告评估 ++ let refs_from_report = String::from( ++ r#"{ ++ "RIM": "7d2e49c8d29f18b748e658e7243ecf26bc292e5fee93f72af11ad9da9810142a", ++ "RPV": "v598upciquf97yngfi4g2k5r9z6pyl1gcudj1vsgpn7v49ad2oafs11m0esdgv7r", ++ "test": "c4ca91mhcxwqi4ka6ysjgl8nn5hhhln9k2n7ppn3zs1jes4aohlflh5krsogqlpz" ++ }"#, ++ ); ++ let data = String::new(); ++ let policy_id: Vec = vec!["test.rego".to_string()]; ++ let result = engine.evaluate(&String::from("vcca"), &refs_from_report, &data, &policy_id).await; ++ assert!(result.is_ok()); ++ match result { ++ Ok(ret) => { ++ for i in ret.keys() { ++ println!("{} : {}", i, ret[i]); ++ } ++ } ++ Err(err) => { ++ println!("{err}"); ++ } ++ } ++ } ++} +diff --git a/service/attestation/attestation-service/policy/src/opa/default_itrustee.rego b/service/attestation/attestation-service/policy/src/opa/default_itrustee.rego +new file mode 100644 +index 0000000..f55449c +--- /dev/null ++++ b/service/attestation/attestation-service/policy/src/opa/default_itrustee.rego +@@ -0,0 +1,10 @@ ++# if create a new rego file, "output" should exist, ++# package name should be "attestation" ++package attestation ++import rego.v1 ++expect_keys := ["itrustee.ta_img", "itrustee.ta_mem"] ++input_keys := object.keys(input) ++output[exist] := input[exist] if { ++ some exist in expect_keys ++ exist in input_keys ++} +diff --git a/service/attestation/attestation-service/policy/src/opa/default_vcca.rego b/service/attestation/attestation-service/policy/src/opa/default_vcca.rego +new file mode 100644 +index 0000000..32229e4 +--- /dev/null ++++ b/service/attestation/attestation-service/policy/src/opa/default_vcca.rego +@@ -0,0 +1,10 @@ ++# if create a new rego file, "output" should exist, ++# package name should be "attestation" ++package attestation ++import rego.v1 ++expect_keys := ["vcca.cvm.rim"] ++input_keys := object.keys(input) ++output[exist] := input[exist] if { ++ some exist in expect_keys ++ exist in input_keys ++} +diff --git a/service/attestation/attestation-service/policy/src/opa/mod.rs b/service/attestation/attestation-service/policy/src/opa/mod.rs +new file mode 100644 +index 0000000..c2e1cdb +--- /dev/null ++++ b/service/attestation/attestation-service/policy/src/opa/mod.rs +@@ -0,0 +1,167 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use base64::Engine; ++use policy_engine::{PolicyEngine, PolicyEngineError}; ++use regorus::Value; ++use std::{collections::HashMap, path::PathBuf}; ++ ++use crate::policy_engine; ++ ++#[derive(Debug, Clone, PartialEq)] ++pub struct OPA { ++ policy_dir: PathBuf, ++ default_policy_dir: PathBuf, ++ default_policy_vcca: String, ++ default_policy_itrustee: String, ++} ++ ++const DEFAULT_POLICY_DIR: &str = "/etc/attestation/attestation-service/policy/"; ++const DEFAULT_VCCA_REGO: &str = "default_vcca.rego"; ++const DEFAULT_ITRUSTEE_REGO: &str = "default_itrustee.rego"; ++ ++impl PolicyEngine for OPA { ++ /// refs comes from report, by using query reference API ++ async fn evaluate( ++ &self, ++ tee: &String, ++ refs: &String, ++ data_for_policy: &String, ++ policy_id: &Vec, ++ ) -> Result, PolicyEngineError> { ++ let mut policy_id_used = policy_id.clone(); ++ let policy_path: PathBuf; ++ if policy_id_used.is_empty() { ++ if tee == "vcca" { ++ policy_id_used.push(String::from(DEFAULT_VCCA_REGO)); ++ } else if tee == "itrustee" { ++ policy_id_used.push(String::from(DEFAULT_ITRUSTEE_REGO)); ++ } else { ++ return Err(PolicyEngineError::TeeTypeUnknown(format!("tee type unknown: {tee}"))); ++ } ++ policy_path = self.default_policy_dir.clone(); ++ } else { ++ policy_path = self.policy_dir.clone(); ++ } ++ ++ let mut result: HashMap = HashMap::new(); ++ for id in policy_id_used { ++ let mut path = policy_path.clone(); ++ path.push(id.clone()); ++ let engine_policy = tokio::fs::read_to_string(path.clone()).await.map_err(|err| { ++ PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)) ++ })?; ++ let mut engine = regorus::Engine::new(); ++ engine.add_policy(id.clone(), engine_policy).map_err(|err| { ++ PolicyEngineError::EngineLoadPolicyError(format!("policy load failed: {}", err)) ++ })?; ++ ++ let input = Value::from_json_str(refs).map_err(|err| { ++ PolicyEngineError::InvalidReport(format!("report to Value failed: {}", err)) ++ })?; ++ engine.set_input(input); ++ ++ if !data_for_policy.is_empty() { ++ let data = Value::from_json_str(data_for_policy).map_err(|err| { ++ PolicyEngineError::EngineLoadDataError(format!("data to Value failed: {}", err)) ++ })?; ++ engine.add_data(data).map_err(|err| { ++ PolicyEngineError::EngineLoadDataError(format!("engine add data failed: {}", err)) ++ })?; ++ } ++ ++ let eval = engine ++ .eval_rule(String::from("data.attestation.output")) ++ .map_err(|err| { ++ PolicyEngineError::EngineEvalError(format!("engine eval error:{}", err)) ++ })?; ++ result.insert(id, eval.to_string()); ++ } ++ Ok(result) ++ } ++ async fn set_policy( ++ &self, ++ policy_id: &String, ++ policy: &String, ++ ) -> Result<(), PolicyEngineError> { ++ let raw = base64::engine::general_purpose::URL_SAFE_NO_PAD ++ .decode(policy) ++ .map_err(|err| PolicyEngineError::InvalidPolicy(format!("policy decode failed: {}", err)))?; ++ ++ let mut policy_file: PathBuf = self.policy_dir.clone(); ++ policy_file.push(format!("{}", policy_id)); ++ tokio::fs::write(policy_file.as_path(), &raw) ++ .await ++ .map_err(|err| PolicyEngineError::WritePolicyError(format!("write policy failed: {}", err)))?; ++ Ok(()) ++ } ++ ++ async fn get_all_policy(&self) -> Result, PolicyEngineError> { ++ let mut items = tokio::fs::read_dir(&self.policy_dir.as_path()) ++ .await ++ .map_err(|err| PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)))?; ++ let mut policies = HashMap::new(); ++ while let Some(item) = items ++ .next_entry() ++ .await ++ .map_err(|err| PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)))? ++ { ++ let path = item.path(); ++ if path.extension().and_then(std::ffi::OsStr::to_str) == Some("rego") { ++ let content: String = ++ tokio::fs::read_to_string(path.clone()).await.map_err(|err| { ++ PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)) ++ })?; ++ let name = path ++ .file_stem() ++ .ok_or(PolicyEngineError::ReadPolicyError( ++ "get policy name failed".to_string(), ++ ))? ++ .to_str() ++ .ok_or(PolicyEngineError::ReadPolicyError( ++ "get policy name failed".to_string(), ++ ))?; ++ let content = ++ base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(content.as_bytes()); ++ policies.insert(name.to_string() + ".rego", content); ++ } ++ } ++ return Ok(policies); ++ } ++ ++ async fn get_policy(&self, policy_id: &String) -> Result { ++ let mut policy_file: PathBuf = self.policy_dir.clone(); ++ policy_file.push(format!("{}", policy_id)); ++ let policy = tokio::fs::read(policy_file.as_path()) ++ .await ++ .map_err(|err| PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)))?; ++ let policy_base64 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(policy); ++ Ok(policy_base64) ++ } ++} ++ ++impl OPA { ++ pub async fn new(policy_dir: &String) -> Result { ++ let policy_path = PathBuf::from(policy_dir); ++ if !policy_path.as_path().exists() { ++ std::fs::create_dir_all(&policy_dir).map_err(|err| { ++ PolicyEngineError::CreatePolicyDirError(format!("policy dir create failed: {}", err)) ++ })?; ++ } ++ ++ Ok(OPA { ++ policy_dir: policy_path, ++ default_policy_dir: PathBuf::from(DEFAULT_POLICY_DIR), ++ default_policy_vcca: String::from(DEFAULT_VCCA_REGO), ++ default_policy_itrustee: String::from(DEFAULT_ITRUSTEE_REGO), ++ }) ++ } ++} +diff --git a/service/attestation/attestation-service/policy/src/policy_engine.rs b/service/attestation/attestation-service/policy/src/policy_engine.rs +new file mode 100644 +index 0000000..a03a8cc +--- /dev/null ++++ b/service/attestation/attestation-service/policy/src/policy_engine.rs +@@ -0,0 +1,73 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use std::{collections::HashMap, fmt::Display}; ++#[derive(Debug)] ++pub enum PolicyEngineError { ++ InvalidPolicy(String), ++ InvalidPolicyId(String), ++ InvalidPolicyDir(String), ++ InvalidReport(String), ++ CreatePolicyDirError(String), ++ CreatePolicyError(String), ++ ReadPolicyError(String), ++ WritePolicyError(String), ++ EngineLoadPolicyError(String), ++ EngineLoadDataError(String), ++ EngineEvalError(String), ++ TeeTypeUnknown(String), ++} ++impl Display for PolicyEngineError { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ match self { ++ PolicyEngineError::InvalidPolicy(msg) => write!(f, "invalid policy: {}", msg), ++ PolicyEngineError::InvalidPolicyId(msg) => write!(f, "invalid policy id: {}", msg), ++ PolicyEngineError::InvalidReport(msg) => write!(f, "invalid report: {}", msg), ++ PolicyEngineError::CreatePolicyDirError(msg) => { ++ write!(f, "create policy dir error: {}", msg) ++ } ++ PolicyEngineError::CreatePolicyError(msg) => write!(f, "create policy error: {}", msg), ++ PolicyEngineError::ReadPolicyError(msg) => write!(f, "read policy error: {}", msg), ++ PolicyEngineError::InvalidPolicyDir(msg) => write!(f, "invalid policy error: {}", msg), ++ PolicyEngineError::WritePolicyError(msg) => write!(f, "write policy error: {}", msg), ++ PolicyEngineError::EngineLoadPolicyError(msg) => { ++ write!(f, "engine load policy error: {}", msg) ++ } ++ PolicyEngineError::EngineLoadDataError(msg) => { ++ write!(f, "engine read data error: {}", msg) ++ } ++ PolicyEngineError::EngineEvalError(msg) => write!(f, "engine evaluate error: {}", msg), ++ PolicyEngineError::TeeTypeUnknown(msg) => write!(f, "tee type error: {}", msg), ++ } ++ } ++} ++ ++pub trait PolicyEngine { ++ fn evaluate( ++ &self, ++ tee: &String, ++ refs: &String, ++ data_for_policy: &String, ++ policy_id: &Vec, ++ ) -> impl std::future::Future, PolicyEngineError>> + Send; ++ fn set_policy( ++ &self, ++ policy_id: &String, ++ policy: &String, ++ ) -> impl std::future::Future> + Send; ++ fn get_all_policy( ++ &self, ++ ) -> impl std::future::Future, PolicyEngineError>> + Send; ++ fn get_policy( ++ &self, ++ policy_id: &String, ++ ) -> impl std::future::Future> + Send; ++} +diff --git a/service/attestation/attestation-service/reference/Cargo.toml b/service/attestation/attestation-service/reference/Cargo.toml +new file mode 100644 +index 0000000..b36991e +--- /dev/null ++++ b/service/attestation/attestation-service/reference/Cargo.toml +@@ -0,0 +1,16 @@ ++[package] ++name = "reference" ++version = "0.1.0" ++edition = "2021" ++ ++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html ++ ++[dependencies] ++serde.workspace = true ++serde_json.workspace = true ++rand.workspace = true ++base64.workspace = true ++sled.workspace = true ++openssl.workspace = true ++hex.workspace = true ++lazy_static.workspace = true +\ No newline at end of file +diff --git a/service/attestation/attestation-service/reference/src/extractor/mod.rs b/service/attestation/attestation-service/reference/src/extractor/mod.rs +new file mode 100644 +index 0000000..41f61aa +--- /dev/null ++++ b/service/attestation/attestation-service/reference/src/extractor/mod.rs +@@ -0,0 +1,30 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use crate::reference::Ref; ++use serde_json::Value; ++pub struct Extractor {} ++impl Extractor { ++ pub fn split(ref_set: &String) -> Option> { ++ // expect ref_set as a json string, like follow: ++ // {"refname1":xx,"refname2":yy} ++ let mut ret: Vec = vec![]; ++ let refs: Value = serde_json::from_str(ref_set.as_str()).ok()?; ++ for (key, val) in refs.as_object().unwrap() { ++ let ref_obj = Ref { ++ name: key.clone(), ++ value: val.clone(), ++ }; ++ ret.push(ref_obj); ++ } ++ Some(ret) ++ } ++} +diff --git a/service/attestation/attestation-service/reference/src/lib.rs b/service/attestation/attestation-service/reference/src/lib.rs +new file mode 100644 +index 0000000..4347fc1 +--- /dev/null ++++ b/service/attestation/attestation-service/reference/src/lib.rs +@@ -0,0 +1,141 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++pub mod local_fs; ++pub mod reference; ++pub mod store; ++mod extractor; ++ ++#[cfg(test)] ++mod tests { ++ use std::thread; ++ ++ use super::*; ++ use rand::{distributions::Alphanumeric, Rng}; ++ use serde_json::Value; ++ ++ #[test] ++ fn localfs_default_test() { ++ let mut ops_default = reference::ReferenceOps::default(); ++ let refs = r#"{"test1":"hash1","test2":"hash2"}"#.to_string(); ++ assert_eq!(ops_default.register(&refs), Ok(())); ++ let ref_query = ops_default.query(&refs).unwrap(); ++ println!("ref:{refs}, query:{ref_query}"); ++ assert_eq!(ref_query, refs); ++ } ++ ++ #[test] ++ fn localfs_empty_reference_test() { ++ let mut ops_default = reference::ReferenceOps::default(); ++ assert_ne!(ops_default.register(&r#""#.to_string()), Ok(())); ++ assert_eq!(ops_default.query(&r#""#.to_string()), None); ++ ++ let refs = r#"{}"#.to_string(); ++ assert_eq!(ops_default.register(&refs), Ok(())); ++ let ref_query = ops_default.query(&refs).unwrap(); ++ println!("ref:{refs}, query:{ref_query}"); ++ assert_eq!(ref_query, refs); ++ } ++ ++ #[test] ++ fn localfs_query_fail_test() { ++ let mut ops_default = reference::ReferenceOps::default(); ++ let refs = r#"{"test1":"hash1"}"#.to_string(); ++ assert_eq!(ops_default.register(&refs), Ok(())); ++ let ref_query = ops_default ++ .query(&r#"{"test":"hash1"}"#.to_string()) ++ .unwrap(); ++ println!("ref:{refs}, query:{ref_query}"); ++ assert_ne!(ref_query, refs); ++ } ++ ++ #[test] ++ fn localfs_default_complex_reference_test() { ++ let mut ops_default = reference::ReferenceOps::default(); ++ let refs = r#"{"test1": { "name1":123, "name2": "val2"},"test2":123}"#.to_string(); ++ assert_eq!(ops_default.register(&refs), Ok(())); ++ let ref_query = ops_default.query(&refs).unwrap(); ++ let json_obj: Value = serde_json::from_str(refs.as_str()).unwrap(); ++ println!("ref:{}, query:{ref_query}", json_obj.to_string()); ++ assert_eq!(ref_query, json_obj.to_string()); ++ } ++ ++ #[test] ++ fn localfs_new_test() { ++ let store = local_fs::LocalFs::new(&String::from("/var/attestation/data_new")); ++ let mut ops = reference::ReferenceOps::new(store); ++ let refs = r#"{"test1":"hash1","test2":"hash2"}"#.to_string(); ++ assert_eq!(ops.register(&refs), Ok(())); ++ let ref_query = ops.query(&refs).unwrap(); ++ println!("ref:{refs}, query:{ref_query}"); ++ assert_eq!(ref_query, refs); ++ } ++ ++ #[test] ++ fn localfs_register_reference_repeat_test() { ++ let mut ops_default = reference::ReferenceOps::default(); ++ let refs = r#"{"test1":"hash1","test2":"hash2"}"#.to_string(); ++ assert_eq!(ops_default.register(&refs), Ok(())); ++ assert_eq!(ops_default.register(&refs), Ok(())); ++ let ref_query = ops_default.query(&refs).unwrap(); ++ println!("ref:{refs}, query:{ref_query}"); ++ assert_eq!(ref_query, refs); ++ } ++ ++ #[test] ++ fn localfs_unregister_reference_test() { ++ let mut ops_default = reference::ReferenceOps::default(); ++ let refs = r#"{"name1":"hash1","name2":"hash2"}"#.to_string(); ++ assert_eq!(ops_default.register(&refs), Ok(())); ++ let ref_query = ops_default.query(&refs).unwrap(); ++ println!("ref:{refs}, query:{ref_query}"); ++ assert_eq!(ref_query, refs); ++ ++ assert_eq!(ops_default.unregister(&refs), Ok(())); ++ let ref_query = ops_default.query(&refs).unwrap(); ++ println!("ref:{refs}, query:{ref_query}"); ++ assert_ne!(refs, ref_query); ++ } ++ ++ #[test] ++ fn localfs_register_query_concurrently() { ++ let mut thread_all = vec![]; ++ let thread_cnt = 1000; ++ for i in 0..thread_cnt { ++ let seq_start = i * thread_cnt; ++ let seq_end = seq_start + thread_cnt; ++ thread_all.push(thread::spawn(move || { ++ let rng = rand::thread_rng(); ++ let mut ops_default = reference::ReferenceOps::default(); ++ ++ for i in seq_start..seq_end { ++ //key ++ let key = format!("ref{}", i); ++ //value ++ let value:String = rng.clone().sample_iter(&Alphanumeric).take(128).map(char::from).collect(); ++ let mut reference = serde_json::json!({}); ++ reference.as_object_mut().unwrap().insert(key, Value::String(value)); ++ let _ = ops_default.register(&reference.to_string()); ++ let ref_query = ops_default.query(&reference.to_string()).unwrap(); ++ println!("ref {} query {}", reference.to_string(), ref_query); ++ assert_eq!(ref_query, reference.to_string()); ++ } ++ })); ++ } ++ for hd in thread_all { ++ match hd.join() { ++ Ok(_) => {} ++ Err(_) => {assert!(false)} ++ } ++ } ++ ++ } ++} +diff --git a/service/attestation/attestation-service/reference/src/local_fs/mod.rs b/service/attestation/attestation-service/reference/src/local_fs/mod.rs +new file mode 100644 +index 0000000..1e03579 +--- /dev/null ++++ b/service/attestation/attestation-service/reference/src/local_fs/mod.rs +@@ -0,0 +1,87 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use lazy_static::lazy_static; ++use std::sync::Arc; ++use sled::Db; ++use std::ops::Deref; ++ ++use crate::store::{KvError, KvStore}; ++ ++ ++pub struct LocalFs { ++ db: Arc, ++} ++ ++impl Default for LocalFs { ++ fn default() -> Self { ++ lazy_static! { ++ static ref db_handle: Arc = Arc::new(sled::open("/etc/attestation/attestation-service/reference").unwrap()); ++ } ++ LocalFs { ++ db: db_handle.clone(), ++ } ++ } ++} ++ ++impl KvStore for LocalFs { ++ fn write(&mut self, key: &str, value: &[u8]) -> Result<(), KvError> { ++ match self.db.insert(key.as_bytes(), value) { ++ Err(_err) => { ++ return Err(KvError::Err("insert error".to_string())); ++ } ++ Ok(_) => {} ++ } ++ match self.db.flush() { ++ Err(_err) => { ++ return Err(KvError::Err("write flush error".to_string())); ++ } ++ Ok(_) => { ++ return Ok(()); ++ } ++ } ++ } ++ fn read(&mut self, key: &str) -> Option> { ++ match self.db.get(key) { ++ Ok(val) => match val { ++ Some(iv) => Some(Vec::from(iv.deref())), ++ None => None, ++ }, ++ Err(_err) => None, ++ } ++ } ++ ++ fn delete(&mut self, key: &str) -> Result<(), KvError> { ++ match self.db.remove(key.as_bytes()) { ++ Err(_err) => { ++ return Err(KvError::Err("delete fail".to_string())); ++ } ++ Ok(_) => (), ++ } ++ match self.db.flush() { ++ Err(_err) => { ++ return Err(KvError::Err("delete flush fail".to_string())); ++ } ++ Ok(_) => { ++ return Ok(()); ++ } ++ } ++ } ++} ++ ++impl LocalFs { ++ pub fn new(path: &String) -> LocalFs { ++ let lfs = LocalFs { ++ db: Arc::new(sled::open(path).unwrap()), ++ }; ++ lfs ++ } ++} +diff --git a/service/attestation/attestation-service/reference/src/reference/mod.rs b/service/attestation/attestation-service/reference/src/reference/mod.rs +new file mode 100644 +index 0000000..bf56c85 +--- /dev/null ++++ b/service/attestation/attestation-service/reference/src/reference/mod.rs +@@ -0,0 +1,147 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use crate::extractor::Extractor; ++use crate::local_fs::LocalFs; ++use crate::store::{KvError, KvStore}; ++use openssl::sha::sha256; ++use serde::{Deserialize, Serialize}; ++use serde_json::{json, Value}; ++ ++pub struct ReferenceOps { ++ store: Box, ++} ++ ++impl Default for ReferenceOps { ++ fn default() -> Self { ++ ReferenceOps { ++ store: Box::new(LocalFs::default()), ++ } ++ } ++} ++#[derive(Debug, Serialize, Deserialize)] ++pub enum HashAlg { ++ SHA256(String), ++ SHA384(String), ++ SHA512(String), ++} ++ ++#[derive(Debug, Serialize, Deserialize)] ++pub struct Ref { ++ pub name: String, ++ pub value: Value, ++} ++ ++#[derive(Debug, PartialEq)] ++pub enum RefOpError { ++ Err(String), ++} ++impl From for RefOpError { ++ fn from(value: KvError) -> Self { ++ match value { ++ KvError::Err(v) => RefOpError::Err(v), ++ } ++ } ++} ++ ++impl ReferenceOps { ++ pub fn new(st: impl KvStore + 'static) -> ReferenceOps { ++ let ops = ReferenceOps { ++ store: Box::new(st), ++ }; ++ ops ++ } ++ ++ fn generate_reference_key(reference: &Ref) -> String { ++ let key = reference.name.clone() + reference.value.to_string().as_str(); ++ hex::encode(sha256(key.as_bytes())) ++ } ++ ++ fn register_reference(&mut self, reference: &Ref) -> Result<(), RefOpError> { ++ // generate reference key ++ let key = Self::generate_reference_key(reference); ++ match self.store.write( ++ &key, ++ serde_json::to_string(&reference) ++ .unwrap() ++ .as_bytes() ++ .as_ref(), ++ ) { ++ Ok(_) => { ++ return Ok(()); ++ } ++ Err(err) => match err { ++ KvError::Err(err) => { ++ return Err(RefOpError::Err(err)); ++ } ++ }, ++ } ++ } ++ ++ fn unregister_reference(&mut self, reference: &Ref) -> Result<(), RefOpError> { ++ let key = Self::generate_reference_key(reference); ++ match self.store.delete(&key) { ++ Ok(_) => { ++ return Ok(()); ++ } ++ Err(err) => match err { ++ KvError::Err(err) => { ++ return Err(RefOpError::Err(err)); ++ } ++ }, ++ } ++ } ++ ++ fn query_reference(&mut self, reference: &Ref) -> Option> { ++ let key = Self::generate_reference_key(reference); ++ self.store.read(&key) ++ } ++ /// ref_set is a json string like:{"refname1":xx,"refname2":yy} ++ pub fn register(&mut self, ref_set: &String) -> Result<(), RefOpError> { ++ let refs = ++ Extractor::split(ref_set).ok_or(RefOpError::Err("parse reference fail".to_string()))?; ++ for item in refs { ++ self.register_reference(&item)? ++ } ++ Ok(()) ++ } ++ ++ pub fn unregister(&mut self, ref_set: &String) -> Result<(), RefOpError> { ++ let refs = ++ Extractor::split(ref_set).ok_or(RefOpError::Err("parse reference fail".to_string()))?; ++ for item in refs { ++ self.unregister_reference(&item)? ++ } ++ Ok(()) ++ } ++ ++ pub fn query(&mut self, ref_set: &String) -> Option { ++ let refs = Extractor::split(ref_set)?; ++ let mut ret: Value = json!({}); ++ for item in refs { ++ // query each reference, reference is set to NULL if not found ++ match self.query_reference(&item) { ++ Some(ref_store) => { ++ let ref_raw: Ref = ++ serde_json::from_str(String::from_utf8(ref_store).unwrap().as_str()) ++ .ok()?; ++ ret.as_object_mut() ++ .unwrap() ++ .insert(ref_raw.name, ref_raw.value); ++ } ++ None => { ++ ret.as_object_mut().unwrap().insert(item.name, Value::Null); ++ } ++ } ++ } ++ Some(ret.to_string()) ++ } ++} +diff --git a/service/attestation/attestation-service/reference/src/store/mod.rs b/service/attestation/attestation-service/reference/src/store/mod.rs +new file mode 100644 +index 0000000..c8c8260 +--- /dev/null ++++ b/service/attestation/attestation-service/reference/src/store/mod.rs +@@ -0,0 +1,19 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++pub enum KvError { ++ Err(String), ++} ++pub trait KvStore { ++ fn write(&mut self, key: &str, value: &[u8]) -> Result<(), KvError>; ++ fn read(&mut self, key: &str) -> Option>; ++ fn delete(&mut self, key: &str) -> Result<(), KvError>; ++} +diff --git a/service/attestation/attestation-service/service/Cargo.toml b/service/attestation/attestation-service/service/Cargo.toml +new file mode 100644 +index 0000000..e8b88b8 +--- /dev/null ++++ b/service/attestation/attestation-service/service/Cargo.toml +@@ -0,0 +1,35 @@ ++[package] ++name = "attestation-service" ++version = "0.1.0" ++edition = "2021" ++ ++[dependencies] ++anyhow.workspace = true ++serde.workspace = true ++hex.workspace = true ++serde_json.workspace = true ++ ++actix-web.workspace = true ++env_logger.workspace = true ++tokio.workspace = true ++log.workspace = true ++base64-url.workspace = true ++base64.workspace = true ++ ++verifier = { path = "../verifier" } ++token_signer = { path = "../token" } ++reference = { path = "../reference" } ++policy = { path = "../policy" } ++strum.workspace = true ++thiserror.workspace = true ++clap.workspace = true ++uuid.workspace = true ++rand.workspace = true ++scc.workspace = true ++attestation-types.workspace = true ++ ++[dev-dependencies] ++futures.workspace = true ++ ++[features] ++ +diff --git a/service/attestation/attestation-service/service/attestation-service.conf b/service/attestation/attestation-service/service/attestation-service.conf +new file mode 100644 +index 0000000..64c5ff0 +--- /dev/null ++++ b/service/attestation/attestation-service/service/attestation-service.conf +@@ -0,0 +1,9 @@ ++{ ++ "token_cfg": { ++ "key": "/etc/attestation/attestation-service/token/private.pem", ++ "iss": "oeas", ++ "nbf": 0, ++ "valid_duration": 300, ++ "alg": "PS256" ++ } ++} +diff --git a/service/attestation/attestation-service/service/src/lib.rs b/service/attestation/attestation-service/service/src/lib.rs +new file mode 100644 +index 0000000..cc3f432 +--- /dev/null ++++ b/service/attestation/attestation-service/service/src/lib.rs +@@ -0,0 +1,204 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use anyhow::{Result, anyhow}; ++use std::fs::File; ++use std::path::Path; ++use std::str::FromStr; ++use serde::{Serialize, Deserialize}; ++use serde_json::Value; ++use rand::RngCore; ++use base64_url; ++ ++use verifier::{Verifier, VerifierAPIs}; ++use token_signer::{EvlReport, TokenSigner, TokenSignConfig}; ++use reference::reference::{ReferenceOps, RefOpError}; ++use policy::opa::OPA; ++use policy::policy_engine::{PolicyEngine, PolicyEngineError}; ++use attestation_types::EvlResult; ++ ++pub mod result; ++#[derive(Clone, Debug, Serialize, Deserialize)] ++pub struct ASConfig { ++ pub token_cfg: TokenSignConfig, ++} ++ ++impl Default for ASConfig { ++ fn default() -> Self { ++ Self { ++ token_cfg: TokenSignConfig::default(), ++ } ++ } ++} ++ ++impl TryFrom<&Path> for ASConfig { ++ /// Load `ASConfig` from a configuration file like: ++ /// { ++ /// "token_cfg": { ++ /// "key": "/etc/attestation/attestation-service/token/private.pem", ++ /// "iss": "oeas", ++ /// "nbf": 0, ++ /// "valid_duration": 300, ++ /// "alg": "PS256" ++ /// } ++ /// } ++ ++ type Error = anyhow::Error; ++ fn try_from(config_path: &Path) -> Result { ++ let file = File::open(config_path)?; ++ serde_json::from_reader::(file).map_err(|e| anyhow!("invalid asconfig {e}")) ++ } ++} ++ ++pub struct AttestationService { ++ pub config: ASConfig, ++ // verify policy sub service ++ //policy: , ++ // reference value provider sub service ++ //rvps: , ++ // tee verifier sub service ++ //verifier: , ++} ++ ++impl Default for AttestationService { ++ fn default() -> Self { ++ Self { ++ config: ASConfig::default(), ++ } ++ } ++} ++ ++impl AttestationService { ++ pub fn new(conf_path: Option) -> Result { ++ let config = match conf_path { ++ Some(conf_path) => { ++ log::info!("Attestation Service config file:{conf_path}"); ++ ASConfig::try_from(Path::new(&conf_path))? ++ } ++ None => { ++ log::warn!("No Attestation Agent config file specified. Using a default config"); ++ ASConfig::default() ++ } ++ }; ++ Ok(AttestationService {config}) ++ } ++ /// evaluate tee evidence with reference and policy, and issue attestation result token ++ pub async fn evaluate( ++ &self, ++ user_data: &[u8], ++ evidence: &[u8], ++ policy_ids: &Option> ++ ) -> Result { ++ let verifier = Verifier::default(); ++ let claims_evidence = verifier.verify_evidence(user_data, evidence).await?; ++ ++ let mut passed = false; ++ let ima_result = verifier.verify_ima(evidence, &claims_evidence).await; ++ if ima_result.is_ok() { ++ passed = true; ++ } ++ // get reference by keys in claims_evidence ++ let mut ops_refs = ReferenceOps::default(); ++ let refs_of_claims = ops_refs.query(&claims_evidence["payload"].to_string()); ++ // apply policy to verify claims_evidence with reference value ++ let policy_ids = match policy_ids { ++ Some(polciy_id) => polciy_id.clone(), ++ None => vec![], ++ }; ++ let policy_dir = String::from("/etc/attestation/attestation-service/policy"); ++ let engine = OPA::new(&policy_dir).await.unwrap(); ++ let data = String::new(); ++ let result = engine.evaluate(&String::from(claims_evidence["tee"] ++ .as_str().ok_or(anyhow!("tee type unknown"))?), ++ &refs_of_claims.unwrap(), &data, &policy_ids).await; ++ let mut report = serde_json::json!({}); ++ let mut ref_exist_null: bool = false; ++ match result { ++ Ok(eval) => { ++ for id in eval.keys() { ++ let val = Value::from_str(&eval[id].clone())?; ++ let refs = match val.as_object().ok_or(Err(anyhow!(""))) { ++ Err(err) => { return Err(err.unwrap()); } ++ Ok(ret) => { ret } ++ }; ++ for key in refs.keys() { ++ // reference value is null means not found ++ if refs[key].is_null() { ++ ref_exist_null = true; ++ } ++ } ++ report.as_object_mut().unwrap().insert(id.clone(), serde_json::Value::String(eval[id].clone())); ++ } ++ } ++ Err(err) => { ++ return Err(anyhow!("evaluate error: {err}")); ++ } ++ } ++ ++ // issue attestation result token ++ let evl_report = EvlReport { ++ tee: String::from(claims_evidence["tee"].as_str().ok_or(anyhow!("tee type unknown"))?), ++ result: EvlResult { ++ eval_result: passed & !ref_exist_null, ++ policy: policy_ids, ++ report: report, ++ }, ++ tcb_status: claims_evidence["payload"].clone(), ++ }; ++ // demo get signer, todo default signer ++ let signer = TokenSigner::new(self.config.token_cfg.clone())?; ++ ++ signer.sign(&evl_report) ++ } ++ ++ pub async fn generate_challenge(&self) -> String { ++ let mut nonce: [u8; 32] = [0; 32]; ++ rand::thread_rng().fill_bytes(&mut nonce); ++ base64_url::encode(&nonce) ++ } ++ ++ // todo pub fun set policy ++ pub async fn set_policy(&self, ++ id: &String, ++ policy: &String, ++ policy_dir: &String, ++ ) -> Result<(), PolicyEngineError> { ++ let engine = OPA::new(policy_dir).await; ++ engine.unwrap() ++ .set_policy(id, policy) ++ .await ++ } ++ // todo pub fun get policy ++ pub async fn get_policy(&self, ++ policy_dir: &String, ++ ) -> Result { ++ let engine = OPA::new(policy_dir).await; ++ match engine.unwrap().get_all_policy().await { ++ Ok(map) => { ++ let mut json_obj: serde_json::Value = serde_json::json!({}); ++ for key in map.keys() { ++ json_obj.as_object_mut() ++ .unwrap() ++ .insert(key.clone(), serde_json::json!(map[key])); ++ } ++ Ok(json_obj.to_string()) ++ } ++ Err(err) => Err(err) ++ } ++ } ++ // todo pub fun import reference value ++ pub async fn register_reference(&self, ++ ref_set: &String ++ ) -> Result<(), RefOpError> { ++ let mut ops_default = ReferenceOps::default(); ++ ops_default.register(ref_set) ++ } ++} +diff --git a/service/attestation/attestation-service/service/src/main.rs b/service/attestation/attestation-service/service/src/main.rs +new file mode 100644 +index 0000000..1ccb152 +--- /dev/null ++++ b/service/attestation/attestation-service/service/src/main.rs +@@ -0,0 +1,76 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++/// RESTful Attestation Service ++ ++use attestation_service::AttestationService; ++mod restapi; ++use restapi::{get_challenge, attestation, reference, get_policy, set_policy}; ++mod session; ++use session::SessionMap; ++ ++use anyhow::Result; ++use env_logger; ++use actix_web::{web, App, HttpServer}; ++use std::{net::{SocketAddr, IpAddr, Ipv4Addr}, sync::Arc}; ++use tokio::sync::RwLock; ++use clap::{Parser, command, arg}; ++ ++const DEFAULT_ASCONFIG_FILE: &str = "/etc/attestation/attestation-service/attestation-service.conf"; ++const DEFAULT_SOCKETADDR: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); ++ ++#[derive(Parser, Debug)] ++#[command(version, about, long_about = None)] ++struct Cli { ++ /// Socket address to listen on ++ #[arg(short, long, default_value_t = DEFAULT_SOCKETADDR)] ++ socketaddr: SocketAddr, ++ ++ /// Attestation Service config file ++ // Load `ASConfig` from a configuration file like: ++ // { ++ // "token_cfg": { ++ // "key": "/etc/attestation/attestation-service/token/private.pem", ++ // "iss": "oeas", ++ // "nbf": 0, ++ // "valid_duration": 300, ++ // "alg": "PS256" ++ // } ++ // } ++ #[arg(short, long, default_value_t = DEFAULT_ASCONFIG_FILE.to_string())] ++ config: String, ++} ++ ++#[actix_web::main] ++async fn main() -> Result<()> { ++ env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); ++ ++ let cli = Cli::parse(); ++ let server:AttestationService = AttestationService::new(Some(cli.config)).unwrap(); ++ let session_map = web::Data::new(SessionMap::new()); ++ ++ let service = web::Data::new(Arc::new(RwLock::new(server))); ++ HttpServer::new(move || { ++ App::new() ++ .app_data(web::Data::clone(&service)) ++ .app_data(web::Data::clone(&session_map)) ++ .service(get_challenge) ++ .service(attestation) ++ .service(reference) ++ .service(set_policy) ++ .service(get_policy) ++ }) ++ .bind((cli.socketaddr.ip().to_string(), cli.socketaddr.port()))? ++ .run() ++ .await?; ++ ++ Ok(()) ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-service/service/src/restapi/mod.rs b/service/attestation/attestation-service/service/src/restapi/mod.rs +new file mode 100644 +index 0000000..ab2ccbf +--- /dev/null ++++ b/service/attestation/attestation-service/service/src/restapi/mod.rs +@@ -0,0 +1,139 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use attestation_service::AttestationService; ++use attestation_service::result::{Result, Error}; ++use crate::session::{Session, SessionMap}; ++ ++use actix_web::{ post, get, web, HttpResponse, HttpRequest}; ++use serde::{Deserialize, Serialize}; ++use std::sync::Arc; ++use tokio::sync::RwLock; ++use log; ++use base64_url; ++ ++const DEFAULT_POLICY_DIR: &str = "/etc/attestation/attestation-service/policy"; ++#[derive(Deserialize, Serialize, Debug)] ++pub struct ChallengeRequest {} ++ ++#[get("/challenge")] ++pub async fn get_challenge( ++ map: web::Data, ++ service: web::Data>>, ++) -> Result { ++ log::debug!("challenge request"); ++ ++ let challenge = service.read().await.generate_challenge().await; ++ let timeout = service.read().await.config.token_cfg.valid_duration; ++ let session = Session::new(challenge, timeout.try_into().unwrap()); ++ let response = HttpResponse::Ok() ++ .cookie(session.cookie()) ++ .json(session.challenge.clone()); ++ map.insert(session); ++ ++ Ok(response) ++} ++ ++#[derive(Deserialize, Serialize, Debug)] ++pub struct AttestationRequest { ++ challenge: String, ++ evidence: String, ++ policy_id: Option>, ++} ++ ++#[post("/attestation")] ++pub async fn attestation( ++ request: web::Json, ++ http_req: HttpRequest, ++ map: web::Data, ++ service: web::Data>>, ++) -> Result { ++ log::debug!("attestation request is coming"); ++ let request = request.0; ++ let mut challenge = request.challenge; ++ if challenge == "" { ++ let cookie = http_req.cookie("oeas-session-id").ok_or(Error::CookieMissing)?; ++ let session = map ++ .session_map ++ .get_async(cookie.value()) ++ .await ++ .ok_or(Error::CookieNotFound)?; ++ if session.is_expired() { ++ return Err(Error::SessionExpired); ++ } ++ log::debug!("session challenge:{}", session.challenge); ++ challenge = session.challenge.clone(); ++ } ++ ++ let nonce = base64_url::decode(&challenge).expect("base64 decode nonce"); ++ let evidence = base64_url::decode(&request.evidence).expect("base64 decode evidence"); ++ let ids = request.policy_id; ++ let token = service.read().await.evaluate(&nonce, &evidence, &ids).await?; ++ ++ Ok(HttpResponse::Ok().body(token)) ++} ++ ++#[derive(Deserialize, Serialize, Debug)] ++pub struct ReferenceRequest { ++ refs: String ++} ++ ++#[post("/reference")] ++pub async fn reference( ++ request: web::Json, ++ service: web::Data>>, ++) -> Result { ++ let request = request.0; ++ log::debug!("reference request: {:?}", request); ++ match service.read().await.register_reference(&request.refs).await { ++ Ok(_) => Ok(HttpResponse::Ok().body("set reference success")), ++ Err(_err) => Ok(HttpResponse::Ok().body("set reference fail")), ++ } ++} ++ ++#[derive(Deserialize, Serialize, Debug)] ++pub struct PolicyRequest { ++ tee: String, ++ id: String, ++ policy: String, ++} ++ ++#[post("/policy")] ++pub async fn set_policy( ++ request: web::Json, ++ service: web::Data>>, ++) -> Result { ++ let request = request.0; ++ log::debug!("set policy request: {:?}", request); ++ let policy_id = request.id.clone(); ++ let policy = request.policy.clone(); ++ let dir:String = String::from(DEFAULT_POLICY_DIR); ++ match service.read().await.set_policy(&policy_id, &policy, &dir).await { ++ Ok(_) => Ok(HttpResponse::Ok().body("set policy success")), ++ Err(err) => { ++ log::debug!("set policy error: {:?}", err); ++ Ok(HttpResponse::Ok().body("set policy fail")) ++ } ++ } ++} ++ ++#[get("/policy")] ++pub async fn get_policy( ++ request: HttpRequest, ++ service: web::Data>>, ++) -> Result { ++ log::debug!("get policy request: {:?}", request); ++ let dir:String = String::from(DEFAULT_POLICY_DIR); ++ match service.read().await.get_policy(&dir).await { ++ Ok(ret) => Ok(HttpResponse::Ok().body(ret)), ++ Err(_err) => Ok(HttpResponse::Ok().body("get policy fail")), ++ } ++} +diff --git a/service/attestation/attestation-service/service/src/result/mod.rs b/service/attestation/attestation-service/service/src/result/mod.rs +new file mode 100644 +index 0000000..667e80f +--- /dev/null ++++ b/service/attestation/attestation-service/service/src/result/mod.rs +@@ -0,0 +1,55 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use thiserror::Error; ++use actix_web::{body::BoxBody, HttpResponse, ResponseError}; ++pub type Result = std::result::Result; ++ ++#[derive(Debug, Error)] ++//#[non_exhaustive] ++//#[allow(missing_docs)] ++pub enum Error { ++ #[error("IO error: {source:?}")] ++ Io { ++ #[from] ++ source: std::io::Error, ++ }, ++ ++ #[error("Web error: {source:?}")] ++ Web { ++ #[from] ++ source: actix_web::error::Error, ++ }, ++ ++ #[error("Deserialize error: {source:?}")] ++ Deserialize { ++ #[from] ++ source: serde_json::Error, ++ }, ++ ++ #[error("Request cookie is missing")] ++ CookieMissing, ++ ++ #[error("Request cookie is not found")] ++ CookieNotFound, ++ ++ #[error("The session of request cookie is expired")] ++ SessionExpired, ++ ++ #[error(transparent)] ++ Other(#[from] anyhow::Error), ++} ++ ++impl ResponseError for Error { ++ fn error_response(&self) -> HttpResponse { ++ HttpResponse::InternalServerError().body(BoxBody::new(format!("{self:#?}"))) ++ } ++} +diff --git a/service/attestation/attestation-service/service/src/session.rs b/service/attestation/attestation-service/service/src/session.rs +new file mode 100644 +index 0000000..5f191a7 +--- /dev/null ++++ b/service/attestation/attestation-service/service/src/session.rs +@@ -0,0 +1,58 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use actix_web::cookie::{time::{Duration, OffsetDateTime}, Cookie}; ++use scc::HashMap; ++use uuid::Uuid; ++ ++pub struct Session { ++ pub id: String, ++ pub challenge: String, ++ timeout: OffsetDateTime, ++} ++ ++impl Session { ++ pub fn new(challenge: String, timeout_m: i64) -> Self { ++ let id = Uuid::new_v4().as_simple().to_string(); ++ let timeout = OffsetDateTime::now_utc() + Duration::minutes(timeout_m); ++ Session { ++ id, ++ challenge, ++ timeout, ++ } ++ } ++ pub fn is_expired(&self) -> bool { ++ return self.timeout < OffsetDateTime::now_utc(); ++ } ++ pub fn cookie(&self) -> Cookie { ++ Cookie::build("oeas-session-id", self.id.clone()) ++ .expires(self.timeout.clone()) ++ .finish() ++ } ++} ++ ++pub struct SessionMap { ++ pub session_map: HashMap, ++} ++ ++impl SessionMap { ++ pub fn new() -> Self { ++ SessionMap { ++ session_map: HashMap::new(), ++ } ++ } ++ pub fn insert(&self, session: Session) { ++ let _ = self.session_map.insert(session.id.clone(), session); ++ } ++ pub fn delete(&self, session: Session) { ++ let _ = self.session_map.remove(&session.id); ++ } ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-service/tests/Cargo.toml b/service/attestation/attestation-service/tests/Cargo.toml +new file mode 100644 +index 0000000..0fde476 +--- /dev/null ++++ b/service/attestation/attestation-service/tests/Cargo.toml +@@ -0,0 +1,9 @@ ++[package] ++name = "tests" ++version = "0.1.0" ++edition = "2021" ++ ++[dependencies] ++serde_json = "1.0.116" ++reqwest = {version = "0.12.5", features = ["blocking"]} ++rand = "0.8.5" +diff --git a/service/attestation/attestation-service/tests/src/lib.rs b/service/attestation/attestation-service/tests/src/lib.rs +new file mode 100644 +index 0000000..abd099f +--- /dev/null ++++ b/service/attestation/attestation-service/tests/src/lib.rs +@@ -0,0 +1,166 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++#[cfg(test)] ++mod tests { ++ use rand::{distributions::Alphanumeric, Rng}; ++ use reqwest::blocking::Client; ++ use serde_json::{json, Value}; ++ use std::thread; ++ ++ #[test] ++ fn api_register_reference_test() { ++ let request_body = json!({ ++ "refs":r#"{ "RIM": "7d2e49c8d29f18b748e658e7243ecf26bc292e5fee93f72af11ad9da9810142a", ++ "PRV": "cGFja2FnZSBhdHRlc3RhdGlvbgppbXBvcnQgcmVnby52MQpleHBlY3Rfa2V5cyA6" ++ }"# ++ }); ++ ++ let client = Client::new(); ++ let endpoint = "http://127.0.0.1:8080/reference"; ++ let res = client ++ .post(endpoint) ++ .header("Content-Type", "application/json") ++ .body(request_body.to_string()) ++ .send() ++ .unwrap(); ++ println!("{:?}", res); ++ assert!(res.text().unwrap().contains("success")); ++ } ++ ++ #[test] ++ fn api_register_concurrently() { ++ let mut thread_all = vec![]; ++ let thread_cnt = 100; ++ for _i in 0..thread_cnt { ++ thread_all.push(thread::spawn(|| { ++ let mut request_body = json!({ ++ "refs":r#"{ "RIM": "7d2e49c8d29f18b748e658e7243ecf26bc292e5fee93f72af11ad9da9810142a", ++ "PRV": "cGFja2FnZSBhdHRlc3RhdGlvbgppbXBvcnQgcmVnby52MQpleHBlY3Rfa2V5cyA6" ++ }"# ++ }); ++ let rng = rand::thread_rng(); ++ request_body["value"] = Value::String( ++ rng.clone() ++ .sample_iter(&Alphanumeric) ++ .take(64) ++ .map(char::from) ++ .collect(), ++ ); ++ ++ let client = Client::new(); ++ let endpoint = "http://127.0.0.1:8080/reference"; ++ let res = client ++ .post(endpoint) ++ .header("Content-Type", "application/json") ++ .body(request_body.to_string()) ++ .send() ++ .unwrap(); ++ println!("{:?}", res); ++ assert!(res.text().unwrap().contains("success")); ++ })); ++ } ++ for hd in thread_all { ++ match hd.join() { ++ Ok(_) => {} ++ Err(_) => { ++ assert!(false) ++ } ++ } ++ } ++ } ++ ++ #[test] ++ fn api_register_complex_reference_test() { ++ let request_body = json!({ ++ "refs":r#"{"complex_ref":{"level1_1":[1,2,3],"level1_2":{"name1":"value1"}}}"# ++ } ++ ); ++ ++ let client = Client::new(); ++ let endpoint = "http://127.0.0.1:8080/reference"; ++ let res = client ++ .post(endpoint) ++ .header("Content-Type", "application/json") ++ .body(request_body.to_string()) ++ .send() ++ .unwrap(); ++ println!("{:?}", res); ++ assert!(res.text().unwrap().contains("success")); ++ } ++ ++ #[test] ++ fn api_set_policy() { ++ let request_body = json!({ ++ "tee":"KUNPENG", ++ "id": "test_policy.rego", ++ "policy":"cGFja2FnZSBhdHRlc3RhdGlvbgppbXBvcnQgcmVnby52MQpleHBlY3Rfa2V5cyA6PSBbIlJJTSIsICJSUFYiXQppbnB1dF9rZXlzIDo9IG9iamVjdC5rZXlzKGlucHV0KQpvdXRwdXRbZXhpc3RdIDo9IGlucHV0W2V4aXN0XSBpZiB7CiAgICBzb21lIGV4aXN0IGluIGV4cGVjdF9rZXlzCiAgICBleGlzdCBpbiBpbnB1dF9rZXlzCn0Kb3V0cHV0W2V4aXN0XSA6PSBudWxsIGlmIHsKICAgIHNvbWUgZXhpc3QgaW4gZXhwZWN0X2tleXMKICAgIG5vdCBleGlzdCBpbiBpbnB1dF9rZXlzCn0Kb3V0cHV0WyJPdGhlciJdIDo9ICJvdGhlciIgaWYgewogICAgInRlc3QiIGluIGlucHV0X2tleXMKfQ" ++ } ++ ); ++ ++ let client = Client::new(); ++ let endpoint = "http://127.0.0.1:8080/policy"; ++ let res = client ++ .post(endpoint) ++ .header("Content-Type", "application/json") ++ .body(request_body.to_string()) ++ .send() ++ .unwrap(); ++ let response = res.text().unwrap(); ++ println!("set policy reponse: {}", response); ++ assert!(response.contains("success")); ++ } ++ ++ #[test] ++ fn api_get_policy() { ++ let client: Client = Client::new(); ++ let endpoint = "http://127.0.0.1:8080/policy"; ++ let res = client ++ .get(endpoint) ++ .send() ++ .unwrap(); ++ assert_eq!(res.status(), reqwest::StatusCode::OK); ++ println!("{:?}", res.text().unwrap()); ++ } ++ ++ #[test] ++ fn api_evaluate() { ++ let request_body = json!({ ++ "policy_id":["test.rego", "test_policy.rego"], ++ "challenge":"71oZilAy6vXCgFuRUhAYNA", ++ "evidence": "eyJ0ZWUiOiJJdHJ1c3RlZSIsImV2aWRlbmNlIjoie1xuXHRcInJlcG9ydF9zaWduXCI6XHR7XG5cdFx0XCJzY2Vfbm9fYXNcIjpcdFwiQjJEUE1NbWRUT0lVN3FpNnFCc3NaOEhFN1gtRnlwQVF3Ml9zWUpjNVVoS0FIZlFUM3phZTM5cnN6TEFzaE5qOGJ5dEIyOHNTUnp2N3RXYmRPSmV3dW5Uc1pUNnJaSEFRWFFEc1k0UzloOFRIdDBPNnlnbUV6Z1MydjZkM0NpeW91MGtQanNVbzFFbHpxbU5KS0JTbFFpejNqQlVzTjZhVXo3dkM5Yllpd3FsdWpBZm9iUlBfT19OM193NGxfMmQ4T05OaWtWRHdGcE5zMjJqcVJ0ZzlxS2VvWkduZVhUSGQwYVIzMVNKTDhsRFJsOG5Ka0FLdkRFUHZ5Zl9GN0Jrd2pEYk5YM2hNdmJMLXFEWkVWT2JNdUcwYldBVGRJV0FUTDFMem9qbDRTUVNPZDNmMEc1VWg1QU9pMEJtcDZUT3ZVTG44c0FpMWkwenF3U3h3Y0U0dlJjSG56OEVwTndTcjdET2tmUXR3bkdCY21fZUZkcTYzYXAtaWN6ZWwxa2pZUFRHZXY0bEdpemt4Wm9VN3FfYTExUXJIc1dkYnppeDBaNHlpMnBWS21lUXB0TjNydmxIYXZzZXE5VTh5VXBwbkVoMnNhVzJ3QlJmS2hYSVIxRFhiTlpNOV9qUHdRNVRTanNGQXpKYTNzbWM5VkxUMlZQa2lKSzBtNzhLS19sNkQ4TVF4ZXMyU2Q1dG9fYS1hcHh1OEE2b1E5aVZXRzBkdS0xS05MUm1hbVRCcUpLZzRfQzh0Z041dUZ3ejRLMVZ2eEYtVjY5RWVEUXpRV0o0SWFQTFNCS3BzSkx1ZUZyQjk1TGNmWnhudk05OG5oQVo4QU5PQ3pFdXJSYlVlR1MwcDM2ZjUtU3BYSGlveTNSbm5rY05tYmlVb2cwbVd6T01HVTE4WTZjeFZJVGNcIlxuXHR9LFxuXHRcImFrY2VydFwiOlx0e1xuXHRcdFwic2NlX25vX2FzXCI6XHR7XG5cdFx0XHRcInNpZ25hdHVyZVwiOlx0e1xuXHRcdFx0XHRcImRya19jZXJ0XCI6XHRcIlRVbEpSV3RxUTBOQk0zRm5RWGRKUWtGblNWSkZVbU40Tnk5NE5sRkRSRnBCUmpGS2IxRkJhMkZGVFhkRVVWbEtTMjlhU1doMlkwNUJVVVZNUWxGQmQxQlVSVXhOUVd0SFFURlZSVUpvVFVOUk1EUjRSSHBCVGtKblRsWkNRVzlVUW10b01WbFlaR3hoVkVWa1RVSnpSMEV4VlVWQmVFMVZVMGhXYUdReVZuQkpSV3hWU1VaQ2VXSXlVakZaTTFGblVUQkZkMGhvWTA1TmFrMTNUbFJGTVUxVVRYbFBSRVY2VjJoalRrMTZaM2RPVkVWNFRWUk5lVTlFUlhwWGFrRTJUVkZ6ZDBOUldVUldVVkZIUlhkS1JGUnFSVkJOUVRCSFFURlZSVU5vVFVkVFNGWm9aREpXY0UxU2IzZEhRVmxFVmxGUlJFVjRSWGROYWxwSlZXdFplRTFGY3pWTlJFRTBUMVJGZUZaRVEwTkJhVWwzUkZGWlNrdHZXa2xvZG1OT1FWRkZRa0pSUVVSblowbFFRVVJEUTBGbmIwTm5aMGxDUVUxek9FTllRMXBvZGtObk1qbE1UbXRWWWxOTU9VbGljR3RaVkhSM1IyWTBLMGhYYjJoRUt6QnNPRVl3YVRGR1dIbEVibnBJYW1keU1UTnNWakp1YkZCR09XeHVVazlNWTJKSlRDdHRVVFJJYm05UVEyWXZZVXhSZFdsRFZUTkVjRXhNVG5aMFIyZFhlbXBoTW5CbFRIVlhPVWxWWkhoMGNuUlNRalV6YVdKSFRHeHdSVkl2ZEZkaVNUSk5SMmhTTldaWFptUXdSVGR4ZFM5Q1VGWnRPVlpKVDB4eFEyMVdaMWhWY0hSblVucDNPSFpIUkZSc09GSjFPRkJ4WWt0WmRucGhUbHBxYlhwek5XNUtPWGx5VW5KSksydFBaekI2TTBwWU1tTllRVzVEVVVneGVDOWlWRzlXV0dodFkybFpPVU5MTTNCR04ydDRWSGxsZUdoR1RqVXhZakp6VVdWNVEzTlhhazVsYnpsWlVGSTBTMUJ0VVVzNVJYWm5kbkpXYW5keVRuZFNMMkZwZDFKWlowbzJTelExUzNwS1FUSnNjWFZ0VmtReFUzcFlORmhtWm14RFNqRlphRkpTWTNWeWFFc3ZaRXA0UVZkUlJsa3JTV2Q0Y1V4SE9IRlFWbGM0U1RFMFpuZzJRbWxUYkZWelIzRlVRWFp5TjFWRlF6aFNWWEJWT0VsT2VUSkpaV3A1TlhOaWMyTnBSVlpsTkVkSGFqUk5PVTB2YzBaVFQzUmlOM1FyVm5GU1RsbFRTV05HYlhKVVZqVlNjbHBDVkZkU1kyeGhOM3BXVkc1NFUwNHdjR2x5UzBWWmNFOVBVV1p6UWxodU56VjNNMFpsU0RKcU5HdEpVV3RWYUVSNU9XcFhaa05sSzFONGVuQnZTMkpvVmxrd1dsbHNVRzl1U2s5QlowaEhWek5UU2tac2JpdG1Ra3BCZFdkc1RqbFphbVJ2UVZkcVZHcDRSRTB6ZFZWdGVUQjFkRFkxU20xME5uQkNkVU1yVVVwWFIySjFhQ3RFTUV0MFNVVnROVEZMVFM5RE1HUTRNME0yYm1GSk1qVmxla0o2T0ZGalpXbG5kRXg0U1UxYVprSkZWU3RYTm1aeU9HaHhWa1pDTldkRFIzVldiRzVUUTJSNmJFbDBlRXc0VldOeWVISmpRazVQWkRSMWNIZGxiMWt4ZEVOS1VUSTVXRGNyV0RjemRVVnNkVVpJUVdkTlFrRkJSMnBuV1RoM1oxbDNkMGgzV1VSV1VqQnFRa0puZDBadlFWVkZiMjh6TjFCc2VsWTVabXRuTjNFeFluZFFjek5oTkZKVWN6aDNRM2RaUkZaU01GQkNRVkZFUVdkUU5FMUdkMGREUTNOSFFWRlZSa0ozUlVKQ1JrRjNWR3BCYjBKblozSkNaMFZHUWxGamQwRnZXV05oU0ZJd1kwUnZka3g2UlhsT2VUUjNUR3BCZFUxVE9XcFpWMng2WXpOV2JFeHRhREJpVkVGcFFtZG5ja0puUlVaQ1VXTjNRVmxaVjJGSVVqQmpSRzkyVEhwRmVVNTVOSGRNYWtGMVRWUnZlVTFFVVRCTmVrRk9RbWRyY1docmFVYzVkekJDUVZGelJrRkJUME5CVVVWQlQzWlVXR1ZFWlZSVE1VNTVibEEzY0ZZMFYwdzBZMFp1UVdoaFNFbE5hbWhwYlZjM1RqbEZRblF3VTB4SFNGQXpaRGhzTTBsblYxSkZiMlpRTkZnd1JWSjFiR1J4WWxweFpucElSWEZhZG5abFZFOVNVVlZHYVdWV1ZURnJLekY0U1doRFUzbGtNMWhxVUdObGQxWkRiMFpDY0dNMlFURlFlbkJDY1dwM1kyTXpLMDFxTUdwcVNXaFpWMDAxVnl0b1VYRlJOVWhuT1RneWFWUmhNM2haTjBGc1UzaExVa2x2YkN0dU16SkpRM2hXTkUxbFQwdzNUa0ZoWVhkVlpGRkdOamh3ZHpGQk5tSmtXV2d5VjNWSWVHWXJaQ3RUWm5aVEwzZGxjVEJFV21rNGRUSnNURzFoVVZjd2JrcEVTVFUzZFcxWFEyTTNTa2d2VkZaR0wzSmlhaXRrZVhCUlJUSkJaa3h2VjNwS2JuUkRaa3d5UjI1eVlrZ3JXbkpPVHpSQ1l6RXpOWEZaU2xad1ZqTnNVSFpOYzFVeVozRk9jRFpvVmtndkt6UkJhRTQyWjFFNVYwbzFkbUprV25aT2VsZGpURlpHYzB4UlBUMFwiLFxuXHRcdFx0XHRcImRya19zaWduXCI6XHRcIkVTLUhLSUFyOTBYQ1h3ZXRfQi1pR1BmS0Uwa0VIdWczeGhnSUpRUWNRak5iSXc5bHJ4bFZaZE1XMnlIM3hfVlNMTUhfZ3RzWjVDSXRjYlpjNlVNaWhCeDRDcXVpdjZ5RG1yMlVOUmJrNGdKd2ZiNjE5em9pNkEzaThTcFpaaUctY2dQUlNGS05IdjNSV0tBbDdXaUd1SU04ZFlvdHR0eHhJT3U0bElrQWlXR2txRlpCUzJCc2JSNXVLdEl2R0hEaG1QZEh5c3JwU3lrb0loVE4wM0FpY0NNYWRFVnhzbUs5Y0pGRlFTOHg3ZlAxU0VMUHJDMFdUT0F4bjlNdFBRVXcwSnV5dzZKeXVJZU5BYjRYRV9uNjZQOU9oRXV5Y3RjajBsa3RxLUpneFFlalRVejFROHVTbVFEV2ROcUZlRTFOQWk3Zmd6YVNjd2tTc3lXa0dtckt0MFZaSnI2a0hCT0xYMFNEMElPRWJROFNRdHFieVRpRko4Uk4tMXZqb05LYjhobnpIUnpqeGJ4eGYtVW9zeWlhY3RNUVRnOUNlTi1rSVRjUDJTUVE4ZjA0OThVVkpRamFoU05OeVcxdnp6c0hXaFQyeWMtcFdwSll6YUtrXzBjdFBVT3UwazJ1VS1EbENOdFQzRkVpS0hMZWVzRmdUSUlmdFJ1Z1JoMklHSFVkcFlvTmJDVmxvRXdaLWRPY19fQ212ZTBOOVVoWWdSUUpER1VhOGRWZVhKVXZXZ2dNQXpJdEE0QUhhVlByTFRFSTRHQld5bEw5Wm1FclU5bmpXZjd3X09XcldvaUFlVm5KMl9yanhpYjVUZ3ZldXJCM3pCbEJMSTVwQ0ExbmxhT0N0aUlMM0c1dEs3N3NNXzNXeUhYSG96cFRTeTR3WG5mU0tGeHUweXROV240XCJcblx0XHRcdH0sXG5cdFx0XHRcInBheWxvYWRcIjpcdHtcblx0XHRcdFx0XCJ2ZXJzaW9uXCI6XHRcIlRFRS5SQS4xLjBcIixcblx0XHRcdFx0XCJ0aW1lc3RhbXBcIjpcdFwiOTU1ODA5NDE0MTIzNTRcIixcblx0XHRcdFx0XCJzY2VuYXJpb1wiOlx0XCJzY2Vfbm9fYXNcIixcblx0XHRcdFx0XCJzaWduX2FsZ1wiOlx0XCJQUzI1NlwiLFxuXHRcdFx0XHRcImhhc2hfYWxnXCI6XHRcIkhTMjU2XCIsXG5cdFx0XHRcdFwicXRhX2ltZ1wiOlx0XCI3V2N1ZjZLMEM0XzBUTVIwZEMyMURBdVZ2c0c2WEIySjhxZ0dtUUFHSVdzXCIsXG5cdFx0XHRcdFwicXRhX21lbVwiOlx0XCJQVXJNdENoSGRzaW1VQWZrRnZrc3Y1Z3ZHMzFSalgtbF8wb0JsLUpCZHNNXCIsXG5cdFx0XHRcdFwidGNiXCI6XHRcIlwiLFxuXHRcdFx0XHRcImFrX3B1YlwiOlx0e1xuXHRcdFx0XHRcdFwia3R5XCI6XHRcIlJTQVwiLFxuXHRcdFx0XHRcdFwiblwiOlx0XCJwUkU1OGtzdW9IeGNMMExiMWs1blNfZkt2eERZSm96anhnYlh0aVU5T0RJNFMxa1hSZ2ZBNWdiRi1BQVJkSDZZOTdzQ0t6TklBNFRZd0pVTWh0ZFlScTZZYWZrSFZLb29tUWhJYU5mSUFaMG1BWXU5V2ttS2hBNE9BRjJNX0xDMW03WVVGMlI1S2taaGxzeFUtMEo3bWwyM2Q3RFB1enI1cnJwREFDYWxqMmZtLTROVk5DMHdYTjdPaWVXSW9UdFF0Sk9DSW5aODFjZ3FWZUxhUEVUSnpmRnBtX0ZBWXgtWDlsVnlrUFVxeWNuZnVFZjJ1Tml2OVNyMHc1d1NlX1BSRmlNQmRCRGxSQllqODBsbHllbGZGb0tWeE41c1BXMENuRkJZdlA2MHV6VVNSMDZjbHZxSU4wWUotTnhEcVhMU1VrajAtZEluNHdFLTVaaU55azI1Q25mWE5tMDk1c2tOeFo4SUxYN3FIZEJkVFhKczYxQ3J1TllsUWg0S2pHZVpWZkxVWkJZaVIwZC1JbjMyWDBYakQ1QWw4OHhLNFdxWFNhdFF6N01MekhBLURKYU8ybHNKZEFxTGFqdGxnV2ZOYXBWbWd5cVB1OFFVTkhfaEhqQlhTUDRNMXEwTUFTd2JCWXlib1VVMl9CY0lSZ0pwTnNneHlRNTJibTU2aUppYVFRQ05rYU55R1FhajRxWEVodjFTQlFlYjlMSVR1dVZfRDQzLUdhS2Z3dEhWZEZiYXdvYjZFbm5yWHoxVW1fREt1dXhLU09QZ3E0MEpRanFCQllMa1J2cXJfbS1ES3BManlVb2w4MS1ldUU1bjNZejFXU1lIbG5tc1hfMEYzbFZDQktQelR5aWd5SGZvZ0hjVXNtX2tTVDFfbWdIajg2ZnZ2OVJGTFFaTFBJRVwiLFxuXHRcdFx0XHRcdFwiZVwiOlx0XCJBUUFCXCJcblx0XHRcdFx0fVxuXHRcdFx0fSxcblx0XHRcdFwiaGFuZGxlclwiOlx0XCJwcm92aXNpb25pbmctb3V0cHV0XCJcblx0XHR9XG5cdH0sXG5cdFwicGF5bG9hZFwiOlx0e1xuXHRcdFwidmVyc2lvblwiOlx0XCJURUUuUkEuMS4wXCIsXG5cdFx0XCJ0aW1lc3RhbXBcIjpcdFwiOTU1ODExNzg4NzA1MzRcIixcblx0XHRcIm5vbmNlXCI6XHRcIjcxb1ppbEF5NnZYQ2dGdVJVaEFZTkFcIixcblx0XHRcImtleVwiOlx0e1xuXHRcdFx0XCJwdWJfa2V5XCI6XHR7XG5cdFx0XHRcdFwiblwiOlx0XCJ1LUtoY1RGLTRMdVhzTUtJZ0I0a0hDMG9NbVZXOV9OUGFGRlB0ajRfSG15azd0b0psS09BMVFENFRiY0dGYUdzenk3aUhvUm9hSUphMnJ6djFuUm1tZ3JlN0lRbTZaSXN2Z3M2bm80OW4ybUJtcENOVDlacEtXemJEMjN3N2hya3VUdkY5LUlTalhMRDk4Tk1ycF9OdUxXalNCczJPV3hyWGJrdUdjU1pETXN5NjJ6dkYxclhIYXFRRmFKNXhpU3cyaGU3YS01YWFfUGpuQmtMa0VYOE1ySF9RSzBtUFI1cmFNeU85a25CVU9UT0owdUpVS3hIQ2dKSEpTU051aDY1ZUt4VlJCS3pKbzNrT0d3Z1lRdjltOEJsZUhjeWoyWlZBVUhfZFFrajRZZFQ3bjAyZ3VBam9RMVR4aHl6R250RnV1VTFTVUZDRVAwZzN3dmI5c2N4UVNmZlNacjl5WTlCMElBR1REckZ3Q0dPM3JUamF3NXJxY2FxSlprY0pkMjFIU05DNTJUOEdENHo4UWhfV05iUHNueV9sRXI5VzU5ZHBXN0FjWlJvSkZlMHhiOGhSaURwRldmOTZJdnBTT2lQZ2RNQVY5SHRjeFRoOS1pdzJiYl9kQzYtekVLS2xfTUtzWE1rSk1DdmxDazdCd3dQX1ZKVnQ2OS1pLVo0dWN3YXhOQUxCdHJfZWFQU0RPNHU2d2JDNUl1bGlPb2VvM0hEci1FQ3RwWVdWWVplVXBGTUIwZF9GU3hsT3NXQjE3QzRNT3ZzNjVtQWcwYXJNOTMyb00xeE5WUWxfYzZkS1g4alVzV1FubXpDcG5zS1JCU3UwRHFwU0JORjhNSnY4ZDR2cFRoNFU2NmhHWFhRYjVWZjdDWkpCbC05dFZRMGJHQTVhNjFfbkYxRXhtc1wiLFxuXHRcdFx0XHRcImVcIjpcdFwiQVFBQlwiLFxuXHRcdFx0XHRcImt0eVwiOlx0XCJSU0FcIlxuXHRcdFx0fSxcblx0XHRcdFwiZW5jX2tleVwiOlx0XCJWa3hVX1FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBakJBQUFBQUFBQUNBQUFBQUFBQUFBREFBQUFBQUFBQUFNQUFBQUFBQUFBQkFBQUFBQUFBQUFFQUFBQUFBQUFBRHQ1TnJxQUpocjE1N0Q5V0QyTDRCdE9nVnlXejJfQzZTLXNLc2lzb0xpakRfWmtGM29GenpnRHV1MG04XzNSRk9oc2xTV1A4MTNJTVB3SUZMRDN4MkhSOG9VbkUyYS1fT2UwY1diRGZrQUtHLUxKaS0zNzFoNWs4T25lMHV0UmhyV1lTRnBCUS1lWGlkdlJEcUg3NUFpcDlNemJ5TExYTUlvbm1LWXlXa3EzczJMWVRiX1g1UkhMSnZqVjd0cDZxS20ySnl3TzY3TUdiVGdTUzJsbzc1SGJIaVQ5dmFBZldaUFF5Y1FDMHROTlhfRUJfUUYyTWJBckpibi1UM3NaTVhya195X3RDempMYTNWZWJXS2lsUGV5aXpYWFFBb1cyc0Q5MGZybEZYazhwMU9lTGhxcWxLVml3di1rUlk0ZU5ISnJXOGVTdTZmQndLUGw4dWJUcGt3eV9ickprYmFXUVVoYVk5ZFN5Wml5bWhPZHFTTGw4dTRwRnZvd0xKdDJQanpWajhDRTNQM0M4OEtSMkdsNjRvaUF3dnk2OWVsT1E3aXRXa1VjLTZlUDR2X0paT1VOcVZUN2hCUzNKWEJ4MmxjeS0yc0lEc1FhMGhSSW10eERFZXhoendWUFJrM2xwQkFwSlljcTR6LUVHWGV6dm5Kb0NERVRHeHVMSHl3MkctRV9lY1FnTTlyVTVQOW9GTG5fSm96OFBBYkJ6ZzgwOURwc0lKM051Slp3ZkhoNkpyNHlRRXZvTHphTFhsa1VVS0IxQXhjWXphLV9tT0R6WEJnRXRHRnBpWk9UYkFoRUhSWXRzQ25paUpDbTR6Mk9nay1HY1YwdVl1cTlCZDhreGIyVnU2NTdqVG1XVGxPVVE5SnZ1bXZ4Mks3MnJyakZBdzRpVy1PSUFab2JpdmVoN1RtUjgwaFdQaThOcTlBd1BGYm9zVWZwWDUzeUNZWUNpSk15ZzUwRUpQZXBwaFduS2NReERGNUNZMjBXSkpEXzN5REJ6WEFOeDdJSmE3NUVGMGRRVm9BSXEtVW1nODJHZWMzdWdrUEtTTXl0RHZNMmZUVlRPMl9QYlJETkFlbFpoZ3VEX0hZMDhhRTVybVE0NTFPLVFlR3pnbWw3QWpNaHJBTVZwQlp1VEpneFBNRGpWaTFWZHZ2ZzlaLWVobTlwYzFENzRCQ1hwdEZFUEVFRHhjd1dvaEhkLXExVTMtczZQTVlRbUs4QzJEQ25zekUzZ0xueTZJQW9PZ19zSGJUT1Jha1dYWnlhZkNMelJ4NmxCaFlyNkZ4OHo1Y1BIOXQ1VEstcmVqVjdPTURZbWVaUFl5QmFCbE5NYzNRbVo4Tk94d0Z2bWN1MnhEMVU4cjZqSXRqR3BVdUN5RDEzRGZnZ0REVmZWem9HSldXVHROMXVLWkNfRHVQdi12blRXdFIwZjQ4cTd6dDY1WXVpVC04NFptUXQ0Q1NMTlZNb2plR2lDbXN6bVZ1Y1ExSENxcklsQVBtM243UTJDSmx6ZEdOUWZyV3FJNmU3VHo2VGg2UWVBUkZCYzM5VVI4RjlMaHpDc2dYcGI1NWRTb0E5UXVDTEswdzRWNnFjWUE5QmJkZzRlb1ZRV1J1V1ljTDlKMl92TGZON3ROSWpCa01rTmhmZDZleXk4M2JrUmpuY3p5UFh6UllKUXF2RkppN3Y3ZjhtS0lSV1VkemNPSzhDOFB6UkJvdFR6SXZqdm9XWkpfcW45OHhWVlRJTU1pYWhZRF9ldHE5cjVTeWpTR2ZHM25nVzllblJ6a2FrVDdIc1V0MjYyalpWekxITzJKUGN0X0h2T1RkVFNWeW9wZjFUYUZlY1dCSHdaMVA2NUZpcXFueFhZam9XVnZyWlc1dmg1UGgtUzBKZ2V5U0Z1NndBWldwTkhEbWtNOGhvR09PVzMzRDZUaTFYeXY1VGpvYTI3ZmlZdGdZd3RWUTBHb2RodTBYN3NxN0Y4UnVjWW55M05yYUtJSXpRaUp4ZHRlSGNzQnpxUTRDWk5JSlNuMFZzellVQkRxR0t1Y0RmeVhVXCJcblx0XHR9LFxuXHRcdFwic2NlbmFyaW9cIjpcdFwic2NlX25vX2FzXCIsXG5cdFx0XCJ1dWlkXCI6XHRcImY2OGZkNzA0LTZlYjEtNGQxNC1iMjE4LTcyMjg1MGViM2VmMFwiLFxuXHRcdFwicmVxdWVzdGVyX3V1aWRcIjpcdFwiXCIsXG5cdFx0XCJoYXNoX2FsZ1wiOlx0XCJIUzI1NlwiLFxuXHRcdFwic2lnbl9hbGdcIjpcdFwiUFMyNTZcIixcblx0XHRcInRhX21lbVwiOlx0XCJRYjkyZWUxTlRnZlVpdnlISXZuaEVZcU5IU0tLS0FPVUlzV1Y5dVl0eXA4XCIsXG5cdFx0XCJ0YV9pbWdcIjpcdFwiQW1VLXI4OHREOVhVSlMwOGhkVkJTREFySDE0WnI3UXFRQlpwLXp2WkE5a1wiLFxuXHRcdFwidGFfYXR0clwiOlx0XCJcIixcblx0XHRcInRjYlwiOlx0XCJcIlxuXHR9LFxuXHRcImhhbmRsZXJcIjpcdFwicmVwb3J0LW91dHB1dFwiXG59In0", ++ } ++ ); ++ let client = Client::new(); ++ let endpoint = "http://127.0.0.1:8080/attestation"; ++ let res = client ++ .post(endpoint) ++ .header("Content-Type", "application/json") ++ .body(request_body.to_string()) ++ .send() ++ .unwrap(); ++ assert_eq!(res.status(), reqwest::StatusCode::OK); ++ println!("{:?}", res.text().unwrap()); ++ } ++ ++ #[test] ++ fn api_get_challenge() { ++ let client: Client = Client::new(); ++ let endpoint = "http://127.0.0.1:8080/challenge"; ++ let res = client ++ .get(endpoint) ++ .send() ++ .unwrap(); ++ assert_eq!(res.status(), reqwest::StatusCode::OK); ++ println!("{:?}", res.text().unwrap()); ++ } ++ ++} +diff --git a/service/attestation/attestation-service/token/Cargo.toml b/service/attestation/attestation-service/token/Cargo.toml +new file mode 100644 +index 0000000..c4b885c +--- /dev/null ++++ b/service/attestation/attestation-service/token/Cargo.toml +@@ -0,0 +1,13 @@ ++[package] ++name = "token_signer" ++version = "0.1.0" ++edition = "2021" ++ ++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html ++ ++[dependencies] ++jsonwebtoken.workspace = true ++serde.workspace = true ++serde_json.workspace = true ++anyhow.workspace = true ++attestation-types.workspace = true +\ No newline at end of file +diff --git a/service/attestation/attestation-service/token/src/lib.rs b/service/attestation/attestation-service/token/src/lib.rs +new file mode 100644 +index 0000000..ed41a4e +--- /dev/null ++++ b/service/attestation/attestation-service/token/src/lib.rs +@@ -0,0 +1,115 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use anyhow::{Result, bail}; ++use jsonwebtoken::{encode, get_current_timestamp, ++ Algorithm, EncodingKey, Header, ++}; ++use std::path::Path; ++use serde::{Deserialize, Serialize}; ++use serde_json::Value; ++use attestation_types::{EvlResult, Claims}; ++ ++ ++#[derive(Debug, Clone, Serialize, Deserialize)] ++pub struct TokenSignConfig { ++ pub iss: String, ++ pub nbf: usize, // 生效时刻 ++ pub valid_duration: usize, // 有效时间 ++ pub alg: SignAlg, ++ pub key: String, ++} ++ ++impl Default for TokenSignConfig { ++ fn default() -> Self { ++ TokenSignConfig { ++ iss: "oeas".to_string(), ++ nbf: 0, ++ valid_duration: 300, ++ alg: SignAlg::PS256, ++ key: "/etc/attestation/attestation-service/token/private.pem".to_string(), ++ } ++ } ++} ++ ++ ++#[derive(Debug, Clone, Serialize, Deserialize)] ++pub struct EvlReport { ++ pub tee: String, ++ pub result: EvlResult, ++ pub tcb_status: Value, ++} ++ ++pub type SignAlg = Algorithm; ++pub struct TokenSigner { ++ pub config: TokenSignConfig, ++} ++ ++impl Default for TokenSigner { ++ fn default() -> Self { ++ TokenSigner { ++ config: TokenSignConfig::default(), ++ } ++ } ++} ++ ++impl TokenSigner { ++ pub fn new(config: TokenSignConfig) -> Result { ++ Ok(TokenSigner { config }) ++ } ++ fn support_rs(alg: &Algorithm) -> bool ++ { ++ if *alg == Algorithm::RS256 || *alg == Algorithm::RS384 || *alg == Algorithm::RS512{ ++ return true; ++ } ++ return false; ++ } ++ fn support_ps(alg: &Algorithm) -> bool ++ { ++ if *alg == Algorithm::PS256 || *alg == Algorithm::PS384 || *alg == Algorithm::PS512 { ++ return true; ++ } ++ return false; ++ } ++ pub fn sign(&self, report: &EvlReport) -> Result { ++ let alg: Algorithm = self.config.alg; ++ let mut header = Header::new(alg); ++ header.typ = Some("JWT".to_string()); ++ let unix_time = get_current_timestamp(); ++ let claims: Claims = Claims { ++ iss: self.config.iss.clone(), ++ iat: usize::try_from(unix_time).expect("unix time to usize error"), ++ nbf: usize::try_from(unix_time).expect("unix time to usize error"), ++ exp: usize::try_from(unix_time).expect("unix time to usize error") ++ + self.config.valid_duration, ++ evaluation_reports: report.result.clone(), ++ tee: report.tee.clone(), ++ tcb_status: report.tcb_status.clone(), ++ }; ++ if !Self::support_rs(&alg) && !Self::support_ps(&alg) { ++ bail!("unknown algrithm {:?}", alg); ++ } ++ if !Path::new(&self.config.key).exists() { ++ bail!("token verfify failed, {:?} cert not exist", self.config.key); ++ } ++ let key = std::fs::read(&self.config.key).unwrap(); ++ let key_value: EncodingKey = match EncodingKey::from_rsa_pem(&key) { ++ Ok(val) => val, ++ _ => bail!("get key from input error"), ++ }; ++ ++ let token = match encode(&header, &claims, &key_value) { ++ Ok(val) => val, ++ Err(e) => bail!("sign jwt token error {:?}", e), ++ }; ++ Ok(token) ++ } ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-service/verifier/Cargo.toml b/service/attestation/attestation-service/verifier/Cargo.toml +new file mode 100644 +index 0000000..e870fa7 +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/Cargo.toml +@@ -0,0 +1,27 @@ ++[package] ++name = "verifier" ++version = "0.1.0" ++edition = "2021" ++ ++[dependencies] ++anyhow.workspace = true ++serde.workspace = true ++serde_json.workspace = true ++async-trait.workspace = true ++cose-rust.workspace = true ++ciborium.workspace = true ++hex.workspace = true ++openssl.workspace = true ++log.workspace = true ++ima-measurements.workspace = true ++rand.workspace = true ++fallible-iterator.workspace = true ++attestation-types.workspace = true ++ ++[dev-dependencies] ++ ++[features] ++default = [ "itrustee-verifier","virtcca-verifier" ] ++itrustee-verifier = [] ++virtcca-verifier = [] ++no_as = [] +diff --git a/service/attestation/attestation-service/verifier/src/itrustee/itrustee.rs b/service/attestation/attestation-service/verifier/src/itrustee/itrustee.rs +new file mode 100644 +index 0000000..9749871 +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/itrustee/itrustee.rs +@@ -0,0 +1,53 @@ ++/* automatically generated by rust-bindgen 0.69.4 */ ++ ++#[repr(C)] ++#[derive(Debug, Copy, Clone)] ++pub struct buffer_data { ++ pub size: ::std::os::raw::c_uint, ++ pub buf: *mut ::std::os::raw::c_uchar, ++} ++#[test] ++fn bindgen_test_layout_buffer_data() { ++ const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); ++ let ptr = UNINIT.as_ptr(); ++ assert_eq!( ++ ::std::mem::size_of::(), ++ 16usize, ++ concat!("Size of: ", stringify!(buffer_data)) ++ ); ++ assert_eq!( ++ ::std::mem::align_of::(), ++ 8usize, ++ concat!("Alignment of ", stringify!(buffer_data)) ++ ); ++ assert_eq!( ++ unsafe { ::std::ptr::addr_of!((*ptr).size) as usize - ptr as usize }, ++ 0usize, ++ concat!( ++ "Offset of field: ", ++ stringify!(buffer_data), ++ "::", ++ stringify!(size) ++ ) ++ ); ++ assert_eq!( ++ unsafe { ::std::ptr::addr_of!((*ptr).buf) as usize - ptr as usize }, ++ 8usize, ++ concat!( ++ "Offset of field: ", ++ stringify!(buffer_data), ++ "::", ++ stringify!(buf) ++ ) ++ ); ++} ++ ++#[link(name = "teeverifier")] ++extern "C" { ++ pub fn tee_verify_report( ++ data_buf: *mut buffer_data, ++ nonce: *mut buffer_data, ++ type_: ::std::os::raw::c_int, ++ filename: *mut ::std::os::raw::c_char, ++ ) -> ::std::os::raw::c_int; ++} +diff --git a/service/attestation/attestation-service/verifier/src/itrustee/mod.rs b/service/attestation/attestation-service/verifier/src/itrustee/mod.rs +new file mode 100644 +index 0000000..67c857a +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/itrustee/mod.rs +@@ -0,0 +1,76 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! itrustee verifier plugin ++ ++use super::*; ++use log; ++use serde_json::json; ++use std::path::Path; ++use std::ops::Add; ++ ++mod itrustee; ++ ++const ITRUSTEE_REF_VALUE_FILE: &str = "/etc/attestation/attestation-service/verifier/itrustee/basevalue.txt"; ++ ++#[derive(Debug, Default)] ++pub struct ItrusteeVerifier {} ++ ++impl ItrusteeVerifier { ++ pub async fn evaluate(&self, user_data: &[u8], evidence: &[u8]) -> Result { ++ return evalute_wrapper(user_data, evidence); ++ } ++} ++ ++fn evalute_wrapper(user_data: &[u8], evidence: &[u8]) -> Result { ++ let mut in_data = user_data.to_vec(); ++ let mut in_evidence = evidence.to_vec(); ++ let mut data_buf: itrustee::buffer_data = itrustee::buffer_data { ++ size: in_evidence.len() as ::std::os::raw::c_uint, ++ buf: in_evidence.as_mut_ptr() as *mut ::std::os::raw::c_uchar, ++ }; ++ let mut nonce = itrustee::buffer_data { ++ size: in_data.len() as ::std::os::raw::c_uint, ++ buf: in_data.as_mut_ptr() as *mut ::std::os::raw::c_uchar, ++ }; ++ log::info!("input nonce:{:?}", nonce); ++ let policy: std::os::raw::c_int = 1; ++ if !Path::new(ITRUSTEE_REF_VALUE_FILE).exists() { ++ log::error!("itrustee verify report {} not exists", ITRUSTEE_REF_VALUE_FILE); ++ bail!("itrustee verify report {} not exists", ITRUSTEE_REF_VALUE_FILE); ++ } ++ let ref_file = String::from(ITRUSTEE_REF_VALUE_FILE); ++ let mut file = ref_file.add("\0"); ++ let basevalue = file.as_mut_ptr() as *mut ::std::os::raw::c_char; ++ unsafe { ++ let ret = itrustee::tee_verify_report(&mut data_buf, &mut nonce, policy, basevalue); ++ if ret != 0 { ++ log::error!("itrustee verify report failed ret:{}", ret); ++ bail!("itrustee verify report failed ret:{}", ret); ++ } ++ } ++ let js_evidence: serde_json::Value = serde_json::from_slice(evidence)?; ++ let payload = json!({ ++ "itrustee.nonce": js_evidence["payload"]["nonce"].clone(), ++ "itrustee.hash_alg": js_evidence["payload"]["hash_alg"].clone(), ++ "itrustee.key": js_evidence["payload"]["key"].clone(), ++ "itrustee.ta_img": js_evidence["payload"]["ta_img"].clone(), ++ "itrustee.ta_mem": js_evidence["payload"]["ta_mem"].clone(), ++ "itrustee.uuid": js_evidence["payload"]["uuid"].clone(), ++ "itrustee.version": js_evidence["payload"]["version"].clone(), ++ }); ++ let claim = json!({ ++ "tee": "itrustee", ++ "payload" : payload, ++ }); ++ Ok(claim as TeeClaim) ++} +diff --git a/service/attestation/attestation-service/verifier/src/lib.rs b/service/attestation/attestation-service/verifier/src/lib.rs +new file mode 100644 +index 0000000..58df3bd +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/lib.rs +@@ -0,0 +1,80 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! Unified tee verifier ++//! ++//! This crate provides unified APIs to verify TEE evidence. ++ ++use anyhow::*; ++use serde_json; ++use async_trait::async_trait; ++ ++use attestation_types::{Evidence, TeeType}; ++ ++#[cfg(feature = "itrustee-verifier")] ++pub mod itrustee; ++ ++#[cfg(feature = "virtcca-verifier")] ++pub mod virtcca; ++ ++pub type TeeClaim = serde_json::Value; ++ ++#[derive(Debug, Default)] ++pub struct Verifier {} ++ ++#[async_trait] ++pub trait VerifierAPIs { ++ async fn verify_evidence(&self, user_data: &[u8], evidence: &[u8]) -> Result; ++ async fn verify_ima(&self, ++ evidence: &[u8], ++ claim: &serde_json::Value, ++ ) -> Result<()>; ++} ++ ++const MAX_CHALLENGE_LEN: usize = 64; ++ ++#[async_trait] ++impl VerifierAPIs for Verifier { ++ async fn verify_evidence(&self, user_data: &[u8], evidence: &[u8]) -> Result { ++ let len = user_data.len(); ++ if len <= 0 || len > MAX_CHALLENGE_LEN { ++ log::error!("challenge len is error, expecting 0 < len <= {}, got {}", MAX_CHALLENGE_LEN, len); ++ bail!("challenge len is error, expecting 0 < len <= {}, got {}", MAX_CHALLENGE_LEN, len); ++ } ++ let aa_evidence: Evidence = serde_json::from_slice(evidence)?; ++ let tee_type = aa_evidence.tee; ++ let evidence = aa_evidence.evidence.as_bytes(); ++ match tee_type { ++ #[cfg(feature = "itrustee-verifier")] ++ TeeType::Itrustee => itrustee::ItrusteeVerifier::default().evaluate(user_data, evidence).await, ++ #[cfg(feature = "virtcca-verifier")] ++ TeeType::Virtcca => virtcca::VirtCCAVerifier::default().evaluate(user_data, evidence).await, ++ _ => bail!("unsupported tee type:{:?}", tee_type), ++ } ++ } ++ async fn verify_ima(&self, ++ evidence: &[u8], ++ claim: &serde_json::Value, ++ ) -> Result<()> { ++ let aa_evidence: Evidence = serde_json::from_slice(evidence)?; ++ let tee_type = aa_evidence.tee; ++ let digest_list_file = "/etc/attestation/attestation-service/verifier/digest_list_file".to_string(); ++ match tee_type { ++ #[cfg(feature = "virtcca-verifier")] ++ TeeType::Virtcca => virtcca::ima::ImaVerify::default().ima_verify(evidence, claim, digest_list_file), ++ _ => { ++ log::info!("unsupported ima type:{:?}", tee_type); ++ Ok(()) ++ }, ++ } ++ } ++} +diff --git a/service/attestation/attestation-service/verifier/src/virtcca/ima.rs b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs +new file mode 100644 +index 0000000..44292e8 +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs +@@ -0,0 +1,91 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use anyhow::{Result, bail}; ++use ima_measurements::{Event, EventData, Parser}; ++use fallible_iterator::FallibleIterator; ++use std::fs; ++use std::process::Command; ++use serde_json::Value; ++use rand::Rng; ++ ++use attestation_types::{Evidence, VirtccaEvidence}; ++ ++#[derive(Debug)] ++pub struct ImaVerify { ++ log_path: String, ++} ++ ++impl Default for ImaVerify { ++ fn default() -> Self { ++ let mut rng = rand::thread_rng(); ++ let n: u64 = rng.gen(); ++ ImaVerify { ++ // log_path: format!("/tmp/attestation-service/ima-log-{}", n), // todo fs::write depends attestation-service dir exist ++ log_path: format!("/tmp/ima-log-{}", n), ++ } ++ } ++} ++ ++impl ImaVerify { ++ // todo return detail verify result list with policy ++ pub fn ima_verify(&self, evidence: &[u8], claim: &Value, digest_list_file: String) -> Result<()> { ++ let aa_evidence: Evidence = serde_json::from_slice(evidence)?; ++ let evidence = aa_evidence.evidence.as_bytes(); ++ let virtcca_ev: VirtccaEvidence = serde_json::from_slice(evidence)?; ++ let ima_log = match virtcca_ev.ima_log { ++ Some(ima_log) => ima_log, ++ _ => {log::info!("no ima log"); return Ok(())}, ++ }; ++ ++ fs::write(&self.log_path, &ima_log).expect("write img log failed"); ++ let f = fs::File::open(&self.log_path).expect("ima log file not found"); ++ ++ let claim_ima_log_hash = claim["payload"]["cvm"]["rem"][0].clone(); ++ let mut parser = Parser::new(f); ++ ++ let mut events: Vec = Vec::new(); ++ while let Some(event) = parser.next()? { ++ events.push(event); ++ } ++ ++ let pcr_values = parser.pcr_values(); ++ let pcr_10 = pcr_values.get(&10).expect("PCR 10 not measured"); ++ let string_pcr_sha256 = hex::encode(pcr_10.sha256); ++ ++ if Value::String(string_pcr_sha256.clone()) != claim_ima_log_hash { ++ log::error!("ima log verify failed string_pcr_sha256 {}, string_claim_ima_log_hash {}", string_pcr_sha256, claim_ima_log_hash); ++ bail!("ima log hash verify failed"); ++ } ++ ++ // parser each file digest in ima log, and compare with reference base value ++ for event in events { ++ let file_digest = match event.data { ++ EventData::ImaNg{digest, name} => {drop(name); digest.digest}, ++ _ => bail!("Inalid event {:?}", event), ++ }; ++ let hex_str_digest = hex::encode(file_digest); ++ //log::info!("hex_str_digest {}", hex_str_digest); ++ let output = Command::new("grep") ++ .arg("-E") ++ .arg("-i") ++ .arg(&hex_str_digest) ++ .arg(&digest_list_file) ++ .output()?; ++ if output.stdout.is_empty() { ++ log::error!("there is no refernce base value of file digest {:?}", hex_str_digest); ++ } ++ } ++ ++ Ok(()) ++ } ++} ++ +diff --git a/service/attestation/attestation-service/verifier/src/virtcca/mod.rs b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs +new file mode 100644 +index 0000000..3ececb7 +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs +@@ -0,0 +1,427 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! virtcca verifier plugin ++use super::TeeClaim; ++ ++use anyhow::{Result, bail, anyhow}; ++use cose::keys::CoseKey; ++use cose::message::CoseMessage; ++use ciborium; ++use ciborium::Value; ++use openssl::rsa; ++use openssl::pkey::Public; ++use openssl::x509; ++use openssl::pkey::PKey; ++use log; ++use serde_json::json; ++ ++pub use attestation_types::VirtccaEvidence; ++pub mod ima; ++ ++#[cfg(not(feature = "no_as"))] ++const VIRTCCA_ROOT_CERT: &str = "/etc/attestation/attestation-service/verifier/virtcca/Huawei Equipment Root CA.pem"; ++#[cfg(not(feature = "no_as"))] ++const VIRTCCA_SUB_CERT: &str = "/etc/attestation/attestation-service/verifier/virtcca/Huawei IT Product CA.pem"; ++ ++// attestation agent local reference ++#[cfg(feature = "no_as")] ++const VIRTCCA_REF_VALUE_FILE: &str = "/etc/attestation/attestation-agent/local_verifier/virtcca/ref_value.json"; ++#[cfg(feature = "no_as")] ++const VIRTCCA_ROOT_CERT: &str = "/etc/attestation/attestation-agent/local_verifier/virtcca/Huawei Equipment Root CA.pem"; ++#[cfg(feature = "no_as")] ++const VIRTCCA_SUB_CERT: &str = "/etc/attestation/attestation-agent/local_verifier/virtcca/Huawei IT Product CA.pem"; ++ ++#[derive(Debug, Default)] ++pub struct VirtCCAVerifier {} ++ ++impl VirtCCAVerifier { ++ pub async fn evaluate(&self, user_data: &[u8], evidence: &[u8]) -> Result { ++ return Evidence::verify(user_data, evidence); ++ } ++} ++ ++const CBOR_TAG: u64 = 399; ++const CVM_LABEL: i128 = 44241; ++ ++const CVM_CHALLENGE_LABEL: i128 = 10; ++const CVM_RPV_LABEL: i128 = 44235; ++const CVM_RIM_LABEL: i128 = 44238; ++const CVM_REM_LABEL: i128 = 44239; ++const CVM_HASH_ALG_LABEL: i128 = 44236; ++const CVM_PUB_KEY_LABEL: i128 = 44237; ++const CVM_PUB_KEY_HASH_ALG_LABEL: i128 = 44240; ++ ++const CVM_CHALLENGE_SIZE: usize = 64; ++const CVM_RPV_SIZE: usize = 64; ++const CVM_REM_ARR_SIZE: usize = 4; ++const CVM_PUB_KEY_SIZE: usize = 550; ++ ++#[derive(Debug)] ++pub struct CvmToken { ++ pub challenge: [u8; CVM_CHALLENGE_SIZE], // 10 => bytes .size 64 ++ pub rpv: [u8; CVM_RPV_SIZE], // 44235 => bytes .size 64 ++ pub rim: Vec, // 44238 => bytes .size {32,48,64} ++ pub rem: [Vec; CVM_REM_ARR_SIZE], // 44239 => [ 4*4 bytes .size {32,48,64} ] ++ pub hash_alg: String, // 44236 => text ++ pub pub_key: [u8; CVM_PUB_KEY_SIZE], // 44237 => bytes .size 550 ++ pub pub_key_hash_alg: String, // 44240 => text ++} ++ ++pub struct Evidence { ++ /// COSE Sign1 envelope for cvm_token ++ pub cvm_envelop: CoseMessage, ++ /// Decoded cvm token ++ pub cvm_token: CvmToken, ++} ++ ++impl Evidence { ++ pub fn new() -> Self { ++ Self { ++ cvm_envelop: CoseMessage::new_sign(), ++ cvm_token: CvmToken::new(), ++ } ++ } ++ pub fn verify(user_data: &[u8], evidence: &[u8]) -> Result { ++ let virtcca_ev: VirtccaEvidence = serde_json::from_slice(evidence)?; ++ let evidence = virtcca_ev.evidence; ++ let dev_cert = virtcca_ev.dev_cert; ++ let mut evidence = Evidence::decode(evidence)?; ++ ++ // verify platform token ++ evidence.verify_platform_token(&dev_cert)?; ++ ++ // verify cvm token ++ evidence.verify_cvm_token(user_data)?; ++ ++ // todo parsed TeeClaim ++ evidence.parse_claim_from_evidence() ++ } ++ fn parse_claim_from_evidence(&self) -> Result { ++ let payload = json!({ ++ "vcca.cvm.challenge": hex::encode(self.cvm_token.challenge.clone()), ++ "vcca.cvm.rpv": hex::encode(self.cvm_token.rpv.clone()), ++ "vcca.cvm.rim": hex::encode(self.cvm_token.rim.clone()), ++ "vcca.cvm.rem.0": hex::encode(self.cvm_token.rem[0].clone()), ++ "vcca.cvm.rem.1": hex::encode(self.cvm_token.rem[1].clone()), ++ "vcca.cvm.rem.2": hex::encode(self.cvm_token.rem[2].clone()), ++ "vcca.cvm.rem.3": hex::encode(self.cvm_token.rem[3].clone()), ++ "vcca.platform": "", ++ }); ++ let claim = json!({ ++ "tee": "vcca", ++ "payload" : payload, ++ }); ++ Ok(claim as TeeClaim) ++ } ++ fn verify_platform_token(&mut self, dev_cert: &[u8]) -> Result<()> { ++ // todo verify platform COSE_Sign1 by dev_cert, virtCCA report has no platform token now ++ ++ // verify dev_cet by cert chain ++ Evidence::verify_dev_cert_chain(dev_cert)?; ++ ++ Ok(()) ++ } ++ // todo verify cert chain, now only verify signature ++ fn verify_dev_cert_chain(dev_cert: &[u8]) -> Result<()> { ++ let dev_cert = x509::X509::from_der(dev_cert)?; ++ let sub_cert_file = std::fs::read(VIRTCCA_SUB_CERT)?; ++ let sub_cert = x509::X509::from_pem(&sub_cert_file)?; ++ let root_cert_file = std::fs::read(VIRTCCA_ROOT_CERT)?; ++ let root_cert = x509::X509::from_pem(&root_cert_file)?; ++ ++ // verify dev_cert by sub_cert ++ let ret = dev_cert.verify(&(sub_cert.public_key()? as PKey))?; ++ if !ret { ++ log::error!("verify dev cert by sub cert failed"); ++ bail!("verify dev cert by sub cert failed"); ++ } ++ // verify sub_cert by root_cert ++ let ret = sub_cert.verify(&(root_cert.public_key()? as PKey))?; ++ if !ret { ++ log::error!("verify sub cert by root cert failed"); ++ bail!("verify sub cert by root cert failed"); ++ } ++ // verify self signed root_cert ++ let ret = root_cert.verify(&(root_cert.public_key()? as PKey))?; ++ if !ret { ++ log::error!("verify self signed root cert failed"); ++ bail!("verify self signed root cert failed"); ++ } ++ Ok(()) ++ } ++ fn verify_cvm_token(&mut self, challenge: &[u8]) -> Result<()> { ++ // verify challenge ++ let len = challenge.len(); ++ let token_challenge = &self.cvm_token.challenge[0..len]; ++ if challenge != token_challenge { ++ log::error!("verify cvm token challenge error, cvm_token challenge {:?}, input challenge {:?}", ++ token_challenge, challenge); ++ bail!("verify cvm token challenge error, cvm_token challenge {:?}, input challenge {:?}", ++ token_challenge, challenge); ++ } ++ ++ // todo verify cvm pubkey by platform.challenge, virtCCA report has no platform token now ++ ++ // verify COSE_Sign1 signature begin ++ let raw_pub_key = self.cvm_token.pub_key; ++ let mut cose_key: CoseKey = Evidence::from_raw_pub_key(&raw_pub_key)?; ++ cose_key.key_ops(vec![cose::keys::KEY_OPS_VERIFY]); ++ match self.cvm_envelop.header.alg { ++ Some(alg) => cose_key.alg(alg), ++ None => bail!("cose sign verify alg is none"), ++ } ++ self.cvm_envelop.key(&cose_key).map_err(|err| anyhow!("set cose_key to COSE_Sign1 envelop failed: {err:?}"))?; ++ self.cvm_envelop.decode(None, None).map_err(|err| anyhow!("verify COSE_Sign1 signature failed:{err:?}"))?; ++ // verify COSE_Sign1 signature end ++ ++ // verfiy cvm token with reference value ++ #[cfg(feature = "no_as")] ++ self.compare_with_ref()?; ++ ++ Ok(()) ++ } ++ #[cfg(feature = "no_as")] ++ fn compare_with_ref(&mut self) -> Result<()> { ++ let ref_file = std::fs::read(VIRTCCA_REF_VALUE_FILE)?; ++ let js_ref = serde_json::from_slice(&ref_file)?; ++ match js_ref { ++ serde_json::Value::Object(obj) => { ++ for (k, v) in obj { ++ if k == "rim" { ++ let rim_ref = match v { ++ serde_json::Value::String(rim) => rim, ++ _ => bail!("tim ref expecting String"), ++ }; ++ let rim = hex::encode(self.cvm_token.rim.clone()); ++ if rim_ref != rim { ++ log::error!("expecting rim: {}, got: {}", rim_ref, rim); ++ bail!("expecting rim: {}, got: {}", rim_ref, rim); ++ } ++ } ++ } ++ } ++ _ => bail!("invalid json ref value"), ++ } ++ ++ Ok(()) ++ } ++ fn from_raw_pub_key(raw_pub_key: &[u8]) -> Result { ++ let pub_key: rsa::Rsa = rsa::Rsa::public_key_from_der(raw_pub_key)?; ++ let mut cose_key = CoseKey::new(); ++ cose_key.kty(cose::keys::RSA); ++ cose_key.e(pub_key.e().to_vec()); ++ cose_key.n(pub_key.n().to_vec()); ++ ++ Ok(cose_key) ++ } ++ pub fn decode(raw_evidence: Vec) -> Result { ++ let mut evidence: Evidence = Evidence::new(); ++ ++ // decode CBOR evidence to ciborium Value ++ let val: Value = ciborium::de::from_reader(raw_evidence.as_slice())?; ++ log::debug!("[debug] decode CBOR virtcca token to ciborium Value:{:?}", val); ++ if let Value::Tag(t, m) = val { ++ if t != CBOR_TAG { ++ log::error!("input evidence error, expecting tag {}, got {}", CBOR_TAG, t); ++ bail!("input evidence error, expecting tag {}, got {}", CBOR_TAG, t); ++ } ++ if let Value::Map(contents) = *m { ++ for (k, v) in contents.iter() { ++ if let Value::Integer(i) = k { ++ match (*i).into() { ++ CVM_LABEL => evidence.set_cvm_token(v)?, ++ err => bail!("unknown label {}", err), ++ } ++ } else { ++ bail!("expecting integer key"); ++ } ++ } ++ } else { ++ bail!("expecting map type"); ++ } ++ } else { ++ bail!("expecting tag type"); ++ } ++ ++ let ret = evidence.cvm_envelop.init_decoder(None); ++ match ret { ++ Ok(_) => log::info!("decode COSE success"), ++ Err(e) => { ++ log::error!("decode COSE failed, {:?}", e); ++ bail!("decode COSE failed"); ++ }, ++ } ++ ++ // decode cvm CBOR payload ++ evidence.cvm_token = CvmToken::decode(&evidence.cvm_envelop.payload)?; ++ Ok(evidence) ++ } ++ fn set_cvm_token(&mut self, v: &Value) -> Result<()> { ++ let tmp = v.as_bytes(); ++ if tmp.is_none() { ++ log::error!("cvm token is none"); ++ bail!("cvm token is none"); ++ } ++ self.cvm_envelop.bytes = tmp.unwrap().clone(); ++ Ok(()) ++ } ++} ++ ++impl CvmToken { ++ pub fn new() -> Self { ++ Self { ++ challenge: [0; CVM_CHALLENGE_SIZE], ++ rpv: [0; CVM_RPV_SIZE], ++ rim: vec![0, 64], ++ rem: Default::default(), ++ hash_alg: String::from(""), ++ pub_key: [0; CVM_PUB_KEY_SIZE], ++ pub_key_hash_alg: String::from(""), ++ } ++ } ++ pub fn decode(raw_payload: &Vec) -> Result { ++ let payload: Vec = ciborium::de::from_reader(raw_payload.as_slice())?; ++ log::debug!("After decode CBOR payload, payload {:?}", payload); ++ let payload: Value = ciborium::de::from_reader(payload.as_slice())?; ++ log::debug!("After decode CBOR payload agin, payload {:?}", payload); ++ let mut cvm_token: CvmToken = CvmToken::new(); ++ if let Value::Map(contents) = payload { ++ for (k, v) in contents.iter() { ++ if let Value::Integer(i) = k { ++ match (*i).into() { ++ CVM_CHALLENGE_LABEL => cvm_token.set_challenge(v)?, ++ CVM_RPV_LABEL => cvm_token.set_rpv(v)?, ++ CVM_RIM_LABEL => cvm_token.set_rim(v)?, ++ CVM_REM_LABEL => cvm_token.set_rem(v)?, ++ CVM_HASH_ALG_LABEL => cvm_token.set_hash_alg(v)?, ++ CVM_PUB_KEY_LABEL => cvm_token.set_pub_key(v)?, ++ CVM_PUB_KEY_HASH_ALG_LABEL => cvm_token.set_pub_key_hash_alg(v)?, ++ err => bail!("cvm payload unkown label {}", err), ++ } ++ } else { ++ bail!("cvm payload expecting integer key"); ++ } ++ } ++ } else { ++ bail!("expecting cvm payload map type"); ++ } ++ log::debug!("cvm_token decode from raw payload, {:?}", cvm_token); ++ Ok(cvm_token) ++ } ++ fn set_challenge(&mut self, v: &Value) -> Result<()> { ++ let tmp = v.as_bytes(); ++ if tmp.is_none() { ++ bail!("cvm token challenge is none"); ++ } ++ let tmp = tmp.unwrap().clone(); ++ if tmp.len() != CVM_CHALLENGE_SIZE { ++ bail!("cvm token challenge expecting {} bytes, got {}", CVM_CHALLENGE_SIZE,tmp.len()); ++ } ++ self.challenge[..].clone_from_slice(&tmp); ++ Ok(()) ++ } ++ fn set_rpv(&mut self, v: &Value) -> Result<()> { ++ let tmp = v.as_bytes(); ++ if tmp.is_none() { ++ bail!("cvm token rpv is none"); ++ } ++ let tmp = tmp.unwrap().clone(); ++ if tmp.len() != CVM_RPV_SIZE { ++ bail!("cvm token rpv expecting {} bytes, got {}", CVM_RPV_SIZE, tmp.len()); ++ } ++ self.rpv[..].clone_from_slice(&tmp); ++ Ok(()) ++ } ++ fn get_measurement(v: &Value, who: &str) -> Result> { ++ let tmp = v.as_bytes(); ++ if tmp.is_none() { ++ bail!("cvm token {} is none", who); ++ } ++ let tmp = tmp.unwrap().clone(); ++ if !matches!(tmp.len(), 32 | 48 | 64) { ++ bail!("cvm token {} expecting 32, 48 or 64 bytes, got {}", who, tmp.len()); ++ } ++ Ok(tmp) ++ } ++ fn set_rim(&mut self, v: &Value) -> Result<()> { ++ self.rim = Self::get_measurement(v, "rim")?; ++ Ok(()) ++ } ++ fn set_rem(&mut self, v: &Value) -> Result<()> { ++ let tmp = v.as_array(); ++ if tmp.is_none() { ++ bail!("cvm token rem is none"); ++ } ++ let tmp = tmp.unwrap().clone(); ++ if tmp.len() != 4 { ++ bail!("cvm token rem expecting size {}, got {}", CVM_REM_ARR_SIZE, tmp.len()); ++ } ++ ++ for (i, val) in tmp.iter().enumerate() { ++ self.rem[i] = Self::get_measurement(val, "rem[{i}]")?; ++ } ++ Ok(()) ++ } ++ fn get_hash_alg(v: &Value, who: &str) -> Result { ++ let alg = v.as_text(); ++ if alg.is_none() { ++ bail!("{} hash alg must be str", who); ++ } ++ Ok(alg.unwrap().to_string()) ++ } ++ fn set_hash_alg(&mut self, v: &Value) -> Result<()> { ++ self.hash_alg = Self::get_hash_alg(v, "cvm token")?; ++ Ok(()) ++ } ++ fn set_pub_key(&mut self, v: &Value) -> Result<()> { ++ let tmp = v.as_bytes(); ++ if tmp.is_none() { ++ bail!("cvm token pub key is none"); ++ } ++ let tmp = tmp.unwrap().clone(); ++ if tmp.len() != CVM_PUB_KEY_SIZE { ++ bail!("cvm token pub key len expecting {}, got {}", CVM_PUB_KEY_SIZE, tmp.len()); ++ } ++ self.pub_key[..].clone_from_slice(&tmp); ++ Ok(()) ++ } ++ fn set_pub_key_hash_alg(&mut self, v: &Value) -> Result<()> { ++ self.pub_key_hash_alg = Self::get_hash_alg(v, "pub key")?; ++ Ok(()) ++ } ++} ++ ++#[cfg(test)] ++mod tests { ++ use super::*; ++ use hex; ++ ++ const TEST_VIRTCCA_TOKEN: &[u8; 2862] = include_bytes!("../../test_data/virtcca.cbor"); ++ #[test] ++ fn decode_token() { ++ let token = hex::decode(TEST_VIRTCCA_TOKEN).unwrap(); ++ let dev_cert = std::fs::read("./test_data/virtcca_aik_cert.der").unwrap(); ++ let challenge = Vec::new(); ++ let virtcca_ev = VirtccaEvidence { ++ evidence: token.to_vec(), ++ dev_cert: dev_cert, ++ ima_log: None, ++ }; ++ let virtcca_ev = serde_json::to_vec(&virtcca_ev).unwrap(); ++ let r = Evidence::verify(&challenge, &virtcca_ev); ++ match r { ++ Ok(claim) => println!("verify success {:?}", claim), ++ Err(e) => assert!(false, "verify failed {:?}", e), ++ } ++ } ++} +diff --git a/service/attestation/attestation-types/Cargo.toml b/service/attestation/attestation-types/Cargo.toml +new file mode 100644 +index 0000000..1fcf465 +--- /dev/null ++++ b/service/attestation/attestation-types/Cargo.toml +@@ -0,0 +1,8 @@ ++[package] ++name = "attestation-types" ++version = "0.1.0" ++edition = "2021" ++ ++[dependencies] ++serde = { version = "1.0", features = ["derive"] } ++serde_json = "1.0" +\ No newline at end of file +diff --git a/service/attestation/attestation-types/src/lib.rs b/service/attestation/attestation-types/src/lib.rs +new file mode 100644 +index 0000000..fcf1d3e +--- /dev/null ++++ b/service/attestation/attestation-types/src/lib.rs +@@ -0,0 +1,52 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use serde::{Serialize, Deserialize}; ++use serde_json::Value; ++ ++#[derive(Debug, Serialize, Deserialize)] ++pub struct VirtccaEvidence { ++ pub evidence: Vec, ++ pub dev_cert: Vec, ++ pub ima_log: Option>, ++} ++ ++#[derive(Debug, Serialize, Deserialize)] ++pub enum TeeType { ++ Itrustee = 1, ++ Virtcca, ++ Invalid, ++} ++ ++ ++#[derive(Debug, Serialize, Deserialize)] ++pub struct Evidence { ++ pub tee: TeeType, ++ pub evidence: String, ++} ++ ++#[derive(Debug, Clone, Serialize, Deserialize)] ++pub struct EvlResult { ++ pub eval_result: bool, ++ pub policy: Vec, ++ pub report: Value, ++} ++ ++#[derive(Debug, Clone, Serialize, Deserialize)] ++pub struct Claims { ++ pub iss: String, ++ pub iat: usize, ++ pub nbf: usize, ++ pub exp: usize, ++ pub evaluation_reports: EvlResult, ++ pub tee: String, ++ pub tcb_status: Value, ++} +\ No newline at end of file +-- +2.33.0 + diff --git a/0081-modify-default-agent-config.patch b/0081-modify-default-agent-config.patch new file mode 100644 index 0000000000000000000000000000000000000000..405708d06dfb7d22bd0aa3eb00876ec4ab25a0df --- /dev/null +++ b/0081-modify-default-agent-config.patch @@ -0,0 +1,29 @@ +From 0793977f51d3f636caf9480f6d155d267ad88f56 Mon Sep 17 00:00:00 2001 +From: houmingyong +Date: Fri, 23 Aug 2024 16:58:24 +0800 +Subject: [PATCH] modify default agent config + +--- + .../attestation-agent/agent/attestation-agent.conf | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/service/attestation/attestation-agent/agent/attestation-agent.conf b/service/attestation/attestation-agent/agent/attestation-agent.conf +index 0d68972a..5c9a0152 100644 +--- a/service/attestation/attestation-agent/agent/attestation-agent.conf ++++ b/service/attestation/attestation-agent/agent/attestation-agent.conf +@@ -1,7 +1,7 @@ + { +- "svr_url": "http://192.168.66.88:8888", ++ "svr_url": "http://127.0.0.1:8080", + "token_cfg": { +- "cert": "/home/cert/as_cert.pem", +- "iss": "oeas" ++ "cert": "/etc/attestation/attestation-agent/as_cert.pem", ++ "iss": "oeas" + } +-} +\ No newline at end of file ++} +-- +2.46.0 + diff --git a/0082-optimize-ima-verify.patch b/0082-optimize-ima-verify.patch new file mode 100644 index 0000000000000000000000000000000000000000..9dbd8fc179eaef7ba4b3763abcbac9a482c5e43c --- /dev/null +++ b/0082-optimize-ima-verify.patch @@ -0,0 +1,243 @@ +From 7d54eae7dbfdbad1de6bac40468c8de9a7260a8b Mon Sep 17 00:00:00 2001 +From: houmingyong +Date: Mon, 26 Aug 2024 20:13:49 +0800 +Subject: [PATCH 1/2] optimize ima verify + +Signed-off-by: houmingyong +--- + .../attestation-service/service/src/lib.rs | 17 +++- + .../attestation-service/verifier/src/lib.rs | 20 ---- + .../verifier/src/virtcca/ima.rs | 93 +++++++++---------- + .../verifier/src/virtcca/mod.rs | 13 ++- + 4 files changed, 69 insertions(+), 74 deletions(-) + +diff --git a/service/attestation/attestation-service/service/src/lib.rs b/service/attestation/attestation-service/service/src/lib.rs +index cc3f4328..3ec73d3d 100644 +--- a/service/attestation/attestation-service/service/src/lib.rs ++++ b/service/attestation/attestation-service/service/src/lib.rs +@@ -100,11 +100,20 @@ impl AttestationService { + let verifier = Verifier::default(); + let claims_evidence = verifier.verify_evidence(user_data, evidence).await?; + +- let mut passed = false; +- let ima_result = verifier.verify_ima(evidence, &claims_evidence).await; +- if ima_result.is_ok() { +- passed = true; ++ let mut passed = true; ++ log::debug!("claims evidece ima: {:?}", claims_evidence["ima"].clone()); ++ match claims_evidence["ima"].clone() { ++ serde_json::Value::Object(obj) => { ++ for (_k, v) in obj { ++ if v == Value::Bool(false) { ++ passed = false; ++ break; ++ } ++ } ++ } ++ _ => log::debug!("no ima result"), + } ++ + // get reference by keys in claims_evidence + let mut ops_refs = ReferenceOps::default(); + let refs_of_claims = ops_refs.query(&claims_evidence["payload"].to_string()); +diff --git a/service/attestation/attestation-service/verifier/src/lib.rs b/service/attestation/attestation-service/verifier/src/lib.rs +index 58df3bd4..0b776c29 100644 +--- a/service/attestation/attestation-service/verifier/src/lib.rs ++++ b/service/attestation/attestation-service/verifier/src/lib.rs +@@ -34,10 +34,6 @@ pub struct Verifier {} + #[async_trait] + pub trait VerifierAPIs { + async fn verify_evidence(&self, user_data: &[u8], evidence: &[u8]) -> Result; +- async fn verify_ima(&self, +- evidence: &[u8], +- claim: &serde_json::Value, +- ) -> Result<()>; + } + + const MAX_CHALLENGE_LEN: usize = 64; +@@ -61,20 +57,4 @@ impl VerifierAPIs for Verifier { + _ => bail!("unsupported tee type:{:?}", tee_type), + } + } +- async fn verify_ima(&self, +- evidence: &[u8], +- claim: &serde_json::Value, +- ) -> Result<()> { +- let aa_evidence: Evidence = serde_json::from_slice(evidence)?; +- let tee_type = aa_evidence.tee; +- let digest_list_file = "/etc/attestation/attestation-service/verifier/digest_list_file".to_string(); +- match tee_type { +- #[cfg(feature = "virtcca-verifier")] +- TeeType::Virtcca => virtcca::ima::ImaVerify::default().ima_verify(evidence, claim, digest_list_file), +- _ => { +- log::info!("unsupported ima type:{:?}", tee_type); +- Ok(()) +- }, +- } +- } + } +diff --git a/service/attestation/attestation-service/verifier/src/virtcca/ima.rs b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs +index 44292e83..30a151fa 100644 +--- a/service/attestation/attestation-service/verifier/src/virtcca/ima.rs ++++ b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs +@@ -12,46 +12,20 @@ + use anyhow::{Result, bail}; + use ima_measurements::{Event, EventData, Parser}; + use fallible_iterator::FallibleIterator; +-use std::fs; +-use std::process::Command; +-use serde_json::Value; +-use rand::Rng; ++use serde_json::{Value, Map, json}; + +-use attestation_types::{Evidence, VirtccaEvidence}; ++const IMA_REFERENCE_FILE: &str = "/etc/attestation/attestation-service/verifier/virtcca/ima/digest_list_file"; + +-#[derive(Debug)] +-pub struct ImaVerify { +- log_path: String, +-} +- +-impl Default for ImaVerify { +- fn default() -> Self { +- let mut rng = rand::thread_rng(); +- let n: u64 = rng.gen(); +- ImaVerify { +- // log_path: format!("/tmp/attestation-service/ima-log-{}", n), // todo fs::write depends attestation-service dir exist +- log_path: format!("/tmp/ima-log-{}", n), +- } +- } +-} ++#[derive(Debug, Default)] ++pub struct ImaVerify {} + + impl ImaVerify { +- // todo return detail verify result list with policy +- pub fn ima_verify(&self, evidence: &[u8], claim: &Value, digest_list_file: String) -> Result<()> { +- let aa_evidence: Evidence = serde_json::from_slice(evidence)?; +- let evidence = aa_evidence.evidence.as_bytes(); +- let virtcca_ev: VirtccaEvidence = serde_json::from_slice(evidence)?; +- let ima_log = match virtcca_ev.ima_log { +- Some(ima_log) => ima_log, +- _ => {log::info!("no ima log"); return Ok(())}, +- }; +- +- fs::write(&self.log_path, &ima_log).expect("write img log failed"); +- let f = fs::File::open(&self.log_path).expect("ima log file not found"); +- +- let claim_ima_log_hash = claim["payload"]["cvm"]["rem"][0].clone(); +- let mut parser = Parser::new(f); ++ pub fn ima_verify(&self, ima_log: &[u8], ima_log_hash: Vec) -> Result { ++ if ima_log.to_vec().is_empty() { ++ return Ok(json!({})); ++ } + ++ let mut parser = Parser::new(ima_log); + let mut events: Vec = Vec::new(); + while let Some(event) = parser.next()? { + events.push(event); +@@ -60,32 +34,55 @@ impl ImaVerify { + let pcr_values = parser.pcr_values(); + let pcr_10 = pcr_values.get(&10).expect("PCR 10 not measured"); + let string_pcr_sha256 = hex::encode(pcr_10.sha256); ++ let string_ima_log_hash = hex::encode(ima_log_hash); + +- if Value::String(string_pcr_sha256.clone()) != claim_ima_log_hash { +- log::error!("ima log verify failed string_pcr_sha256 {}, string_claim_ima_log_hash {}", string_pcr_sha256, claim_ima_log_hash); ++ if string_pcr_sha256.clone() != string_ima_log_hash { ++ log::error!("ima log verify failed string_pcr_sha256 {}, string_ima_log_hash {}", ++ string_pcr_sha256, string_ima_log_hash); + bail!("ima log hash verify failed"); + } + ++ let ima_refs: Vec<_> = file_reader(IMA_REFERENCE_FILE)? ++ .into_iter() ++ .map(String::from) ++ .collect(); ++ ++ let mut ima_detail = Map::new(); + // parser each file digest in ima log, and compare with reference base value + for event in events { +- let file_digest = match event.data { +- EventData::ImaNg{digest, name} => {drop(name); digest.digest}, ++ let (name ,file_digest) = match event.data { ++ EventData::ImaNg{digest, name} => (name, digest.digest), + _ => bail!("Inalid event {:?}", event), + }; + let hex_str_digest = hex::encode(file_digest); +- //log::info!("hex_str_digest {}", hex_str_digest); +- let output = Command::new("grep") +- .arg("-E") +- .arg("-i") +- .arg(&hex_str_digest) +- .arg(&digest_list_file) +- .output()?; +- if output.stdout.is_empty() { ++ if ima_refs.contains(&hex_str_digest) { ++ ima_detail.insert(name, Value::Bool(true)); ++ } else { + log::error!("there is no refernce base value of file digest {:?}", hex_str_digest); ++ ima_detail.insert(name, Value::Bool(false)); + } + } ++ let js_ima_detail: Value = ima_detail.into(); ++ log::debug!("ima verify detail result: {:?}", js_ima_detail); + +- Ok(()) ++ Ok(js_ima_detail) + } + } + ++use std::io::BufRead; ++use std::io::BufReader; ++fn file_reader(file_path: &str) -> ::std::io::Result> { ++ let file = std::fs::File::open(file_path)?; ++ let mut strings = Vec::::new(); ++ let mut reader = BufReader::new(file); ++ let mut buf = String::new(); ++ let mut n: usize; ++ loop { ++ n = reader.read_line(&mut buf)?; ++ if n == 0 { break; } ++ buf.pop(); ++ strings.push(buf.clone()); ++ buf.clear(); ++ } ++ Ok(strings) ++} +diff --git a/service/attestation/attestation-service/verifier/src/virtcca/mod.rs b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs +index 3ececb78..a99f9358 100644 +--- a/service/attestation/attestation-service/verifier/src/virtcca/mod.rs ++++ b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs +@@ -103,10 +103,18 @@ impl Evidence { + // verify cvm token + evidence.verify_cvm_token(user_data)?; + ++ // verify ima ++ let ima_log = match virtcca_ev.ima_log { ++ Some(ima_log) => ima_log, ++ _ => {log::info!("no ima log"); vec![]}, ++ }; ++ let ima: serde_json::Value = ima::ImaVerify::default() ++ .ima_verify(&ima_log, evidence.cvm_token.rem[0].clone())?; ++ + // todo parsed TeeClaim +- evidence.parse_claim_from_evidence() ++ evidence.parse_claim_from_evidence(ima) + } +- fn parse_claim_from_evidence(&self) -> Result { ++ fn parse_claim_from_evidence(&self, ima: serde_json::Value) -> Result { + let payload = json!({ + "vcca.cvm.challenge": hex::encode(self.cvm_token.challenge.clone()), + "vcca.cvm.rpv": hex::encode(self.cvm_token.rpv.clone()), +@@ -120,6 +128,7 @@ impl Evidence { + let claim = json!({ + "tee": "vcca", + "payload" : payload, ++ "ima": ima, + }); + Ok(claim as TeeClaim) + } +-- +2.46.0 + diff --git a/0083-optimize-log-level.patch b/0083-optimize-log-level.patch new file mode 100644 index 0000000000000000000000000000000000000000..04944ec75f5a1074487f461ce44245029844dd9e --- /dev/null +++ b/0083-optimize-log-level.patch @@ -0,0 +1,222 @@ +From 93b932412c210f10464502d81c2973065ae1e614 Mon Sep 17 00:00:00 2001 +From: houmingyong +Date: Fri, 30 Aug 2024 19:42:37 +0800 +Subject: [PATCH 2/2] optimize log level + +Signed-off-by: houmingyong +--- + .../agent/src/bin/aa-test/main.rs | 43 +++++++++++-------- + .../attestation-agent/agent/src/lib.rs | 4 +- + .../attestation-agent/agent/src/main.rs | 2 +- + .../verifier/src/virtcca/mod.rs | 2 +- + 4 files changed, 28 insertions(+), 23 deletions(-) + +diff --git a/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs b/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs +index 58fc389d..89a301bf 100644 +--- a/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs ++++ b/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs +@@ -21,7 +21,7 @@ const TEST_THREAD_NUM: i64 = 1; // multi thread num + + #[tokio::main] + async fn main() { +- env_logger::init(); ++ env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + let mut handles = Vec::with_capacity(TEST_THREAD_NUM as usize); + for i in 0..TEST_THREAD_NUM { + let t = tokio::spawn(async move {aa_proc(i).await;}); +@@ -31,13 +31,14 @@ async fn main() { + for handle in handles { + let _ = tokio::join!(handle); + } +- println!("main stop"); ++ log::info!("main stop"); + } + + async fn aa_proc(i: i64) { +- println!("attestation_proc {} start", i); ++ log::info!("attestation_proc thread {} start", i); + + // get challenge ++ log::info!("thread {} case1 get challenge", i); + let client = reqwest::Client::new(); + let challenge_endpoint = "http://127.0.0.1:8081/challenge"; + let res = client +@@ -52,11 +53,11 @@ async fn aa_proc(i: i64) { + let challenge = match res.status() { + reqwest::StatusCode::OK => { + let respone = res.text().await.unwrap(); +- println!("get challenge success, AA Response: {:?}", respone); ++ log::info!("thread {} case1 get challenge success response: {:?}", i, respone); + respone + } + status => { +- println!("get challenge Failed, AA Response: {:?}", status); ++ log::error!("thread {} case1 get challenge failed response: {:?}", i, status); + return; + } + }; +@@ -66,7 +67,7 @@ async fn aa_proc(i: i64) { + "challenge": challenge, + "uuid": String::from("f68fd704-6eb1-4d14-b218-722850eb3ef0"), + }); +- ++ log::info!("thread {} case2 get evidence, request body: {}", i, request_body); + let attest_endpoint = "http://127.0.0.1:8081/evidence"; + let res = client + .get(attest_endpoint) +@@ -79,11 +80,12 @@ async fn aa_proc(i: i64) { + let evidence = match res.status() { + reqwest::StatusCode::OK => { + let respone = res.text().await.unwrap(); +- println!("get evidence success, AA Response: {:?}", respone); ++ log::info!("thread {} case2 get evidence success", i); ++ log::debug!("thread {} response: {:?}", i, respone); + respone + } + status => { +- println!("get evidence Failed, AA Response: {:?}", status); ++ log::error!("thread {} case2 get evidence failed response: {:?}", i, status); + return; + } + }; +@@ -94,7 +96,7 @@ async fn aa_proc(i: i64) { + "challenge": "", + "evidence": evidence, + }); +- ++ log::info!("thread {} case3 verify evidence with no challenge", i); + let res = client + .post(attest_endpoint) + .header("Content-Type", "application/json") +@@ -106,10 +108,10 @@ async fn aa_proc(i: i64) { + match res.status() { + reqwest::StatusCode::OK => { + let respone = res.text().await.unwrap(); +- println!("verify evidence with no challenge success, AA Response: {:?}", respone); ++ log::info!("thread {} case3 verify evidence with no challenge success response: {:?}", i, respone); + } + status => { +- println!("verify evidence with no challenge Failed, AA Response: {:?}", status); ++ log::error!("thread {} case3 verify evidence with no challenge failed response: {:?}", i, status); + } + } + } +@@ -118,7 +120,7 @@ async fn aa_proc(i: i64) { + "challenge": challenge, + "evidence": evidence, + }); +- ++ log::info!("thread {} case4 verify evidence with challenge", i); + let res = client + .post(attest_endpoint) + .header("Content-Type", "application/json") +@@ -130,10 +132,10 @@ async fn aa_proc(i: i64) { + match res.status() { + reqwest::StatusCode::OK => { + let respone = res.text().await.unwrap(); +- println!("verify evidence success, AA Response: {:?}", respone); ++ log::info!("thread {} case4 verify evidence success response: {:?}", i, respone); + } + status => { +- println!("verify evidence Failed, AA Response: {:?}", status); ++ log::error!("thread {} case4 verify evidence failed response: {:?}", i, status); + } + } + +@@ -145,6 +147,7 @@ async fn aa_proc(i: i64) { + "challenge": challenge, + "uuid": String::from("f68fd704-6eb1-4d14-b218-722850eb3ef0"), + }); ++ log::info!("thread {} case5 get token, request body: {}", i, request_body); + + let res = client + .get(token_endpoint) +@@ -157,11 +160,12 @@ async fn aa_proc(i: i64) { + let token = match res.status() { + reqwest::StatusCode::OK => { + let respone = res.text().await.unwrap(); +- println!("get token success, AA Response: {:?}", respone); ++ log::info!("thread {} case5 get token success", i); ++ log::debug!("thread {} response: {:?}", i, respone); + respone + } + status => { +- println!("get token Failed, AA Response: {:?}", status); ++ log::error!("thread {} case5 get token failed response: {:?}", i, status); + return; + } + }; +@@ -171,6 +175,7 @@ async fn aa_proc(i: i64) { + "token": token, + }); + ++ log::info!("thread {} case6 verify token", i); + let res = client + .post(token_endpoint) + .header("Content-Type", "application/json") +@@ -182,14 +187,14 @@ async fn aa_proc(i: i64) { + match res.status() { + reqwest::StatusCode::OK => { + let respone = res.text().await.unwrap(); +- println!("verify token success, AA Response: {:?}", respone); ++ log::info!("thread {} case6 verify token success response: {:?}", i, respone); + } + status => { +- println!("verify token Failed, AA Response: {:?}", status); ++ log::error!("thread {} case6 verify token failed response: {:?}", i, status); + } + } + } + + +- println!("attestation_proc {} end", i); ++ log::info!("attestation_proc thread {} end", i); + } +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/agent/src/lib.rs b/service/attestation/attestation-agent/agent/src/lib.rs +index 4ff9b585..c4d913b6 100644 +--- a/service/attestation/attestation-agent/agent/src/lib.rs ++++ b/service/attestation/attestation-agent/agent/src/lib.rs +@@ -266,11 +266,11 @@ impl AttestationAgent { + let challenge = match res.status() { + reqwest::StatusCode::OK => { + let respone = res.json().await.unwrap(); +- log::info!("get challenge success, AS Response: {:?}", respone); ++ log::debug!("get challenge success, AS Response: {:?}", respone); + respone + } + status => { +- log::info!("get challenge Failed, AS Response: {:?}", status); ++ log::error!("get challenge Failed, AS Response: {:?}", status); + bail!("get challenge Failed") + } + }; +diff --git a/service/attestation/attestation-agent/agent/src/main.rs b/service/attestation/attestation-agent/agent/src/main.rs +index 76e63dc4..62a4b4d2 100644 +--- a/service/attestation/attestation-agent/agent/src/main.rs ++++ b/service/attestation/attestation-agent/agent/src/main.rs +@@ -43,7 +43,7 @@ struct Cli { + + #[actix_web::main] + async fn main() -> Result<()> { +- env_logger::init_from_env(env_logger::Env::new().default_filter_or("debug")); ++ env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + let cli = Cli::parse(); + let server = AttestationAgent::new(Some(cli.config)).unwrap(); +diff --git a/service/attestation/attestation-service/verifier/src/virtcca/mod.rs b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs +index a99f9358..3de7c9f4 100644 +--- a/service/attestation/attestation-service/verifier/src/virtcca/mod.rs ++++ b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs +@@ -264,7 +264,7 @@ impl Evidence { + + let ret = evidence.cvm_envelop.init_decoder(None); + match ret { +- Ok(_) => log::info!("decode COSE success"), ++ Ok(_) => log::debug!("decode COSE success"), + Err(e) => { + log::error!("decode COSE failed, {:?}", e); + bail!("decode COSE failed"); +-- +2.46.0 + diff --git a/0084-fix-concurrent-request-error-to-aa-or-as.patch b/0084-fix-concurrent-request-error-to-aa-or-as.patch new file mode 100644 index 0000000000000000000000000000000000000000..9334e24caf61568241e470ce5d864173f4ed7ab8 --- /dev/null +++ b/0084-fix-concurrent-request-error-to-aa-or-as.patch @@ -0,0 +1,269 @@ +From bc98b41d9cf8fb247d2c9502b775f03935a9f0dc Mon Sep 17 00:00:00 2001 +From: houmingyong +Date: Tue, 3 Sep 2024 10:57:51 +0800 +Subject: [PATCH] fix concurrent request error to aa or as + +Signed-off-by: houmingyong +--- + .../agent/src/bin/aa-test/main.rs | 34 ++++--------------- + .../attestation-agent/agent/src/lib.rs | 13 ++----- + .../attestation-service/service/src/main.rs | 3 -- + .../service/src/restapi/mod.rs | 30 ++-------------- + .../service/src/session.rs | 3 -- + .../verifier/src/itrustee/mod.rs | 4 +-- + 6 files changed, 14 insertions(+), 73 deletions(-) + +diff --git a/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs b/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs +index 89a301bf..48e3e68e 100644 +--- a/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs ++++ b/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs +@@ -69,6 +69,7 @@ async fn aa_proc(i: i64) { + }); + log::info!("thread {} case2 get evidence, request body: {}", i, request_body); + let attest_endpoint = "http://127.0.0.1:8081/evidence"; ++ let client = reqwest::Client::new(); + let res = client + .get(attest_endpoint) + .header("Content-Type", "application/json") +@@ -89,38 +90,14 @@ async fn aa_proc(i: i64) { + return; + } + }; +- // verify evidence with no challenge +- #[cfg(not(feature = "no_as"))] +- { +- let request_body = json!({ +- "challenge": "", +- "evidence": evidence, +- }); +- log::info!("thread {} case3 verify evidence with no challenge", i); +- let res = client +- .post(attest_endpoint) +- .header("Content-Type", "application/json") +- .json(&request_body) +- .send() +- .await +- .unwrap(); +- +- match res.status() { +- reqwest::StatusCode::OK => { +- let respone = res.text().await.unwrap(); +- log::info!("thread {} case3 verify evidence with no challenge success response: {:?}", i, respone); +- } +- status => { +- log::error!("thread {} case3 verify evidence with no challenge failed response: {:?}", i, status); +- } +- } +- } ++ // case3 verify evidence with no challenge + // verify evidence with challenge + let request_body = json!({ + "challenge": challenge, + "evidence": evidence, + }); + log::info!("thread {} case4 verify evidence with challenge", i); ++ let client = reqwest::Client::new(); + let res = client + .post(attest_endpoint) + .header("Content-Type", "application/json") +@@ -148,7 +125,7 @@ async fn aa_proc(i: i64) { + "uuid": String::from("f68fd704-6eb1-4d14-b218-722850eb3ef0"), + }); + log::info!("thread {} case5 get token, request body: {}", i, request_body); +- ++ let client = reqwest::Client::new(); + let res = client + .get(token_endpoint) + .header("Content-Type", "application/json") +@@ -165,7 +142,7 @@ async fn aa_proc(i: i64) { + respone + } + status => { +- log::error!("thread {} case5 get token failed response: {:?}", i, status); ++ log::error!("thread {} case5 get token failed status: {:?} response: {:?}", i, status, res.text().await.unwrap()); + return; + } + }; +@@ -176,6 +153,7 @@ async fn aa_proc(i: i64) { + }); + + log::info!("thread {} case6 verify token", i); ++ let client = reqwest::Client::new(); + let res = client + .post(token_endpoint) + .header("Content-Type", "application/json") +diff --git a/service/attestation/attestation-agent/agent/src/lib.rs b/service/attestation/attestation-agent/agent/src/lib.rs +index c4d913b6..393914d6 100644 +--- a/service/attestation/attestation-agent/agent/src/lib.rs ++++ b/service/attestation/attestation-agent/agent/src/lib.rs +@@ -171,7 +171,6 @@ impl TryFrom<&Path> for AAConfig { + #[derive(Debug)] + pub struct AttestationAgent { + config: AAConfig, +- client: reqwest::Client, + } + + #[allow(dead_code)] +@@ -187,14 +186,8 @@ impl AttestationAgent { + AAConfig::default() + } + }; +- let client = reqwest::ClientBuilder::new() +- .cookie_store(true) +- .user_agent("attestation-agent-client") +- .build() +- .map_err(|e| result::Error::AttestationAgentError(format!("build http client {e}")))?; + Ok(AttestationAgent { + config, +- client, + }) + } + +@@ -211,7 +204,7 @@ impl AttestationAgent { + }); + + let attest_endpoint = format!("{}/attestation", self.config.svr_url); +- let res = self.client ++ let res = reqwest::Client::new() + .post(attest_endpoint) + .header("Content-Type", "application/json") + .json(&request_body) +@@ -256,7 +249,7 @@ impl AttestationAgent { + } + async fn get_challenge_from_as(&self) -> Result { + let challenge_endpoint = format!("{}/challenge", self.config.svr_url); +- let res = self.client ++ let res = reqwest::Client::new() + .get(challenge_endpoint) + .header("Content-Type", "application/json") + .header("content-length", 0) +@@ -265,7 +258,7 @@ impl AttestationAgent { + .await?; + let challenge = match res.status() { + reqwest::StatusCode::OK => { +- let respone = res.json().await.unwrap(); ++ let respone = res.text().await?; + log::debug!("get challenge success, AS Response: {:?}", respone); + respone + } +diff --git a/service/attestation/attestation-service/service/src/main.rs b/service/attestation/attestation-service/service/src/main.rs +index 1ccb1521..3ced10b9 100644 +--- a/service/attestation/attestation-service/service/src/main.rs ++++ b/service/attestation/attestation-service/service/src/main.rs +@@ -15,7 +15,6 @@ use attestation_service::AttestationService; + mod restapi; + use restapi::{get_challenge, attestation, reference, get_policy, set_policy}; + mod session; +-use session::SessionMap; + + use anyhow::Result; + use env_logger; +@@ -55,13 +54,11 @@ async fn main() -> Result<()> { + + let cli = Cli::parse(); + let server:AttestationService = AttestationService::new(Some(cli.config)).unwrap(); +- let session_map = web::Data::new(SessionMap::new()); + + let service = web::Data::new(Arc::new(RwLock::new(server))); + HttpServer::new(move || { + App::new() + .app_data(web::Data::clone(&service)) +- .app_data(web::Data::clone(&session_map)) + .service(get_challenge) + .service(attestation) + .service(reference) +diff --git a/service/attestation/attestation-service/service/src/restapi/mod.rs b/service/attestation/attestation-service/service/src/restapi/mod.rs +index ab2ccbfd..291b8657 100644 +--- a/service/attestation/attestation-service/service/src/restapi/mod.rs ++++ b/service/attestation/attestation-service/service/src/restapi/mod.rs +@@ -10,8 +10,7 @@ + * See the Mulan PSL v2 for more details. + */ + use attestation_service::AttestationService; +-use attestation_service::result::{Result, Error}; +-use crate::session::{Session, SessionMap}; ++use attestation_service::result::{Result}; + + use actix_web::{ post, get, web, HttpResponse, HttpRequest}; + use serde::{Deserialize, Serialize}; +@@ -26,20 +25,12 @@ pub struct ChallengeRequest {} + + #[get("/challenge")] + pub async fn get_challenge( +- map: web::Data, + service: web::Data>>, + ) -> Result { + log::debug!("challenge request"); + + let challenge = service.read().await.generate_challenge().await; +- let timeout = service.read().await.config.token_cfg.valid_duration; +- let session = Session::new(challenge, timeout.try_into().unwrap()); +- let response = HttpResponse::Ok() +- .cookie(session.cookie()) +- .json(session.challenge.clone()); +- map.insert(session); +- +- Ok(response) ++ Ok(HttpResponse::Ok().body(challenge)) + } + + #[derive(Deserialize, Serialize, Debug)] +@@ -52,26 +43,11 @@ pub struct AttestationRequest { + #[post("/attestation")] + pub async fn attestation( + request: web::Json, +- http_req: HttpRequest, +- map: web::Data, + service: web::Data>>, + ) -> Result { + log::debug!("attestation request is coming"); + let request = request.0; +- let mut challenge = request.challenge; +- if challenge == "" { +- let cookie = http_req.cookie("oeas-session-id").ok_or(Error::CookieMissing)?; +- let session = map +- .session_map +- .get_async(cookie.value()) +- .await +- .ok_or(Error::CookieNotFound)?; +- if session.is_expired() { +- return Err(Error::SessionExpired); +- } +- log::debug!("session challenge:{}", session.challenge); +- challenge = session.challenge.clone(); +- } ++ let challenge = request.challenge; + + let nonce = base64_url::decode(&challenge).expect("base64 decode nonce"); + let evidence = base64_url::decode(&request.evidence).expect("base64 decode evidence"); +diff --git a/service/attestation/attestation-service/service/src/session.rs b/service/attestation/attestation-service/service/src/session.rs +index 5f191a77..2aee35a3 100644 +--- a/service/attestation/attestation-service/service/src/session.rs ++++ b/service/attestation/attestation-service/service/src/session.rs +@@ -52,7 +52,4 @@ impl SessionMap { + pub fn insert(&self, session: Session) { + let _ = self.session_map.insert(session.id.clone(), session); + } +- pub fn delete(&self, session: Session) { +- let _ = self.session_map.remove(&session.id); +- } + } +\ No newline at end of file +diff --git a/service/attestation/attestation-service/verifier/src/itrustee/mod.rs b/service/attestation/attestation-service/verifier/src/itrustee/mod.rs +index 67c857ac..8ce4d24b 100644 +--- a/service/attestation/attestation-service/verifier/src/itrustee/mod.rs ++++ b/service/attestation/attestation-service/verifier/src/itrustee/mod.rs +@@ -42,8 +42,8 @@ fn evalute_wrapper(user_data: &[u8], evidence: &[u8]) -> Result { + size: in_data.len() as ::std::os::raw::c_uint, + buf: in_data.as_mut_ptr() as *mut ::std::os::raw::c_uchar, + }; +- log::info!("input nonce:{:?}", nonce); +- let policy: std::os::raw::c_int = 1; ++ ++ let policy: std::os::raw::c_int = 1; // 1: verify ta_imag; 2: verfiy ta_mem; 3: verify ta_img and ta_mem hash; + if !Path::new(ITRUSTEE_REF_VALUE_FILE).exists() { + log::error!("itrustee verify report {} not exists", ITRUSTEE_REF_VALUE_FILE); + bail!("itrustee verify report {} not exists", ITRUSTEE_REF_VALUE_FILE); +-- +2.46.0 + diff --git a/0085-fix-multi-thread-request-as-generate-challenge-and-v.patch b/0085-fix-multi-thread-request-as-generate-challenge-and-v.patch new file mode 100644 index 0000000000000000000000000000000000000000..e661b9e1ec11e4485fa1f2ab5e7cff017a05c3d2 --- /dev/null +++ b/0085-fix-multi-thread-request-as-generate-challenge-and-v.patch @@ -0,0 +1,438 @@ +From f495edff1e1077f209596bca7f83a614ea5cd139 Mon Sep 17 00:00:00 2001 +From: houmingyong +Date: Fri, 6 Sep 2024 10:38:09 +0800 +Subject: [PATCH] fix multi-thread request as generate challenge and verify + report error + +Signed-off-by: houmingyong +--- + .../attestation/attestation-agent/Cargo.toml | 1 + + .../attestation-agent/agent/Cargo.toml | 2 + + .../attestation-agent/agent/src/lib.rs | 92 ++++++++++++------- + .../attestation-agent/agent/src/session.rs | 55 +++++++++++ + .../attestation-service/service/src/main.rs | 14 +++ + .../service/src/restapi/mod.rs | 32 ++++++- + .../service/src/result/mod.rs | 7 +- + .../attestation/attestation-types/src/lib.rs | 2 + + 8 files changed, 169 insertions(+), 36 deletions(-) + create mode 100644 service/attestation/attestation-agent/agent/src/session.rs + +diff --git a/service/attestation/attestation-agent/Cargo.toml b/service/attestation/attestation-agent/Cargo.toml +index bdc7b120..f6f31b18 100644 +--- a/service/attestation/attestation-agent/Cargo.toml ++++ b/service/attestation/attestation-agent/Cargo.toml +@@ -24,6 +24,7 @@ jsonwebtoken = "9.3.0" + thiserror = "1.0" + actix-web = "4.5" + clap = { version = "4.5.7", features = ["derive"] } ++scc = "2.1" + + verifier = {path = "../attestation-service/verifier", default-features = false} + attestation-types = {path = "../attestation-types"} +diff --git a/service/attestation/attestation-agent/agent/Cargo.toml b/service/attestation/attestation-agent/agent/Cargo.toml +index e29f89be..d2450c87 100644 +--- a/service/attestation/attestation-agent/agent/Cargo.toml ++++ b/service/attestation/attestation-agent/agent/Cargo.toml +@@ -41,6 +41,8 @@ base64-url.workspace = true + thiserror.workspace = true + actix-web.workspace = true + clap.workspace = true ++scc.workspace = true ++attestation-types.workspace = true + + attester = { path = "../attester" } + token_verifier = { path = "../token" } +diff --git a/service/attestation/attestation-agent/agent/src/lib.rs b/service/attestation/attestation-agent/agent/src/lib.rs +index 393914d6..f6e03c6c 100644 +--- a/service/attestation/attestation-agent/agent/src/lib.rs ++++ b/service/attestation/attestation-agent/agent/src/lib.rs +@@ -34,9 +34,16 @@ pub type TeeClaim = serde_json::Value; + use verifier::{Verifier, VerifierAPIs}; + + #[cfg(not(feature = "no_as"))] +-use {serde_json::json, reqwest, base64_url}; ++use { ++ serde_json::json, ++ reqwest::header::{HeaderMap, HeaderValue}, ++ base64_url ++}; + + pub use attester::EvidenceRequest; ++mod session; ++use session::{SessionMap, Session}; ++use attestation_types::SESSION_TIMEOUT_MIN; + + pub type AsTokenClaim = TokenRawData; + +@@ -171,6 +178,7 @@ impl TryFrom<&Path> for AAConfig { + #[derive(Debug)] + pub struct AttestationAgent { + config: AAConfig, ++ as_client_sessions: SessionMap, + } + + #[allow(dead_code)] +@@ -186,8 +194,20 @@ impl AttestationAgent { + AAConfig::default() + } + }; ++ let as_client_sessions = SessionMap::new(); ++ let sessions = as_client_sessions.clone(); ++ tokio::spawn(async move { ++ loop { ++ tokio::time::sleep(std::time::Duration::from_secs(60)).await; ++ sessions ++ .session_map ++ .retain_async(|_, v| !v.is_expired()) ++ .await; ++ } ++ }); + Ok(AttestationAgent { + config, ++ as_client_sessions, + }) + } + +@@ -197,16 +217,33 @@ impl AttestationAgent { + evidence: &[u8], + policy_id: Option> + ) -> Result { ++ let challenge = base64_url::encode(challenge); + let request_body = json!({ +- "challenge": base64_url::encode(challenge), ++ "challenge": challenge, + "evidence": base64_url::encode(evidence), + "policy_id": policy_id, + }); ++ let mut map = HeaderMap::new(); ++ map.insert("Content-Type", HeaderValue::from_static("application/json")); ++ let mut client = reqwest::Client::new(); ++ if !self.as_client_sessions.session_map.is_empty() { ++ let session = self.as_client_sessions ++ .session_map ++ .get_async(&challenge) ++ .await; ++ match session { ++ Some(entry) => { ++ map.insert("as-challenge", HeaderValue::from_static("as")); ++ client = entry.get().as_client.clone() ++ }, ++ None => log::info!("challenge is not as generate"), ++ } ++ } + + let attest_endpoint = format!("{}/attestation", self.config.svr_url); +- let res = reqwest::Client::new() ++ let res = client + .post(attest_endpoint) +- .header("Content-Type", "application/json") ++ .headers(map) + .json(&request_body) + .send() + .await?; +@@ -249,16 +286,18 @@ impl AttestationAgent { + } + async fn get_challenge_from_as(&self) -> Result { + let challenge_endpoint = format!("{}/challenge", self.config.svr_url); +- let res = reqwest::Client::new() ++ let client = reqwest::Client::builder() ++ .cookie_store(true) ++ .build()?; ++ let res = client + .get(challenge_endpoint) + .header("Content-Type", "application/json") + .header("content-length", 0) +- //.json(&request_body) + .send() + .await?; + let challenge = match res.status() { + reqwest::StatusCode::OK => { +- let respone = res.text().await?; ++ let respone: String = res.json().await.unwrap(); + log::debug!("get challenge success, AS Response: {:?}", respone); + respone + } +@@ -267,6 +306,8 @@ impl AttestationAgent { + bail!("get challenge Failed") + } + }; ++ let session = Session::new(challenge.clone(), client, SESSION_TIMEOUT_MIN)?; ++ self.as_client_sessions.insert(session); + Ok(challenge) + } + } +@@ -274,12 +315,19 @@ impl AttestationAgent { + + // attestation agent c interface + use safer_ffi::prelude::*; +-use futures::executor::block_on; + use tokio::runtime::Runtime; + ++#[ffi_export] ++pub fn init_env_logger(c_level: Option<&repr_c::String>) { ++ let level = match c_level { ++ Some(level) => &level, ++ None => "info", ++ }; ++ env_logger::init_from_env(env_logger::Env::new().default_filter_or(level)); ++} ++ + #[ffi_export] + pub fn get_report(c_challenge: Option<&repr_c::Vec>, c_ima: &repr_c::TaggedOption) -> repr_c::Vec { +- env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + log::debug!("input challenge: {:?}, ima: {:?}", c_challenge, c_ima); + let ima = match c_ima { + repr_c::TaggedOption::None => false, +@@ -295,11 +343,12 @@ pub fn get_report(c_challenge: Option<&repr_c::Vec>, c_ima: &repr_c::TaggedO + challenge: challenge, + ima: Some(ima), + }; +- ++ let rt = Runtime::new().unwrap(); + let fut = async { + AttestationAgent::new(Some(DEFAULT_AACONFIG_FILE.to_string())).unwrap().get_evidence(input).await + }; +- let report: Vec = match block_on(fut) { ++ let ret = rt.block_on(fut); ++ let report: Vec = match ret { + Ok(report) => report, + Err(e) => { + log::error!("get report failed {:?}", e); +@@ -357,24 +406,3 @@ pub fn generate_headers() -> ::std::io::Result<()> { + .to_file("./c_header/rust_attestation_agent.h")? + .generate() + } +- +- +-#[cfg(test)] +-mod tests { +- use crate::*; +- +- #[test] +- fn aa_new_no_conf_path() { +- let aa = AttestationAgent::new(None).unwrap(); +- assert_eq!(aa.config.svr_url, "http://127.0.0.1:8080"); +- assert_eq!(aa.config.token_cfg.cert, "/etc/attestation/attestation-agent/as_cert.pem"); +- assert_eq!(aa.config.token_cfg.iss, "openEulerAS"); +- } +- +- #[test] +- fn aa_new_with_example_conf() { +- let aa = AttestationAgent::new(Some("attestation-agent.conf".to_string())).unwrap(); +- assert_eq!(aa.config.token_cfg.cert, "/home/cert/as_cert.pem"); +- assert_eq!(aa.config.token_cfg.iss, "oeas"); +- } +-} +diff --git a/service/attestation/attestation-agent/agent/src/session.rs b/service/attestation/attestation-agent/agent/src/session.rs +new file mode 100644 +index 00000000..5e1c1fc5 +--- /dev/null ++++ b/service/attestation/attestation-agent/agent/src/session.rs +@@ -0,0 +1,55 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use actix_web::cookie::{time::{Duration, OffsetDateTime}}; ++use scc::HashMap; ++use anyhow::Result; ++ ++#[derive(Debug, Clone)] ++pub struct Session { ++ pub challenge: String, ++ pub as_client: reqwest::Client, ++ timeout: OffsetDateTime, ++ // pub token: Option, ++} ++ ++impl Session { ++ pub fn new(challenge: String, as_client: reqwest::Client, timeout_m: i64) -> Result { ++ ++ let timeout = OffsetDateTime::now_utc() + Duration::minutes(timeout_m); ++ // let token = None; ++ Ok(Session { ++ challenge, ++ as_client, ++ timeout, ++ // token, ++ }) ++ } ++ pub fn is_expired(&self) -> bool { ++ return self.timeout < OffsetDateTime::now_utc(); ++ } ++} ++ ++#[derive(Debug, Clone)] ++pub struct SessionMap { ++ pub session_map: HashMap, ++} ++ ++impl SessionMap { ++ pub fn new() -> Self { ++ SessionMap { ++ session_map: HashMap::new(), ++ } ++ } ++ pub fn insert(&self, session: Session) { ++ let _ = self.session_map.insert(session.challenge.clone(), session); ++ } ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-service/service/src/main.rs b/service/attestation/attestation-service/service/src/main.rs +index 3ced10b9..88941b84 100644 +--- a/service/attestation/attestation-service/service/src/main.rs ++++ b/service/attestation/attestation-service/service/src/main.rs +@@ -15,6 +15,7 @@ use attestation_service::AttestationService; + mod restapi; + use restapi::{get_challenge, attestation, reference, get_policy, set_policy}; + mod session; ++use session::SessionMap; + + use anyhow::Result; + use env_logger; +@@ -54,11 +55,24 @@ async fn main() -> Result<()> { + + let cli = Cli::parse(); + let server:AttestationService = AttestationService::new(Some(cli.config)).unwrap(); ++ let session_map = web::Data::new(SessionMap::new()); ++ ++ let sessions_clone = session_map.clone(); ++ tokio::spawn(async move { ++ loop { ++ tokio::time::sleep(std::time::Duration::from_secs(60)).await; ++ sessions_clone ++ .session_map ++ .retain_async(|_, v| !v.is_expired()) ++ .await; ++ } ++ }); + + let service = web::Data::new(Arc::new(RwLock::new(server))); + HttpServer::new(move || { + App::new() + .app_data(web::Data::clone(&service)) ++ .app_data(web::Data::clone(&session_map)) + .service(get_challenge) + .service(attestation) + .service(reference) +diff --git a/service/attestation/attestation-service/service/src/restapi/mod.rs b/service/attestation/attestation-service/service/src/restapi/mod.rs +index 291b8657..a7e6012b 100644 +--- a/service/attestation/attestation-service/service/src/restapi/mod.rs ++++ b/service/attestation/attestation-service/service/src/restapi/mod.rs +@@ -10,7 +10,7 @@ + * See the Mulan PSL v2 for more details. + */ + use attestation_service::AttestationService; +-use attestation_service::result::{Result}; ++use attestation_service::result::{Result, Error}; + + use actix_web::{ post, get, web, HttpResponse, HttpRequest}; + use serde::{Deserialize, Serialize}; +@@ -18,6 +18,8 @@ use std::sync::Arc; + use tokio::sync::RwLock; + use log; + use base64_url; ++use attestation_types::SESSION_TIMEOUT_MIN; ++use crate::session::{Session, SessionMap}; + + const DEFAULT_POLICY_DIR: &str = "/etc/attestation/attestation-service/policy"; + #[derive(Deserialize, Serialize, Debug)] +@@ -25,12 +27,19 @@ pub struct ChallengeRequest {} + + #[get("/challenge")] + pub async fn get_challenge( ++ map: web::Data, + service: web::Data>>, + ) -> Result { + log::debug!("challenge request"); + + let challenge = service.read().await.generate_challenge().await; +- Ok(HttpResponse::Ok().body(challenge)) ++ let session = Session::new(challenge, SESSION_TIMEOUT_MIN); ++ let response = HttpResponse::Ok() ++ .cookie(session.cookie()) ++ .json(session.challenge.clone()); ++ map.insert(session); ++ ++ Ok(response) + } + + #[derive(Deserialize, Serialize, Debug)] +@@ -42,6 +51,8 @@ pub struct AttestationRequest { + + #[post("/attestation")] + pub async fn attestation( ++ http_req: HttpRequest, ++ map: web::Data, + request: web::Json, + service: web::Data>>, + ) -> Result { +@@ -49,6 +60,23 @@ pub async fn attestation( + let request = request.0; + let challenge = request.challenge; + ++ if http_req.headers().contains_key("as-challenge") { ++ log::info!("sessions map len:{}", map.session_map.len()); ++ let cookie = http_req.cookie("oeas-session-id").ok_or(Error::CookieMissing)?; ++ let session = map ++ .session_map ++ .get_async(cookie.value()) ++ .await ++ .ok_or(Error::SessionNotFound)?; ++ if session.is_expired() { ++ return Err(Error::SessionExpired); ++ } ++ if challenge != session.challenge { ++ log::error!("request challenge:{} does not match session challenge:{}", challenge, session.challenge); ++ return Err(Error::ChallengeInvalid); ++ } ++ } ++ + let nonce = base64_url::decode(&challenge).expect("base64 decode nonce"); + let evidence = base64_url::decode(&request.evidence).expect("base64 decode evidence"); + let ids = request.policy_id; +diff --git a/service/attestation/attestation-service/service/src/result/mod.rs b/service/attestation/attestation-service/service/src/result/mod.rs +index 667e80f5..fcb1c123 100644 +--- a/service/attestation/attestation-service/service/src/result/mod.rs ++++ b/service/attestation/attestation-service/service/src/result/mod.rs +@@ -38,12 +38,15 @@ pub enum Error { + #[error("Request cookie is missing")] + CookieMissing, + +- #[error("Request cookie is not found")] +- CookieNotFound, ++ #[error("Request cookie session is not found")] ++ SessionNotFound, + + #[error("The session of request cookie is expired")] + SessionExpired, + ++ #[error("Request challenge is invalid")] ++ ChallengeInvalid, ++ + #[error(transparent)] + Other(#[from] anyhow::Error), + } +diff --git a/service/attestation/attestation-types/src/lib.rs b/service/attestation/attestation-types/src/lib.rs +index fcf1d3ee..67dcf9f8 100644 +--- a/service/attestation/attestation-types/src/lib.rs ++++ b/service/attestation/attestation-types/src/lib.rs +@@ -12,6 +12,8 @@ + use serde::{Serialize, Deserialize}; + use serde_json::Value; + ++pub const SESSION_TIMEOUT_MIN: i64 = 1; ++ + #[derive(Debug, Serialize, Deserialize)] + pub struct VirtccaEvidence { + pub evidence: Vec, +-- +2.46.0 + diff --git a/0086-add-error-type-for-api.patch b/0086-add-error-type-for-api.patch new file mode 100644 index 0000000000000000000000000000000000000000..a42955906e4676426df828e63ed5d0e57dbfc53c --- /dev/null +++ b/0086-add-error-type-for-api.patch @@ -0,0 +1,680 @@ +From fae0b444629f5cd20a544d5513870d9435f72ef0 Mon Sep 17 00:00:00 2001 +From: xuraoqing +Date: Mon, 9 Sep 2024 19:23:48 +0800 +Subject: [PATCH] add error type for api + +Signed-off-by: houmingyong +--- + .../agent/src/bin/aa-test/main.rs | 1 + + .../attestation-agent/agent/src/lib.rs | 24 +++++++-- + .../agent/src/restapi/mod.rs | 31 +++++++----- + .../attestation-agent/agent/src/result/mod.rs | 8 +++ + .../attestation-agent/token/Cargo.toml | 3 +- + .../attestation-agent/token/src/lib.rs | 49 +++++++++---------- + .../policy/src/policy_engine.rs | 3 ++ + .../attestation-service/reference/Cargo.toml | 3 +- + .../reference/src/reference/mod.rs | 39 ++++----------- + .../reference/src/store/mod.rs | 9 ++++ + .../attestation-service/service/src/lib.rs | 9 ++-- + .../service/src/restapi/mod.rs | 25 +++------- + .../service/src/result/mod.rs | 20 ++++++++ + .../attestation-service/token/Cargo.toml | 3 +- + .../attestation-service/token/src/lib.rs | 33 +++++++++---- + 15 files changed, 153 insertions(+), 107 deletions(-) + +diff --git a/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs b/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs +index 48e3e68e..4867a234 100644 +--- a/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs ++++ b/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs +@@ -169,6 +169,7 @@ async fn aa_proc(i: i64) { + } + status => { + log::error!("thread {} case6 verify token failed response: {:?}", i, status); ++ log::error!("thread case6 verify token failed response:{}", res.text().await.unwrap()); + } + } + } +diff --git a/service/attestation/attestation-agent/agent/src/lib.rs b/service/attestation/attestation-agent/agent/src/lib.rs +index f6e03c6c..f1c4510b 100644 +--- a/service/attestation/attestation-agent/agent/src/lib.rs ++++ b/service/attestation/attestation-agent/agent/src/lib.rs +@@ -22,6 +22,7 @@ use async_trait::async_trait; + use std::fs::File; + use std::path::Path; + use rand::RngCore; ++use thiserror; + + use attester::{Attester, AttesterAPIs}; + use token_verifier::{TokenVerifyConfig, TokenVerifier, TokenRawData}; +@@ -30,6 +31,22 @@ pub mod result; + use result::Error; + pub type TeeClaim = serde_json::Value; + ++#[derive(Debug, thiserror::Error)] ++pub enum AgentError { ++ #[error("challenge error: {0}")] ++ ChallengeError(String), ++ #[error("get evidence error: {0}")] ++ DecodeError(String), ++ #[error("get evidence error: {0}")] ++ GetEvidenceError(String), ++ #[error("verify evidence error: {0}")] ++ VerifyEvidenceError(String), ++ #[error("get token error: {0}")] ++ GetTokenError(String), ++ #[error("verify token error: {0}")] ++ VerifyTokenError(String), ++} ++ + #[cfg(feature = "no_as")] + use verifier::{Verifier, VerifierAPIs}; + +@@ -136,11 +153,8 @@ impl AttestationAgentAPIs for AttestationAgent { + + async fn verify_token(&self, token: String) -> Result { + let verifier = TokenVerifier::new(self.config.token_cfg.clone())?; +- let result = verifier.verify(&token); +- match result { +- Ok(raw_token) => Ok(raw_token as AsTokenClaim), +- Err(e) => bail!("verify token failed {:?}", e), +- } ++ let result = verifier.verify(&token)?; ++ Ok(result) + } + } + +diff --git a/service/attestation/attestation-agent/agent/src/restapi/mod.rs b/service/attestation/attestation-agent/agent/src/restapi/mod.rs +index 490242aa..0570060b 100644 +--- a/service/attestation/attestation-agent/agent/src/restapi/mod.rs ++++ b/service/attestation/attestation-agent/agent/src/restapi/mod.rs +@@ -9,7 +9,7 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use attestation_agent::{AttestationAgent, AttestationAgentAPIs, TokenRequest}; ++use attestation_agent::{AttestationAgent, AttestationAgentAPIs, TokenRequest, AgentError}; + use attestation_agent::result::Result; + + use actix_web::{ post, get, web, HttpResponse}; +@@ -18,7 +18,6 @@ use serde::{Deserialize, Serialize}; + use std::sync::Arc; + use tokio::sync::RwLock; + use log; +-use base64_url; + + #[derive(Deserialize, Serialize, Debug)] + struct GetChallengeRequest {} +@@ -30,7 +29,8 @@ pub async fn get_challenge( + ) -> Result { + //let request = request.0; + log::debug!("get challenge request"); +- let challenge = agent.read().await.get_challenge().await?; ++ let challenge = agent.read().await.get_challenge().await ++ .map_err(|err| AgentError::ChallengeError(err.to_string()))?; + + Ok(HttpResponse::Ok().body(challenge)) + } +@@ -49,7 +49,8 @@ pub async fn get_evidence( + ) -> Result { + let request = request.0; + log::debug!("get evidence request: {:?}", request); +- let challenge = base64_url::decode(&request.challenge).expect("base64 decode challenge"); ++ let challenge = base64_url::decode(&request.challenge) ++ .map_err(|err|AgentError::DecodeError(err.to_string()))?; + let uuid = request.uuid; + let ima = request.ima; + let input = EvidenceRequest { +@@ -57,7 +58,8 @@ pub async fn get_evidence( + challenge: challenge, + ima: ima, + }; +- let evidence = agent.read().await.get_evidence(input).await?; ++ let evidence = agent.read().await.get_evidence(input).await ++ .map_err(|err|AgentError::GetEvidenceError(err.to_string()))?; + + + Ok(HttpResponse::Ok().body(evidence)) +@@ -76,11 +78,13 @@ pub async fn verify_evidence( + ) -> Result { + let request = request.0; + log::debug!("verify evidence request: {:?}", request); +- let challenge = base64_url::decode(&request.challenge).expect("base64 decode challenge"); ++ let challenge = base64_url::decode(&"request.challenge".to_string()) ++ .map_err(|err|AgentError::DecodeError(err.to_string()))?; + let evidence = request.evidence; + let policy_id = request.policy_id; + +- let claim = agent.read().await.verify_evidence(&challenge, evidence.as_bytes(), policy_id).await?; ++ let claim = agent.read().await.verify_evidence(&challenge, evidence.as_bytes(), policy_id).await ++ .map_err(|err|AgentError::VerifyEvidenceError(err.to_string()))?; + let string_claim = serde_json::to_string(&claim)?; + + Ok(HttpResponse::Ok().body(string_claim)) +@@ -101,7 +105,8 @@ pub async fn get_token( + ) -> Result { + let request = request.0; + log::debug!("get token request: {:?}", request); +- let challenge = base64_url::decode(&request.challenge).expect("base64 decode challenge"); ++ let challenge = base64_url::decode(&request.challenge) ++ .map_err(|err|AgentError::DecodeError(err.to_string()))?; + let uuid = request.uuid; + let ima = request.ima; + let policy_id = request.policy_id; +@@ -115,8 +120,8 @@ pub async fn get_token( + policy_id: policy_id, + }; + +- let token = agent.read().await.get_token(input).await?; +- ++ let token = agent.read().await.get_token(input).await ++ .map_err(|err|AgentError::GetTokenError(err.to_string()))?; + + Ok(HttpResponse::Ok().body(token)) + } +@@ -133,8 +138,10 @@ pub async fn verify_token( + let request = request.0; + log::debug!("verify token request: {:?}", request); + +- let claim = agent.read().await.verify_token(request.token).await?; +- let string_claim = serde_json::to_string(&claim)?; ++ let claim = agent.read().await.verify_token(request.token).await ++ .map_err(|err|AgentError::VerifyTokenError(err.to_string()))?; ++ let string_claim = serde_json::to_string(&claim) ++ .map_err(|err|AgentError::VerifyTokenError(err.to_string()))?; + + Ok(HttpResponse::Ok().body(string_claim)) + } +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/agent/src/result/mod.rs b/service/attestation/attestation-agent/agent/src/result/mod.rs +index f06f064b..a33be0cb 100644 +--- a/service/attestation/attestation-agent/agent/src/result/mod.rs ++++ b/service/attestation/attestation-agent/agent/src/result/mod.rs +@@ -11,6 +11,8 @@ + */ + use actix_web::{body::BoxBody, HttpResponse, ResponseError}; + ++use crate::AgentError; ++ + pub type Result = std::result::Result; + + /// libdevice error +@@ -30,6 +32,12 @@ pub enum Error { + source: actix_web::error::Error, + }, + ++ #[error("Agent error: {source:?}")] ++ Agent { ++ #[from] ++ source: AgentError, ++ }, ++ + #[error("Deserialize error: {source:?}")] + Deserialize { + #[from] +diff --git a/service/attestation/attestation-agent/token/Cargo.toml b/service/attestation/attestation-agent/token/Cargo.toml +index aa5cafcf..916f2a2e 100644 +--- a/service/attestation/attestation-agent/token/Cargo.toml ++++ b/service/attestation/attestation-agent/token/Cargo.toml +@@ -10,4 +10,5 @@ jsonwebtoken.workspace = true + serde.workspace = true + serde_json.workspace = true + anyhow.workspace = true +-attestation-types.workspace = true +\ No newline at end of file ++attestation-types.workspace = true ++thiserror.workspace = true +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/token/src/lib.rs b/service/attestation/attestation-agent/token/src/lib.rs +index 50a7a7a0..37aab9eb 100644 +--- a/service/attestation/attestation-agent/token/src/lib.rs ++++ b/service/attestation/attestation-agent/token/src/lib.rs +@@ -9,12 +9,23 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use anyhow::{Result, bail}; + use std::path::Path; + use serde::{Deserialize, Serialize}; + use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation }; + use attestation_types::Claims; + ++#[derive(thiserror::Error, Debug)] ++pub enum VerifyError { ++ #[error("parse fail:{0:?}")] ++ CommError(#[from] jsonwebtoken::errors::Error), ++ #[error("unknown algorithm:{0}")] ++ UnknownAlg(String), ++ #[error("certificate not exist:{0}")] ++ CertNotExist(String), ++ #[error("serialize fail:{0}")] ++ SerializeFail(#[from] serde_json::error::Error), ++} ++ + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct TokenVerifyConfig { + pub cert: String, // Attestation Service cert to verify jwt token signature +@@ -52,7 +63,7 @@ pub struct TokenRawData { + } + + impl TokenVerifier { +- pub fn new(config: TokenVerifyConfig) -> Result { ++ pub fn new(config: TokenVerifyConfig) -> Result { + Ok(TokenVerifier { config }) + } + fn support_rs(alg: &Algorithm) -> bool +@@ -72,43 +83,29 @@ impl TokenVerifier { + pub fn verify( + &self, + token: &String +- ) -> Result { +- let header = match decode_header(&token) { +- Ok(h) => h, +- Err(e) => bail!("decode jwt header error {:?}", e), +- }; ++ ) -> Result { ++ let header = decode_header(&token)?; + let alg: Algorithm = header.alg; + + if !Self::support_rs(&alg) && !Self::support_ps(&alg) { +- bail!("unknown algrithm {:?}", alg); ++ return Err(VerifyError::UnknownAlg(format!("unknown algrithm {:?}", alg))); + } + if !Path::new(&self.config.cert).exists() { +- bail!("token verfify failed, {:?} cert not exist", self.config.cert); ++ return Err(VerifyError::CertNotExist(format!("{:?} not exist", self.config.cert))); + } + let cert = std::fs::read(&self.config.cert).unwrap(); + + /* 使用配置的公钥 */ +- let key_value: DecodingKey = match DecodingKey::from_rsa_pem(&cert) +- { +- Ok(key) => key, +- Err(e) => bail!("get key from pem error {:?}", e), +- }; ++ let key_value: DecodingKey = DecodingKey::from_rsa_pem(&cert)?; + + let mut validation = Validation::new(alg); + validation.set_issuer(&[self.config.iss.clone()]); + validation.validate_exp = true; + +- let data = decode::(&token, &key_value, &validation); +- match data { +- Ok(d) => { +- let header = d.header.clone(); +- let claims = d.claims.clone(); +- Ok(TokenRawData { +- header: serde_json::to_string(&header).unwrap(), +- claim: serde_json::to_string(&claims).unwrap(), +- }) +- } +- Err(e) => bail!("verfiy jwt failed {:?}", e), +- } ++ let data = decode::(&token, &key_value, &validation)?; ++ Ok(TokenRawData { ++ header: serde_json::to_string(&data.header)?, ++ claim: serde_json::to_string(&data.claims)?, ++ }) + } + } +diff --git a/service/attestation/attestation-service/policy/src/policy_engine.rs b/service/attestation/attestation-service/policy/src/policy_engine.rs +index a03a8cc8..7a8508ef 100644 +--- a/service/attestation/attestation-service/policy/src/policy_engine.rs ++++ b/service/attestation/attestation-service/policy/src/policy_engine.rs +@@ -10,6 +10,7 @@ + * See the Mulan PSL v2 for more details. + */ + use std::{collections::HashMap, fmt::Display}; ++ + #[derive(Debug)] + pub enum PolicyEngineError { + InvalidPolicy(String), +@@ -50,6 +51,8 @@ impl Display for PolicyEngineError { + } + } + ++impl std::error::Error for PolicyEngineError {} ++ + pub trait PolicyEngine { + fn evaluate( + &self, +diff --git a/service/attestation/attestation-service/reference/Cargo.toml b/service/attestation/attestation-service/reference/Cargo.toml +index b36991e7..fb0a4bbb 100644 +--- a/service/attestation/attestation-service/reference/Cargo.toml ++++ b/service/attestation/attestation-service/reference/Cargo.toml +@@ -13,4 +13,5 @@ base64.workspace = true + sled.workspace = true + openssl.workspace = true + hex.workspace = true +-lazy_static.workspace = true +\ No newline at end of file ++lazy_static.workspace = true ++thiserror.workspace = true +\ No newline at end of file +diff --git a/service/attestation/attestation-service/reference/src/reference/mod.rs b/service/attestation/attestation-service/reference/src/reference/mod.rs +index bf56c854..6ec43714 100644 +--- a/service/attestation/attestation-service/reference/src/reference/mod.rs ++++ b/service/attestation/attestation-service/reference/src/reference/mod.rs +@@ -15,6 +15,7 @@ use crate::store::{KvError, KvStore}; + use openssl::sha::sha256; + use serde::{Deserialize, Serialize}; + use serde_json::{json, Value}; ++use thiserror::{self, Error}; + + pub struct ReferenceOps { + store: Box, +@@ -40,16 +41,12 @@ pub struct Ref { + pub value: Value, + } + +-#[derive(Debug, PartialEq)] ++#[derive(Error, Debug, PartialEq)] + pub enum RefOpError { ++ #[error("reference operation error {0}")] + Err(String), +-} +-impl From for RefOpError { +- fn from(value: KvError) -> Self { +- match value { +- KvError::Err(v) => RefOpError::Err(v), +- } +- } ++ #[error("reference store error: {0:?}")] ++ StoreErr(#[from] KvError) + } + + impl ReferenceOps { +@@ -68,36 +65,20 @@ impl ReferenceOps { + fn register_reference(&mut self, reference: &Ref) -> Result<(), RefOpError> { + // generate reference key + let key = Self::generate_reference_key(reference); +- match self.store.write( ++ self.store.write( + &key, + serde_json::to_string(&reference) + .unwrap() + .as_bytes() + .as_ref(), +- ) { +- Ok(_) => { +- return Ok(()); +- } +- Err(err) => match err { +- KvError::Err(err) => { +- return Err(RefOpError::Err(err)); +- } +- }, +- } ++ )?; ++ Ok(()) + } + + fn unregister_reference(&mut self, reference: &Ref) -> Result<(), RefOpError> { + let key = Self::generate_reference_key(reference); +- match self.store.delete(&key) { +- Ok(_) => { +- return Ok(()); +- } +- Err(err) => match err { +- KvError::Err(err) => { +- return Err(RefOpError::Err(err)); +- } +- }, +- } ++ self.store.delete(&key)?; ++ Ok(()) + } + + fn query_reference(&mut self, reference: &Ref) -> Option> { +diff --git a/service/attestation/attestation-service/reference/src/store/mod.rs b/service/attestation/attestation-service/reference/src/store/mod.rs +index c8c82607..c9597f6a 100644 +--- a/service/attestation/attestation-service/reference/src/store/mod.rs ++++ b/service/attestation/attestation-service/reference/src/store/mod.rs +@@ -9,9 +9,18 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ ++#[derive(Debug, PartialEq)] + pub enum KvError { + Err(String), + } ++impl std::fmt::Display for KvError { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ match self { ++ KvError::Err(msg) => write!(f, "kv store error:{}", msg) ++ } ++ } ++} ++impl std::error::Error for KvError {} + pub trait KvStore { + fn write(&mut self, key: &str, value: &[u8]) -> Result<(), KvError>; + fn read(&mut self, key: &str) -> Option>; +diff --git a/service/attestation/attestation-service/service/src/lib.rs b/service/attestation/attestation-service/service/src/lib.rs +index 3ec73d3d..31e6305d 100644 +--- a/service/attestation/attestation-service/service/src/lib.rs ++++ b/service/attestation/attestation-service/service/src/lib.rs +@@ -134,7 +134,7 @@ impl AttestationService { + Ok(eval) => { + for id in eval.keys() { + let val = Value::from_str(&eval[id].clone())?; +- let refs = match val.as_object().ok_or(Err(anyhow!(""))) { ++ let refs = match val.as_object().ok_or(Err(anyhow!("json value to map fail"))) { + Err(err) => { return Err(err.unwrap()); } + Ok(ret) => { ret } + }; +@@ -165,7 +165,7 @@ impl AttestationService { + // demo get signer, todo default signer + let signer = TokenSigner::new(self.config.token_cfg.clone())?; + +- signer.sign(&evl_report) ++ Ok(signer.sign(&evl_report)?) + } + + pub async fn generate_challenge(&self) -> String { +@@ -174,7 +174,6 @@ impl AttestationService { + base64_url::encode(&nonce) + } + +- // todo pub fun set policy + pub async fn set_policy(&self, + id: &String, + policy: &String, +@@ -185,7 +184,7 @@ impl AttestationService { + .set_policy(id, policy) + .await + } +- // todo pub fun get policy ++ + pub async fn get_policy(&self, + policy_dir: &String, + ) -> Result { +@@ -203,7 +202,7 @@ impl AttestationService { + Err(err) => Err(err) + } + } +- // todo pub fun import reference value ++ + pub async fn register_reference(&self, + ref_set: &String + ) -> Result<(), RefOpError> { +diff --git a/service/attestation/attestation-service/service/src/restapi/mod.rs b/service/attestation/attestation-service/service/src/restapi/mod.rs +index a7e6012b..f49d1755 100644 +--- a/service/attestation/attestation-service/service/src/restapi/mod.rs ++++ b/service/attestation/attestation-service/service/src/restapi/mod.rs +@@ -77,8 +77,8 @@ pub async fn attestation( + } + } + +- let nonce = base64_url::decode(&challenge).expect("base64 decode nonce"); +- let evidence = base64_url::decode(&request.evidence).expect("base64 decode evidence"); ++ let nonce = base64_url::decode(&challenge)?; ++ let evidence = base64_url::decode(&request.evidence)?; + let ids = request.policy_id; + let token = service.read().await.evaluate(&nonce, &evidence, &ids).await?; + +@@ -97,10 +97,8 @@ pub async fn reference( + ) -> Result { + let request = request.0; + log::debug!("reference request: {:?}", request); +- match service.read().await.register_reference(&request.refs).await { +- Ok(_) => Ok(HttpResponse::Ok().body("set reference success")), +- Err(_err) => Ok(HttpResponse::Ok().body("set reference fail")), +- } ++ service.read().await.register_reference(&request.refs).await?; ++ Ok(HttpResponse::Ok().body("set reference success")) + } + + #[derive(Deserialize, Serialize, Debug)] +@@ -120,13 +118,8 @@ pub async fn set_policy( + let policy_id = request.id.clone(); + let policy = request.policy.clone(); + let dir:String = String::from(DEFAULT_POLICY_DIR); +- match service.read().await.set_policy(&policy_id, &policy, &dir).await { +- Ok(_) => Ok(HttpResponse::Ok().body("set policy success")), +- Err(err) => { +- log::debug!("set policy error: {:?}", err); +- Ok(HttpResponse::Ok().body("set policy fail")) +- } +- } ++ service.read().await.set_policy(&policy_id, &policy, &dir).await?; ++ Ok(HttpResponse::Ok().body("set policy success")) + } + + #[get("/policy")] +@@ -136,8 +129,6 @@ pub async fn get_policy( + ) -> Result { + log::debug!("get policy request: {:?}", request); + let dir:String = String::from(DEFAULT_POLICY_DIR); +- match service.read().await.get_policy(&dir).await { +- Ok(ret) => Ok(HttpResponse::Ok().body(ret)), +- Err(_err) => Ok(HttpResponse::Ok().body("get policy fail")), +- } ++ let ret = service.read().await.get_policy(&dir).await?; ++ Ok(HttpResponse::Ok().body(ret)) + } +diff --git a/service/attestation/attestation-service/service/src/result/mod.rs b/service/attestation/attestation-service/service/src/result/mod.rs +index fcb1c123..7261d19b 100644 +--- a/service/attestation/attestation-service/service/src/result/mod.rs ++++ b/service/attestation/attestation-service/service/src/result/mod.rs +@@ -22,6 +22,26 @@ pub enum Error { + #[from] + source: std::io::Error, + }, ++ #[error("attestation error: {source:?}")] ++ DecodeError { ++ #[from] ++ source: base64::DecodeError, ++ }, ++ #[error("Policy Engine error: {source:?}")] ++ PolicyEngine { ++ #[from] ++ source: policy::policy_engine::PolicyEngineError, ++ }, ++ #[error("Reference error: {source:?}")] ++ Reference { ++ #[from] ++ source: reference::reference::RefOpError, ++ }, ++ #[error("Sign error: {source:?}")] ++ Sign { ++ #[from] ++ source: token_signer::SignError, ++ }, + + #[error("Web error: {source:?}")] + Web { +diff --git a/service/attestation/attestation-service/token/Cargo.toml b/service/attestation/attestation-service/token/Cargo.toml +index c4b885c0..029008a1 100644 +--- a/service/attestation/attestation-service/token/Cargo.toml ++++ b/service/attestation/attestation-service/token/Cargo.toml +@@ -10,4 +10,5 @@ jsonwebtoken.workspace = true + serde.workspace = true + serde_json.workspace = true + anyhow.workspace = true +-attestation-types.workspace = true +\ No newline at end of file ++attestation-types.workspace = true ++thiserror.workspace = true +\ No newline at end of file +diff --git a/service/attestation/attestation-service/token/src/lib.rs b/service/attestation/attestation-service/token/src/lib.rs +index ed41a4ec..3ee785e5 100644 +--- a/service/attestation/attestation-service/token/src/lib.rs ++++ b/service/attestation/attestation-service/token/src/lib.rs +@@ -9,7 +9,7 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use anyhow::{Result, bail}; ++use anyhow::{Result}; + use jsonwebtoken::{encode, get_current_timestamp, + Algorithm, EncodingKey, Header, + }; +@@ -17,7 +17,21 @@ use std::path::Path; + use serde::{Deserialize, Serialize}; + use serde_json::Value; + use attestation_types::{EvlResult, Claims}; ++use thiserror; + ++#[derive(thiserror::Error, Debug)] ++pub enum SignError { ++ #[error("get unix time fail:{0:?}")] ++ ToUnixTimeFail(#[from] std::num::TryFromIntError), ++ #[error("unsupport algorith:{0}")] ++ UnsupportAlg(String), ++ #[error("key not exist:{0}")] ++ KeyNotExist(String), ++ #[error("key content read fail:{0}")] ++ ReadKeyFail(String), ++ #[error("sign fail:{0:?}")] ++ SignFail(#[from] jsonwebtoken::errors::Error) ++} + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct TokenSignConfig { +@@ -79,36 +93,35 @@ impl TokenSigner { + } + return false; + } +- pub fn sign(&self, report: &EvlReport) -> Result { ++ pub fn sign(&self, report: &EvlReport) -> Result { + let alg: Algorithm = self.config.alg; + let mut header = Header::new(alg); + header.typ = Some("JWT".to_string()); + let unix_time = get_current_timestamp(); + let claims: Claims = Claims { + iss: self.config.iss.clone(), +- iat: usize::try_from(unix_time).expect("unix time to usize error"), +- nbf: usize::try_from(unix_time).expect("unix time to usize error"), +- exp: usize::try_from(unix_time).expect("unix time to usize error") +- + self.config.valid_duration, ++ iat: usize::try_from(unix_time)?, ++ nbf: usize::try_from(unix_time)?, ++ exp: usize::try_from(unix_time)? + self.config.valid_duration, + evaluation_reports: report.result.clone(), + tee: report.tee.clone(), + tcb_status: report.tcb_status.clone(), + }; + if !Self::support_rs(&alg) && !Self::support_ps(&alg) { +- bail!("unknown algrithm {:?}", alg); ++ return Err(SignError::UnsupportAlg(format!("unknown algrithm {:?}", alg))); + } + if !Path::new(&self.config.key).exists() { +- bail!("token verfify failed, {:?} cert not exist", self.config.key); ++ return Err(SignError::UnsupportAlg(format!("token verfify failed, {:?} cert not exist", self.config.key))); + } + let key = std::fs::read(&self.config.key).unwrap(); + let key_value: EncodingKey = match EncodingKey::from_rsa_pem(&key) { + Ok(val) => val, +- _ => bail!("get key from input error"), ++ _ => {return Err(SignError::ReadKeyFail(format!("get key from input error")));} + }; + + let token = match encode(&header, &claims, &key_value) { + Ok(val) => val, +- Err(e) => bail!("sign jwt token error {:?}", e), ++ Err(e) => {return Err(SignError::SignFail(e));} + }; + Ok(token) + } +-- +2.46.0 + diff --git a/0087-use-id-when-get-policy.patch b/0087-use-id-when-get-policy.patch new file mode 100644 index 0000000000000000000000000000000000000000..a710c91dd9a9626361acd3e951633a9007e61f05 --- /dev/null +++ b/0087-use-id-when-get-policy.patch @@ -0,0 +1,90 @@ +From 32f9270f0cf4ef2ee9c1ababf66c24c7cf10bd17 Mon Sep 17 00:00:00 2001 +From: xuraoqing +Date: Fri, 13 Sep 2024 17:21:47 +0800 +Subject: [PATCH] use id when get policy + +--- + .../attestation-service/service/src/lib.rs | 10 +++++++++- + .../attestation-service/service/src/restapi/mod.rs | 11 +++++++++-- + .../attestation/attestation-service/tests/src/lib.rs | 6 ++++++ + 3 files changed, 24 insertions(+), 3 deletions(-) + +diff --git a/service/attestation/attestation-service/service/src/lib.rs b/service/attestation/attestation-service/service/src/lib.rs +index 31e6305..1c5c907 100644 +--- a/service/attestation/attestation-service/service/src/lib.rs ++++ b/service/attestation/attestation-service/service/src/lib.rs +@@ -185,7 +185,7 @@ impl AttestationService { + .await + } + +- pub async fn get_policy(&self, ++ pub async fn get_all_policy(&self, + policy_dir: &String, + ) -> Result { + let engine = OPA::new(policy_dir).await; +@@ -203,6 +203,14 @@ impl AttestationService { + } + } + ++ pub async fn get_policy(&self, ++ policy_dir: &String, ++ id: &String ++ ) -> Result { ++ let engine = OPA::new(policy_dir).await?; ++ Ok(engine.get_policy(id).await?) ++ } ++ + pub async fn register_reference(&self, + ref_set: &String + ) -> Result<(), RefOpError> { +diff --git a/service/attestation/attestation-service/service/src/restapi/mod.rs b/service/attestation/attestation-service/service/src/restapi/mod.rs +index f49d175..d47698a 100644 +--- a/service/attestation/attestation-service/service/src/restapi/mod.rs ++++ b/service/attestation/attestation-service/service/src/restapi/mod.rs +@@ -122,13 +122,20 @@ pub async fn set_policy( + Ok(HttpResponse::Ok().body("set policy success")) + } + ++#[derive(Deserialize, Serialize, Debug)] ++pub struct PolicyGetRequest { ++ policy_id: String, ++} ++ + #[get("/policy")] + pub async fn get_policy( +- request: HttpRequest, ++ request: web::Json, + service: web::Data>>, + ) -> Result { ++ let request = request.0; + log::debug!("get policy request: {:?}", request); ++ let id = request.policy_id.clone(); + let dir:String = String::from(DEFAULT_POLICY_DIR); +- let ret = service.read().await.get_policy(&dir).await?; ++ let ret = service.read().await.get_policy(&dir, &id.to_string()).await?; + Ok(HttpResponse::Ok().body(ret)) + } +diff --git a/service/attestation/attestation-service/tests/src/lib.rs b/service/attestation/attestation-service/tests/src/lib.rs +index abd099f..b8adb1e 100644 +--- a/service/attestation/attestation-service/tests/src/lib.rs ++++ b/service/attestation/attestation-service/tests/src/lib.rs +@@ -121,10 +121,16 @@ mod tests { + + #[test] + fn api_get_policy() { ++ let request_body = json!({ ++ "policy_id":"test_policy.rego" ++ } ++ ); + let client: Client = Client::new(); + let endpoint = "http://127.0.0.1:8080/policy"; + let res = client + .get(endpoint) ++ .header("Content-Type", "application/json") ++ .body(request_body.to_string()) + .send() + .unwrap(); + assert_eq!(res.status(), reqwest::StatusCode::OK); +-- +2.33.0 + diff --git a/0088-fix-evidence-decode-typos.patch b/0088-fix-evidence-decode-typos.patch new file mode 100644 index 0000000000000000000000000000000000000000..1e2a1c7e5bde7758cc1fb16200f7ef281f2313cf --- /dev/null +++ b/0088-fix-evidence-decode-typos.patch @@ -0,0 +1,26 @@ +From 5f4cbe40acc8197fb2ae140584712e8b3c950805 Mon Sep 17 00:00:00 2001 +From: xuraoqing +Date: Sat, 14 Sep 2024 11:30:52 +0800 +Subject: [PATCH] fix evidence decode typos + +Signed-off-by: xuraoqing +--- + service/attestation/attestation-agent/agent/src/restapi/mod.rs | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/service/attestation/attestation-agent/agent/src/restapi/mod.rs b/service/attestation/attestation-agent/agent/src/restapi/mod.rs +index 0570060..2745443 100644 +--- a/service/attestation/attestation-agent/agent/src/restapi/mod.rs ++++ b/service/attestation/attestation-agent/agent/src/restapi/mod.rs +@@ -78,7 +78,7 @@ pub async fn verify_evidence( + ) -> Result { + let request = request.0; + log::debug!("verify evidence request: {:?}", request); +- let challenge = base64_url::decode(&"request.challenge".to_string()) ++ let challenge = base64_url::decode(&request.challenge) + .map_err(|err|AgentError::DecodeError(err.to_string()))?; + let evidence = request.evidence; + let policy_id = request.policy_id; +-- +2.33.0 + diff --git a/0089-features-support-resource-maitainance.patch b/0089-features-support-resource-maitainance.patch new file mode 100644 index 0000000000000000000000000000000000000000..09e4dcd4a4622e475c2d2533ea04e40187532e70 --- /dev/null +++ b/0089-features-support-resource-maitainance.patch @@ -0,0 +1,7140 @@ +From f1938d334b93ffb2c6b6f1f210767d95ea642243 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Mon, 17 Feb 2025 20:09:31 +0800 +Subject: [PATCH 1/1] support resource matainance + +--- + service/attestation/README.md | 177 ++++++++ + .../attestation/attestation-agent/Cargo.toml | 14 +- + .../attestation-agent/agent/Cargo.toml | 3 +- + .../agent/attestation-agent.conf | 11 +- + .../agent/src/bin/aa-test/main.rs | 88 +++- + .../attestation-agent/agent/src/lib.rs | 380 +++++++++++++----- + .../attestation-agent/agent/src/main.rs | 65 ++- + .../agent/src/restapi/mod.rs | 157 ++++++-- + .../attestation-agent/agent/src/result/mod.rs | 3 + + .../attestation-agent/agent/src/session.rs | 13 +- + .../attestation-agent/attester/Cargo.toml | 4 +- + .../attester/src/itrustee/mod.rs | 26 +- + .../attestation-agent/attester/src/lib.rs | 32 +- + .../attester/src/virtcca/mod.rs | 37 +- + .../attester/src/virtcca/virtcca.rs | 6 +- + .../attestation-agent/c_header/example.c | 90 +++++ + .../c_header/rust_attestation_agent.h | 81 ++++ + .../attestation-agent/token/Cargo.toml | 2 +- + .../attestation-agent/token/src/lib.rs | 41 +- + .../attestation/attestation-client/Cargo.toml | 16 + + .../attestation-client/src/client.rs | 53 +++ + .../attestation-client/src/error.rs | 23 ++ + .../attestation-client/src/main.rs | 52 +++ + .../attestation-client/src/resource/client.rs | 234 +++++++++++ + .../attestation-client/src/resource/mod.rs | 129 ++++++ + .../src/resource_policy/client.rs | 190 +++++++++ + .../src/resource_policy/mod.rs | 100 +++++ + .../attestation-service/Cargo.toml | 17 +- + .../attestation/attestation-service/README.md | 19 +- + .../attestation-service/as_startup.sh | 138 +++++++ + .../attestation-service/policy/Cargo.toml | 1 + + .../attestation-service/policy/src/lib.rs | 8 +- + .../attestation-service/policy/src/opa/mod.rs | 65 ++- + .../attestation-service/reference/Cargo.toml | 2 +- + .../attestation-service/reference/src/lib.rs | 19 +- + .../reference/src/local_fs/mod.rs | 6 +- + .../reference/src/reference/mod.rs | 2 +- + .../reference/src/store/mod.rs | 2 +- + .../attestation-service/service/Cargo.toml | 5 +- + .../attestation-service/service/src/lib.rs | 175 +++++--- + .../attestation-service/service/src/main.rs | 76 ++-- + .../service/src/restapi/mod.rs | 141 ++++--- + .../service/src/restapi/resource/mod.rs | 14 + + .../service/src/restapi/resource/policy.rs | 116 ++++++ + .../service/src/restapi/resource/storage.rs | 158 ++++++++ + .../service/src/result/mod.rs | 17 +- + .../service/src/session.rs | 32 +- + .../attestation-service/tests/Cargo.toml | 2 +- + .../attestation-service/tests/src/lib.rs | 6 +- + .../attestation-service/token/Cargo.toml | 2 +- + .../attestation-service/token/src/lib.rs | 60 ++- + .../attestation-service/verifier/Cargo.toml | 6 +- + .../verifier/src/itrustee/mod.rs | 35 +- + .../attestation-service/verifier/src/lib.rs | 41 +- + .../verifier/src/rustcca/LICENSE | 201 +++++++++ + .../verifier/src/rustcca/mod.rs | 246 ++++++++++++ + .../verifier/src/virtcca/ima.rs | 44 +- + .../verifier/src/virtcca/mod.rs | 152 +++++-- + .../attestation/attestation-types/Cargo.toml | 11 +- + .../attestation/attestation-types/src/lib.rs | 10 +- + .../src/resource/admin/mod.rs | 59 +++ + .../src/resource/admin/simple.rs | 256 ++++++++++++ + .../attestation-types/src/resource/error.rs | 45 +++ + .../attestation-types/src/resource/mod.rs | 145 +++++++ + .../src/resource/policy/mod.rs | 128 ++++++ + .../src/resource/policy/opa/mod.rs | 321 +++++++++++++++ + .../src/resource/policy/opa/virtcca.rego | 12 + + .../src/resource/storage/mod.rs | 67 +++ + .../src/resource/storage/simple.rs | 220 ++++++++++ + .../attestation-types/src/resource/utils.rs | 32 ++ + .../attestation-types/src/service.rs | 83 ++++ + 71 files changed, 4697 insertions(+), 527 deletions(-) + create mode 100644 service/attestation/README.md + create mode 100644 service/attestation/attestation-agent/c_header/example.c + create mode 100644 service/attestation/attestation-agent/c_header/rust_attestation_agent.h + create mode 100644 service/attestation/attestation-client/Cargo.toml + create mode 100644 service/attestation/attestation-client/src/client.rs + create mode 100644 service/attestation/attestation-client/src/error.rs + create mode 100644 service/attestation/attestation-client/src/main.rs + create mode 100644 service/attestation/attestation-client/src/resource/client.rs + create mode 100644 service/attestation/attestation-client/src/resource/mod.rs + create mode 100644 service/attestation/attestation-client/src/resource_policy/client.rs + create mode 100644 service/attestation/attestation-client/src/resource_policy/mod.rs + create mode 100755 service/attestation/attestation-service/as_startup.sh + create mode 100644 service/attestation/attestation-service/service/src/restapi/resource/mod.rs + create mode 100644 service/attestation/attestation-service/service/src/restapi/resource/policy.rs + create mode 100644 service/attestation/attestation-service/service/src/restapi/resource/storage.rs + create mode 100644 service/attestation/attestation-service/verifier/src/rustcca/LICENSE + create mode 100644 service/attestation/attestation-service/verifier/src/rustcca/mod.rs + create mode 100644 service/attestation/attestation-types/src/resource/admin/mod.rs + create mode 100644 service/attestation/attestation-types/src/resource/admin/simple.rs + create mode 100644 service/attestation/attestation-types/src/resource/error.rs + create mode 100644 service/attestation/attestation-types/src/resource/mod.rs + create mode 100644 service/attestation/attestation-types/src/resource/policy/mod.rs + create mode 100644 service/attestation/attestation-types/src/resource/policy/opa/mod.rs + create mode 100644 service/attestation/attestation-types/src/resource/policy/opa/virtcca.rego + create mode 100644 service/attestation/attestation-types/src/resource/storage/mod.rs + create mode 100644 service/attestation/attestation-types/src/resource/storage/simple.rs + create mode 100644 service/attestation/attestation-types/src/resource/utils.rs + create mode 100644 service/attestation/attestation-types/src/service.rs + +diff --git a/service/attestation/README.md b/service/attestation/README.md +new file mode 100644 +index 0000000..32bfd42 +--- /dev/null ++++ b/service/attestation/README.md +@@ -0,0 +1,177 @@ ++# Attestation ++This project provides attestation service and attestation agent for common attestation scenes. ++ ++## Components ++- Attestation Agent: An agent depends by relying party or attester for attestation. ++- Attestation Service: A verifier verifies TEE evidence. ++ ++Note: The roles relying party, attester and verifier is defined in [RFC9334 RATS](https://datatracker.ietf.org/doc/html/rfc9334#name-architectural-overview). ++ ++# Quick Start ++## Prepare ++### Install attester depends SDK ++- OS: openEuler 24.09 ++- Repo ++``` ++vim /etc/yum.repos.d/openEuler.repo ++[everything] ++name=everything ++baseurl=https://repo.openeuler.org/openEuler-24.09/everything/aarch64/ ++enabled=1 ++gpgcheck=0 ++ ++yum install virtCCA_sdk-devel ++``` ++ ++### Generate self-signed certificate and private key ++``` ++openssl genrsa -out private.pem 2048 ++openssl req -new -key private.pem -out server.csr ++openssl x509 -req -in server.csr -out as_cert.pem -signkey private.pem -days 3650 ++ ++mkdir -p /etc/attestation/attestation-service/token ++cp private.pem /etc/attestation/attestation-service/token ++ ++// as_cert.pem will be deployed into AA config directory, AA use it to verify token ++``` ++ ++### Generate AS Config File ++``` ++mkdir -p /etc/attestation/attestation-service/ ++vim /etc/attestation.bak/attestation-service/attestation-service.conf ++{ ++ "token_cfg": { ++ "key": "/etc/attestation/attestation-service/token/private.pem", ++ "iss": "oeas", ++ "nbf": 0, ++ "valid_duration": 300, ++ "alg": "PS256" ++ } ++} ++``` ++ ++### Generate AA Config File ++``` ++mkdir -p /etc/attestation/attestation-agent/ ++// svr_url: url to access attestation service ++// cert: token signature certificate ++// iss: token issuer name ++vim /etc/attestation/attestation-agent/attestation-agent.conf ++{ ++ "svr_url": "http://127.0.0.1:8080", ++ "token_cfg": { ++ "cert": "/etc/attestation/attestation-agent/as_cert.pem", ++ "iss": "oeas" ++ } ++} ++``` ++ ++### Download Huawei root cert chain to verify virtCCA evidence ++ ++[Root Cert](https://download.huawei.com/dl/download.do?actionFlag=download&nid=PKI1000000002&partNo=3001&mid=SUP_PKI)
++[Sub Cert](https://download.huawei.com/dl/download.do?actionFlag=download&nid=PKI1000000040&partNo=3001&mid=SUP_PKI) ++``` ++mkdir -p /etc/attestation/attestation-service/verifier/virtcca ++// copy Root Cert and Sub Cert to the above directory ++``` ++ ++## Build ++ ++### Build AA ++``` ++cd secGear/service/attestation/attestation-agent ++cargo build --features virtcca-attester ++``` ++ ++### Build AS ++``` ++cd secGear/service/attestation/attestation-service ++cargo build ++``` ++ ++## Run AS, AA and Demo(aa-test)Use HTTP ++### Run AS ++``` ++cd secGear/service/attestation/attestation-service ++./target/debug/attestation-service ++``` ++attestation service listens on 127.0.0.1:8080 default, also can specify custom ip:port by -s param such as ++``` ++./target/debug/attestation-service -s ip:port ++``` ++ ++- Config default policy ++``` ++cp secGear/service/attestation/attestation-service/policy/src/opa/default_vcca.rego /etc/attestation/attestation-service/policy ++``` ++- Config virtcca reference ++ ++virtcca reference (such as rim:7d2e49c8d29f18b748e658e7243ecf26bc292e5fee93f72af11ad9da9810142a ) is generated by [rim_ref tools](https://gitee.com/openeuler/virtCCA_sdk/tree/master/attestation/rim_ref) ++``` ++curl -H "Content-Type:application/json" -X POST -d '{"refs":"{\"vcca.cvm.rim\":\"7d2e49c8d29f18b748e658e7243ecf26bc292e5fee93f72af11ad9da9810142a\"}"}' http://127.0.0.1:8080/reference ++``` ++ ++### Run AA ++ ++#### Run AA ++``` ++cd secGear/service/attestation/attestation-agent ++./target/debug/attestation-agent ++``` ++ ++### Run AA demo ++``` ++cd secGear/service/attestation/attestation-agent ++./target/debug/aa-test ++``` ++ ++## Run AS, AA and Demo(aa-test)Use HTTPS ++### Run AS ++- generate self-signed certificate ++``` ++openssl genrsa -out key.pem 2048 ++openssl req -subj "/C=CN/ST=ST/L=CITY/O=Company/CN=server.com" -new -key key.pem -out cert.csr ++openssl x509 -req -extfile /etc/pki/tls/openssl.cnf -extensions v3_req -in cert.csr -out cert.pem -signkey key.pem -days 365 ++``` ++ ++- config hosts ++``` ++vim /etc/hosts ++127.0.0.1 server.com ++``` ++ ++- start service ++``` ++cd secGear/service/attestation/attestation-service ++./target/debug/attestation-service -p https -t cert.pem -k key.pem 2>&1 & ++// you can specified listen port ++./target/debug/attestation-service -p https -t cert.pem -k key.pem -s server.com:8080 2>&1 & ++``` ++ ++- Config default policy ++``` ++cp secGear/service/attestation/attestation-service/policy/src/opa/default_vcca.rego /etc/attestation/attestation-service/policy ++``` ++ ++- Config virtcca reference ++ ++virtcca reference (such as rim:7d2e49c8d29f18b748e658e7243ecf26bc292e5fee93f72af11ad9da9810142a ) is generated by [rim_ref tools](https://gitee.com/openeuler/virtCCA_sdk/tree/master/attestation/rim_ref) ++``` ++curl -k -H "Content-Type:application/json" -X POST -d '{"refs":"{\"vcca.cvm.rim\":\"7d2e49c8d29f18b748e658e7243ecf26bc292e5fee93f72af11ad9da9810142a\"}"}' https://server.com:8080/reference ++``` ++ ++### Run AA ++ ++``` ++cd secGear/service/attestation/attestation-agent ++./target/debug/attestation-agent -p https -t cert.pem 2>&1 & ++ ++// you can use -u specified destination which AA connect to , -s specified port which AA listen at ++./target/debug/attestation-agent -p https -t cert.pem -s server.com:8081 -u server.com:8080 2>&1 & ++ ++``` ++### Run AA demo ++``` ++cd secGear/service/attestation/attestation-agent ++./target/debug/aa-test ++``` +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/Cargo.toml b/service/attestation/attestation-agent/Cargo.toml +index f6f31b1..126a9f4 100644 +--- a/service/attestation/attestation-agent/Cargo.toml ++++ b/service/attestation/attestation-agent/Cargo.toml +@@ -1,10 +1,6 @@ + [workspace] + resolver = "2" +-members = [ +- "agent", +- "attester", +- "token" +-] ++members = ["agent", "attester", "token"] + + [workspace.dependencies] + anyhow = "1.0" +@@ -14,10 +10,10 @@ serde_json = "1.0" + rand = "0.8.5" + base64-url = "3.0.0" + async-trait = "0.1.78" +-tokio = {version = "1.0", features = ["rt"]} ++tokio = { version = "1.0", features = ["rt"] } + log = "0.4.14" + env_logger = "0.9" +-safer-ffi = {version = "0.1.8", features = ["alloc"]} ++safer-ffi = { version = "0.1.8", features = ["alloc"] } + futures = "0.3.30" + reqwest = { version = "0.12", features = ["cookies", "json"] } + jsonwebtoken = "9.3.0" +@@ -26,5 +22,5 @@ actix-web = "4.5" + clap = { version = "4.5.7", features = ["derive"] } + scc = "2.1" + +-verifier = {path = "../attestation-service/verifier", default-features = false} +-attestation-types = {path = "../attestation-types"} ++verifier = { path = "../attestation-service/verifier", default-features = false } ++attestation-types = { path = "../attestation-types" } +diff --git a/service/attestation/attestation-agent/agent/Cargo.toml b/service/attestation/attestation-agent/agent/Cargo.toml +index d2450c8..07c1c01 100644 +--- a/service/attestation/attestation-agent/agent/Cargo.toml ++++ b/service/attestation/attestation-agent/agent/Cargo.toml +@@ -36,13 +36,14 @@ log.workspace = true + env_logger.workspace = true + safer-ffi.workspace = true + futures.workspace = true +-reqwest = { workspace = true, features = ["json"] } ++reqwest = { workspace = true, features = ["json", "cookies"] } + base64-url.workspace = true + thiserror.workspace = true + actix-web.workspace = true + clap.workspace = true + scc.workspace = true + attestation-types.workspace = true ++jsonwebtoken.workspace = true + + attester = { path = "../attester" } + token_verifier = { path = "../token" } +diff --git a/service/attestation/attestation-agent/agent/attestation-agent.conf b/service/attestation/attestation-agent/agent/attestation-agent.conf +index 5c9a015..76ae4d1 100644 +--- a/service/attestation/attestation-agent/agent/attestation-agent.conf ++++ b/service/attestation/attestation-agent/agent/attestation-agent.conf +@@ -1,7 +1,12 @@ + { + "svr_url": "http://127.0.0.1:8080", + "token_cfg": { +- "cert": "/etc/attestation/attestation-agent/as_cert.pem", +- "iss": "oeas" ++ "cert": "/etc/attestation/attestation-agent/as_cert.pem", ++ "iss": "oeas" ++ }, ++ "protocal": { ++ "Http": { ++ "protocal": "http" ++ } + } +-} ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs b/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs +index 4867a23..8aa2200 100644 +--- a/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs ++++ b/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs +@@ -12,19 +12,22 @@ + + //! This is a test bin, test get evidence and verify + //! on kunpeng platform, libqca has white ta lists, need copy target/debug/attestation-agent to /vendor/bin/ +-use tokio; + use env_logger; +-use serde_json::json; + use reqwest; ++use serde_json::json; ++use tokio; + + const TEST_THREAD_NUM: i64 = 1; // multi thread num ++const AA_ADDR: &str = "http://127.0.0.1:8081"; + + #[tokio::main] + async fn main() { + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + let mut handles = Vec::with_capacity(TEST_THREAD_NUM as usize); + for i in 0..TEST_THREAD_NUM { +- let t = tokio::spawn(async move {aa_proc(i).await;}); ++ let t = tokio::spawn(async move { ++ aa_proc(i).await; ++ }); + handles.push(t); + } + +@@ -40,7 +43,7 @@ async fn aa_proc(i: i64) { + // get challenge + log::info!("thread {} case1 get challenge", i); + let client = reqwest::Client::new(); +- let challenge_endpoint = "http://127.0.0.1:8081/challenge"; ++ let challenge_endpoint = format!("{AA_ADDR}/challenge"); + let res = client + .get(challenge_endpoint) + .header("Content-Type", "application/json") +@@ -53,11 +56,19 @@ async fn aa_proc(i: i64) { + let challenge = match res.status() { + reqwest::StatusCode::OK => { + let respone = res.text().await.unwrap(); +- log::info!("thread {} case1 get challenge success response: {:?}", i, respone); ++ log::info!( ++ "thread {} case1 get challenge success response: {:?}", ++ i, ++ respone ++ ); + respone + } + status => { +- log::error!("thread {} case1 get challenge failed response: {:?}", i, status); ++ log::error!( ++ "thread {} case1 get challenge failed response: {:?}", ++ i, ++ status ++ ); + return; + } + }; +@@ -67,11 +78,15 @@ async fn aa_proc(i: i64) { + "challenge": challenge, + "uuid": String::from("f68fd704-6eb1-4d14-b218-722850eb3ef0"), + }); +- log::info!("thread {} case2 get evidence, request body: {}", i, request_body); +- let attest_endpoint = "http://127.0.0.1:8081/evidence"; ++ log::info!( ++ "thread {} case2 get evidence, request body: {}", ++ i, ++ request_body ++ ); ++ let attest_endpoint = format!("{AA_ADDR}/evidence"); + let client = reqwest::Client::new(); + let res = client +- .get(attest_endpoint) ++ .get(attest_endpoint.clone()) + .header("Content-Type", "application/json") + .json(&request_body) + .send() +@@ -86,7 +101,11 @@ async fn aa_proc(i: i64) { + respone + } + status => { +- log::error!("thread {} case2 get evidence failed response: {:?}", i, status); ++ log::error!( ++ "thread {} case2 get evidence failed response: {:?}", ++ i, ++ status ++ ); + return; + } + }; +@@ -109,25 +128,37 @@ async fn aa_proc(i: i64) { + match res.status() { + reqwest::StatusCode::OK => { + let respone = res.text().await.unwrap(); +- log::info!("thread {} case4 verify evidence success response: {:?}", i, respone); ++ log::info!( ++ "thread {} case4 verify evidence success response: {:?}", ++ i, ++ respone ++ ); + } + status => { +- log::error!("thread {} case4 verify evidence failed response: {:?}", i, status); ++ log::error!( ++ "thread {} case4 verify evidence failed response: {:?}", ++ i, ++ status ++ ); + } + } + + #[cfg(not(feature = "no_as"))] + { + // get token +- let token_endpoint = "http://127.0.0.1:8081/token"; ++ let token_endpoint = format!("{AA_ADDR}/token"); + let request_body = json!({ + "challenge": challenge, + "uuid": String::from("f68fd704-6eb1-4d14-b218-722850eb3ef0"), + }); +- log::info!("thread {} case5 get token, request body: {}", i, request_body); ++ log::info!( ++ "thread {} case5 get token, request body: {}", ++ i, ++ request_body ++ ); + let client = reqwest::Client::new(); + let res = client +- .get(token_endpoint) ++ .get(token_endpoint.clone()) + .header("Content-Type", "application/json") + .json(&request_body) + .send() +@@ -142,7 +173,12 @@ async fn aa_proc(i: i64) { + respone + } + status => { +- log::error!("thread {} case5 get token failed status: {:?} response: {:?}", i, status, res.text().await.unwrap()); ++ log::error!( ++ "thread {} case5 get token failed status: {:?} response: {:?}", ++ i, ++ status, ++ res.text().await.unwrap() ++ ); + return; + } + }; +@@ -165,15 +201,25 @@ async fn aa_proc(i: i64) { + match res.status() { + reqwest::StatusCode::OK => { + let respone = res.text().await.unwrap(); +- log::info!("thread {} case6 verify token success response: {:?}", i, respone); ++ log::info!( ++ "thread {} case6 verify token success response: {:?}", ++ i, ++ respone ++ ); + } + status => { +- log::error!("thread {} case6 verify token failed response: {:?}", i, status); +- log::error!("thread case6 verify token failed response:{}", res.text().await.unwrap()); ++ log::error!( ++ "thread {} case6 verify token failed response: {:?}", ++ i, ++ status ++ ); ++ log::error!( ++ "thread case6 verify token failed response:{}", ++ res.text().await.unwrap() ++ ); + } + } + } +- + + log::info!("attestation_proc thread {} end", i); +-} +\ No newline at end of file ++} +diff --git a/service/attestation/attestation-agent/agent/src/lib.rs b/service/attestation/attestation-agent/agent/src/lib.rs +index f1c4510..d1234d1 100644 +--- a/service/attestation/attestation-agent/agent/src/lib.rs ++++ b/service/attestation/attestation-agent/agent/src/lib.rs +@@ -11,24 +11,30 @@ + */ + + //! Attestation Agent +-//! ++//! + //! This crate provides some APIs to get and verify the TEE evidence. + //! Current supports kunpeng itrustee and virtcca TEE types. + +-use anyhow::{Result, bail, anyhow}; +-use log; +-use serde::{Serialize, Deserialize}; ++pub mod restapi; ++pub mod result; ++ ++use actix_web::web::Bytes; ++use anyhow::{anyhow, bail, Context, Result}; + use async_trait::async_trait; ++use attestation_types::{resource::ResourceLocation, service::GetResourceOp}; ++use attester::{Attester, AttesterAPIs}; ++use log; ++use rand::RngCore; ++use reqwest::Client; ++use result::Error; ++use serde::{Deserialize, Serialize}; ++use serde_json::json; ++use serde_json::Value; + use std::fs::File; + use std::path::Path; +-use rand::RngCore; + use thiserror; ++use token_verifier::{TokenRawData, TokenVerifier, TokenVerifyConfig}; + +-use attester::{Attester, AttesterAPIs}; +-use token_verifier::{TokenVerifyConfig, TokenVerifier, TokenRawData}; +- +-pub mod result; +-use result::Error; + pub type TeeClaim = serde_json::Value; + + #[derive(Debug, thiserror::Error)] +@@ -52,15 +58,14 @@ use verifier::{Verifier, VerifierAPIs}; + + #[cfg(not(feature = "no_as"))] + use { +- serde_json::json, ++ base64_url, + reqwest::header::{HeaderMap, HeaderValue}, +- base64_url + }; + + pub use attester::EvidenceRequest; + mod session; +-use session::{SessionMap, Session}; + use attestation_types::SESSION_TIMEOUT_MIN; ++use session::{Session, SessionMap}; + + pub type AsTokenClaim = TokenRawData; + +@@ -72,7 +77,7 @@ pub struct TokenRequest { + + #[async_trait] + pub trait AttestationAgentAPIs { +- async fn get_challenge(&self) -> Result; ++ async fn get_challenge(&self, user_data: Option>) -> Result; + + /// `get_evidence`: get hardware TEE signed evidence due to given user_data, + /// such as input random challenge to prevent replay attacks +@@ -80,65 +85,82 @@ pub trait AttestationAgentAPIs { + + /// `verify_evidence`: verify the integrity of TEE evidence and evaluate the + /// claims against the supplied reference values +- async fn verify_evidence(&self, ++ async fn verify_evidence( ++ &self, + challenge: &[u8], + evidence: &[u8], +- policy_id: Option> ++ policy_id: Option>, + ) -> Result; + + //#[cfg(not(feature = "no_as"))] + async fn get_token(&self, user_data: TokenRequest) -> Result; + + async fn verify_token(&self, token: String) -> Result; ++ ++ async fn get_resource( ++ &self, ++ challenge: &str, ++ restful: &str, ++ resource: ResourceLocation, ++ token: &str, ++ ) -> Result; + } + + #[async_trait] + impl AttestationAgentAPIs for AttestationAgent { + // no_as generate by agent; has as generate by as +- async fn get_challenge(&self) -> Result { ++ async fn get_challenge(&self, user_data: Option>) -> Result { + #[cfg(feature = "no_as")] +- return self.generate_challenge_local().await; ++ return self.generate_challenge_local(user_data).await; + + #[cfg(not(feature = "no_as"))] +- return self.get_challenge_from_as().await; ++ return self.get_challenge_from_as(user_data).await; + } + async fn get_evidence(&self, user_data: EvidenceRequest) -> Result> { + Attester::default().tee_get_evidence(user_data).await + } +- async fn verify_evidence(&self, ++ async fn verify_evidence( ++ &self, + challenge: &[u8], + evidence: &[u8], +- _policy_id: Option> ++ _policy_id: Option>, + ) -> Result { + #[cfg(feature = "no_as")] + { +- let ret = Verifier::default().verify_evidence(challenge, evidence).await; ++ let ret = Verifier::default() ++ .verify_evidence(challenge, evidence) ++ .await; + match ret { + Ok(tee_claim) => Ok(tee_claim), + Err(e) => { +- log::error!("attestation agent verify evidence with no as failed:{:?}", e); ++ log::error!( ++ "attestation agent verify evidence with no as failed:{:?}", ++ e ++ ); + Err(e) +- }, ++ } + } + } + + #[cfg(not(feature = "no_as"))] + { +- let ret = self.verify_evidence_by_as(challenge, evidence, _policy_id).await; ++ let ret = self ++ .verify_evidence_by_as(challenge, evidence, _policy_id) ++ .await; + match ret { +- Ok(token) => { self.token_to_teeclaim(token).await }, ++ Ok(token) => self.token_to_teeclaim(token).await, + Err(e) => { + log::error!("verify evidence with as failed:{:?}", e); + Err(e) +- }, ++ } + } + } + } +- ++ + async fn get_token(&self, user_data: TokenRequest) -> Result { + #[cfg(feature = "no_as")] + { +- return Ok("no as in not supprot get token".to_string()); ++ return Ok("no as in not support get token".to_string()); + } + // todo token 有效期内,不再重新获取报告 + #[cfg(not(feature = "no_as"))] +@@ -147,7 +169,9 @@ impl AttestationAgentAPIs for AttestationAgent { + let challenge = &user_data.ev_req.challenge; + let policy_id = user_data.policy_id; + // request as +- return self.verify_evidence_by_as(challenge, &evidence, policy_id).await; ++ return self ++ .verify_evidence_by_as(challenge, &evidence, policy_id) ++ .await; + } + } + +@@ -156,11 +180,63 @@ impl AttestationAgentAPIs for AttestationAgent { + let result = verifier.verify(&token)?; + Ok(result) + } ++ ++ async fn get_resource( ++ &self, ++ challenge: &str, ++ restful: &str, ++ resource: ResourceLocation, ++ token: &str, ++ ) -> Result { ++ #[cfg(feature = "no_as")] ++ { ++ bail!("resource can only be gotten from attestation server!") ++ } ++ let rest = self ++ .get_resource_from_as(challenge, restful, resource, token) ++ .await?; ++ Ok(String::from_utf8(rest.to_vec())?) ++ } + } + + #[derive(Clone, Debug, Serialize, Deserialize)] +-struct AAConfig { +- svr_url: String, // Attestation Service url ++pub enum HttpProtocal { ++ Http { protocal: String }, ++ // If https is uesd, the root certificate must be provided. ++ Https { protocal: String, cert_root: String }, ++} ++ ++impl Default for HttpProtocal { ++ fn default() -> Self { ++ Self::Http { ++ protocal: "http".to_string(), ++ } ++ } ++} ++ ++impl HttpProtocal { ++ pub fn get_protocal(&self) -> String { ++ match self { ++ Self::Http { protocal } => protocal, ++ Self::Https { protocal, .. } => protocal, ++ } ++ .clone() ++ } ++ ++ pub fn get_cert_root(&self) -> Option { ++ match self { ++ Self::Https { cert_root, .. } => Some(cert_root.clone()), ++ _ => None, ++ } ++ } ++} ++ ++#[derive(Clone, Debug, Serialize, Deserialize)] ++pub struct AAConfig { ++ // Attestation Service url ++ pub svr_url: String, ++ // Http protocal, such as http or https ++ pub protocal: HttpProtocal, + token_cfg: TokenVerifyConfig, + } + +@@ -169,6 +245,7 @@ impl Default for AAConfig { + Self { + svr_url: String::from("http://127.0.0.1:8080"), + token_cfg: TokenVerifyConfig::default(), ++ protocal: HttpProtocal::default(), + } + } + } +@@ -191,23 +268,13 @@ impl TryFrom<&Path> for AAConfig { + + #[derive(Debug)] + pub struct AttestationAgent { +- config: AAConfig, ++ pub config: AAConfig, + as_client_sessions: SessionMap, + } + + #[allow(dead_code)] + impl AttestationAgent { +- pub fn new(conf_path: Option) -> Result { +- let config = match conf_path { +- Some(conf_path) => { +- log::info!("Attestation Agent config file:{conf_path}"); +- AAConfig::try_from(Path::new(&conf_path))? +- } +- None => { +- log::warn!("No Attestation Agent config file specified. Using a default config"); +- AAConfig::default() +- } +- }; ++ pub fn new(config: AAConfig) -> Result { + let as_client_sessions = SessionMap::new(); + let sessions = as_client_sessions.clone(); + tokio::spawn(async move { +@@ -225,13 +292,52 @@ impl AttestationAgent { + }) + } + ++ fn create_client(&self, protocal: HttpProtocal, cookie_store: bool) -> Result { ++ let client: Client = match protocal { ++ HttpProtocal::Http { protocal: _ } => reqwest::Client::builder() ++ .cookie_store(cookie_store) ++ .build()?, ++ HttpProtocal::Https { ++ protocal: _, ++ cert_root, ++ } => { ++ let cert = reqwest::Certificate::from_pem(cert_root.as_bytes())?; ++ reqwest::Client::builder() ++ .cookie_store(cookie_store) ++ .add_root_certificate(cert) ++ .build()? ++ } ++ }; ++ ++ Ok(client) ++ } ++ + #[cfg(not(feature = "no_as"))] +- async fn verify_evidence_by_as(&self, ++ async fn verify_evidence_by_as( ++ &self, + challenge: &[u8], + evidence: &[u8], +- policy_id: Option> ++ policy_id: Option>, + ) -> Result { +- let challenge = base64_url::encode(challenge); ++ let challenge = String::from_utf8_lossy(challenge).to_string(); ++ let mut session = match self ++ .as_client_sessions ++ .session_map ++ .get_async(&challenge) ++ .await ++ { ++ Some(entry) => entry, ++ None => { ++ // Challenge should be posted to service previously. ++ bail!("challenge '{}' does not exist in sessions", challenge); ++ } ++ }; ++ ++ // If the session is already attested, directly use the token. ++ if let Some(t) = session.get().token.as_ref() { ++ return Ok(t.clone()); ++ } ++ + let request_body = json!({ + "challenge": challenge, + "evidence": base64_url::encode(evidence), +@@ -239,21 +345,8 @@ impl AttestationAgent { + }); + let mut map = HeaderMap::new(); + map.insert("Content-Type", HeaderValue::from_static("application/json")); +- let mut client = reqwest::Client::new(); +- if !self.as_client_sessions.session_map.is_empty() { +- let session = self.as_client_sessions +- .session_map +- .get_async(&challenge) +- .await; +- match session { +- Some(entry) => { +- map.insert("as-challenge", HeaderValue::from_static("as")); +- client = entry.get().as_client.clone() +- }, +- None => log::info!("challenge is not as generate"), +- } +- } +- ++ map.insert("as-challenge", HeaderValue::from_static("as")); ++ let client = session.get().as_client.clone(); + let attest_endpoint = format!("{}/attestation", self.config.svr_url); + let res = client + .post(attest_endpoint) +@@ -265,11 +358,15 @@ impl AttestationAgent { + match res.status() { + reqwest::StatusCode::OK => { + let token = res.text().await?; ++ session.get_mut().token = Some(token.clone()); + log::debug!("Remote Attestation success, AS Response: {:?}", token); + Ok(token) + } + _ => { +- bail!("Remote Attestation Failed, AS Response: {:?}", res.text().await?); ++ bail!( ++ "Remote Attestation Failed, AS Response: {:?}", ++ res.text().await? ++ ); + } + } + } +@@ -279,36 +376,42 @@ impl AttestationAgent { + let ret = self.verify_token(token).await; + match ret { + Ok(token) => { +- let token_claim: serde_json::Value = serde_json::from_slice(token.claim.as_bytes())?; +- let tee_claim = json!({ +- "tee": token_claim["tee"].clone(), +- "payload" : token_claim["tcb_status"].clone(), +- }); +- Ok(tee_claim as TeeClaim) +- }, ++ let token_claim: serde_json::Value = ++ serde_json::from_slice(token.claim.as_bytes())?; ++ Ok(token_claim as TeeClaim) ++ } + Err(e) => { + log::error!("token to teeclaim failed:{:?}", e); + Err(e) +- }, ++ } + } + } + +- async fn generate_challenge_local(&self) -> Result { +- let mut nonce: [u8; 32] = [0; 32]; ++ async fn generate_challenge_local(&self, user_data: Option>) -> Result { ++ let mut nonce: Vec = vec![0; 32]; + rand::thread_rng().fill_bytes(&mut nonce); ++ if user_data != None { ++ nonce.append(&mut user_data.unwrap()); ++ } + Ok(base64_url::encode(&nonce)) + } +- async fn get_challenge_from_as(&self) -> Result { ++ ++ async fn get_challenge_from_as(&self, user_data: Option>) -> Result { + let challenge_endpoint = format!("{}/challenge", self.config.svr_url); +- let client = reqwest::Client::builder() +- .cookie_store(true) +- .build()?; ++ let client = self.create_client(self.config.protocal.clone(), true)?; ++ let data: Value; ++ if user_data.is_some() { ++ data = json!({"user_data":user_data.unwrap()}); ++ } else { ++ data = Value::Null; ++ } + let res = client + .get(challenge_endpoint) + .header("Content-Type", "application/json") +- .header("content-length", 0) ++ .body(data.to_string()) + .send() + .await?; ++ + let challenge = match res.status() { + reqwest::StatusCode::OK => { + let respone: String = res.json().await.unwrap(); +@@ -324,8 +427,50 @@ impl AttestationAgent { + self.as_client_sessions.insert(session); + Ok(challenge) + } +-} + ++ async fn get_resource_from_as( ++ &self, ++ challenge: &str, ++ restful: &str, ++ resource: ResourceLocation, ++ token: &str, ++ ) -> Result { ++ // Use the client in the attested session to ++ let session = match self ++ .as_client_sessions ++ .session_map ++ .get_async(challenge) ++ .await ++ { ++ Some(s) => s, ++ None => bail!("getting resource failed because the session is missing"), ++ }; ++ ++ let payload = GetResourceOp::TeeGet { resource }; ++ ++ let response = session ++ .get() ++ .as_client ++ .get(restful) ++ .bearer_auth(token) ++ .json(&payload) ++ .send() ++ .await?; ++ let resource = match response.status() { ++ reqwest::StatusCode::OK => { ++ let respone = response.bytes().await.unwrap(); ++ log::debug!("get resource success, AS Response: {:?}", respone); ++ respone ++ } ++ status => { ++ log::error!("get resource Failed, AS Response: {:?}", status); ++ bail!("get resource Failed") ++ } ++ }; ++ ++ Ok(resource) ++ } ++} + + // attestation agent c interface + use safer_ffi::prelude::*; +@@ -341,14 +486,20 @@ pub fn init_env_logger(c_level: Option<&repr_c::String>) { + } + + #[ffi_export] +-pub fn get_report(c_challenge: Option<&repr_c::Vec>, c_ima: &repr_c::TaggedOption) -> repr_c::Vec { ++pub fn get_report( ++ c_challenge: Option<&repr_c::Vec>, ++ c_ima: &repr_c::TaggedOption, ++) -> repr_c::Vec { + log::debug!("input challenge: {:?}, ima: {:?}", c_challenge, c_ima); + let ima = match c_ima { + repr_c::TaggedOption::None => false, + repr_c::TaggedOption::Some(ima) => *ima, + }; + let challenge = match c_challenge { +- None => {log::error!("challenge is null"); return Vec::new().into();}, ++ None => { ++ log::error!("challenge is null"); ++ return Vec::new().into(); ++ } + Some(cha) => cha.clone().to_vec(), + }; + +@@ -358,8 +509,12 @@ pub fn get_report(c_challenge: Option<&repr_c::Vec>, c_ima: &repr_c::TaggedO + ima: Some(ima), + }; + let rt = Runtime::new().unwrap(); ++ let config = AAConfig::try_from(Path::new(DEFAULT_AACONFIG_FILE)).unwrap(); + let fut = async { +- AttestationAgent::new(Some(DEFAULT_AACONFIG_FILE.to_string())).unwrap().get_evidence(input).await ++ AttestationAgent::new(config) ++ .unwrap() ++ .get_evidence(input) ++ .await + }; + let ret = rt.block_on(fut); + let report: Vec = match ret { +@@ -367,44 +522,83 @@ pub fn get_report(c_challenge: Option<&repr_c::Vec>, c_ima: &repr_c::TaggedO + Err(e) => { + log::error!("get report failed {:?}", e); + Vec::new() +- }, ++ } + }; + + report.into() + } + ++#[cfg(feature = "no_as")] ++use verifier::virtcca_parse_evidence; ++ ++#[cfg(feature = "no_as")] + #[ffi_export] +-pub fn verify_report(c_challenge: Option<&repr_c::Vec>, report: Option<&repr_c::Vec>) -> repr_c::String { ++pub fn parse_report(report: Option<&repr_c::Vec>) -> repr_c::String { ++ let report = match report { ++ None => { ++ log::error!("report is null"); ++ return "".to_string().into(); ++ } ++ Some(report) => report.clone().to_vec(), ++ }; ++ let rt = Runtime::new().unwrap(); ++ let fut = async { virtcca_parse_evidence(&report) }; ++ let ret = rt.block_on(fut); ++ ++ let ret = match ret { ++ Ok(claim) => { ++ log::debug!("claim: {:?}", claim); ++ claim.to_string() ++ } ++ Err(e) => { ++ log::error!("{e}"); ++ "".to_string() ++ } ++ }; ++ ++ return ret.into(); ++} ++ ++#[ffi_export] ++pub fn verify_report( ++ c_challenge: Option<&repr_c::Vec>, ++ report: Option<&repr_c::Vec>, ++) -> repr_c::String { + let challenge = match c_challenge { + None => { + log::error!("challenge is null"); + return "".to_string().into(); +- }, ++ } + Some(cha) => cha.clone().to_vec(), + }; + let report = match report { + None => { + log::error!("report is null"); + return "".to_string().into(); +- }, ++ } + Some(report) => report.clone().to_vec(), + }; + let rt = Runtime::new().unwrap(); +- let fut = async {AttestationAgent::new(Some(DEFAULT_AACONFIG_FILE.to_string())).unwrap().verify_evidence( +- &challenge, &report, None).await}; ++ let config = AAConfig::try_from(Path::new(DEFAULT_AACONFIG_FILE)).unwrap(); ++ let fut = async { ++ AttestationAgent::new(config) ++ .unwrap() ++ .verify_evidence(&challenge, &report, None) ++ .await ++ }; + let ret = rt.block_on(fut); +- ++ + let ret = match ret { + Ok(claim) => { + log::debug!("claim: {:?}", claim); + claim.to_string() +- }, +- Err(e) =>{ ++ } ++ Err(e) => { + log::error!("{e}"); + "".to_string() +- }, ++ } + }; +- ++ + return ret.into(); + } + +diff --git a/service/attestation/attestation-agent/agent/src/main.rs b/service/attestation/attestation-agent/agent/src/main.rs +index 62a4b4d..7cabf79 100644 +--- a/service/attestation/attestation-agent/agent/src/main.rs ++++ b/service/attestation/attestation-agent/agent/src/main.rs +@@ -9,26 +9,30 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use attestation_agent::{AttestationAgent, DEFAULT_AACONFIG_FILE}; +-mod restapi; +-use restapi::{get_challenge, get_evidence, verify_evidence, get_token, verify_token}; +- +-use anyhow::Result; ++use actix_web::{web, App, HttpResponse, HttpServer}; ++use anyhow::{bail, Result}; ++use attestation_agent::{ ++ restapi::{ ++ get_challenge, get_evidence, get_resource, get_token, verify_evidence, verify_token, ++ }, ++ AAConfig, AttestationAgent, HttpProtocal, DEFAULT_AACONFIG_FILE, ++}; ++use clap::{arg, command, Parser}; + use env_logger; +-use actix_web::{web, App, HttpServer, HttpResponse}; +-use std::{net::{SocketAddr, IpAddr, Ipv4Addr}, sync::Arc}; ++use std::{path::Path, sync::Arc}; + use tokio::sync::RwLock; +-use clap::{Parser, command, arg}; + +-const DEFAULT_SOCKETADDR: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081); ++const DEFAULT_SOCKETADDR: &str = "127.0.0.1:8081"; + + #[derive(Parser, Debug)] + #[command(version, about, long_about = None)] +-struct Cli { ++pub struct Cli { + /// Socket address to listen on +- #[arg(short, long, default_value_t = DEFAULT_SOCKETADDR)] +- socketaddr: SocketAddr, +- ++ #[arg(short, long, default_value_t = DEFAULT_SOCKETADDR.to_string())] ++ socketaddr: String, ++ /// Socket address connect to ++ #[arg(short = 'u', long, default_value_t = String::from(""))] ++ serverurl: String, + /// Load `AAConfig` from a configuration file like: + /// { + /// "svr_url": "http://127.0.0.1:8080", +@@ -39,6 +43,13 @@ struct Cli { + /// } + #[arg(short, long, default_value_t = DEFAULT_AACONFIG_FILE.to_string())] + config: String, ++ ++ #[arg(short = 'p', long = "protocol", default_value_t = String::from("http"))] ++ protocol: String, ++ ++ /// root certificate to verify peer ++ #[arg(short = 't', long = "cert_root", default_value_t = String::from(""))] ++ cert_root: String, + } + + #[actix_web::main] +@@ -46,8 +57,29 @@ async fn main() -> Result<()> { + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + let cli = Cli::parse(); +- let server = AttestationAgent::new(Some(cli.config)).unwrap(); ++ // Load config content from file. ++ let mut config = AAConfig::try_from(Path::new(&cli.config))?; + ++ // Override configurations if set by command line tool. ++ match cli.protocol.as_ref() { ++ "http" => {} ++ "https" => { ++ config.protocal = HttpProtocal::Https { ++ protocal: "https".to_string(), ++ cert_root: std::fs::read_to_string(cli.cert_root)?, ++ } ++ } ++ _ => { ++ bail!("Invalid http protocal!"); ++ } ++ } ++ ++ // Override the listening url. ++ if cli.serverurl != "" { ++ config.svr_url = config.protocal.get_protocal() + "://" + &cli.serverurl.clone(); ++ } ++ ++ let server = AttestationAgent::new(config).unwrap(); + let service = web::Data::new(Arc::new(RwLock::new(server))); + HttpServer::new(move || { + App::new() +@@ -57,11 +89,12 @@ async fn main() -> Result<()> { + .service(verify_evidence) + .service(get_token) + .service(verify_token) ++ .service(get_resource) + .default_service(web::to(|| HttpResponse::NotFound())) + }) +- .bind((cli.socketaddr.ip().to_string(), cli.socketaddr.port()))? ++ .bind(cli.socketaddr)? + .run() + .await?; + + Ok(()) +-} +\ No newline at end of file ++} +diff --git a/service/attestation/attestation-agent/agent/src/restapi/mod.rs b/service/attestation/attestation-agent/agent/src/restapi/mod.rs +index 2745443..3cb3a8c 100644 +--- a/service/attestation/attestation-agent/agent/src/restapi/mod.rs ++++ b/service/attestation/attestation-agent/agent/src/restapi/mod.rs +@@ -9,27 +9,52 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use attestation_agent::{AttestationAgent, AttestationAgentAPIs, TokenRequest, AgentError}; +-use attestation_agent::result::Result; +- +-use actix_web::{ post, get, web, HttpResponse}; ++use crate::result::Result; ++use crate::{AgentError, AttestationAgent, AttestationAgentAPIs, TokenRequest}; ++use actix_web::{get, post, web, HttpResponse}; ++use attestation_types::resource::ResourceLocation; + use attester::EvidenceRequest; ++use log; + use serde::{Deserialize, Serialize}; + use std::sync::Arc; + use tokio::sync::RwLock; +-use log; ++ ++#[cfg(feature = "no_as")] ++use crate::result::Error; ++#[cfg(feature = "no_as")] ++use anyhow::anyhow; + + #[derive(Deserialize, Serialize, Debug)] +-struct GetChallengeRequest {} ++struct GetChallengeRequest { ++ pub user_data: Vec, ++} + + #[get("/challenge")] + pub async fn get_challenge( +- //_request: web::Json, ++ request: Option>, + agent: web::Data>>, + ) -> Result { +- //let request = request.0; + log::debug!("get challenge request"); +- let challenge = agent.read().await.get_challenge().await ++ let user_data: Option>; ++ if request.is_some() { ++ user_data = Some(request.unwrap().0.user_data); ++ if user_data.clone().unwrap().len() > 32 { ++ return Err(crate::result::Error::Agent { ++ source: AgentError::ChallengeError(String::from( ++ "user data length should not exceed 32", ++ )), ++ }); ++ } ++ log::debug!("user data is {:?}", user_data.clone().unwrap()); ++ } else { ++ log::debug!("user data is None"); ++ user_data = Option::None; ++ } ++ let challenge = agent ++ .read() ++ .await ++ .get_challenge(user_data) ++ .await + .map_err(|err| AgentError::ChallengeError(err.to_string()))?; + + Ok(HttpResponse::Ok().body(challenge)) +@@ -49,18 +74,20 @@ pub async fn get_evidence( + ) -> Result { + let request = request.0; + log::debug!("get evidence request: {:?}", request); +- let challenge = base64_url::decode(&request.challenge) +- .map_err(|err|AgentError::DecodeError(err.to_string()))?; ++ let challenge = request.challenge; + let uuid = request.uuid; +- let ima = request.ima; ++ let ima = request.ima; + let input = EvidenceRequest { + uuid: uuid, +- challenge: challenge, ++ challenge: challenge.into_bytes(), + ima: ima, + }; +- let evidence = agent.read().await.get_evidence(input).await +- .map_err(|err|AgentError::GetEvidenceError(err.to_string()))?; +- ++ let evidence = agent ++ .read() ++ .await ++ .get_evidence(input) ++ .await ++ .map_err(|err| AgentError::GetEvidenceError(err.to_string()))?; + + Ok(HttpResponse::Ok().body(evidence)) + } +@@ -78,13 +105,16 @@ pub async fn verify_evidence( + ) -> Result { + let request = request.0; + log::debug!("verify evidence request: {:?}", request); +- let challenge = base64_url::decode(&request.challenge) +- .map_err(|err|AgentError::DecodeError(err.to_string()))?; ++ let challenge = request.challenge; + let evidence = request.evidence; +- let policy_id = request.policy_id; +- +- let claim = agent.read().await.verify_evidence(&challenge, evidence.as_bytes(), policy_id).await +- .map_err(|err|AgentError::VerifyEvidenceError(err.to_string()))?; ++ let policy_id = request.policy_id; ++ ++ let claim = agent ++ .read() ++ .await ++ .verify_evidence(&challenge.into_bytes(), evidence.as_bytes(), policy_id) ++ .await ++ .map_err(|err| AgentError::VerifyEvidenceError(err.to_string()))?; + let string_claim = serde_json::to_string(&claim)?; + + Ok(HttpResponse::Ok().body(string_claim)) +@@ -105,14 +135,13 @@ pub async fn get_token( + ) -> Result { + let request = request.0; + log::debug!("get token request: {:?}", request); +- let challenge = base64_url::decode(&request.challenge) +- .map_err(|err|AgentError::DecodeError(err.to_string()))?; ++ let challenge = request.challenge; + let uuid = request.uuid; + let ima = request.ima; +- let policy_id = request.policy_id; ++ let policy_id = request.policy_id; + let ev = EvidenceRequest { + uuid: uuid, +- challenge: challenge, ++ challenge: challenge.into_bytes(), + ima: ima, + }; + let input = TokenRequest { +@@ -120,8 +149,12 @@ pub async fn get_token( + policy_id: policy_id, + }; + +- let token = agent.read().await.get_token(input).await +- .map_err(|err|AgentError::GetTokenError(err.to_string()))?; ++ let token = agent ++ .read() ++ .await ++ .get_token(input) ++ .await ++ .map_err(|err| AgentError::GetTokenError(err.to_string()))?; + + Ok(HttpResponse::Ok().body(token)) + } +@@ -138,10 +171,70 @@ pub async fn verify_token( + let request = request.0; + log::debug!("verify token request: {:?}", request); + +- let claim = agent.read().await.verify_token(request.token).await +- .map_err(|err|AgentError::VerifyTokenError(err.to_string()))?; ++ let claim = agent ++ .read() ++ .await ++ .verify_token(request.token) ++ .await ++ .map_err(|err| AgentError::VerifyTokenError(err.to_string()))?; + let string_claim = serde_json::to_string(&claim) +- .map_err(|err|AgentError::VerifyTokenError(err.to_string()))?; ++ .map_err(|err| AgentError::VerifyTokenError(err.to_string()))?; + + Ok(HttpResponse::Ok().body(string_claim)) +-} +\ No newline at end of file ++} ++ ++#[derive(Deserialize, Serialize, Debug)] ++struct GetResourceRequest { ++ uuid: String, ++ challenge: Option, ++ ima: Option, ++ policy_id: Option>, ++ resource: ResourceLocation, ++} ++ ++#[get("/resource/storage")] ++pub async fn get_resource( ++ request: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ let agent = agent.read().await; ++ ++ // If user provides the challenge number, use the challenge to find session. ++ let challenge = match request.challenge.as_ref() { ++ Some(c) => c.clone(), ++ None => agent ++ .get_challenge(None) ++ .await ++ .map_err(|err| AgentError::ChallengeError(err.to_string()))?, ++ }; ++ ++ // base64 encoded challenge ++ let ev_req = EvidenceRequest { ++ uuid: request.uuid.clone(), ++ challenge: challenge.clone().into_bytes(), ++ ima: request.ima, ++ }; ++ ++ let token_req = TokenRequest { ++ ev_req, ++ policy_id: request.policy_id.clone(), ++ }; ++ ++ #[cfg(feature = "no_as")] ++ { ++ return Err(Error::Other(anyhow!( ++ "Resource can only be got from attestation server." ++ ))); ++ } ++ ++ let token = agent.get_token(token_req).await?; ++ ++ let restful = format!("{}/resource/storage", agent.config.svr_url,); ++ ++ let resource = agent ++ .get_resource(&challenge, &restful, request.resource.clone(), &token) ++ .await ++ .map_err(|err| AgentError::GetTokenError(err.to_string()))?; ++ ++ Ok(HttpResponse::Ok().body(resource)) ++} +diff --git a/service/attestation/attestation-agent/agent/src/result/mod.rs b/service/attestation/attestation-agent/agent/src/result/mod.rs +index a33be0c..b5dd02b 100644 +--- a/service/attestation/attestation-agent/agent/src/result/mod.rs ++++ b/service/attestation/attestation-agent/agent/src/result/mod.rs +@@ -47,6 +47,9 @@ pub enum Error { + #[error("Attestation Agent error:{0}")] + AttestationAgentError(String), + ++ #[error("Client is missing, challenge is invalid.")] ++ ClientMissing, ++ + #[error(transparent)] + Other(#[from] anyhow::Error), + } +diff --git a/service/attestation/attestation-agent/agent/src/session.rs b/service/attestation/attestation-agent/agent/src/session.rs +index 5e1c1fc..d4896df 100644 +--- a/service/attestation/attestation-agent/agent/src/session.rs ++++ b/service/attestation/attestation-agent/agent/src/session.rs +@@ -9,28 +9,27 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use actix_web::cookie::{time::{Duration, OffsetDateTime}}; +-use scc::HashMap; ++use actix_web::cookie::time::{Duration, OffsetDateTime}; + use anyhow::Result; ++use scc::HashMap; + + #[derive(Debug, Clone)] + pub struct Session { + pub challenge: String, + pub as_client: reqwest::Client, + timeout: OffsetDateTime, +- // pub token: Option, ++ /// If token is not none, this session is already attested by attestation server. Then directly use the token. ++ pub token: Option, + } + + impl Session { + pub fn new(challenge: String, as_client: reqwest::Client, timeout_m: i64) -> Result { +- + let timeout = OffsetDateTime::now_utc() + Duration::minutes(timeout_m); +- // let token = None; + Ok(Session { + challenge, + as_client, + timeout, +- // token, ++ token: None, + }) + } + pub fn is_expired(&self) -> bool { +@@ -52,4 +51,4 @@ impl SessionMap { + pub fn insert(&self, session: Session) { + let _ = self.session_map.insert(session.challenge.clone(), session); + } +-} +\ No newline at end of file ++} +diff --git a/service/attestation/attestation-agent/attester/Cargo.toml b/service/attestation/attestation-agent/attester/Cargo.toml +index a7dae2a..2c6a012 100644 +--- a/service/attestation/attestation-agent/attester/Cargo.toml ++++ b/service/attestation/attestation-agent/attester/Cargo.toml +@@ -4,8 +4,8 @@ version = "0.1.0" + edition = "2021" + + [features] +-itrustee-attester = [ "base64-url", "rand" ] +-virtcca-attester = [] ++itrustee-attester = ["base64-url", "rand"] ++virtcca-attester = ["base64-url"] + + [dependencies] + anyhow.workspace = true +diff --git a/service/attestation/attestation-agent/attester/src/itrustee/mod.rs b/service/attestation/attestation-agent/attester/src/itrustee/mod.rs +index 3fde5f7..22b6afd 100644 +--- a/service/attestation/attestation-agent/attester/src/itrustee/mod.rs ++++ b/service/attestation/attestation-agent/attester/src/itrustee/mod.rs +@@ -11,15 +11,15 @@ + */ + + //! itrustee tee plugin +-//! ++//! + //! Call the hardware sdk or driver to get the specific evidence + + use anyhow::*; +-use serde_json; +-use std::path::Path; +-use serde::{Serialize, Deserialize}; + use base64_url; + use log; ++use serde::{Deserialize, Serialize}; ++use serde_json; ++use std::path::Path; + + use crate::EvidenceRequest; + +@@ -59,10 +59,24 @@ struct ItrusteeInput { + handler: String, + payload: ReportInputPayload, + } +- ++const MAX_CHALLENGE_LEN: usize = 64; + fn itrustee_get_evidence(user_data: EvidenceRequest) -> Result { ++ let challenge = base64_url::decode(&user_data.challenge)?; ++ let len = challenge.len(); ++ if len <= 0 || len > MAX_CHALLENGE_LEN { ++ log::error!( ++ "challenge len is error, expecting 0 < len <= {}, got {}", ++ MAX_CHALLENGE_LEN, ++ len ++ ); ++ bail!( ++ "challenge len is error, expecting 0 < len <= {}, got {}", ++ MAX_CHALLENGE_LEN, ++ len ++ ); ++ } + let payload = ReportInputPayload { +- nonce: base64_url::encode(&user_data.challenge), ++ nonce: String::from_utf8(user_data.challenge)?, + uuid: user_data.uuid, + with_tcb: false, + request_key: true, +diff --git a/service/attestation/attestation-agent/attester/src/lib.rs b/service/attestation/attestation-agent/attester/src/lib.rs +index 3c02946..6dd549d 100644 +--- a/service/attestation/attestation-agent/attester/src/lib.rs ++++ b/service/attestation/attestation-agent/attester/src/lib.rs +@@ -11,13 +11,12 @@ + */ + + //! attester +-//! ++//! + //! This crate provides unified APIs to get TEE evidence. + + use anyhow::*; + use async_trait::async_trait; + use log; +-use attestation_types::{TeeType, Evidence}; + + #[cfg(feature = "itrustee-attester")] + mod itrustee; +@@ -42,22 +41,17 @@ pub trait AttesterAPIs { + #[derive(Default)] + pub struct Attester {} + +- +-const MAX_CHALLENGE_LEN: usize = 64; +- + #[async_trait] + impl AttesterAPIs for Attester { + async fn tee_get_evidence(&self, _user_data: EvidenceRequest) -> Result> { +- let len = _user_data.challenge.len(); +- if len <= 0 || len > MAX_CHALLENGE_LEN { +- log::error!("challenge len is error, expecting 0 < len <= {}, got {}", MAX_CHALLENGE_LEN, len); +- bail!("challenge len is error, expecting 0 < len <= {}, got {}", MAX_CHALLENGE_LEN, len); +- } ++ + #[cfg(feature = "itrustee-attester")] + if itrustee::detect_platform() { +- let evidence = itrustee::ItrusteeAttester::default().tee_get_evidence(_user_data).await?; +- let aa_evidence = Evidence { +- tee: TeeType::Itrustee, ++ let evidence = itrustee::ItrusteeAttester::default() ++ .tee_get_evidence(_user_data) ++ .await?; ++ let aa_evidence = attestation_types::Evidence { ++ tee: attestation_types::TeeType::Itrustee, + evidence: evidence, + }; + let evidence = serde_json::to_vec(&aa_evidence)?; +@@ -66,14 +60,16 @@ impl AttesterAPIs for Attester { + } + #[cfg(feature = "virtcca-attester")] + if virtcca::detect_platform() { +- let evidence = virtcca::VirtccaAttester::default().tee_get_evidence(_user_data).await?; +- let aa_evidence = Evidence { +- tee: TeeType::Virtcca, ++ let evidence = virtcca::VirtccaAttester::default() ++ .tee_get_evidence(_user_data) ++ .await?; ++ let aa_evidence = attestation_types::Evidence { ++ tee: attestation_types::TeeType::Virtcca, + evidence: evidence, + }; + let evidence = serde_json::to_vec(&aa_evidence)?; + return Ok(evidence); + } +- bail!("unkown tee platform"); ++ bail!("unknown tee platform"); + } +-} +\ No newline at end of file ++} +diff --git a/service/attestation/attestation-agent/attester/src/virtcca/mod.rs b/service/attestation/attestation-agent/attester/src/virtcca/mod.rs +index c981d91..86f0061 100644 +--- a/service/attestation/attestation-agent/attester/src/virtcca/mod.rs ++++ b/service/attestation/attestation-agent/attester/src/virtcca/mod.rs +@@ -11,24 +11,23 @@ + */ + + //! virtcca tee plugin +-//! ++//! + //! Call the hardware sdk or driver to get the specific evidence + +-use anyhow::{Result, bail}; +-use std::path::Path; +-use log; ++use anyhow::{bail, Result}; + use attestation_types::VirtccaEvidence; ++use log; ++use std::path::Path; + +-use crate::EvidenceRequest; ++use self::virtcca::{get_attestation_token, get_dev_cert, tsi_new_ctx}; + use crate::virtcca::virtcca::tsi_free_ctx; +-use self::virtcca::{tsi_new_ctx, get_attestation_token, get_dev_cert}; ++use crate::EvidenceRequest; + + mod virtcca; + + #[derive(Debug, Default)] + pub struct VirtccaAttester {} + +- + impl VirtccaAttester { + pub async fn tee_get_evidence(&self, user_data: EvidenceRequest) -> Result { + let evidence = virtcca_get_token(user_data)?; +@@ -41,12 +40,24 @@ pub fn detect_platform() -> bool { + Path::new("/dev/tsi").exists() + } + +- ++const MAX_CHALLENGE_LEN: usize = 64; + fn virtcca_get_token(user_data: EvidenceRequest) -> Result { ++ let mut challenge = base64_url::decode(&user_data.challenge)?; ++ let len = challenge.len(); ++ if len <= 0 || len > MAX_CHALLENGE_LEN { ++ log::error!( ++ "challenge len is error, expecting 0 < len <= {}, got {}", ++ MAX_CHALLENGE_LEN, ++ len ++ ); ++ bail!( ++ "challenge len is error, expecting 0 < len <= {}, got {}", ++ MAX_CHALLENGE_LEN, ++ len ++ ); ++ } + unsafe { + let ctx = tsi_new_ctx(); +- +- let mut challenge = user_data.challenge.to_vec(); + let p_challenge = challenge.as_mut_ptr() as *mut ::std::os::raw::c_uchar; + let challenge_len = challenge.len() as usize; + let mut token = Vec::new(); +@@ -78,7 +89,9 @@ fn virtcca_get_token(user_data: EvidenceRequest) -> Result { + None => false, + }; + let ima_log = match with_ima { +- true => Some(std::fs::read("/sys/kernel/security/ima/binary_runtime_measurements").unwrap()), ++ true => { ++ Some(std::fs::read("/sys/kernel/security/ima/binary_runtime_measurements").unwrap()) ++ } + false => None, + }; + +@@ -90,4 +103,4 @@ fn virtcca_get_token(user_data: EvidenceRequest) -> Result { + let _ = tsi_free_ctx(ctx); + Ok(evidence) + } +-} +\ No newline at end of file ++} +diff --git a/service/attestation/attestation-agent/attester/src/virtcca/virtcca.rs b/service/attestation/attestation-agent/attester/src/virtcca/virtcca.rs +index 33318c7..82fd6a0 100644 +--- a/service/attestation/attestation-agent/attester/src/virtcca/virtcca.rs ++++ b/service/attestation/attestation-agent/attester/src/virtcca/virtcca.rs +@@ -85,11 +85,7 @@ extern "C" { + } + extern "C" { + #[allow(dead_code)] +- pub fn get_version( +- ctx: *mut tsi_ctx, +- major: *mut wchar_t, +- minor: *mut wchar_t, +- ) -> wchar_t; ++ pub fn get_version(ctx: *mut tsi_ctx, major: *mut wchar_t, minor: *mut wchar_t) -> wchar_t; + } + extern "C" { + pub fn get_attestation_token( +diff --git a/service/attestation/attestation-agent/c_header/example.c b/service/attestation/attestation-agent/c_header/example.c +new file mode 100644 +index 0000000..a75d018 +--- /dev/null ++++ b/service/attestation/attestation-agent/c_header/example.c +@@ -0,0 +1,90 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++// gcc example.c -o aa-test -L. -lattestation_agent -lcrypto ++#include "rust_attestation_agent.h" ++ ++#include ++#include ++#include ++#include ++#include ++ ++#define CHALLENGE_LEN 32 ++#define TEST_THREAD_NUM 1 ++ ++void *thread_proc(void *arg) ++{ ++ // step1: generate random numbers ++ uint8_t nonce[CHALLENGE_LEN]; ++ RAND_priv_bytes(nonce, CHALLENGE_LEN); ++ Vec_uint8_t challenge = { ++ .ptr = (uint8_t *)&nonce, ++ .len = CHALLENGE_LEN, ++ .cap = CHALLENGE_LEN, ++ }; ++ ++ // step2: define ima input param ++ Tuple2_bool_bool_t ima = { // define input ima = Some(false) ++ ._0 = true, ++ ._1 = false, // true: enable to get report with ima ++ }; ++ ++ // step3: get report ++ Vec_uint8_t report = get_report(&challenge, &ima); ++ Vec_uint8_t claim; ++ if (report.len != 0) { ++ report.ptr[report.len] = '\0'; // rust return string has no '\0' ++ printf("get report success, report:%s\n", report.ptr); ++ ++ // parse report ++ // Vec_uint8_t claim_no_verify = parse_report(&report); ++ // if (claim_no_verify.len != 0) { ++ // claim_no_verify.ptr[claim_no_verify.len] = '\0'; ++ // printf("parse report success: %s\n", claim_no_verify.ptr); ++ // } ++ // free_rust_vec(claim_no_verify); ++ ++ // step4: verify report ++ claim = verify_report(&challenge, &report); ++ } ++ ++ if (claim.len != 0) { ++ claim.ptr[claim.len] = '\0'; // rust return string has no '\0' ++ printf("verify report, return claim:%s\n", claim.ptr); ++ } ++ ++ // step5: free rust resource ++ free_rust_vec(report); ++ free_rust_vec(claim); ++} ++int main() ++{ ++ char *level = "info"; ++ Vec_uint8_t log_level = { ++ .ptr = (uint8_t *)level, ++ .len = strlen(level), ++ .cap = strlen(level), ++ }; ++ init_env_logger(&log_level); ++ ++ pthread_t tids[TEST_THREAD_NUM]; ++ for (int i = 0; i < TEST_THREAD_NUM; i++) { ++ pthread_create(&tids[i], NULL, thread_proc, NULL); ++ } ++ ++ for (int i = 0; i < TEST_THREAD_NUM; i++) { ++ pthread_join(tids[i], NULL); ++ } ++ ++ return 0; ++} +diff --git a/service/attestation/attestation-agent/c_header/rust_attestation_agent.h b/service/attestation/attestation-agent/c_header/rust_attestation_agent.h +new file mode 100644 +index 0000000..9c1a18f +--- /dev/null ++++ b/service/attestation/attestation-agent/c_header/rust_attestation_agent.h +@@ -0,0 +1,81 @@ ++/*! \file */ ++/******************************************* ++ * * ++ * File auto-generated by `::safer_ffi`. * ++ * * ++ * Do not manually edit this file. * ++ * * ++ *******************************************/ ++ ++#ifndef __RUST_ATTESTATION_AGENT__ ++#define __RUST_ATTESTATION_AGENT__ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++ ++#include ++#include ++ ++/** \brief ++ * Same as [`Vec`][`rust::Vec`], but with guaranteed `#[repr(C)]` layout ++ */ ++typedef struct Vec_uint8 { ++ /** */ ++ uint8_t * ptr; ++ ++ /** */ ++ size_t len; ++ ++ /** */ ++ size_t cap; ++} Vec_uint8_t; ++ ++/** */ ++void ++free_rust_vec ( ++ Vec_uint8_t vec); ++ ++ ++#include ++ ++/** \brief ++ * Simplified for lighter documentation, but the actual impls ++ * range from `Tuple1` up to `Tuple6`. ++ */ ++typedef struct Tuple2_bool_bool { ++ /** */ ++ bool _0; ++ ++ /** */ ++ bool _1; ++} Tuple2_bool_bool_t; ++ ++/** */ ++Vec_uint8_t ++get_report ( ++ Vec_uint8_t const * c_challenge, ++ Tuple2_bool_bool_t const * c_ima); ++ ++/** */ ++void ++init_env_logger ( ++ Vec_uint8_t const * c_level); ++ ++/** */ ++Vec_uint8_t ++parse_report ( ++ Vec_uint8_t const * report); ++ ++/** */ ++Vec_uint8_t ++verify_report ( ++ Vec_uint8_t const * c_challenge, ++ Vec_uint8_t const * report); ++ ++ ++#ifdef __cplusplus ++} /* extern \"C\" */ ++#endif ++ ++#endif /* __RUST_ATTESTATION_AGENT__ */ +diff --git a/service/attestation/attestation-agent/token/Cargo.toml b/service/attestation/attestation-agent/token/Cargo.toml +index 916f2a2..d4e8c0d 100644 +--- a/service/attestation/attestation-agent/token/Cargo.toml ++++ b/service/attestation/attestation-agent/token/Cargo.toml +@@ -11,4 +11,4 @@ serde.workspace = true + serde_json.workspace = true + anyhow.workspace = true + attestation-types.workspace = true +-thiserror.workspace = true +\ No newline at end of file ++thiserror.workspace = true +diff --git a/service/attestation/attestation-agent/token/src/lib.rs b/service/attestation/attestation-agent/token/src/lib.rs +index 37aab9e..aea0293 100644 +--- a/service/attestation/attestation-agent/token/src/lib.rs ++++ b/service/attestation/attestation-agent/token/src/lib.rs +@@ -9,10 +9,10 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use std::path::Path; +-use serde::{Deserialize, Serialize}; +-use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation }; + use attestation_types::Claims; ++use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation}; ++use serde::{Deserialize, Serialize}; ++use std::path::Path; + + #[derive(thiserror::Error, Debug)] + pub enum VerifyError { +@@ -28,9 +28,9 @@ pub enum VerifyError { + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct TokenVerifyConfig { +- pub cert: String, // Attestation Service cert to verify jwt token signature +- pub iss: String, // Attestation Service name +- //pub root_cert: String, ++ pub cert: String, // Attestation Service cert to verify jwt token signature ++ pub iss: String, // Attestation Service name ++ //pub root_cert: String, + } + + impl Default for TokenVerifyConfig { +@@ -41,13 +41,11 @@ impl Default for TokenVerifyConfig { + } + } + } +-pub struct TokenVerifier +-{ ++pub struct TokenVerifier { + pub config: TokenVerifyConfig, + } + +-impl Default for TokenVerifier +-{ ++impl Default for TokenVerifier { + fn default() -> Self { + TokenVerifier { + config: TokenVerifyConfig::default(), +@@ -66,32 +64,33 @@ impl TokenVerifier { + pub fn new(config: TokenVerifyConfig) -> Result { + Ok(TokenVerifier { config }) + } +- fn support_rs(alg: &Algorithm) -> bool +- { +- if *alg == Algorithm::RS256 || *alg == Algorithm::RS384 || *alg == Algorithm::RS512{ ++ fn support_rs(alg: &Algorithm) -> bool { ++ if *alg == Algorithm::RS256 || *alg == Algorithm::RS384 || *alg == Algorithm::RS512 { + return true; + } + return false; + } +- fn support_ps(alg: &Algorithm) -> bool +- { ++ fn support_ps(alg: &Algorithm) -> bool { + if *alg == Algorithm::PS256 || *alg == Algorithm::PS384 || *alg == Algorithm::PS512 { + return true; + } + return false; + } +- pub fn verify( +- &self, +- token: &String +- ) -> Result { ++ pub fn verify(&self, token: &String) -> Result { + let header = decode_header(&token)?; + let alg: Algorithm = header.alg; + + if !Self::support_rs(&alg) && !Self::support_ps(&alg) { +- return Err(VerifyError::UnknownAlg(format!("unknown algrithm {:?}", alg))); ++ return Err(VerifyError::UnknownAlg(format!( ++ "unknown algorithm {:?}", ++ alg ++ ))); + } + if !Path::new(&self.config.cert).exists() { +- return Err(VerifyError::CertNotExist(format!("{:?} not exist", self.config.cert))); ++ return Err(VerifyError::CertNotExist(format!( ++ "{:?} not exist", ++ self.config.cert ++ ))); + } + let cert = std::fs::read(&self.config.cert).unwrap(); + +diff --git a/service/attestation/attestation-client/Cargo.toml b/service/attestation/attestation-client/Cargo.toml +new file mode 100644 +index 0000000..e5a068a +--- /dev/null ++++ b/service/attestation/attestation-client/Cargo.toml +@@ -0,0 +1,16 @@ ++[package] ++name = "attestation-client" ++version = "0.1.0" ++edition = "2021" ++ ++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html ++ ++[dependencies] ++async-trait = "0.1.85" ++clap = { version = "4.5.24", features = ["derive", "std"] } ++reqwest = { version = "0.12.5", features = ["blocking", "cookies", "json"] } ++thiserror = "2.0.10" ++http = "1.2.0" ++tokio = { version = "1.43.0", features = ["full"] } ++attestation-types = { path = "../attestation-types" } ++serde_json = "1.0.135" +diff --git a/service/attestation/attestation-client/src/client.rs b/service/attestation/attestation-client/src/client.rs +new file mode 100644 +index 0000000..2c0f139 +--- /dev/null ++++ b/service/attestation/attestation-client/src/client.rs +@@ -0,0 +1,53 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! Common web request client ++ ++use crate::error::Result; ++use reqwest::Client; ++ ++const DEFAULT_AS_ADDRESS: &str = "127.0.0.1:8080"; ++ ++pub(crate) enum Protocal { ++ Http { svr: String }, ++ // Https { svr: String, cert: String }, ++} ++ ++pub(crate) struct AsClient { ++ protocal: Protocal, ++ client: Client, ++} ++ ++impl AsClient { ++ pub(crate) fn new(cookie_store: bool, protocal: Protocal) -> Result { ++ let client = match &protocal { ++ Protocal::Http { svr } => Client::builder().cookie_store(cookie_store).build()?, ++ }; ++ ++ Ok(Self { protocal, client }) ++ } ++ ++ pub(crate) fn default() -> Self { ++ let svr = std::env::var("AS_ADDRESS").unwrap_or(DEFAULT_AS_ADDRESS.to_string()); ++ AsClient::new(false, Protocal::Http { svr }).unwrap() ++ } ++ ++ pub(crate) fn base_url(&self) -> String { ++ match &self.protocal { ++ Protocal::Http { svr } => format!("http://{}", svr), ++ } ++ } ++ ++ pub(crate) fn client(&self) -> Client { ++ self.client.clone() ++ } ++} +diff --git a/service/attestation/attestation-client/src/error.rs b/service/attestation/attestation-client/src/error.rs +new file mode 100644 +index 0000000..2952de2 +--- /dev/null ++++ b/service/attestation/attestation-client/src/error.rs +@@ -0,0 +1,23 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use thiserror::Error; ++ ++pub type Result = std::result::Result; ++ ++#[derive(Error, Debug)] ++#[non_exhaustive] ++pub enum ClientError { ++ #[error("reqwest error: {0}")] ++ ReqwestError(#[from] reqwest::Error), ++ #[error("Http error {0}: {1}")] ++ HttpError(String, http::status::StatusCode), ++} +diff --git a/service/attestation/attestation-client/src/main.rs b/service/attestation/attestation-client/src/main.rs +new file mode 100644 +index 0000000..a779a71 +--- /dev/null ++++ b/service/attestation/attestation-client/src/main.rs +@@ -0,0 +1,52 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! This is the client tool for attestation service, which encapsulates frequently-used web request ++//! into the sub-command of the command line tool. ++ ++mod client; ++mod error; ++mod resource; ++mod resource_policy; ++ ++use crate::resource::ResourceArgs; ++use crate::resource_policy::ResourcePolicyArgs; ++use clap::{Parser, Subcommand}; ++use client::AsClient; ++ ++/// A fictional versioning CLI ++#[derive(Debug, Parser)] // requires `derive` feature ++#[command(name = "attestation-client")] ++#[command(about = "Web client of attestation service", long_about = None)] ++struct Cli { ++ #[command(subcommand)] ++ command: Commands, ++} ++ ++#[derive(Debug, Subcommand)] ++enum Commands { ++ Resource(ResourceArgs), ++ ResourcePolicy(ResourcePolicyArgs), ++} ++ ++fn main() { ++ let args = Cli::parse(); ++ let client = AsClient::default(); ++ match args.command { ++ Commands::Resource(args) => { ++ args.process(client); ++ } ++ Commands::ResourcePolicy(args) => { ++ args.process(client); ++ } ++ } ++} +diff --git a/service/attestation/attestation-client/src/resource/client.rs b/service/attestation/attestation-client/src/resource/client.rs +new file mode 100644 +index 0000000..e0dcb08 +--- /dev/null ++++ b/service/attestation/attestation-client/src/resource/client.rs +@@ -0,0 +1,234 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! Implement web request for resource to attestation service ++ ++use crate::client::AsClient; ++use crate::error::{ClientError, Result}; ++use attestation_types::{ ++ resource::ResourceLocation, ++ service::{GetResourceOp, SetResourceOp, SetResourceRequest}, ++}; ++use reqwest::Client; ++ ++pub(crate) struct ResourceClient { ++ client: AsClient, ++} ++ ++impl ResourceClient { ++ pub(crate) fn new(client: AsClient) -> Self { ++ Self { client } ++ } ++ ++ fn endpoint(&self) -> String { ++ format!("{}/resource/storage", self.client.base_url()) ++ } ++ ++ fn client(&self) -> Client { ++ self.client.client() ++ } ++ ++ pub(crate) async fn vendor_get_resource(&self, vendor: &str) -> Result> { ++ let payload = GetResourceOp::VendorGet { ++ vendor: vendor.to_string(), ++ }; ++ ++ let res = self ++ .client() ++ .get(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.json().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to get resource: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ ++ pub(crate) async fn vendor_add_resource( ++ &self, ++ vendor: &str, ++ path: &str, ++ content: &str, ++ policy: &Vec, ++ ) -> Result { ++ let op = SetResourceOp::Add { ++ content: content.to_string(), ++ policy: policy.clone(), ++ }; ++ let payload = SetResourceRequest { ++ op, ++ resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), ++ }; ++ let res = self ++ .client() ++ .post(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to add resource: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ ++ pub(crate) async fn vendor_delete_resource(&self, vendor: &str, path: &str) -> Result { ++ let op = SetResourceOp::Delete; ++ let payload = SetResourceRequest { ++ op, ++ resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), ++ }; ++ let res = self ++ .client() ++ .post(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to delete resource: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ ++ pub(crate) async fn vendor_modify_resource( ++ &self, ++ vendor: &str, ++ path: &str, ++ content: &str, ++ ) -> Result { ++ let op = SetResourceOp::Modify { ++ content: content.to_string(), ++ }; ++ let payload = SetResourceRequest { ++ op, ++ resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), ++ }; ++ let res = self ++ .client() ++ .post(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to modify resource: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ ++ pub(crate) async fn vendor_bind_resource( ++ &self, ++ vendor: &str, ++ path: &str, ++ policy: &Vec, ++ ) -> Result { ++ let op = SetResourceOp::Bind { ++ policy: policy.clone(), ++ }; ++ let payload = SetResourceRequest { ++ op, ++ resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), ++ }; ++ let res = self ++ .client() ++ .post(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to bind resource: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ ++ pub(crate) async fn vendor_unbind_resource( ++ &self, ++ vendor: &str, ++ path: &str, ++ policy: &Vec, ++ ) -> Result { ++ let op = SetResourceOp::Unbind { ++ policy: policy.clone(), ++ }; ++ let payload = SetResourceRequest { ++ op, ++ resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), ++ }; ++ let res = self ++ .client() ++ .post(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to unbind resource: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++} ++ ++// async fn get_challenge() { ++// let challenge_endpoint = format!("{}/challenge", self.config.svr_url); ++// let client = self.create_client(self.config.protocal.clone(), true)?; ++// let res = client ++// .get(challenge_endpoint) ++// .header("Content-Type", "application/json") ++// .header("content-length", 0) ++// .send() ++// .await?; ++// let challenge = match res.status() { ++// reqwest::StatusCode::OK => { ++// let respone: String = res.json().await.unwrap(); ++// log::debug!("get challenge success, AS Response: {:?}", respone); ++// respone ++// } ++// status => { ++// log::error!("get challenge Failed, AS Response: {:?}", status); ++// bail!("get challenge Failed") ++// } ++// }; ++// } +diff --git a/service/attestation/attestation-client/src/resource/mod.rs b/service/attestation/attestation-client/src/resource/mod.rs +new file mode 100644 +index 0000000..d198ef4 +--- /dev/null ++++ b/service/attestation/attestation-client/src/resource/mod.rs +@@ -0,0 +1,129 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! Subcommand for getting or setting resource. ++//! ++ ++pub(crate) mod client; ++ ++use self::client::ResourceClient; ++use crate::client::AsClient; ++use clap::{Args, Subcommand}; ++ ++#[derive(Debug, Args)] ++#[command(args_conflicts_with_subcommands = true)] ++#[command(flatten_help = true)] ++pub(crate) struct ResourceArgs { ++ #[command(subcommand)] ++ pub(crate) command: ResourceCommand, ++} ++ ++#[derive(Debug, Subcommand)] ++pub(crate) enum ResourceCommand { ++ Get { ++ vendor: String, ++ }, ++ Add { ++ vendor: String, ++ path: String, ++ content: String, ++ policy: Vec, ++ }, ++ Delete { ++ vendor: String, ++ path: String, ++ }, ++ Modify { ++ vendor: String, ++ path: String, ++ content: String, ++ }, ++ BindPolicy { ++ vendor: String, ++ path: String, ++ policy: Vec, ++ }, ++ UnbindPolicy { ++ vendor: String, ++ path: String, ++ policy: Vec, ++ }, ++} ++ ++impl ResourceArgs { ++ pub(crate) fn process(&self, base_client: AsClient) { ++ self.command.dispatch(base_client); ++ } ++} ++ ++impl ResourceCommand { ++ fn dispatch(&self, base_client: AsClient) { ++ let client = ResourceClient::new(base_client); ++ let runtime = tokio::runtime::Runtime::new().unwrap(); ++ ++ match self { ++ ResourceCommand::Get { vendor } => { ++ let ret = runtime ++ .block_on(client.vendor_get_resource(vendor)) ++ .unwrap(); ++ println!("{:?}", ret); ++ } ++ ResourceCommand::Add { ++ vendor, ++ path, ++ content, ++ policy, ++ } => { ++ let ret = runtime ++ .block_on(client.vendor_add_resource(vendor, path, content, policy)) ++ .unwrap(); ++ println!("{:?}", ret); ++ } ++ ResourceCommand::Delete { vendor, path } => { ++ let ret = runtime ++ .block_on(client.vendor_delete_resource(vendor, path)) ++ .unwrap(); ++ println!("{:?}", ret); ++ } ++ ResourceCommand::Modify { ++ vendor, ++ path, ++ content, ++ } => { ++ let ret = runtime ++ .block_on(client.vendor_modify_resource(vendor, path, content)) ++ .unwrap(); ++ println!("{:?}", ret); ++ } ++ ResourceCommand::BindPolicy { ++ vendor, ++ path, ++ policy, ++ } => { ++ let ret = runtime ++ .block_on(client.vendor_bind_resource(vendor, path, policy)) ++ .unwrap(); ++ println!("{:?}", ret); ++ } ++ ResourceCommand::UnbindPolicy { ++ vendor, ++ path, ++ policy, ++ } => { ++ let ret = runtime ++ .block_on(client.vendor_unbind_resource(vendor, path, policy)) ++ .unwrap(); ++ println!("{:?}", ret); ++ } ++ } ++ } ++} +diff --git a/service/attestation/attestation-client/src/resource_policy/client.rs b/service/attestation/attestation-client/src/resource_policy/client.rs +new file mode 100644 +index 0000000..582a6bd +--- /dev/null ++++ b/service/attestation/attestation-client/src/resource_policy/client.rs +@@ -0,0 +1,190 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! Implement web request for resource policy to attestation service ++//! ++ ++use crate::client::AsClient; ++use crate::error::{ClientError, Result}; ++use attestation_types::{ ++ resource::policy::PolicyLocation, ++ service::{GetResourcePolicyOp, SetResourcePolicyOp}, ++}; ++use reqwest::Client; ++ ++pub(crate) struct ResourcePolicyClient { ++ client: AsClient, ++} ++ ++impl ResourcePolicyClient { ++ pub(crate) fn new(client: AsClient) -> Self { ++ Self { client } ++ } ++ ++ fn endpoint(&self) -> String { ++ format!("{}/resource/policy", self.client.base_url()) ++ } ++ ++ fn client(&self) -> Client { ++ self.client.client() ++ } ++ ++ pub(crate) async fn vendor_get_one(&self, vendor: &str, id: &str) -> Result { ++ let payload = GetResourcePolicyOp::GetOne { ++ policy: PolicyLocation { ++ vendor: Some(vendor.to_string()), ++ id: id.to_string(), ++ }, ++ }; ++ ++ let res = self ++ .client() ++ .get(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to get resource policy: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ pub(crate) async fn vendor_get_all(&self) -> Result> { ++ let payload = GetResourcePolicyOp::GetAll; ++ ++ let res = self ++ .client() ++ .get(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.json().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to get all resource policy: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ pub(crate) async fn vendor_get_all_in_vendor(&self, vendor: &str) -> Result> { ++ let payload = GetResourcePolicyOp::GetAllInVendor { ++ vendor: vendor.to_string(), ++ }; ++ ++ let res = self ++ .client() ++ .get(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.json().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!( ++ "failed to get all resource policy in vendor {}: {}", ++ vendor, ++ res.text().await? ++ ), ++ status, ++ )) ++ } ++ } ++ pub(crate) async fn vendor_add(&self, vendor: &str, id: &str, content: &str) -> Result { ++ let payload = SetResourcePolicyOp::Add { ++ policy: PolicyLocation { ++ vendor: Some(vendor.to_string()), ++ id: id.to_string(), ++ }, ++ content: content.to_string(), ++ }; ++ ++ let res = self ++ .client() ++ .post(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to add resource policy: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ pub(crate) async fn vendor_delete(&self, vendor: &str, id: &str) -> Result { ++ let payload = SetResourcePolicyOp::Delete { ++ policy: PolicyLocation { ++ vendor: Some(vendor.to_string()), ++ id: id.to_string(), ++ }, ++ }; ++ ++ let res = self ++ .client() ++ .post(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to delete resource policy: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ ++ pub(crate) async fn vendor_clear_all(&self, vendor: &str) -> Result { ++ let payload = SetResourcePolicyOp::ClearAll { ++ vendor: vendor.to_string(), ++ }; ++ ++ let res = self ++ .client() ++ .post(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!( ++ "failed to clear resource policy in vendor {}: {}", ++ vendor, ++ res.text().await? ++ ), ++ status, ++ )) ++ } ++ } ++} +diff --git a/service/attestation/attestation-client/src/resource_policy/mod.rs b/service/attestation/attestation-client/src/resource_policy/mod.rs +new file mode 100644 +index 0000000..4879412 +--- /dev/null ++++ b/service/attestation/attestation-client/src/resource_policy/mod.rs +@@ -0,0 +1,100 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! Subcommand for getting or setting resource policy. ++//! ++ ++pub(crate) mod client; ++ ++use self::client::ResourcePolicyClient; ++use crate::client::AsClient; ++use clap::{Args, Subcommand}; ++ ++#[derive(Debug, Args)] ++#[command(args_conflicts_with_subcommands = true)] ++#[command(flatten_help = true)] ++pub(crate) struct ResourcePolicyArgs { ++ #[command(subcommand)] ++ pub(crate) command: ResourcePolicyCommand, ++} ++ ++#[derive(Debug, Subcommand)] ++pub(crate) enum ResourcePolicyCommand { ++ GetOne { ++ vendor: String, ++ id: String, ++ }, ++ GetAll, ++ GetAllInVendor { ++ vendor: String, ++ }, ++ Add { ++ vendor: String, ++ id: String, ++ content: String, ++ }, ++ Delete { ++ vendor: String, ++ id: String, ++ }, ++ ClearAll { ++ vendor: String, ++ }, ++} ++ ++impl ResourcePolicyArgs { ++ pub(crate) fn process(&self, base_client: AsClient) { ++ self.command.dispatch(base_client); ++ } ++} ++ ++impl ResourcePolicyCommand { ++ fn dispatch(&self, base_client: AsClient) { ++ let client = ResourcePolicyClient::new(base_client); ++ let runtime = tokio::runtime::Runtime::new().unwrap(); ++ ++ match self { ++ ResourcePolicyCommand::GetOne { vendor, id } => { ++ let ret = runtime.block_on(client.vendor_get_one(vendor, id)).unwrap(); ++ println!("{}", ret); ++ } ++ ResourcePolicyCommand::GetAll => { ++ let ret = runtime.block_on(client.vendor_get_all()).unwrap(); ++ println!("{}", serde_json::json!(ret).to_string()); ++ } ++ ResourcePolicyCommand::GetAllInVendor { vendor } => { ++ let ret = runtime ++ .block_on(client.vendor_get_all_in_vendor(vendor)) ++ .unwrap(); ++ println!("{}", serde_json::json!(ret).to_string()); ++ } ++ ResourcePolicyCommand::Add { ++ vendor, ++ id, ++ content, ++ } => { ++ let ret = runtime ++ .block_on(client.vendor_add(vendor, id, content)) ++ .unwrap(); ++ println!("{}", ret); ++ } ++ ResourcePolicyCommand::Delete { vendor, id } => { ++ let ret = runtime.block_on(client.vendor_delete(vendor, id)).unwrap(); ++ println!("{}", ret); ++ } ++ ResourcePolicyCommand::ClearAll { vendor } => { ++ let ret = runtime.block_on(client.vendor_clear_all(vendor)).unwrap(); ++ println!("{}", ret); ++ } ++ } ++ } ++} +diff --git a/service/attestation/attestation-service/Cargo.toml b/service/attestation/attestation-service/Cargo.toml +index cf0dd87..ed0ebd2 100644 +--- a/service/attestation/attestation-service/Cargo.toml ++++ b/service/attestation/attestation-service/Cargo.toml +@@ -1,19 +1,13 @@ + [workspace] + resolver = "2" +-members = [ +- "service", +- "verifier", +- "token", +- "reference", +- "policy", +- "tests" +-] ++members = ["service", "verifier", "token", "reference", "policy", "tests"] + + [workspace.dependencies] + anyhow = "1.0.80" + serde = "1.0" + serde_json = "1.0" + async-trait = "0.1.78" ++async-recursion = "1.1.1" + cose-rust = "0.1.7" + ciborium = "0.2.2" + hex = "0.4" +@@ -24,7 +18,8 @@ rand = "0.8.5" + ima-measurements = "0.2.0" + fallible-iterator = "0.2.0" + +-actix-web = "4.5" ++actix-web = { version = "4.5.0", features = ["openssl"] } ++actix-web-httpauth = "0.8.2" + env_logger = "0.9" + tokio = { version = "1", features = ["full"] } + strum = { version = "0.25", features = ["derive"] } +@@ -39,4 +34,6 @@ lazy_static = "1.5.0" + uuid = { version = "1.2.2", features = ["serde", "v4"] } + scc = "2.1" + +-attestation-types = {path = "../attestation-types"} ++attestation-types = { path = "../attestation-types" } ++ear = "0.1.1" ++ccatoken = "0.1.0" +diff --git a/service/attestation/attestation-service/README.md b/service/attestation/attestation-service/README.md +index c64e6f1..6443ab2 100644 +--- a/service/attestation/attestation-service/README.md ++++ b/service/attestation/attestation-service/README.md +@@ -2,5 +2,20 @@ + The Attestation Service verifies hardware TEE evidence. + The first phase aims to support Kunpeng Trustzone, virtCCA and QingTian Enclave. In the future, it will support ARM CCA, Intel TDX, Hygon CSV etc. + +-# Overview +-TODO ++# Quick Start ++## Start Attestation Service quickly ++update repository source config ++``` ++vim /etc/yum.repos.d/openEuler.repo ++[everything] ++name=everything ++baseurl=https://repo.openeuler.org/openEuler-24.09/everything/aarch64/ ++enabled=1 ++gpgcheck=0 ++ ++//run service in current host like this, initialize environment automatically ++./as_startup.sh ++ ++//or in docker and specified ip:port ++./as_startup.sh -t docker -l 127.0.0.1:8080 ++``` +diff --git a/service/attestation/attestation-service/as_startup.sh b/service/attestation/attestation-service/as_startup.sh +new file mode 100755 +index 0000000..3a7e9fa +--- /dev/null ++++ b/service/attestation/attestation-service/as_startup.sh +@@ -0,0 +1,138 @@ ++#!/usr/bin/env bash ++DOCKER_TAR="openEuler-docker.aarch64.tar.xz" ++OPENEULER_DOCKER_URL="http://121.36.84.172/dailybuild/EBS-openEuler-24.09/rc5_openeuler-2024-09-12-18-14-43/docker_img/aarch64/${DOCKER_TAR}" ++IMAGE_NAME="openeuler-24.09" ++CONTAINER_NAME="openeuler-2409" ++PRIVATE_KEY="private.pem" ++CSR="server.csr" ++CERT="as_cert.pem" ++DIR_TMP="tmp" ++SERVICE_NAME="attestation-service" ++ ++generate_config() { ++ if [ -d ${DIR_TMP} ]; then ++ cd ${DIR_TMP} ++ if [ -a ${CERT} ] && [ -a ${PRIVATE_KEY} ]; then ++ echo "configuration already exist in ${DIR_TMP}, reuse it" ++ return ++ else ++ echo "${DIR_TMP} exist but broken, rename or delete it" ++ exit 1 ++ fi ++ fi ++ mkdir ${DIR_TMP} ++ cd ${DIR_TMP} ++ ++ openssl genrsa -out ${PRIVATE_KEY} 2048 ++ openssl req -subj "/C=CN/ST=ST/L=CITY/O=Company/CN=test.com" -new -key ${PRIVATE_KEY} -out ${CSR} ++ openssl x509 -req -in ${CSR} -out ${CERT} -signkey ${PRIVATE_KEY} -days 3650 ++ ++ echo "config files generated in ${DIR_TMP}" ++} ++ ++setup() { ++ mkdir -p /etc/attestation/attestation-agent/ ++ mkdir -p /etc/attestation/attestation-service/token ++ cp ${CERT} /etc/attestation/attestation-agent/ ++ cp ${PRIVATE_KEY} /etc/attestation/attestation-service/token ++ yum install secGear-as -y ++} ++ ++as_start_in_host() { ++ listen_at=$1 ++ setup ++ /usr/bin/${SERVICE_NAME} -s ${listen_at} 2>&1 & ++} ++ ++start_container() { ++ wget -V||yum install wget ++ docker -v||yum install docker ++ docker images | grep -E "^${IMAGE_NAME}[ ]" ++ if [ $? -ne 0 ]; then ++ wget ${OPENEULER_DOCKER_URL} ++ docker load -i ${DOCKER_TAR} ++ fi ++ ++ docker ps -a | grep -E "\s${CONTAINER_NAME}$" ++ if [ $? -eq 0 ]; then ++ echo "Error: container ${CONTAINER_NAME} already exist, please delete it or rename it" ++ echo -e "\tdelete command:docker rm ${CONTAINER_NAME} --force" ++ echo -e "\trename command:docker rename ${CONTAINER_NAME} {any_name_you_want}" ++ exit 1 ++ fi ++ docker run -d --name ${CONTAINER_NAME} --network host ${IMAGE_NAME}:latest /bin/bash -c "while true; do sleep 1;done" ++} ++ ++setup_container() { ++ docker exec ${CONTAINER_NAME} mkdir -p /etc/attestation/attestation-agent/ ++ docker exec ${CONTAINER_NAME} mkdir -p /etc/attestation/attestation-service/token ++ docker cp ${CERT} ${CONTAINER_NAME}:/etc/attestation/attestation-agent/ ++ docker cp ${PRIVATE_KEY} ${CONTAINER_NAME}:/etc/attestation/attestation-service/token ++ yum download kunpengsecl-attester kunpengsecl-qcaserver secGear-as cjson compat-openssl11-libs ++ ls *.rpm | xargs -i docker cp {} ${CONTAINER_NAME}:/home ++ docker exec ${CONTAINER_NAME} rpm -ivh /home/*.rpm ++} ++ ++as_start_in_docker() { ++ start_container ++ setup_container ++ docker exec -d ${CONTAINER_NAME} /bin/bash -c "/usr/bin/${SERVICE_NAME} -s ${listen_at} 2>&1" ++} ++ ++start_attestation_service() { ++ run_in=$1 ++ listen_at=$2 ++ generate_config ++ if [[ ${run_in} == "host" ]]; then ++ as_start_in_host ${listen_at} ++ else ++ as_start_in_docker ${listen_at} ++ fi ++ echo ${SERVICE_NAME} started ${listen_at} ++} ++ ++ ++print_usage() { ++ echo "Usage: ./as_startup [-t docker|host] [-l 127.0.0.1:8080]" ++ echo "example: ./as_startup run in host,listen at 127.0.0.1:8080 by default" ++} ++ ++run_in="" ++listen_at="" ++while getopts "t:l:h" optname ++ do ++ case "$optname" in ++ "t") ++ run_in="$OPTARG" ++ echo "run in $run_in" ++ ;; ++ "l") ++ listen_at="$OPTARG" ++ echo "listen at $listen_at" ++ ;; ++ "h") ++ print_usage ++ exit 1 ++ ;; ++ *) ++ print_usage ++ exit 1 ++ ;; ++ esac ++ done ++if [[ ${run_in} == "" ]]; then ++ run_in="host" ++fi ++ ++if [[ ${listen_at} == "" ]]; then ++ listen_at="127.0.0.1:8080" ++fi ++ ++service_run=$(ps aux | grep "/usr/bin/${SERVICE_NAME}" | wc -l) ++if [ ${service_run} -gt 1 ]; then ++ echo "${SERVICE_NAME} already run" ++ exit 1 ++fi ++ ++echo "${SERVICE_NAME} run in $run_in, listen at $listen_at" ++start_attestation_service ${run_in} ${listen_at} +diff --git a/service/attestation/attestation-service/policy/Cargo.toml b/service/attestation/attestation-service/policy/Cargo.toml +index 87917a4..acf961f 100644 +--- a/service/attestation/attestation-service/policy/Cargo.toml ++++ b/service/attestation/attestation-service/policy/Cargo.toml +@@ -10,3 +10,4 @@ regorus.workspace = true + base64.workspace = true + tokio.workspace = true + futures.workspace = true ++async-trait.workspace = true +diff --git a/service/attestation/attestation-service/policy/src/lib.rs b/service/attestation/attestation-service/policy/src/lib.rs +index 0677f45..f63146a 100644 +--- a/service/attestation/attestation-service/policy/src/lib.rs ++++ b/service/attestation/attestation-service/policy/src/lib.rs +@@ -105,7 +105,9 @@ output["Other"] := "other" if { + ); + let data = String::new(); + let policy_id: Vec = vec![]; +- let result = engine.evaluate(&String::from("vcca"), &refs_from_report, &data, &policy_id).await; ++ let result = engine ++ .evaluate(&String::from("vcca"), &refs_from_report, &data, &policy_id) ++ .await; + println!("{:?}", result); + assert!(result.is_ok()); + match result { +@@ -165,7 +167,9 @@ output["Other"] := "other" if { + ); + let data = String::new(); + let policy_id: Vec = vec!["test.rego".to_string()]; +- let result = engine.evaluate(&String::from("vcca"), &refs_from_report, &data, &policy_id).await; ++ let result = engine ++ .evaluate(&String::from("vcca"), &refs_from_report, &data, &policy_id) ++ .await; + assert!(result.is_ok()); + match result { + Ok(ret) => { +diff --git a/service/attestation/attestation-service/policy/src/opa/mod.rs b/service/attestation/attestation-service/policy/src/opa/mod.rs +index c2e1cdb..7ce7f86 100644 +--- a/service/attestation/attestation-service/policy/src/opa/mod.rs ++++ b/service/attestation/attestation-service/policy/src/opa/mod.rs +@@ -45,7 +45,9 @@ impl PolicyEngine for OPA { + } else if tee == "itrustee" { + policy_id_used.push(String::from(DEFAULT_ITRUSTEE_REGO)); + } else { +- return Err(PolicyEngineError::TeeTypeUnknown(format!("tee type unknown: {tee}"))); ++ return Err(PolicyEngineError::TeeTypeUnknown(format!( ++ "tee type unknown: {tee}" ++ ))); + } + policy_path = self.default_policy_dir.clone(); + } else { +@@ -56,13 +58,17 @@ impl PolicyEngine for OPA { + for id in policy_id_used { + let mut path = policy_path.clone(); + path.push(id.clone()); +- let engine_policy = tokio::fs::read_to_string(path.clone()).await.map_err(|err| { +- PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)) +- })?; ++ let engine_policy = tokio::fs::read_to_string(path.clone()) ++ .await ++ .map_err(|err| { ++ PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)) ++ })?; + let mut engine = regorus::Engine::new(); +- engine.add_policy(id.clone(), engine_policy).map_err(|err| { +- PolicyEngineError::EngineLoadPolicyError(format!("policy load failed: {}", err)) +- })?; ++ engine ++ .add_policy(id.clone(), engine_policy) ++ .map_err(|err| { ++ PolicyEngineError::EngineLoadPolicyError(format!("policy load failed: {}", err)) ++ })?; + + let input = Value::from_json_str(refs).map_err(|err| { + PolicyEngineError::InvalidReport(format!("report to Value failed: {}", err)) +@@ -74,7 +80,10 @@ impl PolicyEngine for OPA { + PolicyEngineError::EngineLoadDataError(format!("data to Value failed: {}", err)) + })?; + engine.add_data(data).map_err(|err| { +- PolicyEngineError::EngineLoadDataError(format!("engine add data failed: {}", err)) ++ PolicyEngineError::EngineLoadDataError(format!( ++ "engine add data failed: {}", ++ err ++ )) + })?; + } + +@@ -94,32 +103,41 @@ impl PolicyEngine for OPA { + ) -> Result<(), PolicyEngineError> { + let raw = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(policy) +- .map_err(|err| PolicyEngineError::InvalidPolicy(format!("policy decode failed: {}", err)))?; ++ .map_err(|err| { ++ PolicyEngineError::InvalidPolicy(format!("policy decode failed: {}", err)) ++ })?; + + let mut policy_file: PathBuf = self.policy_dir.clone(); + policy_file.push(format!("{}", policy_id)); + tokio::fs::write(policy_file.as_path(), &raw) + .await +- .map_err(|err| PolicyEngineError::WritePolicyError(format!("write policy failed: {}", err)))?; ++ .map_err(|err| { ++ PolicyEngineError::WritePolicyError(format!("write policy failed: {}", err)) ++ })?; + Ok(()) + } + + async fn get_all_policy(&self) -> Result, PolicyEngineError> { + let mut items = tokio::fs::read_dir(&self.policy_dir.as_path()) + .await +- .map_err(|err| PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)))?; ++ .map_err(|err| { ++ PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)) ++ })?; + let mut policies = HashMap::new(); +- while let Some(item) = items +- .next_entry() +- .await +- .map_err(|err| PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)))? +- { ++ while let Some(item) = items.next_entry().await.map_err(|err| { ++ PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)) ++ })? { + let path = item.path(); + if path.extension().and_then(std::ffi::OsStr::to_str) == Some("rego") { + let content: String = +- tokio::fs::read_to_string(path.clone()).await.map_err(|err| { +- PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)) +- })?; ++ tokio::fs::read_to_string(path.clone()) ++ .await ++ .map_err(|err| { ++ PolicyEngineError::ReadPolicyError(format!( ++ "read policy failed: {}", ++ err ++ )) ++ })?; + let name = path + .file_stem() + .ok_or(PolicyEngineError::ReadPolicyError( +@@ -142,7 +160,9 @@ impl PolicyEngine for OPA { + policy_file.push(format!("{}", policy_id)); + let policy = tokio::fs::read(policy_file.as_path()) + .await +- .map_err(|err| PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)))?; ++ .map_err(|err| { ++ PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)) ++ })?; + let policy_base64 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(policy); + Ok(policy_base64) + } +@@ -153,7 +173,10 @@ impl OPA { + let policy_path = PathBuf::from(policy_dir); + if !policy_path.as_path().exists() { + std::fs::create_dir_all(&policy_dir).map_err(|err| { +- PolicyEngineError::CreatePolicyDirError(format!("policy dir create failed: {}", err)) ++ PolicyEngineError::CreatePolicyDirError(format!( ++ "policy dir create failed: {}", ++ err ++ )) + })?; + } + +diff --git a/service/attestation/attestation-service/reference/Cargo.toml b/service/attestation/attestation-service/reference/Cargo.toml +index fb0a4bb..664f745 100644 +--- a/service/attestation/attestation-service/reference/Cargo.toml ++++ b/service/attestation/attestation-service/reference/Cargo.toml +@@ -14,4 +14,4 @@ sled.workspace = true + openssl.workspace = true + hex.workspace = true + lazy_static.workspace = true +-thiserror.workspace = true +\ No newline at end of file ++thiserror.workspace = true +diff --git a/service/attestation/attestation-service/reference/src/lib.rs b/service/attestation/attestation-service/reference/src/lib.rs +index 4347fc1..aa1a6c7 100644 +--- a/service/attestation/attestation-service/reference/src/lib.rs ++++ b/service/attestation/attestation-service/reference/src/lib.rs +@@ -9,10 +9,10 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ ++mod extractor; + pub mod local_fs; + pub mod reference; + pub mod store; +-mod extractor; + + #[cfg(test)] + mod tests { +@@ -120,9 +120,17 @@ mod tests { + //key + let key = format!("ref{}", i); + //value +- let value:String = rng.clone().sample_iter(&Alphanumeric).take(128).map(char::from).collect(); ++ let value: String = rng ++ .clone() ++ .sample_iter(&Alphanumeric) ++ .take(128) ++ .map(char::from) ++ .collect(); + let mut reference = serde_json::json!({}); +- reference.as_object_mut().unwrap().insert(key, Value::String(value)); ++ reference ++ .as_object_mut() ++ .unwrap() ++ .insert(key, Value::String(value)); + let _ = ops_default.register(&reference.to_string()); + let ref_query = ops_default.query(&reference.to_string()).unwrap(); + println!("ref {} query {}", reference.to_string(), ref_query); +@@ -133,9 +141,10 @@ mod tests { + for hd in thread_all { + match hd.join() { + Ok(_) => {} +- Err(_) => {assert!(false)} ++ Err(_) => { ++ assert!(false) ++ } + } + } +- + } + } +diff --git a/service/attestation/attestation-service/reference/src/local_fs/mod.rs b/service/attestation/attestation-service/reference/src/local_fs/mod.rs +index 1e03579..2220fc0 100644 +--- a/service/attestation/attestation-service/reference/src/local_fs/mod.rs ++++ b/service/attestation/attestation-service/reference/src/local_fs/mod.rs +@@ -10,13 +10,12 @@ + * See the Mulan PSL v2 for more details. + */ + use lazy_static::lazy_static; +-use std::sync::Arc; + use sled::Db; + use std::ops::Deref; ++use std::sync::Arc; + + use crate::store::{KvError, KvStore}; + +- + pub struct LocalFs { + db: Arc, + } +@@ -24,7 +23,8 @@ pub struct LocalFs { + impl Default for LocalFs { + fn default() -> Self { + lazy_static! { +- static ref db_handle: Arc = Arc::new(sled::open("/etc/attestation/attestation-service/reference").unwrap()); ++ static ref db_handle: Arc = ++ Arc::new(sled::open("/etc/attestation/attestation-service/reference").unwrap()); + } + LocalFs { + db: db_handle.clone(), +diff --git a/service/attestation/attestation-service/reference/src/reference/mod.rs b/service/attestation/attestation-service/reference/src/reference/mod.rs +index 6ec4371..c400683 100644 +--- a/service/attestation/attestation-service/reference/src/reference/mod.rs ++++ b/service/attestation/attestation-service/reference/src/reference/mod.rs +@@ -46,7 +46,7 @@ pub enum RefOpError { + #[error("reference operation error {0}")] + Err(String), + #[error("reference store error: {0:?}")] +- StoreErr(#[from] KvError) ++ StoreErr(#[from] KvError), + } + + impl ReferenceOps { +diff --git a/service/attestation/attestation-service/reference/src/store/mod.rs b/service/attestation/attestation-service/reference/src/store/mod.rs +index c9597f6..a7838e7 100644 +--- a/service/attestation/attestation-service/reference/src/store/mod.rs ++++ b/service/attestation/attestation-service/reference/src/store/mod.rs +@@ -16,7 +16,7 @@ pub enum KvError { + impl std::fmt::Display for KvError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { +- KvError::Err(msg) => write!(f, "kv store error:{}", msg) ++ KvError::Err(msg) => write!(f, "kv store error:{}", msg), + } + } + } +diff --git a/service/attestation/attestation-service/service/Cargo.toml b/service/attestation/attestation-service/service/Cargo.toml +index e8b88b8..ffbe0ed 100644 +--- a/service/attestation/attestation-service/service/Cargo.toml ++++ b/service/attestation/attestation-service/service/Cargo.toml +@@ -10,6 +10,7 @@ hex.workspace = true + serde_json.workspace = true + + actix-web.workspace = true ++actix-web-httpauth.workspace = true + env_logger.workspace = true + tokio.workspace = true + log.workspace = true +@@ -27,9 +28,7 @@ uuid.workspace = true + rand.workspace = true + scc.workspace = true + attestation-types.workspace = true +- +-[dev-dependencies] ++openssl.workspace = true + futures.workspace = true + + [features] +- +diff --git a/service/attestation/attestation-service/service/src/lib.rs b/service/attestation/attestation-service/service/src/lib.rs +index 1c5c907..99ae818 100644 +--- a/service/attestation/attestation-service/service/src/lib.rs ++++ b/service/attestation/attestation-service/service/src/lib.rs +@@ -9,32 +9,43 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use anyhow::{Result, anyhow}; +-use std::fs::File; +-use std::path::Path; +-use std::str::FromStr; +-use serde::{Serialize, Deserialize}; +-use serde_json::Value; +-use rand::RngCore; +-use base64_url; + +-use verifier::{Verifier, VerifierAPIs}; +-use token_signer::{EvlReport, TokenSigner, TokenSignConfig}; +-use reference::reference::{ReferenceOps, RefOpError}; ++pub mod restapi; ++pub mod result; ++pub mod session; ++ ++use actix_web::web::{self, Data}; ++use anyhow::{anyhow, Context, Result}; ++use attestation_types::resource::admin::simple::SimpleResourceAdmin; ++use attestation_types::resource::admin::ResourceAdminInterface; ++use attestation_types::resource::ResourceLocation; ++use attestation_types::EvlResult; ++use base64_url; ++use futures::lock::Mutex; + use policy::opa::OPA; + use policy::policy_engine::{PolicyEngine, PolicyEngineError}; +-use attestation_types::EvlResult; +- +-pub mod result; ++use rand::RngCore; ++use reference::reference::{RefOpError, ReferenceOps}; ++use serde::{Deserialize, Serialize}; ++use serde_json::Value; ++use session::SessionMap; ++use std::fs::File; ++use std::path::{Path, PathBuf}; ++use std::str::FromStr; ++use std::sync::Arc; ++use token_signer::{EvlReport, TokenSignConfig, TokenSigner}; ++use verifier::{Verifier, VerifierAPIs}; + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct ASConfig { + pub token_cfg: TokenSignConfig, ++ pub resource_policy: Option, + } + + impl Default for ASConfig { + fn default() -> Self { + Self { + token_cfg: TokenSignConfig::default(), ++ resource_policy: None, + } + } + } +@@ -60,18 +71,22 @@ impl TryFrom<&Path> for ASConfig { + + pub struct AttestationService { + pub config: ASConfig, +- // verify policy sub service +- //policy: , ++ // Resource Administrator ++ pub(crate) resource_admin: Arc>, + // reference value provider sub service + //rvps: , + // tee verifier sub service + //verifier: , ++ // Sessions Map ++ pub(crate) sessions: Data, + } + + impl Default for AttestationService { + fn default() -> Self { + Self { + config: ASConfig::default(), ++ resource_admin: Arc::new(Mutex::new(SimpleResourceAdmin::default())), ++ sessions: web::Data::new(SessionMap::new()), + } + } + } +@@ -88,14 +103,18 @@ impl AttestationService { + ASConfig::default() + } + }; +- Ok(AttestationService {config}) ++ Ok(AttestationService { ++ config, ++ resource_admin: Arc::new(Mutex::new(SimpleResourceAdmin::default())), ++ sessions: web::Data::new(SessionMap::new()), ++ }) + } + /// evaluate tee evidence with reference and policy, and issue attestation result token + pub async fn evaluate( + &self, + user_data: &[u8], + evidence: &[u8], +- policy_ids: &Option> ++ policy_ids: &Option>, + ) -> Result { + let verifier = Verifier::default(); + let claims_evidence = verifier.verify_evidence(user_data, evidence).await?; +@@ -119,42 +138,69 @@ impl AttestationService { + let refs_of_claims = ops_refs.query(&claims_evidence["payload"].to_string()); + // apply policy to verify claims_evidence with reference value + let policy_ids = match policy_ids { +- Some(polciy_id) => polciy_id.clone(), ++ Some(policy_id) => policy_id.clone(), + None => vec![], + }; + let policy_dir = String::from("/etc/attestation/attestation-service/policy"); + let engine = OPA::new(&policy_dir).await.unwrap(); + let data = String::new(); +- let result = engine.evaluate(&String::from(claims_evidence["tee"] +- .as_str().ok_or(anyhow!("tee type unknown"))?), +- &refs_of_claims.unwrap(), &data, &policy_ids).await; ++ let result = engine ++ .evaluate( ++ &String::from( ++ claims_evidence["tee"] ++ .as_str() ++ .ok_or(anyhow!("tee type unknown"))?, ++ ), ++ &refs_of_claims.unwrap(), ++ &data, ++ &policy_ids, ++ ) ++ .await; + let mut report = serde_json::json!({}); + let mut ref_exist_null: bool = false; + match result { + Ok(eval) => { + for id in eval.keys() { + let val = Value::from_str(&eval[id].clone())?; +- let refs = match val.as_object().ok_or(Err(anyhow!("json value to map fail"))) { +- Err(err) => { return Err(err.unwrap()); } +- Ok(ret) => { ret } ++ let refs = match val ++ .as_object() ++ .ok_or(Err(anyhow!("json value to map fail"))) ++ { ++ Err(err) => { ++ return Err(err.unwrap()); ++ } ++ Ok(ret) => ret, + }; + for key in refs.keys() { + // reference value is null means not found + if refs[key].is_null() { + ref_exist_null = true; +- } ++ } + } +- report.as_object_mut().unwrap().insert(id.clone(), serde_json::Value::String(eval[id].clone())); ++ report ++ .as_object_mut() ++ .unwrap() ++ .insert(id.clone(), serde_json::Value::String(eval[id].clone())); + } + } + Err(err) => { + return Err(anyhow!("evaluate error: {err}")); + } + } +- ++ ++ // add ima detail result to report ++ report ++ .as_object_mut() ++ .unwrap() ++ .insert("ima".to_string(), claims_evidence["ima"].clone()); ++ + // issue attestation result token + let evl_report = EvlReport { +- tee: String::from(claims_evidence["tee"].as_str().ok_or(anyhow!("tee type unknown"))?), ++ tee: String::from( ++ claims_evidence["tee"] ++ .as_str() ++ .ok_or(anyhow!("tee type unknown"))?, ++ ), + result: EvlResult { + eval_result: passed & !ref_exist_null, + policy: policy_ids, +@@ -168,53 +214,88 @@ impl AttestationService { + Ok(signer.sign(&evl_report)?) + } + +- pub async fn generate_challenge(&self) -> String { +- let mut nonce: [u8; 32] = [0; 32]; ++ pub async fn generate_challenge(&self, user_data: Option>) -> String { ++ let mut nonce: Vec = vec![0; 32]; + rand::thread_rng().fill_bytes(&mut nonce); ++ if user_data != None { ++ nonce.append(&mut user_data.unwrap()); ++ } + base64_url::encode(&nonce) + } + +- pub async fn set_policy(&self, ++ pub async fn set_policy( ++ &self, + id: &String, + policy: &String, + policy_dir: &String, + ) -> Result<(), PolicyEngineError> { + let engine = OPA::new(policy_dir).await; +- engine.unwrap() +- .set_policy(id, policy) +- .await ++ engine.unwrap().set_policy(id, policy).await + } + +- pub async fn get_all_policy(&self, +- policy_dir: &String, +- ) -> Result { ++ pub async fn get_all_policy(&self, policy_dir: &String) -> Result { + let engine = OPA::new(policy_dir).await; + match engine.unwrap().get_all_policy().await { + Ok(map) => { + let mut json_obj: serde_json::Value = serde_json::json!({}); + for key in map.keys() { +- json_obj.as_object_mut() +- .unwrap() +- .insert(key.clone(), serde_json::json!(map[key])); ++ json_obj ++ .as_object_mut() ++ .unwrap() ++ .insert(key.clone(), serde_json::json!(map[key])); + } + Ok(json_obj.to_string()) + } +- Err(err) => Err(err) ++ Err(err) => Err(err), + } + } + +- pub async fn get_policy(&self, ++ pub async fn get_policy( ++ &self, + policy_dir: &String, +- id: &String ++ id: &String, + ) -> Result { + let engine = OPA::new(policy_dir).await?; + Ok(engine.get_policy(id).await?) + } + +- pub async fn register_reference(&self, +- ref_set: &String +- ) -> Result<(), RefOpError> { ++ pub async fn register_reference(&self, ref_set: &String) -> Result<(), RefOpError> { + let mut ops_default = ReferenceOps::default(); + ops_default.register(ref_set) + } ++ ++ pub async fn resource_evaluate(&self, resource: ResourceLocation, claim: &str) -> Result { ++ Ok(self ++ .resource_admin ++ .lock() ++ .await ++ .evaluate_resource(resource, claim) ++ .await ++ .context("fail to evaluate resource according to the claim")?) ++ } ++ ++ pub async fn get_resource(&self, location: ResourceLocation) -> Result { ++ let resource = self ++ .resource_admin ++ .lock() ++ .await ++ .get_resource(location) ++ .await ++ .context("fail to get resource")?; ++ ++ Ok(serde_json::to_string(&resource.get_content())?) ++ } ++ ++ pub async fn list_resource(&self, vendor: &str) -> Result> { ++ self.resource_admin ++ .lock() ++ .await ++ .list_resource(vendor) ++ .await ++ .context("faile to collect resource list in vendor") ++ } ++ ++ pub fn get_sessions(&self) -> Data { ++ self.sessions.clone() ++ } + } +diff --git a/service/attestation/attestation-service/service/src/main.rs b/service/attestation/attestation-service/service/src/main.rs +index 88941b8..d9918f7 100644 +--- a/service/attestation/attestation-service/service/src/main.rs ++++ b/service/attestation/attestation-service/service/src/main.rs +@@ -9,30 +9,32 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-/// RESTful Attestation Service +- +-use attestation_service::AttestationService; +-mod restapi; +-use restapi::{get_challenge, attestation, reference, get_policy, set_policy}; +-mod session; +-use session::SessionMap; +- ++use actix_web::{web, App, HttpServer}; + use anyhow::Result; ++use attestation_service::restapi::{ ++ attestation, get_challenge, get_policy, reference, ++ resource::{ ++ policy::{get_resource_policy, set_resource_policy}, ++ storage::{get_resource, set_resource}, ++ }, ++ set_policy, ++}; ++use attestation_service::AttestationService; ++use clap::{arg, command, Parser}; + use env_logger; +-use actix_web::{web, App, HttpServer}; +-use std::{net::{SocketAddr, IpAddr, Ipv4Addr}, sync::Arc}; ++use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; ++use std::sync::Arc; + use tokio::sync::RwLock; +-use clap::{Parser, command, arg}; + + const DEFAULT_ASCONFIG_FILE: &str = "/etc/attestation/attestation-service/attestation-service.conf"; +-const DEFAULT_SOCKETADDR: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); ++const DEFAULT_SOCKETADDR: &str = "localhost:8080"; + + #[derive(Parser, Debug)] + #[command(version, about, long_about = None)] + struct Cli { + /// Socket address to listen on +- #[arg(short, long, default_value_t = DEFAULT_SOCKETADDR)] +- socketaddr: SocketAddr, ++ #[arg(short, long, default_value_t = DEFAULT_SOCKETADDR.to_string())] ++ socketaddr: String, + + /// Attestation Service config file + // Load `ASConfig` from a configuration file like: +@@ -47,6 +49,13 @@ struct Cli { + // } + #[arg(short, long, default_value_t = DEFAULT_ASCONFIG_FILE.to_string())] + config: String, ++ ++ #[arg(short = 'p', long = "protocol", default_value_t = String::from("http"))] ++ protocol: String, ++ #[arg(short = 't', long = "https_cert", default_value_t = String::from(""))] ++ https_cert: String, ++ #[arg(short = 'k', long = "https_key", default_value_t = String::from(""))] ++ https_key: String, + } + + #[actix_web::main] +@@ -54,14 +63,12 @@ async fn main() -> Result<()> { + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + let cli = Cli::parse(); +- let server:AttestationService = AttestationService::new(Some(cli.config)).unwrap(); +- let session_map = web::Data::new(SessionMap::new()); +- +- let sessions_clone = session_map.clone(); ++ let server: AttestationService = AttestationService::new(None).unwrap(); ++ let sessions = server.get_sessions(); + tokio::spawn(async move { + loop { + tokio::time::sleep(std::time::Duration::from_secs(60)).await; +- sessions_clone ++ sessions + .session_map + .retain_async(|_, v| !v.is_expired()) + .await; +@@ -69,19 +76,36 @@ async fn main() -> Result<()> { + }); + + let service = web::Data::new(Arc::new(RwLock::new(server))); +- HttpServer::new(move || { ++ let http_server = HttpServer::new(move || { + App::new() + .app_data(web::Data::clone(&service)) +- .app_data(web::Data::clone(&session_map)) + .service(get_challenge) + .service(attestation) + .service(reference) + .service(set_policy) + .service(get_policy) +- }) +- .bind((cli.socketaddr.ip().to_string(), cli.socketaddr.port()))? +- .run() +- .await?; ++ .service(get_resource) ++ .service(set_resource) ++ .service(get_resource_policy) ++ .service(set_resource_policy) ++ }); ++ if cli.protocol == "https" { ++ if cli.https_cert.is_empty() || cli.https_key.is_empty() { ++ log::error!("cert or key is empty"); ++ return Ok(()); ++ } ++ let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls())?; ++ builder.set_private_key_file(cli.https_key, SslFiletype::PEM)?; ++ builder.set_certificate_chain_file(cli.https_cert)?; ++ http_server ++ .bind_openssl(cli.socketaddr, builder)? ++ .run() ++ .await?; ++ } else if cli.protocol == "http" { ++ http_server.bind(cli.socketaddr)?.run().await?; ++ } else { ++ log::error!("unknown protocol {}", cli.protocol); ++ } + + Ok(()) +-} +\ No newline at end of file ++} +diff --git a/service/attestation/attestation-service/service/src/restapi/mod.rs b/service/attestation/attestation-service/service/src/restapi/mod.rs +index d47698a..c3d6309 100644 +--- a/service/attestation/attestation-service/service/src/restapi/mod.rs ++++ b/service/attestation/attestation-service/service/src/restapi/mod.rs +@@ -1,43 +1,61 @@ + /* +- * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. +- * secGear is licensed under the Mulan PSL v2. +- * You can use this software according to the terms and conditions of the Mulan PSL v2. +- * You may obtain a copy of Mulan PSL v2 at: +- * http://license.coscl.org.cn/MulanPSL2 +- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +- * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +- * PURPOSE. +- * See the Mulan PSL v2 for more details. +- */ +-use attestation_service::AttestationService; +-use attestation_service::result::{Result, Error}; +- +-use actix_web::{ post, get, web, HttpResponse, HttpRequest}; ++* Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++* secGear is licensed under the Mulan PSL v2. ++* You can use this software according to the terms and conditions of the Mulan PSL v2. ++* You may obtain a copy of Mulan PSL v2 at: ++* http://license.coscl.org.cn/MulanPSL2 ++* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++* PURPOSE. ++* See the Mulan PSL v2 for more details. ++*/ ++pub mod resource; ++ ++use crate::result::{AsError, Result}; ++use crate::session::Session; ++use crate::AttestationService; ++use actix_web::{get, post, web, HttpRequest, HttpResponse}; ++use attestation_types::SESSION_TIMEOUT_MIN; ++use log; + use serde::{Deserialize, Serialize}; + use std::sync::Arc; + use tokio::sync::RwLock; +-use log; +-use base64_url; +-use attestation_types::SESSION_TIMEOUT_MIN; +-use crate::session::{Session, SessionMap}; + + const DEFAULT_POLICY_DIR: &str = "/etc/attestation/attestation-service/policy"; + #[derive(Deserialize, Serialize, Debug)] +-pub struct ChallengeRequest {} ++pub struct ChallengeRequest { ++ pub user_data: Vec, ++} + + #[get("/challenge")] + pub async fn get_challenge( +- map: web::Data, ++ request: Option>, + service: web::Data>>, + ) -> Result { + log::debug!("challenge request"); ++ let user_data: Option>; ++ ++ if request.is_some() { ++ user_data = Some(request.unwrap().0.user_data); ++ if user_data.clone().unwrap().len() > 32 { ++ return Err(AsError::ParameterInvalid(String::from( ++ "user data length should not exceed 32", ++ ))); ++ } ++ log::debug!("user data is {:?}", user_data.clone().unwrap()); ++ } else { ++ log::debug!("user data is None"); ++ user_data = Option::None; ++ } ++ ++ let map = service.read().await.get_sessions(); ++ let challenge = service.read().await.generate_challenge(user_data).await; ++ let new_session = Session::new(challenge, SESSION_TIMEOUT_MIN); + +- let challenge = service.read().await.generate_challenge().await; +- let session = Session::new(challenge, SESSION_TIMEOUT_MIN); + let response = HttpResponse::Ok() +- .cookie(session.cookie()) +- .json(session.challenge.clone()); +- map.insert(session); ++ .cookie(new_session.cookie()) ++ .json(new_session.challenge.clone()); ++ map.insert(new_session); + + Ok(response) + } +@@ -52,42 +70,55 @@ pub struct AttestationRequest { + #[post("/attestation")] + pub async fn attestation( + http_req: HttpRequest, +- map: web::Data, + request: web::Json, + service: web::Data>>, + ) -> Result { + log::debug!("attestation request is coming"); ++ let map = service.read().await.get_sessions(); + let request = request.0; + let challenge = request.challenge; + + if http_req.headers().contains_key("as-challenge") { +- log::info!("sessions map len:{}", map.session_map.len()); +- let cookie = http_req.cookie("oeas-session-id").ok_or(Error::CookieMissing)?; +- let session = map +- .session_map +- .get_async(cookie.value()) +- .await +- .ok_or(Error::SessionNotFound)?; +- if session.is_expired() { +- return Err(Error::SessionExpired); +- } +- if challenge != session.challenge { +- log::error!("request challenge:{} does not match session challenge:{}", challenge, session.challenge); +- return Err(Error::ChallengeInvalid); +- } ++ log::warn!("attestation request lacks 'as-challenge' header field."); ++ } ++ ++ log::info!("sessions map len:{}", map.session_map.len()); ++ let cookie = http_req ++ .cookie("oeas-session-id") ++ .ok_or(AsError::CookieMissing)?; ++ let session = map ++ .session_map ++ .get_async(cookie.value()) ++ .await ++ .ok_or(AsError::SessionNotFound)?; ++ if session.is_expired() { ++ return Err(AsError::SessionExpired); ++ } ++ if challenge != session.challenge { ++ log::error!( ++ "request challenge:{} does not match session challenge:{}", ++ challenge, ++ session.challenge ++ ); ++ return Err(AsError::ChallengeInvalid); + } + +- let nonce = base64_url::decode(&challenge)?; ++ // The challenge in evidence is base64 encoded. ++ let nonce = challenge.as_bytes(); + let evidence = base64_url::decode(&request.evidence)?; + let ids = request.policy_id; +- let token = service.read().await.evaluate(&nonce, &evidence, &ids).await?; ++ let token = service ++ .read() ++ .await ++ .evaluate(&nonce, &evidence, &ids) ++ .await?; + +- Ok(HttpResponse::Ok().body(token)) ++ Ok(HttpResponse::Ok().cookie(cookie).body(token)) + } + + #[derive(Deserialize, Serialize, Debug)] + pub struct ReferenceRequest { +- refs: String ++ refs: String, + } + + #[post("/reference")] +@@ -97,7 +128,11 @@ pub async fn reference( + ) -> Result { + let request = request.0; + log::debug!("reference request: {:?}", request); +- service.read().await.register_reference(&request.refs).await?; ++ service ++ .read() ++ .await ++ .register_reference(&request.refs) ++ .await?; + Ok(HttpResponse::Ok().body("set reference success")) + } + +@@ -117,8 +152,12 @@ pub async fn set_policy( + log::debug!("set policy request: {:?}", request); + let policy_id = request.id.clone(); + let policy = request.policy.clone(); +- let dir:String = String::from(DEFAULT_POLICY_DIR); +- service.read().await.set_policy(&policy_id, &policy, &dir).await?; ++ let dir: String = String::from(DEFAULT_POLICY_DIR); ++ service ++ .read() ++ .await ++ .set_policy(&policy_id, &policy, &dir) ++ .await?; + Ok(HttpResponse::Ok().body("set policy success")) + } + +@@ -135,7 +174,11 @@ pub async fn get_policy( + let request = request.0; + log::debug!("get policy request: {:?}", request); + let id = request.policy_id.clone(); +- let dir:String = String::from(DEFAULT_POLICY_DIR); +- let ret = service.read().await.get_policy(&dir, &id.to_string()).await?; ++ let dir: String = String::from(DEFAULT_POLICY_DIR); ++ let ret = service ++ .read() ++ .await ++ .get_policy(&dir, &id.to_string()) ++ .await?; + Ok(HttpResponse::Ok().body(ret)) + } +diff --git a/service/attestation/attestation-service/service/src/restapi/resource/mod.rs b/service/attestation/attestation-service/service/src/restapi/resource/mod.rs +new file mode 100644 +index 0000000..4efa7e3 +--- /dev/null ++++ b/service/attestation/attestation-service/service/src/restapi/resource/mod.rs +@@ -0,0 +1,14 @@ ++/* ++* Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++* secGear is licensed under the Mulan PSL v2. ++* You can use this software according to the terms and conditions of the Mulan PSL v2. ++* You may obtain a copy of Mulan PSL v2 at: ++* http://license.coscl.org.cn/MulanPSL2 ++* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++* PURPOSE. ++* See the Mulan PSL v2 for more details. ++*/ ++ ++pub mod policy; ++pub mod storage; +diff --git a/service/attestation/attestation-service/service/src/restapi/resource/policy.rs b/service/attestation/attestation-service/service/src/restapi/resource/policy.rs +new file mode 100644 +index 0000000..77a63f1 +--- /dev/null ++++ b/service/attestation/attestation-service/service/src/restapi/resource/policy.rs +@@ -0,0 +1,116 @@ ++/* ++* Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++* secGear is licensed under the Mulan PSL v2. ++* You can use this software according to the terms and conditions of the Mulan PSL v2. ++* You may obtain a copy of Mulan PSL v2 at: ++* http://license.coscl.org.cn/MulanPSL2 ++* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++* PURPOSE. ++* See the Mulan PSL v2 for more details. ++*/ ++use crate::result::Result; ++use crate::AttestationService; ++use actix_web::{get, post, web, HttpResponse}; ++use attestation_types::resource::policy::PolicyLocation; ++use attestation_types::service::{GetResourcePolicyOp, SetResourcePolicyOp}; ++use serde::{Deserialize, Serialize}; ++use std::sync::Arc; ++use tokio::sync::RwLock; ++ ++#[get("/resource/policy")] ++pub async fn get_resource_policy( ++ body: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ let agent = agent.read().await; ++ let admin = agent.resource_admin.lock().await; ++ let op = body.0; ++ match op { ++ GetResourcePolicyOp::GetOne { policy } => { ++ log::debug!("Request for getting policy {}", policy); ++ match admin.get_policy(policy.clone()).await { ++ Ok(content) => Ok(HttpResponse::Ok().body(content)), ++ Err(e) => { ++ log::warn!("Failed to get policy '{}'", policy); ++ Err(crate::result::AsError::from(e)) ++ } ++ } ++ } ++ GetResourcePolicyOp::GetAll => { ++ log::debug!("Request for getting all policies"); ++ match admin.get_all_policies().await { ++ Ok(policies) => { ++ let ret: Vec = policies ++ .iter() ++ .map(|location| String::from(location)) ++ .collect(); ++ let s = serde_json::to_string(&ret) ++ .unwrap_or(format!("Failed to serialize '{:?}'", ret)); ++ Ok(HttpResponse::Ok().body(s)) ++ } ++ Err(e) => { ++ log::warn!("Failed to get all policies"); ++ Err(crate::result::AsError::from(e)) ++ } ++ } ++ } ++ GetResourcePolicyOp::GetAllInVendor { vendor } => { ++ log::debug!("Request for getting all policies in vendor {}", vendor); ++ match admin.get_all_policies_in_vendor(&vendor).await { ++ Ok(policies) => { ++ let ret: Vec = policies ++ .iter() ++ .map(|location| String::from(location)) ++ .collect(); ++ let s = serde_json::to_string(&ret) ++ .unwrap_or(format!("Failed to serialize '{:?}'", ret)); ++ Ok(HttpResponse::Ok().body(s)) ++ } ++ Err(e) => { ++ log::warn!("Failed to get policies in vendor {}", vendor); ++ Err(crate::result::AsError::from(e)) ++ } ++ } ++ } ++ } ++} ++ ++#[post("/resource/policy")] ++pub async fn set_resource_policy( ++ body: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ let agent = agent.read().await; ++ let admin = agent.resource_admin.lock().await; ++ let op = body.0; ++ ++ match op { ++ SetResourcePolicyOp::Add { policy, content } => { ++ admin ++ .add_policy(policy.clone(), &content) ++ .await ++ .map_err(|e| { ++ log::warn!("Failed to add policy {}: {}", policy, e); ++ e ++ })?; ++ } ++ SetResourcePolicyOp::Delete { policy } => { ++ admin.delete_policy(policy.clone()).await.map_err(|e| { ++ log::warn!("Failed to delete policy {}: {}", policy, e); ++ e ++ })?; ++ } ++ SetResourcePolicyOp::ClearAll { vendor } => { ++ admin ++ .clear_all_policies_in_vendor(&vendor) ++ .await ++ .map_err(|e| { ++ log::warn!("Failed to clear policies in vendor {}: {}", vendor, e); ++ e ++ })?; ++ } ++ } ++ ++ Ok(HttpResponse::Ok().body("successful")) ++} +diff --git a/service/attestation/attestation-service/service/src/restapi/resource/storage.rs b/service/attestation/attestation-service/service/src/restapi/resource/storage.rs +new file mode 100644 +index 0000000..7b90cda +--- /dev/null ++++ b/service/attestation/attestation-service/service/src/restapi/resource/storage.rs +@@ -0,0 +1,158 @@ ++/* ++* Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++* secGear is licensed under the Mulan PSL v2. ++* You can use this software according to the terms and conditions of the Mulan PSL v2. ++* You may obtain a copy of Mulan PSL v2 at: ++* http://license.coscl.org.cn/MulanPSL2 ++* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++* PURPOSE. ++* See the Mulan PSL v2 for more details. ++*/ ++use crate::result::{self, Result}; ++use crate::session::SessionMap; ++use crate::AttestationService; ++use actix_web::http::header::Header; ++use actix_web::web::Data; ++use actix_web::{get, post, web, HttpRequest, HttpResponse}; ++use actix_web_httpauth::headers::authorization::{Authorization, Bearer}; ++use anyhow::Context; ++use attestation_types::resource::ResourceLocation; ++use attestation_types::service::{GetResourceOp, SetResourceOp, SetResourceRequest}; ++use attestation_types::Claims; ++use log; ++use std::sync::Arc; ++use token_signer::verify; ++use tokio::sync::RwLock; ++ ++/// When the consumer request for resource, he should provide the vendor name which owns the resource. ++#[get("/resource/storage")] ++pub async fn get_resource( ++ req: HttpRequest, ++ body: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ log::info!("receive getting resource request"); ++ ++ let sessions = agent.read().await.get_sessions(); ++ let op = body.0; ++ ++ match op { ++ GetResourceOp::TeeGet { resource } => { ++ tee_get_resource(req, sessions, agent, resource).await ++ } ++ GetResourceOp::VendorGet { vendor } => { ++ vendor_get_resource(req, sessions, agent, &vendor).await ++ } ++ } ++} ++ ++async fn tee_get_resource( ++ req: HttpRequest, ++ sessions: Data, ++ agent: web::Data>>, ++ resource: ResourceLocation, ++) -> Result { ++ log::info!("receive tee getting resource request"); ++ ++ // If the corresponding session of the token exists, get the token inside the session. ++ // Otherwise, get the token from the http header. ++ let token = match { ++ if let Some(cookie) = req.cookie("oeas-session-id") { ++ sessions ++ .session_map ++ .get_async(cookie.value()) ++ .await ++ .map(|session| session.get_token()) ++ .flatten() ++ .map(|t| { ++ log::debug!("Get token from session {}", cookie.value()); ++ t ++ }) ++ } else { ++ None ++ } ++ } { ++ Some(token) => token, ++ None => { ++ let bearer = Authorization::::parse(&req) ++ .context("failed to parse bearer token")? ++ .into_scheme(); ++ log::debug!("Get token from headers"); ++ bearer.token().to_string() ++ } ++ }; ++ ++ let claim: Claims = verify(&token).context("illegal token")?; ++ let claim: String = serde_json::to_string(&claim)?; ++ ++ log::debug!("Resource path: {}", resource); ++ log::debug!("Receive claim: {}", claim); ++ ++ match agent ++ .read() ++ .await ++ .resource_evaluate(resource.clone(), &claim) ++ .await ++ { ++ Ok(r) => { ++ if r { ++ log::debug!("Resource evaluate success."); ++ let content = agent.read().await.get_resource(resource).await?; ++ ++ Ok(HttpResponse::Ok().body(content)) ++ } else { ++ log::debug!("Resource evaluate fail."); ++ Ok(HttpResponse::BadRequest().body("resource evaluation failed")) ++ } ++ } ++ Err(e) => { ++ log::debug!("{}", e); ++ Err(result::AsError::Resource( ++ attestation_types::resource::error::ResourceError::LoadPolicy(e), ++ )) ++ } ++ } ++} ++ ++async fn vendor_get_resource( ++ _req: HttpRequest, ++ _sessions: Data, ++ agent: web::Data>>, ++ vendor: &str, ++) -> Result { ++ log::info!("receive vendor getting resource request"); ++ ++ let resource_list: Vec = agent ++ .read() ++ .await ++ .list_resource(vendor) ++ .await? ++ .iter() ++ .map(|v| v.to_string()) ++ .collect(); ++ ++ Ok(HttpResponse::Ok().body(serde_json::to_string(&resource_list)?)) ++} ++ ++#[post("/resource/storage")] ++pub async fn set_resource( ++ body: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ log::info!("receive vendor setting resource request"); ++ ++ let agent = agent.read().await; ++ let admin = agent.resource_admin.lock().await; ++ let resource = body.0.resource.clone(); ++ match body.op.clone() { ++ SetResourceOp::Add { content, policy } => { ++ admin.add_resource(resource, content, policy).await? ++ } ++ SetResourceOp::Delete => admin.del_resource(resource).await?, ++ SetResourceOp::Modify { content } => admin.modify_resource(resource, content).await?, ++ SetResourceOp::Bind { policy } => admin.bind_policy(resource, policy).await?, ++ SetResourceOp::Unbind { policy } => admin.unbind_policy(resource, policy).await?, ++ } ++ Ok(HttpResponse::Ok().body("successful")) ++} +diff --git a/service/attestation/attestation-service/service/src/result/mod.rs b/service/attestation/attestation-service/service/src/result/mod.rs +index 7261d19..96b5a73 100644 +--- a/service/attestation/attestation-service/service/src/result/mod.rs ++++ b/service/attestation/attestation-service/service/src/result/mod.rs +@@ -9,14 +9,14 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use thiserror::Error; + use actix_web::{body::BoxBody, HttpResponse, ResponseError}; +-pub type Result = std::result::Result; ++use thiserror::Error; ++pub type Result = std::result::Result; + + #[derive(Debug, Error)] + //#[non_exhaustive] + //#[allow(missing_docs)] +-pub enum Error { ++pub enum AsError { + #[error("IO error: {source:?}")] + Io { + #[from] +@@ -67,11 +67,20 @@ pub enum Error { + #[error("Request challenge is invalid")] + ChallengeInvalid, + ++ #[error("Request Prameter is invalid")] ++ ParameterInvalid(String), ++ ++ #[error("Illegal token")] ++ TokenIllegal, ++ ++ #[error("Resource Error: {0}")] ++ Resource(#[from] attestation_types::resource::error::ResourceError), ++ + #[error(transparent)] + Other(#[from] anyhow::Error), + } + +-impl ResponseError for Error { ++impl ResponseError for AsError { + fn error_response(&self) -> HttpResponse { + HttpResponse::InternalServerError().body(BoxBody::new(format!("{self:#?}"))) + } +diff --git a/service/attestation/attestation-service/service/src/session.rs b/service/attestation/attestation-service/service/src/session.rs +index 2aee35a..be9f56e 100644 +--- a/service/attestation/attestation-service/service/src/session.rs ++++ b/service/attestation/attestation-service/service/src/session.rs +@@ -9,7 +9,10 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use actix_web::cookie::{time::{Duration, OffsetDateTime}, Cookie}; ++use actix_web::cookie::{ ++ time::{Duration, OffsetDateTime}, ++ Cookie, ++}; + use scc::HashMap; + use uuid::Uuid; + +@@ -17,6 +20,13 @@ pub struct Session { + pub id: String, + pub challenge: String, + timeout: OffsetDateTime, ++ status: SessionStatus, ++} ++ ++enum SessionStatus { ++ Challenge, ++ // carry token ++ Attested(String), + } + + impl Session { +@@ -27,6 +37,7 @@ impl Session { + id, + challenge, + timeout, ++ status: SessionStatus::Challenge, + } + } + pub fn is_expired(&self) -> bool { +@@ -34,8 +45,21 @@ impl Session { + } + pub fn cookie(&self) -> Cookie { + Cookie::build("oeas-session-id", self.id.clone()) +- .expires(self.timeout.clone()) +- .finish() ++ .expires(self.timeout.clone()) ++ .finish() ++ } ++ ++ /// Update status of the session. ++ pub fn update(&mut self, token: String) { ++ self.status = SessionStatus::Attested(token); ++ } ++ ++ /// Get token if the session status is attested. ++ pub fn get_token(&self) -> Option { ++ match &self.status { ++ SessionStatus::Attested(t) => Some(t.clone()), ++ _ => None, ++ } + } + } + +@@ -52,4 +76,4 @@ impl SessionMap { + pub fn insert(&self, session: Session) { + let _ = self.session_map.insert(session.id.clone(), session); + } +-} +\ No newline at end of file ++} +diff --git a/service/attestation/attestation-service/tests/Cargo.toml b/service/attestation/attestation-service/tests/Cargo.toml +index 0fde476..70112b3 100644 +--- a/service/attestation/attestation-service/tests/Cargo.toml ++++ b/service/attestation/attestation-service/tests/Cargo.toml +@@ -5,5 +5,5 @@ edition = "2021" + + [dependencies] + serde_json = "1.0.116" +-reqwest = {version = "0.12.5", features = ["blocking"]} ++reqwest = { version = "0.12.5", features = ["blocking"] } + rand = "0.8.5" +diff --git a/service/attestation/attestation-service/tests/src/lib.rs b/service/attestation/attestation-service/tests/src/lib.rs +index b8adb1e..79a96de 100644 +--- a/service/attestation/attestation-service/tests/src/lib.rs ++++ b/service/attestation/attestation-service/tests/src/lib.rs +@@ -161,12 +161,8 @@ mod tests { + fn api_get_challenge() { + let client: Client = Client::new(); + let endpoint = "http://127.0.0.1:8080/challenge"; +- let res = client +- .get(endpoint) +- .send() +- .unwrap(); ++ let res = client.get(endpoint).send().unwrap(); + assert_eq!(res.status(), reqwest::StatusCode::OK); + println!("{:?}", res.text().unwrap()); + } +- + } +diff --git a/service/attestation/attestation-service/token/Cargo.toml b/service/attestation/attestation-service/token/Cargo.toml +index 029008a..ac58c6c 100644 +--- a/service/attestation/attestation-service/token/Cargo.toml ++++ b/service/attestation/attestation-service/token/Cargo.toml +@@ -11,4 +11,4 @@ serde.workspace = true + serde_json.workspace = true + anyhow.workspace = true + attestation-types.workspace = true +-thiserror.workspace = true +\ No newline at end of file ++thiserror.workspace = true +diff --git a/service/attestation/attestation-service/token/src/lib.rs b/service/attestation/attestation-service/token/src/lib.rs +index 3ee785e..92f427f 100644 +--- a/service/attestation/attestation-service/token/src/lib.rs ++++ b/service/attestation/attestation-service/token/src/lib.rs +@@ -9,17 +9,18 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use anyhow::{Result}; +-use jsonwebtoken::{encode, get_current_timestamp, +- Algorithm, EncodingKey, Header, ++use anyhow::Result; ++use attestation_types::{Claims, EvlResult}; ++use jsonwebtoken::{ ++ decode, decode_header, encode, get_current_timestamp, Algorithm, DecodingKey, EncodingKey, ++ Header, Validation, + }; +-use std::path::Path; + use serde::{Deserialize, Serialize}; + use serde_json::Value; +-use attestation_types::{EvlResult, Claims}; ++use std::path::Path; + use thiserror; + +-#[derive(thiserror::Error, Debug)] ++#[derive(thiserror::Error, Debug)] + pub enum SignError { + #[error("get unix time fail:{0:?}")] + ToUnixTimeFail(#[from] std::num::TryFromIntError), +@@ -30,7 +31,7 @@ pub enum SignError { + #[error("key content read fail:{0}")] + ReadKeyFail(String), + #[error("sign fail:{0:?}")] +- SignFail(#[from] jsonwebtoken::errors::Error) ++ SignFail(#[from] jsonwebtoken::errors::Error), + } + + #[derive(Debug, Clone, Serialize, Deserialize)] +@@ -54,7 +55,6 @@ impl Default for TokenSignConfig { + } + } + +- + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct EvlReport { + pub tee: String, +@@ -79,15 +79,13 @@ impl TokenSigner { + pub fn new(config: TokenSignConfig) -> Result { + Ok(TokenSigner { config }) + } +- fn support_rs(alg: &Algorithm) -> bool +- { +- if *alg == Algorithm::RS256 || *alg == Algorithm::RS384 || *alg == Algorithm::RS512{ ++ fn support_rs(alg: &Algorithm) -> bool { ++ if *alg == Algorithm::RS256 || *alg == Algorithm::RS384 || *alg == Algorithm::RS512 { + return true; + } + return false; + } +- fn support_ps(alg: &Algorithm) -> bool +- { ++ fn support_ps(alg: &Algorithm) -> bool { + if *alg == Algorithm::PS256 || *alg == Algorithm::PS384 || *alg == Algorithm::PS512 { + return true; + } +@@ -108,21 +106,45 @@ impl TokenSigner { + tcb_status: report.tcb_status.clone(), + }; + if !Self::support_rs(&alg) && !Self::support_ps(&alg) { +- return Err(SignError::UnsupportAlg(format!("unknown algrithm {:?}", alg))); ++ return Err(SignError::UnsupportAlg(format!( ++ "unknown algrithm {:?}", ++ alg ++ ))); + } + if !Path::new(&self.config.key).exists() { +- return Err(SignError::UnsupportAlg(format!("token verfify failed, {:?} cert not exist", self.config.key))); ++ return Err(SignError::UnsupportAlg(format!( ++ "token verfify failed, {:?} cert not exist", ++ self.config.key ++ ))); + } + let key = std::fs::read(&self.config.key).unwrap(); + let key_value: EncodingKey = match EncodingKey::from_rsa_pem(&key) { + Ok(val) => val, +- _ => {return Err(SignError::ReadKeyFail(format!("get key from input error")));} ++ _ => { ++ return Err(SignError::ReadKeyFail(format!("get key from input error"))); ++ } + }; +- ++ + let token = match encode(&header, &claims, &key_value) { + Ok(val) => val, +- Err(e) => {return Err(SignError::SignFail(e));} ++ Err(e) => { ++ return Err(SignError::SignFail(e)); ++ } + }; + Ok(token) + } +-} +\ No newline at end of file ++} ++ ++pub fn verify(token: &String) -> Result { ++ let header = decode_header(&token)?; ++ let alg: Algorithm = header.alg; ++ ++ // todo: check support of verification algorithm ++ ++ let cert = std::fs::read("/etc/attestation/attestation-service/token/as_cert.pem").unwrap(); ++ let key_value = DecodingKey::from_rsa_pem(&cert)?; ++ let validation = Validation::new(alg); ++ ++ let data = decode::(&token, &key_value, &validation)?; ++ Ok(data.claims) ++} +diff --git a/service/attestation/attestation-service/verifier/Cargo.toml b/service/attestation/attestation-service/verifier/Cargo.toml +index e870fa7..baab873 100644 +--- a/service/attestation/attestation-service/verifier/Cargo.toml ++++ b/service/attestation/attestation-service/verifier/Cargo.toml +@@ -17,11 +17,15 @@ ima-measurements.workspace = true + rand.workspace = true + fallible-iterator.workspace = true + attestation-types.workspace = true ++ccatoken.workspace = true ++ear.workspace = true ++base64-url.workspace = true + + [dev-dependencies] + + [features] +-default = [ "itrustee-verifier","virtcca-verifier" ] ++default = ["itrustee-verifier", "virtcca-verifier"] + itrustee-verifier = [] + virtcca-verifier = [] ++rustcca-verifier = [] + no_as = [] +diff --git a/service/attestation/attestation-service/verifier/src/itrustee/mod.rs b/service/attestation/attestation-service/verifier/src/itrustee/mod.rs +index 8ce4d24..029f751 100644 +--- a/service/attestation/attestation-service/verifier/src/itrustee/mod.rs ++++ b/service/attestation/attestation-service/verifier/src/itrustee/mod.rs +@@ -15,12 +15,13 @@ + use super::*; + use log; + use serde_json::json; +-use std::path::Path; + use std::ops::Add; ++use std::path::Path; + + mod itrustee; + +-const ITRUSTEE_REF_VALUE_FILE: &str = "/etc/attestation/attestation-service/verifier/itrustee/basevalue.txt"; ++const ITRUSTEE_REF_VALUE_FILE: &str = ++ "/etc/attestation/attestation-service/verifier/itrustee/basevalue.txt"; + + #[derive(Debug, Default)] + pub struct ItrusteeVerifier {} +@@ -30,9 +31,23 @@ impl ItrusteeVerifier { + return evalute_wrapper(user_data, evidence); + } + } +- ++const MAX_CHALLENGE_LEN: usize = 64; + fn evalute_wrapper(user_data: &[u8], evidence: &[u8]) -> Result { +- let mut in_data = user_data.to_vec(); ++ let challenge = base64_url::decode(user_data)?; ++ let len = challenge.len(); ++ if len <= 0 || len > MAX_CHALLENGE_LEN { ++ log::error!( ++ "challenge len is error, expecting 0 < len <= {}, got {}", ++ MAX_CHALLENGE_LEN, ++ len ++ ); ++ bail!( ++ "challenge len is error, expecting 0 < len <= {}, got {}", ++ MAX_CHALLENGE_LEN, ++ len ++ ); ++ } ++ let mut in_data = challenge.to_vec(); + let mut in_evidence = evidence.to_vec(); + let mut data_buf: itrustee::buffer_data = itrustee::buffer_data { + size: in_evidence.len() as ::std::os::raw::c_uint, +@@ -43,10 +58,16 @@ fn evalute_wrapper(user_data: &[u8], evidence: &[u8]) -> Result { + buf: in_data.as_mut_ptr() as *mut ::std::os::raw::c_uchar, + }; + +- let policy: std::os::raw::c_int = 1; // 1: verify ta_imag; 2: verfiy ta_mem; 3: verify ta_img and ta_mem hash; ++ let policy: std::os::raw::c_int = 1; // 1: verify ta_imag; 2: verfiy ta_mem; 3: verify ta_img and ta_mem hash; + if !Path::new(ITRUSTEE_REF_VALUE_FILE).exists() { +- log::error!("itrustee verify report {} not exists", ITRUSTEE_REF_VALUE_FILE); +- bail!("itrustee verify report {} not exists", ITRUSTEE_REF_VALUE_FILE); ++ log::error!( ++ "itrustee verify report {} not exists", ++ ITRUSTEE_REF_VALUE_FILE ++ ); ++ bail!( ++ "itrustee verify report {} not exists", ++ ITRUSTEE_REF_VALUE_FILE ++ ); + } + let ref_file = String::from(ITRUSTEE_REF_VALUE_FILE); + let mut file = ref_file.add("\0"); +diff --git a/service/attestation/attestation-service/verifier/src/lib.rs b/service/attestation/attestation-service/verifier/src/lib.rs +index 0b776c2..43894db 100644 +--- a/service/attestation/attestation-service/verifier/src/lib.rs ++++ b/service/attestation/attestation-service/verifier/src/lib.rs +@@ -11,12 +11,12 @@ + */ + + //! Unified tee verifier +-//! ++//! + //! This crate provides unified APIs to verify TEE evidence. + + use anyhow::*; +-use serde_json; + use async_trait::async_trait; ++use serde_json; + + use attestation_types::{Evidence, TeeType}; + +@@ -26,6 +26,9 @@ pub mod itrustee; + #[cfg(feature = "virtcca-verifier")] + pub mod virtcca; + ++#[cfg(feature = "rustcca-verifier")] ++pub mod rustcca; ++ + pub type TeeClaim = serde_json::Value; + + #[derive(Debug, Default)] +@@ -36,25 +39,41 @@ pub trait VerifierAPIs { + async fn verify_evidence(&self, user_data: &[u8], evidence: &[u8]) -> Result; + } + +-const MAX_CHALLENGE_LEN: usize = 64; +- + #[async_trait] + impl VerifierAPIs for Verifier { + async fn verify_evidence(&self, user_data: &[u8], evidence: &[u8]) -> Result { +- let len = user_data.len(); +- if len <= 0 || len > MAX_CHALLENGE_LEN { +- log::error!("challenge len is error, expecting 0 < len <= {}, got {}", MAX_CHALLENGE_LEN, len); +- bail!("challenge len is error, expecting 0 < len <= {}, got {}", MAX_CHALLENGE_LEN, len); +- } ++ + let aa_evidence: Evidence = serde_json::from_slice(evidence)?; + let tee_type = aa_evidence.tee; + let evidence = aa_evidence.evidence.as_bytes(); + match tee_type { + #[cfg(feature = "itrustee-verifier")] +- TeeType::Itrustee => itrustee::ItrusteeVerifier::default().evaluate(user_data, evidence).await, ++ TeeType::Itrustee => { ++ itrustee::ItrusteeVerifier::default() ++ .evaluate(user_data, evidence) ++ .await ++ } + #[cfg(feature = "virtcca-verifier")] +- TeeType::Virtcca => virtcca::VirtCCAVerifier::default().evaluate(user_data, evidence).await, ++ TeeType::Virtcca => { ++ virtcca::VirtCCAVerifier::default() ++ .evaluate(user_data, evidence) ++ .await ++ } ++ #[cfg(feature = "rustcca-verifier")] ++ TeeType::Rustcca => { ++ rustcca::RustCCAVerifier::default() ++ .evaluate(user_data, evidence) ++ .await ++ } + _ => bail!("unsupported tee type:{:?}", tee_type), + } + } + } ++ ++#[cfg(feature = "no_as")] ++pub fn virtcca_parse_evidence(evidence: &[u8]) -> Result { ++ let aa_evidence: Evidence = serde_json::from_slice(evidence)?; ++ let evidence = aa_evidence.evidence.as_bytes(); ++ ++ return virtcca::Evidence::parse_evidence(evidence); ++} +diff --git a/service/attestation/attestation-service/verifier/src/rustcca/LICENSE b/service/attestation/attestation-service/verifier/src/rustcca/LICENSE +new file mode 100644 +index 0000000..2815c82 +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/rustcca/LICENSE +@@ -0,0 +1,201 @@ ++ Apache License ++ Version 2.0, January 2004 ++ http://www.apache.org/licenses/ ++ ++ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION ++ ++ 1. Definitions. ++ ++ "License" shall mean the terms and conditions for use, reproduction, ++ and distribution as defined by Sections 1 through 9 of this document. ++ ++ "Licensor" shall mean the copyright owner or entity authorized by ++ the copyright owner that is granting the License. ++ ++ "Legal Entity" shall mean the union of the acting entity and all ++ other entities that control, are controlled by, or are under common ++ control with that entity. For the purposes of this definition, ++ "control" means (i) the power, direct or indirect, to cause the ++ direction or management of such entity, whether by contract or ++ otherwise, or (ii) ownership of fifty percent (50%) or more of the ++ outstanding shares, or (iii) beneficial ownership of such entity. ++ ++ "You" (or "Your") shall mean an individual or Legal Entity ++ exercising permissions granted by this License. ++ ++ "Source" form shall mean the preferred form for making modifications, ++ including but not limited to software source code, documentation ++ source, and configuration files. ++ ++ "Object" form shall mean any form resulting from mechanical ++ transformation or translation of a Source form, including but ++ not limited to compiled object code, generated documentation, ++ and conversions to other media types. ++ ++ "Work" shall mean the work of authorship, whether in Source or ++ Object form, made available under the License, as indicated by a ++ copyright notice that is included in or attached to the work ++ (an example is provided in the Appendix below). ++ ++ "Derivative Works" shall mean any work, whether in Source or Object ++ form, that is based on (or derived from) the Work and for which the ++ editorial revisions, annotations, elaborations, or other modifications ++ represent, as a whole, an original work of authorship. For the purposes ++ of this License, Derivative Works shall not include works that remain ++ separable from, or merely link (or bind by name) to the interfaces of, ++ the Work and Derivative Works thereof. ++ ++ "Contribution" shall mean any work of authorship, including ++ the original version of the Work and any modifications or additions ++ to that Work or Derivative Works thereof, that is intentionally ++ submitted to Licensor for inclusion in the Work by the copyright owner ++ or by an individual or Legal Entity authorized to submit on behalf of ++ the copyright owner. For the purposes of this definition, "submitted" ++ means any form of electronic, verbal, or written communication sent ++ to the Licensor or its representatives, including but not limited to ++ communication on electronic mailing lists, source code control systems, ++ and issue tracking systems that are managed by, or on behalf of, the ++ Licensor for the purpose of discussing and improving the Work, but ++ excluding communication that is conspicuously marked or otherwise ++ designated in writing by the copyright owner as "Not a Contribution." ++ ++ "Contributor" shall mean Licensor and any individual or Legal Entity ++ on behalf of whom a Contribution has been received by Licensor and ++ subsequently incorporated within the Work. ++ ++ 2. Grant of Copyright License. Subject to the terms and conditions of ++ this License, each Contributor hereby grants to You a perpetual, ++ worldwide, non-exclusive, no-charge, royalty-free, irrevocable ++ copyright license to reproduce, prepare Derivative Works of, ++ publicly display, publicly perform, sublicense, and distribute the ++ Work and such Derivative Works in Source or Object form. ++ ++ 3. Grant of Patent License. Subject to the terms and conditions of ++ this License, each Contributor hereby grants to You a perpetual, ++ worldwide, non-exclusive, no-charge, royalty-free, irrevocable ++ (except as stated in this section) patent license to make, have made, ++ use, offer to sell, sell, import, and otherwise transfer the Work, ++ where such license applies only to those patent claims licensable ++ by such Contributor that are necessarily infringed by their ++ Contribution(s) alone or by combination of their Contribution(s) ++ with the Work to which such Contribution(s) was submitted. If You ++ institute patent litigation against any entity (including a ++ cross-claim or counterclaim in a lawsuit) alleging that the Work ++ or a Contribution incorporated within the Work constitutes direct ++ or contributory patent infringement, then any patent licenses ++ granted to You under this License for that Work shall terminate ++ as of the date such litigation is filed. ++ ++ 4. Redistribution. You may reproduce and distribute copies of the ++ Work or Derivative Works thereof in any medium, with or without ++ modifications, and in Source or Object form, provided that You ++ meet the following conditions: ++ ++ (a) You must give any other recipients of the Work or ++ Derivative Works a copy of this License; and ++ ++ (b) You must cause any modified files to carry prominent notices ++ stating that You changed the files; and ++ ++ (c) You must retain, in the Source form of any Derivative Works ++ that You distribute, all copyright, patent, trademark, and ++ attribution notices from the Source form of the Work, ++ excluding those notices that do not pertain to any part of ++ the Derivative Works; and ++ ++ (d) If the Work includes a "NOTICE" text file as part of its ++ distribution, then any Derivative Works that You distribute must ++ include a readable copy of the attribution notices contained ++ within such NOTICE file, excluding those notices that do not ++ pertain to any part of the Derivative Works, in at least one ++ of the following places: within a NOTICE text file distributed ++ as part of the Derivative Works; within the Source form or ++ documentation, if provided along with the Derivative Works; or, ++ within a display generated by the Derivative Works, if and ++ wherever such third-party notices normally appear. The contents ++ of the NOTICE file are for informational purposes only and ++ do not modify the License. You may add Your own attribution ++ notices within Derivative Works that You distribute, alongside ++ or as an addendum to the NOTICE text from the Work, provided ++ that such additional attribution notices cannot be construed ++ as modifying the License. ++ ++ You may add Your own copyright statement to Your modifications and ++ may provide additional or different license terms and conditions ++ for use, reproduction, or distribution of Your modifications, or ++ for any such Derivative Works as a whole, provided Your use, ++ reproduction, and distribution of the Work otherwise complies with ++ the conditions stated in this License. ++ ++ 5. Submission of Contributions. Unless You explicitly state otherwise, ++ any Contribution intentionally submitted for inclusion in the Work ++ by You to the Licensor shall be under the terms and conditions of ++ this License, without any additional terms or conditions. ++ Notwithstanding the above, nothing herein shall supersede or modify ++ the terms of any separate license agreement you may have executed ++ with Licensor regarding such Contributions. ++ ++ 6. Trademarks. This License does not grant permission to use the trade ++ names, trademarks, service marks, or product names of the Licensor, ++ except as required for reasonable and customary use in describing the ++ origin of the Work and reproducing the content of the NOTICE file. ++ ++ 7. Disclaimer of Warranty. Unless required by applicable law or ++ agreed to in writing, Licensor provides the Work (and each ++ Contributor provides its Contributions) on an "AS IS" BASIS, ++ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or ++ implied, including, without limitation, any warranties or conditions ++ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A ++ PARTICULAR PURPOSE. You are solely responsible for determining the ++ appropriateness of using or redistributing the Work and assume any ++ risks associated with Your exercise of permissions under this License. ++ ++ 8. Limitation of Liability. In no event and under no legal theory, ++ whether in tort (including negligence), contract, or otherwise, ++ unless required by applicable law (such as deliberate and grossly ++ negligent acts) or agreed to in writing, shall any Contributor be ++ liable to You for damages, including any direct, indirect, special, ++ incidental, or consequential damages of any character arising as a ++ result of this License or out of the use or inability to use the ++ Work (including but not limited to damages for loss of goodwill, ++ work stoppage, computer failure or malfunction, or any and all ++ other commercial damages or losses), even if such Contributor ++ has been advised of the possibility of such damages. ++ ++ 9. Accepting Warranty or Additional Liability. While redistributing ++ the Work or Derivative Works thereof, You may choose to offer, ++ and charge a fee for, acceptance of support, warranty, indemnity, ++ or other liability obligations and/or rights consistent with this ++ License. However, in accepting such obligations, You may act only ++ on Your own behalf and on Your sole responsibility, not on behalf ++ of any other Contributor, and only if You agree to indemnify, ++ defend, and hold each Contributor harmless for any liability ++ incurred by, or claims asserted against, such Contributor by reason ++ of your accepting any such warranty or additional liability. ++ ++ END OF TERMS AND CONDITIONS ++ ++ APPENDIX: How to apply the Apache License to your work. ++ ++ To apply the Apache License to your work, attach the following ++ boilerplate notice, with the fields enclosed by brackets "[]" ++ replaced with your own identifying information. (Don't include ++ the brackets!) The text should be enclosed in the appropriate ++ comment syntax for the file format. We also recommend that a ++ file or class name and description of purpose be included on the ++ same "printed page" as the copyright notice for easier ++ identification within third-party archives. ++ ++ Copyright 2023 Contributors to the Veraison project. ++ ++ Licensed under the Apache License, Version 2.0 (the "License"); ++ you may not use this file except in compliance with the License. ++ You may obtain a copy of the License at ++ ++ http://www.apache.org/licenses/LICENSE-2.0 ++ ++ Unless required by applicable law or agreed to in writing, software ++ distributed under the License is distributed on an "AS IS" BASIS, ++ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ See the License for the specific language governing permissions and ++ limitations under the License. +diff --git a/service/attestation/attestation-service/verifier/src/rustcca/mod.rs b/service/attestation/attestation-service/verifier/src/rustcca/mod.rs +new file mode 100644 +index 0000000..bd2da4c +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/rustcca/mod.rs +@@ -0,0 +1,246 @@ ++// Copyright 2023 Contributors to the Veraison project. ++// SPDX-License-Identifier: Apache-2.0 ++ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! rust-cca verifier plugin ++use super::TeeClaim; ++use anyhow::{bail, Result}; ++use ear::claim::*; ++use serde_json::json; ++extern crate ccatoken; ++use ccatoken::store::{ ++ Cpak, MemoRefValueStore, MemoTrustAnchorStore, PlatformRefValue, RealmRefValue, RefValues, ++ SwComponent, ++}; ++use ccatoken::token; ++ ++use serde_json::value::RawValue; ++use std::error::Error; ++ ++const TEST_CPAK: &str = include_str!("../../test_data/cpak.json"); ++ ++#[derive(Debug, Default)] ++pub struct RustCCAVerifier {} ++ ++impl RustCCAVerifier { ++ pub async fn evaluate(&self, user_data: &[u8], evidence: &[u8]) -> Result { ++ return evalute_wrapper(user_data, evidence); ++ } ++} ++ ++//参数是challenge 和report ++// 1. execute golden to get tas, rvs ++// 2. execute verify ++fn evalute_wrapper(user_data: &[u8], evidence: &[u8]) -> Result { ++ let mut in_evidence = ++ token::Evidence::decode(&evidence.to_vec()).unwrap_or_else(|_| panic!("decode evidence")); ++ ++ let cpak = map_str_to_cpak(&in_evidence.platform_claims, &TEST_CPAK) ++ .unwrap_or_else(|_| panic!("map cpak")); ++ let _ = in_evidence ++ .verify_with_cpak(cpak) ++ .unwrap_or_else(|_| panic!("verify cpak")); ++ ++ let (platform_tvec, realm_tvec) = in_evidence.get_trust_vectors(); ++ if platform_tvec.instance_identity != TRUSTWORTHY_INSTANCE { ++ bail!("platform is not trustworthy"); ++ } ++ if realm_tvec.instance_identity != TRUSTWORTHY_INSTANCE { ++ bail!("realm is not trustworthy"); ++ } ++ ++ let rv = map_evidence_to_refval(&in_evidence).unwrap_or_else(|_| panic!("map refval")); ++ let ta = map_evidence_to_trustanchor(&in_evidence.platform_claims, &TEST_CPAK) ++ .unwrap_or_else(|_| panic!("map trustanchor")); ++ ++ let mut rvs: MemoRefValueStore = Default::default(); ++ rvs.load_json(&rv).unwrap_or_else(|_| panic!("load rvs")); ++ let mut tas: MemoTrustAnchorStore = Default::default(); ++ tas.load_json(&ta).unwrap_or_else(|_| panic!("load tas")); ++ let _ = in_evidence.verify(&tas); ++ ++ //verify challenge ++ let _ = verify_realm_challenge(user_data, &in_evidence.realm_claims); ++ ++ let payload = json!({ ++ "platform trust vector": serde_json::to_string_pretty(&platform_tvec).unwrap(), ++ "realm trust vector" : serde_json::to_string_pretty(&realm_tvec).unwrap(), ++ "realm" : { ++ "challenge" : hex::encode(in_evidence.realm_claims.challenge.clone()), ++ "perso" : hex::encode(in_evidence.realm_claims.perso.clone()), ++ "hash_alg" : hex::encode(in_evidence.realm_claims.hash_alg.clone()), ++ "rak" : hex::encode(in_evidence.realm_claims.rak.clone()) ++ } ++ }); ++ ++ let claim = json!({ ++ "tee_type": "ccatoken", ++ "payload" : payload, ++ }); ++ Ok(claim as TeeClaim) ++} ++ ++fn verify_realm_challenge(challenge: &[u8], realm_token: &token::Realm) -> Result<()> { ++ let len = challenge.len(); ++ let token_challenge = &realm_token.challenge[0..len]; ++ ++ if challenge != token_challenge { ++ log::error!( ++ "verify cvm token challenge error, cvm_token challenge {:?}, input challenge {:?}", ++ token_challenge, ++ challenge ++ ); ++ bail!( ++ "verify cvm token challenge error, cvm_token challenge {:?}, input challenge {:?}", ++ token_challenge, ++ challenge ++ ); ++ } ++ ++ Ok(()) ++} ++fn map_str_to_cpak(p: &token::Platform, cpak_str: &str) -> Result> { ++ let raw_pkey = RawValue::from_string(cpak_str.to_string())?; ++ ++ let mut v = Cpak { ++ raw_pkey, ++ inst_id: p.inst_id, ++ impl_id: p.impl_id, ++ ..Default::default() ++ }; ++ v.parse_pkey()?; ++ Ok(v) ++} ++ ++fn map_evidence_to_refval(e: &token::Evidence) -> Result> { ++ let prv = map_evidence_to_platform_refval(&e.platform_claims)?; ++ let rrv = map_evidence_to_realm_refval(&e.realm_claims)?; ++ ++ let rvs: RefValues = RefValues { ++ platform: Some(vec![prv]), ++ realm: Some(vec![rrv]), ++ }; ++ ++ let j = serde_json::to_string_pretty(&rvs)?; ++ ++ Ok(j) ++} ++ ++fn map_evidence_to_platform_refval( ++ p: &token::Platform, ++) -> Result> { ++ let mut v = PlatformRefValue { ++ impl_id: p.impl_id, ++ config: p.config.clone(), ++ ..Default::default() ++ }; ++ ++ for other in &p.sw_components { ++ let swc = SwComponent { ++ mval: other.mval.clone(), ++ signer_id: other.signer_id.clone(), ++ version: other.version.clone(), ++ mtyp: other.mtyp.clone(), ++ }; ++ ++ v.sw_components.push(swc) ++ } ++ ++ Ok(v) ++} ++ ++fn map_evidence_to_realm_refval(p: &token::Realm) -> Result> { ++ let mut v = RealmRefValue { ++ perso: p.perso.to_vec(), ++ rim: p.rim.clone(), ++ rak_hash_alg: p.rak_hash_alg.clone(), ++ ..Default::default() ++ }; ++ ++ for (i, other) in p.rem.iter().enumerate() { ++ v.rem[i].value.clone_from(other); ++ } ++ ++ Ok(v) ++} ++ ++fn map_evidence_to_trustanchor(p: &token::Platform, cpak: &str) -> Result> { ++ let raw_pkey = RawValue::from_string(cpak.to_string())?; ++ ++ let v = Cpak { ++ raw_pkey, ++ inst_id: p.inst_id, ++ impl_id: p.impl_id, ++ ..Default::default() // pkey is not serialised ++ }; ++ ++ let j = serde_json::to_string_pretty(&vec![v])?; ++ ++ Ok(j) ++} ++#[cfg(test)] ++mod tests { ++ use super::*; ++ use ear::claim::TRUSTWORTHY_INSTANCE; ++ ++ const TEST_CCA_TOKEN: &[u8; 1222] = include_bytes!("../../test_data/cca-token-01.cbor"); ++ //const TEST_CCA_TOKEN: &[u8; 1125] = include_bytes!("../../test_data/cca-token-02.cbor"); ++ const TEST_CPAK: &str = include_str!("../../test_data/cpak.json"); ++ ++ #[test] ++ fn cca_test() -> Result<(), Box> { ++ let mut evidence = ++ token::Evidence::decode(&TEST_CCA_TOKEN.to_vec()).expect("decoding TEST_CCA_TOKEN"); ++ ++ let j = TEST_CPAK; ++ let cpak = map_str_to_cpak(&evidence.platform_claims, &j)?; ++ let _ = evidence.verify_with_cpak(cpak)?; ++ ++ let (platform_tvec, realm_tvec) = evidence.get_trust_vectors(); ++ if platform_tvec.instance_identity != TRUSTWORTHY_INSTANCE { ++ return Err("platform is not trustworthy".into()); ++ } ++ if realm_tvec.instance_identity != TRUSTWORTHY_INSTANCE { ++ return Err("realm is not trustworthy".into()); ++ } ++ ++ let rv = map_evidence_to_refval(&evidence)?; ++ let ta = map_evidence_to_trustanchor(&evidence.platform_claims, &j)?; ++ ++ let mut rvs: MemoRefValueStore = Default::default(); ++ rvs.load_json(&rv)?; ++ let mut tas: MemoTrustAnchorStore = Default::default(); ++ tas.load_json(&ta)?; ++ let _ = evidence.verify(&tas); ++ ++ let (platform_tvec, realm_tvec) = evidence.get_trust_vectors(); ++ let payload = json!({ ++ "platform trust vector": serde_json::to_string_pretty(&platform_tvec).unwrap(), ++ "realm trust vector" : serde_json::to_string_pretty(&realm_tvec).unwrap(), ++ "realm" : { ++ "challenge" : hex::encode(evidence.realm_claims.challenge.clone()), ++ "perso" : hex::encode(evidence.realm_claims.perso.clone()), ++ "hash_alg" : hex::encode(evidence.realm_claims.hash_alg.clone()), ++ "rak" : hex::encode(evidence.realm_claims.rak.clone()) ++ } ++ }); ++ ++ let claim = json!({ ++ "tee_type": "ccatoken", ++ "payload" : payload, ++ }); ++ println!("verify success {:?}", claim); ++ Ok(()) ++ } ++} +diff --git a/service/attestation/attestation-service/verifier/src/virtcca/ima.rs b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs +index 30a151f..220a52d 100644 +--- a/service/attestation/attestation-service/verifier/src/virtcca/ima.rs ++++ b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs +@@ -9,12 +9,19 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use anyhow::{Result, bail}; +-use ima_measurements::{Event, EventData, Parser}; ++use anyhow::{bail, Result}; + use fallible_iterator::FallibleIterator; +-use serde_json::{Value, Map, json}; ++use ima_measurements::{Event, EventData, Parser}; ++use serde_json::{json, Map, Value}; ++ ++#[cfg(not(feature = "no_as"))] ++const IMA_REFERENCE_FILE: &str = ++ "/etc/attestation/attestation-service/verifier/virtcca/ima/digest_list_file"; + +-const IMA_REFERENCE_FILE: &str = "/etc/attestation/attestation-service/verifier/virtcca/ima/digest_list_file"; ++// attestation agent local ima reference ++#[cfg(feature = "no_as")] ++const IMA_REFERENCE_FILE: &str = ++ "/etc/attestation/attestation-agent/local_verifier/virtcca/ima/digest_list_file"; + + #[derive(Debug, Default)] + pub struct ImaVerify {} +@@ -35,10 +42,13 @@ impl ImaVerify { + let pcr_10 = pcr_values.get(&10).expect("PCR 10 not measured"); + let string_pcr_sha256 = hex::encode(pcr_10.sha256); + let string_ima_log_hash = hex::encode(ima_log_hash); +- ++ + if string_pcr_sha256.clone() != string_ima_log_hash { +- log::error!("ima log verify failed string_pcr_sha256 {}, string_ima_log_hash {}", +- string_pcr_sha256, string_ima_log_hash); ++ log::error!( ++ "ima log verify failed string_pcr_sha256 {}, string_ima_log_hash {}", ++ string_pcr_sha256, ++ string_ima_log_hash ++ ); + bail!("ima log hash verify failed"); + } + +@@ -50,15 +60,21 @@ impl ImaVerify { + let mut ima_detail = Map::new(); + // parser each file digest in ima log, and compare with reference base value + for event in events { +- let (name ,file_digest) = match event.data { +- EventData::ImaNg{digest, name} => (name, digest.digest), +- _ => bail!("Inalid event {:?}", event), ++ let (name, file_digest) = match event.data { ++ EventData::ImaNg { digest, name } => (name, digest.digest), ++ _ => bail!("Invalid event {:?}", event), + }; ++ if name == "boot_aggregate".to_string() { ++ continue; ++ } + let hex_str_digest = hex::encode(file_digest); + if ima_refs.contains(&hex_str_digest) { + ima_detail.insert(name, Value::Bool(true)); + } else { +- log::error!("there is no refernce base value of file digest {:?}", hex_str_digest); ++ log::error!( ++ "there is no refernce base value of file digest {:?}", ++ hex_str_digest ++ ); + ima_detail.insert(name, Value::Bool(false)); + } + } +@@ -72,14 +88,16 @@ impl ImaVerify { + use std::io::BufRead; + use std::io::BufReader; + fn file_reader(file_path: &str) -> ::std::io::Result> { +- let file = std::fs::File::open(file_path)?; ++ let file = std::fs::File::open(file_path).expect("open ima reference file failed"); + let mut strings = Vec::::new(); + let mut reader = BufReader::new(file); + let mut buf = String::new(); + let mut n: usize; + loop { + n = reader.read_line(&mut buf)?; +- if n == 0 { break; } ++ if n == 0 { ++ break; ++ } + buf.pop(); + strings.push(buf.clone()); + buf.clear(); +diff --git a/service/attestation/attestation-service/verifier/src/virtcca/mod.rs b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs +index 3de7c9f..ff72f77 100644 +--- a/service/attestation/attestation-service/verifier/src/virtcca/mod.rs ++++ b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs +@@ -13,40 +13,60 @@ + //! virtcca verifier plugin + use super::TeeClaim; + +-use anyhow::{Result, bail, anyhow}; +-use cose::keys::CoseKey; +-use cose::message::CoseMessage; ++use anyhow::{anyhow, bail, Result}; + use ciborium; + use ciborium::Value; +-use openssl::rsa; ++use cose::keys::CoseKey; ++use cose::message::CoseMessage; ++use log; ++use openssl::pkey::PKey; + use openssl::pkey::Public; ++use openssl::rsa; + use openssl::x509; +-use openssl::pkey::PKey; +-use log; + use serde_json::json; + + pub use attestation_types::VirtccaEvidence; + pub mod ima; + + #[cfg(not(feature = "no_as"))] +-const VIRTCCA_ROOT_CERT: &str = "/etc/attestation/attestation-service/verifier/virtcca/Huawei Equipment Root CA.pem"; ++const VIRTCCA_ROOT_CERT: &str = ++ "/etc/attestation/attestation-service/verifier/virtcca/Huawei Equipment Root CA.pem"; + #[cfg(not(feature = "no_as"))] +-const VIRTCCA_SUB_CERT: &str = "/etc/attestation/attestation-service/verifier/virtcca/Huawei IT Product CA.pem"; ++const VIRTCCA_SUB_CERT: &str = ++ "/etc/attestation/attestation-service/verifier/virtcca/Huawei IT Product CA.pem"; + +-// attestation agent local reference ++// attestation agent local reference + #[cfg(feature = "no_as")] +-const VIRTCCA_REF_VALUE_FILE: &str = "/etc/attestation/attestation-agent/local_verifier/virtcca/ref_value.json"; ++const VIRTCCA_REF_VALUE_FILE: &str = ++ "/etc/attestation/attestation-agent/local_verifier/virtcca/ref_value.json"; + #[cfg(feature = "no_as")] +-const VIRTCCA_ROOT_CERT: &str = "/etc/attestation/attestation-agent/local_verifier/virtcca/Huawei Equipment Root CA.pem"; ++const VIRTCCA_ROOT_CERT: &str = ++ "/etc/attestation/attestation-agent/local_verifier/virtcca/Huawei Equipment Root CA.pem"; + #[cfg(feature = "no_as")] +-const VIRTCCA_SUB_CERT: &str = "/etc/attestation/attestation-agent/local_verifier/virtcca/Huawei IT Product CA.pem"; ++const VIRTCCA_SUB_CERT: &str = ++ "/etc/attestation/attestation-agent/local_verifier/virtcca/Huawei IT Product CA.pem"; + + #[derive(Debug, Default)] + pub struct VirtCCAVerifier {} + ++const MAX_CHALLENGE_LEN: usize = 64; + impl VirtCCAVerifier { + pub async fn evaluate(&self, user_data: &[u8], evidence: &[u8]) -> Result { +- return Evidence::verify(user_data, evidence); ++ let challenge = base64_url::decode(user_data)?; ++ let len = challenge.len(); ++ if len <= 0 || len > MAX_CHALLENGE_LEN { ++ log::error!( ++ "challenge len is error, expecting 0 < len <= {}, got {}", ++ MAX_CHALLENGE_LEN, ++ len ++ ); ++ bail!( ++ "challenge len is error, expecting 0 < len <= {}, got {}", ++ MAX_CHALLENGE_LEN, ++ len ++ ); ++ } ++ return Evidence::verify(&challenge.to_vec(), evidence); + } + } + +@@ -68,13 +88,13 @@ const CVM_PUB_KEY_SIZE: usize = 550; + + #[derive(Debug)] + pub struct CvmToken { +- pub challenge: [u8; CVM_CHALLENGE_SIZE], // 10 => bytes .size 64 +- pub rpv: [u8; CVM_RPV_SIZE], // 44235 => bytes .size 64 +- pub rim: Vec, // 44238 => bytes .size {32,48,64} +- pub rem: [Vec; CVM_REM_ARR_SIZE], // 44239 => [ 4*4 bytes .size {32,48,64} ] +- pub hash_alg: String, // 44236 => text +- pub pub_key: [u8; CVM_PUB_KEY_SIZE], // 44237 => bytes .size 550 +- pub pub_key_hash_alg: String, // 44240 => text ++ pub challenge: [u8; CVM_CHALLENGE_SIZE], // 10 => bytes .size 64 ++ pub rpv: [u8; CVM_RPV_SIZE], // 44235 => bytes .size 64 ++ pub rim: Vec, // 44238 => bytes .size {32,48,64} ++ pub rem: [Vec; CVM_REM_ARR_SIZE], // 44239 => [ 4*4 bytes .size {32,48,64} ] ++ pub hash_alg: String, // 44236 => text ++ pub pub_key: [u8; CVM_PUB_KEY_SIZE], // 44237 => bytes .size 550 ++ pub pub_key_hash_alg: String, // 44240 => text + } + + pub struct Evidence { +@@ -106,14 +126,27 @@ impl Evidence { + // verify ima + let ima_log = match virtcca_ev.ima_log { + Some(ima_log) => ima_log, +- _ => {log::info!("no ima log"); vec![]}, ++ _ => { ++ log::info!("no ima log"); ++ vec![] ++ } + }; +- let ima: serde_json::Value = ima::ImaVerify::default() +- .ima_verify(&ima_log, evidence.cvm_token.rem[0].clone())?; ++ let ima: serde_json::Value = ++ ima::ImaVerify::default().ima_verify(&ima_log, evidence.cvm_token.rem[0].clone())?; + + // todo parsed TeeClaim + evidence.parse_claim_from_evidence(ima) + } ++ pub fn parse_evidence(evidence: &[u8]) -> Result { ++ let virtcca_ev: VirtccaEvidence = serde_json::from_slice(evidence)?; ++ let evidence = virtcca_ev.evidence; ++ let evidence = Evidence::decode(evidence)?; ++ ++ let ima = json!(""); ++ // parsed TeeClaim ++ let claim = evidence.parse_claim_from_evidence(ima).unwrap(); ++ Ok(claim["payload"].clone() as TeeClaim) ++ } + fn parse_claim_from_evidence(&self, ima: serde_json::Value) -> Result { + let payload = json!({ + "vcca.cvm.challenge": hex::encode(self.cvm_token.challenge.clone()), +@@ -173,10 +206,16 @@ impl Evidence { + let len = challenge.len(); + let token_challenge = &self.cvm_token.challenge[0..len]; + if challenge != token_challenge { +- log::error!("verify cvm token challenge error, cvm_token challenge {:?}, input challenge {:?}", +- token_challenge, challenge); +- bail!("verify cvm token challenge error, cvm_token challenge {:?}, input challenge {:?}", +- token_challenge, challenge); ++ log::error!( ++ "verify cvm token challenge error, cvm_token challenge {:?}, input challenge {:?}", ++ token_challenge, ++ challenge ++ ); ++ bail!( ++ "verify cvm token challenge error, cvm_token challenge {:?}, input challenge {:?}", ++ token_challenge, ++ challenge ++ ); + } + + // todo verify cvm pubkey by platform.challenge, virtCCA report has no platform token now +@@ -189,8 +228,12 @@ impl Evidence { + Some(alg) => cose_key.alg(alg), + None => bail!("cose sign verify alg is none"), + } +- self.cvm_envelop.key(&cose_key).map_err(|err| anyhow!("set cose_key to COSE_Sign1 envelop failed: {err:?}"))?; +- self.cvm_envelop.decode(None, None).map_err(|err| anyhow!("verify COSE_Sign1 signature failed:{err:?}"))?; ++ self.cvm_envelop ++ .key(&cose_key) ++ .map_err(|err| anyhow!("set cose_key to COSE_Sign1 envelop failed: {err:?}"))?; ++ self.cvm_envelop ++ .decode(None, None) ++ .map_err(|err| anyhow!("verify COSE_Sign1 signature failed:{err:?}"))?; + // verify COSE_Sign1 signature end + + // verfiy cvm token with reference value +@@ -238,11 +281,22 @@ impl Evidence { + + // decode CBOR evidence to ciborium Value + let val: Value = ciborium::de::from_reader(raw_evidence.as_slice())?; +- log::debug!("[debug] decode CBOR virtcca token to ciborium Value:{:?}", val); ++ log::debug!( ++ "[debug] decode CBOR virtcca token to ciborium Value:{:?}", ++ val ++ ); + if let Value::Tag(t, m) = val { + if t != CBOR_TAG { +- log::error!("input evidence error, expecting tag {}, got {}", CBOR_TAG, t); +- bail!("input evidence error, expecting tag {}, got {}", CBOR_TAG, t); ++ log::error!( ++ "input evidence error, expecting tag {}, got {}", ++ CBOR_TAG, ++ t ++ ); ++ bail!( ++ "input evidence error, expecting tag {}, got {}", ++ CBOR_TAG, ++ t ++ ); + } + if let Value::Map(contents) = *m { + for (k, v) in contents.iter() { +@@ -268,7 +322,7 @@ impl Evidence { + Err(e) => { + log::error!("decode COSE failed, {:?}", e); + bail!("decode COSE failed"); +- }, ++ } + } + + // decode cvm CBOR payload +@@ -315,7 +369,7 @@ impl CvmToken { + CVM_HASH_ALG_LABEL => cvm_token.set_hash_alg(v)?, + CVM_PUB_KEY_LABEL => cvm_token.set_pub_key(v)?, + CVM_PUB_KEY_HASH_ALG_LABEL => cvm_token.set_pub_key_hash_alg(v)?, +- err => bail!("cvm payload unkown label {}", err), ++ err => bail!("cvm payload unknown label {}", err), + } + } else { + bail!("cvm payload expecting integer key"); +@@ -334,7 +388,11 @@ impl CvmToken { + } + let tmp = tmp.unwrap().clone(); + if tmp.len() != CVM_CHALLENGE_SIZE { +- bail!("cvm token challenge expecting {} bytes, got {}", CVM_CHALLENGE_SIZE,tmp.len()); ++ bail!( ++ "cvm token challenge expecting {} bytes, got {}", ++ CVM_CHALLENGE_SIZE, ++ tmp.len() ++ ); + } + self.challenge[..].clone_from_slice(&tmp); + Ok(()) +@@ -346,7 +404,11 @@ impl CvmToken { + } + let tmp = tmp.unwrap().clone(); + if tmp.len() != CVM_RPV_SIZE { +- bail!("cvm token rpv expecting {} bytes, got {}", CVM_RPV_SIZE, tmp.len()); ++ bail!( ++ "cvm token rpv expecting {} bytes, got {}", ++ CVM_RPV_SIZE, ++ tmp.len() ++ ); + } + self.rpv[..].clone_from_slice(&tmp); + Ok(()) +@@ -358,7 +420,11 @@ impl CvmToken { + } + let tmp = tmp.unwrap().clone(); + if !matches!(tmp.len(), 32 | 48 | 64) { +- bail!("cvm token {} expecting 32, 48 or 64 bytes, got {}", who, tmp.len()); ++ bail!( ++ "cvm token {} expecting 32, 48 or 64 bytes, got {}", ++ who, ++ tmp.len() ++ ); + } + Ok(tmp) + } +@@ -373,7 +439,11 @@ impl CvmToken { + } + let tmp = tmp.unwrap().clone(); + if tmp.len() != 4 { +- bail!("cvm token rem expecting size {}, got {}", CVM_REM_ARR_SIZE, tmp.len()); ++ bail!( ++ "cvm token rem expecting size {}, got {}", ++ CVM_REM_ARR_SIZE, ++ tmp.len() ++ ); + } + + for (i, val) in tmp.iter().enumerate() { +@@ -399,7 +469,11 @@ impl CvmToken { + } + let tmp = tmp.unwrap().clone(); + if tmp.len() != CVM_PUB_KEY_SIZE { +- bail!("cvm token pub key len expecting {}, got {}", CVM_PUB_KEY_SIZE, tmp.len()); ++ bail!( ++ "cvm token pub key len expecting {}, got {}", ++ CVM_PUB_KEY_SIZE, ++ tmp.len() ++ ); + } + self.pub_key[..].clone_from_slice(&tmp); + Ok(()) +diff --git a/service/attestation/attestation-types/Cargo.toml b/service/attestation/attestation-types/Cargo.toml +index 1fcf465..d50b822 100644 +--- a/service/attestation/attestation-types/Cargo.toml ++++ b/service/attestation/attestation-types/Cargo.toml +@@ -5,4 +5,13 @@ edition = "2021" + + [dependencies] + serde = { version = "1.0", features = ["derive"] } +-serde_json = "1.0" +\ No newline at end of file ++serde_json = "1.0" ++regorus = "0.2.8" ++base64 = "0.22.1" ++tokio = { version = "1.43.0", features = ["full"] } ++futures = "0.3.31" ++async-trait = "0.1.85" ++async-recursion = "1.1.1" ++anyhow = "1.0.95" ++thiserror = "2.0.10" ++log = "0.4.22" +diff --git a/service/attestation/attestation-types/src/lib.rs b/service/attestation/attestation-types/src/lib.rs +index 67dcf9f..82c124c 100644 +--- a/service/attestation/attestation-types/src/lib.rs ++++ b/service/attestation/attestation-types/src/lib.rs +@@ -9,7 +9,11 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use serde::{Serialize, Deserialize}; ++ ++pub mod resource; ++pub mod service; ++ ++use serde::{Deserialize, Serialize}; + use serde_json::Value; + + pub const SESSION_TIMEOUT_MIN: i64 = 1; +@@ -25,10 +29,10 @@ pub struct VirtccaEvidence { + pub enum TeeType { + Itrustee = 1, + Virtcca, ++ Rustcca, + Invalid, + } + +- + #[derive(Debug, Serialize, Deserialize)] + pub struct Evidence { + pub tee: TeeType, +@@ -51,4 +55,4 @@ pub struct Claims { + pub evaluation_reports: EvlResult, + pub tee: String, + pub tcb_status: Value, +-} +\ No newline at end of file ++} +diff --git a/service/attestation/attestation-types/src/resource/admin/mod.rs b/service/attestation/attestation-types/src/resource/admin/mod.rs +new file mode 100644 +index 0000000..6f74a57 +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/admin/mod.rs +@@ -0,0 +1,59 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++pub mod simple; ++ ++use crate::resource::{error::Result, policy::PolicyLocation, Resource, ResourceLocation}; ++use async_trait::async_trait; ++ ++#[async_trait] ++pub trait ResourceAdminInterface: ResourcePolicyAdminInterface + Send + Sync { ++ /// Get resource from the storage ++ async fn get_resource(&self, location: ResourceLocation) -> Result; ++ /// Traverse and get resource list in particular vendor. ++ async fn list_resource(&self, vendor: &str) -> Result>; ++ /// Add new resource. If the resource already exists, error will be thrown. ++ async fn add_resource( ++ &self, ++ _location: ResourceLocation, ++ _content: String, ++ _policy: Vec, ++ ) -> Result<()>; ++ /// Modify the content of specific resource. ++ async fn modify_resource(&self, _location: ResourceLocation, _content: String) -> Result<()>; ++ /// Delete resource. ++ async fn del_resource(&self, _location: ResourceLocation) -> Result<()>; ++ /// Bind policy with resource. ++ async fn bind_policy(&self, _location: ResourceLocation, _policy: Vec) -> Result<()>; ++ /// Unbind policy with resource. ++ async fn unbind_policy(&self, _location: ResourceLocation, _policy: Vec) -> Result<()>; ++ /// Evaluate resource according the claims. ++ async fn evaluate_resource(&self, _location: ResourceLocation, _claim: &str) -> Result; ++} ++ ++#[async_trait] ++pub trait ResourcePolicyAdminInterface: Send + Sync { ++ /// Create a policy file and write the content inside the file. If it already exists, override it. ++ async fn add_policy(&self, _policy: PolicyLocation, _content: &str) -> Result<()>; ++ /// Read the policy content from the file. ++ async fn get_policy(&self, _policy: PolicyLocation) -> Result; ++ /// Delete the policy file. ++ async fn delete_policy(&self, _policy: PolicyLocation) -> Result<()>; ++ /// Get all existing policy files. ++ async fn get_all_policies(&self) -> Result>; ++ /// Get all policy files of a vendor. ++ async fn get_all_policies_in_vendor(&self, _vendor: &str) -> Result>; ++ /// Clear all policy files. ++ async fn clear_all_policies(&self) -> Result<()>; ++ /// Clear all policy files in vendor. ++ async fn clear_all_policies_in_vendor(&self, _vendor: &str) -> Result<()>; ++} +diff --git a/service/attestation/attestation-types/src/resource/admin/simple.rs b/service/attestation/attestation-types/src/resource/admin/simple.rs +new file mode 100644 +index 0000000..5967be9 +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/admin/simple.rs +@@ -0,0 +1,256 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++use crate::resource::admin::ResourceAdminInterface; ++use crate::resource::error::{ResourceError, Result}; ++use crate::resource::policy::opa::OpenPolicyAgent; ++use crate::resource::policy::{PolicyEngine, PolicyLocation}; ++use crate::resource::storage::simple::SimpleStorage; ++use crate::resource::storage::StorageEngine; ++use crate::resource::ResourceLocation; ++use anyhow::Context; ++use async_trait::async_trait; ++use std::path::PathBuf; ++use std::sync::Arc; ++use tokio::sync::Mutex; ++ ++use super::{Resource, ResourcePolicyAdminInterface}; ++ ++pub struct SimpleResourceAdmin { ++ storage_engine: Arc>, ++ policy_engine: Arc>, ++} ++ ++impl SimpleResourceAdmin { ++ pub fn new(storage_base: PathBuf, policy_base: PathBuf) -> Self { ++ SimpleResourceAdmin { ++ storage_engine: Arc::new(Mutex::new(SimpleStorage::new(storage_base))), ++ policy_engine: Arc::new(Mutex::new(OpenPolicyAgent::new(policy_base))), ++ } ++ } ++ ++ pub fn default() -> Self { ++ SimpleResourceAdmin { ++ storage_engine: Arc::new(Mutex::new(SimpleStorage::default())), ++ policy_engine: Arc::new(Mutex::new(OpenPolicyAgent::default())), ++ } ++ } ++} ++ ++#[async_trait] ++impl ResourceAdminInterface for SimpleResourceAdmin { ++ async fn get_resource(&self, location: ResourceLocation) -> Result { ++ self.storage_engine.lock().await.get(location).await ++ } ++ ++ async fn list_resource(&self, vendor: &str) -> Result> { ++ self.storage_engine.lock().await.list(vendor).await ++ } ++ ++ async fn evaluate_resource(&self, location: ResourceLocation, claims: &str) -> Result { ++ let resource = self ++ .get_resource(location.clone()) ++ .await ++ .context("get resource failed") ++ .map_err(|e| { ++ log::debug!("{}", e); ++ e ++ })?; ++ Ok(self ++ .policy_engine ++ .lock() ++ .await ++ .evaluate(location, claims, resource.get_policy()) ++ .await ++ .context("evaluate failed") ++ .map_err(|e| { ++ log::debug!("{}", e); ++ e ++ })?) ++ } ++ ++ // If unmatched policy is found, aborting the adding procedure. ++ async fn add_resource( ++ &self, ++ location: ResourceLocation, ++ content: String, ++ policy: Vec, ++ ) -> Result<()> { ++ let mut legal_policy: Vec = vec![]; ++ for p in policy { ++ let p = match PolicyLocation::try_from(p.clone()) { ++ Ok(p) => p, ++ Err(e) => { ++ log::warn!("Failed to parse policy '{}': {}", p, e); ++ continue; ++ } ++ }; ++ if !location.check_policy_legal(&p) { ++ return Err(ResourceError::UnmatchedPolicyResource( ++ location.to_string(), ++ p.to_string(), ++ )); ++ } ++ legal_policy.push(p.clone()); ++ } ++ let resource = Resource::new(content, legal_policy); ++ self.storage_engine ++ .lock() ++ .await ++ .store(location, resource) ++ .await ++ } ++ ++ async fn del_resource(&self, location: ResourceLocation) -> Result<()> { ++ self.storage_engine.lock().await.delete(location).await ++ } ++ ++ // If unmatched policy is found, aborting the binding procedure. ++ async fn bind_policy(&self, location: ResourceLocation, policy: Vec) -> Result<()> { ++ let mut legal_policy: Vec = vec![]; ++ for p in policy.iter() { ++ if let Ok(p) = p.parse::() { ++ if !location.check_policy_legal(&p) { ++ return Err(ResourceError::UnmatchedPolicyResource( ++ location.to_string(), ++ p.to_string(), ++ )); ++ } ++ legal_policy.push(p); ++ } ++ } ++ self.storage_engine ++ .lock() ++ .await ++ .bind_policies(location, legal_policy) ++ .await ++ } ++ ++ // If unmatched policy is found, aborting the unbinding procedure. ++ async fn unbind_policy(&self, location: ResourceLocation, policy: Vec) -> Result<()> { ++ let mut legal_policy: Vec = vec![]; ++ for p in policy.iter() { ++ let p = p.parse::()?; ++ if !location.check_policy_legal(&p) { ++ return Err(ResourceError::UnmatchedPolicyResource( ++ location.to_string(), ++ p.to_string(), ++ )); ++ } ++ legal_policy.push(p); ++ } ++ self.storage_engine ++ .lock() ++ .await ++ .unbind_policies(location, legal_policy) ++ .await ++ } ++ ++ async fn modify_resource(&self, location: ResourceLocation, content: String) -> Result<()> { ++ self.storage_engine ++ .lock() ++ .await ++ .modify(location, content) ++ .await ++ } ++} ++ ++#[async_trait] ++impl ResourcePolicyAdminInterface for SimpleResourceAdmin { ++ /// Create a policy file and write the content inside the file. If it already exists, override it. ++ async fn add_policy(&self, path: PolicyLocation, policy: &str) -> Result<()> { ++ self.policy_engine ++ .lock() ++ .await ++ .add_policy(path, policy) ++ .await ++ } ++ /// Read the policy content from the file. ++ async fn get_policy(&self, path: PolicyLocation) -> Result { ++ self.policy_engine.lock().await.get_policy(path).await ++ } ++ /// Delete the policy file. ++ async fn delete_policy(&self, path: PolicyLocation) -> Result<()> { ++ self.policy_engine.lock().await.delete_policy(path).await ++ } ++ /// Get all existing policy files. ++ async fn get_all_policies(&self) -> Result> { ++ self.policy_engine.lock().await.get_all_policy().await ++ } ++ /// Get all policy files of a vendor. ++ async fn get_all_policies_in_vendor(&self, vendor: &str) -> Result> { ++ self.policy_engine ++ .lock() ++ .await ++ .get_all_policy_in_vendor(vendor) ++ .await ++ } ++ /// Clear all policy files. ++ async fn clear_all_policies(&self) -> Result<()> { ++ self.policy_engine.lock().await.clear_all_policy().await ++ } ++ /// Clear all policy files in vendor. ++ async fn clear_all_policies_in_vendor(&self, vendor: &str) -> Result<()> { ++ self.policy_engine ++ .lock() ++ .await ++ .clear_all_policy_in_vendor(vendor) ++ .await ++ } ++} ++ ++#[cfg(test)] ++mod tests { ++ use crate::resource::{admin::ResourceAdminInterface, ResourceLocation}; ++ use std::env; ++ use tokio::runtime::Runtime; ++ ++ #[test] ++ fn test_admin_unbind_policy() { ++ let cwd = env::current_dir().unwrap(); ++ let storage_base = cwd.join("storage"); ++ let policy_base = cwd.join("policy"); ++ let tmp_vendor = "test_admin_unbind_policy"; ++ let tmp_resource = "test"; ++ let vendor_path = storage_base.join(tmp_vendor); ++ let resource_path = storage_base.join(tmp_vendor).join(tmp_resource); ++ let admin = super::SimpleResourceAdmin::new(storage_base.clone(), policy_base.clone()); ++ std::fs::create_dir_all(&vendor_path).unwrap(); ++ std::fs::File::create(&resource_path).unwrap(); ++ let resource = r#"{ ++ "content": "hello", ++ "policy": ["test_admin_unbind_policy/c.rego", "test_admin_unbind_policy/a.rego", "default/b.rego", "test_admin_unbind_policy/b.rego"] ++}"#; ++ std::fs::write(&resource_path, resource).unwrap(); ++ ++ let location = ++ ResourceLocation::new(Some(tmp_vendor.to_string()), tmp_resource.to_string()); ++ let unbind_policy = vec![ ++ "default/b.rego".to_string(), ++ "test_admin_unbind_policy/b.rego".to_string(), ++ ]; ++ ++ let runtime = Runtime::new().unwrap(); ++ runtime ++ .block_on(admin.unbind_policy(location.clone(), unbind_policy)) ++ .unwrap(); ++ let r = runtime.block_on(admin.get_resource(location)).unwrap(); ++ let content = r.to_string().unwrap(); ++ println!("{}", r.to_string().unwrap()); ++ assert_eq!( ++ content, ++ r#"{"content":"hello","policy":["test_admin_unbind_policy/a.rego","test_admin_unbind_policy/c.rego"]}"# ++ ); ++ ++ std::fs::remove_dir_all(&storage_base).unwrap(); ++ } ++} +diff --git a/service/attestation/attestation-types/src/resource/error.rs b/service/attestation/attestation-types/src/resource/error.rs +new file mode 100644 +index 0000000..296aae8 +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/error.rs +@@ -0,0 +1,45 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use std::path::StripPrefixError; ++use thiserror::Error; ++ ++pub type Result = std::result::Result; ++ ++#[derive(Error, Debug)] ++pub enum ResourceError { ++ #[error("Trait is not implemented.")] ++ NotImplemented, ++ #[error("Policy is missing.")] ++ PolicyMissing, ++ #[error("Failed to load policy: {0}")] ++ LoadPolicy(#[from] anyhow::Error), ++ #[error("Resource error: {0}")] ++ ResourceError(#[from] std::io::Error), ++ #[error("Illegal resource path: {0}")] ++ IllegalResource(String), ++ #[error("Invalid resource content: {0}")] ++ ResourceFromUtf8(#[from] std::string::FromUtf8Error), ++ #[error("Serde deserialize failure: {0}")] ++ SerdeError(#[from] serde_json::Error), ++ #[error("Illegal policy location path: {0}")] ++ IllegalPolicyLocation(String), ++ #[error("Unmatched vendor between resource {0} and policy {1}")] ++ UnmatchedPolicyResource(String, String), ++ #[error("Convert error: {0}")] ++ IoError(#[from] core::convert::Infallible), ++ #[error("Strip Prefix fail: {0}")] ++ StripPrefix(#[from] StripPrefixError), ++ #[error("Illegal policy suffix: {0}")] ++ IllegalPolicySuffix(String), ++ #[error("Resource already exist: {0}")] ++ ResourceExist(String), ++} +diff --git a/service/attestation/attestation-types/src/resource/mod.rs b/service/attestation/attestation-types/src/resource/mod.rs +new file mode 100644 +index 0000000..037c086 +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/mod.rs +@@ -0,0 +1,145 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++pub mod admin; ++pub mod error; ++pub mod policy; ++pub mod storage; ++ ++pub(crate) mod utils; ++ ++use crate::resource::error::{ResourceError, Result}; ++use crate::resource::policy::PolicyLocation; ++use anyhow::Context; ++use serde::{Deserialize, Serialize}; ++use std::{fmt::Display, path::PathBuf, str::FromStr}; ++ ++pub(crate) const DEFAULT_VENDOR_BASE: &str = "oeas"; ++ ++/// This struct indicates unique resource location under specific base directory. ++/// Base directory should be maintained by the resource management engine. ++#[derive(Deserialize, Serialize, Debug, Clone)] ++pub struct ResourceLocation { ++ pub vendor: Option, ++ pub path: String, ++} ++ ++impl std::convert::From for String { ++ fn from(value: ResourceLocation) -> Self { ++ format!("{}", value) ++ } ++} ++ ++impl std::convert::TryFrom for PathBuf { ++ type Error = ResourceError; ++ ++ fn try_from(value: ResourceLocation) -> std::result::Result { ++ let path: String = value.into(); ++ Ok(PathBuf::from_str(&path)?) ++ } ++} ++ ++impl Display for ResourceLocation { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ write!( ++ f, ++ "{}/{}", ++ self.vendor ++ .clone() ++ .unwrap_or(DEFAULT_VENDOR_BASE.to_string()), ++ self.path, ++ ) ++ } ++} ++ ++impl ResourceLocation { ++ pub fn new(vendor: Option, path: String) -> Self { ++ Self { vendor, path } ++ } ++ ++ /// If the vendor if resource or vendor is None, it means using the 'default' vendor. ++ /// ++ /// If the vendor of policy is 'default', the check always succeed. ++ /// Otherwise the vendor of policy should be the same with resource. ++ /// ++ pub fn check_policy_legal(&self, policy: &PolicyLocation) -> bool { ++ let policy_vendor = if policy.vendor.is_none() { ++ return true; ++ } else { ++ policy.vendor.clone().unwrap() ++ }; ++ ++ if policy_vendor.as_str() == DEFAULT_VENDOR_BASE { ++ return true; ++ } ++ ++ match self.vendor.as_ref() { ++ None => false, ++ Some(v) => v == &policy_vendor, ++ } ++ } ++} ++ ++/// Policy should be expressed like 'vendor/xxx.rego' ++#[derive(Deserialize, Serialize, Debug)] ++pub struct Resource { ++ pub(crate) content: String, ++ pub(crate) policy: Vec, ++} ++ ++impl Resource { ++ pub(crate) fn new(content: String, policy: Vec) -> Self { ++ let mut r = Self { ++ content, ++ policy: vec![], ++ }; ++ r.set_policy(policy); ++ r ++ } ++ ++ pub fn get_content(&self) -> String { ++ self.content.clone() ++ } ++ ++ /// The illegal policy will be ignored and throw warning message. ++ pub fn get_policy(&self) -> Vec { ++ let mut ret: Vec = vec![]; ++ for s in self.policy.iter() { ++ let p = PolicyLocation::try_from(s.clone()); ++ match p { ++ Ok(p) => ret.push(p), ++ Err(_) => { ++ log::warn!("Illegal policy: {}", s); ++ } ++ } ++ } ++ ret ++ } ++ ++ pub fn set_policy(&mut self, policy: Vec) { ++ let policy = policy.iter().map(|p| format!("{}", p)).collect(); ++ self.policy = policy; ++ } ++ ++ pub(crate) async fn read_from_file(path: PathBuf) -> Result { ++ let content = tokio::fs::read(path) ++ .await ++ .context("failed to add resource")?; ++ Ok(serde_json::from_str( ++ &String::from_utf8(content).context("from utf8 error")?, ++ )?) ++ } ++ ++ pub(crate) fn to_string(&self) -> Result { ++ Ok(serde_json::to_string(self)?) ++ } ++} +diff --git a/service/attestation/attestation-types/src/resource/policy/mod.rs b/service/attestation/attestation-types/src/resource/policy/mod.rs +new file mode 100644 +index 0000000..d7ae01d +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/policy/mod.rs +@@ -0,0 +1,128 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++pub(crate) mod opa; ++ ++use crate::resource::error::ResourceError; ++use crate::resource::error::Result; ++use crate::resource::ResourceLocation; ++use crate::resource::DEFAULT_VENDOR_BASE; ++use async_trait::async_trait; ++use serde::{Deserialize, Serialize}; ++use std::fmt::Display; ++use std::path::PathBuf; ++use std::str::FromStr; ++ ++/// This structure indicates unique policy location under specific base directory. ++/// The base directory should be maintained by the policy management engine. ++/// If vendor is none, it should keep the same with the resource vendor. ++/// ++/// To simplify the expression, the policy location can be expressed like 'vendor/policy.rego'. ++#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] ++pub struct PolicyLocation { ++ pub vendor: Option, ++ pub id: String, ++} ++ ++impl std::convert::From for String { ++ fn from(value: PolicyLocation) -> Self { ++ format!("{}", value) ++ } ++} ++ ++impl std::convert::From<&PolicyLocation> for String { ++ fn from(value: &PolicyLocation) -> Self { ++ format!("{}", value) ++ } ++} ++ ++impl std::convert::TryFrom for PathBuf { ++ type Error = ResourceError; ++ ++ fn try_from(value: PolicyLocation) -> std::result::Result { ++ let path: String = value.into(); ++ Ok(PathBuf::from_str(&path)?) ++ } ++} ++ ++impl std::convert::TryFrom<&PolicyLocation> for PathBuf { ++ type Error = ResourceError; ++ ++ fn try_from(value: &PolicyLocation) -> std::result::Result { ++ Ok(PathBuf::from_str(&format!("{}", value))?) ++ } ++} ++ ++impl Display for PolicyLocation { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ write!( ++ f, ++ "{}/{}", ++ self.vendor ++ .clone() ++ .unwrap_or(DEFAULT_VENDOR_BASE.to_string()), ++ self.id, ++ ) ++ } ++} ++ ++impl std::convert::TryFrom for PolicyLocation { ++ type Error = ResourceError; ++ fn try_from(value: String) -> Result { ++ let parts: Vec<&str> = value.split('/').collect(); ++ if parts.len() != 2 { ++ return Err(ResourceError::IllegalPolicyLocation(value)); ++ } ++ ++ let vendor = match parts[0] { ++ DEFAULT_VENDOR_BASE => None, ++ other => Some(other.to_string()), ++ }; ++ let id = parts[1].to_string(); ++ ++ Ok(PolicyLocation { vendor, id }) ++ } ++} ++ ++impl FromStr for PolicyLocation { ++ type Err = ResourceError; ++ ++ fn from_str(s: &str) -> Result { ++ TryFrom::try_from(s.to_string()) ++ } ++} ++ ++/// Manage the policy files and evaluate the legality of resource ++#[async_trait] ++pub(crate) trait PolicyEngine: Send + Sync { ++ /// Given the resource location and claims, read the resource content from the storage and evaluate the resource according to the claims. ++ async fn evaluate( ++ &self, ++ _resource: ResourceLocation, ++ _claims: &str, ++ _policy: Vec, ++ ) -> Result; ++ /// Create a policy file and write the content inside the file. If it already exists, override it. ++ async fn add_policy(&self, _path: PolicyLocation, _policy: &str) -> Result<()>; ++ /// Read the policy content from the file. ++ async fn get_policy(&self, _path: PolicyLocation) -> Result; ++ /// Delete the policy file. ++ async fn delete_policy(&self, _path: PolicyLocation) -> Result<()>; ++ /// Get all existing policy files. ++ async fn get_all_policy(&self) -> Result>; ++ /// Get all policy files of a vendor. ++ async fn get_all_policy_in_vendor(&self, _vendor: &str) -> Result>; ++ /// Clear all policy files. ++ async fn clear_all_policy(&self) -> Result<()>; ++ /// Clear all policy files in vendor. ++ async fn clear_all_policy_in_vendor(&self, _vendor: &str) -> Result<()>; ++} +diff --git a/service/attestation/attestation-types/src/resource/policy/opa/mod.rs b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +new file mode 100644 +index 0000000..0ec506a +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +@@ -0,0 +1,321 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++use super::PolicyLocation; ++use crate::resource::{ ++ error::{ResourceError, Result}, ++ policy::PolicyEngine, ++ ResourceLocation, DEFAULT_VENDOR_BASE, ++}; ++use anyhow::{bail, Context}; ++use async_trait::async_trait; ++use std::path::PathBuf; ++ ++pub(crate) const DEFAULT_RESOURCE_POLICY_DIR: &str = ++ "/run/attestation/attestation-service/resource/policy/"; ++pub(crate) const DEFAULT_RESOURCE_VIRTCCA_DEFAULT_POLICY: &str = "virtcca.rego"; ++ ++pub(crate) struct OpenPolicyAgent { ++ base: PathBuf, ++} ++ ++impl OpenPolicyAgent { ++ pub(crate) fn new(base: PathBuf) -> Self { ++ OpenPolicyAgent { base } ++ } ++ ++ pub fn default() -> Self { ++ Self::new(PathBuf::from(DEFAULT_RESOURCE_POLICY_DIR)) ++ } ++} ++ ++#[async_trait] ++impl PolicyEngine for OpenPolicyAgent { ++ async fn evaluate( ++ &self, ++ resource: ResourceLocation, ++ claim: &str, ++ policy: Vec, ++ ) -> Result { ++ let mut engine = regorus::Engine::new(); ++ let mut eval_targets: Vec = vec![]; ++ ++ if policy.is_empty() { ++ /* Apply default policy according to the tee type from the claims. */ ++ let claim_json: serde_json::Value = serde_json::from_str(claim)?; ++ if let Some(tee) = claim_json.get("tee") { ++ if let Some(tee_str) = tee.as_str() { ++ match tee_str { ++ "vcca" => { ++ engine ++ .add_policy_from_file( ++ self.base ++ .join(DEFAULT_VENDOR_BASE) ++ .join(DEFAULT_RESOURCE_VIRTCCA_DEFAULT_POLICY), ++ ) ++ .context("failed to add policy from file")?; ++ let vendor = DEFAULT_VENDOR_BASE; ++ let id = match DEFAULT_RESOURCE_VIRTCCA_DEFAULT_POLICY ++ .strip_suffix(".rego") ++ { ++ Some(v) => v, ++ None => { ++ log::debug!( ++ "Invalid default policy id '{}'", ++ DEFAULT_RESOURCE_VIRTCCA_DEFAULT_POLICY ++ ); ++ return Err(ResourceError::IllegalPolicySuffix( ++ DEFAULT_RESOURCE_VIRTCCA_DEFAULT_POLICY.to_string(), ++ )); ++ } ++ }; ++ eval_targets.push(format!("data.{}.{}.allow", vendor, id)) ++ } ++ _ => {} ++ } ++ } ++ } ++ } ++ ++ for file in policy.iter() { ++ let sub_id = match file.id.strip_suffix(".rego") { ++ Some(v) => v, ++ None => { ++ log::debug!("Invalid policy id '{}'", file); ++ return Err(ResourceError::IllegalPolicySuffix(file.to_string())); ++ } ++ }; ++ let p: PathBuf = file.try_into()?; ++ if let Err(e) = engine.add_policy_from_file(self.base.join(p)) { ++ log::debug!("Failed to add policy: {}", e); ++ return Err(e.into()); ++ } ++ // .context("failed to add policy from file")?; ++ eval_targets.push(format!( ++ "data.{}.{}.allow", ++ file.vendor ++ .clone() ++ .unwrap_or(DEFAULT_VENDOR_BASE.to_string()), ++ sub_id ++ )) ++ } ++ log::debug!("Evaluate query targest: {:?}", eval_targets); ++ if let Err(e) = engine.add_data_json(&format!("{{\"resource\":\"{}\"}}", resource)) { ++ log::debug!("Failed to add resource data: {}", e); ++ return Err(e.into()); ++ } ++ if let Err(e) = engine.set_input_json(claim) { ++ log::debug!("Failed to set input claim: {}", e); ++ return Err(e.into()); ++ } ++ ++ let mut ret = true; ++ ++ for eval in eval_targets { ++ let v = match engine.eval_bool_query(eval.clone(), false) { ++ Ok(v) => v, ++ Err(e) => { ++ log::debug!("Failed to evaluate {}: {}", eval, e); ++ return Err(e.into()); ++ } ++ }; ++ log::debug!("Evaluate {} = {}", eval, v); ++ ret = ret && v; ++ } ++ ++ Ok(ret) ++ } ++ ++ async fn get_policy(&self, path: PolicyLocation) -> Result { ++ let p = self.base.join(format!("{}", path)); ++ let raw = tokio::fs::read(p).await?; ++ Ok(String::from_utf8(raw)?) ++ } ++ ++ async fn add_policy(&self, path: PolicyLocation, policy: &str) -> Result<()> { ++ let p = self.base.join(format!("{}", path)); ++ if let Some(parent) = p.parent() { ++ if let Err(e) = tokio::fs::create_dir_all(parent).await { ++ log::warn!( ++ "Failed to create vendor directory for policy '{}': {}", ++ path, ++ e ++ ); ++ } ++ } ++ tokio::fs::write(p, policy.as_bytes()).await?; ++ Ok(()) ++ } ++ ++ async fn delete_policy(&self, path: PolicyLocation) -> Result<()> { ++ let p = self.base.join(format!("{}", path)); ++ tokio::fs::remove_file(p).await?; ++ Ok(()) ++ } ++ ++ async fn get_all_policy(&self) -> Result> { ++ let mut ret: Vec = vec![]; ++ let mut dir = tokio::fs::read_dir(&self.base).await?; ++ while let Some(d) = dir.next_entry().await? { ++ match d.file_type().await { ++ Ok(t) => { ++ if !t.is_dir() { ++ continue; ++ } ++ } ++ Err(_) => { ++ continue; ++ } ++ } ++ ++ let vendor = match d.file_name().into_string() { ++ Ok(s) => s, ++ Err(s) => { ++ log::warn!("Illegal policy vendor directory '{:?}'", s); ++ continue; ++ } ++ }; ++ ++ let mut several = match self.get_all_policy_in_vendor(&vendor).await { ++ Ok(v) => v, ++ Err(e) => { ++ log::warn!("Failed to get policy from vendor '{}': {}", vendor, e); ++ continue; ++ } ++ }; ++ ++ ret.append(&mut several); ++ } ++ ++ Ok(ret) ++ } ++ ++ async fn get_all_policy_in_vendor(&self, vendor: &str) -> Result> { ++ let vendor_dir = self.base.join(&vendor); ++ let mut dir = tokio::fs::read_dir(vendor_dir).await?; ++ let mut ret: Vec = vec![]; ++ while let Some(d) = dir.next_entry().await? { ++ if let Ok(t) = d.file_type().await { ++ if !t.is_file() { ++ continue; ++ } ++ } ++ ++ let rego = match d.file_name().into_string() { ++ Ok(s) => s, ++ Err(s) => { ++ log::warn!("Illegal policy file name '{:?}'", s); ++ continue; ++ } ++ }; ++ if !rego.ends_with("rego") { ++ continue; ++ } ++ ++ ret.push(PolicyLocation { ++ vendor: if vendor == DEFAULT_VENDOR_BASE { ++ None ++ } else { ++ Some(vendor.to_string()) ++ }, ++ id: rego, ++ }); ++ } ++ ++ Ok(ret) ++ } ++ ++ async fn clear_all_policy(&self) -> Result<()> { ++ let mut dir = tokio::fs::read_dir(&self.base).await?; ++ while let Some(d) = dir.next_entry().await? { ++ match d.file_type().await { ++ Ok(t) => { ++ if !t.is_dir() { ++ continue; ++ } ++ } ++ Err(_) => { ++ continue; ++ } ++ } ++ ++ match d.file_name().into_string() { ++ Ok(s) => { ++ if let Err(e) = self.clear_all_policy_in_vendor(&s).await { ++ log::warn!("Failed to clear vendor '{}': {}", s, e); ++ } ++ } ++ Err(e) => { ++ log::warn!("Illegal vendor directory name '{:?}'", e); ++ continue; ++ } ++ } ++ } ++ Ok(()) ++ } ++ ++ async fn clear_all_policy_in_vendor(&self, vendor: &str) -> Result<()> { ++ let vendor_dir = self.base.join(&vendor); ++ let md = tokio::fs::metadata(&vendor_dir) ++ .await ++ .context("invalid vendor")?; ++ if md.is_dir() { ++ tokio::fs::remove_dir_all(vendor_dir).await?; ++ } ++ Ok(()) ++ } ++} ++ ++#[cfg(test)] ++mod tests { ++ use tokio::runtime; ++ ++ use super::ResourceLocation; ++ use super::{OpenPolicyAgent, PolicyEngine}; ++ ++ #[test] ++ fn test_evaluate() { ++ let pwd = std::env::current_dir().expect("failed to get pwd"); ++ let opa = OpenPolicyAgent::new(pwd.join("src/policy/opa")); ++ let resource = ResourceLocation::new(None, "b/p/f".to_string()); ++ let claims = r#" ++{ ++ "iss": "oeas", ++ "iat": 1735635443, ++ "nbf": 1735635443, ++ "exp": 1735635743, ++ "evaluation_reports": { ++ "eval_result": true, ++ "policy": [], ++ "report": { ++ "default_vcca.rego": "{\"vcca.cvm.rim\":\"1ee366339c8245a34a8ad9d27a0b912a588af7da8aef514ae8dec22746956dd1\"}", ++ "ima": {} ++ } ++ }, ++ "tee": "vcca", ++ "tcb_status": { ++ "vcca.cvm.challenge": "586667776b4972524b58684550524f384771654c7244695356485134715f372d4e36375064587a50457763000000000000000000000000000000000000000000", ++ "vcca.cvm.rem.0": "927b62bc7f4d9fd03afd0b9b2fe8832004b570b4c4bffc2949c4e461b0a0ff63", ++ "vcca.cvm.rem.1": "0000000000000000000000000000000000000000000000000000000000000000", ++ "vcca.cvm.rem.2": "0000000000000000000000000000000000000000000000000000000000000000", ++ "vcca.cvm.rem.3": "0000000000000000000000000000000000000000000000000000000000000000", ++ "vcca.cvm.rim": "1ee366339c8245a34a8ad9d27a0b912a588af7da8aef514ae8dec22746956dd1", ++ "vcca.cvm.rpv": "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", ++ "vcca.platform": "" ++ } ++}"#; ++ let policy = vec![]; ++ let rt = runtime::Runtime::new().unwrap(); ++ let r = rt.block_on(opa.evaluate(resource, claims, policy)); ++ assert_eq!(r.unwrap(), true); ++ } ++} +diff --git a/service/attestation/attestation-types/src/resource/policy/opa/virtcca.rego b/service/attestation/attestation-types/src/resource/policy/opa/virtcca.rego +new file mode 100644 +index 0000000..fa32e38 +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/policy/opa/virtcca.rego +@@ -0,0 +1,12 @@ ++# The naming scheme of package is ".". ++# ++# The policy location of the corresponding policy file should be "/.rego". ++# ++ ++package oeas.virtcca ++ ++default allow = false ++ ++allow { ++ input["tee"] == "vcca" ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-types/src/resource/storage/mod.rs b/service/attestation/attestation-types/src/resource/storage/mod.rs +new file mode 100644 +index 0000000..fd7b0c7 +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/storage/mod.rs +@@ -0,0 +1,67 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++pub(crate) mod simple; ++ ++use crate::resource::error::ResourceError; ++use crate::resource::error::Result; ++use crate::resource::policy::PolicyLocation; ++use crate::resource::Resource; ++use crate::resource::ResourceLocation; ++use async_trait::async_trait; ++ ++#[async_trait] ++pub(crate) trait StorageEngine: StorageOp + PolicyOp {} ++ ++#[async_trait] ++pub(crate) trait StorageOp: Send + Sync { ++ /// Get the resource inside the storage and return a structure instance. ++ async fn get(&self, location: ResourceLocation) -> Result; ++ /// Traverse and collect resource list in particular vendor. ++ async fn list(&self, vendor: &str) -> Result>; ++ /// Create a new resource if it does not exist. If the resource already exists, error will be thrown. ++ async fn store(&self, location: ResourceLocation, resource: Resource) -> Result<()>; ++ /// Override the content field in the resource, while keep other fields the same. ++ async fn modify(&self, location: ResourceLocation, content: String) -> Result<()>; ++ /// Delete the resource inside the storage. ++ async fn delete(&self, location: ResourceLocation) -> Result<()>; ++ /// Flush the buffer into the storage ++ async fn flush(&self) -> Result<()> { ++ Err(ResourceError::NotImplemented) ++ } ++} ++ ++#[async_trait] ++pub(crate) trait PolicyOp: StorageOp + Send + Sync { ++ /// Clear the original policy and set the new ones. ++ async fn set_policies( ++ &self, ++ location: ResourceLocation, ++ policy: Vec, ++ ) -> Result<()>; ++ /// Get all policy from the resource. ++ async fn get_all_policies(&self, location: ResourceLocation) -> Result>; ++ /// Clear the original policy inside the resource. ++ async fn clea_policies(&self, location: ResourceLocation) -> Result<()>; ++ /// Delete the specific policy from the resource. ++ async fn unbind_policies( ++ &self, ++ location: ResourceLocation, ++ policy: Vec, ++ ) -> Result<()>; ++ /// Append new policy inside the resource. ++ async fn bind_policies( ++ &self, ++ location: ResourceLocation, ++ policies: Vec, ++ ) -> Result<()>; ++} +diff --git a/service/attestation/attestation-types/src/resource/storage/simple.rs b/service/attestation/attestation-types/src/resource/storage/simple.rs +new file mode 100644 +index 0000000..b8fd536 +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/storage/simple.rs +@@ -0,0 +1,220 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++use crate::resource::error::ResourceError; ++use crate::resource::error::Result; ++use crate::resource::policy::PolicyLocation; ++use crate::resource::storage::StorageOp; ++use crate::resource::utils::traverse_regular_file; ++use crate::resource::ResourceLocation; ++use anyhow::Context; ++use async_trait::async_trait; ++use std::path::PathBuf; ++ ++use super::PolicyOp; ++use super::Resource; ++use super::StorageEngine; ++ ++pub(crate) const STORAGE_BASE: &str = "/run/attestation/attestation-service/resource/storage/"; ++ ++pub(crate) struct SimpleStorage { ++ base: PathBuf, ++} ++ ++impl SimpleStorage { ++ pub(crate) fn new(base: PathBuf) -> Self { ++ Self { base } ++ } ++ ++ pub(crate) fn default() -> Self { ++ Self::new(PathBuf::from(STORAGE_BASE)) ++ } ++ ++ /// Resource location can not contain dot characters to avoid visiting parent directory. All the resource is stored under the base directory. ++ fn regular(&self, location: &str) -> Result { ++ /* abandon passing relative path */ ++ if !self.check_legal(location) { ++ return Err(ResourceError::IllegalResource(location.to_string())); ++ } ++ let base = PathBuf::from(&self.base); ++ let path = base.join(location); ++ Ok(path) ++ } ++ ++ fn check_legal(&self, location: &str) -> bool { ++ !location.contains(|c| ['.'].contains(&c)) ++ } ++} ++ ++#[async_trait] ++impl StorageEngine for SimpleStorage {} ++ ++#[async_trait] ++impl StorageOp for SimpleStorage { ++ async fn get(&self, location: ResourceLocation) -> Result { ++ let regularized = self.regular(&format!("{}", location))?; ++ Resource::read_from_file(regularized).await ++ } ++ ++ async fn list(&self, vendor: &str) -> Result> { ++ let vendor_base = self.regular(vendor)?; ++ let resource_list = traverse_regular_file(&vendor_base).await?; ++ let mut ret: Vec = vec![]; ++ for p in resource_list.iter() { ++ let path = p.strip_prefix(&vendor_base)?; ++ let resource = ResourceLocation::new( ++ Some(vendor.to_string()), ++ path.to_str() ++ .ok_or(ResourceError::IllegalResource(format!("{:?}", path)))? ++ .to_string(), ++ ); ++ ret.push(resource); ++ } ++ Ok(ret) ++ } ++ ++ async fn store(&self, location: ResourceLocation, resource: Resource) -> Result<()> { ++ let regularized = self.regular(&format!("{}", location))?; ++ ++ if regularized.exists() { ++ return Err(ResourceError::ResourceExist(location.to_string())); ++ } ++ ++ if let Some(parent) = regularized.parent() { ++ if let Err(e) = tokio::fs::create_dir_all(parent).await { ++ log::warn!( ++ "Failed to create vendor directory for resource '{}': {}", ++ location, ++ e ++ ); ++ } ++ } ++ tokio::fs::write(regularized, serde_json::to_string(&resource)?) ++ .await ++ .context("failed to add resource")?; ++ Ok(()) ++ } ++ ++ async fn modify(&self, location: ResourceLocation, content: String) -> Result<()> { ++ let regularized = self.regular(&format!("{}", location))?; ++ let mut resource = Resource::read_from_file(regularized.clone()).await?; ++ resource.content = content; ++ tokio::fs::write(regularized, resource.to_string()?) ++ .await ++ .context("failed to modify resource")?; ++ Ok(()) ++ } ++ ++ async fn delete(&self, location: ResourceLocation) -> Result<()> { ++ let regularized = self.regular(&format!("{}", location))?; ++ tokio::fs::remove_file(regularized) ++ .await ++ .context("failed to delete resource")?; ++ Ok(()) ++ } ++} ++ ++#[async_trait] ++impl PolicyOp for SimpleStorage { ++ async fn set_policies( ++ &self, ++ location: ResourceLocation, ++ policy: Vec, ++ ) -> Result<()> { ++ let mut resource = self.get(location.clone()).await?; ++ resource.set_policy(policy); ++ self.store(location, resource).await ++ } ++ async fn get_all_policies(&self, location: ResourceLocation) -> Result> { ++ let resource = self.get(location).await?; ++ Ok(resource.get_policy()) ++ } ++ async fn clea_policies(&self, location: ResourceLocation) -> Result<()> { ++ let mut resource = self.get(location.clone()).await?; ++ resource.policy = vec![]; ++ self.store(location, resource).await ++ } ++ async fn unbind_policies( ++ &self, ++ location: ResourceLocation, ++ policy: Vec, ++ ) -> Result<()> { ++ let mut resource = self.get(location.clone()).await?; ++ resource.policy.sort(); ++ for p in policy.iter() { ++ if let Ok(idx) = resource.policy.binary_search(&format!("{}", p)) { ++ resource.policy.remove(idx); ++ } ++ } ++ self.store(location, resource).await ++ } ++ async fn bind_policies( ++ &self, ++ location: ResourceLocation, ++ policy: Vec, ++ ) -> Result<()> { ++ let mut resource = self.get(location.clone()).await?; ++ for p in policy.iter() { ++ resource.policy.push(format!("{}", p)); ++ } ++ self.store(location.clone(), resource).await ++ } ++} ++ ++#[cfg(test)] ++mod tests { ++ use crate::resource::{policy::PolicyLocation, ResourceLocation}; ++ use std::env; ++ use tokio::runtime::Runtime; ++ ++ use super::{PolicyOp, StorageOp}; ++ ++ #[test] ++ fn test_unbind_policies() { ++ let cwd = env::current_dir().unwrap(); ++ let tmp_vendor = "test_unbind_policies"; ++ let tmp_resource = "test"; ++ let vendor_path = cwd.join(tmp_vendor); ++ let resource_path = cwd.join(tmp_vendor).join(tmp_resource); ++ let storage = super::SimpleStorage::new(cwd); ++ std::fs::create_dir_all(&vendor_path).unwrap(); ++ std::fs::File::create(&resource_path).unwrap(); ++ let resource = r#"{ ++ "content": "hello", ++ "policy": ["test_unbind_policies/c.rego", "test_unbind_policies/a.rego", "default/b.rego", "test_unbind_policies/b.rego"] ++}"#; ++ std::fs::write(&resource_path, resource).unwrap(); ++ ++ let location = ++ ResourceLocation::new(Some(tmp_vendor.to_string()), tmp_resource.to_string()); ++ let unbind_policy = vec![ ++ "default/b.rego".parse::().unwrap(), ++ "test_unbind_policies/b.rego" ++ .parse::() ++ .unwrap(), ++ ]; ++ ++ let runtime = Runtime::new().unwrap(); ++ runtime ++ .block_on(storage.unbind_policies(location.clone(), unbind_policy)) ++ .unwrap(); ++ let r = runtime.block_on(storage.get(location)).unwrap(); ++ let content = r.to_string().unwrap(); ++ println!("{}", r.to_string().unwrap()); ++ assert_eq!( ++ content, ++ r#"{"content":"hello","policy":["test_unbind_policies/a.rego","test_unbind_policies/c.rego"]}"# ++ ); ++ ++ std::fs::remove_dir_all(&vendor_path).unwrap(); ++ } ++} +diff --git a/service/attestation/attestation-types/src/resource/utils.rs b/service/attestation/attestation-types/src/resource/utils.rs +new file mode 100644 +index 0000000..ba87c9c +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/utils.rs +@@ -0,0 +1,32 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++use crate::resource::error::Result; ++use async_recursion::async_recursion; ++use std::path::PathBuf; ++ ++#[async_recursion(Sync)] ++pub(crate) async fn traverse_regular_file(base: &PathBuf) -> Result> { ++ let mut entries = tokio::fs::read_dir(base).await?; ++ let mut ret: Vec = vec![]; ++ while let Some(entry) = entries.next_entry().await? { ++ let path = entry.path(); ++ if path.is_dir() { ++ let mut parts = traverse_regular_file(&path).await?; ++ ret.append(&mut parts); ++ } else if path.is_file() { ++ ret.push(path); ++ } ++ } ++ ++ Ok(ret) ++} +diff --git a/service/attestation/attestation-types/src/service.rs b/service/attestation/attestation-types/src/service.rs +new file mode 100644 +index 0000000..a7047e1 +--- /dev/null ++++ b/service/attestation/attestation-types/src/service.rs +@@ -0,0 +1,83 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++use crate::resource::{policy::PolicyLocation, ResourceLocation}; ++use serde::{Deserialize, Serialize}; ++ ++#[derive(Debug, Serialize, Deserialize)] ++pub enum GetResourceOp { ++ /// User in TEE environment can get resource content. ++ TeeGet { resource: ResourceLocation }, ++ /// Vendor can only get the list of resource files that are already published in AS. ++ VendorGet { vendor: String }, ++} ++ ++#[derive(Debug, Serialize, Deserialize, Clone)] ++pub enum SetResourceOp { ++ /// Add new resource. ++ /// The vendor of each policy should be 'default' or the same with the resource. ++ /// Otherwise error will be raised. ++ /// ++ /// If the resource already exists, the content will be overrided. ++ Add { ++ content: String, ++ policy: Vec, ++ }, ++ /// Delete specific resource. ++ Delete, ++ /// Modify the content of specific resource. Other fields of the resource will be kept. ++ Modify { content: String }, ++ /// Bind policy to specific resource. ++ /// The vendor of any policy should be 'default' or the same with the resource. ++ /// Otherwise error will be raised. ++ Bind { policy: Vec }, ++ /// Unbind policy of specific resource. ++ /// The vendor of any policy should be 'default' or the same with the resource. ++ /// Otherwise error will be raised. ++ Unbind { policy: Vec }, ++} ++ ++#[derive(Debug, Serialize, Deserialize, Clone)] ++pub struct SetResourceRequest { ++ pub op: SetResourceOp, ++ /// The vendor of the resource should be the same with that granted in the token. ++ pub resource: ResourceLocation, ++} ++ ++#[derive(Debug, Serialize, Deserialize)] ++pub enum GetResourcePolicyOp { ++ /// Get specific policy under a vendor. ++ GetOne { policy: PolicyLocation }, ++ /// Get all policy under different vendors. ++ /// The returned value is a vector of policy identifer, such as '["vendor_A/example.rego", "vendor_B/example.rego"]'. ++ GetAll, ++ /// Get all policy under particular vendor. ++ /// The returned value is a vector of policy identifer, such as '["vendor_A/example_1.rego", "vendor_A/example_2.rego"]'. ++ GetAllInVendor { vendor: String }, ++} ++ ++#[derive(Debug, Serialize, Deserialize)] ++pub enum SetResourcePolicyOp { ++ /// Add new policy file, if it already exists, override its content. ++ /// ++ /// The vendor of policy should be the same with that in the token granted to the user. ++ Add { ++ policy: PolicyLocation, ++ content: String, ++ }, ++ /// Delete particular policy file. ++ /// ++ /// The vendor of policy should be the same with that in the token granted to the user. ++ Delete { policy: PolicyLocation }, ++ /// Clear all policy files of particular vendor. ++ ClearAll { vendor: String }, ++} +-- +2.46.0 + diff --git a/0090-add-ra_tls-support.patch b/0090-add-ra_tls-support.patch new file mode 100644 index 0000000000000000000000000000000000000000..1313fec51684e00416f90c03432ad4ae4d8a5443 --- /dev/null +++ b/0090-add-ra_tls-support.patch @@ -0,0 +1,2054 @@ +From dcb66bb5945fdb61aaa42799a8a13899a92f04ff Mon Sep 17 00:00:00 2001 +From: xuraoqing +Date: Tue, 18 Feb 2025 11:59:35 +0800 +Subject: [PATCH] add ra_tls support + +Signed-off-by: xuraoqing +--- + component/CMakeLists.txt | 1 + + component/ra_tls/CMakeLists.txt | 43 ++ + component/ra_tls/LICENSE | 194 +++++++ + component/ra_tls/README.md | 3 + + component/ra_tls/openssl_imp.c | 669 ++++++++++++++++++++++++ + component/ra_tls/ra_tls.c | 898 ++++++++++++++++++++++++++++++++ + component/ra_tls/ra_tls.h | 65 +++ + component/ra_tls/ra_tls_imp.h | 97 ++++ + 8 files changed, 1970 insertions(+) + create mode 100644 component/ra_tls/CMakeLists.txt + create mode 100644 component/ra_tls/LICENSE + create mode 100644 component/ra_tls/README.md + create mode 100644 component/ra_tls/openssl_imp.c + create mode 100644 component/ra_tls/ra_tls.c + create mode 100644 component/ra_tls/ra_tls.h + create mode 100644 component/ra_tls/ra_tls_imp.h + +diff --git a/component/CMakeLists.txt b/component/CMakeLists.txt +index 83aa4cd1..4d917455 100644 +--- a/component/CMakeLists.txt ++++ b/component/CMakeLists.txt +@@ -14,6 +14,7 @@ ADD_SUBDIRECTORY(secure_channel) + + ADD_SUBDIRECTORY(remote_attest) + ADD_SUBDIRECTORY(local_attest) ++ADD_SUBDIRECTORY(ra_tls) + + + +diff --git a/component/ra_tls/CMakeLists.txt b/component/ra_tls/CMakeLists.txt +new file mode 100644 +index 00000000..c46a3ee4 +--- /dev/null ++++ b/component/ra_tls/CMakeLists.txt +@@ -0,0 +1,43 @@ ++# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++# secGear is licensed under the Mulan PSL v2. ++# You can use this software according to the terms and conditions of the Mulan PSL v2. ++# You may obtain a copy of Mulan PSL v2 at: ++# http://license.coscl.org.cn/MulanPSL2 ++# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++# PURPOSE. ++# See the Mulan PSL v2 for more details. ++ ++cmake_minimum_required(VERSION 3.10 FATAL_ERROR) ++project(ra_tls VERSION 0.1) ++ ++set(LIB_NAME ra_tls) ++set(LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/ra_tls.c) ++set(LD_SO cjson curl) ++set(HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/ra_tls.h ${CMAKE_CURRENT_SOURCE_DIR}/ra_tls_imp.h) ++if (NOT TLS_LIB) ++set(TLS_LIB OPENSSL) ++endif() ++if (CMAKE_BUILD_TYPE MATCHES Debug) ++add_definitions(-DDEBUG) ++endif() ++if (TLS_LIB MATCHES OPENSSL) ++ add_definitions(-DUSE_OPENSSL) ++ set(LD_SO ${LD_SO} crypto ssl) ++ set(LIB_SRC ${LIB_SRC} ${CMAKE_CURRENT_SOURCE_DIR}/openssl_imp.c) ++else() ++ message(FATAL_ERROR "TLS_LIB should defined") ++endif() ++ ++FILE(GLOB_RECURSE BASE64_SRC CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../../thirdparty/base64url/*.c") ++include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../thirdparty/base64url) ++ ++set(LIB_SRC ${LIB_SRC} ${BASE64_SRC}) ++add_library(${LIB_NAME} SHARED ${LIB_SRC}) ++target_link_libraries(${LIB_NAME} PUBLIC ${LD_SO}) ++ ++set_target_properties(${LIB_NAME} PROPERTIES PUBLIC_HEADER "${HEADER_FILES}") ++install(TARGETS ${LIB_NAME} ++ LIBRARY DESTINATION ${LOCAL_ROOT_PATH_INSTALL}/usr/lib64 ++ PUBLIC_HEADER DESTINATION ${LOCAL_ROOT_PATH_INSTALL}/usr/include/secGear ++ PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +\ No newline at end of file +diff --git a/component/ra_tls/LICENSE b/component/ra_tls/LICENSE +new file mode 100644 +index 00000000..f63f5a9c +--- /dev/null ++++ b/component/ra_tls/LICENSE +@@ -0,0 +1,194 @@ ++木兰宽松许可证,第2版 ++ ++木兰宽松许可证,第2版 ++ ++2020年1月 http://license.coscl.org.cn/MulanPSL2 ++ ++您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: ++ ++0. 定义 ++ ++“软件” 是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 ++ ++“贡献” 是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 ++ ++“贡献者” 是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 ++ ++“法人实体” 是指提交贡献的机构及其“关联实体”。 ++ ++“关联实体” 是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是 ++指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 ++ ++1. 授予版权许可 ++ ++每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可 ++以复制、使用、修改、分发其“贡献”,不论修改与否。 ++ ++2. 授予专利许可 ++ ++每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定 ++撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡 ++献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软 ++件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“ ++关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或 ++其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权 ++行动之日终止。 ++ ++3. 无商标许可 ++ ++“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定 ++的声明义务而必须使用除外。 ++ ++4. 分发限制 ++ ++您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“ ++本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 ++ ++5. 免责声明与责任限制 ++ ++“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对 ++任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于 ++何种法律理论,即使其曾被建议有此种损失的可能性。 ++ ++6. 语言 ++ ++“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文 ++版为准。 ++ ++条款结束 ++ ++如何将木兰宽松许可证,第2版,应用到您的软件 ++ ++如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: ++ ++1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; ++ ++2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; ++ ++3, 请将如下声明文本放入每个源文件的头部注释中。 ++ ++Copyright (c) [Year] [name of copyright holder] ++[Software Name] is licensed under Mulan PSL v2. ++You can use this software according to the terms and conditions of the Mulan ++PSL v2. ++You may obtain a copy of Mulan PSL v2 at: ++ http://license.coscl.org.cn/MulanPSL2 ++THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY ++KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ++NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. ++See the Mulan PSL v2 for more details. ++ ++Mulan Permissive Software License,Version 2 ++ ++Mulan Permissive Software License,Version 2 (Mulan PSL v2) ++ ++January 2020 http://license.coscl.org.cn/MulanPSL2 ++ ++Your reproduction, use, modification and distribution of the Software shall ++be subject to Mulan PSL v2 (this License) with the following terms and ++conditions: ++ ++0. Definition ++ ++Software means the program and related documents which are licensed under ++this License and comprise all Contribution(s). ++ ++Contribution means the copyrightable work licensed by a particular ++Contributor under this License. ++ ++Contributor means the Individual or Legal Entity who licenses its ++copyrightable work under this License. ++ ++Legal Entity means the entity making a Contribution and all its ++Affiliates. ++ ++Affiliates means entities that control, are controlled by, or are under ++common control with the acting entity under this License, ‘control’ means ++direct or indirect ownership of at least fifty percent (50%) of the voting ++power, capital or other securities of controlled or commonly controlled ++entity. ++ ++1. Grant of Copyright License ++ ++Subject to the terms and conditions of this License, each Contributor hereby ++grants to you a perpetual, worldwide, royalty-free, non-exclusive, ++irrevocable copyright license to reproduce, use, modify, or distribute its ++Contribution, with modification or not. ++ ++2. Grant of Patent License ++ ++Subject to the terms and conditions of this License, each Contributor hereby ++grants to you a perpetual, worldwide, royalty-free, non-exclusive, ++irrevocable (except for revocation under this Section) patent license to ++make, have made, use, offer for sale, sell, import or otherwise transfer its ++Contribution, where such patent license is only limited to the patent claims ++owned or controlled by such Contributor now or in future which will be ++necessarily infringed by its Contribution alone, or by combination of the ++Contribution with the Software to which the Contribution was contributed. ++The patent license shall not apply to any modification of the Contribution, ++and any other combination which includes the Contribution. If you or your ++Affiliates directly or indirectly institute patent litigation (including a ++cross claim or counterclaim in a litigation) or other patent enforcement ++activities against any individual or entity by alleging that the Software or ++any Contribution in it infringes patents, then any patent license granted to ++you under this License for the Software shall terminate as of the date such ++litigation or activity is filed or taken. ++ ++3. No Trademark License ++ ++No trademark license is granted to use the trade names, trademarks, service ++marks, or product names of Contributor, except as required to fulfill notice ++requirements in section 4. ++ ++4. Distribution Restriction ++ ++You may distribute the Software in any medium with or without modification, ++whether in source or executable forms, provided that you provide recipients ++with a copy of this License and retain copyright, patent, trademark and ++disclaimer statements in the Software. ++ ++5. Disclaimer of Warranty and Limitation of Liability ++ ++THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY ++KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR ++COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT ++LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING ++FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO ++MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF ++THE POSSIBILITY OF SUCH DAMAGES. ++ ++6. Language ++ ++THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION ++AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF ++DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION ++SHALL PREVAIL. ++ ++END OF THE TERMS AND CONDITIONS ++ ++How to Apply the Mulan Permissive Software License,Version 2 ++(Mulan PSL v2) to Your Software ++ ++To apply the Mulan PSL v2 to your work, for easy identification by ++recipients, you are suggested to complete following three steps: ++ ++i. Fill in the blanks in following statement, including insert your software ++name, the year of the first publication of your software, and your name ++identified as the copyright owner; ++ ++ii. Create a file named "LICENSE" which contains the whole context of this ++License in the first directory of your software package; ++ ++iii. Attach the statement to the appropriate annotated syntax at the ++beginning of each source file. ++ ++Copyright (c) [Year] [name of copyright holder] ++[Software Name] is licensed under Mulan PSL v2. ++You can use this software according to the terms and conditions of the Mulan ++PSL v2. ++You may obtain a copy of Mulan PSL v2 at: ++ http://license.coscl.org.cn/MulanPSL2 ++THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY ++KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ++NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. ++See the Mulan PSL v2 for more details. +diff --git a/component/ra_tls/README.md b/component/ra_tls/README.md +new file mode 100644 +index 00000000..f926477f +--- /dev/null ++++ b/component/ra_tls/README.md +@@ -0,0 +1,3 @@ ++# ra-tls ++ ++secGear支持ra-tls +\ No newline at end of file +diff --git a/component/ra_tls/openssl_imp.c b/component/ra_tls/openssl_imp.c +new file mode 100644 +index 00000000..82e0fd0a +--- /dev/null ++++ b/component/ra_tls/openssl_imp.c +@@ -0,0 +1,669 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "ra_tls_imp.h" ++ ++#define OID_LEN_MAX 64 ++#define CERT_TIME_STR_LEN 17 ++#define RSA_PRIVATE_KEY_BITS_2048 2048 ++#define RSA_PRIVATE_KEY_BITS_3072 3072 ++#define RSA_PRIVATE_KEY_BUF_2048 2048 ++#define RSA_PRIVATE_KEY_BUF_3072 3072 ++#define RSA_PUB_KEY_BUF_2048 512 ++#define RSA_PUB_KEY_BUF_3072 650 ++ ++#define ERR_CHECK(CONDITION, RESULT, ERRNO, GOTO_ERR, PREFIX) do { \ ++ if ((CONDITION)) { \ ++ RESULT = ERRNO; \ ++ printf("%s:%d, %s: %s", __FILE__, __LINE__, PREFIX, ERR_reason_error_string(ERR_get_error())); \ ++ goto GOTO_ERR; \ ++ } \ ++} while (0) ++ ++const size_t MIN_CERTIFICATE_SIZE = 4096; ++ ++int ra_tls_buf_init(ra_tls_buf *buf, int len) ++{ ++ if (buf == NULL || len < 0) { ++ return -1; ++ } ++ if (len == 0) { ++ buf->buf = NULL; ++ buf->len = 0; ++ buf->filled = 0; ++ return 0; ++ } ++ buf->buf = malloc(len); ++ if (buf->buf == NULL) { ++ return -1; ++ } ++ memset(buf->buf, 0, len); ++ buf->len = len; ++ buf->filled = 0; ++ return len; ++} ++ ++void ra_tls_buf_free(ra_tls_buf *buf) ++{ ++ if (buf == NULL) { ++ return; ++ } ++ if (buf->buf) { ++ free(buf->buf); ++ buf->buf = NULL; ++ } ++ buf->len = 0; ++ buf->filled = 0; ++} ++ ++static int generate_pkey_rsa(EVP_PKEY *pk, key_size key_len) ++{ ++ int ret = -1; ++ int key_bits = 0; ++ if (pk == NULL) { ++ return ret; ++ } ++ EVP_PKEY_CTX *ctx = NULL; ++ if (key_len == RSA_2048) { ++ key_bits = RSA_PRIVATE_KEY_BITS_2048; ++ } else if (key_len == RSA_3072) { ++ key_bits = RSA_PRIVATE_KEY_BITS_3072; ++ } else { ++ printf("unknown key length:%d\n", key_len); ++ return ret; ++ } ++ ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); ++ if (ctx == NULL) { ++ return ret; ++ } ++ int res = EVP_PKEY_keygen_init(ctx); ++ if (res <= 0) { ++ printf("key generate failed (%d)\n", res); ++ ret = -1; ++ goto done; ++ } ++ ++ res = EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, key_bits); ++ if (res <= 0) { ++ printf("set_rsa_kengen_bits failed (%d)\n", res); ++ ret = -1; ++ goto done; ++ } ++ res = EVP_PKEY_keygen(ctx, &pk); ++ if (res <= 0) { ++ printf("keygen failed (%d)\n", res); ++ ret = -1; ++ goto done; ++ } ++ ret = 0; ++done: ++ if (ctx) { ++ EVP_PKEY_CTX_free(ctx); ++ } ++ ++ return ret; ++} ++ ++// type = 0, 公钥,=1 私钥 ++static int read_key(EVP_PKEY *pkey, key_type type, ra_tls_buf *key) ++{ ++ BIO *bio = NULL; ++ int ret = -1; ++ int res; ++ if (pkey == NULL) { ++ return ret; ++ } ++ bio = BIO_new(BIO_s_mem()); ++ if (bio == NULL) { ++ printf("bio new failed"); ++ return ret; ++ } ++ if (type == KEY_PUBLIC) { ++ ret = i2d_PUBKEY_bio(bio, pkey); ++ } else if (type == KEY_PRIVATE) { ++ ret = i2d_PrivateKey_bio(bio, pkey); ++ } else { ++ printf("unknown key type\n"); ++ ret = -1; ++ } ++ res = BIO_read(bio, key->buf, key->len); ++ if (res > 0) { ++ ret = 0; ++ key->filled = res; ++ } ++ if (bio) { ++ BIO_free(bio); ++ } ++ bio = NULL; ++ return ret; ++} ++ ++static X509_NAME *create_x509_name(const char *country_name, const char *org, const char *comm_name) ++{ ++ X509_NAME* name = X509_NAME_new(); ++ X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (const unsigned char *)country_name, -1, -1, 0); ++ X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (const unsigned char *)org, -1, -1, 0); ++ X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char *)comm_name, -1, -1, 0); ++ return name; ++} ++ ++static int set_public_key(X509 *cert, ra_tls_buf *pub_key) ++{ ++ int ret = -1; ++ int res = 0; ++ EVP_PKEY *pkey = NULL; ++ BIO *bio = NULL; ++ pkey = EVP_PKEY_new(); ++ ERR_CHECK(pkey == NULL, ret, -1, err, "new public key error"); ++ bio = BIO_new_mem_buf((const void*)pub_key->buf, (int)pub_key->filled); ++ ERR_CHECK(bio == NULL, ret, -1, err, "bio new error"); ++ EVP_PKEY *key = d2i_PUBKEY_bio(bio, &pkey); ++ ERR_CHECK(key == NULL, ret, -1, err, "read public key error"); ++ res = X509_set_pubkey(cert, pkey); ++ ERR_CHECK(res == 0, ret, -1, err, "set x509 public key error"); ++ ret = 0; ++err: ++ if (bio) { ++ BIO_free(bio); ++ } ++ if (pkey) { ++ EVP_PKEY_free(pkey); ++ } ++ return ret; ++} ++ ++static int sign_cert(X509 *cert, ra_tls_buf *prv_key) ++{ ++ int ret = -1; ++ int res = 0; ++ EVP_PKEY *pkey = NULL; ++ BIO *bio = NULL; ++ pkey = EVP_PKEY_new(); ++ ERR_CHECK(pkey == NULL, ret, -1, err, "new private key error"); ++ bio = BIO_new_mem_buf((const void*)prv_key->buf, (int)prv_key->filled); ++ ERR_CHECK(bio == NULL, ret, -1, err, "bio new error"); ++ EVP_PKEY *key = d2i_PrivateKey_bio(bio, &pkey); ++ ERR_CHECK(key == NULL, ret, -1, err, "read private key error"); ++ res = X509_sign(cert, pkey, EVP_sha256()); ++ ERR_CHECK(res == 0, ret, -1, err, "sign error"); ++ ret = 0; ++err: ++ if (bio) { ++ BIO_free(bio); ++ } ++ if (pkey) { ++ EVP_PKEY_free(pkey); ++ } ++ return ret; ++} ++ ++static int set_subject(X509 *cert) ++{ ++ int ret = -1; ++ int res = 0; ++ X509_NAME *name = NULL; ++ name = create_x509_name("ZH", "Huawei Corporation", "VirtCCA Enclave"); ++ ERR_CHECK(name == NULL, ret, -1, err, "create subject name error"); ++ res = X509_set_subject_name(cert, name); ++ ERR_CHECK(res == 0, ret, -1, err, "set x509 subject name error"); ++ ret = 0; ++err: ++ if (name) { ++ X509_NAME_free(name); ++ } ++ return ret; ++} ++ ++static int set_issuer(X509 *cert) ++{ ++ int ret = -1; ++ int res = 0; ++ X509_NAME *name = NULL; ++ name = create_x509_name("ZH", "Huawei Corporation", "VirtCCA Enclave"); ++ ERR_CHECK(name == NULL, ret, -1, err, "create issuer name error"); ++ res = X509_set_issuer_name(cert, name); ++ ERR_CHECK(res == 0, ret, -1, err, "set x509 issuer name error"); ++ ret = 0; ++err: ++ if (name) { ++ X509_NAME_free(name); ++ } ++ return ret; ++} ++ ++static int set_extension(X509 *cert, cert_config *cfg) ++{ ++ int res = 0; ++ int ret = -1; ++ X509V3_CTX ctx; ++ ASN1_OBJECT *obj = NULL; ++ ASN1_OCTET_STRING *data = NULL; ++ X509_EXTENSION *ext = NULL; ++ ++ X509V3_set_ctx_nodb(&ctx); ++ X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0); ++ ++ // set extension ++ ext = X509V3_EXT_conf_nid(NULL, &ctx, NID_basic_constraints, "CA:FALSE"); ++ res = X509_add_ext(cert, ext, -1); ++ ERR_CHECK(res == 0, ret, -1, err, "x509 add basic constraints error"); ++ X509_EXTENSION_free(ext); ++ ext = NULL; ++ ++ ext = X509V3_EXT_conf_nid(NULL, &ctx, NID_subject_key_identifier, "hash"); ++ res = X509_add_ext(cert, ext, -1); ++ ERR_CHECK(res == 0, ret, -1, err, "x509 add subject key identifier error"); ++ X509_EXTENSION_free(ext); ++ ext = NULL; ++ ++ ext = X509V3_EXT_conf_nid(NULL, &ctx, NID_authority_key_identifier, "keyid:always"); ++ res = X509_add_ext(cert, ext, -1); ++ ERR_CHECK(res == 0, ret, -1, err, "x509 add authority key identifier error"); ++ X509_EXTENSION_free(ext); ++ ext = NULL; ++ ++ // add extension evidence ++ obj = OBJ_txt2obj(cfg->ext_oid, 1); ++ ERR_CHECK(obj == NULL, ret, -1, err, "create asn1 obj error"); ++ data = ASN1_OCTET_STRING_new(); ++ ERR_CHECK(data == NULL, ret, -1, err, "create asn1 string error"); ++ res = ASN1_OCTET_STRING_set(data, cfg->ext.buf, strlen((const char *)cfg->ext.buf)); ++ ERR_CHECK(res == 0, ret, -1, err, "asn1 set string error"); ++ ext = X509_EXTENSION_create_by_OBJ(&ext, obj, 0, data); ++ ERR_CHECK(ext == NULL, ret, -1, err, "create x509 extension error"); ++ res = X509_add_ext(cert, ext, -1); ++ ERR_CHECK(res == 0, ret, -1, err, "x509 add evidence error"); ++ ret = 0; ++err: ++ if (ext) { ++ X509_EXTENSION_free(ext); ++ } ++ if (data) { ++ ASN1_OCTET_STRING_free(data); ++ } ++ if (obj) { ++ ASN1_OBJECT_free(obj); ++ } ++ return ret; ++} ++ ++static int output_certificate_der(ra_tls_buf *output, X509 *cert) ++{ ++ int ret = -1; ++ int res = 0; ++ BIO *bio = NULL; ++ bio = BIO_new(BIO_s_mem()); ++ ERR_CHECK(bio == NULL, ret, -1, err, "bio new failed"); ++ ERR_clear_error(); ++ res = i2d_X509_bio(bio, cert); ++ ERR_CHECK(res == 0, ret, -1, err, "output certificate to bio error"); ++ res = BIO_read(bio, output->buf, output->len); ++ ERR_CHECK(res <= 0, ret, -1, err, "read cert from bio error"); ++ output->filled = res; ++ ret = 0; ++ goto end; ++err: ++ ra_tls_buf_free(output); ++end: ++ if (bio) { ++ BIO_free(bio); ++ } ++ return ret; ++} ++ ++static int generate_x509_self_signed_certificate(ra_tls_buf *output_cert, cert_config *cfg) ++{ ++ int res = 0; ++ int ret = -1; ++ const int x509_ver = 2; // 2 means X509 Version 3 ++ X509 *x509cert = NULL; ++ if (output_cert == NULL || cfg == NULL) { ++ return ret; ++ } ++ ERR_load_crypto_strings(); ++ OPENSSL_init_crypto(0, NULL); ++ // certificate version 3 ++ x509cert = X509_new(); ++ res = X509_set_version(x509cert, x509_ver); ++ ERR_CHECK(res == 0, ret, -1, err, "set x509 version error"); ++ ret = set_public_key(x509cert, &(cfg->pub_key)); ++ ERR_CHECK(res < 0, ret, -1, err, "set x509 public key error"); ++ res = set_subject(x509cert); ++ ERR_CHECK(res < 0, ret, -1, err, "set x509 subject name error"); ++ res = set_issuer(x509cert); ++ ERR_CHECK(res < 0, ret, -1, err, "set x509 issuer name error"); ++ ++ // set serial number ++ res = ASN1_INTEGER_set(X509_get_serialNumber(x509cert), 1); ++ ERR_CHECK(res == 0, ret, -1, err, "set x509 serial number error"); ++ ++ // set date ++ res = ASN1_TIME_set_string(X509_getm_notBefore(x509cert), cfg->not_before); ++ ERR_CHECK(res == 0, ret, -1, err, "set x509 not before error"); ++ res = ASN1_TIME_set_string(X509_getm_notAfter(x509cert), cfg->not_after); ++ ERR_CHECK(res == 0, ret, -1, err, "set x509 not after error"); ++ ++ // set extension ++ res = set_extension(x509cert, cfg); ++ ERR_CHECK(res < 0, ret, -1, err, "set x509 extension error"); ++ // sign certificate ++ res = sign_cert(x509cert, &(cfg->prv_key)); ++ ERR_CHECK(res < 0, ret, -1, err, "sign error"); ++ // output certificate ++ res = ra_tls_buf_init(output_cert, cfg->pub_key.len + cfg->ext.len + MIN_CERTIFICATE_SIZE); ++ ERR_CHECK(res <= 0, ret, -1, err, "init buffer failed"); ++ res = output_certificate_der(output_cert, x509cert); ++ ERR_CHECK(res < 0, ret, -1, err, "output certificate failed"); ++ ret = 0; ++err: ++ if (x509cert) { ++ X509_free(x509cert); ++ } ++ return ret; ++} ++ ++static int openssl_generate_certificate_with_extension(ra_tls_buf *cert, ra_tls_buf *ext, ++ ra_tls_buf *public_key, ra_tls_buf *private_key, const char *oid) ++{ ++ int res = 0; ++ int ret = -1; ++ char not_before[CERT_TIME_STR_LEN] = {0}; ++ char not_after[CERT_TIME_STR_LEN] = {0}; ++ // fill config and generate certificate ++ if (cert == NULL || ext == NULL || public_key == NULL || private_key == NULL || oid == NULL) { ++ return ret; ++ } ++ time_t now = time(NULL); ++ struct tm *tm = localtime(&now); ++ ++ res = strftime(not_before, CERT_TIME_STR_LEN, "%Y%m%d%H%M%SZ", tm); ++ if (!res) { ++ return ret; ++ } ++ tm->tm_year += DEFAULT_CERT_LIFETIME_YEARS; ++ res = strftime(not_after, CERT_TIME_STR_LEN, "%Y%m%d%H%M%SZ", tm); ++ if (!res) { ++ return ret; ++ } ++ cert_config cert_cfg; ++ cert_cfg.prv_key = *private_key; ++ cert_cfg.pub_key = *public_key; ++ cert_cfg.not_before = not_before; ++ cert_cfg.not_after = not_after; ++ cert_cfg.ext_oid = oid; ++ cert_cfg.ext = *ext; ++ res = generate_x509_self_signed_certificate(cert, &cert_cfg); ++ if (res >= 0) { ++ ret = 0; ++ } ++ return ret; ++} ++ ++static int init_key_buffer(key_size key_len, ra_tls_buf *public_key, ra_tls_buf *private_key) ++{ ++ int ret = -1; ++ int pub_len = 0; ++ int prv_len = 0; ++ if (key_len == RSA_2048) { ++ pub_len = RSA_PUB_KEY_BUF_2048; ++ prv_len = RSA_PRIVATE_KEY_BUF_2048; ++ } else if (key_len == RSA_3072) { ++ pub_len = RSA_PUB_KEY_BUF_3072; ++ prv_len = RSA_PRIVATE_KEY_BUF_3072; ++ } else { ++ printf("unknown key length\n"); ++ return -1; ++ } ++ if (ra_tls_buf_init(public_key, pub_len) < 0) { ++ ret = -1; ++ goto err; ++ } ++ if (ra_tls_buf_init(private_key, prv_len) < 0) { ++ ret = -1; ++ goto err; ++ } ++ ret = 0; ++ return ret; ++err: ++ ra_tls_buf_free(public_key); ++ ra_tls_buf_free(private_key); ++ return ret; ++} ++ ++static int get_sha256(ra_tls_buf* hash, ra_tls_buf* input) ++{ ++ int res = 0; ++ int ret = -1; ++ unsigned int hash_len; ++ EVP_MD_CTX *md_ctx; ++ if (hash == NULL || input == NULL) { ++ return ret; ++ } ++ md_ctx = EVP_MD_CTX_new(); ++ if (EVP_DigestInit_ex(md_ctx, EVP_sha256(), NULL) != 1) { ++ goto err; ++ } ++ EVP_DigestUpdate(md_ctx, input->buf, input->filled); ++ ra_tls_buf_init(hash, EVP_MAX_MD_SIZE); ++ hash_len = EVP_MAX_MD_SIZE; ++ res = EVP_DigestFinal_ex(md_ctx, hash->buf, &hash_len); ++ if (res <= 0) { ++ ret = -1; ++ goto err; ++ } ++ hash->filled = hash_len; ++ ret = 0; ++ goto end; ++err: ++ ra_tls_buf_free(hash); ++end: ++ if (md_ctx) { ++ EVP_MD_CTX_free(md_ctx); ++ } ++ return ret; ++} ++ ++int generate_key_pair_der(key_size key_len, ra_tls_buf *public_key, ra_tls_buf *private_key) ++{ ++ int res; ++ int ret = -1; ++ EVP_PKEY *pkey = NULL; ++ ra_tls_buf pub_key = RA_TLS_BUF_INIT; ++ ra_tls_buf prv_key = RA_TLS_BUF_INIT; ++ if (public_key == NULL || private_key == NULL) { ++ return ret; ++ } ++ res = init_key_buffer(key_len, &pub_key, &prv_key); ++ if (res < 0) { ++ return -1; ++ } ++ ++ pkey = EVP_PKEY_new(); ++ if (pkey == NULL) { ++ printf("EVP_PKEY_new return NULL\n"); ++ return ret; ++ } ++ res = generate_pkey_rsa(pkey, key_len); ++ if (res < 0) { ++ printf("generate rsa key failed\n"); ++ ret = -1; ++ goto err; ++ } ++ res = read_key(pkey, KEY_PUBLIC, &pub_key); ++ if (res < 0) { ++ printf("read public key failed\n"); ++ ret = -1; ++ goto err; ++ } ++ res = read_key(pkey, KEY_PRIVATE, &prv_key); ++ if (res < 0) { ++ printf("read private key failed\n"); ++ ret = -1; ++ goto err; ++ } ++ *public_key = pub_key; ++ *private_key = prv_key; ++ ret = 0; ++err: ++ if (pkey) { ++ EVP_PKEY_free(pkey); ++ } ++ return ret; ++} ++ ++int get_hash(ra_tls_buf *hash, ra_tls_buf *input, hash_type type) ++{ ++ switch (type) { ++ case SHA_256: ++ get_sha256(hash, input); ++ break; ++ default: ++ printf("unknown hash type\n"); ++ return -1; ++ } ++ return 0; ++} ++ ++int generate_certificate_with_extension(ra_tls_buf *cert, ra_tls_buf *extension, ra_tls_buf *public_key, ++ ra_tls_buf *private_key, const char *oid) ++{ ++ return openssl_generate_certificate_with_extension(cert, extension, public_key, private_key, oid); ++} ++ ++static int get_extension_from_X509(ra_tls_buf *ext_buf, ra_tls_buf *oid, X509 *cert) ++{ ++ int extensions_cnt = 0; ++ if (cert == NULL || ext_buf == NULL || oid == NULL) { ++ return -1; ++ } ++ const STACK_OF(X509_EXTENSION) *extensions = NULL; ++ if (!(extensions = X509_get0_extensions(cert))) { ++ printf("get extensions failed: %s\n", ERR_reason_error_string(ERR_get_error())); ++ return -1; ++ } ++ extensions_cnt = sk_X509_EXTENSION_num(extensions); ++ ++ ra_tls_buf_init(oid, OID_LEN_MAX + 1); ++ for (int i = 0; i < extensions_cnt; i++) { ++ X509_EXTENSION *ext; ++ ASN1_OBJECT *asn1_obj; ++ ASN1_OCTET_STRING *asn1_str; ++ ext = sk_X509_EXTENSION_value(extensions, i); ++ if (ext == NULL) { ++ printf("get extension[%d] failed: %s\n", i, ERR_reason_error_string(ERR_get_error())); ++ goto err; ++ } ++ if (!(asn1_obj = X509_EXTENSION_get_object(ext))) { ++ printf("get extensions obj failed: %s\n", ERR_reason_error_string(ERR_get_error())); ++ goto err; ++ } ++ if (!OBJ_obj2txt((char *)oid->buf, oid->len, asn1_obj, 1)) { ++ printf("get extensions oid failed: %s\n", ERR_reason_error_string(ERR_get_error())); ++ goto err; ++ } ++ if (strcmp((const char *)oid->buf, EVIDENCE_OID) != 0 && strcmp((const char *)oid->buf, TOKEN_OID) != 0) { ++ continue; ++ } ++ if (!(asn1_str = X509_EXTENSION_get_data(ext))) { ++ printf("get extensions data failed: %s\n", ERR_reason_error_string(ERR_get_error())); ++ goto err; ++ } ++ ra_tls_buf_init(ext_buf, asn1_str->length + 1); ++ memcpy(ext_buf->buf, asn1_str->data, asn1_str->length); ++ ext_buf->filled = asn1_str->length; ++ ext_buf->buf[ext_buf->filled] = '\0'; ++#ifdef DEBUG ++ printf("oid: %s\n", oid->buf); ++ printf("extension:\n%s\n", ext_buf->buf); ++#endif ++ return 0; ++ } ++err: ++ ra_tls_buf_free(ext_buf); ++ ra_tls_buf_free(oid); ++ return -1; ++} ++// for verify ++int get_extension_from_certificate_context(ra_tls_buf *ext_buf, ra_tls_buf *oid, void *cert_ctx) ++{ ++ int ret = -1; ++ X509 *cert = NULL; ++ X509_STORE_CTX *x509_ctx = (X509_STORE_CTX *)cert_ctx; ++ if (ext_buf == NULL || oid == NULL) { ++ goto end; ++ } ++ cert = X509_STORE_CTX_get_current_cert(x509_ctx); ++ if (cert == NULL) { ++ printf("get certificate failed: %s\n", ERR_reason_error_string(ERR_get_error())); ++ goto end; ++ } ++ if (get_extension_from_X509(ext_buf, oid, cert) < 0) { ++ goto end; ++ } ++ ret = 0; ++end: ++ return ret; ++} ++ ++int get_public_key_from_certificate_context(ra_tls_buf* key_der, void* cert_ctx) ++{ ++ int ret = -1; ++ X509 *cert = NULL; ++ unsigned char *pub_key = NULL; ++ int pub_key_len = 0; ++ X509_STORE_CTX *x509_ctx = (X509_STORE_CTX*)cert_ctx; ++ cert = X509_STORE_CTX_get_current_cert(x509_ctx); ++ if (cert == NULL) { ++ printf("get certificate failed: %s\n", ERR_reason_error_string(ERR_get_error())); ++ goto err; ++ } ++ ra_tls_buf_init(key_der, KEY_SIZE_MAX); ++ pub_key = key_der->buf; ++ pub_key_len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &pub_key); ++ if (pub_key_len <= 0) { ++ printf("get public key failed: %s\n", ERR_reason_error_string(ERR_get_error())); ++ goto err; ++ } ++ key_der->filled = pub_key_len; ++ ret = 0; ++err: ++ return ret; ++} ++ ++int get_extension_from_certificate_der(ra_tls_buf *ext_buf, ra_tls_buf *oid, ra_tls_buf *cert_der) ++{ ++ int ret = -1; ++ BIO *bio_cert = BIO_new_mem_buf(cert_der->buf, cert_der->filled); ++ X509 *icert = NULL; ++ if (d2i_X509_bio(bio_cert, &icert) == NULL) { ++ printf("der read certificate failed: %s\n", ERR_reason_error_string(ERR_get_error())); ++ goto err; ++ } ++ if (get_extension_from_X509(ext_buf, oid, icert) < 0) { ++ goto err; ++ } ++ ret = 0; ++err: ++ if (bio_cert) { ++ BIO_free(bio_cert); ++ } ++ if (icert) { ++ X509_free(icert); ++ } ++ return ret; ++} +diff --git a/component/ra_tls/ra_tls.c b/component/ra_tls/ra_tls.c +new file mode 100644 +index 00000000..eba084fa +--- /dev/null ++++ b/component/ra_tls/ra_tls.c +@@ -0,0 +1,898 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include "base64url.h" ++#include "ra_tls.h" ++ ++static char *agent_addr = NULL; ++ ++typedef enum { ++ EVIDENCE, ++ TOKEN ++}extension_type; ++ ++/* internal api */ ++static size_t wb(char *ptr, size_t size, size_t nmemb, void *userdata) ++{ ++ size_t total = size * nmemb; ++ ra_tls_buf *buf = (ra_tls_buf*)userdata; ++ ra_tls_buf_init(buf, total + 1); ++ memcpy(buf->buf, ptr, total); ++ buf->filled = total; ++ buf->buf[buf->filled] = '\0'; ++ return total; ++} ++ ++static int http_request(const char *endpoint, const char *type, const char *data, ra_tls_buf *response) ++{ ++ int ret = -1; ++ CURLcode res; ++ CURL *curl = NULL; ++ struct curl_slist *plist = NULL; ++ curl = curl_easy_init(); ++ if (curl == NULL) { ++ goto err; ++ } ++ plist = curl_slist_append(NULL, "Content-Type:application/json;charset=UTF-8"); ++ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, plist); ++ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, type); ++ if (data) { ++ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); ++ } ++ curl_easy_setopt(curl, CURLOPT_URL, endpoint); ++ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, wb); ++ curl_easy_setopt(curl, CURLOPT_WRITEDATA, response); ++ curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); ++ res = curl_easy_perform(curl); ++ if (res != CURLE_OK) { ++ ret = -1; ++ goto err; ++ } ++ ret = 0; ++ goto end; ++err: ++ ra_tls_buf_free(response); ++end: ++ if (plist) { ++ curl_slist_free_all(plist); ++ } ++ if (curl) { ++ curl_easy_cleanup(curl); ++ } ++ return ret; ++} ++ ++static int get_quote(ra_tls_buf *quote, const char *endpoint_prefix, const char *type, ++ const char *uuid, ra_tls_buf *challenge) ++{ ++ int ret = -1; ++ int res = 0; ++ char *endpoint = NULL; ++ size_t endpoint_len = 0; ++ const char *http_data_format = "{\"challenge\":\"%s\",\"uuid\":\"%s\"}"; ++ char *http_data = NULL; ++ int http_data_len; ++ if (endpoint_prefix == NULL || quote == NULL || uuid == NULL || challenge == NULL) { ++ return -1; ++ } ++ endpoint_len = strlen(endpoint_prefix) + strlen(type) + 1; ++ endpoint = malloc(endpoint_len); ++ if (endpoint == NULL) { ++ goto err; ++ } ++ strcpy(endpoint, endpoint_prefix); ++ strcat(endpoint, type); ++ http_data_len = strlen(uuid) + challenge->filled + strlen(http_data_format) + 1; ++ http_data = malloc(http_data_len); ++ if (http_data == NULL) { ++ ret = -1; ++ goto err; ++ } ++ res = sprintf(http_data, http_data_format, challenge->buf, uuid); ++ ++ res = http_request(endpoint, "GET", http_data, quote); ++ if (res < 0) { ++ goto err; ++ } ++ ret = 0; ++ goto end; ++err: ++ ra_tls_buf_free(quote); ++end: ++ if (http_data) { ++ free(http_data); ++ } ++ if (endpoint) { ++ free(endpoint); ++ } ++ return ret; ++} ++ ++static int get_evidence_ok(const ra_tls_buf *evidence) ++{ ++ int ret = -1; ++ cJSON *json_root = NULL; ++ if (evidence == NULL) { ++ return ret; ++ } ++ if (evidence->filled <= 0) { ++ return ret; ++ } ++ json_root = cJSON_Parse((char*)evidence->buf); ++ if (json_root != NULL) { ++ ret = 0; ++ } ++ cJSON_Delete(json_root); ++ return ret; ++} ++static int get_evidence(ra_tls_buf *evidence, const char *endpoint_prefix, const char *uuid, ra_tls_buf *challenge) ++{ ++ int ret = -1; ++ int res = 0; ++ res = get_quote(evidence, endpoint_prefix, "evidence", uuid, challenge); ++ if (res < 0) { ++ return ret; ++ } ++ res = get_evidence_ok(evidence); ++ if (res < 0) { ++ return ret; ++ } ++ ret = 0; ++ return ret; ++} ++ ++static int get_token_ok(const ra_tls_buf *token) ++{ ++ int ret = -1; ++ size_t offset = 0; ++ cJSON *json_root = NULL; ++ uint8_t *header_base64 = NULL; ++ int header_base64_len = 0; ++ char *header = NULL; ++ size_t header_len = 0; ++ if (token == NULL) { ++ return ret; ++ } ++ if (token->filled <= 0) { ++ return ret; ++ } ++ while (offset < token->filled) { ++ // token sperated by '.' ++ if (token->buf[offset] == '.') { ++ break; ++ } ++ offset++; ++ } ++ if (offset == token->filled) { ++ goto err; ++ } ++ header_base64_len = offset; ++ header_base64 = malloc(header_base64_len + 1); ++ memcpy(header_base64, token->buf, header_base64_len); ++ header_base64[header_base64_len] = '\0'; ++ header = (char*)kpsecl_base64urldecode((const char*)header_base64, header_base64_len, &header_len); ++ if (header_len <= 0) { ++ goto err; ++ } ++ json_root = cJSON_Parse((const char*)header); ++ if (json_root != NULL) { ++ ret = 0; ++ } ++err: ++ if (json_root) { ++ cJSON_Delete(json_root); ++ } ++ if (header_base64) { ++ free(header_base64); ++ } ++ if (header) { ++ free(header); ++ } ++ return ret; ++} ++static int get_token(ra_tls_buf *token, const char *endpoint_prefix, const char *uuid, ra_tls_buf *challenge) ++{ ++ int ret = -1; ++ int res = 0; ++ res = get_quote(token, endpoint_prefix, "token", uuid, challenge); ++ if (res < 0) { ++ return ret; ++ } ++ res = get_token_ok(token); ++ if (res < 0) { ++ return ret; ++ } ++ ret = 0; ++ return ret; ++} ++ ++static char *fill_endpoint(char **endpoint, const char *prefix, const char *source) ++{ ++ size_t endpoint_len = 0; ++ endpoint_len = strlen(prefix) + strlen(source) + 1; ++ *endpoint = malloc(endpoint_len); ++ if (*endpoint == NULL) { ++ return NULL; ++ } ++ strcpy(*endpoint, prefix); ++ strcat(*endpoint, source); ++ return *endpoint; ++} ++ ++static char *fill_http_data(char **data, ra_tls_buf *key_hash) ++{ ++ const char *http_data_format = "{\"user_data\":[]}"; ++ size_t data_len = 0; ++ int res; ++ char *ptr; ++ data_len = strlen(http_data_format) + PUBLIC_KEY_HASH_PRINT_LEN + strlen("]}") + 1; ++ *data = malloc(data_len); ++ if (*data == NULL) { ++ return NULL; ++ } ++ ptr = *data; ++ res = sprintf(ptr, "{\"user_data\":["); ++ ptr += res; ++ for (size_t i = 0; i < key_hash->filled; i++) { ++ res = sprintf(ptr, "%hhu,", key_hash->buf[i]); ++ ptr += res; ++ if (ptr >= *data + data_len) { ++ goto err; ++ } ++ } ++ // point to last character ++ --ptr; ++ if (data_len - (ptr - *data) < 3) { // 3 means min buffer left to filled ++ goto err; ++ } ++ (void)sprintf(ptr, "]}"); ++ goto end; ++err: ++ if (*data) { ++ free(*data); ++ *data = NULL; ++ } ++end: ++ return *data; ++} ++ ++static int get_challenge(ra_tls_buf *challenge, const char *endpoint_prefix, ra_tls_buf *user_data) ++{ ++ int res; ++ int ret = -1; ++ const char *source_name = "challenge"; ++ char *endpoint = NULL; ++ char *http_data = NULL; ++ ra_tls_buf key_hash = RA_TLS_BUF_INIT; ++ ra_tls_buf *pub_key = user_data; ++ if (endpoint_prefix == NULL || challenge == NULL || pub_key == NULL) { ++ return -1; ++ } ++ ra_tls_buf_init(&key_hash, 0); ++ get_hash(&key_hash, pub_key, SHA_256); ++#ifdef DEBUG ++ printf("public key hash:"); ++ for (size_t i = 0; i < key_hash.filled; i++) { ++ printf("%02X", key_hash.buf[i]); ++ } ++ printf("\n"); ++#endif ++ if (fill_endpoint(&endpoint, endpoint_prefix, source_name) == NULL) { ++ goto err; ++ } ++ if (fill_http_data(&http_data, &key_hash) == NULL) { ++ goto err; ++ } ++ res = http_request(endpoint, "GET", http_data, challenge); ++ if (res < 0) { ++ goto err; ++ } ++ ret = 0; ++err: ++ if (endpoint) { ++ free(endpoint); ++ } ++ if (http_data) { ++ free(http_data); ++ } ++ ra_tls_buf_free(&key_hash); ++ return ret; ++} ++ ++static int generate_extension_string(ra_tls_buf *extension, ra_tls_buf *challenge, ra_tls_buf *quote) ++{ ++ int ret = -1; ++ cJSON *ext = NULL; ++ char *json_str = NULL; ++ if (extension == NULL || challenge == NULL || quote == NULL) { ++ return ret; ++ } ++ ext = cJSON_CreateObject(); ++ if (ext == NULL) { ++ return ret; ++ } ++ cJSON_AddStringToObject(ext, "challenge", (char *)challenge->buf); ++ cJSON_AddStringToObject(ext, "quote", (char *)quote->buf); ++ if ((json_str = cJSON_PrintUnformatted(ext)) == NULL) { ++ goto err; ++ } ++ cJSON_Minify(json_str); ++ ra_tls_buf_init(extension, strlen(json_str) + 1); ++ strcpy((char *)extension->buf, json_str); ++ extension->filled = strlen(json_str) + 1; ++ ret = 0; ++err: ++ if (ext) { ++ cJSON_Delete(ext); ++ } ++ if (json_str) { ++ cJSON_free(json_str); ++ } ++ return ret; ++} ++ ++static int get_quote_ra(ra_tls_buf *quote, ra_tls_buf *challenge, ra_tls_buf *pub_key, ra_cfg *cfg) ++{ ++ int ret = -1; ++ int res = 0; ++/* ++ get challenge ++ The public key in the signature certificate needs to be protected. ++ now we use pub_key hash concatenated with challenge. ++*/ ++ res = get_challenge(challenge, cfg->aa_addr, pub_key); ++ if (res < 0) { ++ printf("get challenge failed\n"); ++ goto err; ++ } ++#ifdef DEBUG ++ printf("challenge: %s\n", challenge->buf); ++#endif ++ if (cfg->mode == BACKGROUND) { ++ // get evidence ++ res = get_evidence(quote, cfg->aa_addr, cfg->uuid, challenge); ++ if (res < 0) { ++ printf("get evidence failed\n"); ++ goto err; ++ } ++#ifdef DEBUG ++ printf("evidence: %s\n", quote->buf); ++#endif ++ } else if (cfg->mode == PASSPORT) { ++ // get token ++ res = get_token(quote, cfg->aa_addr, cfg->uuid, challenge); ++ if (res < 0) { ++ printf("get token failed\n"); ++ goto err; ++ } ++#ifdef DEBUG ++ printf("token: %s\n", quote->buf); ++#endif ++ } else { ++ printf("unknown work mode\n"); ++ goto err; ++ } ++ ret = 0; ++err: ++ return ret; ++} ++ ++/* output certificate and private key */ ++int ra_tls_generate_certificate(ra_tls_buf *cert, ra_tls_buf *private_key, ra_cfg *cfg, key_size size) ++{ ++ int res; ++ int ret = -1; ++ const char* oid = NULL; ++ ra_tls_buf pub_key = RA_TLS_BUF_INIT; ++ ra_tls_buf prv_key = RA_TLS_BUF_INIT; ++ ra_tls_buf challenge = RA_TLS_BUF_INIT; ++ ra_tls_buf extension = RA_TLS_BUF_INIT; ++ ra_tls_buf quote = RA_TLS_BUF_INIT; ++ if (cert == NULL || private_key == NULL || cfg == NULL) { ++ return ret; ++ } ++ if (cfg->aa_addr == NULL || cfg->uuid == NULL) { ++ return ret; ++ } ++ if (cfg->aa_addr[strlen(cfg->aa_addr) - 1] != '/') { ++ printf("aa_addr should end with '/'\n"); ++ return ret; ++ } ++ res = generate_key_pair_der(size, &pub_key, &prv_key); ++ if (res < 0) { ++ return ret; ++ } ++ *private_key = prv_key; ++ res = get_quote_ra("e, &challenge, &pub_key, cfg); ++ if (res < 0) { ++ goto err; ++ } ++// extension contained: evidence or token and challenge ++ if (generate_extension_string(&extension, &challenge, "e) < 0) { ++ goto err; ++ } ++#ifdef DEBUG ++ printf("certificate extension: %s\n", extension.buf); ++#endif ++// generate certificate ++ oid = (cfg->mode == BACKGROUND)? EVIDENCE_OID : TOKEN_OID; ++ res = generate_certificate_with_extension(cert, &extension, &pub_key, &prv_key, oid); ++ if (res < 0) { ++ ret = -1; ++ } ++ ret = 0; ++err: ++ ra_tls_buf_free(&pub_key); ++ ra_tls_buf_free(&challenge); ++ ra_tls_buf_free(&extension); ++ ra_tls_buf_free("e); ++ return ret; ++} ++ ++// for client to verify externsion in certificate ++int ra_tls_set_addr(char *addr) ++{ ++ if (addr == NULL) { ++ return -1; ++ } ++ if (addr[strlen(addr) - 1] != '/') { ++ printf("host should end with '/'\n"); ++ return -1; ++ } ++ agent_addr = addr; ++ return 0; ++} ++ ++/* ++ token is a string, separated by '.', like this: ++ HEADER.CLAIM.SIGNATURE ++ HEADER,CLAIM encoded by base64_url ++*/ ++static int parse_claim_from_token(ra_tls_buf *claim, ra_tls_buf *token) ++{ ++ size_t claim_start = 0; ++ size_t claim_end = 0; ++ while (claim_start < token->filled) { ++ if (token->buf[claim_start] != '.') { ++ claim_start++; ++ } else { ++ break; ++ } ++ } ++ claim_start++; ++ claim_end = claim_start; ++ while (claim_end < token->filled) { ++ if (token->buf[claim_end] != '.') { ++ claim_end++; ++ } else { ++ break; ++ } ++ } ++ claim_end--; ++ if (claim_end <= claim_start) { ++ return -1; ++ } ++ ra_tls_buf_init(claim, claim_end - claim_start + 1); ++ (void)memcpy(claim->buf, &token->buf[claim_start], claim_end - claim_start + 1); ++ claim->filled = claim_end - claim_start + 1; ++ return 0; ++} ++ ++static int expect_response_true(ra_tls_buf *resp, extension_type mode) ++{ ++ int ret = -1; ++ cJSON *root = NULL; ++ cJSON *obj_parse = NULL; ++ cJSON *obj_get = NULL; ++ if (resp == NULL || resp->buf == NULL) { ++ return -1; ++ } ++ if (mode == EVIDENCE) { ++ root = cJSON_Parse((const char *)resp->buf); ++ cJSON *obj_get = cJSON_GetObjectItemCaseSensitive(root, "evaluation_reports"); ++ if (obj_get == NULL) { ++ goto err; ++ } ++ if (NULL == (obj_get = cJSON_GetObjectItemCaseSensitive(obj_get, "eval_result"))) { ++ goto err; ++ } ++ if (cJSON_IsTrue(obj_get)) { ++ ret = 0; ++ } ++ } else { ++ root = cJSON_Parse((const char*)resp->buf); ++ obj_get = cJSON_GetObjectItemCaseSensitive(root, "claim"); ++ if (obj_get == NULL) { ++ goto err; ++ } ++ char *str = cJSON_GetStringValue(obj_get); ++ if (str == NULL) { ++ goto err; ++ } ++ cJSON* obj_parse = cJSON_Parse(str); ++ cJSON* obj_get = cJSON_GetObjectItemCaseSensitive(obj_parse, "evaluation_reports"); ++ if (obj_get == NULL) { ++ goto err; ++ } ++ if (NULL == (obj_get = cJSON_GetObjectItemCaseSensitive(obj_get, "eval_result"))) { ++ goto err; ++ } ++ if (cJSON_IsTrue(obj_get)) { ++ ret = 0; ++ } ++ } ++err: ++ if (root) { ++ cJSON_Delete(root); ++ } ++ if (obj_parse) { ++ cJSON_Delete(obj_parse); ++ } ++ return ret; ++} ++// extension like this:"{"challenge":"base64_url string","quote":"token or evidence"}" ++static int parse_challenge_from_extension(ra_tls_buf *challenge, ra_tls_buf *ext) ++{ ++ int ret = -1; ++ cJSON *json_root = NULL; ++ cJSON *obj; ++ char *str = NULL; ++ if (challenge == NULL || ext == NULL) { ++ return ret; ++ } ++ if (NULL == (json_root = cJSON_Parse((const char*)ext->buf))) { ++ goto err; ++ } ++ if (NULL == (obj = cJSON_GetObjectItemCaseSensitive(json_root, "challenge"))) { ++ goto err; ++ } ++ if (NULL == (str = cJSON_GetStringValue(obj))) { ++ goto err; ++ } ++ ra_tls_buf_init(challenge, strlen(str) + 1); ++ strcpy((char *)challenge->buf, str); ++ challenge->filled = strlen(str) + 1; ++ ret = 0; ++err: ++ if (json_root) { ++ cJSON_Delete(json_root); ++ } ++ return ret; ++} ++ ++static int parse_quote_from_extension(ra_tls_buf *quote, ra_tls_buf *ext) ++{ ++ int ret = -1; ++ cJSON *json_root = NULL; ++ cJSON *obj; ++ char *str = NULL; ++ if (quote == NULL || ext == NULL) { ++ return ret; ++ } ++ if (NULL == (json_root = cJSON_Parse((const char *)ext->buf))) { ++ goto err; ++ } ++ if (NULL == (obj = cJSON_GetObjectItemCaseSensitive(json_root, "quote"))) { ++ goto err; ++ } ++ if (NULL == (str = cJSON_GetStringValue(obj))) { ++ goto err; ++ } ++ ra_tls_buf_init(quote, strlen(str) + 1); ++ strcpy((char *)quote->buf, str); ++ quote->filled = strlen(str) + 1; ++ ret = 0; ++err: ++ if (json_root) { ++ cJSON_Delete(json_root); ++ } ++ return ret; ++} ++ ++static char *generate_ra_http_data(extension_type type, char **http_data, ra_tls_buf *quote, ra_tls_buf *challenge) ++{ ++ if (type == EVIDENCE) { ++ cJSON* obj = cJSON_CreateObject(); ++ cJSON_AddStringToObject(obj, "evidence", (const char *)quote->buf); ++ cJSON_AddStringToObject(obj, "challenge", (const char *)challenge->buf); ++ *http_data = cJSON_PrintUnformatted(obj); ++ cJSON_Minify(*http_data); ++ cJSON_Delete(obj); ++ } else { ++ cJSON* obj = cJSON_CreateObject(); ++ cJSON_AddStringToObject(obj, "token", (const char *)quote->buf); ++ *http_data = cJSON_PrintUnformatted(obj); ++ cJSON_Minify(*http_data); ++ cJSON_Delete(obj); ++ } ++ return *http_data; ++} ++ ++static int verify_extension_ra(extension_type type, ra_tls_buf *quote, ra_tls_buf *challenge) ++{ ++ int ret = -1; ++ int res = 0; ++ ra_tls_buf response = RA_TLS_BUF_INIT; ++ const char *source_name = NULL; ++ char *endpoint = NULL; ++ size_t endpoint_len = 0; ++ char *http_data = NULL; ++ ++ if (type == EVIDENCE) { ++ source_name = "evidence"; ++ } else if (type == TOKEN) { ++ source_name = "token"; ++ } else { ++ printf("unkonwn extension type\n"); ++ goto err; ++ } ++ endpoint_len = strlen(agent_addr) + strlen(source_name) + 1; ++ endpoint = malloc(endpoint_len); ++ if (endpoint == NULL) { ++ goto err; ++ } ++ strcpy(endpoint, agent_addr); ++ strcat(endpoint, source_name); ++ generate_ra_http_data(type, &http_data, quote, challenge); ++#ifdef DEBUG ++ printf("http request\n"); ++ printf("endpoint: %s\n", endpoint); ++ printf("http data: %s\n", http_data); ++#endif ++ res = http_request(endpoint, "POST", http_data, &response); ++ if (res < 0) { ++ goto err; ++ } ++#ifdef DEBUG ++ printf("response: %s\n", response.buf); ++#endif ++ // check as service response ++ if (expect_response_true(&response, type) != 0) { ++ printf("expect evaluation_reports.eval_result = true, but false or not exist\n"); ++ goto err; ++ } ++ ret = 0; ++err: ++ if (endpoint) { ++ free(endpoint); ++ } ++ if (http_data) { ++ cJSON_free(http_data); ++ } ++ ra_tls_buf_free(&response); ++ return ret; ++} ++ ++static int verify_extension(ra_tls_buf *ext, extension_type type) ++{ ++ int ret = -1; ++ int res; ++ ra_tls_buf challenge = RA_TLS_BUF_INIT; ++ ra_tls_buf quote = RA_TLS_BUF_INIT; ++ ++ if (agent_addr == NULL || ext == NULL) { ++ return ret; ++ } ++ res = parse_challenge_from_extension(&challenge, ext); ++ if (res < 0) { ++ goto err; ++ } ++ res = parse_quote_from_extension("e, ext); ++ if (res < 0) { ++ goto err; ++ } ++ res = verify_extension_ra(type, "e, &challenge); ++ if (res != 0) { ++ ret = -1; ++ } else { ++ ret = 0; ++ } ++err: ++ ra_tls_buf_free(&challenge); ++ ra_tls_buf_free("e); ++ return ret; ++} ++ ++// challenge :last 32 bytes in base64_url_decode(challenge) is the public key hash ++static int get_public_key_hash(ra_tls_buf *key_hash, ra_tls_buf *ext) ++{ ++ int ret = -1; ++ int res; ++ ra_tls_buf challenge_base64 = RA_TLS_BUF_INIT; ++ uint8_t* challenge = NULL; ++ size_t challenge_len = 0; ++ res = parse_challenge_from_extension(&challenge_base64, ext); ++ if (res < 0) { ++ goto err; ++ } ++ challenge = kpsecl_base64urldecode((const char*)challenge_base64.buf, challenge_base64.filled, &challenge_len); ++ if (challenge == NULL || challenge_len < HASH_OFFSET + HASH_LEN) { ++ goto err; ++ } ++ ra_tls_buf_init(key_hash, HASH_LEN); ++ memcpy(key_hash->buf, &challenge[HASH_OFFSET], HASH_LEN); ++ key_hash->filled = HASH_LEN; ++ ret = 0; ++err: ++ ra_tls_buf_free(&challenge_base64); ++ if (challenge) { ++ free(challenge); ++ } ++ return ret; ++} ++ ++static int check_public_key_hash(ra_tls_buf *key_hash, ra_tls_buf *ker_der) ++{ ++ int ret = -1; ++ ra_tls_buf cal_hash = RA_TLS_BUF_INIT; ++ ra_tls_buf_init(&cal_hash, HASH_LEN); ++ if (key_hash->filled != HASH_LEN) { ++ return -1; ++ } ++ if (0 != get_hash(&cal_hash, ker_der, SHA_256)) { ++ goto err; ++ } ++#ifdef DEBUG ++ printf("compare key hash\n"); ++ printf("expected: "); ++ for (size_t i = 0; i < key_hash->filled; i++) { ++ printf("%02X", key_hash->buf[i]); ++ } ++ printf("\n"); ++ printf("get hash of input: "); ++ for (size_t i = 0; i < cal_hash.filled; i++) { ++ printf("%02X", cal_hash.buf[i]); ++ } ++ printf("\n"); ++#endif ++ if (0 != memcmp(key_hash->buf, cal_hash.buf, cal_hash.filled)) { ++ printf("public key hash check Failed\n"); ++ goto err; ++ } ++ ret = 0; ++err: ++ ra_tls_buf_free(&cal_hash); ++ return ret; ++} ++ ++bool ra_tls_cert_extension_expired(ra_tls_buf *cert) ++{ ++ bool ret = true; ++ ra_tls_buf token = RA_TLS_BUF_INIT; ++ ra_tls_buf oid = RA_TLS_BUF_INIT; ++ ra_tls_buf claim = RA_TLS_BUF_INIT; ++ char *raw_claim = NULL; ++ size_t raw_claim_len = 0; ++ cJSON *root = NULL; ++ cJSON *obj_get = NULL; ++ size_t expired = 0; ++ if (cert == NULL || cert->filled == 0) { ++ goto err; ++ } ++ if (get_extension_from_certificate_der(&token, &oid, cert) < 0) { ++ goto err; ++ } ++ // check extension expired ++ if (parse_claim_from_token(&claim, &token) < 0) { ++ goto err; ++ } ++ raw_claim = (char*)kpsecl_base64urldecode((const char *)claim.buf, claim.filled, &raw_claim_len); ++ if (raw_claim == NULL) { ++ goto err; ++ } ++#ifdef DEBUG ++ printf("raw claim: %s", raw_claim); ++#endif ++ root = cJSON_Parse(raw_claim); ++ if (root == NULL) { ++ goto err; ++ } ++ obj_get = cJSON_GetObjectItemCaseSensitive(root, "exp"); ++ if (obj_get == NULL || !cJSON_IsNumber(obj_get)) { ++ goto err; ++ } ++ expired = cJSON_GetNumberValue(obj_get); ++ if ((size_t)time(NULL) + EXTENSION_EXPIRED_OFFSET_SECONDS >= expired) { ++ ret = true; ++ } else { ++ ret = false; ++ } ++err: ++ ra_tls_buf_free(&token); ++ ra_tls_buf_free(&oid); ++ ra_tls_buf_free(&claim); ++ if (raw_claim) { ++ free(raw_claim); ++ } ++ if (root) { ++ cJSON_Delete(root); ++ } ++ return ret; ++} ++ ++static int verify_certificate_extension(void *cert_ctx) ++{ ++ int res; ++ int ret = -1; ++ ra_tls_buf ext = RA_TLS_BUF_INIT; ++ ra_tls_buf key_hash = RA_TLS_BUF_INIT; ++ ra_tls_buf key_der = RA_TLS_BUF_INIT; ++ ra_tls_buf oid = RA_TLS_BUF_INIT; ++ extension_type type; ++ ++// below depend api declare in ra_tls_imp.h ++ res = get_extension_from_certificate_context(&ext, &oid, cert_ctx); ++ if (res < 0) { ++ printf("get extension from certificate failed\n"); ++ goto err; ++ } ++ if (strcmp((const char*)oid.buf, EVIDENCE_OID) == 0) { ++ type = EVIDENCE; ++ } else if (strcmp((const char*)oid.buf, TOKEN_OID) == 0) { ++ type = TOKEN; ++ } else { ++ goto err; ++ } ++ res = verify_extension(&ext, type); ++ if (res < 0) { ++ printf("extension verfiy failed\n"); ++ goto err; ++ } ++ res = get_public_key_from_certificate_context(&key_der, cert_ctx); ++ if (res < 0) { ++ printf("get public key failed\n"); ++ } ++ res = get_public_key_hash(&key_hash, &ext); ++ if (res < 0) { ++ printf("get public key hash failed\n"); ++ } ++ res = check_public_key_hash(&key_hash, &key_der); ++ if (res < 0) { ++ printf("public key hash check failed\n"); ++ goto err; ++ } ++ // success ++ ret = 0; ++err: ++ ra_tls_buf_free(&ext); ++ ra_tls_buf_free(&key_hash); ++ ra_tls_buf_free(&key_der); ++ ra_tls_buf_free(&oid); ++ return ret; ++} ++ ++#ifdef USE_OPENSSL ++// 0 failed 1 ok ++int ra_tls_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) ++{ ++ int res; ++ int ret = 1; ++ if (preverify_ok == 0) { ++ res = X509_STORE_CTX_get_error(x509_ctx); ++ if (res == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) { ++ printf("self-signed certificate\n"); ++ ret = 1; ++ return ret; ++ } ++ } ++ res = verify_certificate_extension(x509_ctx); ++ if (res < 0) { ++ ret = 0; ++ } ++ return ret; ++} ++#endif +\ No newline at end of file +diff --git a/component/ra_tls/ra_tls.h b/component/ra_tls/ra_tls.h +new file mode 100644 +index 00000000..a3dca269 +--- /dev/null ++++ b/component/ra_tls/ra_tls.h +@@ -0,0 +1,65 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++#ifndef RA_TLS_H_ ++#define RA_TLS_H_ ++ ++#ifndef __cplusplus ++#include ++#endif ++#if defined(USE_OPENSSL) ++#include ++#include ++#include ++#elif defined(USE_MBEDTLS) ++#include ++#include ++#include ++#endif ++#include "ra_tls_imp.h" ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++typedef struct { ++ // attestation agent listen address ++ char *aa_addr; ++ char *uuid; ++ ra_mode mode; ++}ra_cfg; ++ ++#define CHALLENGE_LEN 64 ++#define HASH_LEN 32 ++#define HASH_OFFSET 32 ++#define PUBLIC_KEY_HASH_PRINT_LEN (4 * HASH_LEN) ++#define EXTENSION_EXPIRED_OFFSET_SECONDS (5) ++ ++int ra_tls_generate_certificate(ra_tls_buf *cert, ra_tls_buf *private_key, ra_cfg *cfg_ra, key_size size); ++// set attestation agent address, addr is ip:port or domain:port ++int ra_tls_set_addr(char *addr); ++// cert is DER-encoded ++bool ra_tls_cert_extension_expired(ra_tls_buf *cert); ++ ++#if defined(USE_OPENSSL) ++// return 0 failed, 1 ok ++int ra_tls_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx); ++#elif defined(USE_MBEDTLS) ++// return 0 ok, or failed result ++int ra_tls_verify_callback(void *data, mbedtls_509_crt *crt, int depth, uint32_t *flasgs); ++#endif ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif +\ No newline at end of file +diff --git a/component/ra_tls/ra_tls_imp.h b/component/ra_tls/ra_tls_imp.h +new file mode 100644 +index 00000000..5d85ba98 +--- /dev/null ++++ b/component/ra_tls/ra_tls_imp.h +@@ -0,0 +1,97 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++#ifndef RA_TLS_IMP_H_ ++#define RA_TLS_IMP_H_ ++#include ++#include ++#if defined(USE_OPENSSL) ++#include ++#include ++#include ++#else ++ #error TLS library Must be specified ++#endif ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++// todo register oid ++#define EVIDENCE_OID "1.3.6.1.4.1.2011.2.8" ++#define TOKEN_OID "1.3.6.1.4.1.2011.2.10" ++ ++#define KEY_SIZE_MAX (3072) ++#define DEFAULT_CERT_LIFETIME_YEARS (1) ++ ++typedef struct { ++ uint8_t *buf; ++ size_t len; ++ size_t filled; ++} ra_tls_buf; ++ ++typedef enum key_size_t { ++ RSA_2048, ++ RSA_3072 ++} key_size; ++ ++typedef enum key_type_t { ++ KEY_PUBLIC, ++ KEY_PRIVATE ++} key_type; ++ ++typedef enum hash_type_t { ++ SHA_256, ++ SHA_512 ++} hash_type; ++ ++typedef enum ra_mode_t { ++ BACKGROUND, ++ PASSPORT ++} ra_mode; ++ ++typedef struct { ++ ra_tls_buf prv_key; ++ ra_tls_buf pub_key; ++ char *subject_name; ++ char *issuer_name; ++ char *not_before; // format:YYYYMMDDHHMMSSZ ++ char *not_after; // format:YYYYMMDDHHMMSSZ ++ const char *ext_oid; ++ ra_tls_buf ext; ++} cert_config; ++ ++#define RA_TLS_BUF_INIT {NULL, 0, 0} ++int ra_tls_buf_init(ra_tls_buf *buf, int len); ++void ra_tls_buf_free(ra_tls_buf *buf); ++ ++int get_hash(ra_tls_buf *hash, ra_tls_buf *input, hash_type type); ++int generate_key_pair_der(key_size key_len, ra_tls_buf *public_key, ra_tls_buf *private_key); ++// generate pem certificate,use evidence filled extension specified by oid ++int generate_certificate_with_extension(ra_tls_buf *cert, ra_tls_buf *evidence, ra_tls_buf *public_key, ++ ra_tls_buf *private_key, const char *oid); ++ ++#if defined(USE_OPENSSL) ++/* ++ get extension by oid (EVIDENCE_OID or TOKEN_OID) in certificate ++ cert_ctx associate with library, it may be a runtime context ++*/ ++int get_extension_from_certificate_context(ra_tls_buf *ext_buf, ra_tls_buf *oid, void *cert_ctx); ++int get_public_key_from_certificate_context(ra_tls_buf *key_der, void *cert_ctx); ++int get_extension_from_certificate_der(ra_tls_buf *ext_buf, ra_tls_buf *oid, ra_tls_buf *cert_der); ++#endif ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif +-- +2.46.0 + diff --git a/0091-fix-allow-bind-unbind-and-clear-resource-policy-when.patch b/0091-fix-allow-bind-unbind-and-clear-resource-policy-when.patch new file mode 100644 index 0000000000000000000000000000000000000000..4a197f1ca3856f1fdc0b4ad63deea2bf6c1a6d81 --- /dev/null +++ b/0091-fix-allow-bind-unbind-and-clear-resource-policy-when.patch @@ -0,0 +1,119 @@ +From 435f93ddb97be16f60fcd1ace909cafb418f642b Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Wed, 19 Feb 2025 10:59:20 +0800 +Subject: [PATCH 1/1] fix: allow bind, unbind and clear resource policy when + resource already exists + +This influence the interface behavior for: +resource policy bind, +resource policy unbind, +resource policy clear. +--- + .../src/resource/admin/simple.rs | 2 +- + .../src/resource/storage/mod.rs | 9 +++++++-- + .../src/resource/storage/simple.rs | 19 ++++++++++++------- + 3 files changed, 20 insertions(+), 10 deletions(-) + +diff --git a/service/attestation/attestation-types/src/resource/admin/simple.rs b/service/attestation/attestation-types/src/resource/admin/simple.rs +index 5967be9..bf2b5dd 100644 +--- a/service/attestation/attestation-types/src/resource/admin/simple.rs ++++ b/service/attestation/attestation-types/src/resource/admin/simple.rs +@@ -106,7 +106,7 @@ impl ResourceAdminInterface for SimpleResourceAdmin { + self.storage_engine + .lock() + .await +- .store(location, resource) ++ .store(location, resource, false) + .await + } + +diff --git a/service/attestation/attestation-types/src/resource/storage/mod.rs b/service/attestation/attestation-types/src/resource/storage/mod.rs +index fd7b0c7..3614769 100644 +--- a/service/attestation/attestation-types/src/resource/storage/mod.rs ++++ b/service/attestation/attestation-types/src/resource/storage/mod.rs +@@ -29,7 +29,12 @@ pub(crate) trait StorageOp: Send + Sync { + /// Traverse and collect resource list in particular vendor. + async fn list(&self, vendor: &str) -> Result>; + /// Create a new resource if it does not exist. If the resource already exists, error will be thrown. +- async fn store(&self, location: ResourceLocation, resource: Resource) -> Result<()>; ++ async fn store( ++ &self, ++ location: ResourceLocation, ++ resource: Resource, ++ force: bool, ++ ) -> Result<()>; + /// Override the content field in the resource, while keep other fields the same. + async fn modify(&self, location: ResourceLocation, content: String) -> Result<()>; + /// Delete the resource inside the storage. +@@ -51,7 +56,7 @@ pub(crate) trait PolicyOp: StorageOp + Send + Sync { + /// Get all policy from the resource. + async fn get_all_policies(&self, location: ResourceLocation) -> Result>; + /// Clear the original policy inside the resource. +- async fn clea_policies(&self, location: ResourceLocation) -> Result<()>; ++ async fn clear_policies(&self, location: ResourceLocation) -> Result<()>; + /// Delete the specific policy from the resource. + async fn unbind_policies( + &self, +diff --git a/service/attestation/attestation-types/src/resource/storage/simple.rs b/service/attestation/attestation-types/src/resource/storage/simple.rs +index b8fd536..d5f1bc5 100644 +--- a/service/attestation/attestation-types/src/resource/storage/simple.rs ++++ b/service/attestation/attestation-types/src/resource/storage/simple.rs +@@ -82,10 +82,15 @@ impl StorageOp for SimpleStorage { + Ok(ret) + } + +- async fn store(&self, location: ResourceLocation, resource: Resource) -> Result<()> { ++ async fn store( ++ &self, ++ location: ResourceLocation, ++ resource: Resource, ++ force: bool, ++ ) -> Result<()> { + let regularized = self.regular(&format!("{}", location))?; + +- if regularized.exists() { ++ if !force && regularized.exists() { + return Err(ResourceError::ResourceExist(location.to_string())); + } + +@@ -132,16 +137,16 @@ impl PolicyOp for SimpleStorage { + ) -> Result<()> { + let mut resource = self.get(location.clone()).await?; + resource.set_policy(policy); +- self.store(location, resource).await ++ self.store(location, resource, true).await + } + async fn get_all_policies(&self, location: ResourceLocation) -> Result> { + let resource = self.get(location).await?; + Ok(resource.get_policy()) + } +- async fn clea_policies(&self, location: ResourceLocation) -> Result<()> { ++ async fn clear_policies(&self, location: ResourceLocation) -> Result<()> { + let mut resource = self.get(location.clone()).await?; + resource.policy = vec![]; +- self.store(location, resource).await ++ self.store(location, resource, true).await + } + async fn unbind_policies( + &self, +@@ -155,7 +160,7 @@ impl PolicyOp for SimpleStorage { + resource.policy.remove(idx); + } + } +- self.store(location, resource).await ++ self.store(location, resource, true).await + } + async fn bind_policies( + &self, +@@ -166,7 +171,7 @@ impl PolicyOp for SimpleStorage { + for p in policy.iter() { + resource.policy.push(format!("{}", p)); + } +- self.store(location.clone(), resource).await ++ self.store(location.clone(), resource, true).await + } + } + +-- +2.46.0 + diff --git a/0092-fix-use-etc-as-the-base-directory-for-resource-stora.patch b/0092-fix-use-etc-as-the-base-directory-for-resource-stora.patch new file mode 100644 index 0000000000000000000000000000000000000000..867f63cf5beae2a14e89fd883d4224f91d01475d --- /dev/null +++ b/0092-fix-use-etc-as-the-base-directory-for-resource-stora.patch @@ -0,0 +1,39 @@ +From 7cd62c2fa0d264ea3a1898d7522cfc55f2b16d39 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Wed, 19 Feb 2025 16:44:44 +0800 +Subject: [PATCH 1/1] fix: use etc as the base directory for resource storage + +--- + .../attestation-types/src/resource/policy/opa/mod.rs | 2 +- + .../attestation-types/src/resource/storage/simple.rs | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/service/attestation/attestation-types/src/resource/policy/opa/mod.rs b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +index 0ec506a..ad159f5 100644 +--- a/service/attestation/attestation-types/src/resource/policy/opa/mod.rs ++++ b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +@@ -21,7 +21,7 @@ use async_trait::async_trait; + use std::path::PathBuf; + + pub(crate) const DEFAULT_RESOURCE_POLICY_DIR: &str = +- "/run/attestation/attestation-service/resource/policy/"; ++ "/etc/attestation/attestation-service/resource/policy/"; + pub(crate) const DEFAULT_RESOURCE_VIRTCCA_DEFAULT_POLICY: &str = "virtcca.rego"; + + pub(crate) struct OpenPolicyAgent { +diff --git a/service/attestation/attestation-types/src/resource/storage/simple.rs b/service/attestation/attestation-types/src/resource/storage/simple.rs +index d5f1bc5..dad24e0 100644 +--- a/service/attestation/attestation-types/src/resource/storage/simple.rs ++++ b/service/attestation/attestation-types/src/resource/storage/simple.rs +@@ -24,7 +24,7 @@ use super::PolicyOp; + use super::Resource; + use super::StorageEngine; + +-pub(crate) const STORAGE_BASE: &str = "/run/attestation/attestation-service/resource/storage/"; ++pub(crate) const STORAGE_BASE: &str = "/etc/attestation/attestation-service/resource/storage/"; + + pub(crate) struct SimpleStorage { + base: PathBuf, +-- +2.46.0 + diff --git a/0093-fix-raise-formatted-error-message-if-http-error-happ.patch b/0093-fix-raise-formatted-error-message-if-http-error-happ.patch new file mode 100644 index 0000000000000000000000000000000000000000..a345f3477160f05a99d20bf5411819ab42d0b63b --- /dev/null +++ b/0093-fix-raise-formatted-error-message-if-http-error-happ.patch @@ -0,0 +1,790 @@ +From a639aa650a7f0045474ec9c01e8dc9747cc3e3fe Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Thu, 27 Feb 2025 20:00:04 +0800 +Subject: [PATCH 1/3] fix: raise formatted error message if http error happens + +--- + .../attestation-client/src/client.rs | 2 +- + .../attestation-client/src/common.rs | 23 ++++ + .../attestation-client/src/error.rs | 22 ++-- + .../attestation-client/src/main.rs | 1 + + .../attestation-client/src/resource/client.rs | 120 ++++-------------- + .../attestation-client/src/resource/mod.rs | 90 ++++++++----- + .../src/resource_policy/client.rs | 107 ++++------------ + .../src/resource_policy/mod.rs | 75 +++++++---- + 8 files changed, 191 insertions(+), 249 deletions(-) + create mode 100644 service/attestation/attestation-client/src/common.rs + +diff --git a/service/attestation/attestation-client/src/client.rs b/service/attestation/attestation-client/src/client.rs +index 2c0f139..1200823 100644 +--- a/service/attestation/attestation-client/src/client.rs ++++ b/service/attestation/attestation-client/src/client.rs +@@ -30,7 +30,7 @@ pub(crate) struct AsClient { + impl AsClient { + pub(crate) fn new(cookie_store: bool, protocal: Protocal) -> Result { + let client = match &protocal { +- Protocal::Http { svr } => Client::builder().cookie_store(cookie_store).build()?, ++ Protocal::Http { svr: _ } => Client::builder().cookie_store(cookie_store).build()?, + }; + + Ok(Self { protocal, client }) +diff --git a/service/attestation/attestation-client/src/common.rs b/service/attestation/attestation-client/src/common.rs +new file mode 100644 +index 0000000..5f8ec98 +--- /dev/null ++++ b/service/attestation/attestation-client/src/common.rs +@@ -0,0 +1,23 @@ ++/* ++* Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++* secGear is licensed under the Mulan PSL v2. ++* You can use this software according to the terms and conditions of the Mulan PSL v2. ++* You may obtain a copy of Mulan PSL v2 at: ++* http://license.coscl.org.cn/MulanPSL2 ++* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++* PURPOSE. ++* See the Mulan PSL v2 for more details. ++*/ ++ ++use reqwest::Response; ++ ++pub(crate) async fn response_display(resp: Response) { ++ if !resp.status().is_success() { ++ println!("{:?}", resp); ++ } ++ let txt = resp.text().await.unwrap(); ++ if !txt.is_empty() { ++ println!("{}", txt); ++ } ++} +diff --git a/service/attestation/attestation-client/src/error.rs b/service/attestation/attestation-client/src/error.rs +index 2952de2..3245efa 100644 +--- a/service/attestation/attestation-client/src/error.rs ++++ b/service/attestation/attestation-client/src/error.rs +@@ -1,14 +1,14 @@ + /* +- * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. +- * secGear is licensed under the Mulan PSL v2. +- * You can use this software according to the terms and conditions of the Mulan PSL v2. +- * You may obtain a copy of Mulan PSL v2 at: +- * http://license.coscl.org.cn/MulanPSL2 +- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +- * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +- * PURPOSE. +- * See the Mulan PSL v2 for more details. +- */ ++* Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++* secGear is licensed under the Mulan PSL v2. ++* You can use this software according to the terms and conditions of the Mulan PSL v2. ++* You may obtain a copy of Mulan PSL v2 at: ++* http://license.coscl.org.cn/MulanPSL2 ++* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++* PURPOSE. ++* See the Mulan PSL v2 for more details. ++*/ + use thiserror::Error; + + pub type Result = std::result::Result; +@@ -18,6 +18,4 @@ pub type Result = std::result::Result; + pub enum ClientError { + #[error("reqwest error: {0}")] + ReqwestError(#[from] reqwest::Error), +- #[error("Http error {0}: {1}")] +- HttpError(String, http::status::StatusCode), + } +diff --git a/service/attestation/attestation-client/src/main.rs b/service/attestation/attestation-client/src/main.rs +index a779a71..512055e 100644 +--- a/service/attestation/attestation-client/src/main.rs ++++ b/service/attestation/attestation-client/src/main.rs +@@ -17,6 +17,7 @@ mod client; + mod error; + mod resource; + mod resource_policy; ++mod common; + + use crate::resource::ResourceArgs; + use crate::resource_policy::ResourcePolicyArgs; +diff --git a/service/attestation/attestation-client/src/resource/client.rs b/service/attestation/attestation-client/src/resource/client.rs +index e0dcb08..ecb8a7a 100644 +--- a/service/attestation/attestation-client/src/resource/client.rs ++++ b/service/attestation/attestation-client/src/resource/client.rs +@@ -13,12 +13,12 @@ + //! Implement web request for resource to attestation service + + use crate::client::AsClient; +-use crate::error::{ClientError, Result}; ++use crate::error::Result; + use attestation_types::{ + resource::ResourceLocation, + service::{GetResourceOp, SetResourceOp, SetResourceRequest}, + }; +-use reqwest::Client; ++use reqwest::{Client, Response}; + + pub(crate) struct ResourceClient { + client: AsClient, +@@ -37,27 +37,18 @@ impl ResourceClient { + self.client.client() + } + +- pub(crate) async fn vendor_get_resource(&self, vendor: &str) -> Result> { ++ pub(crate) async fn vendor_get_resource(&self, vendor: &str) -> Result { + let payload = GetResourceOp::VendorGet { + vendor: vendor.to_string(), + }; + +- let res = self ++ Ok(self + .client() + .get(self.endpoint()) + .header("Content-Type", "application/json") + .json(&payload) + .send() +- .await?; +- let status = res.status(); +- if status.is_success() { +- Ok(res.json().await?) +- } else { +- Err(ClientError::HttpError( +- format!("failed to get resource: {}", res.text().await?), +- status, +- )) +- } ++ .await?) + } + + pub(crate) async fn vendor_add_resource( +@@ -66,7 +57,7 @@ impl ResourceClient { + path: &str, + content: &str, + policy: &Vec, +- ) -> Result { ++ ) -> Result { + let op = SetResourceOp::Add { + content: content.to_string(), + policy: policy.clone(), +@@ -75,46 +66,32 @@ impl ResourceClient { + op, + resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), + }; +- let res = self ++ Ok(self + .client() + .post(self.endpoint()) + .header("Content-Type", "application/json") + .json(&payload) + .send() +- .await?; +- let status = res.status(); +- if status.is_success() { +- Ok(res.text().await?) +- } else { +- Err(ClientError::HttpError( +- format!("failed to add resource: {}", res.text().await?), +- status, +- )) +- } ++ .await?) + } + +- pub(crate) async fn vendor_delete_resource(&self, vendor: &str, path: &str) -> Result { ++ pub(crate) async fn vendor_delete_resource( ++ &self, ++ vendor: &str, ++ path: &str, ++ ) -> Result { + let op = SetResourceOp::Delete; + let payload = SetResourceRequest { + op, + resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), + }; +- let res = self ++ Ok(self + .client() + .post(self.endpoint()) + .header("Content-Type", "application/json") + .json(&payload) + .send() +- .await?; +- let status = res.status(); +- if status.is_success() { +- Ok(res.text().await?) +- } else { +- Err(ClientError::HttpError( +- format!("failed to delete resource: {}", res.text().await?), +- status, +- )) +- } ++ .await?) + } + + pub(crate) async fn vendor_modify_resource( +@@ -122,7 +99,7 @@ impl ResourceClient { + vendor: &str, + path: &str, + content: &str, +- ) -> Result { ++ ) -> Result { + let op = SetResourceOp::Modify { + content: content.to_string(), + }; +@@ -130,22 +107,13 @@ impl ResourceClient { + op, + resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), + }; +- let res = self ++ Ok(self + .client() + .post(self.endpoint()) + .header("Content-Type", "application/json") + .json(&payload) + .send() +- .await?; +- let status = res.status(); +- if status.is_success() { +- Ok(res.text().await?) +- } else { +- Err(ClientError::HttpError( +- format!("failed to modify resource: {}", res.text().await?), +- status, +- )) +- } ++ .await?) + } + + pub(crate) async fn vendor_bind_resource( +@@ -153,7 +121,7 @@ impl ResourceClient { + vendor: &str, + path: &str, + policy: &Vec, +- ) -> Result { ++ ) -> Result { + let op = SetResourceOp::Bind { + policy: policy.clone(), + }; +@@ -161,22 +129,13 @@ impl ResourceClient { + op, + resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), + }; +- let res = self ++ Ok(self + .client() + .post(self.endpoint()) + .header("Content-Type", "application/json") + .json(&payload) + .send() +- .await?; +- let status = res.status(); +- if status.is_success() { +- Ok(res.text().await?) +- } else { +- Err(ClientError::HttpError( +- format!("failed to bind resource: {}", res.text().await?), +- status, +- )) +- } ++ .await?) + } + + pub(crate) async fn vendor_unbind_resource( +@@ -184,7 +143,7 @@ impl ResourceClient { + vendor: &str, + path: &str, + policy: &Vec, +- ) -> Result { ++ ) -> Result { + let op = SetResourceOp::Unbind { + policy: policy.clone(), + }; +@@ -192,43 +151,12 @@ impl ResourceClient { + op, + resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), + }; +- let res = self ++ Ok(self + .client() + .post(self.endpoint()) + .header("Content-Type", "application/json") + .json(&payload) + .send() +- .await?; +- let status = res.status(); +- if status.is_success() { +- Ok(res.text().await?) +- } else { +- Err(ClientError::HttpError( +- format!("failed to unbind resource: {}", res.text().await?), +- status, +- )) +- } ++ .await?) + } + } +- +-// async fn get_challenge() { +-// let challenge_endpoint = format!("{}/challenge", self.config.svr_url); +-// let client = self.create_client(self.config.protocal.clone(), true)?; +-// let res = client +-// .get(challenge_endpoint) +-// .header("Content-Type", "application/json") +-// .header("content-length", 0) +-// .send() +-// .await?; +-// let challenge = match res.status() { +-// reqwest::StatusCode::OK => { +-// let respone: String = res.json().await.unwrap(); +-// log::debug!("get challenge success, AS Response: {:?}", respone); +-// respone +-// } +-// status => { +-// log::error!("get challenge Failed, AS Response: {:?}", status); +-// bail!("get challenge Failed") +-// } +-// }; +-// } +diff --git a/service/attestation/attestation-client/src/resource/mod.rs b/service/attestation/attestation-client/src/resource/mod.rs +index d198ef4..35c1c5a 100644 +--- a/service/attestation/attestation-client/src/resource/mod.rs ++++ b/service/attestation/attestation-client/src/resource/mod.rs +@@ -17,6 +17,7 @@ pub(crate) mod client; + + use self::client::ResourceClient; + use crate::client::AsClient; ++use crate::common::response_display; + use clap::{Args, Subcommand}; + + #[derive(Debug, Args)] +@@ -61,69 +62,88 @@ pub(crate) enum ResourceCommand { + + impl ResourceArgs { + pub(crate) fn process(&self, base_client: AsClient) { +- self.command.dispatch(base_client); ++ let runtime = tokio::runtime::Runtime::new().unwrap(); ++ runtime.block_on(self.command.dispatch(base_client)); + } + } + + impl ResourceCommand { +- fn dispatch(&self, base_client: AsClient) { ++ async fn dispatch(&self, base_client: AsClient) { + let client = ResourceClient::new(base_client); +- let runtime = tokio::runtime::Runtime::new().unwrap(); + + match self { +- ResourceCommand::Get { vendor } => { +- let ret = runtime +- .block_on(client.vendor_get_resource(vendor)) +- .unwrap(); +- println!("{:?}", ret); +- } ++ ResourceCommand::Get { vendor } => match client.vendor_get_resource(vendor).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(e) => { ++ println!("{:?}", e); ++ } ++ }, + ResourceCommand::Add { + vendor, + path, + content, + policy, + } => { +- let ret = runtime +- .block_on(client.vendor_add_resource(vendor, path, content, policy)) +- .unwrap(); +- println!("{:?}", ret); ++ match client ++ .vendor_add_resource(vendor, path, content, policy) ++ .await ++ { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(e) => { ++ println!("{:?}", e); ++ } ++ } + } + ResourceCommand::Delete { vendor, path } => { +- let ret = runtime +- .block_on(client.vendor_delete_resource(vendor, path)) +- .unwrap(); +- println!("{:?}", ret); ++ match client.vendor_delete_resource(vendor, path).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(e) => { ++ println!("{:?}", e); ++ } ++ } + } + ResourceCommand::Modify { + vendor, + path, + content, +- } => { +- let ret = runtime +- .block_on(client.vendor_modify_resource(vendor, path, content)) +- .unwrap(); +- println!("{:?}", ret); +- } ++ } => match client.vendor_modify_resource(vendor, path, content).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(rsp) => { ++ println!("{:?}", rsp); ++ } ++ }, + ResourceCommand::BindPolicy { + vendor, + path, + policy, +- } => { +- let ret = runtime +- .block_on(client.vendor_bind_resource(vendor, path, policy)) +- .unwrap(); +- println!("{:?}", ret); +- } ++ } => match client.vendor_bind_resource(vendor, path, policy).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(rsp) => { ++ println!("{:?}", rsp); ++ } ++ }, + ResourceCommand::UnbindPolicy { + vendor, + path, + policy, +- } => { +- let ret = runtime +- .block_on(client.vendor_unbind_resource(vendor, path, policy)) +- .unwrap(); +- println!("{:?}", ret); +- } ++ } => match client.vendor_unbind_resource(vendor, path, policy).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(rsp) => { ++ println!("{:?}", rsp); ++ } ++ }, + } + } + } +diff --git a/service/attestation/attestation-client/src/resource_policy/client.rs b/service/attestation/attestation-client/src/resource_policy/client.rs +index 582a6bd..08b75c1 100644 +--- a/service/attestation/attestation-client/src/resource_policy/client.rs ++++ b/service/attestation/attestation-client/src/resource_policy/client.rs +@@ -14,12 +14,12 @@ + //! + + use crate::client::AsClient; +-use crate::error::{ClientError, Result}; ++use crate::error::Result; + use attestation_types::{ + resource::policy::PolicyLocation, + service::{GetResourcePolicyOp, SetResourcePolicyOp}, + }; +-use reqwest::Client; ++use reqwest::{Client, Response}; + + pub(crate) struct ResourcePolicyClient { + client: AsClient, +@@ -38,7 +38,7 @@ impl ResourcePolicyClient { + self.client.client() + } + +- pub(crate) async fn vendor_get_one(&self, vendor: &str, id: &str) -> Result { ++ pub(crate) async fn vendor_get_one(&self, vendor: &str, id: &str) -> Result { + let payload = GetResourcePolicyOp::GetOne { + policy: PolicyLocation { + vendor: Some(vendor.to_string()), +@@ -46,70 +46,44 @@ impl ResourcePolicyClient { + }, + }; + +- let res = self ++ Ok(self + .client() + .get(self.endpoint()) + .header("Content-Type", "application/json") + .json(&payload) + .send() +- .await?; +- let status = res.status(); +- if status.is_success() { +- Ok(res.text().await?) +- } else { +- Err(ClientError::HttpError( +- format!("failed to get resource policy: {}", res.text().await?), +- status, +- )) +- } ++ .await?) + } +- pub(crate) async fn vendor_get_all(&self) -> Result> { ++ pub(crate) async fn vendor_get_all(&self) -> Result { + let payload = GetResourcePolicyOp::GetAll; + +- let res = self ++ Ok(self + .client() + .get(self.endpoint()) + .header("Content-Type", "application/json") + .json(&payload) + .send() +- .await?; +- let status = res.status(); +- if status.is_success() { +- Ok(res.json().await?) +- } else { +- Err(ClientError::HttpError( +- format!("failed to get all resource policy: {}", res.text().await?), +- status, +- )) +- } ++ .await?) + } +- pub(crate) async fn vendor_get_all_in_vendor(&self, vendor: &str) -> Result> { ++ pub(crate) async fn vendor_get_all_in_vendor(&self, vendor: &str) -> Result { + let payload = GetResourcePolicyOp::GetAllInVendor { + vendor: vendor.to_string(), + }; + +- let res = self ++ Ok(self + .client() + .get(self.endpoint()) + .header("Content-Type", "application/json") + .json(&payload) + .send() +- .await?; +- let status = res.status(); +- if status.is_success() { +- Ok(res.json().await?) +- } else { +- Err(ClientError::HttpError( +- format!( +- "failed to get all resource policy in vendor {}: {}", +- vendor, +- res.text().await? +- ), +- status, +- )) +- } ++ .await?) + } +- pub(crate) async fn vendor_add(&self, vendor: &str, id: &str, content: &str) -> Result { ++ pub(crate) async fn vendor_add( ++ &self, ++ vendor: &str, ++ id: &str, ++ content: &str, ++ ) -> Result { + let payload = SetResourcePolicyOp::Add { + policy: PolicyLocation { + vendor: Some(vendor.to_string()), +@@ -118,24 +92,15 @@ impl ResourcePolicyClient { + content: content.to_string(), + }; + +- let res = self ++ Ok(self + .client() + .post(self.endpoint()) + .header("Content-Type", "application/json") + .json(&payload) + .send() +- .await?; +- let status = res.status(); +- if status.is_success() { +- Ok(res.text().await?) +- } else { +- Err(ClientError::HttpError( +- format!("failed to add resource policy: {}", res.text().await?), +- status, +- )) +- } ++ .await?) + } +- pub(crate) async fn vendor_delete(&self, vendor: &str, id: &str) -> Result { ++ pub(crate) async fn vendor_delete(&self, vendor: &str, id: &str) -> Result { + let payload = SetResourcePolicyOp::Delete { + policy: PolicyLocation { + vendor: Some(vendor.to_string()), +@@ -143,48 +108,26 @@ impl ResourcePolicyClient { + }, + }; + +- let res = self ++ Ok(self + .client() + .post(self.endpoint()) + .header("Content-Type", "application/json") + .json(&payload) + .send() +- .await?; +- let status = res.status(); +- if status.is_success() { +- Ok(res.text().await?) +- } else { +- Err(ClientError::HttpError( +- format!("failed to delete resource policy: {}", res.text().await?), +- status, +- )) +- } ++ .await?) + } + +- pub(crate) async fn vendor_clear_all(&self, vendor: &str) -> Result { ++ pub(crate) async fn vendor_clear_all(&self, vendor: &str) -> Result { + let payload = SetResourcePolicyOp::ClearAll { + vendor: vendor.to_string(), + }; + +- let res = self ++ Ok(self + .client() + .post(self.endpoint()) + .header("Content-Type", "application/json") + .json(&payload) + .send() +- .await?; +- let status = res.status(); +- if status.is_success() { +- Ok(res.text().await?) +- } else { +- Err(ClientError::HttpError( +- format!( +- "failed to clear resource policy in vendor {}: {}", +- vendor, +- res.text().await? +- ), +- status, +- )) +- } ++ .await?) + } + } +diff --git a/service/attestation/attestation-client/src/resource_policy/mod.rs b/service/attestation/attestation-client/src/resource_policy/mod.rs +index 4879412..d8afb6b 100644 +--- a/service/attestation/attestation-client/src/resource_policy/mod.rs ++++ b/service/attestation/attestation-client/src/resource_policy/mod.rs +@@ -17,6 +17,7 @@ pub(crate) mod client; + + use self::client::ResourcePolicyClient; + use crate::client::AsClient; ++use crate::common::response_display; + use clap::{Args, Subcommand}; + + #[derive(Debug, Args)] +@@ -53,47 +54,75 @@ pub(crate) enum ResourcePolicyCommand { + + impl ResourcePolicyArgs { + pub(crate) fn process(&self, base_client: AsClient) { +- self.command.dispatch(base_client); ++ let runtime = tokio::runtime::Runtime::new().unwrap(); ++ runtime.block_on(self.command.dispatch(base_client)); + } + } + + impl ResourcePolicyCommand { +- fn dispatch(&self, base_client: AsClient) { ++ async fn dispatch(&self, base_client: AsClient) { + let client = ResourcePolicyClient::new(base_client); +- let runtime = tokio::runtime::Runtime::new().unwrap(); + + match self { + ResourcePolicyCommand::GetOne { vendor, id } => { +- let ret = runtime.block_on(client.vendor_get_one(vendor, id)).unwrap(); +- println!("{}", ret); +- } +- ResourcePolicyCommand::GetAll => { +- let ret = runtime.block_on(client.vendor_get_all()).unwrap(); +- println!("{}", serde_json::json!(ret).to_string()); ++ match client.vendor_get_one(vendor, id).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(e) => { ++ println!("{:?}", e); ++ } ++ } + } ++ ResourcePolicyCommand::GetAll => match client.vendor_get_all().await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(rsp) => { ++ println!("{:?}", rsp); ++ } ++ }, + ResourcePolicyCommand::GetAllInVendor { vendor } => { +- let ret = runtime +- .block_on(client.vendor_get_all_in_vendor(vendor)) +- .unwrap(); +- println!("{}", serde_json::json!(ret).to_string()); ++ match client.vendor_get_all_in_vendor(vendor).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(e) => { ++ println!("{:?}", e); ++ } ++ } + } + ResourcePolicyCommand::Add { + vendor, + id, + content, +- } => { +- let ret = runtime +- .block_on(client.vendor_add(vendor, id, content)) +- .unwrap(); +- println!("{}", ret); +- } ++ } => match client.vendor_add(vendor, id, content).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(rsp) => { ++ println!("{:?}", rsp); ++ } ++ }, + ResourcePolicyCommand::Delete { vendor, id } => { +- let ret = runtime.block_on(client.vendor_delete(vendor, id)).unwrap(); +- println!("{}", ret); ++ match client.vendor_delete(vendor, id).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(e) => { ++ println!("{:?}", e); ++ } ++ } + } + ResourcePolicyCommand::ClearAll { vendor } => { +- let ret = runtime.block_on(client.vendor_clear_all(vendor)).unwrap(); +- println!("{}", ret); ++ match client.vendor_clear_all(vendor).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(e) => { ++ println!("{:?}", e); ++ } ++ } + } + } + } +-- +2.46.0 + diff --git a/0094-fix-use-default-vendor-if-it-is-not-set-or-is-empty.patch b/0094-fix-use-default-vendor-if-it-is-not-set-or-is-empty.patch new file mode 100644 index 0000000000000000000000000000000000000000..0f21a995ea2eec38cdd30ea7640b5c8670fdbfe2 --- /dev/null +++ b/0094-fix-use-default-vendor-if-it-is-not-set-or-is-empty.patch @@ -0,0 +1,42 @@ +From 28d4f96fa92f342a32b4f7e145db964291a111a4 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Mon, 3 Mar 2025 21:23:43 +0800 +Subject: [PATCH 2/3] fix: use default vendor if it is not set or is empty + +--- + .../attestation-types/src/resource/mod.rs | 19 +++++++++++-------- + 1 file changed, 11 insertions(+), 8 deletions(-) + +diff --git a/service/attestation/attestation-types/src/resource/mod.rs b/service/attestation/attestation-types/src/resource/mod.rs +index 037c086..f5c7fa8 100644 +--- a/service/attestation/attestation-types/src/resource/mod.rs ++++ b/service/attestation/attestation-types/src/resource/mod.rs +@@ -50,14 +50,17 @@ impl std::convert::TryFrom for PathBuf { + + impl Display for ResourceLocation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +- write!( +- f, +- "{}/{}", +- self.vendor +- .clone() +- .unwrap_or(DEFAULT_VENDOR_BASE.to_string()), +- self.path, +- ) ++ let vendor = self ++ .vendor ++ .clone() ++ .unwrap_or(DEFAULT_VENDOR_BASE.to_string()); ++ let v = if vendor.is_empty() { ++ DEFAULT_VENDOR_BASE.to_string() ++ } else { ++ vendor ++ }; ++ ++ write!(f, "{}/{}", v, self.path,) + } + } + +-- +2.46.0 + diff --git a/0095-fix-abort-binding-policies-if-any-policy-location-is.patch b/0095-fix-abort-binding-policies-if-any-policy-location-is.patch new file mode 100644 index 0000000000000000000000000000000000000000..5494eda97f211228aecae52716e01a59b9831ea8 --- /dev/null +++ b/0095-fix-abort-binding-policies-if-any-policy-location-is.patch @@ -0,0 +1,40 @@ +From 6aa683bcbb5a04a79d2784bac2edc3cc1ad0e1b5 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Mon, 3 Mar 2025 21:46:14 +0800 +Subject: [PATCH 3/3] fix: abort binding policies if any policy location is + illegal + +--- + .../src/resource/admin/simple.rs | 15 +++++++-------- + 1 file changed, 7 insertions(+), 8 deletions(-) + +diff --git a/service/attestation/attestation-types/src/resource/admin/simple.rs b/service/attestation/attestation-types/src/resource/admin/simple.rs +index bf2b5dd..641d903 100644 +--- a/service/attestation/attestation-types/src/resource/admin/simple.rs ++++ b/service/attestation/attestation-types/src/resource/admin/simple.rs +@@ -118,15 +118,14 @@ impl ResourceAdminInterface for SimpleResourceAdmin { + async fn bind_policy(&self, location: ResourceLocation, policy: Vec) -> Result<()> { + let mut legal_policy: Vec = vec![]; + for p in policy.iter() { +- if let Ok(p) = p.parse::() { +- if !location.check_policy_legal(&p) { +- return Err(ResourceError::UnmatchedPolicyResource( +- location.to_string(), +- p.to_string(), +- )); +- } +- legal_policy.push(p); ++ let p = p.parse::()?; ++ if !location.check_policy_legal(&p) { ++ return Err(ResourceError::UnmatchedPolicyResource( ++ location.to_string(), ++ p.to_string(), ++ )); + } ++ legal_policy.push(p); + } + self.storage_engine + .lock() +-- +2.46.0 + diff --git a/0096-fix-use-default-vendor-for-resource-policy-if-it-is-.patch b/0096-fix-use-default-vendor-for-resource-policy-if-it-is-.patch new file mode 100644 index 0000000000000000000000000000000000000000..b5a4dab76d5c590cb3a598a81cd0d8fb59232920 --- /dev/null +++ b/0096-fix-use-default-vendor-for-resource-policy-if-it-is-.patch @@ -0,0 +1,44 @@ +From 59f5903023a97bacae25abf2e097156bbd9a3225 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Mon, 3 Mar 2025 21:23:43 +0800 +Subject: [PATCH 1/3] fix: use default vendor for resource policy if it is not + set or is empty + +--- + .../src/resource/policy/mod.rs | 20 +++++++++++-------- + 1 file changed, 12 insertions(+), 8 deletions(-) + +diff --git a/service/attestation/attestation-types/src/resource/policy/mod.rs b/service/attestation/attestation-types/src/resource/policy/mod.rs +index d7ae01d..6ad05dd 100644 +--- a/service/attestation/attestation-types/src/resource/policy/mod.rs ++++ b/service/attestation/attestation-types/src/resource/policy/mod.rs +@@ -64,14 +64,18 @@ impl std::convert::TryFrom<&PolicyLocation> for PathBuf { + + impl Display for PolicyLocation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +- write!( +- f, +- "{}/{}", +- self.vendor +- .clone() +- .unwrap_or(DEFAULT_VENDOR_BASE.to_string()), +- self.id, +- ) ++ let vendor = self ++ .vendor ++ .clone() ++ .unwrap_or(DEFAULT_VENDOR_BASE.to_string()); ++ ++ let v = if vendor.is_empty() { ++ DEFAULT_VENDOR_BASE.to_string() ++ } else { ++ vendor ++ }; ++ ++ write!(f, "{}/{}", v, self.id,) + } + } + +-- +2.46.0 + diff --git a/0097-fix-fix-incorrect-error-message-when-modifying-a-non.patch b/0097-fix-fix-incorrect-error-message-when-modifying-a-non.patch new file mode 100644 index 0000000000000000000000000000000000000000..29a2b478610bc5a238cf5126934d9b66f20251cd --- /dev/null +++ b/0097-fix-fix-incorrect-error-message-when-modifying-a-non.patch @@ -0,0 +1,81 @@ +From 5d920a9902e66565c92e7a62b025fdceb5a1647d Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Tue, 4 Mar 2025 10:36:29 +0800 +Subject: [PATCH 2/3] fix: fix incorrect error message when modifying a + non-existing resource + +--- + .../service/src/restapi/resource/storage.rs | 4 +--- + service/attestation/attestation-types/src/resource/error.rs | 6 +++--- + service/attestation/attestation-types/src/resource/mod.rs | 2 +- + .../attestation-types/src/resource/storage/simple.rs | 2 +- + 4 files changed, 6 insertions(+), 8 deletions(-) + +diff --git a/service/attestation/attestation-service/service/src/restapi/resource/storage.rs b/service/attestation/attestation-service/service/src/restapi/resource/storage.rs +index 7b90cda..18fcfab 100644 +--- a/service/attestation/attestation-service/service/src/restapi/resource/storage.rs ++++ b/service/attestation/attestation-service/service/src/restapi/resource/storage.rs +@@ -108,9 +108,7 @@ async fn tee_get_resource( + } + Err(e) => { + log::debug!("{}", e); +- Err(result::AsError::Resource( +- attestation_types::resource::error::ResourceError::LoadPolicy(e), +- )) ++ Err(result::AsError::Other(e)) + } + } + } +diff --git a/service/attestation/attestation-types/src/resource/error.rs b/service/attestation/attestation-types/src/resource/error.rs +index 296aae8..8061730 100644 +--- a/service/attestation/attestation-types/src/resource/error.rs ++++ b/service/attestation/attestation-types/src/resource/error.rs +@@ -20,8 +20,8 @@ pub enum ResourceError { + NotImplemented, + #[error("Policy is missing.")] + PolicyMissing, +- #[error("Failed to load policy: {0}")] +- LoadPolicy(#[from] anyhow::Error), ++ #[error("{0}")] ++ Other(#[from] anyhow::Error), + #[error("Resource error: {0}")] + ResourceError(#[from] std::io::Error), + #[error("Illegal resource path: {0}")] +@@ -35,7 +35,7 @@ pub enum ResourceError { + #[error("Unmatched vendor between resource {0} and policy {1}")] + UnmatchedPolicyResource(String, String), + #[error("Convert error: {0}")] +- IoError(#[from] core::convert::Infallible), ++ ConvertError(#[from] core::convert::Infallible), + #[error("Strip Prefix fail: {0}")] + StripPrefix(#[from] StripPrefixError), + #[error("Illegal policy suffix: {0}")] +diff --git a/service/attestation/attestation-types/src/resource/mod.rs b/service/attestation/attestation-types/src/resource/mod.rs +index f5c7fa8..66007d6 100644 +--- a/service/attestation/attestation-types/src/resource/mod.rs ++++ b/service/attestation/attestation-types/src/resource/mod.rs +@@ -136,7 +136,7 @@ impl Resource { + pub(crate) async fn read_from_file(path: PathBuf) -> Result { + let content = tokio::fs::read(path) + .await +- .context("failed to add resource")?; ++ .context("failed to read resource")?; + Ok(serde_json::from_str( + &String::from_utf8(content).context("from utf8 error")?, + )?) +diff --git a/service/attestation/attestation-types/src/resource/storage/simple.rs b/service/attestation/attestation-types/src/resource/storage/simple.rs +index dad24e0..7ac9fdf 100644 +--- a/service/attestation/attestation-types/src/resource/storage/simple.rs ++++ b/service/attestation/attestation-types/src/resource/storage/simple.rs +@@ -105,7 +105,7 @@ impl StorageOp for SimpleStorage { + } + tokio::fs::write(regularized, serde_json::to_string(&resource)?) + .await +- .context("failed to add resource")?; ++ .context("failed to store resource")?; + Ok(()) + } + +-- +2.46.0 + diff --git a/0098-fix-avoid-clear-resource-policies-if-illegal-vendor-.patch b/0098-fix-avoid-clear-resource-policies-if-illegal-vendor-.patch new file mode 100644 index 0000000000000000000000000000000000000000..18255f93bcc22d9ad0ad2430511061ff8faa53ea --- /dev/null +++ b/0098-fix-avoid-clear-resource-policies-if-illegal-vendor-.patch @@ -0,0 +1,106 @@ +From dbe45fa419ff70f3f0077efd6359a1cc253f2bfc Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Tue, 4 Mar 2025 15:27:24 +0800 +Subject: [PATCH 3/3] fix: avoid clear resource policies if illegal vendor is + given + +--- + .../attestation-types/src/resource/error.rs | 2 ++ + .../src/resource/policy/opa/mod.rs | 33 +++++++++++++++---- + 2 files changed, 29 insertions(+), 6 deletions(-) + +diff --git a/service/attestation/attestation-types/src/resource/error.rs b/service/attestation/attestation-types/src/resource/error.rs +index 8061730..1ce2cbe 100644 +--- a/service/attestation/attestation-types/src/resource/error.rs ++++ b/service/attestation/attestation-types/src/resource/error.rs +@@ -26,6 +26,8 @@ pub enum ResourceError { + ResourceError(#[from] std::io::Error), + #[error("Illegal resource path: {0}")] + IllegalResource(String), ++ #[error("Illegal vendor: {0}")] ++ IllegalVendor(String), + #[error("Invalid resource content: {0}")] + ResourceFromUtf8(#[from] std::string::FromUtf8Error), + #[error("Serde deserialize failure: {0}")] +diff --git a/service/attestation/attestation-types/src/resource/policy/opa/mod.rs b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +index ad159f5..8e2486a 100644 +--- a/service/attestation/attestation-types/src/resource/policy/opa/mod.rs ++++ b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +@@ -33,6 +33,27 @@ impl OpenPolicyAgent { + OpenPolicyAgent { base } + } + ++ pub(crate) fn regular(&self, vendor: &str) -> Result { ++ if !Self::check_vendor_legal(vendor) { ++ return Err(ResourceError::IllegalVendor(vendor.to_string())); ++ } ++ ++ let v = if vendor.is_empty() { ++ DEFAULT_VENDOR_BASE ++ } else { ++ vendor ++ }; ++ ++ Ok(self.base.join(v)) ++ } ++ ++ pub(crate) fn check_vendor_legal(vendor: &str) -> bool { ++ if vendor.contains('.') { ++ return false; ++ } ++ true ++ } ++ + pub fn default() -> Self { + Self::new(PathBuf::from(DEFAULT_RESOURCE_POLICY_DIR)) + } +@@ -136,13 +157,13 @@ impl PolicyEngine for OpenPolicyAgent { + } + + async fn get_policy(&self, path: PolicyLocation) -> Result { +- let p = self.base.join(format!("{}", path)); ++ let p = self.regular(&format!("{}", path))?; + let raw = tokio::fs::read(p).await?; + Ok(String::from_utf8(raw)?) + } + + async fn add_policy(&self, path: PolicyLocation, policy: &str) -> Result<()> { +- let p = self.base.join(format!("{}", path)); ++ let p = self.regular(&format!("{}", path))?; + if let Some(parent) = p.parent() { + if let Err(e) = tokio::fs::create_dir_all(parent).await { + log::warn!( +@@ -157,7 +178,7 @@ impl PolicyEngine for OpenPolicyAgent { + } + + async fn delete_policy(&self, path: PolicyLocation) -> Result<()> { +- let p = self.base.join(format!("{}", path)); ++ let p = self.regular(&format!("{}", path))?; + tokio::fs::remove_file(p).await?; + Ok(()) + } +@@ -200,7 +221,7 @@ impl PolicyEngine for OpenPolicyAgent { + } + + async fn get_all_policy_in_vendor(&self, vendor: &str) -> Result> { +- let vendor_dir = self.base.join(&vendor); ++ let vendor_dir = self.regular(vendor)?; + let mut dir = tokio::fs::read_dir(vendor_dir).await?; + let mut ret: Vec = vec![]; + while let Some(d) = dir.next_entry().await? { +@@ -264,10 +285,10 @@ impl PolicyEngine for OpenPolicyAgent { + } + + async fn clear_all_policy_in_vendor(&self, vendor: &str) -> Result<()> { +- let vendor_dir = self.base.join(&vendor); ++ let vendor_dir = self.regular(vendor)?; + let md = tokio::fs::metadata(&vendor_dir) + .await +- .context("invalid vendor")?; ++ .context("fetching metadata failed")?; + if md.is_dir() { + tokio::fs::remove_dir_all(vendor_dir).await?; + } +-- +2.46.0 + diff --git a/0099-fix-permit-dots-in-the-resource-policy-id.patch b/0099-fix-permit-dots-in-the-resource-policy-id.patch new file mode 100644 index 0000000000000000000000000000000000000000..621a672f48ed729cdeea5a218d31ba353ed4e8db --- /dev/null +++ b/0099-fix-permit-dots-in-the-resource-policy-id.patch @@ -0,0 +1,125 @@ +From ff132ef73f293a5627a4dae58417a2c571fb6674 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Mon, 10 Mar 2025 14:34:55 +0800 +Subject: [PATCH 1/1] fix: permit dots in the resource policy id + +The resource policy id ends with '.rego', thus dots should be allowed +when checking the legitimacy of resource policy id. +--- + .../src/resource/policy/mod.rs | 16 ++++++++++++ + .../src/resource/policy/opa/mod.rs | 25 +++++++++++++------ + 2 files changed, 33 insertions(+), 8 deletions(-) + +diff --git a/service/attestation/attestation-types/src/resource/policy/mod.rs b/service/attestation/attestation-types/src/resource/policy/mod.rs +index 6ad05dd..46f46ae 100644 +--- a/service/attestation/attestation-types/src/resource/policy/mod.rs ++++ b/service/attestation/attestation-types/src/resource/policy/mod.rs +@@ -33,6 +33,22 @@ pub struct PolicyLocation { + pub id: String, + } + ++impl PolicyLocation { ++ pub(crate) fn check_legal(&self) -> bool { ++ if let Some(v) = &self.vendor { ++ if v.contains(['.', '/']) { ++ return false; ++ } ++ } ++ ++ if self.id.contains(['/']) || !self.id.ends_with(".rego") { ++ return false; ++ } ++ ++ true ++ } ++} ++ + impl std::convert::From for String { + fn from(value: PolicyLocation) -> Self { + format!("{}", value) +diff --git a/service/attestation/attestation-types/src/resource/policy/opa/mod.rs b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +index 8e2486a..d702061 100644 +--- a/service/attestation/attestation-types/src/resource/policy/opa/mod.rs ++++ b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +@@ -16,7 +16,7 @@ use crate::resource::{ + policy::PolicyEngine, + ResourceLocation, DEFAULT_VENDOR_BASE, + }; +-use anyhow::{bail, Context}; ++use anyhow::Context; + use async_trait::async_trait; + use std::path::PathBuf; + +@@ -33,7 +33,16 @@ impl OpenPolicyAgent { + OpenPolicyAgent { base } + } + +- pub(crate) fn regular(&self, vendor: &str) -> Result { ++ pub(crate) fn regular_policy(&self, policy: &PolicyLocation) -> Result { ++ let p = policy.to_string(); ++ if !policy.check_legal() { ++ return Err(ResourceError::IllegalPolicyLocation(p)); ++ } ++ ++ Ok(self.base.join(p)) ++ } ++ ++ pub(crate) fn regular_vendor(&self, vendor: &str) -> Result { + if !Self::check_vendor_legal(vendor) { + return Err(ResourceError::IllegalVendor(vendor.to_string())); + } +@@ -48,7 +57,7 @@ impl OpenPolicyAgent { + } + + pub(crate) fn check_vendor_legal(vendor: &str) -> bool { +- if vendor.contains('.') { ++ if vendor.contains(['.', '/']) { + return false; + } + true +@@ -157,13 +166,13 @@ impl PolicyEngine for OpenPolicyAgent { + } + + async fn get_policy(&self, path: PolicyLocation) -> Result { +- let p = self.regular(&format!("{}", path))?; ++ let p = self.regular_policy(&path)?; + let raw = tokio::fs::read(p).await?; + Ok(String::from_utf8(raw)?) + } + + async fn add_policy(&self, path: PolicyLocation, policy: &str) -> Result<()> { +- let p = self.regular(&format!("{}", path))?; ++ let p = self.regular_policy(&path)?; + if let Some(parent) = p.parent() { + if let Err(e) = tokio::fs::create_dir_all(parent).await { + log::warn!( +@@ -178,7 +187,7 @@ impl PolicyEngine for OpenPolicyAgent { + } + + async fn delete_policy(&self, path: PolicyLocation) -> Result<()> { +- let p = self.regular(&format!("{}", path))?; ++ let p = self.regular_policy(&path)?; + tokio::fs::remove_file(p).await?; + Ok(()) + } +@@ -221,7 +230,7 @@ impl PolicyEngine for OpenPolicyAgent { + } + + async fn get_all_policy_in_vendor(&self, vendor: &str) -> Result> { +- let vendor_dir = self.regular(vendor)?; ++ let vendor_dir = self.regular_vendor(vendor)?; + let mut dir = tokio::fs::read_dir(vendor_dir).await?; + let mut ret: Vec = vec![]; + while let Some(d) = dir.next_entry().await? { +@@ -285,7 +294,7 @@ impl PolicyEngine for OpenPolicyAgent { + } + + async fn clear_all_policy_in_vendor(&self, vendor: &str) -> Result<()> { +- let vendor_dir = self.regular(vendor)?; ++ let vendor_dir = self.regular_vendor(vendor)?; + let md = tokio::fs::metadata(&vendor_dir) + .await + .context("fetching metadata failed")?; +-- +2.46.0 + diff --git a/0100-challenge-may-generate-by-requester-so-aa-and-as-may.patch b/0100-challenge-may-generate-by-requester-so-aa-and-as-may.patch new file mode 100644 index 0000000000000000000000000000000000000000..e750c0b25534066ceeb2f28c17c0846fcaf19cdf --- /dev/null +++ b/0100-challenge-may-generate-by-requester-so-aa-and-as-may.patch @@ -0,0 +1,155 @@ +From 54e0d971449ac9b8f30bad4f15adb82d42395dc6 Mon Sep 17 00:00:00 2001 +From: xuraoqing +Date: Tue, 11 Mar 2025 08:22:18 +0800 +Subject: [PATCH 1/3] challenge may generate by requester,so aa and as may can + not get previous session by challenge + +Signed-off-by: xuraoqing +--- + .../attestation-agent/agent/src/lib.rs | 37 ++++++------ + .../service/src/restapi/mod.rs | 57 +++++++++++-------- + 2 files changed, 51 insertions(+), 43 deletions(-) + +diff --git a/service/attestation/attestation-agent/agent/src/lib.rs b/service/attestation/attestation-agent/agent/src/lib.rs +index d1234d1..dae5559 100644 +--- a/service/attestation/attestation-agent/agent/src/lib.rs ++++ b/service/attestation/attestation-agent/agent/src/lib.rs +@@ -320,23 +320,11 @@ impl AttestationAgent { + policy_id: Option>, + ) -> Result { + let challenge = String::from_utf8_lossy(challenge).to_string(); +- let mut session = match self ++ let ss = self + .as_client_sessions + .session_map + .get_async(&challenge) +- .await +- { +- Some(entry) => entry, +- None => { +- // Challenge should be posted to service previously. +- bail!("challenge '{}' does not exist in sessions", challenge); +- } +- }; +- +- // If the session is already attested, directly use the token. +- if let Some(t) = session.get().token.as_ref() { +- return Ok(t.clone()); +- } ++ .await; + + let request_body = json!({ + "challenge": challenge, +@@ -344,9 +332,20 @@ impl AttestationAgent { + "policy_id": policy_id, + }); + let mut map = HeaderMap::new(); +- map.insert("Content-Type", HeaderValue::from_static("application/json")); +- map.insert("as-challenge", HeaderValue::from_static("as")); +- let client = session.get().as_client.clone(); ++ let client; ++ if ss.is_none() { ++ client = self.create_client(self.config.protocal.clone(), true)?; ++ map.insert("Content-Type", HeaderValue::from_static("application/json")); ++ } else { ++ // If the session is already attested, directly use the token. ++ if let Some(t) = ss.as_ref().unwrap().get().token.as_ref() { ++ return Ok(t.clone()); ++ } ++ map.insert("Content-Type", HeaderValue::from_static("application/json")); ++ map.insert("as-challenge", HeaderValue::from_static("as")); ++ client = ss.as_ref().unwrap().get().as_client.clone(); ++ } ++ + let attest_endpoint = format!("{}/attestation", self.config.svr_url); + let res = client + .post(attest_endpoint) +@@ -358,7 +357,9 @@ impl AttestationAgent { + match res.status() { + reqwest::StatusCode::OK => { + let token = res.text().await?; +- session.get_mut().token = Some(token.clone()); ++ if ss.as_ref().is_some() { ++ ss.unwrap().get_mut().token = Some(token.clone()); ++ } + log::debug!("Remote Attestation success, AS Response: {:?}", token); + Ok(token) + } +diff --git a/service/attestation/attestation-service/service/src/restapi/mod.rs b/service/attestation/attestation-service/service/src/restapi/mod.rs +index c3d6309..26a0f84 100644 +--- a/service/attestation/attestation-service/service/src/restapi/mod.rs ++++ b/service/attestation/attestation-service/service/src/restapi/mod.rs +@@ -77,30 +77,32 @@ pub async fn attestation( + let map = service.read().await.get_sessions(); + let request = request.0; + let challenge = request.challenge; +- +- if http_req.headers().contains_key("as-challenge") { +- log::warn!("attestation request lacks 'as-challenge' header field."); +- } +- +- log::info!("sessions map len:{}", map.session_map.len()); +- let cookie = http_req +- .cookie("oeas-session-id") +- .ok_or(AsError::CookieMissing)?; +- let session = map +- .session_map +- .get_async(cookie.value()) +- .await +- .ok_or(AsError::SessionNotFound)?; +- if session.is_expired() { +- return Err(AsError::SessionExpired); +- } +- if challenge != session.challenge { +- log::error!( +- "request challenge:{} does not match session challenge:{}", +- challenge, +- session.challenge +- ); +- return Err(AsError::ChallengeInvalid); ++ let mut cookie_exist = false; ++ let mut cookie = actix_web::cookie::Cookie::new("init", "init"); ++ if !http_req.headers().contains_key("as-challenge") { ++ log::info!("attestation request lacks 'as-challenge' header field."); ++ } else { ++ log::info!("sessions map len:{}", map.session_map.len()); ++ cookie = http_req ++ .cookie("oeas-session-id") ++ .ok_or(AsError::CookieMissing)?; ++ cookie_exist = true; ++ let session = map ++ .session_map ++ .get_async(cookie.value()) ++ .await ++ .ok_or(AsError::SessionNotFound)?; ++ if session.is_expired() { ++ return Err(AsError::SessionExpired); ++ } ++ if challenge != session.challenge { ++ log::error!( ++ "request challenge:{} does not match session challenge:{}", ++ challenge, ++ session.challenge ++ ); ++ return Err(AsError::ChallengeInvalid); ++ } + } + + // The challenge in evidence is base64 encoded. +@@ -113,7 +115,12 @@ pub async fn attestation( + .evaluate(&nonce, &evidence, &ids) + .await?; + +- Ok(HttpResponse::Ok().cookie(cookie).body(token)) ++ if cookie_exist { ++ Ok(HttpResponse::Ok().cookie(cookie).body(token)) ++ } else { ++ Ok(HttpResponse::Ok().body(token)) ++ } ++ + } + + #[derive(Deserialize, Serialize, Debug)] +-- +2.33.0 + diff --git a/0101-generate-random-by-ra_tls-itself.patch b/0101-generate-random-by-ra_tls-itself.patch new file mode 100644 index 0000000000000000000000000000000000000000..8dccb6f243b89bd195ccdd299453ddcebb12290c --- /dev/null +++ b/0101-generate-random-by-ra_tls-itself.patch @@ -0,0 +1,176 @@ +From 7ca3d2ff7269501d4810c10e209e18a70fdfda95 Mon Sep 17 00:00:00 2001 +From: xuraoqing +Date: Tue, 11 Mar 2025 12:41:24 +0800 +Subject: [PATCH 2/3] generate random by ra_tls itself + +Signed-off-by: xuraoqing +--- + component/ra_tls/openssl_imp.c | 14 ++++++ + component/ra_tls/ra_tls.c | 87 +++++++++------------------------- + component/ra_tls/ra_tls_imp.h | 1 + + 3 files changed, 37 insertions(+), 65 deletions(-) + +diff --git a/component/ra_tls/openssl_imp.c b/component/ra_tls/openssl_imp.c +index 82e0fd0..19bb280 100644 +--- a/component/ra_tls/openssl_imp.c ++++ b/component/ra_tls/openssl_imp.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include "ra_tls_imp.h" + + #define OID_LEN_MAX 64 +@@ -539,6 +540,19 @@ int get_hash(ra_tls_buf *hash, ra_tls_buf *input, hash_type type) + return 0; + } + ++int get_random(uint8_t *random, size_t len) ++{ ++ if (random == NULL) { ++ printf("buf for random is empty\n"); ++ return -1; ++ } ++ if (RAND_bytes(random, len) != 1) { ++ printf("generate random failed\n"); ++ return -1; ++ } ++ return 0; ++} ++ + int generate_certificate_with_extension(ra_tls_buf *cert, ra_tls_buf *extension, ra_tls_buf *public_key, + ra_tls_buf *private_key, const char *oid) + { +diff --git a/component/ra_tls/ra_tls.c b/component/ra_tls/ra_tls.c +index eba084f..b0ba9a0 100644 +--- a/component/ra_tls/ra_tls.c ++++ b/component/ra_tls/ra_tls.c +@@ -220,64 +220,15 @@ static int get_token(ra_tls_buf *token, const char *endpoint_prefix, const char + return ret; + } + +-static char *fill_endpoint(char **endpoint, const char *prefix, const char *source) +-{ +- size_t endpoint_len = 0; +- endpoint_len = strlen(prefix) + strlen(source) + 1; +- *endpoint = malloc(endpoint_len); +- if (*endpoint == NULL) { +- return NULL; +- } +- strcpy(*endpoint, prefix); +- strcat(*endpoint, source); +- return *endpoint; +-} +- +-static char *fill_http_data(char **data, ra_tls_buf *key_hash) +-{ +- const char *http_data_format = "{\"user_data\":[]}"; +- size_t data_len = 0; +- int res; +- char *ptr; +- data_len = strlen(http_data_format) + PUBLIC_KEY_HASH_PRINT_LEN + strlen("]}") + 1; +- *data = malloc(data_len); +- if (*data == NULL) { +- return NULL; +- } +- ptr = *data; +- res = sprintf(ptr, "{\"user_data\":["); +- ptr += res; +- for (size_t i = 0; i < key_hash->filled; i++) { +- res = sprintf(ptr, "%hhu,", key_hash->buf[i]); +- ptr += res; +- if (ptr >= *data + data_len) { +- goto err; +- } +- } +- // point to last character +- --ptr; +- if (data_len - (ptr - *data) < 3) { // 3 means min buffer left to filled +- goto err; +- } +- (void)sprintf(ptr, "]}"); +- goto end; +-err: +- if (*data) { +- free(*data); +- *data = NULL; +- } +-end: +- return *data; +-} +- + static int get_challenge(ra_tls_buf *challenge, const char *endpoint_prefix, ra_tls_buf *user_data) + { + int res; + int ret = -1; +- const char *source_name = "challenge"; +- char *endpoint = NULL; +- char *http_data = NULL; ++ const size_t challenge_len = 32; // 32 means the length of challenge by default ++ size_t base64_len = 0; ++ uint8_t *base64 = NULL; + ra_tls_buf key_hash = RA_TLS_BUF_INIT; ++ ra_tls_buf challenge_raw = RA_TLS_BUF_INIT; + ra_tls_buf *pub_key = user_data; + if (endpoint_prefix == NULL || challenge == NULL || pub_key == NULL) { + return -1; +@@ -291,25 +242,31 @@ static int get_challenge(ra_tls_buf *challenge, const char *endpoint_prefix, ra_ + } + printf("\n"); + #endif +- if (fill_endpoint(&endpoint, endpoint_prefix, source_name) == NULL) { +- goto err; +- } +- if (fill_http_data(&http_data, &key_hash) == NULL) { ++// generate random 32B, concate with public key hash, then base64_url_encode ++ ra_tls_buf_init(&challenge_raw, challenge_len + key_hash.filled); ++ res = get_random(challenge_raw.buf, challenge_len); ++ if (res < 0) { ++ printf("get random failed\n"); + goto err; + } +- res = http_request(endpoint, "GET", http_data, challenge); +- if (res < 0) { ++ memcpy(challenge_raw.buf + challenge_len, key_hash.buf, key_hash.filled); ++ challenge_raw.filled = challenge_len + key_hash.filled; ++ ++ base64 = (uint8_t*)kpsecl_base64urlencode(challenge_raw.buf, challenge_raw.filled, &base64_len); ++ if (base64 == NULL) { + goto err; + } ++ ++ ra_tls_buf_init(challenge, base64_len); ++ memcpy(challenge->buf, base64, base64_len); ++ challenge->filled = base64_len; + ret = 0; + err: +- if (endpoint) { +- free(endpoint); +- } +- if (http_data) { +- free(http_data); +- } + ra_tls_buf_free(&key_hash); ++ ra_tls_buf_free(&challenge_raw); ++ if (base64) { ++ free(base64); ++ } + return ret; + } + +diff --git a/component/ra_tls/ra_tls_imp.h b/component/ra_tls/ra_tls_imp.h +index 5d85ba9..7440a8d 100644 +--- a/component/ra_tls/ra_tls_imp.h ++++ b/component/ra_tls/ra_tls_imp.h +@@ -79,6 +79,7 @@ int generate_key_pair_der(key_size key_len, ra_tls_buf *public_key, ra_tls_buf * + // generate pem certificate,use evidence filled extension specified by oid + int generate_certificate_with_extension(ra_tls_buf *cert, ra_tls_buf *evidence, ra_tls_buf *public_key, + ra_tls_buf *private_key, const char *oid); ++int get_random(uint8_t *random, size_t len); + + #if defined(USE_OPENSSL) + /* +-- +2.33.0 + diff --git a/0102-Add-support-for-UEFI-measured-boot-attestation.patch b/0102-Add-support-for-UEFI-measured-boot-attestation.patch new file mode 100644 index 0000000000000000000000000000000000000000..5ca864eb2fdfefa404fa34c4d2df19b59b64eb88 --- /dev/null +++ b/0102-Add-support-for-UEFI-measured-boot-attestation.patch @@ -0,0 +1,1388 @@ +From fd5d243308369d6045d331be8a24676cd30becc8 Mon Sep 17 00:00:00 2001 +From: SPYFAMILY +Date: Mon, 12 May 2025 14:42:51 +0800 +Subject: [PATCH] =?UTF-8?q?=E8=BF=9C=E7=A8=8B=E8=AF=81=E6=98=8E=E7=BB=9F?= + =?UTF-8?q?=E4=B8=80=E6=A1=86=E6=9E=B6uefi=E5=BA=A6=E9=87=8F=E5=90=AF?= + =?UTF-8?q?=E5=8A=A8=E9=80=82=E9=85=8D?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +--- + .../attestation-agent/attester/src/lib.rs | 1 - + .../attester/src/virtcca/mod.rs | 39 +++- + .../attestation-service/service/src/lib.rs | 44 +++- + .../attestation-service/verifier/Cargo.toml | 1 + + .../verifier/eventlog-rs/Cargo.toml | 18 ++ + .../verifier/eventlog-rs/LICENSE | 201 +++++++++++++++++ + .../verifier/eventlog-rs/README.md | 2 + + .../verifier/eventlog-rs/src/bios_eventlog.rs | 87 ++++++++ + .../verifier/eventlog-rs/src/enums.rs | 54 +++++ + .../verifier/eventlog-rs/src/lib.rs | 177 +++++++++++++++ + .../verifier/eventlog-rs/src/read.rs | 111 ++++++++++ + .../attestation-service/verifier/src/lib.rs | 1 - + .../verifier/src/virtcca/ima.rs | 42 ++-- + .../verifier/src/virtcca/mod.rs | 68 +++++- + .../verifier/src/virtcca/uefi.rs | 209 ++++++++++++++++++ + .../attestation/attestation-types/src/lib.rs | 7 + + .../src/resource/policy/opa/mod.rs | 1 + + 17 files changed, 1010 insertions(+), 53 deletions(-) + create mode 100644 service/attestation/attestation-service/verifier/eventlog-rs/Cargo.toml + create mode 100644 service/attestation/attestation-service/verifier/eventlog-rs/LICENSE + create mode 100644 service/attestation/attestation-service/verifier/eventlog-rs/README.md + create mode 100644 service/attestation/attestation-service/verifier/eventlog-rs/src/bios_eventlog.rs + create mode 100644 service/attestation/attestation-service/verifier/eventlog-rs/src/enums.rs + create mode 100644 service/attestation/attestation-service/verifier/eventlog-rs/src/lib.rs + create mode 100644 service/attestation/attestation-service/verifier/eventlog-rs/src/read.rs + create mode 100644 service/attestation/attestation-service/verifier/src/virtcca/uefi.rs + +diff --git a/service/attestation/attestation-agent/attester/src/lib.rs b/service/attestation/attestation-agent/attester/src/lib.rs +index 6dd549d..d495a6f 100644 +--- a/service/attestation/attestation-agent/attester/src/lib.rs ++++ b/service/attestation/attestation-agent/attester/src/lib.rs +@@ -16,7 +16,6 @@ + + use anyhow::*; + use async_trait::async_trait; +-use log; + + #[cfg(feature = "itrustee-attester")] + mod itrustee; +diff --git a/service/attestation/attestation-agent/attester/src/virtcca/mod.rs b/service/attestation/attestation-agent/attester/src/virtcca/mod.rs +index 86f0061..9f84ed4 100644 +--- a/service/attestation/attestation-agent/attester/src/virtcca/mod.rs ++++ b/service/attestation/attestation-agent/attester/src/virtcca/mod.rs +@@ -15,7 +15,7 @@ + //! Call the hardware sdk or driver to get the specific evidence + + use anyhow::{bail, Result}; +-use attestation_types::VirtccaEvidence; ++use attestation_types::{UefiLog, VirtccaEvidence}; + use log; + use std::path::Path; + +@@ -25,6 +25,10 @@ use crate::EvidenceRequest; + + mod virtcca; + ++const IMA_LOG_PATH: &str = "/sys/kernel/security/ima/binary_runtime_measurements"; ++const CCEL_TABLE_PATH: &str = "/sys/firmware/acpi/tables/CCEL"; ++const CCEL_DATA_PATH: &str = "/sys/firmware/acpi/tables/data/CCEL"; ++ + #[derive(Debug, Default)] + pub struct VirtccaAttester {} + +@@ -41,6 +45,7 @@ pub fn detect_platform() -> bool { + } + + const MAX_CHALLENGE_LEN: usize = 64; ++ + fn virtcca_get_token(user_data: EvidenceRequest) -> Result { + let mut challenge = base64_url::decode(&user_data.challenge)?; + let len = challenge.len(); +@@ -88,18 +93,46 @@ fn virtcca_get_token(user_data: EvidenceRequest) -> Result { + Some(ima) => ima, + None => false, + }; ++ + let ima_log = match with_ima { + true => { +- Some(std::fs::read("/sys/kernel/security/ima/binary_runtime_measurements").unwrap()) +- } ++ match std::fs::read(IMA_LOG_PATH) { ++ Ok(d) => { ++ log::info!("read ima log success"); ++ Some(d) ++ }, ++ Err(e) => { ++ log::error!("read IMA log failed: {}", e); ++ bail!("get ima log failed"); ++ } ++ } ++ }, + false => None, + }; + ++ let ccel_table = std::fs::read(CCEL_TABLE_PATH).ok(); ++ let ccel_data = std::fs::read(CCEL_DATA_PATH).ok(); ++ let uefi_log = match (ccel_table, ccel_data) { ++ (Some(table), Some(data)) => { ++ log::info!("read ccel table and data success"); ++ Some(UefiLog { ++ ccel_table: table, ++ ccel_data: data, ++ }) ++ }, ++ _ => { ++ log::warn!("read ccel table or data failed"); ++ None ++ } ++ }; ++ + let evidence = VirtccaEvidence { + evidence: token, + dev_cert: dev_cert, + ima_log: ima_log, ++ uefi_log: uefi_log, + }; ++ + let _ = tsi_free_ctx(ctx); + Ok(evidence) + } +diff --git a/service/attestation/attestation-service/service/src/lib.rs b/service/attestation/attestation-service/service/src/lib.rs +index 99ae818..96328ce 100644 +--- a/service/attestation/attestation-service/service/src/lib.rs ++++ b/service/attestation/attestation-service/service/src/lib.rs +@@ -109,6 +109,28 @@ impl AttestationService { + sessions: web::Data::new(SessionMap::new()), + }) + } ++ ++ async fn evaluate_evidence_field(claims_evidence: &Value, field: &str, mut passed: &mut bool) { ++ log::debug!( ++ "claims evidence {}: {:?}", ++ field, ++ claims_evidence[field].clone() ++ ); ++ if *passed { ++ match claims_evidence[field].clone() { ++ Value::Object(obj) => { ++ for (_k, v) in obj { ++ if v == Value::Bool(false) { ++ *passed = false; ++ break; ++ } ++ } ++ } ++ _ => log::debug!("no {} result", field), ++ } ++ } ++ } ++ + /// evaluate tee evidence with reference and policy, and issue attestation result token + pub async fn evaluate( + &self, +@@ -120,18 +142,8 @@ impl AttestationService { + let claims_evidence = verifier.verify_evidence(user_data, evidence).await?; + + let mut passed = true; +- log::debug!("claims evidece ima: {:?}", claims_evidence["ima"].clone()); +- match claims_evidence["ima"].clone() { +- serde_json::Value::Object(obj) => { +- for (_k, v) in obj { +- if v == Value::Bool(false) { +- passed = false; +- break; +- } +- } +- } +- _ => log::debug!("no ima result"), +- } ++ AttestationService::evaluate_evidence_field(&claims_evidence, "ima", &mut passed).await; ++ AttestationService::evaluate_evidence_field(&claims_evidence, "uefi", &mut passed).await; + + // get reference by keys in claims_evidence + let mut ops_refs = ReferenceOps::default(); +@@ -158,8 +170,10 @@ impl AttestationService { + .await; + let mut report = serde_json::json!({}); + let mut ref_exist_null: bool = false; ++ + match result { + Ok(eval) => { ++ log::debug!("policy: {:?}", eval); + for id in eval.keys() { + let val = Value::from_str(&eval[id].clone())?; + let refs = match val +@@ -194,6 +208,12 @@ impl AttestationService { + .unwrap() + .insert("ima".to_string(), claims_evidence["ima"].clone()); + ++ // add uefi detail result to report ++ report ++ .as_object_mut() ++ .unwrap() ++ .insert("uefi".to_string(), claims_evidence["uefi"].clone()); ++ + // issue attestation result token + let evl_report = EvlReport { + tee: String::from( +diff --git a/service/attestation/attestation-service/verifier/Cargo.toml b/service/attestation/attestation-service/verifier/Cargo.toml +index baab873..e66e5ea 100644 +--- a/service/attestation/attestation-service/verifier/Cargo.toml ++++ b/service/attestation/attestation-service/verifier/Cargo.toml +@@ -20,6 +20,7 @@ attestation-types.workspace = true + ccatoken.workspace = true + ear.workspace = true + base64-url.workspace = true ++eventlog-rs = { path = "./eventlog-rs" } + + [dev-dependencies] + +diff --git a/service/attestation/attestation-service/verifier/eventlog-rs/Cargo.toml b/service/attestation/attestation-service/verifier/eventlog-rs/Cargo.toml +new file mode 100644 +index 0000000..20796fe +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/eventlog-rs/Cargo.toml +@@ -0,0 +1,18 @@ ++[package] ++name = "eventlog-rs" ++description = "Rust Parsing tool for TCG EventLog" ++version = "0.1.7" ++authors = ["Jiale Zhang "] ++edition = "2021" ++homepage = "https://github.com/inclavare-containers/eventlog-rs" ++license = "Apache-2.0" ++ ++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html ++ ++[dependencies] ++log = "0.4.14" ++lazy_static = "1.4.0" ++byteorder = "1" ++hex = "0.4.3" ++sha2 = "0.10" ++anyhow = "1.0" +diff --git a/service/attestation/attestation-service/verifier/eventlog-rs/LICENSE b/service/attestation/attestation-service/verifier/eventlog-rs/LICENSE +new file mode 100644 +index 0000000..261eeb9 +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/eventlog-rs/LICENSE +@@ -0,0 +1,201 @@ ++ Apache License ++ Version 2.0, January 2004 ++ http://www.apache.org/licenses/ ++ ++ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION ++ ++ 1. Definitions. ++ ++ "License" shall mean the terms and conditions for use, reproduction, ++ and distribution as defined by Sections 1 through 9 of this document. ++ ++ "Licensor" shall mean the copyright owner or entity authorized by ++ the copyright owner that is granting the License. ++ ++ "Legal Entity" shall mean the union of the acting entity and all ++ other entities that control, are controlled by, or are under common ++ control with that entity. For the purposes of this definition, ++ "control" means (i) the power, direct or indirect, to cause the ++ direction or management of such entity, whether by contract or ++ otherwise, or (ii) ownership of fifty percent (50%) or more of the ++ outstanding shares, or (iii) beneficial ownership of such entity. ++ ++ "You" (or "Your") shall mean an individual or Legal Entity ++ exercising permissions granted by this License. ++ ++ "Source" form shall mean the preferred form for making modifications, ++ including but not limited to software source code, documentation ++ source, and configuration files. ++ ++ "Object" form shall mean any form resulting from mechanical ++ transformation or translation of a Source form, including but ++ not limited to compiled object code, generated documentation, ++ and conversions to other media types. ++ ++ "Work" shall mean the work of authorship, whether in Source or ++ Object form, made available under the License, as indicated by a ++ copyright notice that is included in or attached to the work ++ (an example is provided in the Appendix below). ++ ++ "Derivative Works" shall mean any work, whether in Source or Object ++ form, that is based on (or derived from) the Work and for which the ++ editorial revisions, annotations, elaborations, or other modifications ++ represent, as a whole, an original work of authorship. For the purposes ++ of this License, Derivative Works shall not include works that remain ++ separable from, or merely link (or bind by name) to the interfaces of, ++ the Work and Derivative Works thereof. ++ ++ "Contribution" shall mean any work of authorship, including ++ the original version of the Work and any modifications or additions ++ to that Work or Derivative Works thereof, that is intentionally ++ submitted to Licensor for inclusion in the Work by the copyright owner ++ or by an individual or Legal Entity authorized to submit on behalf of ++ the copyright owner. For the purposes of this definition, "submitted" ++ means any form of electronic, verbal, or written communication sent ++ to the Licensor or its representatives, including but not limited to ++ communication on electronic mailing lists, source code control systems, ++ and issue tracking systems that are managed by, or on behalf of, the ++ Licensor for the purpose of discussing and improving the Work, but ++ excluding communication that is conspicuously marked or otherwise ++ designated in writing by the copyright owner as "Not a Contribution." ++ ++ "Contributor" shall mean Licensor and any individual or Legal Entity ++ on behalf of whom a Contribution has been received by Licensor and ++ subsequently incorporated within the Work. ++ ++ 2. Grant of Copyright License. Subject to the terms and conditions of ++ this License, each Contributor hereby grants to You a perpetual, ++ worldwide, non-exclusive, no-charge, royalty-free, irrevocable ++ copyright license to reproduce, prepare Derivative Works of, ++ publicly display, publicly perform, sublicense, and distribute the ++ Work and such Derivative Works in Source or Object form. ++ ++ 3. Grant of Patent License. Subject to the terms and conditions of ++ this License, each Contributor hereby grants to You a perpetual, ++ worldwide, non-exclusive, no-charge, royalty-free, irrevocable ++ (except as stated in this section) patent license to make, have made, ++ use, offer to sell, sell, import, and otherwise transfer the Work, ++ where such license applies only to those patent claims licensable ++ by such Contributor that are necessarily infringed by their ++ Contribution(s) alone or by combination of their Contribution(s) ++ with the Work to which such Contribution(s) was submitted. If You ++ institute patent litigation against any entity (including a ++ cross-claim or counterclaim in a lawsuit) alleging that the Work ++ or a Contribution incorporated within the Work constitutes direct ++ or contributory patent infringement, then any patent licenses ++ granted to You under this License for that Work shall terminate ++ as of the date such litigation is filed. ++ ++ 4. Redistribution. You may reproduce and distribute copies of the ++ Work or Derivative Works thereof in any medium, with or without ++ modifications, and in Source or Object form, provided that You ++ meet the following conditions: ++ ++ (a) You must give any other recipients of the Work or ++ Derivative Works a copy of this License; and ++ ++ (b) You must cause any modified files to carry prominent notices ++ stating that You changed the files; and ++ ++ (c) You must retain, in the Source form of any Derivative Works ++ that You distribute, all copyright, patent, trademark, and ++ attribution notices from the Source form of the Work, ++ excluding those notices that do not pertain to any part of ++ the Derivative Works; and ++ ++ (d) If the Work includes a "NOTICE" text file as part of its ++ distribution, then any Derivative Works that You distribute must ++ include a readable copy of the attribution notices contained ++ within such NOTICE file, excluding those notices that do not ++ pertain to any part of the Derivative Works, in at least one ++ of the following places: within a NOTICE text file distributed ++ as part of the Derivative Works; within the Source form or ++ documentation, if provided along with the Derivative Works; or, ++ within a display generated by the Derivative Works, if and ++ wherever such third-party notices normally appear. The contents ++ of the NOTICE file are for informational purposes only and ++ do not modify the License. You may add Your own attribution ++ notices within Derivative Works that You distribute, alongside ++ or as an addendum to the NOTICE text from the Work, provided ++ that such additional attribution notices cannot be construed ++ as modifying the License. ++ ++ You may add Your own copyright statement to Your modifications and ++ may provide additional or different license terms and conditions ++ for use, reproduction, or distribution of Your modifications, or ++ for any such Derivative Works as a whole, provided Your use, ++ reproduction, and distribution of the Work otherwise complies with ++ the conditions stated in this License. ++ ++ 5. Submission of Contributions. Unless You explicitly state otherwise, ++ any Contribution intentionally submitted for inclusion in the Work ++ by You to the Licensor shall be under the terms and conditions of ++ this License, without any additional terms or conditions. ++ Notwithstanding the above, nothing herein shall supersede or modify ++ the terms of any separate license agreement you may have executed ++ with Licensor regarding such Contributions. ++ ++ 6. Trademarks. This License does not grant permission to use the trade ++ names, trademarks, service marks, or product names of the Licensor, ++ except as required for reasonable and customary use in describing the ++ origin of the Work and reproducing the content of the NOTICE file. ++ ++ 7. Disclaimer of Warranty. Unless required by applicable law or ++ agreed to in writing, Licensor provides the Work (and each ++ Contributor provides its Contributions) on an "AS IS" BASIS, ++ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or ++ implied, including, without limitation, any warranties or conditions ++ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A ++ PARTICULAR PURPOSE. You are solely responsible for determining the ++ appropriateness of using or redistributing the Work and assume any ++ risks associated with Your exercise of permissions under this License. ++ ++ 8. Limitation of Liability. In no event and under no legal theory, ++ whether in tort (including negligence), contract, or otherwise, ++ unless required by applicable law (such as deliberate and grossly ++ negligent acts) or agreed to in writing, shall any Contributor be ++ liable to You for damages, including any direct, indirect, special, ++ incidental, or consequential damages of any character arising as a ++ result of this License or out of the use or inability to use the ++ Work (including but not limited to damages for loss of goodwill, ++ work stoppage, computer failure or malfunction, or any and all ++ other commercial damages or losses), even if such Contributor ++ has been advised of the possibility of such damages. ++ ++ 9. Accepting Warranty or Additional Liability. While redistributing ++ the Work or Derivative Works thereof, You may choose to offer, ++ and charge a fee for, acceptance of support, warranty, indemnity, ++ or other liability obligations and/or rights consistent with this ++ License. However, in accepting such obligations, You may act only ++ on Your own behalf and on Your sole responsibility, not on behalf ++ of any other Contributor, and only if You agree to indemnify, ++ defend, and hold each Contributor harmless for any liability ++ incurred by, or claims asserted against, such Contributor by reason ++ of your accepting any such warranty or additional liability. ++ ++ END OF TERMS AND CONDITIONS ++ ++ APPENDIX: How to apply the Apache License to your work. ++ ++ To apply the Apache License to your work, attach the following ++ boilerplate notice, with the fields enclosed by brackets "[]" ++ replaced with your own identifying information. (Don't include ++ the brackets!) The text should be enclosed in the appropriate ++ comment syntax for the file format. We also recommend that a ++ file or class name and description of purpose be included on the ++ same "printed page" as the copyright notice for easier ++ identification within third-party archives. ++ ++ Copyright [yyyy] [name of copyright owner] ++ ++ Licensed under the Apache License, Version 2.0 (the "License"); ++ you may not use this file except in compliance with the License. ++ You may obtain a copy of the License at ++ ++ http://www.apache.org/licenses/LICENSE-2.0 ++ ++ Unless required by applicable law or agreed to in writing, software ++ distributed under the License is distributed on an "AS IS" BASIS, ++ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ See the License for the specific language governing permissions and ++ limitations under the License. +diff --git a/service/attestation/attestation-service/verifier/eventlog-rs/README.md b/service/attestation/attestation-service/verifier/eventlog-rs/README.md +new file mode 100644 +index 0000000..d7c242f +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/eventlog-rs/README.md +@@ -0,0 +1,2 @@ ++# eventlog-rs ++lib to analyze and extract TEE's event log +diff --git a/service/attestation/attestation-service/verifier/eventlog-rs/src/bios_eventlog.rs b/service/attestation/attestation-service/verifier/eventlog-rs/src/bios_eventlog.rs +new file mode 100644 +index 0000000..5cbcf8b +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/eventlog-rs/src/bios_eventlog.rs +@@ -0,0 +1,87 @@ ++// TCG Eventlog for Conventional BIOS ++// Spec: https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientImplementation_1-21_1_00.pdf ++ ++use crate::enums::EVENTLOG_TYPES; ++use anyhow::*; ++use byteorder::{LittleEndian, ReadBytesExt}; ++use core::fmt; ++use std::convert::TryFrom; ++ ++const SHA1_DIGEST_SIZE: usize = 20; ++ ++#[derive(Clone)] ++pub struct BiosEventlog { ++ pub log: Vec, ++} ++ ++impl fmt::Display for BiosEventlog { ++ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { ++ let mut parsed_el = String::default(); ++ for event_entry in self.log.clone() { ++ parsed_el = format!( ++ "{}\nEvent Entry:\n\tPCR: {}\n\tEvent Type: {}\n\tDigest: {}\n\tEvent Data: {}\n", ++ parsed_el, ++ event_entry.pcr_index, ++ event_entry.event_type, ++ hex::encode(event_entry.digest.clone()), ++ String::from_utf8(event_entry.event_data.clone()) ++ .unwrap_or_else(|_| hex::encode(event_entry.event_data.clone())), ++ ); ++ } ++ ++ write!(f, "{parsed_el}") ++ } ++} ++ ++#[derive(Clone)] ++pub struct BiosEventlogEntry { ++ pub pcr_index: u32, ++ pub event_type: String, ++ pub digest: Vec, ++ pub event_data: Vec, ++} ++ ++impl TryFrom> for BiosEventlog { ++ type Error = anyhow::Error; ++ ++ fn try_from(data: Vec) -> Result { ++ let mut index = 0; ++ let mut event_log: Vec = Vec::new(); ++ ++ while index < data.len() as usize { ++ let stop_flag = (&data[index..(index + 8)]).read_u64::()?; ++ let pcr_index = (&data[index..(index + 4)]).read_u32::()?; ++ index += 4; ++ ++ let event_type_num = (&data[index..(index + 4)]).read_u32::()?; ++ index += 4; ++ let event_type = match EVENTLOG_TYPES.get(&event_type_num) { ++ Some(type_name) => type_name.to_string(), ++ None => format!("UNKOWN_TYPE: {:x}", &event_type_num), ++ }; ++ ++ if stop_flag == 0xFFFFFFFFFFFFFFFF || stop_flag == 0x0000000000000000 { ++ break; ++ } ++ ++ let digest = data[index..(index + SHA1_DIGEST_SIZE)].to_vec(); ++ index += SHA1_DIGEST_SIZE; ++ ++ let event_data_size = (&data[index..(index + 4)]).read_u32::()? as usize; ++ index += 4; ++ let event_data = data[index..(index + event_data_size)].to_vec(); ++ index += event_data_size; ++ ++ let eventlog_entry = BiosEventlogEntry { ++ pcr_index, ++ event_type, ++ digest, ++ event_data, ++ }; ++ ++ event_log.push(eventlog_entry) ++ } ++ ++ Ok(BiosEventlog { log: event_log }) ++ } ++} +diff --git a/service/attestation/attestation-service/verifier/eventlog-rs/src/enums.rs b/service/attestation/attestation-service/verifier/eventlog-rs/src/enums.rs +new file mode 100644 +index 0000000..be3fefc +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/eventlog-rs/src/enums.rs +@@ -0,0 +1,54 @@ ++use lazy_static::lazy_static; ++use std::collections::HashMap; ++ ++lazy_static! { ++ pub static ref TCG_ALGORITHMS: HashMap = HashMap::from([ ++ (0x1, "TPM_ALG_RSA"), ++ (0x3, "TPM_ALG_TDES"), ++ (0x4, "TPM_ALG_SHA1"), ++ (0xB, "TPM_ALG_SHA256"), ++ (0xC, "TPM_ALG_SHA384"), ++ (0xD, "TPM_ALG_SHA512"), ++ ]); ++} ++ ++lazy_static! { ++ pub static ref EVENTLOG_TYPES: HashMap = HashMap::from( ++ [ ++ (0x0, "EV_PREBOOT_CERT"), ++ (0x1, "EV_POST_CODE"), ++ (0x2, "EV_UNUSED"), ++ (0x3, "EV_NO_ACTION"), ++ (0x4, "EV_SEPARATOR"), ++ (0x5, "EV_ACTION"), ++ (0x6, "EV_EVENT_TAG"), ++ (0x7, "EV_S_CRTM_CONTENTS"), ++ (0x8, "EV_S_CRTM_VERSION"), ++ (0x9, "EV_CPU_MICROCODE"), ++ (0xa, "EV_PLATFORM_CONFIG_FLAGS"), ++ (0xb, "EV_TABLE_OF_DEVICES"), ++ (0xc, "EV_COMPACT_HASH"), ++ (0xd, "EV_IPL"), ++ (0xe, "EV_IPL_PARTITION_DATA"), ++ (0xf, "EV_NONHOST_CODE"), ++ (0x10, "EV_NONHOST_CONFIG"), ++ (0x11, "EV_NONHOST_INFO"), ++ (0x12, "EV_OMIT_BOOT_DEVICE_EVENTS"), ++ ++ // TCG EFI Platform Specification For TPM Family 1.1 or 1.2 ++ (0x80000000, "EV_EFI_EVENT_BASE"), ++ (0x80000001, "EV_EFI_VARIABLE_DRIVER_CONFIG"), ++ (0x80000002, "EV_EFI_VARIABLE_BOOT"), ++ (0x80000003, "EV_EFI_BOOT_SERVICES_APPLICATION"), ++ (0x80000004, "EV_EFI_BOOT_SERVICES_DRIVER"), ++ (0x80000005, "EV_EFI_RUNTIME_SERVICES_DRIVER"), ++ (0x80000006, "EV_EFI_GPT_EVENT"), ++ (0x80000007, "EV_EFI_ACTION"), ++ (0x80000008, "EV_EFI_PLATFORM_FIRMWARE_BLOB"), ++ (0x80000009, "EV_EFI_HANDOFF_TABLES"), ++ (0x8000000a, "EV_EFI_PLATFORM_FIRMWARE_BLOB2"), ++ (0x8000000b, "EV_EFI_HANDOFF_TABLES2"), ++ (0x800000e0, "EV_EFI_VARIABLE_AUTHORITY"), ++ ] ++ ); ++} +diff --git a/service/attestation/attestation-service/verifier/eventlog-rs/src/lib.rs b/service/attestation/attestation-service/verifier/eventlog-rs/src/lib.rs +new file mode 100644 +index 0000000..ced292b +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/eventlog-rs/src/lib.rs +@@ -0,0 +1,177 @@ ++use anyhow::{anyhow, Result}; ++use byteorder::{LittleEndian, ReadBytesExt}; ++use core::fmt; ++use enums::{EVENTLOG_TYPES, TCG_ALGORITHMS}; ++use sha2::{Digest, Sha256}; ++use std::collections::HashMap; ++use std::convert::TryFrom; ++ ++const RTMR_LENGTH_BY_BYTES: usize = 32; ++ ++mod bios_eventlog; ++mod enums; ++ ++pub use bios_eventlog::BiosEventlog; ++pub mod read; ++ ++#[derive(Clone)] ++pub struct Eventlog { ++ pub log: Vec, ++} ++ ++impl fmt::Display for Eventlog { ++ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { ++ let mut parsed_el = String::default(); ++ for event_entry in self.log.clone() { ++ parsed_el = format!( ++ "{}\nEvent Entry:\nPCR(CC Event Log MR): {}\n\tEvent Type id: {}\n\tEvent Type: {}\n\tDigest Algorithm: {}\n\tDigest: {}\n\tEvent Desc: {}\n", ++ parsed_el, ++ event_entry.target_measurement_registry, ++ format!("0x{:08X}", event_entry.event_type_id), ++ event_entry.event_type, ++ event_entry.digests[0].algorithm, ++ hex::encode(event_entry.digests[0].digest.clone()), ++ String::from_utf8(event_entry.event_desc.clone()) ++ .unwrap_or_else(|_| hex::encode(event_entry.event_desc.clone())), ++ ); ++ } ++ ++ write!(f, "{parsed_el}") ++ } ++} ++ ++#[derive(Clone)] ++pub struct EventlogEntry { ++ pub target_measurement_registry: u32, ++ pub event_type_id: u32, ++ pub event_type: String, ++ pub digests: Vec, ++ pub event_desc: Vec, ++} ++ ++#[derive(Debug, Clone)] ++pub struct ElDigest { ++ pub algorithm: String, ++ pub digest: Vec, ++} ++ ++impl Eventlog { ++ pub fn replay_measurement_registry(&self) -> HashMap> { ++ // result dictionary for classifying event logs by rtmr index ++ // the key is a integer, which represents rtmr index ++ // the value is a list of event log entries whose rtmr index is equal to its related key ++ let mut event_logs_by_mr_index: HashMap> = HashMap::new(); ++ ++ let mut result: HashMap> = HashMap::new(); ++ ++ for log_entry in self.log.iter() { ++ match event_logs_by_mr_index.get_mut(&log_entry.target_measurement_registry) { ++ Some(logs) => logs.push(log_entry.clone()), ++ None => { ++ event_logs_by_mr_index.insert( ++ log_entry.target_measurement_registry, ++ vec![log_entry.clone()], ++ ); ++ } ++ } ++ } ++ ++ for (mr_index, log_set) in event_logs_by_mr_index.iter() { ++ let mut mr_value = [0; RTMR_LENGTH_BY_BYTES]; ++ ++ for log in log_set.iter() { ++ let digest = &log.digests[0].digest; ++ let mut sha_algo = Sha256::new(); ++ sha_algo.update(&mr_value); ++ sha_algo.update(digest.as_slice()); ++ mr_value.copy_from_slice(sha_algo.finalize().as_slice()); ++ } ++ result.insert(mr_index.clone(), mr_value.to_vec()); ++ } ++ ++ result ++ } ++} ++ ++impl TryFrom> for Eventlog { ++ type Error = anyhow::Error; ++ ++ fn try_from(data: Vec) -> Result { ++ let mut index = 0; ++ let mut event_log: Vec = Vec::new(); ++ let mut digest_size_map: HashMap = HashMap::new(); ++ ++ while index < data.len() as usize { ++ let stop_flag = (&data[index..(index + 8)]).read_u64::()?; ++ let target_measurement_registry = ++ (&data[index..(index + 4)]).read_u32::()?; ++ index += 4; ++ ++ let event_type_num = (&data[index..(index + 4)]).read_u32::()?; ++ index += 4; ++ let event_type = match EVENTLOG_TYPES.get(&event_type_num) { ++ Some(type_name) => type_name.to_string(), ++ None => format!("UNKNOWN_TYPE: {:x}", &event_type_num), ++ }; ++ ++ let event_type_id = event_type_num; ++ if event_type == "EV_NO_ACTION".to_string() { ++ index += 48; ++ let algo_number = (&data[index..(index + 4)]).read_u32::()?; ++ index += 4; ++ for _ in 0..algo_number { ++ digest_size_map.insert( ++ (&data[index..(index + 2)]).read_u16::()?, ++ (&data[(index + 2)..(index + 4)]).read_u16::()?, ++ ); ++ index += 4; ++ } ++ let vendor_size = data[index]; ++ index += vendor_size as usize + 1; ++ continue; ++ } ++ ++ if stop_flag == 0xFFFFFFFFFFFFFFFF || stop_flag == 0x0000000000000000 { ++ break; ++ } ++ ++ let digest_count = (&data[index..(index + 4)]).read_u32::()?; ++ index += 4; ++ let mut digests: Vec = Vec::new(); ++ for _ in 0..digest_count { ++ let digest_algo_num = (&data[index..(index + 2)]).read_u16::()?; ++ index += 2; ++ let algorithm = match TCG_ALGORITHMS.get(&digest_algo_num) { ++ Some(digest_algo_name) => digest_algo_name.to_string(), ++ None => format!("UNKNOWN_ALGORITHM: {:x}", &digest_algo_num), ++ }; ++ let digest_size = digest_size_map ++ .get(&digest_algo_num) ++ .ok_or(anyhow!( ++ "Internal Error: get digest size failed when parse eventlog entry, digest_algo_num: {:?}", &digest_algo_num ++ ))? ++ .to_owned() as usize; ++ let digest = data[index..(index + digest_size)].to_vec(); ++ index += digest_size; ++ digests.push(ElDigest { algorithm, digest }); ++ } ++ ++ let event_desc_size = (&data[index..(index + 4)]).read_u32::()? as usize; ++ index += 4; ++ let event_desc = data[index..(index + event_desc_size)].to_vec(); ++ index += event_desc_size; ++ ++ let eventlog_entry = EventlogEntry { ++ target_measurement_registry, ++ event_type_id, ++ event_type, ++ digests, ++ event_desc, ++ }; ++ ++ event_log.push(eventlog_entry) ++ } ++ ++ Ok(Eventlog { log: event_log }) ++ } ++} +diff --git a/service/attestation/attestation-service/verifier/eventlog-rs/src/read.rs b/service/attestation/attestation-service/verifier/eventlog-rs/src/read.rs +new file mode 100644 +index 0000000..bbc7dd8 +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/eventlog-rs/src/read.rs +@@ -0,0 +1,111 @@ ++// Copyright (c) 2024 Alibaba Cloud ++// ++// SPDX-License-Identifier: Apache-2.0 ++// ++ ++use anyhow::{bail, Context, Result}; ++use byteorder::{LittleEndian, ReadBytesExt}; ++ ++use std::{ ++ fs, ++ io::{Read, Seek}, ++ path::Path, ++}; ++ ++pub const CCEL_PATH: &str = "/sys/firmware/acpi/tables/data/CCEL"; ++ ++/// Path to the ACPI table CCEL description ++pub const CCEL_ACPI_DESCRIPTION: &str = "/sys/firmware/acpi/tables/CCEL"; ++ ++/// Guest memory which is used to read the CCEL ++pub const GUEST_MEMORY: &str = "/dev/mem"; ++ ++/// Signature of CCEL's ACPI Description Header ++pub const CCEL_SIGNATURE: &[u8] = b"CCEL"; ++ ++/// Try to read CCEL from either ACPI data or guest memory ++/// ++/// If read from guest memory, the offset and length will be read from the CCEL ACPI description. ++/// defined as ++/// ```no-run ++/// pub struct EfiAcpiDescriptionHeader { ++/// signature: u32, ++/// length: u32, ++/// revision: u8, ++/// checksum: u8, ++/// oem_id: [u8; 6], ++/// oem_table_id: u64, ++/// oem_revision: u32, ++/// creator_id: u32, ++/// creator_revision: u32, ++/// } ++/// ++/// pub struct TdxEventLogACPITable { ++/// efi_acpi_description_header: EfiAcpiDescriptionHeader, ++/// rsv: u32, ++/// laml: u64, ++/// lasa: u64, ++/// } ++/// ``` ++pub fn read_ccel() -> Result> { ++ if Path::new(CCEL_PATH).exists() { ++ let ccel = fs::read(CCEL_PATH)?; ++ return Ok(ccel); ++ } ++ ++ let efi_acpi_description = ++ fs::read(CCEL_ACPI_DESCRIPTION).context("ccel description does not exist")?; ++ if efi_acpi_description.len() < 56 { ++ bail!("invalid CCEL ACPI description"); ++ } ++ ++ let mut index = 0; ++ ++ let signature = (&efi_acpi_description[index..index + 4]).read_u32::()?; ++ index += 4; ++ ++ let length = (&efi_acpi_description[index..index + 4]).read_u32::()?; ++ index += 32; ++ ++ let rsv = (&efi_acpi_description[index..index + 4]).read_u32::()?; ++ index += 4; ++ ++ let laml = (&efi_acpi_description[index..index + 8]).read_u64::()?; ++ index += 8; ++ ++ let lasa = (&efi_acpi_description[index..index + 8]).read_u64::()?; ++ ++ let ccel_signature = u32::from_le_bytes(CCEL_SIGNATURE.try_into()?); ++ if signature != ccel_signature { ++ bail!("invalid CCEL ACPI table: wrong CCEL signature"); ++ } ++ ++ if rsv != 0 { ++ bail!("invalid CCEL ACPI table: RSV must be 0"); ++ } ++ ++ if length != efi_acpi_description.len() as u32 { ++ bail!("invalid CCEL ACPI table: header length not match"); ++ } ++ ++ let mut guest_memory = fs::OpenOptions::new().read(true).open(GUEST_MEMORY)?; ++ guest_memory.seek(std::io::SeekFrom::Start(lasa))?; ++ let mut ccel = vec![0; laml as usize]; ++ let read_size = guest_memory.read(&mut ccel)?; ++ if read_size == 0 { ++ bail!("read CCEL failed"); ++ } ++ ++ Ok(ccel) ++} ++ ++#[cfg(test)] ++mod tests { ++ use super::read_ccel; ++ ++ #[ignore] ++ #[test] ++ fn test_read_ccel() { ++ let _ccel = read_ccel().unwrap(); ++ } ++} +diff --git a/service/attestation/attestation-service/verifier/src/lib.rs b/service/attestation/attestation-service/verifier/src/lib.rs +index 43894db..c2ef3bc 100644 +--- a/service/attestation/attestation-service/verifier/src/lib.rs ++++ b/service/attestation/attestation-service/verifier/src/lib.rs +@@ -16,7 +16,6 @@ + + use anyhow::*; + use async_trait::async_trait; +-use serde_json; + + use attestation_types::{Evidence, TeeType}; + +diff --git a/service/attestation/attestation-service/verifier/src/virtcca/ima.rs b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs +index 220a52d..271c9d2 100644 +--- a/service/attestation/attestation-service/verifier/src/virtcca/ima.rs ++++ b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs +@@ -9,6 +9,7 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ ++use super::{file_reader, CVM_REM_ARR_SIZE}; + use anyhow::{bail, Result}; + use fallible_iterator::FallibleIterator; + use ima_measurements::{Event, EventData, Parser}; +@@ -27,7 +28,7 @@ const IMA_REFERENCE_FILE: &str = + pub struct ImaVerify {} + + impl ImaVerify { +- pub fn ima_verify(&self, ima_log: &[u8], ima_log_hash: Vec) -> Result { ++ pub fn ima_verify(&self, ima_log: &[u8], cvm_rem: &[Vec; CVM_REM_ARR_SIZE]) -> Result { + if ima_log.to_vec().is_empty() { + return Ok(json!({})); + } +@@ -38,10 +39,16 @@ impl ImaVerify { + events.push(event); + } + ++ let pcr_index = events[1].pcr_index; ++ let ima_index : usize = match (pcr_index-1).try_into() { ++ Ok(idx) => idx, ++ Err(_) => bail!("Invalid pcr_index for IMA"), ++ }; + let pcr_values = parser.pcr_values(); +- let pcr_10 = pcr_values.get(&10).expect("PCR 10 not measured"); +- let string_pcr_sha256 = hex::encode(pcr_10.sha256); +- let string_ima_log_hash = hex::encode(ima_log_hash); ++ let pcr_value = pcr_values.get(&pcr_index).expect("PCR not measured"); ++ let string_pcr_sha256 = hex::encode(pcr_value.sha256); ++ let string_ima_log_hash = hex::encode(cvm_rem[ima_index].clone()); ++ log::debug!("pcr_index: {}, string_pcr_sha256: {}, string_ima_log_hash: {}",pcr_index, string_pcr_sha256, string_ima_log_hash); + + if string_pcr_sha256.clone() != string_ima_log_hash { + log::error!( +@@ -49,13 +56,10 @@ impl ImaVerify { + string_pcr_sha256, + string_ima_log_hash + ); +- bail!("ima log hash verify failed"); ++ bail!("IMA log hash verification failed. Please check the log and reference data, and verify if PCR has been extended to PCR4."); + } + +- let ima_refs: Vec<_> = file_reader(IMA_REFERENCE_FILE)? +- .into_iter() +- .map(String::from) +- .collect(); ++ let ima_refs = file_reader(IMA_REFERENCE_FILE)?; + + let mut ima_detail = Map::new(); + // parser each file digest in ima log, and compare with reference base value +@@ -84,23 +88,3 @@ impl ImaVerify { + Ok(js_ima_detail) + } + } +- +-use std::io::BufRead; +-use std::io::BufReader; +-fn file_reader(file_path: &str) -> ::std::io::Result> { +- let file = std::fs::File::open(file_path).expect("open ima reference file failed"); +- let mut strings = Vec::::new(); +- let mut reader = BufReader::new(file); +- let mut buf = String::new(); +- let mut n: usize; +- loop { +- n = reader.read_line(&mut buf)?; +- if n == 0 { +- break; +- } +- buf.pop(); +- strings.push(buf.clone()); +- buf.clear(); +- } +- Ok(strings) +-} +diff --git a/service/attestation/attestation-service/verifier/src/virtcca/mod.rs b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs +index ff72f77..a8c0959 100644 +--- a/service/attestation/attestation-service/verifier/src/virtcca/mod.rs ++++ b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs +@@ -25,8 +25,9 @@ use openssl::rsa; + use openssl::x509; + use serde_json::json; + +-pub use attestation_types::VirtccaEvidence; ++pub use attestation_types::{UefiLog, VirtccaEvidence}; + pub mod ima; ++pub mod uefi; + + #[cfg(not(feature = "no_as"))] + const VIRTCCA_ROOT_CERT: &str = +@@ -125,29 +126,62 @@ impl Evidence { + + // verify ima + let ima_log = match virtcca_ev.ima_log { +- Some(ima_log) => ima_log, +- _ => { ++ Some(ima_log) => { ++ log::info!("get ima log"); ++ ima_log ++ } ++ None => { + log::info!("no ima log"); + vec![] + } + }; ++ + let ima: serde_json::Value = +- ima::ImaVerify::default().ima_verify(&ima_log, evidence.cvm_token.rem[0].clone())?; ++ ima::ImaVerify::default().ima_verify(&ima_log, &evidence.cvm_token.rem)?; ++ ++ // verify uefi ++ let uefi_log = if let Some(uefi_log) = virtcca_ev.uefi_log { ++ if !uefi_log.ccel_table.is_empty() && !uefi_log.ccel_data.is_empty() { ++ log::info!("get valid uefi log"); ++ uefi_log ++ } else { ++ log::info!("uefi log is invalid (empty fields)"); ++ UefiLog { ++ ccel_table: vec![], ++ ccel_data: vec![], ++ } ++ } ++ } else { ++ log::info!("no uefi log at all"); ++ UefiLog { ++ ccel_table: vec![], ++ ccel_data: vec![], ++ } ++ }; ++ ++ let uefi: serde_json::Value = ++ uefi::UefiVerify::default().uefi_verify(uefi_log, evidence.cvm_token.rem.clone())?; + + // todo parsed TeeClaim +- evidence.parse_claim_from_evidence(ima) ++ evidence.parse_claim_from_evidence(ima, uefi) + } ++ + pub fn parse_evidence(evidence: &[u8]) -> Result { + let virtcca_ev: VirtccaEvidence = serde_json::from_slice(evidence)?; + let evidence = virtcca_ev.evidence; + let evidence = Evidence::decode(evidence)?; + + let ima = json!(""); ++ let uefi = json!(""); + // parsed TeeClaim +- let claim = evidence.parse_claim_from_evidence(ima).unwrap(); ++ let claim = evidence.parse_claim_from_evidence(ima, uefi).unwrap(); + Ok(claim["payload"].clone() as TeeClaim) + } +- fn parse_claim_from_evidence(&self, ima: serde_json::Value) -> Result { ++ fn parse_claim_from_evidence( ++ &self, ++ ima: serde_json::Value, ++ uefi: serde_json::Value, ++ ) -> Result { + let payload = json!({ + "vcca.cvm.challenge": hex::encode(self.cvm_token.challenge.clone()), + "vcca.cvm.rpv": hex::encode(self.cvm_token.rpv.clone()), +@@ -162,6 +196,7 @@ impl Evidence { + "tee": "vcca", + "payload" : payload, + "ima": ima, ++ "uefi": uefi, + }); + Ok(claim as TeeClaim) + } +@@ -242,6 +277,7 @@ impl Evidence { + + Ok(()) + } ++ + #[cfg(feature = "no_as")] + fn compare_with_ref(&mut self) -> Result<()> { + let ref_file = std::fs::read(VIRTCCA_REF_VALUE_FILE)?; +@@ -499,6 +535,7 @@ mod tests { + evidence: token.to_vec(), + dev_cert: dev_cert, + ima_log: None, ++ uefi_log: None, + }; + let virtcca_ev = serde_json::to_vec(&virtcca_ev).unwrap(); + let r = Evidence::verify(&challenge, &virtcca_ev); +@@ -508,3 +545,20 @@ mod tests { + } + } + } ++ ++use std::collections::HashSet; ++use std::fs::File; ++use std::io::{self, BufRead, BufReader}; ++ ++pub fn file_reader(file_path: &str) -> io::Result> { ++ let file = File::open(file_path)?; ++ let reader = BufReader::new(file); ++ let mut set = HashSet::new(); ++ ++ for line in reader.lines() { ++ let line = line?; ++ set.insert(line.trim_end().to_string()); ++ } ++ ++ Ok(set) ++} +diff --git a/service/attestation/attestation-service/verifier/src/virtcca/uefi.rs b/service/attestation/attestation-service/verifier/src/virtcca/uefi.rs +new file mode 100644 +index 0000000..e2397ae +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/virtcca/uefi.rs +@@ -0,0 +1,209 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++*/ ++ ++use super::{file_reader, CVM_REM_ARR_SIZE}; ++use anyhow::{bail, Result}; ++use attestation_types::UefiLog; ++use eventlog_rs::{self, Eventlog}; ++use hex; ++use log; ++use serde_json::{json, Map, Value}; ++use std::collections::{HashMap, HashSet}; ++use std::convert::TryFrom; ++ ++#[cfg(not(feature = "no_as"))] ++const UEFI_REFERENCE_FILE: &str = ++ "/etc/attestation/attestation-service/verifier/virtcca/uefi/digest_list_file"; ++ ++// attestation agent local uefi reference ++#[cfg(feature = "no_as")] ++const UEFI_REFERENCE_FILE: &str = ++ "/etc/attestation/attestation-agent/local_verifier/virtcca/uefi/digest_list_file"; ++ ++#[derive(Debug)] ++pub struct FirmwareState { ++ pub grub_image_count: u8, ++ pub grub_image_list: Vec, ++ pub state_hash: HashMap, ++} ++ ++#[derive(Debug, Default)] ++pub struct UefiVerify {} ++ ++impl UefiVerify { ++ pub fn compare_rtmr_with_uefi_log( ++ _replayed_rtmr: &HashMap>, ++ uefi_log_hash: &[Vec; CVM_REM_ARR_SIZE], ++ ) -> bool { ++ for i in 1..CVM_REM_ARR_SIZE as u32 { ++ let index = i as usize - 1; ++ ++ // 获取 RTMR 值 ++ let Some(rtmr_value) = _replayed_rtmr.get(&i) else { ++ log::error!("RTMR[{}] not found in hashmap", i); ++ return false; ++ }; ++ ++ // 检查长度是否为 SHA256 (32 bytes) ++ if rtmr_value.len() != 32 { ++ log::error!("RTMR[{}] hash length invalid: {}", i, rtmr_value.len()); ++ return false; ++ } ++ ++ if uefi_log_hash[index].len() != 32 { ++ log::error!( ++ "UEFI_LOG_HASH[{}] hash length invalid: {}", ++ i, ++ uefi_log_hash[index].len() ++ ); ++ return false; ++ } ++ ++ // 如果哈希不匹配,记录详细信息 ++ if rtmr_value != &uefi_log_hash[index] { ++ log::error!("RTMR[{}] and UEFI_LOG_HASH[{}] do not match.", i, index); ++ log::debug!("RTMR[{}] = {}", i, hex::encode(rtmr_value)); ++ log::debug!( ++ "UEFI_LOG_HASH[{}] = {}", ++ index, ++ hex::encode(&uefi_log_hash[index]) ++ ); ++ return false; ++ } ++ ++ log::debug!("RTMR[{}] = {}", i, hex::encode(rtmr_value)); ++ log::debug!( ++ "UEFI_LOG_HASH[{}] = {}", ++ index, ++ hex::encode(&uefi_log_hash[index]) ++ ); ++ } ++ ++ log::info!("All RTMR values match UEFI log hashes."); ++ true ++ } ++ ++ pub fn firmware_log_state(event_log: &Eventlog) -> FirmwareState { ++ let grub_event_type: &str = "EV_EFI_BOOT_SERVICES_APPLICATION"; ++ let exclude_event_descs: &[&str] = &["grub_cmd:"]; ++ let event_descs: HashMap<_, _> = HashMap::from([ ++ ("grub_cfg", "grub.cfg"), ++ ("kernel", "/vmlinuz-"), ++ ("initramfs", "/initramfs-"), ++ ]); ++ ++ let mut state = FirmwareState { ++ grub_image_count: 0, ++ grub_image_list: vec![], ++ state_hash: HashMap::new(), ++ }; ++ ++ //based event_type get grub image info ++ for event_entry in event_log.log.iter() { ++ if event_entry.event_type == grub_event_type { ++ state.grub_image_count += 1; ++ state ++ .grub_image_list ++ .push(hex::encode(event_entry.digests[0].digest.clone())); ++ } else { ++ let event_desc = match std::str::from_utf8(&event_entry.event_desc) { ++ Ok(s) => s, ++ Err(_) => continue, ++ }; ++ ++ if exclude_event_descs.iter().any(|&s| event_desc.contains(s)) { ++ continue; ++ } ++ ++ for (&key, &pattern) in event_descs.iter() { ++ if state.state_hash.contains_key(key) { ++ continue; ++ } ++ ++ if event_desc.contains(pattern) { ++ state.state_hash.insert( ++ key.to_string(), ++ hex::encode(event_entry.digests[0].digest.clone()), ++ ); ++ } ++ } ++ ++ if state.state_hash.len() == event_descs.len() { ++ break; ++ } ++ } ++ } ++ ++ state ++ } ++ ++ pub fn check_uefi_references( ++ firmware_state: &FirmwareState, ++ uefi_refs: &HashSet, ++ ) -> serde_json::Value { ++ let mut uefi_detail: Map = Map::new(); ++ ++ for (index, image) in firmware_state.grub_image_list.iter().enumerate() { ++ let exists = uefi_refs.contains(image); ++ let key = format!("Image[{}]", index); ++ uefi_detail.insert(key, Value::Bool(exists)); ++ if !exists { ++ log::debug!( ++ "GRUB Image[{}] ('{}') not found in UEFI reference set.", ++ index, ++ image ++ ); ++ } ++ } ++ ++ for (key, value) in firmware_state.state_hash.iter() { ++ let exists = uefi_refs.contains(value); ++ uefi_detail.insert(key.clone(), Value::Bool(exists)); ++ if !exists { ++ log::debug!("'{}' : '{}' not found in UEFI reference set.", key, value); ++ } ++ } ++ let js_uefi_detail: Value = uefi_detail.into(); ++ log::debug!("uefi event verify detail result: {:?}", js_uefi_detail); ++ log::info!("uefi event verify Finished"); ++ js_uefi_detail ++ } ++ ++ pub fn uefi_verify( ++ &self, ++ uefi_log: UefiLog, ++ uefi_log_hash: [Vec; CVM_REM_ARR_SIZE], ++ ) -> Result { ++ if uefi_log.ccel_data.is_empty() { ++ return Ok(json!({})); ++ } ++ ++ let event_log = eventlog_rs::Eventlog::try_from(uefi_log.ccel_data).unwrap(); ++ let _replayed_rtmr = event_log.replay_measurement_registry(); ++ ++ if !UefiVerify::compare_rtmr_with_uefi_log(&_replayed_rtmr, &uefi_log_hash) { ++ log::error!("uefi log hash verify failed"); ++ bail!("uefi log hash verify failed"); ++ } ++ ++ let uefi_refs = file_reader(UEFI_REFERENCE_FILE)?; ++ log::debug!("uefi reference file: {:?}", uefi_refs); ++ ++ let firmware_state: FirmwareState = UefiVerify::firmware_log_state(&event_log); ++ log::debug!("firmware state: {:?}", firmware_state); ++ ++ Ok(UefiVerify::check_uefi_references( ++ &firmware_state, ++ &uefi_refs, ++ )) ++ } ++} +diff --git a/service/attestation/attestation-types/src/lib.rs b/service/attestation/attestation-types/src/lib.rs +index 82c124c..5f3d43b 100644 +--- a/service/attestation/attestation-types/src/lib.rs ++++ b/service/attestation/attestation-types/src/lib.rs +@@ -18,11 +18,18 @@ use serde_json::Value; + + pub const SESSION_TIMEOUT_MIN: i64 = 1; + ++#[derive(Debug, Serialize, Deserialize)] ++pub struct UefiLog { ++ pub ccel_table: Vec, ++ pub ccel_data: Vec, ++} ++ + #[derive(Debug, Serialize, Deserialize)] + pub struct VirtccaEvidence { + pub evidence: Vec, + pub dev_cert: Vec, + pub ima_log: Option>, ++ pub uefi_log: Option, + } + + #[derive(Debug, Serialize, Deserialize)] +diff --git a/service/attestation/attestation-types/src/resource/policy/opa/mod.rs b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +index d702061..be8c4f9 100644 +--- a/service/attestation/attestation-types/src/resource/policy/opa/mod.rs ++++ b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +@@ -329,6 +329,7 @@ mod tests { + "report": { + "default_vcca.rego": "{\"vcca.cvm.rim\":\"1ee366339c8245a34a8ad9d27a0b912a588af7da8aef514ae8dec22746956dd1\"}", + "ima": {} ++ "uefi": {} + } + }, + "tee": "vcca", +-- +2.49.0.windows.1 + diff --git a/secGear.spec b/secGear.spec index 6930488507d2f7ad2f8f4b291eadd5a14340265b..203b4fef8ba4931c7e63ef2b0341ae7b98966dab 100644 --- a/secGear.spec +++ b/secGear.spec @@ -1,6 +1,6 @@ Name: secGear Version: 0.1.0 -Release: 38 +Release: 60 Summary: secGear is an SDK to develop confidential computing apps based on hardware enclave features @@ -8,6 +8,7 @@ Group: OS Security License: MulanPSL-2.0 URL: https://gitee.com/openeuler/secGear Source0: https://gitee.com/openeuler/secGear/repository/archive/v%{version}.tar.gz +Source1: vendor.tar.gz Patch0: 0001-add-README.cn.md.patch Patch1: 0002-it-is-better-to-define-enum-from-0-rather-than-1.patch @@ -80,6 +81,37 @@ Patch67: 0068-bugfix-when-input-empty-hash.patch Patch68: 0069-adapt-sign-tool-to-pass-API_LEVEL.patch Patch69: 0070-sign-tool-add-invalid-param-verify.patch Patch70: 0071-adapt-report-with-request-key.patch +Patch71: 0072-use-memset-instead-of-explicit_bzero.patch +Patch72: 0073-memset-no-optimize.patch +Patch73: 0074-add-codegen-compile-marco.patch +Patch74: 0075-Correct-the-error-in-the-comment.patch +Patch75: 0076-change-log-file-permission-0400.patch +Patch76: 0077-support-CPU-core-binding.patch +Patch77: 0078-register-shared-memory-by-open-session.patch +Patch78: 0079-Optimize-the-registration-shared-memory.patch +Patch79: 0080-add-attestation-service.patch +Patch80: 0081-modify-default-agent-config.patch +Patch81: 0082-optimize-ima-verify.patch +Patch82: 0083-optimize-log-level.patch +Patch83: 0084-fix-concurrent-request-error-to-aa-or-as.patch +Patch84: 0085-fix-multi-thread-request-as-generate-challenge-and-v.patch +Patch85: 0086-add-error-type-for-api.patch +Patch86: 0087-use-id-when-get-policy.patch +Patch87: 0088-fix-evidence-decode-typos.patch +Patch88: 0089-features-support-resource-maitainance.patch +Patch89: 0090-add-ra_tls-support.patch +Patch90: 0091-fix-allow-bind-unbind-and-clear-resource-policy-when.patch +Patch91: 0092-fix-use-etc-as-the-base-directory-for-resource-stora.patch +Patch92: 0093-fix-raise-formatted-error-message-if-http-error-happ.patch +Patch93: 0094-fix-use-default-vendor-if-it-is-not-set-or-is-empty.patch +Patch94: 0095-fix-abort-binding-policies-if-any-policy-location-is.patch +Patch95: 0096-fix-use-default-vendor-for-resource-policy-if-it-is-.patch +Patch96: 0097-fix-fix-incorrect-error-message-when-modifying-a-non.patch +Patch97: 0098-fix-avoid-clear-resource-policies-if-illegal-vendor-.patch +Patch98: 0099-fix-permit-dots-in-the-resource-policy-id.patch +Patch99: 0100-challenge-may-generate-by-requester-so-aa-and-as-may.patch +Patch100: 0101-generate-random-by-ra_tls-itself.patch +Patch101: 0102-Add-support-for-UEFI-measured-boot-attestation.patch BuildRequires: gcc python automake autoconf libtool BUildRequires: glibc glibc-devel cmake ocaml-dune rpm gcc-c++ compat-openssl11-libs compat-openssl11-devel @@ -87,6 +119,7 @@ BUildRequires: glibc glibc-devel cmake ocaml-dune rpm gcc-c++ compat-openssl11-l BUildRequires: sgxsdk libsgx-launch libsgx-urts intel-sgx-ssl-devel %else BUildRequires: itrustee_sdk itrustee_sdk-devel +BuildRequires: rust cargo rust-packaging virtCCA_sdk-devel virtCCA_sdk kunpengsecl-attester %endif Requires: rsyslog compat-openssl11-libs @@ -117,10 +150,70 @@ Summary: simulation package files for %{name} Requires: %{name}%{?isa} = %{version}-%{release} %description sim The %{name}-sim is package contains simulation libraries for developing applications +%else +%package aa +Summary: Attestation agent for %{name} +%description aa +The %{name}-aa is package contains attestation agent + +%define _cargo /usr/bin/env CARGO_HOME=.cargo RUSTC_BOOTSTRAP=1 /usr/bin/cargo + +%package as +Summary: Attestation Service for %{name} +Requires: kunpengsecl-attester +%description as +The %{name}-as is package contains attestation service + +%package ac +Summary: Attestation Client for %{name} +%description ac +The %{name}-ac provides command line tool for attestation service. %endif +%package ra_tls +Summary: RA_TLS for %{name} +Requires: cjson curl compat-openssl11-libs +BUildRequires: libcurl-devel cjson-devel compat-openssl11-devel +%description ra_tls +The %{name}-ra_tls package support tls depend on remote attestation + +%package ra_tls-devel +Summary: Development files for %{name}-ra_tls-devel +Requires: libcurl-devel cjson-devel compat-openssl11-devel +%description ra_tls-devel +The %{name}-ra_tls-devel contains Header file for using RA_TLS + %prep %autosetup -n %{name} -p1 +%ifnarch x86_64 +cd %{_builddir}/%{name}/service/attestation/attestation-agent/ +tar xf %{SOURCE1} + +mkdir -p .cargo +touch .cargo/config.toml +cat << EOF >> ./.cargo/config.toml +[source.crates-io] +replace-with = "vendored-sources" + +[source.vendored-sources] +directory = "vendor" +EOF + +cd %{_builddir}/%{name}/service/attestation/attestation-service/ +tar xf %{SOURCE1} +mkdir -p .cargo +cp %{_builddir}/%{name}/service/attestation/attestation-agent/.cargo/config.toml .cargo/ + +cd %{_builddir}/%{name}/service/attestation/attestation-client/ +tar xf %{SOURCE1} +mkdir -p .cargo/ +cp %{_builddir}/%{name}/service/attestation/attestation-agent/.cargo/config.toml .cargo/ + +cd %{_builddir}/%{name}/service/attestation/attestation-types/ +tar xf %{SOURCE1} +mkdir -p .cargo/ +cp %{_builddir}/%{name}/service/attestation/attestation-agent/.cargo/config.toml .cargo/ +%endif %build source ./environment @@ -131,6 +224,13 @@ make %else cmake -DCMAKE_BUILD_TYPE=Debug -DENCLAVE=GP make + +cd %{_builddir}/%{name}/service/attestation/attestation-agent/ +%{_cargo} build --features virtcca-attester --bins --release +cd %{_builddir}/%{name}/service/attestation/attestation-service/ +%{_cargo} build --bins --release +cd %{_builddir}/%{name}/service/attestation/attestation-client/ +%{_cargo} build --bins --release %endif %install @@ -160,12 +260,27 @@ install -pm 644 inc/host_inc/gp/*.edl %{buildroot}/%{_includedir}/secGear install -pm 644 inc/enclave_inc/*.h %{buildroot}/%{_includedir}/secGear install -pm 644 inc/enclave_inc/gp/*.h %{buildroot}/%{_includedir}/secGear install -pm 644 inc/enclave_inc/gp/itrustee/*.h %{buildroot}/%{_includedir}/secGear +install -pm 751 service/attestation/attestation-agent/target/release/attestation-agent %{buildroot}/%{_bindir} +install -d %{buildroot}%{_sysconfdir}/attestation/attestation-agent/ +install -pm 644 service/attestation/attestation-agent/agent/attestation-agent.conf %{buildroot}%{_sysconfdir}/attestation/attestation-agent/ + +install -d %{buildroot}%{_sysconfdir}/attestation/attestation-service/token +install -d %{buildroot}%{_sysconfdir}/attestation/attestation-service/policy +install -d %{buildroot}%{_sysconfdir}/attestation/attestation-service/verifier/itrustee +install -d %{buildroot}%{_sysconfdir}/attestation/attestation-service/verifier/virtcca +install -d %{buildroot}%{_sysconfdir}/attestation/attestation-service/resource/policy/oeas +install -pm 644 service/attestation/attestation-service/service/attestation-service.conf %{buildroot}%{_sysconfdir}/attestation/attestation-service/ +install -pm 644 service/attestation/attestation-service/policy/src/opa/*.rego %{buildroot}%{_sysconfdir}/attestation/attestation-service/policy/ +install -pm 644 service/attestation/attestation-types/src/resource/policy/opa/virtcca.rego %{buildroot}%{_sysconfdir}/attestation/attestation-service/resource/policy/oeas/ +install -pm 751 service/attestation/attestation-service/target/release/attestation-service %{buildroot}/%{_bindir} +install -pm 751 service/attestation/attestation-client/target/release/attestation-client %{buildroot}/%{_bindir} %endif install -pm 644 component/remote_attest/ra_report/sg_ra_report.h %{buildroot}/%{_includedir}/secGear install -pm 644 component/remote_attest/ra_verify/sg_ra_report_verify.h %{buildroot}/%{_includedir}/secGear install -pm 644 component/remote_attest/sg_report_st.h %{buildroot}/%{_includedir}/secGear install -pm 644 component/local_attest/sg_local_attest.h %{buildroot}/%{_includedir}/secGear + pushd %{buildroot} rm `find . -name secgear_helloworld` -rf rm `find . -name secgear_seal_data` -rf @@ -197,7 +312,11 @@ popd %files devel %{_bindir}/* +%exclude %{_bindir}/attestation-agent +%exclude %{_bindir}/attestation-service +%exclude %{_bindir}/attestation-client %{_includedir}/secGear/* +%exclude %{_includedir}/secGear/ra_tls* %ifarch x86_64 %files sim @@ -205,12 +324,103 @@ popd %license License/LICENSE %{_libdir}/libsecgearsim.so %{_libdir}/libsgxsim_0.so +%else + +%files aa +%{_bindir}/attestation-agent +%config(noreplace) %attr(0600,root,root) %{_sysconfdir}/attestation/attestation-agent/attestation-agent.conf + +%files as +%{_bindir}/attestation-service +%config(noreplace) %attr(0600,root,root) %{_sysconfdir}/attestation/attestation-service/attestation-service.conf +%{_sysconfdir}/attestation/attestation-service/token +%{_sysconfdir}/attestation/attestation-service/policy/* +%{_sysconfdir}/attestation/attestation-service/verifier/itrustee +%{_sysconfdir}/attestation/attestation-service/verifier/virtcca +%{_sysconfdir}/attestation/attestation-service/resource/policy/oeas/virtcca.rego + +%files ac +%{_bindir}/attestation-client + %endif +%files ra_tls +%{_libdir}/libra_tls.so + +%files ra_tls-devel +%{_includedir}/secGear/ra_tls* + %post systemctl restart rsyslog %changelog +* Wed May 14 2025 SPYFAMILY - 0.1.0-60 +- Add support for UEFI measured boot attestation + +* Thu Mar 13 2025 xuraoqing - 0.1.0-59 +- cargo config.toml generation move to prep stage + +* Tue Mar 11 2025 houmingyong - 0.1.0-58 +- fix ra_tls verify failed while use evidence + +* Mon Mar 10 2025 chenjiayi - 0.1.0-57 +- fix permit dots in the resource policy id + +* Tue Mar 4 2025 ExtinctFire - 0.1.0-56 +- fix several bugs. + +* Wed Feb 19 2025 chenjiayi - 0.1.0-55 +- fix bugs on resource policy binding, unbindg, etc. Also install +- default resource policy. + +* Tue Feb 18 2025 xuraoqing - 0.1.0-54 +- add ra_tls support + +* Sat Feb 15 2025 chenjiayi - 0.1.0-53 +- support resource maintainance + +* Tue Nov 26 2024 houmingyong - 0.1.0-52 +- fix evidence decode typos + +* Tue Nov 26 2024 houmingyong - 0.1.0-51 +- get policy with id + +* Tue Nov 26 2024 houmingyong - 0.1.0-50 +- add error type for api + +* Tue Nov 26 2024 houmingyong - 0.1.0-49 +- fix multi thread request-as generate challenge + +* Tue Nov 26 2024 houmingyong - 0.1.0-48 +- fix concurrent request error to aa or as + +* Fri Nov 8 2024 houmingyong - 0.1.0-47 +- remove attestation-agent and attestation-service from devel + +* Fri Nov 8 2024 houmingyong - 0.1.0-46 +- add secGear-aa secGear-as package + +* Fri Nov 8 2024 steven - 0.1.0-45 +- backport patches from 2409 + +* Fri Nov 8 2024 steven - 0.1.0-44 +- backport patches from 2409 + +* Fri Oct 25 2024 houmingyong - 0.1.0-43 +- add rust third party dependencies + +* Thu Sep 19 2024 houmingyong - 0.1.0-42 +- register shared memory by open session + +* Thu Aug 15 2024 zhengxiaoxiao - 0.1.0-41 +- support CPU core binding + +* Thu Jun 20 2024 houmingyong - 0.1.0-40 +- synchoronous features + +* Wed Mar 27 2024 zhengxiaoxiao - 0.1.0-39 +- use memset instead of explicit_bzero + * Wed Sep 13 2023 wangqingsan - 0.1.0-38 - synchronous features diff --git a/vendor.tar.gz b/vendor.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..2af6924a64ea50fd8f9624d816255864a166981f Binary files /dev/null and b/vendor.tar.gz differ