SGX入门:如何开发第一个最简单的 SGX 应用 HelloWorld

本文将向大家展示如何基于 Intel SGX SDK 开发一个最简单 SGX 应用:HelloWorld,这个程序在可信区生产 "Hello world"并传递给不可信代码(缓冲区)打印输出到终端。

虽然 Intel SGX SDK 安装目录中默认提供了数个 Sample,但每个 Sample 对于初学者来说非常复杂和难以理解。

前提条件

  • [必须] 你的开发环境必须安装了 Intel SGX SDK。

    • 默认安装到 /opt/intel/sgxsdk
  • [可选] 开发环境主机 CPU 支持 SGX;若不支持,可采用模拟器编译运行。

SGX IDE

目前只有微软 Visual Studio 2012 或 2013 专业版支持 SGX,且只支持 Windows; VS 提供了 SGX 项目模板以及 编译和调试相关功能。

基本原理

SGX入门:如何开发第一个最简单的 SGX 应用 HelloWorld

在演示代码之前,有必要先了解下 SGX 程序最基本的原理:

  • SGX应用由两部分组成:

    • untrusted 不可信区

      • 代码和数据运行在普通非加密内存区域,程序 main 入口必须在非可信区;上图中的 main()bar() 函数均在非可信区。
    • truested 可信区

      • 代码和数据运行在硬件加密内存区域,此区域由 CPU 创建的且只有CPU有权限访问; 上图中的 helloworld()foo() 函数运行在可信区。
  • 非可信区只能通过 ECALL 函数调用可信区内的函数。
  • 可信区只能通过 OCALL 函数调用非可信区的函数。
  • ECALL 函数和 OCALL 函数通过 EDL 文件声明。

第一个 SGX 程序: HelloWorld

目录结构

HelloWorld/
├── App
│   ├── App.cpp
│   └── App.h
├── Enclave
│   ├── Enclave.config.xml
│   ├── Enclave.cpp
│   ├── Enclave.edl
│   ├── Enclave.h
│   ├── Enclave.lds
│   └── Enclave_private.pem
├── Include
└── Makefile

上面目录结构仿照了 sgxsdk/SampleCode 目录下示例代码目录:

  • App 目录内为不可信区域代码,包括 main 入口、OCALL 函数内具体逻辑代码等等。
  • Enclave 目录为可信区域代码,包括 ECALL 函数内具体逻辑代码实现。

    • Enclave.lds

      • EDL(Enclave Description Language) 文件。
    • Enclave.lds

      • Enclave linker script。
    • Enclave_private.pem

      • enclave.so 的签名私钥。
    • Enclave.config.xml

      • Enclave 配置文件,如堆栈大小、是否客气 Debug 等。
    • Enclave.h & Enclave.cpp

      • 应用安全区代码实现。
  • Include 目录是不可信代码和可信代码共享的头文件。

编译 & 运行

比较简单,直接在项目目录下执行 make,在项目根目录下会生成一个名为 app 的 Binary,运行这个 app:

$ make
GEN  =>  App/Enclave_u.h
CC   <=  App/Enclave_u.c
CXX  <=  App/App.cpp
LINK =>  app
GEN  =>  Enclave/Enclave_t.h
CC   <=  Enclave/Enclave_t.c
CXX  <=  Enclave/Enclave.cpp
LINK =>  enclave.so
<EnclaveConfiguration>
    <ProdID>0</ProdID>
    <ISVSVN>0</ISVSVN>
    <StackMaxSize>0x40000</StackMaxSize>
    <HeapMaxSize>0x100000</HeapMaxSize>
    <TCSNum>10</TCSNum>
    <TCSPolicy>1</TCSPolicy>
    <!-- Recommend changing 'DisableDebug' to 1 to make the enclave undebuggable for enclave release -->
    <DisableDebug>0</DisableDebug>
    <MiscSelect>0</MiscSelect>
    <MiscMask>0xFFFFFFFF</MiscMask>
</EnclaveConfiguration>
tcs_num 10, tcs_max_num 10, tcs_min_pool 1
The required memory is 3960832B.
The required memory is 0x3c7000, 3868 KB.
Succeed.
SIGN =>  enclave.signed.so
The project has been built in debug hardware mode.

$ ./app
Hello world
Info: SampleEnclave successfully returned.
Enter a character before exit ...

编译基本流程(Makefile):

  1. 通过 sgx_edger8r 工具在 App/ 目录下生成不可信代码(Enclave_u.c 和 Enclave_u.h),这部分生成代码主要会调用 ECALL (sgx_ecall);
  2. 编译不可信部分 Binary: app
  3. 通过sgx_edger8r 工具在 Enclave/ 目录下生成可信代码(Enclave_t.c 和 Enclave_t.h);
  4. 编译可信动态链接库(enclave.so);
  5. 通过sgx_sing工具签名可信动态链接库(enclave.signed.so);
  6. 结束。

编译后的代码目录结构:

HelloWorld
├── app
├── App
│   ├── App.cpp
│   ├── App.h
│   ├── App.o        #[generated]
│   ├── Enclave_u.c  #[generated] 
│   ├── Enclave_u.h  #[generated] 
│   └── Enclave_u.o  #[generated]
├── Enclave
│   ├── Enclave.config.xml
│   ├── Enclave.cpp
│   ├── Enclave.edl
│   ├── Enclave.h
│   ├── Enclave.lds
│   ├── Enclave.o     #[generated]
│   ├── Enclave_private.pem
│   ├── Enclave_t.c   #[generated]
│   ├── Enclave_t.h   #[generated]
│   └── Enclave_t.o   #[generated]
├── enclave.signed.so #[generated]
├── enclave.so        #[generated]
├── Include
└── Makefile

代码文件

  • Encalve/Enclave.edl
enclave {
    trusted {
        public void ecall_hello_from_enclave([out, size=len] char* buf, size_t len);
    };
};

EDL 中声明了一个公共 ECALL 函数,每个 SGX 应用的 EDL 必须至少声明一个 public 类型的 ECALL 函数。
trusted {...} 内声明 ECALL 函数, untrusted {...} 内申明 OCALL 函数,由于本例中安全区不需要向非安全区调用(OCALL),所以只声明了一个 ECALL 函数 ecall_hello_from_enclave,这个 ECALL 函数目的是在安全区创建一个 Buffer 并填充 "Hello world",然后这个 Buffer 的内容拷贝到非安全的 Buffer 中,非安全区调用 printf 打印这个非安全 Buffer 内容。

  • Enclave/Enclave.lds
enclave.so
{
    global:
        g_global_data_sim;
        g_global_data;
        enclave_entry;
        g_peak_heap_used;
    local:
        *;
};
  • Enclave/Enclave.config.xml
<EnclaveConfiguration>
  <ProdID>0</ProdID>
  <ISVSVN>0</ISVSVN>
  <StackMaxSize>0x40000</StackMaxSize>
  <HeapMaxSize>0x100000</HeapMaxSize>
  <TCSNum>10</TCSNum>
  <TCSPolicy>1</TCSPolicy>
  <!-- Recommend changing 'DisableDebug' to 1 to make the enclave undebuggable for enclave release -->
  <DisableDebug>0</DisableDebug>
  <MiscSelect>0</MiscSelect>
  <MiscMask>0xFFFFFFFF</MiscMask>
</EnclaveConfiguration>
  • Enclave/Enclave.h

这个头文件内容基本为空的。

#ifndef _ENCLAVE_H_
#define _ENCLAVE_H_
#endif
  • Enclave/Enclave.cpp
#include "Enclave.h"
#include "Enclave_t.h" /* print_string */
#include <string.h>

void ecall_hello_from_enclave(char *buf, size_t len)
{
    const char *hello = "Hello world";

    size_t size = len;
    if(strlen(hello) < len)
    {
        size = strlen(hello) + 1;
    }

    memcpy(buf, hello, size - 1);
    buf[size-1] = '\0';
}
  • Enclave/Enclave_private.pem

生成签名秘钥:

$ openssl genrsa -out Enclave/Enclave_private.pem -3 3072
  • App/App.h
#ifndef _APP_H_
#define _APP_H_

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

#include "sgx_error.h"       /* sgx_status_t */
#include "sgx_eid.h"     /* sgx_enclave_id_t */

#ifndef TRUE
# define TRUE 1
#endif

#ifndef FALSE
# define FALSE 0
#endif

# define TOKEN_FILENAME   "enclave.token"
# define ENCLAVE_FILENAME "enclave.signed.so"

extern sgx_enclave_id_t global_eid;    /* global enclave id */

#if defined(__cplusplus)
extern "C" {
#endif


#if defined(__cplusplus)
}
#endif

#endif /* !_APP_H_ */
  • App/App.h
#include <stdio.h>
#include <string.h>
#include <assert.h>

# include <unistd.h>
# include <pwd.h>
# define MAX_PATH FILENAME_MAX

#include "sgx_urts.h"
#include "App.h"
#include "Enclave_u.h"

/* Global EID shared by multiple threads */
sgx_enclave_id_t global_eid = 0;

int initialize_enclave(void)
{
    sgx_status_t ret = SGX_ERROR_UNEXPECTED;
    
    /* 调用 sgx_create_enclave 创建一个 Enclave 实例 */
    /* Debug Support: set 2nd parameter to 1 */
    ret = sgx_create_enclave(ENCLAVE_FILENAME, SGX_DEBUG_FLAG, NULL, NULL, &global_eid, NULL);
    if (ret != SGX_SUCCESS) {
        printf("Failed to create enclave, ret code: %d\n", ret);
        return -1;
    }

    return 0;
}

/* 应用程序入口 */
int SGX_CDECL main(int argc, char *argv[])
{
    (void)(argc);
    (void)(argv);

    const size_t max_buf_len = 100;
    char buffer[max_buf_len] = {0};


    /* 创建并初始化 Enclave */
    if(initialize_enclave() < 0){
        printf("Enter a character before exit ...\n");
        getchar();
        return -1;
    }

    /* ECALL 调用 */
    ecall_hello_from_enclave(global_eid, buffer, max_buf_len);
    printf("%s\n", buffer);

    /* 销毁 Enclave */
    sgx_destroy_enclave(global_eid);

    printf("Info: SampleEnclave successfully returned.\n");

    printf("Enter a character before exit ...\n");
    getchar();
    return 0;
}

总结

即便最简单的 SGX HelloWold 也比较复杂,当然“安全性”和“成本”(技术壁垒门槛、开发成本、维护成本、物料成本等)总是成正比的,和“效率”成反比的。希望这篇文章对那些想入门开发 SGX 应用的用户有所帮助。

参考资料

上一篇:【Release Notes】Kubernetes解决方案更新


下一篇:选择 Microsoft Security Essentials 需知的几点注意