纵观全书,我们已经介绍了怎样在程序中使用TPM命令的例子。这一章将关注怎样组合几种命令来创建一个使用多种TPM功能的应用程序。这样的程序在TPM1.2中并不容易实现,但是TPM2.0已经增加了一些功能来解决这些问题。
每一个客户端的TPM都会有一个EK。它是一个存储秘钥,并且还附带一个能够证明它来自一个真实TPM的证书。一个企业可能会有一个EK证书列表,证书分别对应这个企业购买的客户端机器。企业很可能会希望每个系统上有一个唯一的签名秘钥(通常叫做IDevID),它可以用于初始化一个VPN连接。但是,EK作为一个存储秘钥,不能用作VPN秘钥。TPM1.2有一个复杂的协议可以用于创建一个TPM签名秘钥的证书,这个证书可以证明这个秘钥是在TPM中创建的。但是,并没有商业上可用的CA机构支持这个协议。
TPM2.0EK比1.2EK的鲁棒性更强。一个2.0EK可以用加密其他的秘钥。具体来讲,一个企业可以创建一个限制性签名秘钥,然后用EK加密,这样一来,只有拥有这个EK的的TPM可以导入这个秘钥。这与现在用于配置秘钥的“发送一个PKCS#12文件”技术类似(TPM这个更安全)。通过这样的方式,包含私钥的PKCS#12文件会被创建并发送到客户端。然后客户端还会通过旁路收到一个口令,这个口令用于解密刚刚收到的PK12文件;然后将解密出的私钥部分存储在安全的地方。这种技术会可能将私钥暴露,安全性与口令相当。相比之下,TPM的协议更加安全。
有三种方式可以将一个签名秘钥声称为IDevID:
- 技术1:在服务器端的TPM或者TPM模拟器中创建IDevID,然后使用标准的CA创建这个秘钥的证书。然后执行TPM复制秘钥操作,从而使拥有客户端EK的系统能够导入这个秘钥。这个过程叫做复制这个秘钥,秘钥的新父秘钥是客户端系统的EK。
- 技术2:(在TPM外部)创建一个IDevID秘钥并认证它。将它加密从而使秘钥看起来像一个复制过的秘钥,这样就可以被导入到客户端的EK下。
- 技术3:(在TPM外部)创建一个IDevID秘钥并认证它。将秘钥导入本地的TPM或者TPM模拟器中,然后像技术1一样将秘钥复制到客户端系统的EK下。
这三种技术在实现上有稍许不同,接下来我们将介绍。
在这种情况下,因为TPM会复制密钥,所以必须满足以下条件。首先,密钥是可复制得。也就是说密钥的fixedTPM属性为false,同时fixedParent属性为false。第二,密钥必须要有Policy授权,因为只有具备policy授权的密钥才可以被复制。这个policy还要至少满足以下条件中的一条:
- 使用TPM2_PolicyCommandCode命令,参数为TPM2_Duplicate。
- 使用TPM2_PolicyDuplicateSelect命令。
以上条件中的一个必须至少在一个Policy分支中。因为你肯定不想让密钥被复直到目标TPM以外的地方,因此如果你使用上述第一个选项,还必须得进一步地限制这个Policy分支——或许是使用TPM2_PolicySigned。此时,使用第二种选择更好:仅仅将密钥复制的目标父密钥固定为EK的公钥即可。
更进一步,因为IDevID密钥与AIK类似,所以IDevID必须一个限制性的签名密钥(此时可能已经完全忘了AIK是什么东东了)。在服务器上使用TPM2_Create命令创建这个密钥。
接下来你需要使用你的企业CA为这个密钥创建证书。在创建证书之前,需要首先验证EK的证书,确认它有效。然后这个新创建的证书就可以声称这个IDevID密钥隶属于PC上的EK。因为这个密钥是签名密钥,这个过程应该不难——一个正常的CA协议应该就可以完成这个任务。
为了复制这个密钥,现在你需要创建三个文件,每个文件都代表一个用于将IDevID密钥导入到目标PC的EK下时的一个参数。这个复制过程使用TPM2_Duplicate命令来完成。(当然了,在执行这个命令之前,你还要首先满足允许复制密钥的Policy分支。)这个命令有三个输出,分别存储到三个文件种:
- TPM2B_PUBLIC:这个数据结构是对IDevID密钥的描述,其中还包括密钥的公共部分。
- TPM2B_PRIVATE:这个数据结构包含IDevID的私钥部分,并且是经过对称加密的;同时还有一个与公钥部分绑定的HMAC值。
- 一个加密过的值,这个值可以让拥有正确EK的TPM重新生成一个种子。
这三个输出是TPM2_Import命令的输入。种子用于TPM生成一个AES密钥,密钥用于解密经过对称加密私钥部分,以及解密用于验证复制的公钥私钥没有篡改的HMAC密钥。
最后,你需要将这三个文件发送到目标PC上。如果PC当前没有存储EK,需要首先使用TPM2_CreatePrimary重新生成EK。然后使用TPM2_Import命令,并传递收到的三个文件作为参数。TPM2_Import命令会返回一个TPM加密过的数据块,这个数据块可以通过TPM2_Load命令将IDEVID密钥加载到TPM中,当然EK必须存在才行。
这个技术的优点就是TPM(或者模拟器)会完成大部分的工作。它的缺点是终端用户必须创建Policy,并且必须依赖TPM或者模拟器的随机数发生器。一个硬件TPM的随机数发生器可能太慢,而模拟器中的软件实现的随机数发生器又可能质量不够高。
如果你想使用TPM来做密钥复制,那密钥必须使用Policy来授权。但是你也完全在TPM之外创建密钥,然后将它打包成经过复制的密钥的格式,这种格式的密钥可以通过它引用的EK被导入到TPM中。如果使用这种方式,你就不用为密钥设置Policy授权。此时,你需要自己编写软件来创建TPM2_Import命令的三个参数文件。
第一个文件包含了IDevID的公共数据;第二个文件包含使用一个AES密钥加密过的IDevID的私有数据,以及IDevID公共和私有数据的HMAC值;第三个文件用于拥有对应EK的TPM计算一个种子,这个种子用于派生AES密钥和HMAC密钥。创建这个文件时有许多细节内容需要决定;这些内容在TPM规范中描述的很详细,尤其是第1部分和第2部分。
如果IDevID是一个RSA公钥,它将会使用TPM2B_PUBLIC这个数据结构(在规范第2部分,12.2.5节)。这个结构体引用了TPMT_PUBLIC结构体,它在规范第2部分的12.2.4节中定义。TPMT_PUBLIC反过来又引用了许多其他结构体和参数:
- TPMI_ALG_PUBLIC,在这里的具体值是TPM_ALG_RSA。
- TPMI_ALG_HASH,在这里的具体值是TPM_ALG_SHA256。
- 一个TPMA_OBJECT位图,这用于描述密钥的类型(可以用于指定密钥是否是签名密钥,是否可以被复制等等属性,这跟使用TPM创建密钥时的情况一样)
接下来TPMT_PUBLIC结构体的两个参数是联合体:
- TPMU_PUBLIC_PARAMS(参考12.2.3.7节)。假设你的密钥是TPM_ALG_RSA,此时这个联合就是TPMS_RSA_PARAMS(12.2.3.5)。
- TPMU_PUBLIC_ID(12.2.3.2)。假设你的密钥是TPM_ALG_RSA,这个联合就会变成TPMS_PUBLIC_KEY_RSA(包含RSA公钥的长度和具体内容)。
在创建这些文件时,第三个要先于第二个文件创建,尽管逻辑上来说公钥和私钥文件通常是一块创建的。这样做是因为我们需要使用第三个文件来加密第二个。第三个文件是使用新的父密钥对象加密过的种子。这个种子可以用一个随机数生成器生成(TPM设备中的随机数发生器就很好),并且种子的大小应该与哈希值得大小相同——在这个例子中,也就是256比特。一旦拿到这个种子之后,就可以用它生成两个密钥:一个HMAC密钥用于证明密钥公私密钥之间联系的完整性,另一个是用加密私钥部分的对称加密密钥。HMAC密钥的描述参考规范第1部分的22.5节,公式37:
HMACkey := KDFa(pNameAlg, seedValue, "INTEGRITY", NULL, NULL, bits)
对称加密密钥使用共识35:
symKey := KDFa(pNameAlg, seedValue, "STORAGE", name, NULL, bits)
现在你必须使用EK的公钥加密这个种子。这是根据规范第1部分的附录相关内容来做的。具体到RSA算法,附录B10.3显示采用了使用RSA公钥的OAEP(Optimal Asymmetric Encryption Padding)
密钥的私有数据是一个叫做TPM2B_PRIVATE的结构体,可以在规范第2部分12.3.7节中找到。它包含一个加密的私钥数据和数据的大小。加密的私有数据区域包含两部分:一个完整性区域和一个敏感数据区域。首先计算敏感数据区域。
敏感数据区域包含一个TPMT_SENSITIVE数据,这可以在规范第2部分12.3.2.4的表188中找到。这其中又包含TPMU_SENSITIVE_COMPOSE,它在表187中对应TPM_ALG_RSA的内容为TPM2B_PRIVATE_KEY_RSA。另一个表显示,TPM2B_PRIVATE_KEY_RSA的前面还有一个2字节的长度信息(注:可能是规范版本不同的原因,译者发现这些表的索引不能match,所以后来干脆用“另一个表”来表达)。敏感数据被一个AES密钥使用CFB模式加密,使用IV为0。这同样可以在规范第1部分中找到。
完整性摘要是针对密钥公共数据和加密过的私有数据计算的HMAC,HMAC密钥则由种子派生。HMAC的计算公式如下:
outerHMAC := SHA256(HMACkey, encrypted Sensitive area || TPM2B_PUBLIC)
完整性摘要会被放置到敏感数据区域前面,摘要前面则会插入一个2字节的摘要大小,这样最终的私有数据结构就完成了。这一步完成之后,就可以将三个文件发送到远程客户端PC中,进而是用TPM2_Import命令将密钥导入到EK下。
这个技术有点复杂,但是它允许以管理员想要方式创建密钥,并且这也是在没有Policy授权的情况下创建一个可复制密钥的唯一办法。这种方式保证了同时还保证,这个密钥不能从目标PC的TPM中再次复制到其他任何TPM中,因为TPM复制密钥时总是要求使用Policy授权。
如果你希望自己生成IDevID密钥,或许这是因为你有一个可信的随机数发生器,但是你不希望自己做生成三个文件的工作,那你可以直接将这个密钥导入到本地TPM(或者模拟器)中,然后让TPM来做复制密钥的工作。这种方式仍然需要用户生成TPM格式的密钥公共数据,这其中就包含一个可以允许复制的Policy数据。但是生成种子的工作则可以交给TPM来完成。(种子的作用就不再赘述了,真佩服老外的耐心细致,不厌其烦地给你讲种子的作用。)
这种方法会使用TPM2_LoadExternal命令,这个命令不需要密钥加载时私钥部分是加密过的。加载完成后,用户可以继续使用技术1中的步骤。接下来用户需要加载EK的公钥,然后满足用于复制的Policy,然后将密钥复制到EK下。这时就会产生三个文件,用户确认自己系统的TPM已经加载EK之后就可以导入这个密钥了。
今天大多数的PC有很多额外的硬盘空间。很多企业都有许多需要定期备份数据的PC。一个理想的情况是这些PC可以将它们的数据备份到其他的商用PC中。这样一来,一个企业扩展更多的上用PC时,对应的备份用的可用空间也同步增加。有一些算法能够实现只要m份数据中的n份存在就可以恢复数据,这将有助于高效地利用空间。但是,本书不会描述这些技术,因为我们关心的是另外一个问题:怎样保证备份的数据安全性?
首先,数据必须加密,并且只有数据的所有者可以访问。这可以通过如下过程来实现:使用TPM创建一个HMAC密钥,并使用TPM2_EvictControl命令加载到TPM的持续性内存中。然后使用这个HMAC密钥对文件的名称作HMAC,这个HMAC用于创建一个AES密钥,AES密钥最终用于需要备份的文件。(这就为每个文件创建一个加密密钥,并且能够访问HMAC密钥的人很容恢复这个AES加密密钥)又或者是,你可以让TPM生成一个AES密钥(使用TPM的随机数发生器,因此对于每个文件来说,加密密钥也是唯一的);TPM生成的这个密钥可以被TPM的一个公钥加密,这个密钥本身用于加密文件。不管是上述哪一种情况,为了防止TPM出问题,HMAC密钥和加密AES密钥的公钥对应的私钥都应在另外一个系统的TPM中做备份。
现在的终端用户需要选择和设置以下组织架构的授权值:存储组织架构(所有者授权),背书组织架构(隐私管理员使用),以及字典攻击复位参数(用于TPM管理员复位字典攻击)。这就根据不同类型的管理员对TPM的控制权做了分割。但是可能还需要更细致的命令控制(尤其是所有者授权)。
第10章展示了很多不同Policy的示例,但是并没有涉及一个存储组织架构的Policy怎样被分割。存储组织架构的所有者可以创建NV索引,创建主存住组织架构,以及让密钥变成持续性的密钥(反之也可以),还能做很多其他的事情。一个管理员可能非常想让一个终端用户可以创建主密钥,但是不能将新创建的主密钥变成持续性密钥,或者想让用户可以创建NV索引,但是不能删除存储组织架构下的持续性密钥。
如果一个根存储密钥(Storage Root Key)是由一个管理员创建并设置成持续性密钥的,那么如果没有所有者的授权,就不能修改这个密钥的授权值。这一点非常重要,因为这个授权值通常被设置成空,这是为了让任意软件使用存储在TPM中的SRK。如果恶意代码试图擦除这个密钥并使用不同的授权值来重新创建,这将是一种DOS攻击。
但是终端用户可能希望软件可以使用与SRK默认算法不同的算法。这时候,所有者就希望能创建使用不同算法的SRK。最后的结果就是,所有者可能希望软件可以使用直接由TPM2_CreatePrimary命令创建的密钥。但是,所有者又不希望这些密钥变成持续性密钥,因为这样会使用宝贵的TPM资源——这可能是另外一种形式的DOS攻击。
最后,所有者可能希望允许软件创建一些有空间限制的NVRAM索引。但不是所有以上的功能都能通过使用正确的Policy来实现。
为了分割特权,所有者首先将所有者授权值设置成一个很长的随机数口令,并保证这个口令被安全地存储。这就要求终端用户使用Policy来完成他们的目标(工作)。接下来,为所有者希望用户使用的每一个命令设置Policy分支。为了允许用户使用TPM2_CreatePrimary命令,这个Polciy分支就是执行TPM2_PolicyCommandCode并以TPM2_CreatePrimary命令作为参数。为来仅仅允许用户创建特定的NVRAM索引,需要使用TPM2_PolicyCpHash命令,命令的参数为TPM2_NV_DefineSpace和以及表示将要被创建的索引参数和索引的公共参数。所有者希望用户创建的每一个索引都按上述方式创建Policy分支。(这有点像在防火墙中开一个端口——并不代表这个端口会被使用。)
口令是一种弱的授权方式。开始时,用户名和对应的口令被存储在服务器上。如果有些人可以访问到这个用户名和口令得列表,这个方法是很脆弱的。因此,为了让访问真正的列表更困难,创建了一个镜像文件系统。但是这样仍然太弱了。更进一步的方法是存储口令的哈希而不是存储口令本身。但是,字典攻击和彩虹表攻击也会这种技术变得脆弱。接下来,人们还尝试了加盐的口令哈希值(在口令在做哈希之前增加一个随机数),但是在验证口令的时候盐值必须存在。此时,高性能计算机就可以对加盐的口令哈希列表进行字典攻击。因此,服务器开始对口令做多轮哈希运算,可能有1000次,试图让字典攻击的代价更大,但是实际上并没有,同时也使得口令得验证时间太长。之后,云计算的出现让这种防护更加低效。
TPM可以用于解决这个问题。可以使用TPM的密钥生成一个加密的哈希(也就是HMAC),而不是使用之前的明文盐值。这个HMAC是针对用户名和口令来做的。若果这样做,即使整个口令的HMAC列表被公开也不会对攻击者有什么帮助——没有HMAC密钥,攻击者不得不依赖TPM来产生HMAC值。这就降低了系统遭受并行化攻击的风险,或者减少了使用高性能计算机做线下攻击的可能性。
实现上述的技术相对来说比较简单。在Linux中,认证是通过可拔插的授权模块来实现的(Pluggable Authentication Modules);它们在设计时就允许增加不同类型的认证方式。
首先要生成一个大的随机数——假设是一个256比特长的数——用作你的HMAC密钥。这个数可以通过TPM2_GetRandom命令或者FAPI的Tpm2_GetRandom函数来生成。这些命令会提示你需要多少字节长的随机数:在这个案例中,是32字节。这个命令的结果称为M,并且将它安全的存储起来,以防TPM损坏或者主板被移除。
接下来你需要加载M到TPM中。可以使用TPM2_Load命令来实现。需要为M指定一个空口令,因为你想在不授权的情况下使用它。(因为你想将M持续性的存储在TPM中,因此你不能使用TPM2_LoadExternal命令。)这个随机数(HMAC密钥)通过所有者授权被加载到存储组织架构中。然后使用TPM2_EvictControl命令让M变成持续性的对象。这个命令调用会返回一个持续性的handle,这个handle可以在后续用作M的ID。
现在你需要写一个PAM(这不在本书的讨论范围内)。当提供一个新的口令时,这个PAM就使用TPM2_HMAC命令结合M来对用户ID和口令做HMAC。当用户ID和口令被传递到PAM用于授权时,PAM使用类似的方式生成HMAC并与用户ID/HMAC列表中对应的HMAC比较。
HMAC命令相对来说更快,因此这应该不会明显地增加认证过程的延迟,但是这样能很好的防止系统遭受,窃取密码文件并试图用离线方法逆向的攻击。
医疗系统需要足够安全。不幸的是,有很多研究表明,像心脏起搏器和血糖控制系统并不是那么安全。一种让系统更加安全的方式是,确保设备上运行的固件是经过设备厂商允许的。这也就意味着这个设备需要有一个公钥用于验证它要加载的固件是经过厂商正确签名的。但是密钥存储在哪里呢?
一个明显得解决方案是将密钥存储在TPM中。这个公钥可以通过TPM2_LoadExternal命令加载,然后使用TPM2_EvictControl命令让密钥变成持续性的密钥。现在,用于更新固件的命令就可以在加载固件之前,使用认证过的TPM2_VerifySignature命令来验证新的固件确实是经过厂商签名的。自己编写密码学的验证代码很难做到不犯错,因此使用经过认证的代码(这里是指TPM固件)来做密码学运算真得是一个优势。
以下作为一个旁注,类似的方式也可以用于文档:护照或者甚至是现金可以包含签名。一个可移植的带有TPM的扫描器可以用于快速验证这个签名,扫描器中包含文档授权机构的公钥。如果一个伪造者开始产生大量的假币,他们首先得需要同样数量的真货币来复制上面的签名。如果他们仅仅复制了少量的货币,一旦被发现,这些相关的证书就可以被撤销(想法很新鲜,这里的意思是指一个货币一个证书?)。
TPM2.0不仅仅是一个集成各种可供使用算法的硬件。它是新功能的推动者。这一章中的示例还没有被试图应用到商业或者开源软件中,但是我们很乐意看到这样的情况。即使是一个仅仅用于加强口令存储的应用实现,也将是整个社区的一个胜利。安全问题到处都是,TPM2.0可以作为提供这些问题的解决方案的工具。