控制 TCPA
可信计算平台联盟 (TCPA, www.trustedcomputing.org) 已经发布了安全芯片和相关软件接口的开放规范。TCPA 芯片旨在为客户端机器提供客户端安全所需的最小但至关重要的硬件基础。它提供两个重要的安全功能:签名和加密密钥的安全存储以及系统软件完整性测量。TCPA 的安全存储可用于保护个人的 RSA 身份验证私钥或环回文件系统的主密钥免遭盗窃或泄露。TCPA 的完整性测量可用于检测软件是否被破坏,例如内核被 root,并在发现破坏时锁定对受保护密钥和数据的使用。
IBM 现在正在销售配备 TCPA 芯片的 ThinkPad 和台式机型号。IBM 已经发布了一套 TCPA 开源教程代码,可在 www.research.ibm.com/gsal/tcpa 获取。本教程包旨在帮助人们了解 TCPA,并介绍如何在 Linux 下对 TCPA 芯片进行编程。
在本文中,我们尝试通过介绍 TCPA 的基本原理、描述 IBM 面向 Linux 的开源 TCPA 教程包以及解释如何使用 TCPA 对文档进行签名和存储加密环回文件系统的密钥,来帮助您更好地理解 TCPA 规范和教程包。
TCPA 安全子系统包括硬件和软件组件。硬件提供的功能称为 TPM(可信平台模块)功能;软件提供的功能称为 TSS(可信支持服务)。从程序员的角度来看,IBM 版本的 TPM(或 TCPA 芯片)如图 1 所示。
TPM 包括五个密码功能单元。它有一个硬件随机数生成器 (RNG),为芯片上的密钥生成以及应用程序使用提供高质量的随机数源。它有一个哈希单元 (SHA-1) 和一个相关的消息认证哈希计算器 (HMAC)。它还能够在芯片上生成高达 2,048 位的 RSA 密钥,基于 RNG 提供的随机数。最后,它有一个 RSA 引擎,可以执行签名、加密和解密。TPM 不进行签名验证,因为这不是敏感操作,并且可以使用软件更轻松、更快速地完成。
TPM 在非易失性存储器中存储三个重要的密钥。认可密钥是一个 2,048 位的 RSA 公钥和私钥对,它在制造时在芯片上随机创建,并且无法更改。私钥永远不会离开芯片,而公钥用于证明和加密发送到芯片的敏感数据,就像在 TPM_TakeOwnership 命令期间发生的那样。由于此密钥从隐私角度来看是敏感的,因此 TPM 所有者可以完全禁用其使用。
存储根密钥 (SRK) 是一个 2,048 位的 RSA 密钥对。它最初为空,并且作为 TPM_TakeOwnership 命令的一部分创建。此密钥永远不会离开芯片。它用于加密(包装)私钥以便在 TPM 外部存储,并在将它们加载回 TPM 时解密它们。SRK 可以由系统所有者清除。
所有者授权密钥是与 TPM 所有者共享的 160 位密钥。所有者在 TPM_TakeOwnership 命令中将其加载到 TPM 中。此密钥用于授权敏感的所有者命令请求。
易失性存储器部分包含十个插槽,用于临时存储 RSA 密钥对。任意数量的包装密钥可以外部存储,并在需要时加载到这些插槽中使用。尽管加载的密钥被认为是易失性的,并且不保证在断电后仍然存在,但在 IBM 芯片版本的情况下,密钥通常确实会持续存在,并且可能需要被逐出以为加载新密钥腾出空间。
易失性存储器部分还包含 15 个平台配置寄存器 (PCR)。这些寄存器包含软件完整性的 160 位测量值(哈希值)。在系统启动期间,会对 BIOS、扩展 BIOS、MBR、GRUB 引导阶段和任何指定的文件(例如内核)进行测量。这些测量值被添加到各种 PCR 中。BIOS 在启动时通电时主动重置所有 PCR 值。当系统从挂起状态恢复时,BIOS 尝试以恢复已保存 PCR 值的模式启动 TPM。为了使此功能正常工作,TPM 设备驱动程序必须在芯片挂起之前发出 TPM_SaveState 命令。
易失性存储器还用于存储两种类型的句柄。密钥句柄用于为加载的密钥提供临时名称,以便在加载多个密钥时,后续命令可以指示应使用哪个密钥。当相应的密钥被逐出时,密钥句柄将被清除。授权会话句柄用于跨多个命令识别授权状态数据。授权句柄由 TPM_OSAP 和 TPM_OIAP 授权命令创建,并且在不再需要时必须使用 TPM_Terminate_Handle 命令清除它们。
在查看这些特定的 TPM 命令之前,我们应该介绍 TPM 最令人困惑的方面之一——如何启动它。幸运的是,BIOS 负责启动和清除 TPM,因此这实际上并不像 TPM 规范中看起来那么复杂。在通电时,TPM 被激活但未启动。然后,BIOS 必须发出 TPM_Startup 命令。此命令可以执行三件事之一:停用 TPM、启动 TPM 并重置 PCR 寄存器,或者启动 TPM 并从其保存状态恢复 PCR 值(如恢复时)。如果 BIOS 停用 TPM,则它将保持停用状态,直到下一个电源周期;没有软件命令可以重新激活它。在启动时完成启动并清除 PCR,因此在启动期间正确计算所有 PCR 值。TPM 设备驱动程序负责在挂起时发出 TPM_SaveState 请求,以确保在恢复时可以使用有效的 PCR 值。
如果需要,BIOS 还负责执行 TPM_ForceClear。清除命令是对 TPM 的完全重置,它卸载所有密钥和句柄,并清除 SRK 和所有者授权密钥。TPM_ForceClear 需要物理存在的证明,这通常是通过在系统通电时按住 Fn 键(左下角的蓝色键)来提供的。
BIOS 对 TPM 停用和清除的控制是在 BIOS 设置模式中设置的。要开始使用 TPM,请按住 Fn 键并按下电源按钮。当 BIOS 屏幕出现时,松开 Fn,然后按 F1 进入 BIOS 设置模式。接下来,选择 Config→Security System,然后选择 Enable 和 Clear 条目。这些步骤启用 TPM 的操作并清除芯片,使其准备好供我们取得所有权。
TPM 设备驱动程序 tpm.o 是一个可加载的内核模块,它为 TPM 芯片提供字符设备接口。它正式注册为 Linux 主设备号 10,次设备号 224。应用程序通常通过特殊文件 /dev/tpm 访问它。
要向 TPM 发送命令,需要以读/写方式打开 /dev/tpm,写入命令包并读取响应包。TPM 一次只能处理一个命令,因此必须发送整个请求并读取整个响应,然后才能发出另一个请求。
所有命令包都具有通用结构
所有响应包都具有类似的结构
所有 16 位和 32 位值都采用网络字节顺序(大端),并且必须与主机字节顺序之间进行转换。在写入 TPM 时,请完全按照包的总长度字段中指示的包字节数进行写入。读取响应时,应尝试读取 4,096 字节(定义的 TPM 包最大大小),读取的返回值指示返回包中的字节数。这应与返回包的长度字段完全匹配。对于成功的命令,返回代码为零,正值是特定的错误代码。
用于发送/接收 TPM 包的函数可能如下所示(为清晰起见,省略了错误处理)
uint32_t TPM_Transmit(unsigned char *blob) { int tpmfp, len; uint32_t size; tpmfp = open("/dev/tpm", O_RDWR); size = ntohl(*(uint32_t *)&blob[2]); len = write(tpmfp, blob, size); len = read(tpmfp, blob, 4096); return(ntohl(*(uint32_t *)&blob[6])); }
一旦通过 BIOS 设置启用并清除了 TPM,并且加载了 TPM 设备驱动程序,我们就可以尝试一些简单的 TPM 命令。TCPA 主要规范详细介绍了大约 73 个 TPM 命令。幸运的是,在本教程中,我们只需使用其中 14 个命令即可演示所需的签名和密封功能。
最简单的命令是 TPM_Reset,它是一个刷新任何现有授权句柄的请求。TPM_Reset 是一个很好的命令,用于测试驱动程序和库,因为它简短、固定并且应该始终成功,返回结果代码零。以下是 TPM_Reset 的示例代码
uint32_t TPM_Reset() { unsigned char blob[4096] = { 0,193, /*TPM_TAG_RQU_COMMAND*/ 0,0,0,10, /* blob length, bytes */ 0,0,0,90}; /*TPM_ORD_Reset */ return(TPM_Transmit(blob)); }
重要的是调整 blob[] 的大小,以允许返回的 TPM 数据达到最大允许包大小 4,096 字节。
TPM_GetCapability 命令是另一个简单的函数,它可以返回有关给定 TPM 的多项信息。它可以返回当前 TPM 的版本、TPM 中密钥插槽的总数(通常为十个)、已加载密钥及其句柄的数量以及 PCR 寄存器的数量(通常为 16 个)。以下是使用 TPM_GetCapability 读取 TPM 版本的示例代码
uint32_t TPM_GetCapability_Version() { unsigned char blob[4096] = { 0,193, /* TPM_TAG_RQU_COMMAND */ 0,0,0,18, /* blob length, bytes */ 0,0,0,101, /* TPM_ORD_GetCapability */ 0,0,0,6, /* TCPA_CAP_VERSION */ 0,0,0,0}; /* no sub capability */ return(TPM_Transmit(blob)); }
TPM_PcrRead 返回指定 PCR 寄存器的 20 个字节(160 位)。它可用于检查修改后的 GRUB 引导加载程序是否正在进行任何所需的 TPM 测量。
TPM_ReadPubek 用于读取 TPM 的固定公钥认可密钥 (Pubek)。最初必须读取 Pubek,以便所有者可以使用它在 TPM_TakeOwnership 命令中加密敏感数据。一旦建立所有权,所有者通常会出于隐私原因禁用读取 Pubek;此后,此命令将失败。
某些 TPM 命令需要授权。与所有者相关的命令通常需要基于对所有者授权 160 位密钥的了解进行授权。同样,密钥的使用可能需要基于密钥的授权密钥进行授权。通常,这是以应用于密钥时密码或 PIN 的哈希形式完成的。
TPM 支持两种授权协议:对象独立授权协议 (OIAP) 和对象特定授权协议 (OSAP)。这两种协议都是相似的,因为它们都创建了一个授权上下文,并将句柄返回给用户,并且它们都使用滚动 nonce。主要区别在于 OIAP 创建了一个具有新会话密钥的长期会话,并且可以在会话中的多个对象中使用。OSAP 与单个对象相关,例如给定的密钥。在 TPM_TakeOwnership 的情况下,必须使用 OIAP,因为对象和密钥尚未建立。在大多数其他情况下,可以使用任一授权协议。
TPM_OIAP 和 TPM_OSAP 都创建授权句柄,这些句柄在完成时应终止(释放)。这是通过 TPM_Terminate_Handle 命令完成的。
我们已准备好执行必要的 TPM_TakeOwnership。此命令执行四个关键功能:它安装所有者提供的所有者授权密钥,创建 SRK,应用所有者提供的 SRK 授权密钥,并可选择将公共 SRK 部分返回给所有者。有了可用的 SRK,我们现在有了一个功能正常的 TPM,并且能够创建和使用签名和加密密钥。
TPM_CreateWrapKey 使用硬件 RNG 在芯片上生成新的 RSA 密钥。密钥必须被键入为用于签名或用于加密/解密。TPM 不允许签名密钥加密或加密密钥签名,因为这可能会导致攻击。可以选择为密钥提供一个密钥,将来使用该密钥时需要生成该密钥。此外,密钥可以包装到指定的 PCR 值。如果这样做,则授权数据和指定的 PCR 数据都必须匹配才能使用密钥。所有密钥都必须有一个父密钥(可能是 SRK),该父密钥用于在将密钥结构返回给用户之前加密密钥的私有部分。返回的密钥数据必须由用户存储以供将来加载。
TPM_LoadKey 用于将密钥加载到 TPM 中易失性密钥存储插槽之一。此命令需要父密钥的授权密码;加载后,TPM 使用父密钥解密加载的密钥的私有数据以供使用。如果密钥具有授权密钥,则加载密钥不需要它,但对于任何尝试使用密钥进行加密或签名的后续命令,则需要它。
由于 TPM 中可用的密钥插槽数量有限,因此当不再需要密钥时,必须将其逐出,以便该插槽可用于其他密钥。
TPM_Sign 命令使用加载的密钥对呈现的数据进行签名,通常是实际数据的哈希值。TPM_Seal 用于执行数据的 RSA 加密;它需要加载的加密密钥和该密钥的任何授权密钥。TPM_Seal 还可以指定要在密封中使用的 PCR 值。如果尝试在没有匹配 PCR 值的情况下进行未来的解封,则解封将失败。TPM_Seal 还将用户提供的数据授权值(密码)应用于密封数据。因此,要解封数据,用户可能需要密封密钥和数据的密码,并且 PCR 值可能必须匹配。TPM_Unseal 执行相应的解封操作。
IBM TCPA 教程包为五个主要组件提供源代码:设备驱动程序、libtcpa、示例、GRUB 补丁和环回补丁。
设备驱动程序代码允许您为内核编译 tpm.o 可加载模块。libtcpa 代码为本文讨论的应用级 TPM 命令提供了易于使用的 C 接口。示例程序演示了如何使用 libtcpa 执行常见操作,包括取得所有权、创建密钥、加载密钥、签名、密封和解封。GRUB 补丁是 GRUB 引导加载程序的源代码补丁。它增加了对 grub 本身以及任何指定文件(例如内核)的 PCR 测量的支持。环回补丁是环回驱动程序和相关实用程序的源代码补丁。此补丁允许环回加密密钥以 TPM 密封形式存储,并且仅在提供相应的密码且 PCR 值匹配时才释放它。安装此补丁后,环回挂载看起来正常;它会要求输入密码,但此密码仅用于授权解封实际的环回密钥数据。
那么,使用 TCPA 芯片进行签名和密封/解封对我们有什么作用呢?我们的私钥是在芯片上创建的,除非在受保护的公钥下加密,否则它们永远不会离开芯片。PCR 的使用还可以通过拒绝授权密钥的使用来保护我们的密钥,如果系统没有以正确的方式启动,或者如果测量的文件的完整性受到损害。密封环回密钥类似地可以防止替代启动和受损软件。
David Safford 是 IBM T. J. Watson 研究中心的研究员,他在那里领导一个安全分析小组,并有机会玩 TCPA、Linux 和无线安全等有趣的东西。可以通过 safford@watson.ibm.com 联系他。
Jeff Kravitz 在 IBM T. J. Watson 研究中心工作,他曾在多个项目上工作,包括通信网关、嵌入式系统的多任务操作系统以及千兆光网络控制器软件。Jeff 目前致力于公钥密码学的应用。
Leendert van Doorn 是 IBM T. J. Watson 研究中心的研究员,他在那里 руководит 安全嵌入式系统组。他 активно 研究过许多版本的 UNIX(从 V6 开始)、Amoeba、Paramecium 和 Linux。他甚至 известность, но решительно отрицает, что писал драйверы Windows。他目前的兴趣包括操作系统、安全、安全协处理器、模拟器和 гипервизоры。可以通过 leendert@watson.ibm.com 联系他。