文章目录
- 一.LocalAttestation原理介绍
- 1.1本地认证原理
- 1.2 本地认证基本流程
- 1.3 本地认证核心原理
- 二.源码分析
- 2.1 README
- 2.1.1 编译流程
- 2.1.2 执行流程(双进程执行 or 单进程执行,在后面执行部分有展示效果)
- 2.1.3 如何获取已签名的Enclave的MRSIGNER
- 2.2 重点代码分析
- 2.2.1 App文件夹
- 2.2.1.1 App/App.cpp
- 2.2.1.2 App/UntrustedEnclaveMessageExchange.cpp
- 2.2.2 AppInitiator文件夹
- 2.2.2.1 AppInitiator/App.cpp
- 2.2.2.2 AppInitiator/UntrustedEnclaveMessageExchange.cpp
- 2.2.3 AppResponder文件夹
- 2.2.3.1 AppResponder/App.cpp
- 2.2.3.2 AppResponder/CPServer.cpp
- 2.2.3.3 AppResponder/CPTask.cpp
- 2.2.4 EnclaveInitiator文件夹
- 2.2.5 EnclaveResponder文件夹
- 2.3 编译执行
- 2.3.1 Makefile文件分析
- 2.3.2 执行
- 2.4 总结
- 三.参考文献
- 四.感谢支持
下面将给出一些sgx源码包中的示例LocalAttestation
分析,从中学习本地认证和SGX的基本使用方法:关于 SGX 开发运行环境的搭建可参考之前的一篇博客:【SGX系列教程】(一)。
一.LocalAttestation原理介绍
1.1本地认证原理
本地认证是一种允许两个在同一台机器上运行的SGX实例(Enclave)相互认证(也就是一台机器上的两个enclave
互相认证),确保它们都在可信环境中运行,并且它们的签名和测量值(MRENCLAVE和MRSIGNER)符合预期。这一过程无需依赖外部的可信第三方(如远程认证中的IAS服务),可以高效地验证在本地运行的两个Enclave的可信性。
1.2 本地认证基本流程
1.初始化
- 准备工作:在进行本地认证之前,需要确保系统上安装了英特尔SGX驱动和SGX SDK,并创建和签署了两个至少包含认证代码的Enclave。
2.创建会话
- 会话请求:一个EnclaveB(称为请求者)启动会话请求(1),通过指定的本地认证API向另一个EnclaveA(称为响应者)发送认证请求。
- 会话处理:响应者接收到请求者的会话请求后,生成会话密钥,并准备响应。
3.生成认证报告
- 认证报告生成:请求者和响应者各自生成包含测量值(
MRENCLAVE
和MRSIGNER
)、会话密钥等信息的报告REPORT
。此报告用于证明Enclave的身份和状态。 - 认证报告交换:请求者和响应者交换各自生成的认证报告。
4.验证认证报告
- 验证报告:请求者和响应者分别验证对方的认证报告。这一步包括检查报告中的测量值、签名情况和认证会话密钥。
- 验证MRENCLAVE:验证对方Enclave的测量值(MRENCLAVE),确保其为预期的可信代码。
- 验证MRSIGNER:验证签名者的测量值(MRSIGNER),确保其由受信任的签名者生成。
- 验证会话密钥:确保会话密钥的正确性,用于后续的安全通讯。
5.交换密钥并通信
- 密钥交换:交换经过验证的会话密钥。
- 安全通信:一旦认证成功,请求者和响应者可以使用协商好的会话密钥进行安全通信,该密钥用于加密和解密传输的数据,确保数据传递的机密性和完整性。
总结:Intel SGX本地认证的步骤主要包括以下几个关键步骤:
- 两个Enclave初始化并准备各自的会话请求和响应。
- 请求者向响应者发起会话请求,双方生成并交换认证报告。
- 验证报告的真实性和可信度,确保双方的Enclave环境是可信的。
- 交换会话密钥,建立安全的通信渠道。
以上这些步骤确保了两个在同一台机器上运行的Enclave可以安全、高效地相互认证,从而在本地保护敏感数据和计算过程。
1.3 本地认证核心原理
就是响应方EnclaveA生成MAC值并返回给请求方,MAC值是由一个报告密钥生成,这个密钥只对目标EnclaveB和相同平台的EREPORT
指令可见。当EnclaveB收到REPORT信息之后,调用EGETKEY
指令来获取密钥,用来计算MAC值,进而将重新计算的MAC值和收到的REPORT
的MAC值进行比较(核心思想:验证MAC值),来确认EnclaveA运行在相同平台之上。当可信硬件部分得以证实之后(也就是说MAC值验证的是硬件可信),EnclaveB在通过REPORT中的信息认证EnclaveA的身份。最后EnclaveA再以同样的方式认证EnclaveB的身份,完成双向互认。
二.源码分析
2.1 README
---------------------------
The project aims to demo SGX local attestation flow.
------------------------------------
How to Build the Sample Code
------------------------------------
1. Install Intel(R) Software Guard Extensions (Intel(R) SGX) SDK for Linux* OS
2. Enclave test key(two options):
a. Install openssl first, then the project will generate a test key<EnclaveInitiator_private_test.pem>/<EnclaveResponder_private_test.pem> automatically when you build the project.
b. Rename your test key(3072-bit RSA private key) to <EnclaveInitiator_private_test.pem>/<EnclaveResponder_private_test.pem> and put it under the <EnclaveInitiator>/<EnclaveResponder> folder.
3. Build the project with the prepared Makefile:
a. Hardware Mode, Debug build:
$ make
b. Hardware Mode, Pre-release build:
$ make SGX_PRERELEASE=1 SGX_DEBUG=0
c. Hardware Mode, release build:
$ make SGX_DEBUG=0
d. Simulation Mode, Debug build:
$ make SGX_MODE=SIM
e. Simulation Mode, Pre-release build:
$ make SGX_MODE=SIM SGX_PRERELEASE=1 SGX_DEBUG=0
f. Simulation Mode, Release build:
$ make SGX_MODE=SIM SGX_DEBUG=0
g. Use Local Attestation 2.0 protocol, Hardware Mode, Debug build:
$ make LAv2=1
Note: Local Attestation 2.0 protocol will be used if 'LAv2' is defined.
When build is successful, you can find executable binaries in "bin" sub-folder.
------------------------------------
How to Execute the Sample Code
------------------------------------
1. Install SGX driver and PSW for Linux* OS
2. If you want to try local attestation flow from two process, you can goto "bin" sub-folder
a. run "./appresponder".
It would launch a process to act as local attestation responder.
b. run "./appinitiator"
It would launch a process to act as local attestation initator.
3. If you want to try local attestation flow from one process, you can goto "bin" sub-folder and run "./app"
------------------------------------
How to Get Signed Enclave's MRSIGNER
------------------------------------
1. Install Intel(R) Software Guard Extensions (Intel(R) SGX) SDK for Linux* OS
2. Execute blow command to get your signed enclave's MRSIGNER:
<SGX_SDK Installation Path>/bin/x64/sgx_sign dump -enclave <Signed Enclave> -dumpfile mrsigner.txt
3. Find the signed enclave's MRSIGNER in the mrsigner.txt(mrsigner->value:)
该示例的主要功能是测试enclave本地认证功能,根据上面README
中的内容总结一下LocalAttestation
的执行步骤如下:
2.1.1 编译流程
- 首先需要安装
SGX sdk
,以及等等对SGX的支持测试,具体可参考之前的一篇博客:【SGX系列教程】(一); - 安装
openssl
,目的是为enclave.so
生成签名私钥:
openssl genrsa -out Enclave/Enclave_private_test.pem -3 3072
- 确保sdk路径生效(source),基本在安装sdk时会指定安装路径为:
/opt/intel/sgxsdk
; - 使用Makefile构建工程:
- 硬件模式, Debug build(通常默认选择这个):
make
- 硬件模式, Pre-release build:
make SGX_PRERELEASE=1 SGX_DEBUG=0
- 硬件模式, Release build:
make SGX_DEBUG=0
- 仿真模式, Debug build:
make SGX_MODE=SIM
- 仿真模式, Pre-release build:
make SGX_MODE=SIM SGX_PRERELEASE=1 SGX_DEBUG=0
- 仿真模式, Release build:
make SGX_MODE=SIM SGX_DEBUG=0
- 使用 2.0 认证协议, 硬件模式, Debug build::
make LAv2=1
- 硬件模式, Debug build(通常默认选择这个):
2.1.2 执行流程(双进程执行 or 单进程执行,在后面执行部分有展示效果)
- 在
Linux
系统下安装SGX
驱动和PSW
(具体可以参考系列教程一); - 可以通过两个进程进行本地认证流程,进入
bin
子文件夹:
a. 运行./appresponder
: 这将启动一个进程,使其充当本地认证响应者。
b. 运行./appinitiator
: 这将启动一个进程,使其充当本地认证发起者。 - 如果您想通过一个进程进行完成全部本地认证流程,进入
bin
子文件夹直接运行./app
。
2.1.3 如何获取已签名的Enclave的MRSIGNER
- 安装SDK,适用于Linux操作系统。
- 执行以下命令以获取已签名的Enclave的MRSIGNER:
<SGX_SDK Installation Path>/bin/x64/sgx_sign dump -enclave <Signed Enclave> -dumpfile mrsigner.txt
- 在文件
mrsigner.txt
中找到已签名的Enclave的MRSIGNER
(mrsigner->value:
)(找到 mrsigner->value: 字段,即为签名该Enclave的签名者的测量值)
注意,这里的<Signed Enclave>
指的是编译之后针对每个enclave生成的签名文件,通常是.signed.so
或 .signed.dll
扩展名(具体取决于目标平台)。这里是在bin
目录下生成的libenclave_initiator.signed.so
和libenclave_responder.signed.so
两个文件。
因为分别执行下面两个命令,为initiator和responder两个enclave分别生成相应的签名者信息文件:
// 1.为initiator enclave生成initiatormrsigner.txt
/opt/intel/sgxsdk/bin/x64/sgx_sign dump -enclave ./bin/libenclave_initiator.signed.so -dumpfile initiatormrsigner.txt
// 2.为responder enclave生成respondermrsigner.txt
/opt/intel/sgxsdk/bin/x64/sgx_sign dump -enclave ./bin/libenclave_responder.signed.so -dumpfile respondermrsigner.txt
生成成功会输出Succeed.
,并在目录下面会生成如下两个文件,分别代表两个enclave的签名信息:
然后需要将mrsigner->value:
值分别copy到程序代码中以使用真实的签名者测量值(相当于公钥):
通过上述步骤,可以成功生成并替换已签名的Enclave
的MRSIGNER
信息。
2.2 重点代码分析
SGX工程的整个构建流程在之前的文章中已经给出给出了详细教程:【SGX系列教程】(二)第一个 SGX 程序: HelloWorld,因此在此处仅给出部分重点代码分析,主要讲清楚enclave如何实现本地认证,其他通用代码不做展开分析。
2.2.1 App文件夹
2.2.1.1 App/App.cpp
#include <stdio.h>
#include <map>
#include "sgx_eid.h"
#include "sgx_urts.h"
#include "EnclaveInitiator_u.h"
#include "EnclaveResponder_u.h"
#include "sgx_utils.h"
// 定义信任域(Enclave)的名称
// 定义两个信任域ID,ENCLAVE_INITIATOR_NAME 和 ENCLAVE_RESPONDER_NAME 分别代表发起者和响应者的已签名共享库文件
#define ENCLAVE_INITIATOR_NAME "libenclave_initiator.signed.so"
#define ENCLAVE_RESPONDER_NAME "libenclave_responder.signed.so"
// 定义两个Enclave ID,分别用于发起者和响应者Enclave
sgx_enclave_id_t initiator_enclave_id = 0, responder_enclave_id = 0;
int main(int argc, char* argv[])
{
int update = 0; // 定义更新标志
uint32_t ret_status; // 定义返回状态
sgx_status_t status; // 定义SGX状态类型
sgx_launch_token_t token = {0}; // 定义启动令牌,并初始化为0
// 避免未使用参数警告,argc和argv被标记为void
(void)argc;
(void)argv;
// 加载发起者和响应者enclave
if (SGX_SUCCESS != sgx_create_enclave(ENCLAVE_INITIATOR_NAME, SGX_DEBUG_FLAG, &token, &update, &initiator_enclave_id, NULL)
|| SGX_SUCCESS != sgx_create_enclave(ENCLAVE_RESPONDER_NAME, SGX_DEBUG_FLAG, &token, &update, &responder_enclave_id, NULL)) {
// 如果加载Enclave失败,打印错误信息并进入销毁流程
printf("failed to load enclave.\n");
goto destroy_enclave;
}
printf("succeed to load enclaves.\n"); // 如果成功加载,打印成功信息
// 使用发起者信任域创建ECDH会话,会话对象为响应者信任域
status = test_create_session(initiator_enclave_id, &ret_status);
if (status != SGX_SUCCESS || ret_status != 0) {
// 如果会话创建失败,打印错误信息并进入销毁流程
printf("failed to establish secure channel: ECALL return 0x%x, error code is 0x%x.\n", status, ret_status);
goto destroy_enclave;
}
printf("succeed to establish secure channel.\n");
// 如果会话创建成功,打印成功信息
// 测试发起者信任域和响应者信任域之间的消息交换
status = test_message_exchange(initiator_enclave_id, &ret_status);
if (status != SGX_SUCCESS || ret_status != 0) {
// 如果消息交换失败,打印错误信息并进入销毁流程
printf("test_message_exchange Ecall failed: ECALL return 0x%x, error code is 0x%x.\n", status, ret_status);
goto destroy_enclave;
}
printf("Succeed to exchange secure message...\n");
// 如果消息交换成功,打印成功信息
// 关闭ECDH会话
status = test_close_session(initiator_enclave_id, &ret_status);
if (status != SGX_SUCCESS || ret_status != 0) {
// 如果关闭会话失败,打印错误信息并进入销毁流程
printf("test_close_session Ecall failed: ECALL return 0x%x, error code is 0x%x.\n", status, ret_status);
goto destroy_enclave;
}
printf("Succeed to close Session...\n");
// 如果关闭会话成功,打印成功信息
destroy_enclave:
sgx_destroy_enclave(initiator_enclave_id);
sgx_destroy_enclave(responder_enclave_id);
// 销毁发起者和响应者信任域(Enclave)
return 0;
// 主函数返回0,表示正常结束
}
程序解释:
- 加载enclave: sgx_create_enclave,enclave API,函数用于加载Enclave文件,并初始化发起者和响应者的Enclave实例;
- 创建会话: test_create_session,ECALL函数,用于创建ECDH安全会话,发起者(Initiator Enclave)需要与响应者(Responder Enclave)建立一个安全会话。
- 消息交换:test_message_exchange,ECALL函数,用于在发起者和响应者之间进行消息传递。
- 关闭会话:test_close_session,ECALL函数,用于关闭之前建立的安全会话。
- 销毁Enclave:sgx_destroy_enclave,enclave API,函数用于销毁发起者和响应者的Enclave实例。无论前面的操作是否成功,最后都会执行销毁步骤,确保资源被正确释放。
总的来说,App.cpp代码用于演示Intel SGX中本地认证(Local Attestation)的基本过程,包括加载Enclave、建立会话、消息交换和关闭会话。每一步都使用了SGX提供的API,并结合错误处理机制,确保在失败时能正确退出并释放资源。
2.2.1.2 App/UntrustedEnclaveMessageExchange.cpp
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include "sgx_eid.h" // 用于SGX Enclave ID定义的头文件
#include "error_codes.h" // 自定义错误码的头文件
#include "datatypes.h" // 自定义数据类型的头文件
#include "sgx_urts.h" // 用于SGX User Runtime库的头文件
#include "UntrustedEnclaveMessageExchange.h" // 非受信任部分的消息交换头文件
#include "sgx_dh.h" // 用于Diffie-Hellman密钥交换的SGX头文件
#include "fifo_def.h" // FIFO定义文件
#include "EnclaveResponder_u.h" // 自动生成的Enclave Responder的未受信部分头文件
// 声明外部变量 responder_enclave_id,用于存储响应者Enclave的ID
extern sgx_enclave_id_t responder_enclave_id;
/* 函数描述: 这是发起者Enclave获取响应者Enclave的ECDH消息1和会话ID的OCALL接口
* 参数描述:
* [输入,输出] dh_msg1: 指向ECDH消息1缓冲区的指针,这个缓冲区在发起者Enclave中分配,并由响应者Enclave填充
* [输出] session_id: 指向由响应者Enclave分配的会话ID的指针
* */
extern "C" ATTESTATION_STATUS session_request_ocall(sgx_dh_msg1_t* dh_msg1, uint32_t* session_id)
{
sgx_status_t ret;
uint32_t retcode;
// 调用 session_request 函数,获取来自响应者Enclave的ECDH消息1和会话ID
ret = session_request(responder_enclave_id, &retcode, dh_msg1, session_id);
if (ret != SGX_SUCCESS || retcode != SGX_SUCCESS)
return ATTESTATION_ERROR;
return (ATTESTATION_STATUS)0;
}
/* 函数描述: 这是发起者Enclave发送ECDH消息2到响应者Enclave,并接收来自响应者Enclave的ECDH消息3的OCALL接口
* 参数描述:
* [输入] dh_msg2: 指向由发起者Enclave生成的ECDH消息2的指针
* [输入,输出] dh_msg3: 指向ECDH消息3的指针,这个缓冲区在发起者Enclave中分配,并由响应者Enclave填充
* [输入] session_id: 由响应者Enclave分配的会话ID
* */
ATTESTATION_STATUS exchange_report_ocall(sgx_dh_msg2_t *dh_msg2, sgx_dh_msg3_t *dh_msg3, uint32_t session_id)
{
sgx_status_t ret;
uint32_t retcode;
// 调用 exchange_report 函数,将消息2发送给响应者Enclave,并接收消息3
ret = exchange_report(responder_enclave_id, &retcode, dh_msg2, dh_msg3, session_id);
if (ret != SGX_SUCCESS || retcode != SGX_SUCCESS)
return ATTESTATION_ERROR;
return (ATTESTATION_STATUS)0;
}
/* 函数描述: 这是发起者Enclave发送加密请求消息到响应者Enclave,并接收来自响应者Enclave响应消息的OCALL接口
* 参数描述:
* [输入] session_id: 由响应者Enclave分配的会话ID
* [输入] req_message: 指向请求消息的指针
* [输入] req_message_size: 请求消息的大小
* [输入] max_payload_size: 响应消息中的最大有效负载大小
* [输入,输出] resp_message: 指向响应消息的指针,该缓冲区由发起者Enclave分配并由响应者Enclave填充
* [输入] response message size: 响应消息的大小
* */
ATTESTATION_STATUS send_request_ocall(uint32_t session_id, secure_message_t* req_message, size_t req_message_size, size_t max_payload_size, secure_message_t* resp_message, size_t resp_message_size)
{
sgx_status_t ret;
uint32_t retcode;
// 调用 generate_response 函数,向响应者Enclave发送请求并接收响应
ret = generate_response(responder_enclave_id, &retcode, req_message, req_message_size, max_payload_size, resp_message, resp_message_size, session_id);
if (ret != SGX_SUCCESS || retcode != SGX_SUCCESS)
return INVALID_SESSION;
return (ATTESTATION_STATUS)0;
}
/* 函数描述: 这是发起者Enclave关闭安全会话的OCALL接口
* 参数描述:
* [输入] session_id: 由响应者Enclave分配的会话ID
* */
ATTESTATION_STATUS end_session_ocall(uint32_t session_id)
{
sgx_status_t ret;
uint32_t retcode = (uint32_t) INVALID_SESSION;
// 调用 end_session 函数,关闭会话
ret = end_session(responder_enclave_id, &retcode, session_id);
if (ret != SGX_SUCCESS || retcode != SGX_SUCCESS)
return INVALID_SESSION;
return (ATTESTATION_STATUS)0;
}
程序解释:
这段代码实现了本地认证过程中发起者Enclave与响应者Enclave之间的一些OCALL接口。OCALL是指从受信任的环境调用未受信任环境中的功能。具体实现了以下4个OCALL接口:
- session_request_ocall:
功能:从响应者Enclave获取ECDH协议的消息1和会话ID。
实现:调用session_request函数,传递响应者Enclave的ID、返回代码、消息1缓冲区和会话ID指针。如果调用失败,返回认证错误代码。 - exchange_report_ocall:
功能:向响应者Enclave发送ECDH协议的消息2,并接收来自响应者Enclave的消息3。
实现:调用exchange_report函数,传递响应者Enclave的ID、返回代码、消息2、消息3和会话ID。如果调用失败,返回认证错误代码。 - send_request_ocall:
功能:向响应者Enclave发送加密请求消息,并接收响应消息。
实现:调用generate_response函数,传递响应者Enclave的ID、返回代码、请求消息及其大小、最大有效载荷大小、响应消息及其大小和会话ID。如果调用失败,返回无效会话错误代码。 - end_session_ocall:
功能:关闭发起者Enclave与响应者Enclave之间的安全会话。
实现:调用end_session函数,传递响应者Enclave的ID、返回代码和会话ID。如果调用失败,返回无效会话错误代码。
这些OCALL接口将请求传递到响应者Enclave并返回结果,用于协调本地认证流程,确保两个Enclave之间的通信安全。
2.2.2 AppInitiator文件夹
上面App是单线程实现两个enclave之间的互相认证,下面介绍的AppInitiator和AppResponder是通过两个线程分别实现请求者和响应者程序。
2.2.2.1 AppInitiator/App.cpp
#include <stdio.h>
#include <map>
#include <sched.h>
#include <sys/sysinfo.h>
#include <unistd.h> // POSIX标准API,如sleep、write等
#include "sgx_eid.h" // 用于SGX Enclave ID定义的头文件
#include "sgx_urts.h" // 用于SGX User Runtime库的头文件
#include "EnclaveInitiator_u.h" // 自动生成的未受信任部分的头文件,包含发起者Enclave的ECALL声明
#define ENCLAVE_INITIATOR_NAME "libenclave_initiator.signed.so"
// 定义发起者Enclave的已签名共享库文件名称
int main(int argc, char* argv[])
{
int update = 0; // 更新标志
uint32_t ret_status; // 返回状态
sgx_status_t status; // SGX状态类型
sgx_launch_token_t token = {0}; // 启动令牌,初始化为全零
sgx_enclave_id_t initiator_enclave_id = 0; // 定义发起者Enclave的ID
(void)argc;
(void)argv;
// 避免未使用参数的警告
// 创建ECDH发起者Enclave
status = sgx_create_enclave(ENCLAVE_INITIATOR_NAME, SGX_DEBUG_FLAG, &token, &update, &initiator_enclave_id, NULL);
if (status != SGX_SUCCESS) {
// 如果加载Enclave失败,打印错误信息并返回-1
printf("failed to load enclave %s, error code is 0x%x.\n", ENCLAVE_INITIATOR_NAME, status);
return -1;
}
printf("succeed to load enclave %s\n", ENCLAVE_INITIATOR_NAME);
// 成功加载,打印成功信息
// 使用发起者Enclave创建ECDH会话
status = test_create_session(initiator_enclave_id, &ret_status);
if (status != SGX_SUCCESS || ret_status != 0) {
// 如果会话创建失败,打印错误信息、销毁Enclave并返回-1
printf("failed to establish secure channel: ECALL return 0x%x, error code is 0x%x.\n", status, ret_status);
sgx_destroy_enclave(initiator_enclave_id);
return -1;
}
printf("succeed to establish secure channel.\n");
// 成功建立会话,打印成功信息
// 测试发起者Enclave和响应者Enclave之间的消息交换
status = test_message_exchange(initiator_enclave_id, &ret_status);
if (status != SGX_SUCCESS || ret_status != 0) {
// 如果消息交换失败,打印错误信息、销毁Enclave并返回-1
printf("test_message_exchange Ecall failed: ECALL return 0x%x, error code is 0x%x.\n", status, ret_status);
sgx_destroy_enclave(initiator_enclave_id);
return -1;
}
printf("Succeed to exchange secure message...\n");
// 成功消息交换,打印成功信息
// 关闭ECDH会话
status = test_close_session(initiator_enclave_id, &ret_status);
if (status != SGX_SUCCESS || ret_status != 0) {
// 如果关闭会话失败,打印错误信息、销毁Enclave并返回-1
printf("test_close_session Ecall failed: ECALL return 0x%x, error code is 0x%x.\n", status, ret_status);
sgx_destroy_enclave(initiator_enclave_id);
return -1;
}
printf("Succeed to close Session.\n");
// 成功关闭会话,打印成功信息
sgx_destroy_enclave(initiator_enclave_id);
// 销毁发起者Enclave
return 0;
// 程序正常退出,返回0
}
总的来说,与上面单线程的App/App.cpp相差无几。
2.2.2.2 AppInitiator/UntrustedEnclaveMessageExchange.cpp
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h> // POSIX标准API,如sleep、write等
#include "sgx_eid.h" // 用于SGX Enclave ID定义的头文件
#include "error_codes.h" // 自定义错误码的头文件
#include "datatypes.h" // 自定义数据类型的头文件
#include "sgx_urts.h" // 用于SGX User Runtime库的头文件
#include "UntrustedEnclaveMessageExchange.h" // 非受信任部分的消息交换头文件
#include "sgx_dh.h" // 用于Diffie-Hellman密钥交换的SGX头文件
#include "fifo_def.h" // FIFO定义文件
#include <map> // 标准库map,用于字典结构
/* 函数描述:这是发起者Enclave获取响应者Enclave的ECDH消息1和会话ID的OCALL接口
* 参数描述:
* [输入,输出] dh_msg1: 指向ECDH消息1缓冲区的指针,这个缓冲区在发起者Enclave中分配,并由响应者Enclave填充
* [输出] session_id: 指向由响应者Enclave分配的会话ID的指针
* */
extern "C" ATTESTATION_STATUS session_request_ocall(sgx_dh_msg1_t* dh_msg1, uint32_t* session_id)
{
FIFO_MSG msg1_request; // 定义消息1请求对象
FIFO_MSG *msg1_response; // 指向消息1响应的指针
SESSION_MSG1_RESP *msg1_respbody = NULL; // 指向消息1响应体的指针
size_t msg1_resp_size; // 响应消息的大小
msg1_request.header.type = FIFO_DH_REQ_MSG1; // 设置消息类型为DH密钥交换请求
msg1_request.header.size = 0; // 设置消息大小为0
// 发送并接收消息1请求
if ((client_send_receive(&msg1_request, sizeof(FIFO_MSG), &msg1_response, &msg1_resp_size) != 0)
|| (msg1_response == NULL))
{
// 如果发送接收失败,打印错误信息并返回无效会话错误
printf("fail to send and receive message.\n");
return INVALID_SESSION;
}
// 将响应消息填充至dh_msg1和session_id
msg1_respbody = (SESSION_MSG1_RESP *)msg1_response->msgbuf;
memcpy(dh_msg1, &msg1_respbody->dh_msg1, sizeof(sgx_dh_msg1_t));
*session_id = msg1_respbody->sessionid;
free(msg1_response); // 释放响应消息
return (ATTESTATION_STATUS)0;
}
/* 函数描述:这是发起者Enclave发送ECDH消息2到响应者Enclave,并接收来自响应者Enclave的ECDH消息3的OCALL接口
* 参数描述:
* [输入] dh_msg2: 指向由发起者Enclave生成的ECDH消息2的指针
* [输入,输出] dh_msg3: 指向ECDH消息3的指针,这个缓冲区在发起者Enclave中分配,并由响应者Enclave填充
* [输入] session_id: 由响应者Enclave分配的会话ID
* */
ATTESTATION_STATUS exchange_report_ocall(sgx_dh_msg2_t *dh_msg2, sgx_dh_msg3_t *dh_msg3, uint32_t session_id)
{
FIFO_MSG *msg2 = NULL, *msg3 = NULL; // 定义消息2和消息3对象
FIFO_MSG_HEADER *msg2_header = NULL; // 定义消息2头部指针
SESSION_MSG2 *msg2_body = NULL; // 定义消息2体指针
SESSION_MSG3 *msg3_body = NULL; // 定义消息3体指针
size_t msg2size, msg3size; // 定义消息2和消息3大小
msg2size = sizeof(FIFO_MSG_HEADER) + sizeof(SESSION_MSG2); // 设置消息2大小
msg2 = (FIFO_MSG *)malloc(msg2size); // 为消息2分配内存
if (!msg2)
{
// 如果内存分配失败,返回内存不足错误
return ERROR_OUT_OF_MEMORY;
}
memset(msg2, 0, msg2size); // 清空消息2内存
msg2_header = (FIFO_MSG_HEADER *)msg2; // 消息2头部指针
msg2_header->type = FIFO_DH_MSG2; // 设置消息类型为DH消息2
msg2_header->size = sizeof(SESSION_MSG2); // 设置消息大小
msg2_body = (SESSION_MSG2 *)msg2->msgbuf; // 消息2体指针
memcpy(&msg2_body->dh_msg2, dh_msg2, sizeof(sgx_dh_msg2_t)); // 复制消息2内容
msg2_body->sessionid = session_id; // 设置会话ID
// 发送并接收消息2
if (client_send_receive(msg2, msg2size, &msg3, &msg3size) != 0)
{
free(msg2); // 释放消息2内存
printf("failed to send and receive message.\n");
return INVALID_SESSION;
}
// 将消息3内容复制到输出参数dh_msg3中
msg3_body = (SESSION_MSG3 *)msg3->msgbuf;
memcpy(dh_msg3, &msg3_body->dh_msg3, sizeof(sgx_dh_msg3_t));
free(msg3); // 释放消息3内存
free(msg2); // 再次释放消息2内存
return (ATTESTATION_STATUS)0;
}
/* 函数描述:这是发起者Enclave发送加密请求消息到响应者Enclave,并接收来自响应者Enclave响应消息的OCALL接口
* 参数描述:
* [输入] session_id: 由响应者Enclave分配的会话ID
* [输入] req_message: 指向请求消息的指针
* [输入] req_message_size: 请求消息的大小
* [输入] max_payload_size: 响应消息中的最大有效负载大小
* [输入,输出] resp_message: 指向响应消息的指针,该缓冲区由发起者Enclave分配并由响应者Enclave填充
* [输入] response message size: 响应消息的大小
* */
ATTESTATION_STATUS send_request_ocall(uint32_t session_id, secure_message_t* req_message, size_t req_message_size, size_t max_payload_size, secure_message_t* resp_message, size_t resp_message_size)
{
FIFO_MSG *msgreq = NULL, *msgresp = NULL; // 定义请求消息和响应消息对象
FIFO_MSGBODY_REQ *msgbody; // 定义消息体指针
size_t reqsize, respsize; // 定义请求和响应大小
reqsize = sizeof(FIFO_MSG_HEADER) + sizeof(FIFO_MSGBODY_REQ) + req_message_size; // 设置请求大小
msgreq = (FIFO_MSG *)malloc(reqsize); // 为请求消息分配内存
if (!msgreq)
{
// 如果内存分配失败,返回内存不足错误
return ERROR_OUT_OF_MEMORY;
}
memset(msgreq, 0, reqsize); // 清空请求消息内存
msgreq->header.type = FIFO_DH_MSG_REQ; // 设置消息类型为DH请求消息
msgreq->header.size = sizeof(FIFO_MSGBODY_REQ) + req_message_size; // 设置消息大小
msgbody = (FIFO_MSGBODY_REQ *)msgreq->msgbuf; // 消息体指针
msgbody->max_payload_size = max_payload_size; // 设置最大有效负载大小
msgbody->size = req_message_size; // 设置请求消息大小
msgbody->session_id = session_id; // 设置会话ID
memcpy(msgbody->buf, req_message, req_message_size); // 复制请求消息内容
// 发送并接收请求消息
if (client_send_receive(msgreq, reqsize, &msgresp, &respsize) != 0)
{
free(msgreq); // 释放请求消息内存
printf("fail to send and receive message.\n");
return INVALID_SESSION;
}
// 将响应消息内容复制到输出参数resp_message中
memcpy(resp_message, msgresp->msgbuf, msgresp->header.size < resp_message_size ? msgresp->header.size : resp_message_size);
free(msgresp); // 释放响应消息内存
free(msgreq); // 再次释放请求消息内存
return (ATTESTATION_STATUS)0;
}
/* 参数描述:
* [输入] session_id: 由响应者Enclave分配的会话ID
* */
ATTESTATION_STATUS end_session_ocall(uint32_t session_id)
{
FIFO_MSG *msgresp = NULL; // 定义指向响应消息的指针
FIFO_MSG *closemsg; // 定义关闭消息对象
SESSION_CLOSE_REQ *body; // 定义会话关闭请求体指针
size_t reqsize, respsize; // 请求和响应的大小
reqsize = sizeof(FIFO_MSG) + sizeof(SESSION_CLOSE_REQ); // 设置请求消息的大小
closemsg = (FIFO_MSG *)malloc(reqsize); // 为关闭消息分配内存
if (!closemsg)
{
// 如果内存分配失败,返回内存不足错误
return ERROR_OUT_OF_MEMORY;
}
memset(closemsg, 0, reqsize); // 清空关闭消息内存
closemsg->header.type = FIFO_DH_CLOSE_REQ; // 设置消息类型为会话关闭请求
closemsg->header.size = sizeof(SESSION_CLOSE_REQ); // 设置消息体大小
body = (SESSION_CLOSE_REQ *)closemsg->msgbuf; // 消息体指针
body->session_id = session_id; // 设置会话ID
// 发送关闭消息并接收响应
if (client_send_receive(closemsg, reqsize, &msgresp, &respsize) != 0)
{
free(closemsg); // 释放关闭消息内存
printf("fail to send and receive message.\n");
return INVALID_SESSION;
}
free(closemsg); // 释放关闭消息内存
free(msgresp); // 释放响应消息内存
return (ATTESTATION_STATUS)0;
}
总的来说,与上面单线程的App/UntrustedEnclaveMessageExchange.cpp相差无几。
2.2.3 AppResponder文件夹
2.2.3.1 AppResponder/App.cpp
// App.cpp : Defines the entry point for the console application.
#include <stdio.h>
#include <map>
#include <sys/types.h>
#include <sys/stat.h>
#include <sched.h>
#include <sys/sysinfo.h>
#include <signal.h>
#include "EnclaveResponder_u.h" // 自动生成的未受信任部分头文件,包含响应者Enclave的ECALL声明
#include "sgx_eid.h" // 用于SGX Enclave ID定义的头文件
#include "sgx_urts.h" // 用于SGX User Runtime库的头文件
#include "fifo_def.h" // FIFO定义文件
#include "datatypes.h" // 自定义数据类型头文件
#include "CPTask.h" // 任务处理类的头文件
#include "CPServer.h" // 服务器类的头文件
#define __STDC_FORMAT_MACROS
#include <inttypes.h> // 提供大整数格式化的头文件
#define UNUSED(val) (void)(val) // 定义未使用参数的宏
#define TCHAR char
#define _TCHAR char
#define _T(str) str
#define scanf_s scanf
#define _tmain main
CPTask * g_cptask = NULL; // 全局变量,任务处理类指针
CPServer * g_cpserver = NULL; // 全局变量,服务器类指针
// 信号处理函数
void signal_handler(int sig)
{
switch(sig)
{
case SIGINT: // 处理SIGINT信号
case SIGTERM: // 处理SIGTERM信号
{
if (g_cpserver)
g_cpserver->shutDown(); // 如果服务器存在,调用其关闭函数
}
break;
default:
break;
}
exit(1); // 退出程序
}
// 清理函数,销毁全局对象
void cleanup()
{
if(g_cptask != NULL)
delete g_cptask;
if(g_cpserver != NULL)
delete g_cpserver;
}
// 主函数
int main(int argc, char* argv[])
{
(void)argc;
(void)argv; // 忽略未使用的参数
// 创建服务器实例,它将在套接字上监听并处理客户端请求
g_cptask = new (std::nothrow) CPTask;
g_cpserver = new (std::nothrow) CPServer(g_cptask);
if (!g_cptask || !g_cpserver)
return -1; // 如果创建失败,返回-1
atexit(cleanup); // 注册退出时的清理函数
// 注册信号处理器函数,以响应用户中断
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
g_cptask->start(); // 启动任务处理
if (g_cpserver->init() != 0)
{
printf("fail to init server\n"); // 初始化服务器失败,打印错误信息
}
else
{
printf("Server is ON...\n");
printf("Press Ctrl+C to exit...\n");
g_cpserver->doWork(); // 服务器初始化成功,启动服务器工作
}
return 0; // 程序正常结束
}
程序解释:
这段代码主要用于设置和启动一个SGX应用程序中的服务器实例。它包括以下几个主要步骤:
- 创建和初始化
CPTask
任务处理器和CPServer
服务器实例。 - 注册信号处理器和退出时的清理函数,以确保资源在程序终止时被正确释放。
- 启动任务处理器和服务器,处理客户端请求和本地认证流程。
2.2.3.2 AppResponder/CPServer.cpp
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/un.h>
#include "CPServer.h"
#define BACKLOG 5 // 最大连接等待队列长度
#define CONCURRENT_MAX 32 // 最大并发连接数
#define SERVER_PORT 8888 // 服务器端口号(Windows)
#define BUFFER_SIZE 1024 // 缓冲区大小
#define UNIX_DOMAIN "/tmp/UNIX.domain" // Unix Domain Socket 文件路径
/* 函数描述:
* 这是服务器初始化例程,它创建TCP套接字并监听一个端口。
* 在Linux上,它会监听名为 '/tmp/UNIX.domain' 的域套接字
* 在Windows上,它会监听8888端口,仅用于示范目的
* */
int CPServer::init()
{
struct sockaddr_un srv_addr;
// 创建UNIX域套接字
m_server_sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (m_server_sock_fd == -1)
{
printf("socket initiazation error\n");
return -1; // 套接字初始化失败,返回-1
}
srv_addr.sun_family = AF_UNIX;
strncpy(srv_addr.sun_path, UNIX_DOMAIN, sizeof(srv_addr.sun_path) - 1); // 设置域套接字路径
unlink(UNIX_DOMAIN); // 删除已存在的文件
// 绑定套接字
int bind_result = bind(m_server_sock_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
if (bind_result == -1)
{
printf("bind error\n");
close(m_server_sock_fd); // 绑定失败,关闭套接字并返回-1
return -1;
}
// 监听套接字
if (listen(m_server_sock_fd, BACKLOG) == -1)
{
printf("listen error\n");
close(m_server_sock_fd); // 监听失败,关闭套接字并返回-1
return -1;
}
m_shutdown = 0; // 初始化停止标志
return 0; // 成功返回0
}
/* 函数描述:
* 这是服务器的主要例程,它使用select()来接受新连接并接收来自客户端的消息。
* 当它接收到客户端的请求消息后,它会将消息放入任务队列并唤醒工作线程处理请求。
* */
void CPServer::doWork()
{
int client_fds[CONCURRENT_MAX] = {0}; // 客户端连接描述符数组,初始化为0
fd_set server_fd_set; // 文件描述符集合
int max_fd = -1; // 最大文件描述符
struct timeval tv; // 超时时间
char input_msg[BUFFER_SIZE]; // 输入消息缓冲区
char recv_msg[BUFFER_SIZE]; // 接收消息缓冲区
while (!m_shutdown)
{
// 为select()设置20秒超时
tv.tv_sec = 20;
tv.tv_usec = 0;
FD_ZERO(&server_fd_set); // 清空文件描述符集合
FD_SET(STDIN_FILENO, &server_fd_set); // 将标准输入加入集合
if (max_fd < STDIN_FILENO)
max_fd = STDIN_FILENO; // 更新最大文件描述符
// 监听服务器套接字
FD_SET(m_server_sock_fd, &server_fd_set);
if (max_fd < m_server_sock_fd)
max_fd = m_server_sock_fd; // 更新最大文件描述符
// 监听所有客户端连接
for (int i = 0; i < CONCURRENT_MAX; i++) {
if (client_fds[i] != 0) {
FD_SET(client_fds[i], &server_fd_set);
if (max_fd < client_fds[i])
max_fd = client_fds[i]; // 更新最大文件描述符
}
}
// 调用select()等待事件
int ret = select(max_fd + 1, &server_fd_set, NULL, NULL, &tv);
if (ret < 0) {
printf("Warning: server would shutdown\n");
continue; // select()失败,继续循环
} else if (ret == 0) {
// 超时,继续循环
continue;
}
if (FD_ISSET(m_server_sock_fd, &server_fd_set)) {
// 如果有新的连接请求
struct sockaddr_un clt_addr;
socklen_t len = sizeof(clt_addr);
// 接受连接请求
int client_sock_fd = accept(m_server_sock_fd, (struct sockaddr *)&clt_addr, &len);
if (client_sock_fd > 0) {
// 如果连接池未满,将新连接加入连接池
int index = -1;
for (int i = 0; i < CONCURRENT_MAX; i++) {
if (client_fds[i] == 0) {
index = i;
client_fds[i] = client_sock_fd;
break;
}
}
if (index < 0) {
printf("server reach maximum connection!\n");
bzero(input_msg, BUFFER_SIZE);
strcpy(input_msg, "server reach maximum connection\n");
send(client_sock_fd, input_msg, BUFFER_SIZE, 0);
} else if (client_sock_fd < 0) {
printf("server: accept() return failure, %s, would exit.\n", strerror(errno));
close(m_server_sock_fd);
break;
}
}
}
for (int i = 0; i < CONCURRENT_MAX; i++) {
if ((client_fds[i] != 0)
&& (FD_ISSET(client_fds[i], &server_fd_set))) {
// 如果有来自客户端的请求消息
FIFO_MSG *msg;
bzero(recv_msg, BUFFER_SIZE);
long byte_num = recv(client_fds[i], recv_msg, BUFFER_SIZE, 0);
if (byte_num > 0) {
if (byte_num > BUFFER_SIZE)
byte_num = BUFFER_SIZE;
recv_msg[byte_num] = '\0';
msg = (FIFO_MSG *)malloc(byte_num);
if (!msg) {
printf("memory allocation failure\n");
continue;
}
memset(msg, 0, byte_num);
memcpy(msg, recv_msg, byte_num);
msg->header.sockfd = client_fds[i];
// 将请求消息放入事件队列
m_cptask->puttask(msg);
} else if (byte_num < 0) {
printf("failed to receive message.\n");
} else {
// 客户端连接关闭
FD_CLR(client_fds[i], &server_fd_set);
close(client_fds[i]);
client_fds[i] = 0;
}
}
}
}
}
/* 函数描述:
* 这是关闭服务器的函数。当进程退出时调用。
* */
void CPServer::shutDown()
{
printf("Server would shutdown...\n");
m_shutdown = 1; // 设置停止标志
m_cptask->shutdown(); // 停止任务处理
close(m_server_sock_fd); // 关闭服务器套接字
}
功能分析
- 初始化服务器(init方法):
功能:创建UNIX域套接字绑定到路径/tmp/UNIX.domain并开始监听连接请求。
实现:创建套接字 socket(AF_UNIX, SOCK_STREAM, 0)。
设置套接字地址结构 srv_addr 的家庭类型为 AF_UNIX,并指定路径 UNIX_DOMAIN。调用bind函数绑定套接字到地址。通过listen函数使套接字开始监听连接请求。如果过程中任何一步失败,关闭套接字并返回-1,否则返回0表示成功。 - 处理客户端连接和消息(doWork方法):
功能:使用select函数接受新的连接和处理来自客户端的消息。
实现:使用select函数监视多个文件描述符,检测新的连接请求和来自客户端的消息。如果select检测到新连接请求,调用accept函数接受连接并将新客户端加入client_fds数组中。如果select检测到客户端的请求消息,调用recv函数接收消息并将其复制到FIFO_MSG结构中,之后将消息放入任务队列中供处理。如果客户端连接关闭或发生错误,关闭相应的客户端套接字并从client_fds数组中移除。 - 关闭服务器(shutDown方法):
功能:当服务器需要关闭时调用的函数,控制服务器的关机流程。
实现:打印关闭服务器的消息。设置停止标志 m_shutdown 为 1。
2.2.3.3 AppResponder/CPTask.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <map>
#include <sys/stat.h>
#include <sched.h>
#include "EnclaveResponder_u.h" // 自动生成的未受信任部分头文件,包含响应者Enclave的ECALL声明
#include "sgx_eid.h" // 用于SGX Enclave ID定义的头文件
#include "sgx_urts.h" // 用于SGX User Runtime库的头文件
#include "cpdef.h" // 自定义的协议定义头文件
#include "fifo_def.h" // FIFO定义文件
#include "datatypes.h" // 自定义数据类型头文件
#include "CPTask.h" // 任务处理类的头文件
#include "CPServer.h" // 服务器类的头文件
sgx_enclave_id_t e2_enclave_id = 0; // 定义响应者Enclave ID
#define ENCLAVE_RESPONDER_NAME "libenclave_responder.signed.so" // 定义响应者Enclave的已签名共享库文件名称
/* 函数描述:加载响应者Enclave
* */
int load_enclaves()
{
sgx_status_t ret = SGX_SUCCESS; // SGX状态类型
sgx_launch_token_t token = {0}; // 启动令牌,初始化为全零
int update = 0; // 更新标志
ret = sgx_create_enclave(ENCLAVE_RESPONDER_NAME, SGX_DEBUG_FLAG, &token, &update, &e2_enclave_id, NULL);
if (ret != SGX_SUCCESS)
{
// 如果加载Enclave失败,打印错误信息并返回-1
printf("failed to load enclave %s, error code is 0x%x.\n", ENCLAVE_RESPONDER_NAME, ret);
return -1;
}
return 0; // 加载成功返回0
}
/* 函数描述:
* 该函数响应发起者Enclave的连接请求,通过生成并返回ECDH消息1
* 参数描述:
* [输入] clientfd: 客户端的连接ID。在生成ECDH消息1后,服务器将通过此连接ID发送响应。
* */
int generate_and_send_session_msg1_resp(int clientfd)
{
int retcode = 0; // 初始化返回码
uint32_t status = 0; // SGX状态码
sgx_status_t ret = SGX_SUCCESS; // SGX状态类型
SESSION_MSG1_RESP msg1resp; // 会话消息1响应结构体
FIFO_MSG *fifo_resp = NULL; // 指向FIFO响应消息的指针
size_t respmsgsize; // 响应消息大小
memset(&msg1resp, 0, sizeof(SESSION_MSG1_RESP)); // 清空消息1响应结构体
// 调用响应者Enclave生成ECDH消息1
ret = session_request(e2_enclave_id, &status, &msg1resp.dh_msg1, &msg1resp.sessionid);
if (ret != SGX_SUCCESS)
{
printf("failed to do ECALL session_request.\n");
return -1; // 如果生成失败,打印错误信息并返回-1
}
respmsgsize = sizeof(FIFO_MSG) + sizeof(SESSION_MSG1_RESP); // 计算响应消息大小
fifo_resp = (FIFO_MSG *)malloc(respmsgsize); // 分配内存
if (!fifo_resp)
{
printf("memory allocation failure.\n");
return -1; // 如果内存分配失败,打印错误信息并返回-1
}
memset(fifo_resp, 0, respmsgsize); // 清空分配的内存
fifo_resp->header.type = FIFO_DH_RESP_MSG1; // 设置消息类型
fifo_resp->header.size = sizeof(SESSION_MSG1_RESP); // 设置消息大小
memcpy(fifo_resp->msgbuf, &msg1resp, sizeof(SESSION_MSG1_RESP)); // 复制响应内容到FIFO消息中
// 发送消息1到客户端
if (send(clientfd, reinterpret_cast<char *>(fifo_resp), static_cast<int>(respmsgsize), 0) == -1)
{
printf("fail to send msg1 response.\n");
retcode = -1; // 如果发送失败,打印错误信息,并设置返回码为-1
}
free(fifo_resp); // 释放内存
return retcode; // 返回结果码
}
/* 函数描述:
* 该函数处理从客户端接收的ECDH消息2,并向客户端发送消息3
* 参数描述:
* [输入] clientfd: 客户端的连接ID
* [输入] msg2: 包含从客户端接收的ECDH消息2
* */
int process_exchange_report(int clientfd, SESSION_MSG2 *msg2)
{
uint32_t status = 0; // SGX状态码
sgx_status_t ret = SGX_SUCCESS; // SGX状态类型
FIFO_MSG *response; // 指向FIFO响应消息的指针
SESSION_MSG3 *msg3; // 指向ECDH消息3的指针
size_t msgsize; // 消息大小
if (!msg2)
return -1; // 检查消息2是否为空,如果为空返回-1
msgsize = sizeof(FIFO_MSG_HEADER) + sizeof(SESSION_MSG3); // 计算消息大小
response = (FIFO_MSG *)malloc(msgsize); // 分配内存
if (!response)
{
printf("memory allocation failure\n");
return -1; // 如果内存分配失败,打印错误信息并返回-1
}
memset(response, 0, msgsize); // 清空分配的内存
response->header.type = FIFO_DH_MSG3; // 设置消息类型
response->header.size = sizeof(SESSION_MSG3); // 设置消息大小
msg3 = (SESSION_MSG3 *)response->msgbuf; // 消息体指针
msg3->sessionid = msg2->sessionid; // 设置会话ID
// 调用响应者Enclave处理ECDH消息2并生成消息3
ret = exchange_report(e2_enclave_id, &status, &msg2->dh_msg2, &msg3->dh_msg3, msg2->sessionid);
if (ret != SGX_SUCCESS)
{
printf("EnclaveResponse_exchange_report failure.\n");
free(response); // 如果失败,打印错误信息并释放内存
return -1;
}
// 发送消息3到客户端
if (send(clientfd, reinterpret_cast<char *>(response), static_cast<int>(msgsize), 0) == -1)
{
printf("server_send() failure.\n");
free(response); // 如果发送失败,打印错误信息并释放内存
return -1;
}
free(response); // 释放内存
return 0; // 返回0表示成功
}
/* 函数描述:
* 该函数处理从客户端接收的消息通信
* 参数描述:
* [输入] clientfd: 客户端的连接ID
* [输入] req_msg: 指向从客户端接收的请求消息的指针
* */
int process_msg_transfer(int clientfd, FIFO_MSGBODY_REQ *req_msg)
{
uint32_t status = 0; // SGX状态码
sgx_status_t ret = SGX_SUCCESS; // SGX状态类型
secure_message_t *resp_message = NULL; // 指向响应消息的指针
FIFO_MSG *fifo_resp = NULL; // 指向FIFO响应消息的指针
size_t resp_message_size; // 响应消息大小
if (!req_msg)
{
printf("invalid parameter.\n");
return -1; // 检查请求消息是否为空,如果为空返回-1
}
resp_message_size = sizeof(secure_message_t) + req_msg->max_payload_size; // 计算响应消息大小
// 分配内存给响应消息
resp_message = (secure_message_t*)malloc(resp_message_size);
if (!resp_message)
{
printf("memory allocation failure.\n");
return -1; // 如果内存分配失败,打印错误信息并返回-1
}
memset(resp_message, 0, resp_message_size); // 清空分配的内存
// 调用响应者Enclave生成响应消息
ret = generate_response(e2_enclave_id, &status, (secure_message_t *)req_msg->buf, req_msg->size, req_msg->max_payload_size, resp_message, resp_message_size, req_msg->session_id);
if (ret != SGX_SUCCESS)
{
printf("EnclaveResponder_generate_response error.\n");
free(resp_message); // 如果失败,打印错误信息并释放内存
return -1;
}
fifo_resp = (FIFO_MSG *)malloc(sizeof(FIFO_MSG) + resp_message_size); // 分配内存给FIFO响应消息
if (!fifo_resp)
{
printf("memory allocation failure.\n");
free(resp_message); // 释放响应消息内存
return -1; // 返回内存分配失败错误
}
memset(fifo_resp, 0, sizeof(FIFO_MSG) + resp_message_size); // 清空分配的FIFO响应消息内存
fifo_resp->header.type = FIFO_DH_MSG_RESP; // 设置消息类型为响应消息
fifo_resp->header.size = resp_message_size; // 设置消息大小
memcpy(fifo_resp->msgbuf, resp_message, resp_message_size); // 复制响应消息内容到FIFO消息缓冲区
free(resp_message); // 释放响应消息内存
// 发送响应消息到客户端
if (send(clientfd, reinterpret_cast<char *>(fifo_resp), sizeof(FIFO_MSG) + static_cast<int>(resp_message_size), 0) == -1)
{
printf("server_send() failure.\n");
free(fifo_resp); // 如果发送失败,打印错误信息并释放FIFO消息内存
return -1; // 返回发送失败错误
}
free(fifo_resp); // 释放FIFO消息内存
return 0; // 返回0表示成功
}
/* 函数描述:处理来自客户端的会话关闭请求
* 参数描述:
* [输入] clientfd: 客户端的连接ID
* [输入] close_req: 指向客户端的会话关闭请求的指针
* */
int process_close_req(int clientfd, SESSION_CLOSE_REQ *close_req)
{
uint32_t status = 0; // SGX状态码
sgx_status_t ret = SGX_SUCCESS; // SGX状态类型
FIFO_MSG close_ack; // 定义关闭确认消息
if (!close_req)
return -1; // 检查关闭请求是否为空,如果为空返回-1
// 调用响应者Enclave关闭会话
ret = end_session(e2_enclave_id, &status, close_req->session_id);
if (ret != SGX_SUCCESS)
return -1; // 如果失败,返回会话关闭错误
// 发送会话关闭确认响应
memset(&close_ack, 0, sizeof(FIFO_MSG)); // 清空关闭确认消息内存
close_ack.header.type = FIFO_DH_CLOSE_RESP; // 设置消息类型为关闭响应
close_ack.header.size = 0; // 设置消息大小为0
if (send(clientfd, reinterpret_cast<char *>(&close_ack), sizeof(FIFO_MSG), 0) == -1)
{
printf("server_send() failure.\n");
return -1; // 如果发送失败,打印错误信息并返回发送失败错误
}
return 0; // 返回0表示成功
}
void CPTask::run()
{
FIFO_MSG *message = NULL; // 定义指向消息的指针
sgx_launch_token_t token = {0}; // 启动令牌,初始化为全零
sgx_status_t status; // SGX状态类型
int update = 0; // 更新标志
// 加载响应者Enclave
status = sgx_create_enclave(ENCLAVE_RESPONDER_NAME, SGX_DEBUG_FLAG, &token, &update, &e2_enclave_id, NULL);
if (status != SGX_SUCCESS)
{
printf("failed to load enclave %s, error code is 0x%x.\n", ENCLAVE_RESPONDER_NAME, status);
return; // 如果加载失败,打印错误信息并返回
}
while (!isStopped())
{
/* 从队列中接收任务 */
message = m_queue.blockingPop();
if (isStopped())
{
free(message); // 如果已停止,释放消息内存并跳出循环
break;
}
switch (message->header.type)
{
case FIFO_DH_REQ_MSG1:
{
// 处理ECDH会话连接请求
int clientfd = message->header.sockfd;
if (generate_and_send_session_msg1_resp(clientfd) != 0)
{
printf("failed to generate and send session msg1 resp.\n");
break; // 如果生成并发送会话消息1失败,打印错误信息并跳出循环
}
}
break;
case FIFO_DH_MSG2:
{
// 处理ECDH消息2
int clientfd = message->header.sockfd;
SESSION_MSG2 *msg2 = NULL;
msg2 = (SESSION_MSG2 *)message->msgbuf;
if (process_exchange_report(clientfd, msg2) != 0)
{
printf("failed to process exchange_report request.\n");
break; // 如果处理消息交换报告失败,打印错误信息并跳出循环
}
}
break;
case FIFO_DH_MSG_REQ:
{
// 处理消息传输请求
int clientfd = message->header.sockfd;
FIFO_MSGBODY_REQ *msg = NULL;
msg = (FIFO_MSGBODY_REQ *)message->msgbuf;
if (process_msg_transfer(clientfd, msg) != 0)
{
printf("failed to process message transfer request.\n");
break; // 如果处理消息传输请求失败,打印错误信息并跳出循环
}
}
break;
case FIFO_DH_CLOSE_REQ:
{
// 处理关闭会话请求
int clientfd = message->header.sockfd;
SESSION_CLOSE_REQ *closereq = NULL;
closereq = (SESSION_CLOSE_REQ *)message->msgbuf;
process_close_req(clientfd, closereq); // 处理关闭会话请求
}
break;
default:
{
printf("Unknown message.\n"); // 未知消息类型,打印错误信息
}
break;
}
free(message); // 释放消息内存
message = NULL;
}
sgx_destroy_enclave(e2_enclave_id); // 销毁响应者Enclave
}
void CPTask::shutdown()
{
stop(); // 停止任务
m_queue.close(); // 关闭消息队列
join(); // 等待任务结束
}
void CPTask::puttask(FIFO_MSG* requestData)
{
if (isStopped()) {
return; // 如果已停止,不处理任务
}
m_queue.push(requestData); // 将任务放入消息队列
}
这段代码实现了一个负责处理从客户端接收的任务类CPTask,并通过与SGX Enclave进行交互完成任务。主要功能包括以下几个部分:
函数 process_close_req:
功能:处理来自客户端的会话关闭请求。
实现:检查关闭请求参数是否为空,如果为空则返回-1。调用 end_session 函数通过Enclave关闭会话。发送会话关闭确认响应到客户端。成功关闭会话返回0,否则返回-1。
方法 CPTask::run:
功能:任务处理的主逻辑,加载Enclave并处理消息队列中的任务。
实现:加载响应者Enclave。在循环中不断从消息队列中取出任务,根据任务类型分别处理不同的任务。包括会话请求、消息交换、消息传输和会话关闭等任务。如果遇到未知的消息类型,打印错误信息。结束后销毁Enclave。
方法 CPTask::shutdown:
功能:停止任务处理,关闭消息队列并等待任务结束。
实现:调用 stop 方法停止任务。关闭消息队列。等待任务结束。
方法 CPTask::puttask:
功能:将任务放入消息队列。
实现:检查任务是否已停止,如果已停止则不处理任务。
将任务放入消息队列。
综上所述,这段代码通过注册信号处理器和任务队列的使用,确保了从客户端接收任务并对其进行处理的完整流程。主要实现了本地认证过程中的消息交换和会话管理,并与SGX Enclave进行交互,确保数据和通信的安全性。
2.2.4 EnclaveInitiator文件夹
- EnclaveInitiator.cpp
// Enclave1.cpp : Defines the exported functions for the .so application
#include "sgx_eid.h" // 用于SGX Enclave ID定义的头文件
#include "EnclaveInitiator_t.h" // 自动生成的可信部分头文件,包含发起者Enclave的ECALL声明
#include "EnclaveMessageExchange.h" // 消息交换相关头文件
#include "error_codes.h" // 自定义错误码头文件
#include "Utility_E1.h" // 实用函数头文件
#include "sgx_dh.h" // 用于Diffie-Hellman密钥交换的SGX头文件
#include "sgx_utils.h" // 用于SGX实用函数的头文件
#include <map> // 标准库map,用于字典结构
#define UNUSED(val) (void)(val) // 忽略未使用参数的宏定义
#define RESPONDER_PRODID 1 // 响应者Enclave的产品ID
std::map<sgx_enclave_id_t, dh_session_t> g_src_session_info_map; // 定义全局映射,存储源会话信息
dh_session_t g_session; // 定义一个全局会话变量
// 这是响应者Enclave的MRSIGNER,需要与响应者Enclave的签名密钥一致
// 请在您的项目中替换为您的响应者Enclave的真实MRSIGNER!!!(参考上面MRSIGNER的生成方法,这里已经替换)
// 获取签名的Enclave的MRSIGNER的命令:<SGX_SDK安装路径>/bin/x64/sgx_sign dump -enclave <已签名的Enclave,.signer.so文件> -dumpfile mrsigner.txt
// 在mrsigner.txt中找到签名的Enclave的MRSIGNER(mrsigner->value:),然后替换下面的值
sgx_measurement_t g_responder_mrsigner = {
{
0x68, 0xfb, 0x5b, 0x3c, 0x69, 0x38, 0x34, 0x4f, 0x7b, 0x0e, 0x3d, 0x68, 0x2a, 0x46, 0x36, 0x70,
0xca, 0xc5, 0x2b, 0xf2, 0xfd, 0xf6, 0xc8, 0x90, 0x48, 0xc3, 0x6d, 0xcb, 0x85, 0x3f, 0xc8, 0x59
}
};
/* 函数描述:
* 这是创建ECDH会话的ECALL例程。
* 当成功创建ECDH会话时,会话上下文保存到g_session中。
* */
extern "C" uint32_t test_create_session()
{
return create_session(&g_session); // 调用create_session函数创建会话
}
/* 函数描述:
* 这是与ECDH对等方传输消息的ECALL例程。
* */
uint32_t test_message_exchange()
{
ATTESTATION_STATUS ke_status = SUCCESS; // 定义验证状态
uint32_t target_fn_id, msg_type; // 目标函数ID和消息类型
char* marshalled_inp_buff; // 序列化输入缓冲区指针
size_t marshalled_inp_buff_len; // 序列化输入缓冲区长度
char* out_buff; // 输出缓冲区指针
size_t out_buff_len; // 输出缓冲区长度
size_t max_out_buff_size; // 最大输出缓冲区大小
char* secret_response; // 秘密响应指针
uint32_t secret_data; // 秘密数据
target_fn_id = 0;
msg_type = MESSAGE_EXCHANGE;
max_out_buff_size = 50; // 假设响应消息中的最大有效负载大小为50字节,出于演示目的
secret_data = 0x12345678; // 秘密数据在此仅用于演示目的
// 将秘密数据序列化到缓冲区中
ke_status = marshal_message_exchange_request(target_fn_id, msg_type, secret_data, &marshalled_inp_buff, &marshalled_inp_buff_len);
if(ke_status != SUCCESS)
{
return ke_status;
}
// 核心参考代码函数
ke_status = send_request_receive_response(&g_session, marshalled_inp_buff,marshalled_inp_buff_len, max_out_buff_size, &out_buff, &out_buff_len);
if(ke_status != SUCCESS)
{
SAFE_FREE(marshalled_inp_buff);
SAFE_FREE(out_buff);
return ke_status;
}
// 反序列化秘密响应数据
ke_status = umarshal_message_exchange_response(out_buff, &secret_response);
if(ke_status != SUCCESS)
{
SAFE_FREE(marshalled_inp_buff);
SAFE_FREE(out_buff);
return ke_status;
}
SAFE_FREE(marshalled_inp_buff);
SAFE_FREE(out_buff);
SAFE_FREE(secret_response);
return SUCCESS;
}
/* 函数描述:
* 这是关闭安全会话的ECALL接口*/
extern "C" uint32_t test_close_session()
{
ATTESTATION_STATUS ke_status = SUCCESS; // 定义验证状态
ke_status = close_session(&g_session); // 调用close_session函数关闭会话
// 擦除会话上下文
memset(&g_session, 0, sizeof(dh_session_t));
return ke_status;
}
/* 函数描述:
* 验证对等Enclave的身份。
* 出于演示目的,验证以下几点:
* 1. 对等Enclave的MRSIGNER符合预期
* 2. 对等Enclave的产品ID(Prod ID)符合预期
* 3. 对等Enclave的属性合理:它是已初始化的Enclave;在非调试配置中,Enclave未以调试模式加载。
* */
extern "C" uint32_t verify_peer_enclave_trust(sgx_dh_session_enclave_identity_t* peer_enclave_identity)
{
if (!peer_enclave_identity)
return INVALID_PARAMETER_ERROR;
// 检查对等Enclave的MRSIGNER,请在您的项目中启用此检查!!!这步很关键!!!
// 注意:一定是上面的g_responder_mrsigner替换为真实的签名值后才能验证通过!!!
if (memcmp((uint8_t *)&peer_enclave_identity->mr_signer, (uint8_t*)&g_responder_mrsigner, sizeof(sgx_measurement_t)))
return ENCLAVE_TRUST_ERROR;
// 检查对等Enclave的产品ID和Enclave属性(应已初始化)
if (peer_enclave_identity->isv_prod_id != RESPONDER_PRODID || !(peer_enclave_identity->attributes.flags & SGX_FLAGS_INITTED))
return ENCLAVE_TRUST_ERROR;
// 检查Enclave未以调试模式加载,除非项目是为调试目的构建的
#if defined(NDEBUG)
if (peer_enclave_identity->attributes.flags & SGX_FLAGS_DEBUG)
return ENCLAVE_TRUST_ERROR;
#endif
return SUCCESS;
}
/* 函数描述:操作输入秘密并生成输出秘密
* */
uint32_t get_message_exchange_response(uint32_t inp_secret_data)
{
uint32_t secret_response;
// 用户应使用更复杂的加密方法来保护他们的秘密,下面的例子只是一个简单的示例
secret_response = inp_secret_data & 0x11111111;
return secret_response;
}
// 从请求消息生成响应
/* 函数描述:
* 处理请求消息并生成响应
* 参数描述:
* [输入] decrypted_data: 指向解密消息的指针
* [输出] resp_buffer: 指向响应消息的指针,缓冲区在本函数内分配
* [输出] resp_length: 指向响应长度的指针
* */
extern "C" uint32_t message_exchange_response_generator(char* decrypted_data,
char** resp_buffer,
size_t* resp_length)
{
ms_in_msg_exchange_t *ms;
uint32_t inp_secret_data;
uint32_t out_secret_data;
if(!decrypted_data || !resp_length)
{
return INVALID_PARAMETER_ERROR;
}
ms = (ms_in_msg_exchange_t *)decrypted_data;
if(umarshal_message_exchange_request(&inp_secret_data, ms) != SUCCESS)
return ATTESTATION_ERROR;
out_secret_data = get_message_exchange_response(inp_secret_data);
if(marshal_message_exchange_response(resp_buffer, resp_length, out_secret_data) != SUCCESS)
return MALLOC_ERROR;
return SUCCESS;
}
功能分析:
创建会话 test_create_session:
功能:调用 create_session 函数创建一个ECDH会话,并将会话上下文保存到全局变量 g_session 中。
实现:直接调用 create_session 函数并返回其结果。
消息交换 test_message_exchange:
功能:与ECDH对等方传输消息,包括序列化发送和反序列化接收。
- EnclaveInitiator.edl
enclave {
include "sgx_eid.h"
include "datatypes.h"
include "dh_session_protocol.h"
trusted{
public uint32_t test_create_session();
public uint32_t test_message_exchange();
public uint32_t test_close_session();
};
untrusted{
uint32_t session_request_ocall([out] sgx_dh_msg1_t *dh_msg1,[out] uint32_t *session_id);
uint32_t exchange_report_ocall([in] sgx_dh_msg2_t *dh_msg2, [out] sgx_dh_msg3_t *dh_msg3, uint32_t session_id);
uint32_t send_request_ocall(uint32_t session_id, [in, size = req_message_size] secure_message_t* req_message, size_t req_message_size, size_t max_payload_size, [out, size=resp_message_size] secure_message_t* resp_message, size_t resp_message_size);
uint32_t end_session_ocall(uint32_t session_id);
};
};
注意,这里只有一点需要解释,在Initiator enclave中定义的edl文件主要包含trusted和untrusted两部分,untrusted部分是该enclave主动发起调用的OCALL接口,这主要因为Initiator enclave作为认证的发起方需要主动向外界发起调用,而下面的Responder Enclave的edl文件中就只有trusted部分而没有untrusted,因为Responder Enclave只要被动响应而不需要主要对外界发起。捋清楚这一点有助于帮我们分析整个程序流程!
2.2.5 EnclaveResponder文件夹
// Enclave2.cpp : Defines the exported functions for the DLL application
#include "sgx_eid.h" // 用于SGX Enclave ID定义的头文件
#include "EnclaveResponder_t.h" // 自动生成的可信部分头文件,包含响应者Enclave的ECALL声明
#include "EnclaveMessageExchange.h" // 消息交换相关头文件
#include "error_codes.h" // 自定义错误码头文件
#include "Utility_E2.h" // 实用函数头文件
#include "sgx_dh.h" // 用于Diffie-Hellman密钥交换的SGX头文件
#include "sgx_utils.h" // 用于SGX实用函数的头文件
#include <map> // 标准库map,用于字典结构
#define UNUSED(val) (void)(val) // 忽略未使用参数的宏定义
std::map<sgx_enclave_id_t, dh_session_t> g_src_session_info_map; // 定义全局映射,存储源会话信息
// 这是预期的发起者的MRSIGNER,请在您的项目中替换为您的响应者Enclave的MRSIGNER!!!(这里已替换为我的真实值)
// 获取签名的Enclave的MRSIGNER的命令:<SGX_SDK安装路径>/bin/x64/sgx_sign dump -enclave <已签名的Enclave> -dumpfile mrsigner.txt
// 在mrsigner.txt中找到签名的Enclave的MRSIGNER(mrsigner->value:),然后替换下面的值
sgx_measurement_t g_initiator_mrsigner = {
{
0x39, 0xc7, 0xa4, 0xae, 0xa9, 0x64, 0xb9, 0x8e, 0xda, 0xa7, 0x22, 0x69, 0x98, 0x85, 0x0f, 0x92,
0x4d, 0xc3, 0x30, 0xa0, 0xac, 0xfe, 0x8f, 0xe0, 0xe2, 0x0a, 0xef, 0x9a, 0xd4, 0xde, 0xa9, 0x48
}
};
/* 函数描述:
* 验证对等Enclave的身份
* 出于演示目的,验证以下几点:
* 1. 对等Enclave的MRSIGNER符合预期
* 2. 对等Enclave的产品ID(Prod ID)符合预期
* 3. 对等Enclave的属性合理,应为已初始化且未设置DEBUG属性(除非项目是以DEBUG选项构建的)
* */
extern "C" uint32_t verify_peer_enclave_trust(sgx_dh_session_enclave_identity_t* peer_enclave_identity)
{
if (!peer_enclave_identity)
return INVALID_PARAMETER_ERROR; // 检查输入参数是否为空
// 检查对等Enclave的MRSIGNER,请在您的项目中启用此检查!!!这步很关键!!!
// 注意:一定是上面的g_responder_mrsigner替换为真实的签名值后才能验证通过!!!
if (memcmp((uint8_t *)&peer_enclave_identity->mr_signer, (uint8_t*)&g_initiator_mrsigner, sizeof(sgx_measurement_t)))
return ENCLAVE_TRUST_ERROR;
// 检查对等Enclave的产品ID和属性(应已初始化)
if (peer_enclave_identity->isv_prod_id != 0 || !(peer_enclave_identity->attributes.flags & SGX_FLAGS_INITTED))
return ENCLAVE_TRUST_ERROR;
// 检查Enclave未以调试模式加载,除非项目是为调试目的构建的
#if defined(NDEBUG)
if (peer_enclave_identity->attributes.flags & SGX_FLAGS_DEBUG)
return ENCLAVE_TRUST_ERROR;
#endif
return SUCCESS; // 验证成功
}
/* 函数描述:操作输入秘密并生成输出秘密 */
uint32_t get_message_exchange_response(uint32_t inp_secret_data)
{
uint32_t secret_response;
// 用户应使用更复杂的加密方法来保护他们的秘密,下面的例子只是一个简单的示例
secret_response = inp_secret_data & 0x11111111;
return secret_response;
}
/* 函数描述:从请求消息生成响应
* 参数描述:
* [输入] decrypted_data: 指向解密数据的指针
* [输出] resp_buffer: 指向响应消息的指针,缓冲区在本函数中分配
* [输出] resp_length: 指向响应长度的指针
* */
extern "C" uint32_t message_exchange_response_generator(char* decrypted_data,
char** resp_buffer,
size_t* resp_length)
{
ms_in_msg_exchange_t *ms; // 定义消息交换结构体指针
uint32_t inp_secret_data; // 定义输入秘密数据
uint32_t out_secret_data; // 定义输出秘密数据
if (!decrypted_data || !resp_length)
return INVALID_PARAMETER_ERROR; // 检查输入参数是否为空
ms = (ms_in_msg_exchange_t *)decrypted_data; // 将解密数据转换为消息交换结构体
if (umarshal_message_exchange_request(&inp_secret_data, ms) != SUCCESS)
return ATTESTATION_ERROR; // 解组消息交换请求失败
out_secret_data = get_message_exchange_response(inp_secret_data); // 生成响应数据
if (marshal_message_exchange_response(resp_buffer, resp_length, out_secret_data) != SUCCESS)
return MALLOC_ERROR; // 序列化消息交换响应失败
return SUCCESS; // 成功返回
}
功能分析
验证对等Enclave身份 verify_peer_enclave_trust:
功能:验证对等Enclave的身份以确保其可信度。
实现:检查输入参数 peer_enclave_identity 是否为空,避免空指针错误。
检查对等Enclave的MRSIGNER是否符合预期(预留代码,实际使用需要取消注释)。
检查对等Enclave的产品ID和属性,确保Enclave已初始化且未设置DEBUG属性。
如果项目是非调试配置,检查Enclave未以调试模式加载。
验证成功则返回 SUCCESS,否则返回适当的错误码。
生成消息交换响应 get_message_exchange_response:
功能:根据输入数据生成响应数据。
实现:对输入数据进行简单的按位与操作生成响应数据(实际使用中应使用更复杂的加密方法保护秘密数据)。返回生成的响应数据。
处理请求消息并生成响应 message_exchange_response_generator:
功能:从请求消息生成响应消息。
实现:检查输入的解密数据和响应长度指针是否为空,确保有有效输入。将解密数据转换为消息交换结构体。解组请求消息以获取输入秘密数据。调用 get_message_exchange_response 生成响应数据。序列化响应数据到缓冲区。返回成功状态或适当的错误码。代码相关性和用途,这段代码用于在SGX Enclave环境中实现本地认证的响应者端。通过验证对等Enclave的身份,确保通信双方的可信度。处理消息交换请求并生成相应的响应,以确保安全通信和数据交换。
注意事项
在实际项目中,应根据实际情况启用对MRSIGNER的检查,以增强安全性。加密方法应根据实际需求进行调整,以确保数据的安全性和完整性。
- EnclaveResponder.edl
enclave {
include "sgx_eid.h"
include "datatypes.h"
include "../Include/dh_session_protocol.h"
trusted{
public uint32_t session_request([out] sgx_dh_msg1_t *dh_msg1, [out] uint32_t *session_id);
public uint32_t exchange_report([in] sgx_dh_msg2_t *dh_msg2, [out] sgx_dh_msg3_t *dh_msg3, uint32_t session_id);
public uint32_t generate_response([in, size = req_message_size] secure_message_t* req_message, size_t req_message_size, size_t max_payload_size, [out, size=resp_message_size] secure_message_t* resp_message, size_t resp_message_size, uint32_t session_id);
public uint32_t end_session(uint32_t session_id);
};
};
2.3 编译执行
2.3.1 Makefile文件分析
include buildenv.mk
# 包含外部的环境变量定义文件 buildenv.mk。
# 根据 SGX_MODE 和相关标志设置 Build_Mode 的变量。
# 如果 SGX_MODE 是硬件模式 (HW), 则根据 SGX_DEBUG 和 SGX_PRERELEASE 决定具体的构建模式。
ifeq ($(SGX_MODE), HW)
ifeq ($(SGX_DEBUG), 1)
Build_Mode = HW_DEBUG
else ifeq ($(SGX_PRERELEASE), 1)
Build_Mode = HW_PRERELEASE
else
Build_Mode = HW_RELEASE
endif
endif
# 如果 SGX_MODE 是仿真模式 (SIM), 同样根据 SGX_DEBUG 和 SGX_PRERELEASE 决定具体的构建模式。
ifeq ($(SGX_MODE), SIM)
ifeq ($(SGX_DEBUG), 1)
Build_Mode = SIM_DEBUG
else ifeq ($(SGX_PRERELEASE), 1)
Build_Mode = SIM_PRERELEASE
else
Build_Mode = SIM_RELEASE
endif
endif
# 定义子目录列表,包含 AppInitiator, AppResponder, EnclaveInitiator, EnclaveResponder 和 App。
SUB_DIR := AppInitiator AppResponder EnclaveInitiator EnclaveResponder App
# 如果 OUTDIR 变量不为空,创建输出目录。
ifneq ($(OUTDIR),)
$(shell mkdir -p $(OUTDIR))
endif
# 定义伪目标 all 和 clean。伪目标不会被当作实际文件处理。
.PHONY: all clean
# all规则,构建每个子目录中的目标。
# 根据不同的构建模式,输出对应的构建模式信息。
all:
for dir in $(SUB_DIR); do \
$(MAKE) -C $$dir; \
done
ifeq ($(Build_Mode), HW_DEBUG)
@echo "The project has been built in hardware debug mode."
else ifeq ($(Build_Mode), HW_RELEAESE)
@echo "The project has been built in hardware release mode."
else ifeq ($(Build_Mode), HW_PRERELEAESE)
@echo "The project has been built in hardware pre-release mode."
else ifeq ($(Build_Mode), SIM_DEBUG)
@echo "The project has been built in simulation debug mode."
else ifeq ($(Build_Mode), SIM_RELEAESE)
@echo "The project has been built in simulation release mode."
else ifeq ($(Build_Mode), SIM_PRERELEAESE)
@echo "The project has been built in simulation pre-release mode."
endif
# clean规则,删除输出目录,然后清理每个子目录中的目标文件,并删除 util 目录下的所有 .o 文件。
clean:
@rm -rf $(OUTDIR)
for dir in $(SUB_DIR); do \
$(MAKE) -C $$dir clean; \
done
rm -f util/*.o
注意,该Makefile
文件依赖于另外一个buildenv.mk
文件以及会调用各个目录下的子Makefile
文件(App、AppInitiator、AppResponder、EnclaveInitiator、EnclaveResponder),在这些依赖Makefile
文件中定义了一些环境变量的值,以及当前目录下编译的方法,此处不再细节展开。
直接执行make
编译。
2.3.2 执行
- 单线程执行,也即一个进程完成全部本地认证流程(即充当发起者也充当响应者);
./app
succeed to load enclaves.
succeed to establish secure channel.
Succeed to exchange secure message...
Succeed to close Session...
- 双线程执行(一个线程充当发起者
appinitiator
,另一个线程充当响应者appresponder
)
当响应者进程先启动时:
./appresponder
Server is ON...
Press Ctrl+C to exit...
启动发起者进程会成功认证并输出如下:
./appinitiator
succeed to load enclave libenclave_initiator.signed.so
succeed to establish secure channel.
Succeed to exchange secure message...
Succeed to close Session...
但是在没有启动响应者进程的情况下,七点发起者进程会认证失败并输出如下(主要是connection error):
./appinitiator
succeed to load enclave libenclave_initiator.signed.so
connection error, Connection refused, line 80.
fail to send and receive message.
failed to establish secure channel: ECALL return 0x0, error code is 0xe3.
2.4 总结
在本地认证(Local Attestation)过程中,SGX Enclave间通过相互认证以确保彼此的可信性。以下是工程中主要代码模块之间的调用和交互关系的总结。
-
项目结构: 一个典型的SGX Local Attestation项目通常包括以下主要模块:
AppInitiator:发起本地认证请求的应用程序。
AppResponder:响应本地认证请求的应用程序。
EnclaveInitiator:包含发起本地认证的逻辑的Enclave。
EnclaveResponder:包含响应本地认证的逻辑的Enclave。 -
流程图概述
AppInitiator <====> EnclaveInitiator
|
V
AppResponder <====> EnclaveResponder
-
具体流程说明
3.1 初始化阶段
加载Enclave:
AppInitiator和AppResponder分别调用sgx_create_enclave函数加载各自的Enclave。
EnclaveInitiator和EnclaveResponder在加载后各自初始化所需的数据和上下文。
3.2 会话创建阶段
发起者创建会话请求:
AppInitiator通过调用test_create_session函数发起会话创建请求,ECALL进入EnclaveInitiator。
EnclaveInitiator调用create_session函数,生成会话密钥并保存会话上下文(dh_session_t)。
发送会话请求到响应者:
EnclaveInitiator通过OCALL接口session_request_ocall将会话请求消息(包含ECDH消息1)发送给AppResponder。
3.3 会话处理阶段
处理会话请求:
AppResponder接收会话请求消息后,通过OCALL接口调用EnclaveResponder的session_request函数。
EnclaveResponder生成响应消息(包含ECDH消息2和Session ID),并通过OCALL接口exchange_report_ocall返回给AppInitiator。
处理会话响应:
AppInitiator接收到会话响应消息后,通过ECALL接口调用EnclaveInitiator的exchange_report函数。
EnclaveInitiator处理会话响应消息,生成会话密钥并保存上下文。
3.4 消息交换阶段
发送消息:
AppInitiator调用test_message_exchange函数,通过ECALL进入EnclaveInitiator。
EnclaveInitiator通过OCALL接口send_request_receive_response发送消息请求到AppResponder。
处理消息请求:
AppResponder接收消息请求后,通过OCALL接口调用EnclaveResponder的generate_response函数。
EnclaveResponder处理消息并生成响应消息,返回给AppInitiator。
接收消息响应:
AppInitiator接收到消息响应后,EnclaveInitiator处理响应数据,完成一次消息交换。
3.5 会话关闭阶段
关闭会话:
AppInitiator调用test_close_session函数,通过ECALL进入EnclaveInitiator。
EnclaveInitiator调用close_session函数关闭会话,并通过OCALL接口end_session_ocall通知AppResponder。
处理会话关闭请求:
AppResponder接收会话关闭请求后,通过OCALL接口调用EnclaveResponder的end_session函数销毁会话。 -
代码模块交互
以下是各主要模块的调用关系:
4.1 AppInitiator/Main.cpp 调用 sgx_create_enclave 初始化 EnclaveInitiator。
4.2 EnclaveInitiator 有以下交互:
ECALL:test_create_session 创建会话。
OCALL:session_request_ocall 从 AppResponder 获取会话响应。
处理会话响应的 OCALL:exchange_report_ocall。
ECALL:test_message_exchange 发送消息并接收响应。
处理消息交换的 OCALL:send_request_receive_response。
ECALL:test_close_session 关闭会话。
处理会话关闭的 OCALL:end_session_ocall。
4.3 AppResponder/Main.cpp 调用 sgx_create_enclave 初始化 EnclaveResponder。
4.4 EnclaveResponder 有以下交互:
OCALL:generate_and_send_session_msg1_resp 生成并发送会话消息1。
处理会话请求的 OCALL:process_exchange_report。
处理消息交换请求的 OCALL:process_msg_transfer。
处理会话关闭请求的 OCALL:process_close_req。
三.参考文献
王进文, 江勇, 李琦, 等. SGX 技术应用研究综述[J]. 网络新媒体技术, 2017, 6(5): 3-9.
四.感谢支持
完结撒花!后续将持续输出,形成Intel SGX的系列教程,并结合密码学算法设计更复杂功能。希望看到这里的小伙伴能点个关注,也欢迎志同道合的小伙伴一起广泛交流。
码字实属不易,如果本文对你有10分帮助,就赏个10分把,感谢各位大佬支持!