OpenSGX的编译与使用

一、OpenSGX介绍

英特尔提供的SGX是一个扩展的x86指令集架构,它能让应用程序在叫做enclave的受保护的容器内运行。为不信任特权软件(如:操作系统和虚拟机管理程序)上的应用程序提供隔离的执行和内存保护。通过减少可信计算基(TCB),SGX提供窄攻击面。掌握了所有的软件组件和硬件,除CPU包外的敌人不能危害到SGX应用的代码和数据。SGX不仅保证了程序代码和数据的完整性,还保证了SGX程序的保密性。
OpenSGX是基于QEMU的二进制翻译实现的软件SGX仿真器,它在指令集水平仿真SGX硬件组件。此外,OpenSGX完全是一个用于SGX开发的平台,它包含仿真硬件和操作系统组件,enclave程序加载器,一个OpenSGX用户库,还支持调试和性能监控。使用OpenSGX能实现调试和评估一个应用程序。

图1:OpenSGX的设计和活跃enclave程序的内存状态概述。包含enclave程序、包信息和SGX库的打包程序在同一虚拟地址空间中作为单个进程运行。

使用QEMU实现SGX指令以及仿真硬件组件。特别的,利用它的二进制翻译功能,在QEMU的用户空间仿真模式的顶层实现OpenSGX。图1展示整体设计组件。OpenSGX包含6个一起工作的组件,提供功能齐全的SGX开发环境。6个组件如下:

  1. Intel SGX硬件仿真(Emulated Intel SGX hardware):硬件组件包括SGX指令,SGX数据结构,EPC和它的访问保护,以及SGX处理键内QEMU软件。
  2. 操作系统仿真(OS emulation):系统调用仿真enclave程序用于执行SGX操作(如:enclave配置和动态内存分配)。
  3. enclave程序加载(Enclave program loader):一个用于自动处理通过enclave代码和数据段加载到EPC及适当的提供enclave栈和堆区域的加载器。
  4. OpenSGX用户库(libsgx):一个用在enclave内部和外部的方便的库。它为所有SGX用户级指令提供封装,为高级应用程序提供接口。
  5. 调试器支持( Debugger support):扩展gbd来映射仿真指令(这里,OpenSGX也通过gdb公开SGX数据结构的密钥)
  6. 性能监视(Performance monitoring):性能计数器/分析器,允许用户手机enclave程序的性能数据。

需要注意的是OpenSGX不支持英特尔SGX二进制兼容性。现在还没有具体或标准的二进制级别的互操作。虽然OpenSGX支持大部分的具体的指令,我们并不实现所有的指令。例如,OpenSGX并没有实现Intel SGX指定的调试指令,但是软件层能提供丰富的调试环境(例如,熟悉的GBD存根)。OpenSGX是一种软件仿真器,不提供安全保证。它的安全保证和Intel SGX不是一个层次的。

二、OpenSGX编译

2.1 环境依赖

操作系统:Ubuntu 18.04.6
包依赖:libglib, zlib, libaio, autoconf, libtool, libssl-dev, libelf-dev

##Ubuntu
apt-get install libglib2.0-dev zlib1g-dev libaio-dev autoconf libtool libssl-dev libelf-dev

2.2 安装编译

  1. 下载源码
git clone https://github.com/sslab-gatech/opensgx.git
  1. 编译QEMU
cd opensgx/qemu
./configure-arch
make -j $(nproc)

注:
a. 如果出现下图中的问题,是使用cmake编译低版本gcc时候出现的。

在这里插入图片描述

解决措施,将user-exec.c中60行ucontext修改为ucontext_t,执行make clean后重新编译。

在这里插入图片描述

b. 如果出现下图中的问题,同a。

在这里插入图片描述

解决措施,将user-exec.c中230行ucontext修改为ucontext_t,执行make clean后重新编译。

在这里插入图片描述

  1. 编译sgx库
cd ..
make -C libsgx
  1. 编译user level
make -C user

至此,OpenSGX编译完成。

2.3 OpenSGX源代码结构

opensgx用于运行应用程序OpenSGX工具链
qemu用于SGX指令的硬件仿真
qemu/target-i386/sgx-helper.cENCLU/ENCLS指令的辅助功能
qemu/target-i386/sgx.hEPC, EPCM以及SGX的数据结构
libsgxOpenSGX库
libsgx/musl-libc/为OpenSGX程序定制的libc库
libsgx/polarssl/OpenSGX程序轻加密库
libsgx/sgx-basic.cOpenSGX为封装ENCLU指令封装的函数
libsgx/sgx-entry.c进入封装的enclave程序
user系统仿真,系统调用接口,用户水平API,OpenSGX测试用例
user/sgx-tool.c用于创建密钥和数据结构的工具
user/sgx-runtime.c启动enclave的运行库
user/sgx-user.c用户水平API及系统调用接口
user/sgx-kern.c用于创建enclave的操作系统级封装
user/sgx-loader.c用于执行enclave程序的enclave加载器
user/sgx-trampoline.c用于SGX库支持的Trampoline封装
user/sgx.ldsOpenSGX的链接脚本
user/test/简单测试用例
gdb用于调试EPCM,EPC和SECS的Python脚本

2.4 SGX硬件配置

最初,硬件组件如EPC,EPCM和处理器密钥实际上是处理器的组成部分。此外,软件组件,如 SIGSTRUCTEINITTOKEN 应驻留在EPC为受保护的数据结构的一部分。要模拟这些组件,OpenSGX提供了一个名为“SGX-tool”来指定硬件配置,如EPC的大小和SGX处理器密钥。它位于user/。

2.4.1 配置硬件组件

要编译,构建和执行程序OpenSGX,两个密钥必需的。一个是处理器密钥,而另一个是enclave密钥。这两个密钥可以通过使用sgx-tool -k选项生成。默认情况下,OpenSGX使用处理器的密钥对为128位,使用RSA的enclave密钥对为3072位。

cd user/
##enclave key 3072
./sgx-tool -k 3072
##device key 128
./sgx-tool -k 128

每次生成完密钥后,需要手动创建密钥文件,将生成的密钥相关参数复制进去,如在user/conf/下创建enclavekey.conf和devicekey.conf,将两次生成的密钥分别保存至这两文件中。
EPC和EPCM的大小也是可配置的。在qemu/target-i386/directory中,有一个“sgx.h”头文件。在第26行, 定义了 NUM_EPC变量(默认,1500)。它是一个enclave开始包含的EPC页的数量。如果你想改变一个enclave的大小,你可以更改此值,并重新编译qemu。

2.4.2 配置数据结构

有两个核心数据结构来验证enclave和enclave程序员的身份。那是 SIGSTRUCTEINITTOKEN
SIGSTRUCT 从enclave签名者(OpenSGX程序员)得到enclave信息,并且还具有enclave的散列值。它用来验证发动encalve时enclave签名者的身份。
EINITTOKEN 在EINIT指令验证目标enclave是否允许推出时使用。它包含一个通过启动密钥(处理器密钥)计算的加密的MAC,以检查enclave是否是在运行在SGX启用的平台。
下面是产生 SIGSTRUCTEINITTOKEN 数据结构的具体过程。

  1. SIGSTRUCT

首先,生成空的数据结构;

cd user/
##在生成SIGSTRUCT前,需要创建一个空文件用以保存该数据结构
touch conf/sigstruct.conf
##生成SIGSTRUCT数据结构,此时为初始状态,绝大多数参数填充为0
./sgx-tool -S conf/sigstruct.conf
##在生成之后,需要手动将打印在bash中的数据结构手动保存至创建的空文件中

其次,生成enclave hash;

##此处-m的参数可以选取test目录下的一个二进制文件(存疑:随意一个二进制文件都可)
./sgx-tool -m test/simple
##在哈希生成成功之后,将该值保存至conf/sigstruct.conf中的ENCLAVEHASH一项

最后,使用enclave密钥对该数据结构进行签名。

./sgx-tool -s conf/sigstruct.conf --key=conf/enclavekey.conf
##将屏幕打印的数据结构覆盖至conf/sigstruct.conf
  1. EINITTOKEN

首先,生成空的数据结构;

cd user/
##在生成EINITTOKEN前,需要创建一个空文件用以保存该数据结构
touch conf/einittoken.conf
##生成EINITTOKEN数据结构,此时为初始状态,绝大多数参数填充为0
./sgx-tool -E conf/einittoken.conf
##在生成之后,需要手动将打印在bash中的数据结构手动保存至创建的空文件中

最后,使用device密钥对该数据结构生成MAC。

./sgx-tool -M conf/einittoken.conf --key=conf/devicekey.conf
##将屏幕打印的数据结构覆盖至conf/einittoken.conf

2.5 OpenSGX二进制编译

要编译二进制OpenSGX,它需要多个前提步骤。
首先,SGX应用程序开发者创建了他/她自己的签名密钥签名者身份认证;在这之后,你可以编译OpenSGX程序和使用自己的密钥进行签名,在这个阶段中,OpenSGX还可以通过计算代码和数据区域的哈希值计算enclave程序同一性(身份);最后,你可以运行你自己OpenSGX的应用程序。
下面以user/demo/hello.c为例,编译运行步骤如下:

##生成自己的签名密钥,当前目录下会生成sign.key
./opensgx -k
##生成hello.sgx,编译一个OpenSGX程序
./opensgx -c user/demo/hello.c
##生成hello.conf,对OpenSGX程序签名和计算身份
./opensgx -s user/demo/hello.sgx --key sign.key
##运行OpenSGX程序
./opensgx user/demo/hello.sgx user/demo/hello.conf

效果如下:
在这里插入图片描述
至此,OpenSGX一个简单的hello程序跑通,说明OpenSGX编译成功。

三、OpenSGX应用实现

实现一个OpenSGX程序类似于正常的C程序。由于对SGX程序的操作系统支持,OpenSGX支持系统调用接口和用户级的API,用户只需用他们建立并执行OpenSGX二进制文件。
唯一的区别在于,使用enclave_main()而不是主main(),使用sgx_exit(null)代替return。
在OpenSGX程序中,在编译OpenSGX程序时,有可能使用现有的libc库函数或通过将存档文件加密的库函数如polarssl。由于OpenSGX提供它自己的定制链接脚本和加载器,所以通过修改,它们用到其他库中的enclave二进制文件。
下面是简单的Hello World程序的源代码:

#include "test.h"

void enclave_main()
{
    char *hello = "hello sgx!\n";
    puts(hello);
    sgx_exit(NULL);
}

3.1 Trampoline & Stub

OpenSGX通过使用共享代码和数据内存提供更严格通信协议,分别为Trampoline & Stub。使用Trampoline & Stub的定义狭窄enclave接口,这使得强制执行相关的安全特性易于处理。enclave首先在stub设置输入参数,然后调用一个预定义的处理程序,trampoline,通过退出其enclave模式(即,通过调用EEXIT)。
(没理解。。)

3.2 使用libc库

在OpenSGX应用程序中使用libc库,使用trampoline and stub接口是通过修改几个原glibc库的代码。下面是一个包含trampoline and stub接口修改libc函数的源代码。在此,stub的数据结构设置对应的共享区(STUB_ADDR),函数类型(FCODE)和在库函数输入/输出参数。在这里,输出参数(out_arg)是函数的输入变量,输入参数(in_arg)是函数的返回值。out_arg值被设定之后,它会调用“sgx_exit(stub->trampoline )”退出enclave 模式。

ssize_t read(int fd, void *buf, size_t count)
{
	sgx_stub_info *stub = (sgx_stub_info *)STUB_ADDR;
	int tmp_len;
	ssize_t rt;
	int i;
	
	rt = 0;
	for (i = 0; i < count / SGXLIB_MAX_ARG + 1; i++) {
		stub->fcode = FUNC_READ;
		stub->out_arg1 = fd;
		if (i == count / SGXLIB_MAX_ARG)
			tmp_len = (int)count % SGXLIB_MAX_ARG;
		else
			tmp_len = SGXLIB_MAX_ARG;
		stub->out_arg2 = tmp_len;
		sgx_exit(stub->trampoline);
		memcpy((uint8_t *)buf + i * SGXLIB_MAX_ARG, stub->in_data1, tmp_len);
		rt += stub->in_arg1;
	}
	return rt;
}

一但主机程序或OS处理enclave请求时,它把结果或返回值存储到stub中,而且通过调用ERESUME重新进入enclave模式。将程序的控制权交还给已知enclave位置后,enclave最终能得到返回值(e.g,in_arg1 in stub)。

static int sgx_read_tramp(int fd, void *buf, size_t count)
{
	return read(fd, buf, count);
}
void sgx_trampoline()
{
	switch (stub->fcode) {
	case FUNC_PUTS:case FUNC_READ:
	stub->in_arg1 = sgx_read_tramp(stub->out_arg1, stub->in_data1, (size_t)stub->out_arg2);
	break;}

通过将trampoline & stub 代码添加到OpenSGX用户库和glibc库函数中,可以在enclave程序中使用它们。它们在enclave程序中的使用与实现Hello-world示例中所示的普通C程序完全相同(请参见puts函数)

3.3 使用第三方库

要使用enclave程序中的第三方库,他们的存档文件(*.a)中或目标文件(*.o)应该被编译以及连接到SGX程序。目前,OpenSGX支持mbedtls(轻量级SSL库)和OpenSSL库。您可以不需要任何修改的使用enclave程序内的库,在OpenSGX,我们使用mbedtls库来计算用于 mrenclave的SHA256哈希和用于报告数据结构的MAC(消息身份验证码Message Authenticate Code)。使用这些库的唯一事情就是修改Makefile文件;你应该在编译过程中链接归档文件或目标文件复制到目标SGX程序(见user/ Makefile中的细节),这是使用enclave内OpenSSL库函数的示例代码。

/user/test/openssl/simple-openssl.c

#include "../test.h"
#include <openssl/bn.h>
#include <openssl/rsa.h>

BIGNUM *bn = NULL;
RSA *rsa = NULL;

void enclave_main()
{
	// Bignum allocation for global variable
	bn = BN_new();
	// Bignum allocation for local variable
	BIGNUM *bn2 = BN_new();
	// RSA allocation for global variable
	rsa = RSA_new();
	// RSA allocation for local variable
	RSA *rsa2 = RSA_new();
	printf("%x\n", (unsigned long)rsa2);
}

3.4 参数向量

OpenSGX还支持参数向量指令输入传递给enclave程序。为了实现这个属性,OpenSGX提供sgx-entry.S中的汇编代码传递位于libsgx目录中argc和argv,调用enclave_main之前,sgx-runtime保存参数向量到用户空间寄存器(RDI,RDX等),并将其传递到入口点(enclave_start)。在这里,“SGX-entry.S”文件定义了enclave_start象征,它得到的寄存器的值,并把它们进栈。你可以在/user/sgx-runtime.c和/libsgx/sgx-entry.S文件中看到参数向量处理过程的细节。下面是使用参数向量的示例代码。

/user/test/simple-arg.c

#include "test.h"

void enclave_main(int argc, char **argv)
{
	printf("argc = %d\n", argc);
	puts(argv[0]);
	puts(argv[1]);
	puts(argv[2]);
}

上述以 “test_arg”为参数简单的带参数的程序执行的结果如下。类似的参数向量的一般用法,它可以传递参数argc和argv到enclave_main.需要注意的是argv[0] 包括“./…/user/sgx-runtime”字符串,因为test.sh脚本执行sgx-runtime来加载SGX二进制和调用EENTER指令。
在这里插入图片描述

3.5 enclave和non-enclave过程的分离

要实现SGX程序,最重要的是减少了TCB(可信计算基)。为了实现这一目标,SGX程序员需要尽量减少SGX程序的大小,例如,如果程序员尝试实现和执行现有的应用程序,他们需要把核心部分分开(例如,私钥创建签名),把他们变成enclave程序。在本教程中我们把在enclave内执行的进程叫做enclave进程,把其他的进程叫做non-enclave进程。在本节中,我们提供了他们如何互相通信,并给他们一个简单的例子。
enclave和non-enclave进程通过管道协议进行通信,当non-enclave进程请求核心操作的(例如,要求创建签名),它发送请求信息给enclave进程,然后,它发送输入的数据(例如,数据将签署)到enclave进程。enclave进程接收non-enclave的输入数据并在enclave执行秘密操作(例如,创建一个签名)。表明秘密应用程序(例如,私有密钥)不会被泄露以及在enclave内部被安全保护,安全操作完成后,将结果发送回no-encalve进程。enclave 进程和non-enclave进程位于 user/test/simple-pipe.c和user/non_enclave/simple-pipe.c。

3.6 远程认证

认证相关的源代码OpenSGX支持本地/远程认证。在libsgx目录有几个与认证相关函数。您可以通过include "sgx-lib.h"使用本地/远程认证库函数到enclave程序。下面是相关认证函数原型。

Part of /libsgx/include/sgx-lib.h

extern int sgx_enclave_read(void *buf, int len);
extern int sgx_enclave_write(void *buf, int len);
 
/* SIGSTRUCT parsing function */
extern sigstruct_t *sgx_load_sigstruct(char *conf);
 
/* Attestation supporting funcitons */
extern int sgx_make_server(int port);
extern int sgx_connect_server(const char *target_ip, int target_port);
extern int sgx_get_report(int fd, report_t *report);
extern int sgx_read_sock(int fd, void *buf, int len);
extern int sgx_write_sock(int fd, void *buf, int len);
extern int sgx_match_mac(unsigned char *report_key, report_t *report);
extern int sgx_make_quote(const char* pers, report_t *report, unsigned char *rsa_N, unsigned char *rsa_E);
 
/* Intra attestation supporting functions */
extern int sgx_intra_attest_challenger(int target_port, char *conf);
extern int sgx_intra_attest_target(int port);
 
/* Remote attestation supporing functions */
extern int sgx_remote_attest_challenger(const char *target_ip, int target_port, const char *challenge);
extern int sgx_remote_attest_target(int challenger_port, int quote_port, char *conf);
extern int sgx_remote_attest_quote(int target_port)

也可再libsgx中查看函数代码,相关文件如下:

libsgx/sgx-attest.c
libsgx/sgx-intra-attest.c
libsgx/sgx-remote-attest.c

3.7 内/远程认证机制

OpenSGX产生两种认证特性,一种是本地认证,一种是远程认证。
本地认证出现在两个enclave之间,challenger和target。challenger enclave想要验证target enclave,使用套接字与target enclave相连。然后challenger enclave从配置文件(.conf)中得到target enclave的身份。challenger enclave通过target enclave的配置文件加载target enclave 的SIGSTRUCT ,然后challenger enclave通过EREPORT指令创建REPORT 并通过套接字写发送给target enclave,target enclave收到REPORT 然后提取报告密钥来计算MAC。然后REPORT 验证MAC的REPORT 并把它的REPORT 发送给challenger enclave。最后,challenger enclave使用target enclave的REPORT相互验证来反复验证这个过程。
远程认证比本地部认证更加困难。远程认证过程需要三个enclave,challenger, target, 和quoting enclave。首先,challenger enclave建立与target enclave的套接字连接,然后向target enclave发送请求。target enclave收到target enclave发送的请求后,就和存在于同一个SGX平台的quoting enclave开始内认证,两个enclave完成相互验证后,target enclave发送认证成功信息给quoting enclave,然后quoting enclave生成QUOTE and RSA 密钥并发送发送给target enclave。target enclave收到QUOTE and RSA 密钥后将其转发给challenger enclave。最后challenger enclave使用RSA密钥对验证QUOTE来验证target enclave。OpenSGX中所有的远程认证都是基于SGX标准实现的,使用RSA密钥方案作为替代EPID。我们使用公共签名方案(RSA)作为证明的概念,并留下采用EPID作为今后的工作。

3.8 测试本/远程认证

OpenSGX支持相关的内部/远程认证测试代码,因为target/quoting enclave配置文件对内/远程认证是必需的,所以首先需要在demo目录新建配置文件。可以通过键入下面的命令简单地测试认证码。

  1. 本地认证测试

Terminal #1

./opensgx -k
./opensgx -c user/demo/simple-intra-attest-target.c
./opensgx -s user/demo/simple-intra-attest-target.sgx --key sign.key
./opensgx user/demo/simple-intra-attest-target.sgx user/demo/simple-intra-attest-target.conf

Terminal #2

cd user
./test.sh test/simple-intra-attest-challenger
  1. 远程认证测试

Terminal #1

./opensgx -k
./opensgx -c user/demo/simple-remote-attest-quote.c
./opensgx -s user/demo/simple-remote-attest-quote.sgx --key sign.key
./opensgx user/demo/simple-remote-attest-quote.sgx user/demo/simple-remote-attest-quote.conf

Terminal #2

cd user
./test.sh test/simple-remote-attest-target

Terminal #3

cd user
./test.sh test/simple-remote-attest-challenger

由于上面的测试在本地计算机环境设计的测试,远程验证测试本地使用套接字连接。然而如果你想测试远程认证在远程OpenSGX环境,可以简单的修改测试代码中的IP地址和端口。
如果测试成功,会打印出 “Intra/Remote Attestation Success!”,另外,远程认证测试,您可能需要等待几秒钟,因为生成RSA密钥和解密QUOTE需要一些时间。

3.9 调试支持

由于OpenSGX基于QEMU的用户模式仿真实现,其二进制转换使调试更加困难,因为一个调试器只能观察转换后的指令。 从而,OpenSGX扩展gdb映射仿真的该指令。这下面是一个调试OpenSGX应用程序过程的例子。

  1. 用调试操作运行目标应用程序
./opensgx -d 1234 user/demo/hello.sgx user/demo/hello.conf &
  1. 连接远程dbg到目标端口
gdb user/sgx-runtime
(gdb) target remote localhost:1234
(gdb) b sgx-runtime.c:63
(gdb) c

在这里插入图片描述

  1. 找到一个文本偏移
readelf -S user/demo/hello.sgx | grep text

在这里插入图片描述

  1. 通过指定文本偏移量在gdb中添加符号文件
(gdb) add-symbol-file user/demo/hello.sgx 0000000050000110

在这里插入图片描述

  1. 在enclave二进制文件上设置断点并开始调试
(gdb) b enclave_main
(gdb) c

3.10 性能监控

OpenSGX还支持性能监视功能。因为它是一个软件仿真器,它不能提供精确的性能指标。但是,OpenSGX通过提供类似于性能计数器的模拟性能统计数据的帮助开发人员和研究人员推测潜在性能问题。它公开了一个系统调用来查询OpenSGX仿真数据。如发生上下文切换次数、SGX指令的执行次数等等。此外,opensgx工具链提供-i选项(通过使用QEMU的tcg-插件),以计算enclave程序以软件方式消耗的CPU周期数。

cd user
./test.sh -i test/simple-hello

Example: 给定的Hello World程序的性能测试
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值