版权属于Intel, 如有出入,请以原文档为准。
Intel SGX开发者文档提供了如何使用Intel SGX技术开发一个强壮的应用enclave。这份指南不是Intel SGX技术的简介和安全编码指南。这份指南假设在评估了使用Intel SGX开发的益处,代价和限制后,你决定选择使用这门技术,并且想要知道如何能正确地使用它去开发应用enclave。
这份文档借助一个假设通用的运行时系统(runtime System)中基础和原则,去构建相应的编程例子。这个运行时系统(run-time System)包括如下:
- Untrusted Run-time System(uRTS,不可信运行时系统)-在intel sgx enclave环境外执行的代码,功能主要如下:
- 加载和管理enclave
- 对enclave发起调用,或从enclave中获取调用
- Trusted Run-Time System(tRTS)-在intel sgx enclave环境内执行的代码,履行着如下的功能:
- 管理着enclave自身的代码
- 对enclave外部发起调用,并且接受相应的结果
- 标准C/C++ 库和运行时环境
- Edge Routines
- 第三方库-它是一种特意移植进入到intel sgx enclave 环境内部工作的库。移植第三方库的主要原因在于可以简单地获取大量C代码,而无需专门地去重新开发library。
专业名词解释:
- ECall:“Enclave Call”一个调用enclave内部接口函数的操作。
- OCall:“Out call”从enclave内部向外部的应用发起调用的操作。
- Trusted:任何在enclave内部trusted 环境下执行的代码或者数据结构。
- Trusted Thread Context:线程的上下文存放在enclave中,主要包括以下几个部分组成:
- Thread Control Structure(TCS)线程控制结构
- Thread Data/Thread Local Storage
- State Save Area(SSA)-线程异常处理机制
- Stack-线程栈
Untrusted:指代任何运行在应用的“untrusted”环境下(等价于enclave外部)的代码或者数据结构。
Intel SGX 软件,可以使用标准的工具和开发环境去进行开发。虽然编程范式与常规软件开发过程非常相似,然而在Intel SGX软件设计,开发,和调试的过程中仍然会有一些不同。
在这个章节中,我们比较了开发enclave和开发传统的应用,例如Android,Linux,OS X,windows 操作系统的异同点。相似的编程模式可以降低开发者进入Intel SGX 技术的门槛。然而,enclave的开发者必须意识到在Intel SGX软件的设计过程,开发过程和调试过程中,仍然有一些显著的差异,例如远程认证,秘密提供和密封。在这个文档的后面章节,我们将对它们进行阐述。
理解Intel SGX的编程模型后,开发者可以最大程度地去基于Intel SGX构建安全的应用。开发者必须仔细审视下面正确开发应用enclave的原则,不这么做的话可能会导致相应的安全问题,并在后面爆发出来。
- enclave是一个庞大而单一的软件实体,目标在于将对于一个应用而言的可信计算基降低至可信运行时系统,ISV代码和第三方可信库。在上述组件中的任一个bug将会导致enclave的安全问题。
- 非可信域控制了enclave 接口函数什么时候被调用的顺序。
- 当调用进入enclave中,是由不可信域来在enclave中选择可信线程上下文去执行。
- 并不保证传递进去enclave(ecall)中的参数和从enclave向外部发送的参数(ocall)是正确的,因为这是由不可信域提供的。
- enclave向外部发送的调用OCall,不能保证不可信函数一定正确地被执行。
- 任何人都可以加载一个enclave。更为严重的是,攻击者可能使用攻击性应用程序去加载一个enclave及,利用该enclave存在的安全问题,窃取相关数据。
从高层上看,Intel SGX提供了类似于开发Android,Linux, OS X和Windows 应用的编程模型,某种意义上来说,enclave与Windows OS上的DLL,OS X上的Dynamic Library和Linux OS,Android OS上的Shared Object的开发有一定的相似性。
一个标准的DLL,Dynamic Library和Shared Object文件通常包括代码和数据部分。操作系统在当share library加载进去为首先分配一个堆,并且当线程创建时,为其分配一个相应的栈。
相似地,一个enclave library 文件包括可信代码和数据部分,但enclave被创建时,这些将会被导入到受保护的enclave 内存中。(即EPC)在一个enclave文件中,还存在着Intel SGX特定的数据结构,enclave元数据。元数据没有导入EPC中。取而代之的是,它被不可信的加载器加载,并决定enclave如何导入到EPC中。元数据中定义了可信线程上下文的数量,包括可信堆和可信栈的大小。在enclave初始化的时候,被可信运行时系统初始化。可信堆和可信栈是支持可信执行环境所必要的东西。元数据同时包括enclave的签名,这对于enclave的认证和初始化时非常重要的。
不管enclave中定义了多少个可信线程,在设计的过程中不能假设不可信应用将会以特定的顺序去调用Enclave中的接口函数。一旦enclave被初始化,一个攻击者可以调用Enclave接口函数,以任何可能的顺序去发起调用并且提供相应的输入参数。
设计一个Intel SGX应用的第一步首先是那些资产是需要受保护的,以及这个资产包含着哪些数据结构,在这些数据结构上会执行哪些相关的操作,然后并将他们放入到一个单独的可信库中。由于ISV(独立软件开发商)最了解应用,ISV需要做应用的安全分析,并做最终的决定,将那些数据和代码放入到enclave中。
enclave中的代码和存在于通常应用中的代码无太大的不同。但enclave初始化的时候,enclave的代码将会以一种特别的方式加载,特权代码和不可信应用的其它部分不能直接读取存在于保护环境中的代码,在enclave中也不能以毫无痕迹的方式改变代码的执行逻辑。因此,识别出一个安全软件开发过程中重要的秘密处理组件和涉及到这些关键步骤所使用的资源,并将它们放入到Intel SGX中是一个必不可少的过程。
将一个应用分为可信部分组件和不可信部分组件从安全的视角上有着其他的含义。通常意义上来说,更小的代码和数据通常更难以被检测出来。这意味更安全的软件通常有着更小的暴露出来的攻击面。因此,将应用的大部分移植进入到enclave,可能不是一种好的方式。当设计enclave时,可信计算基的大小是一个很重要的考虑因素。ISV应该尝试去最小化enclave的大小。
将应用进行拆分在Intel SGX应用管理电量事件的时候同时扮演着一种关键的角色。具体请查看Section Power ManangerMent章节。enclave中的状态信息越小,那么enclave能够备份相应的数据,并从相应的状态还原出来。
当在Intel SGX应用中定义了可信(enclave)组件和不可信(应用)组件后,开发者应该仔细地去定义非可信组件和可信组件之间的接口。ISV可信代码在以下的场景被执行:
- 非可信应用明确地调用enclave内部的接口函数。从ISV接口函数发起ECall调用,和一个通用的应用调用shared library比较类似。
- 当encalve向外部的应用发起的OCall返回时。OCall的返回和从一个shared library调用其他的shared library返回是比较类似的。例如,调用一个标准的C库去执行一个I/O操作。当一个OCall返回时,发起OCall的可信函数继续执行。
- 当一个中断返回时,enclave代码同样被执行。然而,Intel SGX架构上的表现似乎中断从未发生过。这类似于执行shared library的过程中中断发生的状况。
一个enclave必须将ECcall和OCall的行为暴露出去,enclave开发者定义ECall和OCall的边界接口。ECall定义了不可信应用可能使用的接口,作为开发者应该尽可能地去限制ECall的数量。需要明确的是,enclave无法控制哪个ECall被执行,或者ECall将会以怎样的顺序被调用。因此,enclave不能期待enclave的调用时以某种特定的顺序的。另一方面,ISV接口函数仅仅只能在enclave初始化后被调用,这意味着:
- 任何必要的地址基地址重置被正确地执行。
- 可信全局数据,例如栈等被正确地初始化。
- 可信线程的上下文,可信线程被正确地初始化。
- 不直接说明的可信初始化函数执行完成。(例如ISV全局构造函数)
Enclave输入和输出可以被不可信的代码观察和修改。enclave开发者不应该信赖所有来自于不可信域的信息,并且在ECall的输入值和OCall的返回值时进行检测。为了防止针对输入函数的攻击,软件开发者应该采用加密的方式和完整性检测来保证enclave执行的可信性。
当一个encalve函数被调用时:
- 函数的参数和其他任何通过引用传递的序列化数据参数进入到可信环境中,并且对于攻击者无法访问的。
- 在参数上进行读写操作,返回值和其他序列化的引用,根据enclave开发者的参数与特殊定义,将不会影响ISV代码和数据的保密和完整性。
- 参数和返回值,序列化数据被可信运行时分配和管理,不对任何ISV代码和数据进行覆盖。
- 参数,返回值和序列化引用的大小被ISV所指定。
当ISV接口函数被调用时,输入参数将被停留在enclave中。然而,但一个输入参数通过引用的形式传递,只有引用(或指针)将会放置到enclave中。引用所指向的值停留在外部,并且会不断地变化。例如,一个攻击者可能在enclave检查函数参数后改变所引用的值。
enclave开发者必须在使用引用或者指针时极其小心。一个应用可能将指向encclave内部的某一块内存地址的引用作为参数传递给enclave,这样可能会造成enclave无意地修改了代码和数据。相似地,如果enclave软件没有意识到一个指针指向一个不可信地址,enclave会泄漏秘密。为了阻止这些问题,enclave软件开发者必须决定是否使用指针。另外,enclave必须保证在它进行安全检查之后数据不能修改。开发者应该只传递enclave的范围中知道的接口对象,C的数据结构满足要求,而C++不符合。
Enclave无法直接访问OS提供的服务。作为替代的是,encalve必须发起一个OCall将其传递到不可信应用中。虽然外部调用带来了损耗,但是并没有保密性的泄漏。
虽然OCall在某些时刻是必须的,但是enclave外部调用仍然存在着一些安全问题:
- Enclave操作例如需要OCall支持的,比如线程同步和I/O,将会暴露到不可信域中。一个enclave必须以防止泄漏任何边车道信息为目的,否则这将会允许攻击者观察不可信函数调用,获取相应的enclave的秘密数据。
- Enclave必须准备去处理OCall函数没有被完全执行的场景。OCall的返回值,作为enclave的输入,来自于不可信域中,并且是不可被依靠的。一个OCall可能看起来真正完成,其实并没有。例如,一个攻击者可能对丢掉enclave去写入密封数据到硬盘的enclave请求,并告知enclave已经成功写入。
- 一个enclave不能依赖嵌套ECall(包含OCall)以之前固定的顺序执行。一个开发者可能在OCall的过程中限制ECall,虽然状态信息可以被存储在enclave中。然而,一个一个enclave发起OCall,没有人保证不可信域将会以递归调用的方式进入到enclave中。这样enclave将无法控制调用的顺序。
当一个enclave内的ISV函数发起OCall时:
- OCall仅仅暴露OCall的参数和返回值到不可信域中。
- 当一个OCall发生时,返回值和任何序列化数据的引用将传递进入可信执行环境中,对攻击者不可以访问。另外enclave需要检查指针。
- 当一个OCall发生时,可信线程上下文与OCall之前的相同,除了在栈上的数据和易变寄存器上的值。
我们在之前提到了enclave的代码必须所有的安全检查在enclave内部的ISV接口函数中,同样的,暴露给非可信域的其他enclave接口函数。对于第三方可信库同样如此推荐。如果一个可信库依赖任何暴露的函数,ISV必须确认该函数同样进行了参数检查。
如果一个可信库的函数仅仅会在enclave内部调用,或者可信库是开源实现的enclave版本,参数检查可能不是很严格。但一个第三方库没有检查它的参数,不推荐直接修改第三方库的代码。enclave开发者可以在API外面添加一个wrapper。这并不会改变第三方的实现,同时也移除了风险。
在软件中通过enclave建立信任的过程中主要有着以下三种行为:
- measurement(度量):作为enclave在可信环境中初始化时用来验证文件准确性的身份。
- Attestation(认证):向其它实体证明当前的环境被正确的初始化。
- Sealing(数据的密封):通过某种方式保证可信环境上的数据可以正确地持久化和重新加载。
这一部分主要聚焦于measurement。认证和密封将在接下来的章节描述。 Enclave包含一个来自于enclave持有人的自签名证书,同样被称为Enclave签名(SIGSTRUCT)。 Enclave签名包含允许Intel SGX硬件检测enclave的完整性是否被篡改的信息。这可以保证Enclave被正确地加载到EPC中,并且是可信的。由于硬件仅仅在enclave被加载的过程中验证measurement,这意味着任何人可以修改enclave,并且使用他/她的key对enclave进行签名。为了阻止这种类型的攻击,enclave的签名同时识别enclave持有人的身份。encalve 签名包含这几个如下的重要字段,对于外部实体的认证非常重要。
- Enclave Measurement- 一个简单的256位哈希值用来标注代码和放入到enclave中的初始化数据,包括他们放入到EPC中期待的顺序和位置,以及这些页面的安全属性。即使这个变量中的一个发生变化都会产生不同的值。当enclave代码/数据加载到EPC中,CPU开始计算enclave measurement并且将这个值存储在MRENCLAVE 寄存器中。接着CPU比较MRENCLAVE寄存器中的内容和存放在SIGSTRUCT中的enclave measurement值。当且仅当它们相等的时候,CPU允许enclave被初始化。
- Enclave所有者的公钥-当一个enclave被正确地初始化,CPU将enclave所有者的公钥的hash存放在MRSIGNER寄存器中。MRSIGNER中的内容将会用作enclave所有者的身份证明。被同一个key认证的enclave在MRSIGNER寄存器中的值相同。
- Enclave的安全版本号(ISVSVN)- enclave所有者对于enclave的每个版本号分配一个安全版本号。安全版本号反映了enclave的安全属性级别,并且需要单调递增,随着安全属性的不断上升。在一个enclave被正确初始化后,CPU记录SVN,并且在远程认证的过程中被使用。一个有着相同安全属性的enclave的不同版本应该分配相同的安全版本号。例如,一个没有解决安全相关bug的新版本enclave需要与旧版本的enclave持有相同的安全版本号。
- Enclave的产品ID(ISVPRODID)- enclave所有者针对每一个enclave分配一个产品ID。产品ID允许enclave所有者使用相同的enclave所有者身份去分割enclave。当一个enclave被正确的初始化后,CPU记录Product ID,在远程认证的过程中可以被使用。
enclave的开发者必须提供一个enclave的安全版本和产品ID,以及一对签名公私钥用来生成enclave签名。CPU获取enclave所有人的公钥的身份,私钥用来对enclave进行签名。enclave mesasurement的计算必须基于代码和被加载的enclave中的初始数据以及页面的安全属性。这些代码和被加载到enclave中的初始数据和页面的安全属性是由编译器生成的,他们的导入是由enclave加载器控制的。
Enclave签名公钥是enclave身份的一部分,对于保护它的秘密而言非常重要。一个拿到ISV的签名私钥可以发起以下的攻击行为:
- 写下一个含有恶意的enclave,并且成功地认证为合法的enclave
- 根据恶意的enclave写下中间件,获取个人平台上的隐私信息。
标准密钥管理实践提供了安全地保护私钥的方法,主要有以下几种:
- 只获取私钥的最小访问权限。
- 使用HSM(硬件加密机)或者其他enclave来保存私钥,并且执行私钥签名。
- 将发布密钥和测试密钥分离,使用不同的密钥对。
SDK提供了一个工具,用于enclave的签名,叫做sgx_sign。它获取enclave文件,并将enclave的签名添加进去。这个工具支持一步测试签名使用配置在本地系统的测试密钥,也可以使用两步签名,使用在签名设备或者平台中受保护的发布密钥进行签名。
sgx_sign同时可以从一个签名的enclave file中生成白名单材料。
ISV必须维护开发环境不受到恶意软件和其它潜在线程的干扰。
认证是用来证明一段软件成功地在某个平台上运行的过程。在Intel SGX中,认证是一种由第三方实体验证软件是否运行在开启Intel SGX的平台中,运行在enclave上从而实现了秘密和私有数据的保护的一种机制。认证依赖于平台去生成正确反映enclave签名的证书,同时包含这个enclave安全属性的信息。Intel SGX支持两种形式的验证机制。一种是同一平台的enclave之间的基本认证。另外一种是提供了enclave和远端第三方之间的认证。
应用开发者可能希望编写的enclave能够和平台上的另外一个enclave在较高层次的接口进行合作调用。为了实现这个目标,开发者需要一种机制来使enclave可以向平台上的另外一个enclave证明他的身份和合法性。Intel SGX提供了可信硬件基机制来实现这个目标。一个enclave可以请求硬件生成一个证书,即report,其中通过密码学证据证明着enclave存在这个平台上。这个report可以给其他的enclave,让其进行验证report是同一平台上生成。内部enclave之间认证机制使用的是对称密钥,只有验证report结构的enclave和创建enclave report的硬件可以访问到,并且嵌入到硬件平台中。
一个enclave report包含着以下的信息:
- enclave中的code和初始化数据的measurement。
- enclave初始化的时候记录ISV证书公钥的哈希值。
- 用户后来添加的数据。
- 其他安全相关的状态信息。
- 上述所有数据的一个签名,可以被生成report的同一平台去进行验证。
图上本地认证例子展示了同一平台上的两个enclave之间如何进行互相认证的过程。
- 在上图中,应用A持有enclave A,应用B持有enclave B。在不可信应用A和B为两个enclave建立通信后,enclave B向enclave A发送了MRENCLAVE的值。
说明:应用A和B可以是同一应用。
有两种方法对于应用而言可以提取enclave的MRENCLAVE measurement。
- 应用B从enclave B的enclave证书中提取MRENCLAVE的值。
- enclave B提供了一个接口来导出该值,通过创建一个report。
- Encalve A请求硬件生成一份report送至enclave B,并且其中包含着从enclave B获取到的数据。enclave A通过不可信应用将数据发送给enclave B。
- 当enclave B收到了来自enclave A的报告,enclave B要求硬件去验证report来确认enclave A和B在同一平台上运行。enclave B从enclave A中提取相应的MRENCLAVE,然后请求硬件生成自己的report,接着发送给enclave A。
- enclave A验证enclave B的report,并且验证enclave B和自己运行在同一平台上。
一个持有enclave的应用可以要求enclave去生成一份report,接着将这份report传到平台服务去生成一种类型的证书,反映着enclave和平台的状态。这种类型的证书被称为quote。这个quote可以传递到平台外的实体,并且使用Intel Enhanced Privacy ID(Intel EPID)签名技术去进行验证。作为结果,CPU的密钥并没有直接从平台内部泄露出去。
一个quote包含这以下的数据:
- enclave中代码和初始化数据的measurement。
- enclave初始化的时候记录ISV证书公钥的哈希值。
- enclave的产品ID和安全版本号。
- enclave的属性,例如,encalve是否运行在debug mode。
- enclave中添加的用户数据。提供了一种方式由enclave向外部实体提供数据。
- 上述所有数据的一个签名,借助于Intel EPID组签名技术。 包含在quote中的enclave 数据(MRENCLAVE,MRSIGNER,ISVPRODID,ISVSVN, ATTRIBUTES,等等)在远程认证过程的末尾提供给远程服务使用上。服务提供者将根据这些数据去进行判断是否可信。
如果由enclave所在的机器使用自己的cpuid对quote进行签名,那么cpu的身份也会被泄密进而导致隐私问题。为了解决这个问题,Intel引入了匿名签名技术,即Intel Enhanced Privacy ID(Intel EPID),用来签名enclave quote。
Intel EPID是一种组签名技术,允许平台去匿名签名对象并且可以保护签名者的隐私。通过Intel EPID签名技术,组里面的每一个签名者都有他们各自的私钥,但是验证者食用相同的公钥去验证个人的签名。因此,用户无法识别出两个交易是否来自于同一个机构,因为用户无法检测中是组里面哪个成员做了签名。在Intel SGX中,这个组是所有支持Intel SGX平台的集合。
Intel中内嵌了一种特殊的enclave,成为Quoting Enclave(QE),QE验证report正确地按照起MRENCLAVE mesurement值进行创建,然后将它用一个硬件特定的非对称密钥(Intel EPID key)进行签名。输出的内容称为quote,可以在外部的平台中进行验签。当enclave 系统正在运行的过程中,只有QE有权限访问Intel EPID key。因此quote可以看作直接来自于硬件本身但是CPU key永远不会暴露到平台外部。
下面的图展示了一个远程认证的过程:
上图远程认证过程的例子展示了包括一个最简洁的enclave认证基本的过程。步骤描述如下:
- 当应用需要在平台外部的服务,它首先和外部服务提供商建立连接。服务提供商需要应用证明它确实运行在enclave中。服务提供商并向其发送了一个有时间限制的nonce。
- 应用从应用的enclave请求一份report,并将来自外部服务提供商的nonce传递进enclave中。
- enclave生成一份report结构。
- report发送给QE去做签名。
- QE验证report。
- QE转换report的body转换为quote,并对其使用EPID进行签名。
- QE返回quote structure 请求。
- 应用返回quote结构体,和其他相关的信息到外部服务商
- 外部服务商使用EPID验证公钥去验证quote的签名信息。
- 外部服务商对比来自enclave的信息,并与其它可信配置的信息进行比较。判断enclave是否满足条件。其中包括检测enclave是否运行在debug模式,及其的measurement,产品ID和enclave所有人等等。 这些步骤可以作为一个简单的例子,用来描述enclave如何被外部实体验证。
上述过程8中只是简单作为从enclave到外部服务提供商验证的例子。对于外部服务提供商想要获取可信配置信息的机制不再本节讨论中。
Intel SGX不直接提供一种机制(例如,通过自动生成的REPORT字段)来区分同一个enclave的两个不同的实例。
如果想要尝试的话,可以使用RDRAND函数生成随机数,并作为作为用户数据,嵌入到REPORT中。
一个远程的实体可能在完成enclave的远程认证后向enclave发送一些秘密数据。通常情况下,秘密的发送通过安全通道来完成。安全通道的建立一定要在远程验证的过程中。否则,远程的服务器可能会提供给另一个实体秘密,而非认证的实体。这种攻击方式通常称为“中间人攻击”。
下面将描述如何实现秘密发送的过程。
- 在远程认证过程中,发送的report中可以包含用来创建可信通道的公钥数据。为了实现这个目的,在步骤三中enclave可能想首先去认证服务器,来保证它是从一个可信的实体受到的秘密。例如,一个好的根证书可以嵌入到enclave的代码或初始化数据中,允许enclave去验证服务器。一旦远程服务器被验证,enclave会生成一个短暂的公私钥并将它的公钥塞入report的用户数据部分。
- 当enclave完成远程认证的步骤七,服务器可以生成一个加密的公钥E,并将它用enclave的公钥P进行加密,将结果P(E)通过通道发送给应用。通道本身不需要安全保护,因为发送的秘密是加密的。
- 一旦P(E)被enclave的应用接收,它可以传递到enclave中。在enclave中,它将解密P(E),因为它持有私钥,所以目前无论远程服务器还是enclave都持有加密密钥E。
如同远程认证一般,上面的例子并不是唯一的在enclave和远程实体之间建立传送数据的可信通道的方式,仅仅就是一个例子。
远程验证的验证着必须检测在提供任何秘密之前必须检测签名的身份(MRSIGNER)。Intel SGX技术不验证证书链当enclave被初始化的过程中。硬件也仅仅验证enclave的measurement(MRENCLAVE),并将在enclave 签名中的ISV公钥的哈希保存在MRSIGNER。这意味着任何人可以修改enclave并且重新签名。相似的是,验证者必须检查enclave的属性,避免提供秘密给任何处于debug模式下工作的enclave。
一旦安全通道建立起来,秘密将会发送给enclave。远程server可以使用密钥E对发送的秘密S进行加密,并将E(S)发送给应用,最终传送到enclave中。enclave可以使用密钥E对E(S)进行解密,然后获取S的内容。这会是不方便的,主要原因在于每次enclave初始化的时候都需要远程实体去连接enclave并完成秘密提供的这一个过程。取而代之的是,enclave可以借助下一章节中的密封技术等非易失性存储来存储秘密。即使密封的秘密从enclave中到处,秘密仍然对任何人是不可访问的。仅仅有enclave可以将它解封,并且仅仅局限于在它密封的那个平台(那台机器)上。
秘密提供是Intel SGX技术提供的一个非常关键的特性。它允许去构建比当前防篡改软件(TRS)更为强壮的应用。TRS简单地通过混淆来提供安全性,举例而言,它通过在可执行文件中混淆秘密来尝试从未认证机构手中保证秘密的安全性。然而,这种方法仅仅使时间变长,但仍然存在可能性,从TRS的二进制文件中提取秘密。此外,它对于开发者并不是非常友好。
提供给一个debug enclave秘密数据是不安全。一个debug enclave的内存并不是受硬件保护的,可以通过Intel SGX debug指令进行查看和修改。在report和quote中,enclave的证书包含了enclave的属性,其中就有debug标志位。为了保证所有提供给产品级的enclave的秘密,本地和远程实体必须检查enclave的属性,在开发阶段可以交换某些调试秘密,但是生产环境下不应提供任何秘密给一个debug enclave。
Enclave秘密必须被安全地存储在enclave边界外部,当这样的秘密被密封后。然而,在某些场景下,需要在enclave中将这样的秘密清除。enclave开发者必须使用memset_s函数来清除任何包含私密数据的变量。这个函数将保证编译器不对写内存的方式进行优化(例如,借助cache然后之后写入到内存中),直接对秘密数据进行完整性的清除。当秘密数据保存在一个动态分配的缓存器中使用memset_s去清除数据是非常重要的。在这之后缓冲被释放,并且可以再分配。对于它之前的数据,如果他们没有被擦除,将可能泄露到enclave的外部。
memset_s实现上没有做性能优化,而memset函数用来初始化buffer,并且清除不包含秘密数据的缓冲。
当一个enclave初始化后,它对数据提供保密性和完整性保护,通过将它放置在enclave的边界中。enclave开发者当下面的事件(进而导致enclave被毁坏)发生时,可能有持久化秘密数据的请求,并将保存的数据还原出来。
- 应用完成,并将enclave关闭。
- 应用自身关闭。
- 平台进入休眠或者关机状态。
通常情况下,提供给enclave的秘密在enclave关闭的时候丢失。但是遇到了上述的事件,如果在enclave中的秘密数据由于将来使用的需要需要被持久化,那么它必须在enclave关闭之前储存在enclave的边界之外。为了保护和持久化数据,提供了一种由enclave软件从特定的enclave中提取key的机制。这个Key只能在特定的平台上被某个enclave生成。enclave 软件使用那个key去在平台上加密数据或者从平台上解密已经存在的数据。我们把这种加密和解密的操作成为密封和解封。
当密封数据的时候,enclave需要去指定数据被解封的时候需要满足什么特定的条件。目前有两种可供的选项:
将数据密封到当前的enclave使用enclave measurement的当前版本(MRENCLAVE),但enclave创建的时候,将这个值绑定到密封操作所需要使用的key上。这个绑定操作由硬件执行通过EGETKEY指令。
只有有着相同的MRENCLAVE 度量的enclave才能够将密封的数据进行解密。如果enclave的DLL,Dynamic Library,或者Shared Object文件被篡改,那么enclave的measurement将会发生改变。作为影响,密封的key同样会发生变化,数据将不会被还原。
将数据密封到enclave的当前的所有者需要使用enclave所有者的身份,这个值由CPU存储在MRSIGNER寄存器中,在enclave初始化的过程中,并且把这个字绑定到密封数据函数所需要使用到的key中。这个绑定是由硬件通过EGETKEY指令完成。被密封数据的函数使用的这个key被绑定到enclave的产品ID上。产品ID存储在CPU中,但enclave初始化的时候。
当MRSIGNER 度量寄存器的值和产品ID相同时,enclave才能去解密以这种形式密封的数据。 在密封到enclave的基础上提供这种机制的好处是两方面。首先,它允许enclave的所有者对enclave进行升级,但是不需要进行复杂的升级过程来解密之前密封在之前版本的enclave(MRENCLAVE 度量值不同)中的数据,并且重新使用密封到新的版本。第二,它允许来自相同的所有者的enclave实现共享数据。
Enclave所有者生产出enclave后,可以为之指定安全版本号。这个安全版本号同样在enclave初始化的时候存储在CPU中。一个enclave必须在从CPU中获取密封key的请求中必须提供一个相应的安全版本号。 一个enclave不能指定一个比它当前安全版本号靠后的enclave,问题在于它可能尝试获取当前还没有生成的encalve的seal key。然而,一个encalve可以指定一个在当前安全版本号之前的安全版本好。这个选项使enclave获得了解封之前版本的enclave中的数据,对于enclave软件更新,是有着巨大的好处的。
在enclave中密封数据的主要过程如下:
- 为加密数据和密封数据结构在enclave中分配内存。其中密封数据结构主要包括要加密的数据和额外的认证数据组成。额外认证数据指额外的参与MAC计算但不进行加密的数据或文本。在加密数据结构中仍然是明文的数据。额外认证数据中的信息可能包括应用enclave,版本号,数据,等等。
- 调用加密数据的API来执行加密操作,一个加密操作算法如下:
- 验证输入参数是有效的。例如,如果作为参数进行传递的是一个指向加密数据结构的指针,它指向的缓存必须存在于enclave中。
- 初始化并向其中填充一个将要被EGETKEY指令来执行的密钥请求数据结构。过程如下:
- 获取EREPORT去获得安全ISV和TCB安全版本号,将在key获取中使用。
- Key 名称:识别获取key的名字,在这种情况下即为Seal key。
- Key Policy:识别即将使用的软件密封策略。使用MRSIGNER来表示密封到enclave的所有者上,使用MRENCLAVE来表示密封到当前的enclave(enclave measurement)中。保留位必须被清除。
- 调用RDRAND来获取一个随机数。
- 属性字段。表明密封密钥应该与什么属性进行绑定。
- 使用上一步构造出的密钥请求结构调用EGETKEY获取Seal key。
- 使用加密算法来使用密封密钥来执行密封操作。推荐使用AES-GCM加解密函数,例如Rijndael128GCM。
- 将密封数据结构(包括密钥请求结构)从enclave中放到外部内存中,考虑到将来的使用。密钥请求结构将用来在enclave初始化后去获取密封密钥。
在enclave中对加密数据进行解密主要包括以下过程:
- 为待解密的数据分配内存。
- 调用解密api去执行解密操作。一个解密操作算法如下:
- 验证输入参数的合法性。
- 从密封数据结构中提取key request 数据结构,在密封数据结构中AAD数据是明文为加密的。
- 借助key request数据结构去通过EGETKEY指令获取密封密钥。
- 调用解密算法去使用密封密钥,执行解密算法。
- 从内存中删除密封密钥以防止边侧道攻击引起的线路。
- 验证解密算法生成的hash与加密过程中生成的hash一致。
Enclave开发人员必须意识到即使两个同一个enclave的两个不同的实例可以在它们被认证的时刻被区分,但是目前Intel SGX不提供一种机制来阻止另外一个enclave实例去访问某个enclave生成的数据,但两个enclave同时使用EGETKEY指令时。所有的实例将会返回相同的密钥值,这是保证数据能够在电量时间触发后enclave关闭又重新加载数据成功的保证。
如果你需要类似的功能,推荐由enclave开发者为不同的enclave实例分配一个不同的身份,借助enclave签名机制。
在Intel SGX技术之前,硬件平台从来不是可信计算基用来加密用户数据的一部分。这允许用户很容易地去移植他们的数据,即使已经加密过,从一个平台到另一个平台下。现在CPU帮助生成决定性的enclave 密封密钥。因此,跨平台的用户数据一直就成为了一个需要仔细关注的问题。
如果应用从一个旧的Intel SGX系统到一个新的Intel SGX系统(平台升级)或者从一个处理器到另一个处理器(系统中CPU替换或者云环境下的负载均衡),enclave将无法在新的平台上解密数据。数据迁移需要借助一个后端服务器来作为桥梁,实现共享数据的一直。无论ISV采取怎样的方法,seal key都不能泄漏出去,否则会导致安全性问题。
Intel SGX架构中包括了debug标志位,同时包含在key request structure中。在密封密钥的提取过程中,两种不同的encalve启动方式(debug和non-debug)将会获得不同的密封密钥。这个机制保护了生产环境中的密封数据,不会被一个debug的enclave解密。
Intel SGX 架构提供的安全保证对于应用的性能而言并不是无影响的。Intel会去最小化安全检查和安全机制带来的性能影响,但是还是存在着一些方法来最大程度地去实现应用的最好性能。
理解了本章描述的潜在性能损耗点,并且采用相关的编程推荐,将不会遇到显著的性能问题。
Enclave创建是第一个需要考虑的地方。enclave的大小很大程度上影响了创建enclave的时间,主要是由于在enclave的度量(measurement)过程中,需要进行一系列操作以保证所有的代码加载到enclave中是可信的。
在enclave的创建过程中,一系列的EADD和EEXTEND指令将被运行,主要用于加载和度量enclave的页面。
- EADD每次加载4k字节的数据。
- EEXTEND每次度量256字节的数据。这意味着对于EADD添加的4KB的数据,需要发起16次的EEXTEND调用。
从EADD和EEXTEND指令的工作上可以看出,创建enclave的时间和enclave的大小成正比。
相关的性能优化建议:
- 减小enclave的大小。仔细检查在enclave中每段代码和数据元素,如有必要,将其移除。(使用工具:Intel VTune Amplifier)。例如,将即将静态链接的.o 文件先转换为诶.a文件。
- Intel SGX 允许enclave通过Enclave Dynamic Memory Management(EDMM)的方式去进行扩展。当你的OS支持EDMM,那么可以先创建一个比较小的enclave,然后扩展它。
- 通过在application中添加加载条的方式来获取用户的注意力,借此隐藏应用的加载时间。
- 避免频繁的enclave创建和重新加载,进而最小化重复的加载性能损耗。
在enclave中控制权的频繁切换是第二个需要考虑的问题。下面解释了在不可信应用和enclave之间大量数据的切换和加载是如何影响着切换的时间,同时讨论了如何减小这方面的性能损耗。
在enclave之间的频繁转变需要在上下文之间切换非常多次。当一个EENTER指令触发进入enclave时,为了保证enclave可以正常运行,属于不可信运行时的注册状态(register state)和其它信息被存储,enclave中的线程状态和其它归属于可信状态的信息被加载。这部分工作主要是由SDK生成的代码执行。一个相反的过程发生在从enclave中退出(被EEXIT指令触发)。可信线程状态信息被保存,不可信的注册状态和其它信息被还原。在这个过程中同样执行着安全检查。这部分工作也主要由SDK生成的代码自动执行。这些行为构成了在应用和enclave之间控制权切换所带来的固定负载。
然而,这个转变中有一些可变变量影响着损耗。即传递参数的大小。参数在从不可信应用部分到可信enclave传递的过程中被序列化,并且返回的值被反序列化。从可信enclave中,来自不可信应用的参数被反序列化,并且将要返回的值被序列化。如果应用传递大量的参数,将会有显著的性能损耗。
如果在enclave和应用之间的控制权的转变很大程度地冲击着性能,考虑通过通过以下的方式降低影响:
- 减小传递参数的总大小。(使用工具:Intel VTunte Amplifier)
- 当确实有大量数据需要进行传递的时候,考虑使用指针的方式。需要说明的是,使用指针会带来一部分安全风险,为此你必须自己去实现指针检查器,如果使用这种方式的话。
- 如果转变时间是一个问题,可以考虑一些其它的方式,例如由Google提出的Exit-less Service。论文地址
补充:
当执行enclave的过程中发生了中断,例如一条AEX指令被触发,将会导致enclave中暂停。一条AEX相对于标准的中断指令在enclave中有着更高的上下文切换负载。由于中断在操作系统的控制下,因此这里没有任何应用可以做工作的地方。但是理解中断如何影响性能是一件好事。
第三个需要考虑的地方是缓存未命中的影响。在现在处理器中,缓存(cache)通常位于CPU中。引入缓存会对内存读写带来的影响众所周知,然而,Intel SGX在这个的基础上引入了其他的损耗,主要在于encalve所存储的内存内容在处理器cache之外的部分都是加密受保护的。这种类型的保护在从内存中获取cache lines带来了相应的负载。这种负载和Intel SGX实现方式有直接关系。
Intel SGX架构中在缓存未命中的情况下,相对于其他传统的负载,新增了两种类型的负载:
- 对于不在处理器cache中的每一条cache line所执行的完成check/anti-replay 检查,以及在系统内存中更新相应的数据结构(如果必要的话)。这种类型的负载依赖于内存访问模式。
- 在cache和内存中加载和移除数据所带来的加解密。 当缓存未命中频率非常高时,这两类负载会对系统的性能产生很大的影响。(需要说明的是,访问在cache中的enclave内存不会受到影响。从enclave中访问在encalve内存外的数据有一些小的影响。) 如果你的系统受到大量的缓存未命中而相关的性能损耗的冲击,可以考虑下面的步骤:
- 减小enclave中的数据的大小。观察数据,以保证只有必要的数据才能装载进入enclave中。更少的数据意味着更少的加减密和更少的数据结构检查,在Intel SGX内存控制/保护机制下。可以使用Intel VTune Amplifier来观察应用中的cache行为来制作相关的行动。
- 可以查看下面的文档去创建一个更加“缓存友好”的应用:PDF地址
第四个方面需要考虑的是Intel SGX应用频繁大量的页面切换对性能的影响,并且如何最小化这方面的后果。对于典型的操作系统而言,分页本身会带来性能损耗。然而,Intel SGX下分页所产生的性能损耗更严重,如下所述。
Intel SGX使用安全存储,即EPC,来存储enclave中的内容。Enclave页面大小为4KB。当enclave比EPC总可用的内存要大的话,enclave分页机制可能会被某些特权软件来使用。当OS尝试交换enclave页面时,CPU使用EWB指令执行以下的步骤:
- 读取要替换出去的Intel SGX页面(移出)
- 加密页面中的内容
- 写入加密的页面到未收到保护的系统内存中。 由于这个过程有着固定的负载,因此越多的页面被替换出去,越多的性能损耗发生。
为了阻止你的应用频繁经历这种页面的交换,尽可能确保enclave的大小小于EPC。尽可能只将秘密数据和在这之上的操作放入enclave中,从而最小化页面交换的可能性。可以使用Intel VTune Amplifier工具来观察应用中页面替换的行为,来保证做出正确的决定。
如果你的应用是多线程的,请从数据同步,锁,线程模型和内存分配算法上去寻找改善性能的方法。
- Intel SGX sdk一些关于同步和锁的原语已经被优化。
- 对于较重的多线程应用,推荐选择更好的内存分配算法。Intel SGX SDK针对linux提供了TCMalloc内存分配算法,相对于默认的dlmalloc内存分配算法,有着更好的性能表现。
- 当有太多的enclave在运行时,尽管做了尽可能的性能优化,enclave的性能仍然可能不会达到预期。
现代操作系统提供了允许应用被平台上电量事件通知的机制。当平台进入了电量管理的S3或者S4电量状态时(例如模拟休眠等),所有的enclave将会被清除,并且所有的key被清除。Enclave想要在进入S3,S4,S5的电量事件下保护相应秘密,那么必须将状态信息持久化到硬盘上。
Intel SGX 架构上并不提供一种将电量事件直接传递进入enclave的方式。应用可以在OS层上对类似的事件注册一个callback函数。当回调函数被调用时,应用可以直接调用enclave,来保存相应secret的状态,将其持久化到硬盘上。然而,操作系统并不能保证为enclave提供足够的时间来密封它全部的内部状态。因此在可能遇到电量变化的事件时,需要持久化状态的enclave,必须阶段性地去密封enclave的数据(到enclave外部的硬盘或者云上)。当重新初始化应用的时候,enclave从零开始重新构建,并且基于它之前持久化的状态进行还原。
为了最小化地在硬盘上或者云上频繁地密封秘密和存储加密数据的性能损耗,enclave开发者应该在enclave中的持久化要存储的数据和状态信息的最小集,这样遇到电量变化的事件,enclave可以简单地完成持久化的任务。