Windows10下使用Intel SGX功能(二):helloworld流程分析
参考文献
- helloworld README 文档
- API reference and supported libraries
- Build and Sign an Enclave
- Build options for using Open Enclave SDK libraries
- Open Enclave SDK CMake Package
- tee-internal-core-api-specification
- Open Enclave edger8r
helloworld
代码解读
代码运行环境: windows 10
代码目录结构
调用流程
1. 首先定义 host 和 enclave 之间相互调用的函数
定义见 helloworld.edl
enclave {
from "openenclave/edl/syscall.edl" import *;
from "platform.edl" import *;
trusted {
public void enclave_helloworld();
};
untrusted {
void host_helloworld();
};
};
其中 public void enclave_helloworld()
是 enclave 向 host 暴露的接口。 void host_helloworld();
是 enclave 向 host 返回数据的回调函数。
2. 实现 enclave 端的 enclave_helloworld()
逻辑
见 helloworld/enclave/enc.c
中代码。
// 导入 enclave 库, 完整的 enclave 库见【API reference and supported libraries】
// 此示例中包含 stdio.h 头文件,因为我们调用 CRT 函数 fprintf 在屏幕上打印消息。
// 但是,此函数依赖于内核在屏幕上打印消息,因此此代码无法在安全区本身内执行。
// 相反,这个函数将调用转交给主机,以代表enclave执行调用。
#include <stdio.h>
// 导入在构建过程中生成的受信任的helloworld头文件。
// 该文件是通过针对 helloworld.edl 文件调用sdk工具oeedger8r生成的。
#include "helloworld_t.h"
// 这是主机调用的函数。它在 enclave 中打印一条信息,然后再回调到主机中打印一条信息。
void enclave_helloworld()
{
// 打印一个来自 enclave 的信息。
// 注意,不是直接调用fprintf,而是将调用转交到host,并从那里调用fprintf。
// 这是因为fprintf函数不是enclave的一部分,因为它需要内核的支持。
fprintf(stdout, "Hello world from the enclave\n");
// Call back into the host
oe_result_t result = host_helloworld();
if (result != OE_OK) // 验证是否成功
{
fprintf(stderr, "Call to host_helloworld failed: result=%u (%s)\n", result,
oe_result_str(result));
}
}
3. 实现 host 端的 host_helloworld()
逻辑
代码见 helloworld/host/host.c
。
// 包括本文件中使用的Open Enclave函数的头,(例如oe_terminate_enclave)。
#include <openenclave/host.h>
// 包括标准的CRT库。与 enclave 实现不同的是,enclave 实现包括一个特殊的 enclave 版本的 stdio 库,该库将API传递给主机。
// 而 host 不受保护,所以使用所有正常的C库和函数。
#include <stdio.h>
// Include the untrusted helloworld header that is generated
// during the build. This file is generated by calling the
// sdk tool oeedger8r against the helloworld.edl file.
#include "helloworld_u.h"
// 这是 enclave 调用的实际的 host 函数。该函数在 helloworld.edl 文件中定义,并在这里实现。
void host_helloworld()
{
fprintf(stdout, "Enclave called into host to print: Hello World!\n");
}
// host 是创建和调用enclave的应用程序,所以host是一个正常的C可执行文件,有一个标准的主函数。
int main(int argc, const char* argv[])
{
oe_result_t result;
int ret = 1;
oe_enclave_t* enclave = NULL;
if (argc != 2)
{
fprintf(stderr, "Usage: %s enclave_image_path\n", argv[0]);
goto exit;
}
// 1. 通过调用oe_create_helloworld_enclave来创建一个enclave,该enclave的签名库文件的路径刚好作为第一个参数传递给启动应用程序。
// oe_create_helloworld_enclave函数是由oeedger8r生成的,默认名字方式是oe_create_[xxxx]_enclave,其中xxx是 EDL文件的名称。该函数创建了一个enclave 镜像供主机进程使用。
// 该函数功能包括:
// (1). 分配 enclave 地址空间。
// (2). 从enclave 库文件中加载 enclave 代码和数据到它的地址空间。
// (3). 设置enclave环境,包括enclave堆和每个enclave线程的数据结构。
// (4). 测量产生的enclave身份,并确保它与enclave签名相匹配。
// (5). 初始化 "enclave",使其准备好从主机上调用。
// 2. OE_ENCLAVE_FLAG_DEBUG标志允许在没有签署enclave二进制的情况下创建enclave。
// 它还允许开发人员调试进程并获得对enclave内存的访问。这意味着**不要发送带有OE_ENCLAVE_FLAG_DEBUG的代码**,因为它是不安全的。它所提供的是更容易开发你的enclave的能力。
// 在你运送代码之前,你需要为enclave可执行文件准备一个适当的代码签名故事。
// 一些较新的英特尔SGX平台允许使用自签名的证书,但一些较老的英特尔SGX平台要求英特尔对你的enclave可执行文件进行签名。
// 3. 在成功创建后,该函数会返回一个不透明的enclave句柄,用于今后对enclae的任何操作。
result = oe_create_helloworld_enclave(
argv[1], OE_ENCLAVE_TYPE_AUTO, OE_ENCLAVE_FLAG_DEBUG, NULL, 0, &enclave);
if (result != OE_OK)
{
fprintf(stderr, "oe_create_helloworld_enclave(): result=%u (%s)\n", result, oe_result_str(result));
goto exit;
}
// 调用了从helloworld.edl文件中生成的主机marshaling函数。它处理任何参数的代码,并在enclave本身中调用该函数。
// 在这个例子中,没有任何实际的函数参数。
// 同时,尽管函数enclave_helloworld()是一个无效的返回类型,但marshaling代码本身可能会失败,所以需要验证与之相关的返回代码。
// 如果enclave_helloworld()要返回一个值,这将作为一个out参数传回来。
// Open Enclave处理host模式和enclave模式之间的所有上下文切换。
result = enclave_helloworld(enclave);
if (result != OE_OK)
{
fprintf(stderr, "calling into enclave_helloworld failed: result=%u (%s)\n", result, oe_result_str(result));
goto exit;
}
ret = 0;
exit:
// 终止enclave并释放与之相关的所有资源。
if (enclave)
oe_terminate_enclave(enclave);
return ret;
}
4. 编译 enclave 应用程序
4.1 使用 oeedger8r
编译 helloworld.edl
的 untrusted 部分,生成 host 端的相关文件
oeedger8r --search-path c:\openenclave\include --search-path c:\openenclave\include\openenclave\edl\sgx ..\helloworld.edl --untrusted
file | description |
---|---|
host/helloworld_args.h | 所有函数的参数定义 |
host/helloworld_u.c | 包含 "enclave_helloworld() "函数,该函数具有调用 enclave 版本的 "enclave_helloworld() "函数的 marshaling 代码。 |
host/helloworld_u.h | `enclave_helloworld()'函数的函数原型 |
Open Enclave edger8r 的说明文档在这儿。
4.2 编译
与英特尔SGX SDK一样,Open Enclave SDK目前只支持构建单一的二进制 enclave 。与英特尔SGX SDK一样,这些二进制文件也必须用一组特定的包含物和构建标志来构建。
为了简化指定正确构建参数的过程,Open Enclave SDK提供了一套pkg-config设置:
- Building enclave or host binary
- Using GCC or Clang build tools
- Compiling C or C++ code
具体的语法是:
oe<enclave|host>-<gcc|g++|clang|clang++>
helloworld 编译方法如下:
$(CC) -g -c $(CFLAGS) -DOE_API_VERSION=2 enc.c -o enc.o
$(CC) -g -c $(CFLAGS) -DOE_API_VERSION=2 helloworld_t.c -o helloworld_t.o
$(CC) -o helloworldenc helloworld_t.o enc.o $(LDFLAGS) $(CRYPTO_LDFLAGS)
4.3 签名 enclave 程序
在运行SGX enclave 之前,需要为 enclave 指定定义 enclave 应如何加载的属性。这些属性与签名密钥一起,定义了用于验证和密封操作的 enclave 身份。
使用 essign 的 SDK 工具对 encalve 库进行签名。
oesign sign -e helloworldenc -c helloworld.conf -k private.pem
enclave 的签名证书必须是 3072-bit RSA keys with exponent 3
。
密钥生成方法如下:
openssl genrsa -out myprivate.pem -3 3072
签名的配置文件在 helloworld.conf
。该文件定义了enclave的配置。
必选项:
- Debug: enclave 是否可以以 debug 模式加载
- NumTCS:要在 enclave 中分配的线程控制结构(TCS)的数量。这决定了在 enclave 中可以执行的最大并发线程数。
- NumStackPages:为 enclave 中每个线程分配的堆栈页数。
- NumHeapPages:为 enclave 分配的作为堆内存的页数。
上述4个属性也将反映在产生的 enclave 的UniqueID(MRENCLAVE)中。此外,以下两个属性是由开发者定义的,并直接映射到以下SGX身份属性。
- ProductID:产品标识(ISVPRODID),供开发者区分以相同MRSIGNER值签署的不同 enclave。
- SecurityVersion:enclave 的安全版本号(ISVSVN),它可以用来防止对密封密钥的回滚攻击。每当对 enclave 代码进行安全修复时,这个值都应该被递增。
可选项:
-
Linux, PSW >= 2.14.1
-
CreateZeroBaseEnclave:是否应该用基础地址0x0创建 enclave ?默认为0。
-
StartAddress:当 enclave 基址为0x0(CreateZeroBaseEnclave=1)时, enclave 镜像将在此地址创建。该值需要与OE_PAGE_SIZE(0x1000)对齐,并大于mmap最小地址(/proc/sys/vm/mmap_min_addr)。
基于0的 enclave 在访问0页时保证了NullPointerException行为。依赖于这种行为的应用程序现在可以在 enclave 内运行(例如,.NET运行时)。
-
-
指定额外的密钥共享和分离(KSS)身份属性(for SGX enclave only)
- FamilyID:开发者可以指定产品系列标识(SGX的ISVFAMILYID),将不同的 enclave 组合在一个共同的标识下,如包括几个 enclave 应用程序的应用套件的标识。
- ExtendedProductID:扩展的产品标识(SGX的ISVEXTPRODID)值,当16位的产品标识被证明过于局限时,开发者可以将其作为enclave的128位全球唯一标识。更多细节,请参见《英特尔软件》的表37-19。
比如:FamilyID=47183823-2574-4bfd-b411-99ed177d3e43 ExtendedProductID=2768c720-1e28-11eb-adc1-0242ac120002
-
SGX2 机器
- CapturePFGPExceptions:是否应该启用(1)或不启用(0) enclave 内异常处理程序来捕获#PF和#GP异常(SGX2特性,默认值:0)。
上述所有设置可以通过宏定义(OE_SET_ENCLAVE_SGX2
或OE_SET_ENCLAVE_SGX
)来进行设置。
OE_SET_ENCLAVE_SGX2
支持 KSS 设置。
OE_SET_ENCLAVE_SGX2(
1, /* ProductID */
1, /* SecurityVersion */
({0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, 0xb4, 0x11, 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43}), /* ExtendedProductID */
({0x27, 0x68, 0xc7, 0x20, 0x1e, 0x28, 0x11, 0xeb, 0xad, 0xc1, 0x02, 0x42, 0xac, 0x12, 0x00, 0x02}), /* FamilyID */
true, /* Debug */
true, /* CapturePFGPExceptions */
true /* RequireKSS */
false, /* CreateZeroBaseEnclave */
0, /* StartAddress */
1024, /* NumHeapPages */
1024, /* NumStackPages */
1); /* NumTCS */
OE_SET_ENCLAVE_SGX
设置下,允许在调试模式下运行enclave,而不用先签署它。在这种情况下,enclave 被视为具有标准签名者ID(MRSIGNER)的值。
OE_SET_ENCLAVE_SGX(
1, /* ProductID */
1, /* SecurityVersion */
1, /* Debug */
1024, /* NumHeapPages: heap size in units of 4KB pages */
1024, /* NumStackPages: stack size, in units of 4KB pages */
1); /* NumTCS */
如果是 OP-TEE (ARM TrustZone),则使用sign.py
对其进行签名。具体用法参考Signing an OP-TEE Enclave。
总结
更多关于Intel TEE API的操作参考 tee-internal-core-api-specification
5. 编译 host 端应用程序
5.1 使用 oeedger8r
编译 helloworld.edl
的 trusted 部分,生成 enclave 端的相关文件
oeedger8r --search-path c:\openenclave\include --search-path c:\openenclave\include\openenclave\edl\sgx ..\helloworld.edl --trusted
file | description |
---|---|
enclave/helloworld_args.h | 所有函数的参数定义 |
enclave/helloworld_t.c | 包含 "host_helloworld() "函数,其中包含调用 host 的 "host_helloworld() "函数的 marshaling 代码。 |
enclave/helloworld_t.h | host_helloworld() 函数的函数原型 |