SGX

阅读提示:文章由intel官方文档 翻译而来,其中客户端与服务提供商角色可能有点混乱,所以在阅读过程请参照文中最后的流程图片,这样会有比较清晰的阅读体验。

1. 使用条件

类型 型号/工具
操作系统 Win10, Linux
硬件 6th gen Intel® Core™ 或以上, Intel® Xeon® E3 v6
软件 windows:VS2015专业版或者更新版,Intel® Software Guard Extensions SDK for Windows* (Intel® SGX SDK for Windows*)
要求 C/C++编程

2. 简介

如今,现实世界中的应用程序越来越需要处理敏感数据。 诸如用户的身份验证凭证,机密文件或高价值的知识产权都是必须防止被暴露和泄露给其他未授权的第三方的信息。 由于安全漏洞会给企业造成重大经济损失和声誉损害,因此当前有强烈的需求要求开发能够守卫和保护机密的应用程序。 当前硬件和软件安全性方面的最新进展为开发人员提供了多种工具和技术供您选择,以实现这一目标。

3. Intel® SGX的任务

根据简介中可以知道当前在需要开发能够守卫并保护机密的应用程序来确保用户数据不会遭受系统或者物理层面的攻击。
英特尔®软件防护扩展(Intel®SGX)就是一种这样的技术,随着第六代英特尔®酷睿™处理器的推出而,该技术也开始可用。
英特尔SGX允许应用程序通过在应用程序空间内创建不易受到恶意行为者检查的保护区(Enclave)来控制自身的安全性-即使当攻击来自特权软件,无论是受威胁的操作系统,虚拟内存管理器还是设备驱动程序。 SGX保护模型

4. 通过远程证明提供密钥

将Intel SGX与其他现代软件和硬件技术结合使用可以帮助应用程序为现有机密提供最佳保护。 但是这些机密在传输过程中仍然容易受到攻击。因此应用程序如何首先获取机密是一个问题。

在某些情况下,可能会要求用户提供该信息(指密钥等敏感信息),但是在平台上缺少可信输入/输出的情况下,该输入可能会泄露给恶意软件。一些敏感内容,例如受数字版权管理(DRM)保护的媒体,甚至可能根本不是用户生成的,可能源自远程服务器,也可能存在大量漏洞:虽然客户端和服务器之间的通信路径可能已加密,但不能保证客户端计算机不会受到恶意软件的破坏。

这些漏洞使得信任使用敏感数据客户端计算机具有一定危险性。但是,SGX的中的“远程证明”高级功能可以显着提高提供给客户端的信任级别。

使用远程证明流程,客户端的安全区域(Enclave)可以向远程实体明它是可信的,并与该实体建立经过身份验证的通信通道。作为证明的一部分,客户端安全区证明以下内容:

  • 它的身份
  • 它尚未被篡改
  • 它在启用了Intel SGX的正版平台上运行
  • 它以最新的安全级别(也称为可信计算库(TCB)级别)运行
    远程认证

5.远程认证流程

5.1 先决条件

  • 为了利用英特尔认证服务(IAS)进行远程认证,英特尔要求独立软件供应商(ISV)从利用增强的隐私ID(EPID)的英特尔®SGX认证服务获取API密钥。 (生产服务器将继续支持较早的基于证书的身份验证,以最大程度地减少对现有客户的干扰,并且目前没有停产日期。)

  • 可以选择使用可链接或不可链接的证明策略。 可链接策略允许ISV确定是否有多个证明请求来自同一平台。 这不会标识个人平台,但允许ISV确定多个请求是否源自同一平台。

  • 向ISV分发服务提供商ID(SPID)及其API密钥。服务提供商在与IAS通信时将使用此ID。

5.2 客户端-服务器协议

远程认证利用修改的Sigma协议来实现客户端与服务器之间的Diffie-Hellman密钥交换(DHKE)。从此交换中获得的共享密钥可由服务提供商用来加密要提供给客户端的密钥。 客户安全区能够导出相同的交换密钥,并使用它来解密密钥。

5.3 通讯顺序

5.3.1 供应请求

远程证明的第一步涉及客户端要求服务提供商提供密钥。 这通常是服务提供商应用为发出这样的请求而实现的特定API端点。 服务提供者通过发出质询来请求客户端证明自己,从而对此请求做出响应。

5.3.2 Msg0 (client to server)

为了响应质询请求,客户端执行几个步骤来构造“远程证明”流的初始消息。 假设客户端的安全区域(Encalve)已经初始化,则不受信任的代码将执行以下步骤。

  1. 执行一个ECALL调用进入安全区域
  2. 在安全区中执行
  • 调用sgx_ra_init()
  • 将结果和DHKE上下文参数返回到不受信任的应用程序
  1. 调用sgx_get_extended_epid_group_id()

函数sgx_ra_init()接受服务提供商的公钥作为参数,并为在远程认证期间发生的DHKE返回一个不透明的上下文 使用客户端不透明的上下文可提供重放保护。(这句我也没太懂,原文是‘Using a context that is opaque to the client provides replay protection.’)

公钥的格式由sgx_tcrypto.h中的sgx_ec25_public_t数据类型设置。 EC密钥表示为其x坐标和y坐标:

1
2
3
4
5
6
typedef struct _sgx_ec256_public_t
{
uint8_t gx[SGX_ECP256_KEY_SIZE];
uint8_t gy[SGX_ECP256_KEY_SIZE];
} sgx_ec256_public_t;

服务提供商者的公钥应该被硬编码到安全区中,结合安全区的签名确保密钥不会被终端用户修改,使得安全区只能与预期的远程服务通信。

如果该安全区需要访问平台服务,则必须在证明序列中包括平台服务安全区(Platform Services Enclave,PSE)。 PSE是包含在英特尔SGX软件包中的体系结构安全区,可为受信任的时间和单调计数器提供服务。这些服务可用于在生成随机数期间进行重放保护,并用于安全地计算密钥有效的时间长度。

如果使用PSE,那么流程将会变成如下:

  1. 执行一个ECALL调用进入安全区域
  2. 在安全区中执行
  • sgx_create_pse_session()
  • sgx_ra_init()
  • sgx_close_pse_session()
  • 将结果和DHKE上下文参数返回到不受信任的应用程序
  1. 调用sgx_get_extended_epid_group_id()

无论安全区是否使用平台服务,安全区编写器都应公开执行ECALL的包装函数sgx_ra_init()

sgx_ra_init()获得成功结果后,客户端接下来将调用sgx_get_extended_epid_group_id()以检索英特尔®增强隐私ID(Intel®EPID)的扩展组ID(GID)。 英特尔EPID是用于验证的匿名签名方案。

关于EPID的详细资料:英特尔SGX:EPID设置和证明服

扩展的GID作为msg0的主体发送到服务提供商。 msg0的格式和来自服务器的响应的详细信息由服务提供商决定。 但是,服务提供商必须验证其接收的GID是否受支持,如果不支持,则中止认证过程。 英特尔证明服务仅对扩展GID支持零值。??

Msg0和Msg1(5.3.3 Msg1)可以选择一起发送。

5.3.3 Msg1 (client to server)

给定对msg0的成功响应,或者如果将msg0与msg1一起发送,则客户端调用sgx_ra_get_msg1() 函数来构造包含DHKE客户端公共密钥的msg1对象,并将其发送给服务提供商。 此方法的其他参数包括在上一步中获得的DHKE上下文和指向sgx_ra_get_ga() 存根函数的指针,用于计算客户端DHKE密钥。

当安全区开发人员在安全区定义语言(EDL)文件中链接sgx_tkey_exchange库中并导入sgx_tkey_exchange.edl时,SDK会自动生成此函数。

msg1结构如下表所示

Element Size
Ga_x 32 bytes
Ga_y 32 bytes
Intel® EPID GID 4 bytes

SGX SDK 在sgx_key_exchange.h中为 msg1 定义的数据结构如下所示:

1
2
3
4
5
6
typedef struct _ra_msg1_t
{
sgx_ec256_public_t g_a;
sgx_epid_group_id_t gid;
} sgx_ra_msg1_t;

5.3.4 Msg2 (server to client)

从客户端收到msg1时,服务提供商将检查请求中的值,生成其自己的DHKE参数,并发送查询到IAS以获取客户端发送的Intel EPID GID的签名吊销列表(SigRL)。

为了处理msg1和生成msg2,服务提供者提供一下步骤:

  1. 使用P-256曲线生成随机EC密钥,即密钥G_b。
  2. 从G_a和G_b派生密钥派生密钥(KDK):
    1. 使用客户端的公共会话密钥G_a和服务提供商的私有会话密钥(从步骤1获得)G_b计算共享密钥。 此操作的结果将是Gab的x坐标,表示为Gab_x
    1. 通过反转Gab_x的字节,将其转换为Little-endian字节顺序。
    1. 使用0x00字节的块作为密钥,以Gabx的小字节序形式执行AES-128 CMAC。
  • 步骤 ii 和 iii 的结果就是KDK
  1. 通过对如下字节序列执行AES-128 CMAC,从KDK派生SMK:
1
0x01 || SMK || 0x00 || 0x80 || 0x00

使用KDK作为密钥。 请注意|| 表示连接,“SMK”是文字字符串(不带引号)。

  1. 确定应从客户端请求的引用类型(0x0表示不可链接,0x1表示可链接)。 请注意,这是服务提供商的政策决定,并且SPID必须与正确的引用类型相关联。

  2. 设置KDF_ID。 通常是0x1

  3. 使用服务提供者的EC秘钥计算以下内容的ECDSA签名:

1
Gbx || Gby || Gax || Gay
  1. 使用SMK作为秘钥计算一下内容的AES-128 CMAC:
    1
    Gb || SPID || Quote_Type || KDF_ID || SigSP
  2. 查询IAS以获取客户端的Intel EPID GID的对应的SigRL。

msg2结构如下表所示:

Element Size
A Gb_x 32 bytes
A Gb_y 32 bytes
A SPID 16 bytes
A Quote_Type 2 bytes (uint16_t)
A KDF_ID bytes (uint16_t)
A Sig(Gb_Ga)_x 32 bytes
A Sig(Gb_Ga)_y 32 bytes
CMAC_SMK(A) 16 bytes
SigRL_Size 4 bytes (uint32_t)
SigRL_data SigRL_Size bytes

SGX SDK在sgx_key_exchange.h 中为msg2定义了数据结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct _ra_msg2_t
{
sgx_ec256_public_t g_b;
sgx_spid_t spid;
uint16_t quote_type;
uint16_t kdf_id;
sgx_ec256_signature_t sign_gb_ga;
sgx_mac_t mac;
uint32_t sig_rl_size;
uint8_t sig_rl[];
} sgx_ra_msg2_t;

再次,请参阅Msg0部分中提到的Intel EPID文章资源,以获取有关SigRL的详细说明。

该服务提供商的公钥和SigRL信息被打包到msg2中,并响应于msg1请求发送给客户端。

5.3.5 Msg3 (client to server)

在客户端,当收到msg2时,应用程序将调用sgx_ra_proc_msg2() 函数来生成msg3。 该调用执行以下任务:

  • 验证服务提供商签名。
  • 检查SigRL。
  • 返回msg3,其中包含用于证明特定安全区的引用。

传递给sgx_ra_proc_msg2() 的两个参数是sgx_ra_proc_msg2_trustedsgx_ra_get_msg3_trusted。 这些是edger8r工具自动生成的函数的指针,这意味着安全区必须执行以下操作:

链接到受信任的服务库(Linux上为libsgx_tservice.a,Windows上为sgx_tservice.lib

在encalve的EDL文件的trust部分中包括以下内容:

1
2
3
4
include "sgx_tkey_exchange.h"

from "sgx_tkey_exchange.edl" import *;

sgx_ra_get_msg2_trusted() 进行的部分处理是获取安全区引用。 引用包括使用平台的EPID密钥签名的当前运行安全区的加密哈希或度量。只有英特尔认证服务可以验证此签名。它还包含有关平台上平台服务区域(PSE)的信息,IAS也将对其进行验证。

msg3的结构如下表所示:

Element Size
CMAC_SMK(M) 16 bytes
Ga_x 32 bytes
Ga_y 32 bytes
Ps_Security_Prop 256 bytes
Quote (436 + quote.signature_len) bytes

SGX SDK在sgx_key_exchange.h 定义的msg3的数据结构如下:

1
2
3
4
5
6
7
8
typedef struct _ra_msg3_t
{
sgx_mac_t mac
sgx_ec256_public_t g_a;
sgx_ps_sec_prop_desc_t ps_sec_prop;
uint8_t quote[];
} sgx_ra_msg3_t;

引用的结构定义在sgx_quote.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct _quote_t
{
uint16_t version; /* 0 */
uint16_t sign_type; /* 2 */
sgx_epid_group_id_t epid_group_id; /* 4 */
sgx_isv_svn_t qe_svn; /* 8 */
sgx_isv_svn_t pce_svn; /* 10 */
uint32_t xeid; /* 12 */
sgx_basename_t basename; /* 16 */
sgx_report_body_t report_body; /* 48 */
uint32_t signature_len; /* 432 */
uint8_t signature[]; /* 436 */
} sgx_quote_t;

5.3.6 Msg4 (server to client)

在接收到客户端发送的msg3,服务提供者必须按顺序执行一下步骤:

  1. 验证msg3中的Ga与msg1中的Ga是否一致。
  2. 验证CMAC_SMK(M)。
  3. 验证报告数据的前32个字节是否与(Ga || Gb || VK)的SHA-256摘要匹配,其中|| 表示串联。 通过使用KDK作为密钥,对以下字节序列执行AES-128 CMAC,可以得出VK:
    1
    0x01 || "VK" || 0x00 || 0x80 || 0x00
  4. 验证客户提供的证明证据。
    1. 从msg3中提取引用。
    1. 将引用提交给IAS,调用API函数以验证证明证据。
    1. 验证在报告响应中收到的签名证书。
    1. 使用签名证书验证报告签名。
  1. 如果在步骤3中成功验证了引用,请执行以下操作:
  • 1)提取安全区以及PSE(如果提供)的证明状态。
    1. 检查安全区标识(MRSIGNER),安全版本和产品ID。
    1. 检查调试属性,并确保未设置(在生产环境中)。
    1. 决定是否信任安全区以及PSE(如果提供)。
  1. 派生会话密钥SK和MK,该密钥将用于在会话期间在客户端和服务器之间传输将来的消息。 客户端可以简单地调用sgx_ra_get_keys() ,但是服务器必须使用KDK作为密钥,通过对以下字节序列执行AES-128 CMAC来手动派生它们:
    1
    2
    MK: 0x01 || "MK" || 0x00 || 0x80 || 0x00
    SK: 0x01 || "SK" || 0x00 || 0x80 || 0x00
  2. 生成msg4并发送给客户端。

验证证明证据要求服务提供商将引用提交给IAS并获得证明报告。 该报告由IAS报告签名私钥签名,服务提供商必须使用IAS报告签名公钥验证此签名。

报告本身包含响应标头和有效负载,有效负载包含安全区和PSE清单的证明状态。 值为“OK”表示该组件可以信任。 任何其他值表示该组件是不可信的,或者如果采取了某些操作(例如软件或BIOS更新),则该组件是可信的。 有关完整的详细信息,请参阅IAS API 文档。 ISV负责将这些状态消息传递给客户端。

如果证明状态不是“OK”,则IAS可以选择发送Platform Info Blob(PIB),服务提供商可以选择将其转发给客户端。 客户端可以将PIB传递给sgx_report_attestation_status() 函数,以获取有关故障的更多详细信息。

msg4的格式取决于服务提供商。 它至少必须包含:

  • 飞地是否受信任。
  • 如果提交了PSE清单,则是否信任PSE清单。

它可以包含:

  • PIB(如果存在)。
  • 提供给安全区的秘钥。 应该使用从KDK派生的密钥对所有密钥进行加密(请参阅msg2)。
  • 安全区重新认证超时。
  • 当前时间。
  • 客户端需要信任的其他服务器的公钥。

以上所述的完整流程如下图所示:
image

未完待续!!!!

最后更新: 2020年04月01日 14:44

原始链接: https://silence-linhl.github.io/blog/2020/03/27/SGX/

× 请我吃糖~
打赏二维码