【SGX系列教程】(四)Intel-SGX 官方示例分析(SampleCode)——LocalAttestation

news2024/12/24 3:30:47

文章目录

  • 一.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.生成认证报告

  • 认证报告生成:请求者和响应者各自生成包含测量值(MRENCLAVEMRSIGNER)、会话密钥等信息的报告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 编译流程

  1. 首先需要安装SGX sdk,以及等等对SGX的支持测试,具体可参考之前的一篇博客:【SGX系列教程】(一);
  2. 安装openssl,目的是为enclave.so生成签名私钥:

openssl genrsa -out Enclave/Enclave_private_test.pem -3 3072

  1. 确保sdk路径生效(source),基本在安装sdk时会指定安装路径为:/opt/intel/sgxsdk
  2. 使用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

2.1.2 执行流程(双进程执行 or 单进程执行,在后面执行部分有展示效果)

  1. Linux系统下安装SGX驱动和PSW(具体可以参考系列教程一);
  2. 可以通过两个进程进行本地认证流程,进入bin子文件夹:
    a. 运行 ./appresponder: 这将启动一个进程,使其充当本地认证响应者
    b. 运行 ./appinitiator: 这将启动一个进程,使其充当本地认证发起者
  3. 如果您想通过一个进程进行完成全部本地认证流程,进入bin子文件夹直接运行 ./app

2.1.3 如何获取已签名的Enclave的MRSIGNER

  1. 安装SDK,适用于Linux操作系统。
  2. 执行以下命令以获取已签名的Enclave的MRSIGNER:
<SGX_SDK Installation Path>/bin/x64/sgx_sign dump -enclave <Signed Enclave> -dumpfile mrsigner.txt
  1. 在文件mrsigner.txt中找到已签名的Enclave的MRSIGNERmrsigner->value:)(找到 mrsigner->value: 字段,即为签名该Enclave的签名者的测量值)

   注意,这里的<Signed Enclave>指的是编译之后针对每个enclave生成的签名文件,通常是.signed.so.signed.dll 扩展名(具体取决于目标平台)。这里是在bin目录下生成的libenclave_initiator.signed.solibenclave_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到程序代码中以使用真实的签名者测量值(相当于公钥):

在这里插入图片描述

在这里插入图片描述

   通过上述步骤,可以成功生成并替换已签名的EnclaveMRSIGNER信息。

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间通过相互认证以确保彼此的可信性。以下是工程中主要代码模块之间的调用和交互关系的总结。

  1. 项目结构: 一个典型的SGX Local Attestation项目通常包括以下主要模块:
    AppInitiator:发起本地认证请求的应用程序。
    AppResponder:响应本地认证请求的应用程序。
    EnclaveInitiator:包含发起本地认证的逻辑的Enclave。
    EnclaveResponder:包含响应本地认证的逻辑的Enclave。

  2. 流程图概述

AppInitiator  <====>  EnclaveInitiator
    |
    V
AppResponder  <====>  EnclaveResponder
  1. 具体流程说明
    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函数销毁会话。

  2. 代码模块交互
    以下是各主要模块的调用关系:
    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分把,感谢各位大佬支持!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1878174.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

青岛网站建设一般多少钱

青岛网站建设的价格一般会根据网站的规模、功能、设计风格等因素来定&#xff0c;价格会存在着一定的差异。一般来说&#xff0c;一个简单的网站建设可能在数千元到一万元之间&#xff0c;而一个复杂的大型网站建设可能会需要数万元到数十万元不等。所以在选择网站建设服务时&a…

DAY17-力扣刷题

1.相同的树 100. 相同的树 - 力扣&#xff08;LeetCode&#xff09; 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 class Solution {public…

守护你的每一步:揭秘电子厂劳保鞋的秘密

在电子厂的繁忙车间里&#xff0c;工友们忙碌的身影中&#xff0c;你是否注意到那一双双看似普通的劳保鞋&#xff1f;它们不仅承载着工人们辛勤的汗水&#xff0c;更是守护他们每一步安全的重要装备。今天&#xff0c;就让我们一起揭秘电子厂劳保鞋的秘密&#xff0c;看看它们…

一站式企业服务平台能够帮助企业解决哪些问题?

近年来一站式企业服务平台备受区域政府及园区管理者的青睐&#xff0c;充当着区域政府或园区的千里眼和顺风耳&#xff0c;可以用来捕捉与区域经济发展相关的信息&#xff0c;也可以用来倾听企业的诉求&#xff0c;更是成为了区域深抓企业服务的多面手。 同时&#xff0c;一站式…

【漏洞复现】学分制系统GetTimeTableData SQL注入

0x01 产品简介 学分制系统由上海鹏达计算机系统开发有限公司研发&#xff0c;是基于对职业教育特点和需求的深入理解&#xff0c;结合教育部相关文件精神&#xff0c;并广泛吸纳专家、学者意见而开发的一款综合性管理系统。系统采用模块化的设计方法&#xff0c;方便学校根据自…

Java对应C++ STL的用法

sort&#xff1a; 1&#xff1a;java.util.Arrays中的静态方法Arrays.sort()方法&#xff0c;针对基本数据类型和引用对象类型的数组元素排序 2&#xff1a;java.util.Collections中的静态方法的Collections.sort()方法&#xff0c;针对集合框架中的动态数组&#xff0c;链表&…

大数据开发如何管理项目

在面试的时候总是 会问起项目&#xff0c;那在大数据开发的实际工作中&#xff0c;如何做好一个项目呢&#xff1f; 目录 1. 需求分析与项目规划1.1 需求收集与梳理1.2 可行性分析1.3 项目章程与计划 2. 数据准备与处理2.1 数据源接入2.2 数据仓库建设2.3 数据质量管理 3. 系统…

2024年4家HTTP代理服务商网站最新测评

一、芝麻HTTP芝麻HTTP作为代理服务领域的佼佼者&#xff0c;其HTTP代理服务同样表现出色。凭借海量IP资源和高效稳定的性能&#xff0c;芝麻HTTP为用户提供了卓越的代理服务体验。 特点与优势 ① 海量IP资源&#xff1a;拥有庞大的代理IP池&#xff0c;确保用户能够随时获取到…

安装OpenHarmony编译库和工具集

一、搭建开发环境 1.1、Ubuntu搭建&#xff0c;参考 VMware完美安装Ubuntu20.04-CSDN博客文章浏览阅读286次&#xff0c;点赞5次&#xff0c;收藏3次。详细介绍了VMware下安装Ubuntu20.04https://blog.csdn.net/longyuzi/article/details/139935769 1.2、拉取OpenHarmony源码…

数据结构速成--查找

由于是速成专题&#xff0c;因此内容不会十分全面&#xff0c;只会涵盖考试重点&#xff0c;各学校课程要求不同 &#xff0c;大家可以按照考纲复习&#xff0c;不全面的内容&#xff0c;可以看一下小编主页数据结构初阶的内容&#xff0c;找到对应专题详细学习一下。 目录 …

核方法总结(四)——高斯过程回归学习笔记

一、定义 基于核方法的线性回归模型和传统线性回归一样&#xff0c;可以用未知数据进行预测&#xff0c;但不能确定 预测的可信度。在参考书第二章中可知&#xff0c;基于贝叶斯方法可以实现对未知数据依概率预测&#xff0c;进而可得到预测的可信度。这一方法中&#xff0c;通…

C++中的三大池:线程池,内存池,数据库连接池

C中有三大池&#xff0c;即我们常说的&#xff1a;线程池&#xff0c;内存池&#xff0c;数据库连接池。 一.线程池 多线程同时访问共享资源造成数据混乱的原因就是因为CPU的上下文切换导致&#xff0c;线程池就是为了解决此问题而生。 多线程常用的有&#xff1a;std::threa…

编译原理必考大题:子集法将NFA转换为DFA【详细讲解,真题实战】

写在最前&#xff0c;本文为实战向&#xff0c;解决问题的求法&#xff0c;理论基础见我的上一篇博客,当然了&#xff0c;只做题的话&#xff0c;看这个就够用了 文章目录 NFA转换为DFA如何求ε-闭包&#xff1f;如何求状态转换弧集?NFA转换为DFA的方法论真题实战例题一例题二…

认识100种电路之稳压电路

在电子电路中&#xff0c;稳压电路扮演着至关重要的角色。那么&#xff0c;为什么电路需要稳压&#xff1f;稳压的原理又是什么&#xff1f;以及稳压需要用到哪些元器件&#xff0c;数量又有多少呢&#xff1f;今天&#xff0c;就让我们一同揭开稳压电路的神秘面纱。 【电路为什…

版本控制系统:Git 纯应用(持续更新)

基本操作 ctrl上行键&#xff1a;上次代码 本地仓库&#xff1a;Git init 新建文件&#xff1a;touch xxxx.xxx 查看状态&#xff1a;Git status 文件从工作区——暂存区&#xff1a;Git add ./文件名(.是通配符代表所有) 暂存区——仓库&#xff1a;Git commit -m &…

【接口自动化测试】第三节.实现项目核心业务接口自动化

文章目录 前言一、实现登录接口对象封装和调用 1.0 登录接口的接口测试文档 1.1 接口对象层&#xff08;封装&#xff09; 1.2 测试脚本层&#xff08;调用&#xff09;二、课程新增接口对象封装和调用 2.0 课程新增接口的接口测试文档 2.1 接口对象层…

软考高项备考经验分享

高项备考经验分享 在备考被论文卡两次后&#xff0c;这次终于通过了高项&#xff0c;分不是很高&#xff0c;比较幸运&#xff0c;对这次考试做个总结与分享&#xff0c;希望对同学们有所帮助。 1、备考时间 首先备考时间上不建议拉的太长&#xff0c;每天坚持看书3~6个月时…

Ubuntu22.04 源码安装 PCL13+VTK-9.3+Qt6,踩坑记录

Ubuntu 22.04LTS;cmake-3.25.0;VTK-9.3;PCL-1.13;Qt6.6 PCL可以通过 apt 命令直接安装(sudo apt install libpcl-dev),apt 命令安装的 VTK 是简略版,没有对 Qt 支持的包,所以笔者使用源码安装 PCL 和 VTK。 1. 安装 VTK 1) 安装 ccmake 和 VTK 依赖项: sudo apt-g…

食品行业怎么用JSON群发短信

食品作为日常生活不可缺少的元素&#xff0c;市场需求是很稳定的&#xff0c;但是份额就那么多&#xff0c;商家都要来抢占的话&#xff0c;就需要运营推广各凭本事&#xff0c;市场运营中选择合适的推广方式&#xff0c;可以增加店铺销售额&#xff0c;很多实体店或商城都会建…

几个常见的FPGA问题之序列发生器、编码器、D触发器

几个常见的FPGA问题之序列发生器、编码器、D触发器 语言 :Verilg HDL 、VHDL EDA工具: Vivado 几个常见的FPGA问题之序列发生器、编码器、D触发器一、引言二、背景1、序列发生器(Sequence Generator)2、编码器(Encoder)3、D触发器(D Flip-Flop)二、问题及解决方案1. 序…