授权和会话是TPM2.0规范中最重要的概念之一。授权用于控制TPM实体的访问,它为TPM提供了很多安全保证。会话是授权的引擎,并且也会维护多个命令之前的状态。除此之外,会话还用于配置一些命令相关的属性,比如说密令和命令响应参数以及审计的加密和解密操作。这一章介绍会话是因为他们与实体的授权动作相关。第16,17章将介绍命令会话相关的会话使用修饰符细节。
授权和会话是一个范围很大的话题,所以这张将会按照以下的过程介绍:
- 授权你将学习针对授权和会话的新概念。
- 你将会从较高的层次了解口令,HMAC,和policy授权,同时还有它们各自的安全属性。
- 这一章会澄清会话和授权的异同,同时还有一些可能让人混淆的规范文档中的内容。
- 你将深入学习授权的放方方面面,它们都适用于以下三种授权:口令,HMAC,以及Policy。你将会学习授权角色,以及命令和命令响应字节流中的授权区域。
- 你将会详细的研究不同授权类型,从最简单到最复杂的口令,HMAC和Policy。介绍完口令授权以后,你将会首先了解一些HMAC和Policy授权的共同点,然后分别介绍这两种授权的详细内容。
- 最后,所有的授权类型组合到一起形成混合型授权生命周期。
这一章不会描述各种各样的Policy授权命令。也不会介绍加密,解密和审计会话,只是提醒读者会话是这些操作的引擎。
这一章会使用图表,逻辑流,以及可以运行的代码示例来描述授权和会话是怎样工作的。这些材料是理解TPM2.0的基础。准备好艰难但会收获满满的学习旅程吧。
在我们深入挖掘授权和会话这个主题之前,我们需要清楚地理解一些新的概念。这些概念不在我们第5章介绍的概念范围之内。在阅读本章后续的内容时需要参考第5章的概念以及以下几个新的概念:
- 会话创建的变量:这些变量是在会话创建时设置的,并且会在会话生命周期内一直存在。它们用于决定会话和HMAC密钥是怎样创建的,以及HMAC是怎样生成的。一共有两种选择:bound和unbound,以及salted和unsalted。这两种选择的组合构成了四个会话变种。后续我们会详细地介绍它们。现在我们仅仅做一些概括的介绍:
- Bound会话实际上就是将授权与一些实体的授权值绑定。这个绑定是通过将被绑定实体的授权值加入到会话密钥创建过程中。这个会影响到所有依赖会话密钥的计算,包括HMAC,policy,加解密计算。
- 一个Unbound会话就是在会话密钥生成过程中不使用其他实体的授权信息。
- 一个Salted会话会增加额外的熵值,这个盐,会被加入到会话密钥创建过程中;这与bound会话,它也同样会影响到依赖会话密钥的计算。这个额外的熵值是以加密的形式被发送到TPM设备中的,加密过的盐参数会被TPM2_StartAuthSession命令使用。
- 一个Unsalted会话就是创建会话时不加入额外的熵值。
- 会话使用修饰符:这些修饰符会改变HMAC和Policy会话在一次命令中的行为。Continue,encrypt,decrypt,以及audit是比较常用的几种修饰符:
- Continue:如果不设置,这个会话在命令执行成功后就会终止。
- Decrypt:用于指示发送到TPM设备的第一个TPM2B命令参数是加密过的。
- Encrypt:用于指示TPM设备将发送过来的命令响应的第一个TPM2B参数进行加密。
- Audit:指示命令使用这个会话的命令要被审计。 在以上概念的基础上,我们就可以描述不同类型的会话了。
所有这三种会话都是执行授权动作的方式,在HMAC和Policy会话都是基于单次命令来配置会话的。口令会话是最简单的授权类型:一个明文口令会被传递到TPM设备中用于授权一个一个动作。如果TPM是以远程的方式来访问的,这显然会有安全性的问题;口令授权的初衷就是用于本地访问。在TPM设备中有一个单一的,永远可用的口令会话用于授权一个单独的TPM命令,会话不会保存两次命令之间的任何状态。正因为如此,口令会话从来不需要被启动。
HMAC授权是一种更安全地使用口令的方式;一旦应用程序和TPM设备针对口令达成一致(这是在实体被创建或者实体的授权值被修改时形成的一种共识)后,就再也不需要传输口令了。而在创建实体或者修改实体授权信息时的一次性口令传输也可以以一种安全的方式完成:那就是口令可以用加密形式传输。一个HMAC会话可以把口令(这个口令在TPM2.0规范中叫做authValue)作为命令和响应计算HMAC时的一个输入参数,从而时授权达到一个很高的安全级别。在执行一个命令时,调用命令的应用程序会计算HMAC值并将这个值插入到命令字节流中。当TPM收到这个命令字节流后,如果TPM判定HMAC被正确地计算,那么相应的动作就被授权。在响应一个命令时,TPM会计算响应数据的HMAC值然后将这个值插入到响应字节流中。调用命令的应用程序收到命令响应数据流后会再单独计算一次HMAC,然后再与响应字节流中TPM计算的结果对比。如果这两个值匹配,那么响应数据就是可信的。所有的这些过程都是建立在应用程序和TPM都知道并且达成共识的authValue之上的。
HMAC会话会用到两个随机数,又叫作nonce,一个来自于调用者(nonceCaller),另外一个来自TPM(nonceTPM),这两个随机数可以用于防止重放攻击。随机数会用于HMAC的计算中。因为nonceTPM在每次执行命令时都会改变,调用命令的应用程序如果愿意的话也可以在每次执行命令的时候修改nonceCaller,所以攻击者就不能重复使用命令数据流。重复使用的命令字节流用于HMAC计算时一定会失败,这是因为随机数(确切的说是nonceTPM?)在重复使用时会变化。
HMAC会话会维护会话生命周期内的状态,因此会话可以用于针对TPM实体的多个动作。启动一个HMAC会话使用TPM2_StartAuthSession命令。当启动会话时,它可以被配置为bound vs. unbound以及salted vs. unsalted会话。这两种选择的组合构成了四种HMAC会话的变量;这四个变量决定了会话密钥和HMAC如何被创建。
Policy会话,又被称作增强的授权(Enhanced Authorizaiton),是建立在HMAC会话的基础上的,并且增加了额外的授权级别。HMAC授权仅仅使用授权值或者口令,然而Policy授权在此基础上做了增强,它可以根据TPM命令的顺序,TPM状态,以及外部设备如指纹读卡器,视网膜扫描器,和智能卡等信息来做授权。许多这些授权条件可以通过AND和OR操作组合成复杂的授权树,从而提供无限多的授权可能。
表13-1全局性地总结了各种类型的授权方式。
表13-1。
在此基础上,我们接下来了解一下规范在使用会话和授权时的细微差别。这里请注意;理解这些差别将极大地增加你阅读和理解TPM2.0规范以及本章后续章节的能力。
会话和授权是紧密相连的,并且有时候在TPM2.0规范中是重叠的,但是它们并不是相同的概念。会话是执行授权的引擎,但是会话还会用于授权之外的目的,这可能是与授权结合或者是完全独立于授权。比如说,用于授权的会话也可以被用于指定命令修饰符,如encrypt,decrypt,以及audit。会话也可以仅仅被用于指定这些修饰符而不做任何授权。
TPM2.0规范本身经常会重叠使用会话和授权这两个概念。如下是一些规范中的示例:
- 命令中的授权区域会用于授权和会话。但是会话可能以一种与授权完全不相关的方式被使用。比方说,它们可以用于加密和解密命令以及命令响应的参数,以及用于使能命令的审计。所以,与授权完全不相关的会话可能因为上述这些目的而被建立。
- TPM_ST_NO_SESSIONS和TPM_ST_NO_SESSIONS这两个标签被用于指示一个命令中是否包含授权区域,这显然缺乏命名上的一致性。
- 会话使用TPM2_StartAuthSession命令来启动。这个命令的名称表明一个授权过程被启动了,但是实际上是一个会话被启动了。然而这个刚刚启动的会话可能根本不会用于授权。
- 另外一个案例是口令授权。技术上说这些过程是会话,但是命令之前没有维护任何状态,并且TPM2_StartAuthSession并没有用于启动口令会话。一个口令授权就是一个一次性的授权方式,它仅仅对一次命令有效。
提醒你注意以上这些方面是为了有助于你理解规范。理解这些模糊的应用之间的区别曾经帮助我理解这些概念。最终我制作了一个图表将各种类型的授权,会话,以及会话修饰符归类。希望这个也可以帮到你。
图13-1 授权和会话的韦恩图
以下几点是针对这个图表的特殊提醒:
- 授权可以是口令,HMAC,或者是Policy授权。
- 口令授权不能被用于会话使用修饰符中。
注意:在图13-1中,审计,加密或者解密仅仅是展现出来的会话使用修饰符,还有其他的没有表现出来。展现着三种修饰符是因为他们通常更加常用。
- HMAC和Policy会话可以被用于授权,但是也可以被用于设置会话应用修饰符并且跟授权完全无关。这也是图中HMAC和Policy会话的区域越出“授权”圆形边界的原因。
- 命令的“授权区域”包含了所有的授权操作,会话,以及指定的会话修饰符。
- 命令修饰符可以在用于授权的会话中使用,也可以在不用于授权的会话中使用,这也是审计,加密,和解密圆形区域越出授权圆形边界的原因。
- 即使不是用于授权的会话也可以出现在命令和命令响应的授权区域内。
- Policy会话可以被用于加密,解密,但不能用于审计(根据TPM2.0规范开发者的说法,这样做是出于优化的原因,而非任何基础性的技术困难)。
- HMAC会话可以被用于加密,解密,以及审计。
图13-2 授权和会话框图
图13-2以不同的角度描绘了他们之间关系。关于这个图表,需要注意一下几点:
- 授权区域可以指定用于口令,HMAC,或者Policy会话的参数。授权区域将会在下一个小节中详细介绍。
- 使用TPM2_StartAuthSession命令启动的会话可以是HMAC,Policy,或者测试(trial)Policy会话。
- HMAC会话可以根据单个命令配置成audit,decrypt和encrypt会话。
- Policy会话可以根据单个命令配置成decrypt和encrypt会话。不能用于audit。
- 图示的四种会话初始化变量可以应用于HMAC,Policy,或者Trial Policy会话中。
需要记住的重要一点是,会话时执行授权操作的引擎,但是会话也可以用于授权无关的单次命令动作,这些动作是通过会话修饰符来设置的。
先不考虑会话是怎样被使用的,或者授权的类型,如果有授权或者会话,这些数据都是通过命令和命令响应的授权区域来输入和输出TPM设备的。在我介绍命令和命令响应区域之前,你需要先明白授权角色的功能。
TPM2.0规范的第3部分对每一个命令的描述中都有指定授权角色。这角色和角色相关的规则与计算机文件夹的ACLs(Accessd Control Lists)的工作方式类似。授权角色控制着与命令执行相关的授权的类型,实际上也就是控制着谁可以在什么情况下执行哪些命令。
一共有三种可能的授权角色:USER,ADMIN,和DUP。USER用于TPM实体的正常使用。ADMIN用于系统管理相关的任务。DUP,一个狭义的角色,是TPM2_Duplicate命令允许的角色。
用于决定一个TPM实体授权类型的两个属性是userWithAuth和userWithPolicy。这些属性既可以在创建对象的时候显式地指定,也可以通过特定的永久性handle和NV索引来决定:
- userWithAuth:
- 置为1表示USER角色的授权可以通过口令,HMAC,或者Policy会话提供。
- 置为0表示USER角色的授权必须通过Policy会话提供。
- adminWithPolicy:
- 置为1表示ADMIN角色的授权必须通过Policy会话提供。
- 置为0表示ADMIN角色的授权可以通过口令,HAMC,或者Policy会话提供。
如果一个命令的授权角色是ADMIN:
- 对于TPM对象的handle来说,需要的授权由这个对象的adminWithPolicy属性决定,这个属性是在对象创建的时候设置的。
- 对于TPM_RH_OWNER,TPM_RH_ENDORSEMENT,以及TPM_RH_PLATFORM这些handle来说,它们的授权相当于adminWithPolicy置为1。也就是说必须通过Policy会话提供授权。
- 对于NV索引来说,它们的授权相当于创建NV索引时将adminWithPolicy这个属性置为1。
如果一个命令的授权角色是USER:
- 对于TPM对象的handle来说,需要的授权由这个对象的userWithAuth属性来决定,这个属性是在创建对象的时候设置的。
- 对于TPM_RH_OWNER,TPM_RH_ENDORSEMENT,以及TPM_RH_PLATFORM这些handle来说,它们的授权相当于userWithAuth置为1.
- 对于NV索引来说,它们的授权类型由如下的NV索引属性决定:TPMA_NV_POLICYWRITE,TPMA_NV_POLICYREAD,TPMA_NV_AUTHWRITE,以及 TPMA_NV_AUTHREAD。这些NV索引的属性是在创建NV索引时指定的。
如果一个命令的授权角色是DUP:
- 授权必须是Policy。
- DUP角色只能用于TPM对象。
如果授权角色是DUP或者ADMIN,索要授权的命令必须在Policy中指定(PolicyCommandCode)。
现在你应该已经理解了授权角色了,接下来让我们看一下授权区域。
第5章描述了命令和命令响应的数据原理图,但是故意留下一个重要的数据区域没有介绍:授权区域。这个区域就是命令和命令响应数据流中被指定用于会话和授权数据的地方,详细的情况被延后到本章来介绍。
为了让这些概念的更具实践性,这一节会使用TPM2_NV_Read来仔细探究这两个授权区域。其他可以拥有授权区域的相关数据格式与我们介绍的是相同的。
图13-3展示了TPM2_NV_Read命令和命令响应数据结构以及授权区域的位置。主要注意的是这些授权区域在TPM2.0规范中并没有明确地被指出来,它们是通过隐式的方式展现的;这也是将授权区域说明放在命令及命令授权区域数据结构表左侧的框格中的原因。对于所有需要授权的命令来说,授权区域都会被放置在handle区域之后以及参数区域之前。命令响应的授权区域被放置在响应的末尾,紧随响应参数之后。
图13-3 NV_Read命令和命令响应数据结构图,摘自TPM2.0规范,第3部分,以及授权区域的位置。结构图左侧的格子表明了授权数据将被插入的位置。这对于新读者来说会感到很疑惑,但是掌握这一点却非常重要。
对于任意可以使用授权的命令来说,授权区域最多可以有三个授权结构。对于一个成功执行的TPM2.0命令来说,命令响应的授权结构的数量总是与命令的授权数量相同。对于一个执行失败的TPM2.0命令来说,命令响应的授权数据结构的数量总是0。
如果一个命令的authHandle前面用@符号修饰:这就意味着authHandle对应的实体的授权数据需要加入到这个命令的授权区域中。在命令图表中描述这一列的进一步注释中,“Auth role:USER”表示这个命令需要授权角色。
图13-4描述了命令授权结构体,TPMS_AUTH_COMMAND。这个图展示了图13-3中命令授权框格的详细内容。
- session handle:一个四字节的数值用于表示与这个数据块相关的会话序号。
- size field:一个两字节的数值用于表示nonce中的字节数。
- nonce:如果存在的话就表示命令调用者的nonce值。
- session attributes:一个字节,其中的一些位用于表示会话的用途。
- size field:一个两字节的数值用于指定authorization的大小。
- authorization:如果存在的话,它包含一个口令或者HMAC值,具体是什么由会话的类型决定。
图13-4 命令授权结构体,TPM2_AUTH_COMMAND。
尽管当前的TPM2.0规范并没有把authorizationSize严格地定义成命令授权数据结构的一部分,但是如果命令的标签是用于指示授权区域存在的TPM_ST_SESSION时,命令数据中确实会存在一个authorizationSize。这个值用于帮助解析命令数据的代码确定授权区域中有多少会话,以及到哪里找会话的参数。如图13-5所示,这个值就在授权数据结构的前面。
图13-5。
图13-6展示了一个命令响应的授权数据结构,TPMS_AUTH_RESPONSE。这个图同样展示了图13-3中命令响应的授权框格的详细内容。
- size field:一个两字节的数值用于表示nonce中的字节数。
- nonce:如果存在的话就表示TPM的nonce值。
- session attributes:一个字节,其中的一些位用于表示会话的用途。
- size field:一个两字节的数值用于指定acknowledgment的大小。
- acknowledgment:如果存在的话,它包含HMAC值。
命令响应授权区域在整个命令响应区域的末尾。为了便于找到授权区域,规范在命令响应参数区域签名插入一个UINT32类型的parameterSize值,它指示了所有命令响应参数的大小。用于解析命令响应授权区域的代码只要使用parameterSize来跳过响应参数区域,这样就可以找到授权区域了。如果响应数据中没有授权区域的话,也就没有parameterSize这个值了。
图13-7
现在我们已经了解授权区域是什么样的了,接下来我们详细了解一下三种类型的授权方式。
口令授权是最简单的授权方式,所以我们首先来介绍它。这一小节展示了口令授权的生命周期:怎样创建一个使用口令授权的实体,怎样修改一个已有实体的授权口令,以及怎样使用一个口令来授权实体相关的动作。
一个口令授权的生命周期非常简单:创建一个使用口令作为授权的实体,然后使用口令对实体相关的动作授权。具体来说,创建和使用口令授权的所需要大概步骤如下:
- 创建一个使用口令授权的实体,或者修改已有实体的口令值。这个步骤对每一个实体来说只做一次。
- 使用口令授权实体相关的动作。这一步骤可以执行多次,并且一旦给实体设定好授权口令后随时都有可能发生。
首先我们来看一下步骤1,创建一个使用口令授权的实体,或者修改已有实体的授权口令。
为了创建一个实体,需要使用以下命令:TPM2_CreatePrimary,TPM2_Create,以及TPM2_NV_DefineSpace。这几个命令都有一个用户传递authValue的参数,这个authValue就是用于授权的口令。authValue既可以是一个明文口令,也可以是HMAC授权的输入,但是既然这一小节就是要介绍口令授权,所以我们只说authValue是一个明文口令。口令授权介绍完以后就会接着介绍HMAC授权。
如下是TPM用于创建实体的命令的详细说明:
- TPM2_CreatePrimary用于创建一个组织架构中的主对象(或者说是基础对象?)。如果参数inPublic的userWithAuth属性为1,USER授权可以是一个口令授权;这也就是说,使用USER角色的授权动作可以是口令或者HMAC。authValue的值是通过参数inSensitive中的userAuth传递的,现在我们指的authValue就是口令值。
- TPM2_Create用于创建可以加载到TPM中的对象。这个命令的授权类型userWithAuth,以及authValue的配置方式都与TPM2_CreatPrimary相同。
- TPM2_NV_DefineSpace用于定义一个NV索引。如果属性TPMA_NV_AUTHREAD或者TPMA_NV_AUTHWRITE被设置,就可以使用口令授权。输入参数authValue是通过TPM2_NV_DefineSpace的auth参数来传递的。
如果需要修改一个实体的授权口令,可能需要以下命令:
- TPM2_ObjectChangeAuth:它可以用于修改“非”主对象的授权值。
- TPM2_HierarchyChangeAuth:用于修改一个组织架构的授权值(平台,所有者,或者背书),或者是锁定机构的授权值。
- TPM2_NV_ChangeAuth:用于修改一个NV索引的授权值。
现在让我们来了解一下步骤2,使用口令来授权一个相关的动作。
再次说明,口令授权是TPM最简单的授权方式。使用口令授权一个动作不需要启动会话。调用者仅仅需要配置如图13-8所示的命令授权数据结构就可以了。
图13-8。
命令响应授权区域如图13-9。
图13-9。
列表13-1展示了通过TSS SAPI使用口令会话得示例代码。这段代码使用了第7章中介绍得SAPI。因为现在还没有学习授权和会话,这章得最后才会介绍授权相关得数据结构得函数。为了能够让你明白示例代码,你需要了解SAPI中会话和授权相关的数据结构:
- TSS2_SYS_CMD_AUTHS:指定了命令的授权区域个数以及授权内容。这个结构体看起来如下:
typedef struct {
uint8_t cmdAuthsCount;
TPMS_AUTH_COMMAND **cmdAuths;
} TSS2_SYS_CMD_AUTHS;
- TSS2_SYS_RSP_AUTHS:与上一个结构体类似,它指定了命令响应中包含的授权区域的个数以及授权内容。这个数据结构看起来如下:
typedef struct {
uint8_t rspAuthsCount;
TPMS_AUTH_RESPONSE **rspAuths;
} TSS2_SYS_RSP_AUTHS;
注意:代码示例中使用的CheckPassed和CheckFailed函数与第7章中SAPI小节的同名函数一样。
现在你了解了新的数据结构,让我们一起来看示例代码吧。我已经在每一个主要的代码段前加了注释,从而帮助你更好的理解它们。
列表13-1
// Password used to authorize access to the NV index.
char password[] = "test password";
// NV Index used for the password test.
#define TPM20_INDEX_PASSWORD_TEST 0x01500020
void PasswordTest()
{
UINT32 rval;
int i;
// Authorization structure for command.
TPMS_AUTH_COMMAND sessionData;
// Authorization structure for response.
TPMS_AUTH_RESPONSE sessionDataOut;
// Create and init authorization area for command:
// only 1 authorization area.
TPMS_AUTH_COMMAND *sessionDataArray[1] = { &sessionData };
// Create authorization area for response:
// only 1 authorization area.
TPMS_AUTH_RESPONSE *sessionDataOutArray[1] = { &sessionDataOut };
// Authorization array for command (only has one auth structure).
TSS2_SYS_CMD_AUTHS sessionsData = { 1, &sessionDataArray[0] };
// Authorization array for response (only has one auth structure).
TSS2_SYS_RSP_AUTHS sessionsDataOut = { 1, &sessionDataOutArray[0] };
TPM2B_MAX_NV_BUFFER nvWriteData;
printf( "\nPASSWORD TESTS:\n" );
// Create an NV index that will use password
// authorizations. The password will be
// "test password".
CreatePasswordTestNV( TPM20_INDEX_PASSWORD_TEST, password );
//
// Initialize the command authorization area.
//
// Init sessionHandle, nonce, session
// attributes, and hmac (password).
sessionData.sessionHandle = TPM_RS_PW;
// Set zero sized nonce.
sessionData.nonce.t.size = 0;
// sessionAttributes is a bit field. To initialize
// it to 0, cast to a pointer to UINT8 and
// write 0 to that pointer.
*( (UINT8 *)&sessionData.sessionAttributes ) = 0;
// Init password (HMAC field in authorization structure).
sessionData.hmac.t.size = strlen( password );
memcpy( &( sessionData.hmac.t.buffer[0] ),
&( password[0] ), sessionData.hmac.t.size );
// Initialize write data.
nvWriteData.t.size = 4;
for( i = 0; i < nvWriteData.t.size; i++ )
nvWriteData.t.buffer[i] = 0xff - i;
// Attempt write with the correct password.
// It should pass.
rval = Tss2_Sys_NV_Write( sysContext,
TPM20_INDEX_PASSWORD_TEST,
TPM20_INDEX_PASSWORD_TEST,
&sessionsData, &nvWriteData, 0,
&sessionsDataOut );
// Check that the function passed as
// expected. Otherwise, exit.
CheckPassed( rval );
// Alter the password so it's incorrect.
sessionData.hmac.t.buffer[4] = 0xff;
rval = Tss2_Sys_NV_Write( sysContext,
TPM20_INDEX_PASSWORD_TEST,
TPM20_INDEX_PASSWORD_TEST,
&sessionsData, &nvWriteData, 0,
&sessionsDataOut );
// Check that the function failed as expected,
// since password was incorrect. If wrong
// response code received, exit.
CheckFailed( rval,
TPM_RC_S + TPM_RC_1 + TPM_RC_AUTH_FAIL );
// Change hmac to null one, since null auth is
// used to undefine the index.
sessionData.hmac.t.size = 0;
// Now undefine the index.
rval = Tss2_Sys_NV_UndefineSpace( sysContext, TPM_RH_PLATFORM,
TPM20_INDEX_PASSWORD_TEST, &sessionsData, 0 );
CheckPassed( rval );
}
理解口令授权以及授权过程中使用的数据结构为理解其他的授权方式提供了基础。接下来我们将介绍HMAC和Policy授权:着重介绍怎样启动它们。
HMAC和Policy会话都是由TPM2_StartAuthSession命令来启动的。当会话被启动的时候,它必须是以下几种会话类型之一:HMAC,Policy,或者是Trial Policy。之前我概括介绍了HMAC和Policy会话,但是这些描述中并没有提及Trial Policy会话。Trial Policy会话是一个功能不健全的Policy会话:它们们不能被用于授权任何动作,但是可以用于在创建实体之前生成Policy摘要。基于这一小节的目的,Policy和Trial Policy会话将会被组织在一起介绍。
当一个会话启动之后,会话的基本特性已经确定了。具体来说,会话是bound或者unbound,会话是salted或者unsalted,会话密钥的强度,防止重放攻击的强度,参数加解密的强度,以及会话的HMAC强度都通过TPM2_StartAuthSession的参数来确定。
在开始描述启动HMAC和Policy会话之前,需要先了解一些概念:
- KDFa:密钥生成函数用于创建会话密钥。一个HMAC函数可以作为一个伪随机数生成函数来创建密钥。KDFa的输入是一个哈希算法;一个HMAC密钥;K值(接下来介绍);一个用于指定KDFa输出内容的用途的四字节字符串;contextU和contextV(可变长度的字符串);以及输出内容的比特位数。KDFa函数将会按照密钥学的方式将这些参数组合用于生成会话密钥,接下来会介绍。
- K:KDFa函数的一个参数,这个参数是一个密钥。对于会话密钥的创建来说,K是authValue和TPM2_StartAuthSession的salt参数的组合。
- sessionKey:当HMAC或者Policy会话启动时会创建的一个密钥。对于会话密钥的创建来说,KDFa使用以下内容作为输入:
- sessionAlg(一个哈希算法)
- K(一个用于KDFa的HMAC函数的HMAC密钥)
- 一个四字节的标签值,ATH(三个字符加一个终结符)
- 两个随机数,nonceTPM和nonceCaller(对应KDFa的contextU和contextV)。
- 输出密钥的比特位数。
- nonceCaller:TPM2_StartAuthSession调用者向TPM设备输入的随机数。
- nonceTPM:TPM2_StartAuthSession命令响应中返回给调用者的随机数,这个随机数由TPM生成。
我们之前已经提到,TPM2_StartAuthSession函数的参数决定了许多会话属性,其中就包括会话的安全属性。命令的概要图解如图13-10所示;命令响应的如图13-11所示。
图13-10 TPM2_StartAuthSession命令
图13-11 TPM2_StartAuthSession命令响应
这个命令将以下handle和参数作为输入:
- 两个handle:
- 如果tpmKey是TPM_RH_NULL,那么者会话是未加盐的会话;否则,就是加盐的会话,并且encryptedSalt参数将会被TPM解密然后拿到salt值来增加熵值。TPM使用tpmKey这个handle指向的已加载密钥来解密encryptSalt。
- 如果bind是TPM_RH_NULL,那么这个会话就是unbound会话。否则,就是一个bound会话,并且bind指向的实体的authValue将会和K的salt值连接,K是用于计算sessionKey的HMAC密钥。
- 五个参数:
- nonceCaller是第一个随机数,它由调用者设置。这个随机数也决定了后面由TPM返回的随机数的大小。
- encryptedSalt只有在会话是salted时才会使用,我们在前面讨论tpmKey是已经说过这一点。如果会话是unsalted,这个参数必须是空。
- sessionType决定了会话的类型:HMAC,policy,或者trial policy。
- symmetric,当会话被设置成encrypt或者decrypt时,它用于指定参数加密的类型。
- authHash是一个算法ID,这个ID指定了会话的HMAC操作所需的哈希算法。
当一个会话被启动后,TPM会处理命令然后创建一个会话handle,然后计算nonceTPM,以及一个会话密钥。这个会话密钥会用于产生HMAC值,加密命令参数,以及解密命令响应参数。当会话被创建以后,会话密钥就保持不变直到会话结束。会话的handle和nonceTPM将通过命令响应返回。
会话密钥将由传递到命令TPM2_StartAuthSession的参数决定,这些参数包括:tpmKey,bind,encryptedSalt,nonceCaller,以及authHash。命令响应的参数,nonceTPM,也被包含在会话密钥中。使用nonceTPM来创建会话密钥可以保证使用相同的authValue,salt,以及nonceCaller将会产生不同的会话密钥。
因为调用命令的应用同样也需要会话密钥,所以应用会使用nonceTPM以及其他输入参数复制TPM的计算过程。到现在为止,会话已经启动了,并且调用者和TPM都知道会话密钥。
表13-2描述了各种各样的会话以及sessionKey和HMAC密钥时如何创建的。将这些信息组织到一个表中将会非常有用,这也是我们这样做的原因。
表13-2
Session Variation | bind | tpmKey | K | sessionKey | HMAC key |
---|---|---|---|---|---|
Unbounded/Unsalted | TPM_RH_NULL | TPM_RH_NULL | Null | NULL Key | Entity authorizaiton value, authValue(entity) |
bound | Not TPM_RH_NULL | TPM_RH_NULL | bind entity's authorization value, authValue(bind) | KDFa(sessionAlg, authValue(bind), "ATH", nonceTPM, nonceCaller, bits) | if entity == bind entity AND not a policy session: sessionKey if entity != bind entity OR session is a policy session: sessionKey || authValue(entity) |
salted | TPM_RH_NULL | Not TPM_RH_NULL | salt | KDFa(sessionAlg, salt, "ATH", nonceTPM, nonceCaller, bits) | sessionKey || authValue(entity) |
bound/salted | Not TPM_RH_NULL | Not TPM_RH_NULL | authValue(bind) || salt | KDFa(sessionAlg, authValue(bind)|| salt, "ATH", nonceTPM, nonceCaller, bits) | if entity == bind entity AND not a policy session: sessionKey if entity != bind entity OR session is a policy session: sessionKey || authValue(entity) |
从表13-2的详细描述中,我们可以推理出选择TPM2_StartAuthSession命令参数的一些规则。会话密钥的强度是由bind,tpmKey,encryptedSalt,nonceCaller,以及用于会话的哈希算法共同决定的。
强度最高的会话密钥是通过如下的配置得来的:使用bind指向一个TPM实体(意思是这是一个bound会话),tpmKey指向一个已经加载到TPM的密钥(意思是指这是一个salted会话),nonceCaller的大小设置成哈希算法输出的大小。
如果bind和tpmKey都被设置成TPM_RH_NULL,那结果就是一个长度为0的会话密钥——非常弱的会话密钥。但是,只要实体本身的authValue的强度足够高,那HMAC密钥(实际上就是authValue本身)也是足够强的。第17章中我们将会详细介绍会话密钥的强度直接影响命令和命令响应参数加解密的强度。
会话中参数nonceCaller的长度决定了nonceTPM的长度。这个随机数越大,会话防止重放攻击的效果就越好。
会话密钥和实体的授权值最终将会用于生成会话的HMAC。所以再次声明,一个高强度的会话密钥和授权值将会带来更好的安全性。
调用TPM2_StartAuthSession的程序员在选择命令参数时需要认真考虑需要什么样的安全属性。
现在让我们来研究一下bound vs. unbound以及salted vs. unsalted会话的具体的含义。我们同时也会增加一些相关应用案例的描述。
HMAC和Policy会话都可以是salted或者unsalted。一个加盐的会话会给会话密钥的创建过程增加更多熵值。一个会话是否加盐取决于TPM2_StartAuthSession的tpmKey参数。使用tpmKey解密后的盐值会被加入到会话密钥的创建过程中。如果authValue的强度很弱,给一个会话加盐有助于防止线下的暴力破解。线下暴力破解是指多次尝试authValue的值看是否可以生成正确的HMAC值。如果破解成功的话,authValue就暴露了。给会话加盐会提高这种类型攻击的门槛。
同样的,HMAC和Policy会话都可以是bound或者unbound。一个bound会话就意味着会话被“绑定”到一个特定的TPM实体上,也就是前面提到的“bind”实体;一个bound会话通常是用于针对“bind”实体授权多次动作。绑定实体的授权值用于计算会话密钥,但是之后就不再需要了。从安全的角度来讲,授权值只使用一次是一个优势,因为调用程序不用一直提示用户输入授权值(口令)或者将授权值存储到内存中。
Bound会话还可以用于针对其他实体(非“bind”实体)的授权,从这个角度来讲,“bind”实体的authValue为会话密钥增加熵值,从而得到更强命令和命令响应参数加解密。bind实体和将要被授权的实体的授权值都会被加入到HMAC的计算中。
一个unbound会话可以用于授权针对许多不同实体的动作。一个Policy会话通常会被配置成一个unbound会话。相对于Policy会话提供的安全性来说,HMAC值就没有那么重要了,并且使用Policy会话时不用计算和插入HMAC值将会让授权变得容易很多。
现在让我们来回答一些显而易见的问题:bound/unboud和salted/unsalted会话的主要应用案例是什么?有许多可能性,但是最常用的几种如下:
- Unbound会话主要常用于以下两种情况:
- 如果会话同时也是Unsalted类型,这种组合经常用于不需要HMAC的Policy会话。这种情况没有问题是因为Policy会话使用Pilicy命令,并且许多情况下并不需要HMAC授权。这种配置通过祛除计算HMAC的计算开销来简化Policy会话的使用。这种应用适用于任何不包含TPM2_PolicyAuthValue命令的Policy授权。
- 它们还可以用于使用HMAC会话针对许多不同实体的授权动作。
- Bound会话有两种应用案例:
- 授权针对bind实体的动作:这种HMAC授权可以在不用每次提示输入口令的情况下授权针对bind实体的多个动作。比如说,一个雇员可能想要多次查看他自己的文档;这种类型的授权就适用。
- 授权针对不是bind实体的动作:在这种情况下,bind实体和被授权实体的authValue都将被包含到HMAC的计算中去。结果就是会话拥有更强的会话密钥以及更强的加解密密钥。
- Unslated会话:当bind实体的authValue值确实是足够强到可以生成强度很高的会话密钥和加解密密钥时。如果系统管理员可以强制控制口令的强度时,使用一个unsalted未加盐的会话应该就足够了。
- Salted会话:当authValue被认为强度不足以生成安全的会话密钥和加解密密钥时。一个网站可能会向用户请求两种不同的口令:一个是用于加密密钥使用授权,另外一个用于加盐操作。只要使用从密码学上来说足够强得盐值,这两个口令的结合的强度比使用其中一个要强得多。 现在你已经具备了启动一个会话得基本知识,下面让我们来看一看HMAC和Policy会话的不同。
HMAC和Policy会话的主要不同体现在动作授权的方式上。对于使用HMAC会话发送的命令来说,只有当和命令一起被发送到TPM的HMAC值正确时命令才会成功。为了生成一个正确的HMAC值,命令调用者和TPM之间需要共享一个秘密信息(authValue)。换句话说,计算正确的HMAC值需要知道会话密钥和实体的authValue,这样才能有效地授权实体执行动作。如果不知道会话密钥或者实体的authValue,就不能计算正确的HMAC值,进而导致命令失败。
Policy会话的授权基于一个正确的policy命令序列,在许多情况下还有这些命令成功执行所要求的条件。这仅仅是对这个丰富而又复杂的授权方式的简单描述。第14章将会详细描述,现在我们说Policy授权需要以下条件就足够了:
- 在将要授权的命令执行以前需要有一个Policy命令执行序列。TPM通过检查policyDigest值来确认这个命令执行序列。每一个Policy命令都会将Policy相关的数据的哈希扩展到会话的policyDigest中。最简单的情况就是,将当前会话的policyDigest与被授权实体的policyDigest比较,比较的结果用于决定是否授权相应的命令。
- 在执行被授权动作的命令之前或者命令执行中,必须满足policy中包含的一组条件。如果这些条件不满足,那么对应的Policy命令就会失败(命令执行之前的条件)或者被授权的命令失败(命令执行中的条件)。后续我们会详细介绍。 有趣的是,根据TPM2.0规范第一部分的描述,Policy会话的授权区域仍然可以有一个HMAC值,尽管Policy会话最常用的场景并没有这个HMAC值。这里所指的最常用的场景假设这个会话是unbound和unsalted。但是当授权区域使用了HMAC时(可能是因为,1,会话是bound/salted。2,使用了TPM2_PolicyAuthValue命令),与HMAC会话相反的是,计算HMAC时总是假设会话没有“绑定”到任何实体上(基于优化的原因,参考原书)。在Policy会话中,bind实体的authValue仅仅用于创建会话密钥并且从来不用于HMAC值计算。使用TPM的应用程序在计算HMAC值时需要特别注意这一点。
总结下来就是,HAMC授权比口令授权更安全,Policy授权是最丰富也是最复杂的授权。HMAC授权使用事先完全计算好的HMAC值作为证明调用者知道授权秘密信息的方式。Policy授权需要一组Policy命令以及一组policy命令需要的条件来授权一个动作。对于HMAC和Policy授权来说,HMAC值可以用于保证命令和命令响应的完整性。
现在我们一起看看HMAC授权的细节吧。
这一节我们将深入研究HMAC授权的细节。我们将从高层级的角度介绍HMAC授权的生命周期,以及它生命周期中的每一步:实体创建或者修改,HMAC会话的创建,以及HMAC会话的使用。本节将以HMAC会话的安全属性描述作为结尾。
当你读这一小节时,我建议你同时参考有代码示例的一小节。其中的讨论也将引用示例代码的行数。这一节主要集中描述NV索引的写操作。NV索引的读操作代码非常简单,我们将这些步骤留给读者自己练习。
创建并授权基于HMAC授权的实体包括以下几个步骤:
- 创建一个使用授权值的实体,或者修改已有实体的授权值。对于每一个实体来说,这个步骤通常仅仅执行一次。
- 创建一个HMAC会话。
- 使用HMAC会话执行基于这个实体的操作。这个操作可以在步骤1和2完成之后的任意时间执行,并且可以执行多次。一个HMAC会话可以用于授权多个动作。
为了创建一个实体,选择authValue的方法与之前描述的口令授权方式相同。修改已有实体的授权值也是如此。 在这两种操作中,authValue是被同等对待的。
在示例代码中,第19-26,42-44,以及55行设置了authValue,以及创建NV索引所需要的authPolicy。第101,104-105,以及112行设置了NV的属性。第115-117行创建了我们将要授权的NV索引。
一个HMAC会话是通过将TPM2_StartAuthSession命令的sessionType参数设置成TPM_SE_HAMC来创建的。当HMAC会话启动以后,TPM会根据我们之前的描述创建一个会话密钥。会话密钥是在TPM内部创建的。TPM2_StartAuthSession命令返回之后,调用者会重新创建会话密钥,创建这个密钥会使用bind实体的authValue,salt(盐值),以及通过TPM2_StartAuthSession发送到TPM的参数nonceCaller,还有TPM返回的nonceTPM。
第140,143,以及150行配置了用于启动会话的参数,第154-156行是实际创建会话的代码。
图13-12描述了在一个会话中执行命令的整体机制。
图13-12 HMAC会话:一个命令。主要注意的是这个图假设应用软件使用TSS SAPI层。为了简单起见TAB和资源管理层被忽略了。这个图展示了怎样使用HAMC会话执行TSS SAPI Tss2_Sys_XXXX_Prepare以及一次性的调用接口。
为了使用HMAC会话授权命令执行,操作步骤如下(阅读代码的同时参考图13-12):
- 输入参数被标准化,连接并组织到一个缓冲区中,叫做cpParams。示例代码的183-185行的Tss2_Sys_NV_Write_Prepare命令会执行这一操作并将cpParams缓冲区”放置“到sysContext结构中。
- 调用者计算cpHash值,这个值是通过对cpParams缓冲区中标准化的命令参数做哈希得来的。示例代码的第202-205行的ComputeCommandHamcs函数会执行这样的操作。
- 调用者计算命令的HMAC值。cpHash是这个计算的输入参数之一。这个操作同样也是第202-205行的ComputeCommandHamcs函数来做的。
- 计算出来的HMAC值同时还会被ComputeCommandHamcs自动地复制到HAMC会话的HMAC区域中--注意一下这个函数的指针类型的参数nvCmdAuths。
- 完整的命令数据必须被标准化并组织到一个字节流中发送到TPM设备。这些数据包括命令头,会话,以及命令参数。参考示例代码的211-214行。
- 从TPM中读取命令响应信息。这同样也是在第211-214行的一次调用的函数中执行的。
- 接收到命令响应以后,调用者计算rpHash,也就是经过格式化的命令响应参数的哈希值。第224-226行的CheckResponseHmacs函数会执行这个操作。
- 调用者计算命令响应的HMAC值,rpHash是这个计算的输入之一。这同样也是第224-226行的CheckResponseHmacs函数来做的。
- 调用者比较刚刚计算出的HMAC和命令响应会话中HMAC区域的HMAC值。如果它们不同,那么就认为命令响应参数已经被破坏了,因此这些参数数据都是不可信的。如果两者相同,那就表明命令参数被正确地接收了。这个过程都是由CheckResponseHmacs函数来做的。它会计算命令响应的HMAC值,然后和通过nvRspAuths返回的HMAC值比较。
- 如果命令响应的HMAC正确无误,命令响应的参数就可以被反序列化成C语言的结构体以供调用者使用;这个过程也是由211-214行的一次调用函数来执行的。对于这个一次调用的函数来说,需要主要的是,它会假设HMAC值是正确的,并直接将参数反序列化。之后,如果命令响应的HMAC值如果被证明是错的,那就可以忽略已经反序列化的命令响应参数。
列表13-2展示了一个怎样执行HMAC和Policy会话的代码示例。这段代码是可以实际被执行的,所有的辅助函数都可以在第7章中描述的TSS SAPI 的测试代码中找到(注意:这个代码的版本可能已经比较旧了,但是任然不影响理解)。为了让代码尽可能地简单,它使用一个unbound和unsalted的会话。如果你想看更复杂的示例,所有关于bound/unbound以及salted/unsalted的变种都可以在TSS SAPI测试代码的HmacSessionTest中找到。
注意:管理HMAC会话和计算HMAC授权是非常复杂的工作。列表13-2中的一些函数仅仅做了概括性的解释。目的是从整体的角度展现HMAC授权的过程,而不至于让你被底层的细节困扰。如果你想深究,所有子函数的源码都可以在第7章中描述的TSS SAPI测试代码中找到。
为了对比HAMC和Policy会话,这段代码会根据输入的hmacTest参数执行HAMC或者Policy授权;代码中使用hmacTest的if条件都是用于HMAC或者Policy相关的特定代码。因为现在这一节主要是介绍HMAC授权,所以忽略代码中的Policy会话相关的内容就好。
关于这段代码的一些提醒:
- 代码主要的操作是对一个NV索引先写后读。读写这两个操作都使用HMAC授权。
- 为授权读写操作,这段代码使用一个HMAC会话或者包含TPM2_PolicyAuthValue命令的Policy会话。这就提供了相似的功能(这两种都会使用HMAC值来授权),这样一来就能很好地为对比HAMC和Policy会话提供基础。
- RollNonces函数将nonceNewer复制到nonceOlder,然后想新的随机数复制到nonceNewer。在每一个命令执行前和命令响应之后都要翻转nonce值。这将在“使用HMAC会话来发送多次命令”中详细解释。如下是这个函数的完整代码:
void RollNonces( SESSION *session, TPM2B_NONCE *newNonce )
{
session->nonceOlder = session->nonceNewer;
session->nonceNewer = *newNonce;
}
这段代码在读写NV授权时使用一个字节的nonceCaller。通常这不是推荐的做法,为最大化对重放攻击的保护力度,你最好使用和会话的哈希算法输出长度相同的nonce值,同时每次的nonce值使用随机产生的值(这也是有时候我直接将nonce值翻译成随机数的原因,实际使用中其实它们并没有本质区别)。
- 这段代码重度依赖应用层的数据结构,SESSION,这个结构维护了所有的会话状态信息,包括nonce值。维护会话的状态有很多种方法,这仅仅是我选择的一种实现方式。这个数据结构看起来如下:
typedef struct {
// Inputs to StartAuthSession; these need to be saved
// so that HMACs can be calculated.
TPMI_DH_OBJECT tpmKey;
TPMI_DH_ENTITY bind;
TPM2B_ENCRYPTED_SECRET encryptedSalt;
TPM2B_MAX_BUFFER salt;
TPM_SE sessionType;
TPMT_SYM_DEF symmetric;
TPMI_ALG_HASH authHash;
// Outputs from StartAuthSession; these also need
// to be saved for calculating HMACs and
// other session related functions.
TPMI_SH_AUTH_SESSION sessionHandle;
TPM2B_NONCE nonceTPM;
// Internal state for the session
TPM2B_DIGEST sessionKey;
TPM2B_DIGEST authValueBind; // authValue of bind object
TPM2B_NONCE nonceNewer;
TPM2B_NONCE nonceOlder;
TPM2B_NONCE nonceTpmDecrypt;
TPM2B_NONCE nonceTpmEncrypt;
TPM2B_NAME name; // Name of the object the session handle
// points to. Used for computing HMAC for
// any HMAC sessions present.
//
void *hmacPtr; // Pointer to HMAC field in the marshalled
// data stream for the session.
// This allows the function to calculate
// and fill in the HMAC after marshalling
// of all the inputs is done.
//
// This is only used if the session is an
// HMAC session.
//
UINT8 nvNameChanged;// Used for some special case code
// dealing with the NV written state.
} SESSION;
- StartAuthSessionWithParams函数会启动会话,并将状态信息存储到SESSION结构中,然后将SESSION结构添加到用于存储应用已经打开的会话的列表中。
- EndAuthSession用于会话结束后,从应用的已打开会话列表中清除SESSION结构。
列表13-2 一个简单的HAMC和Policy代码示例
1 void SimpleHmacOrPolicyTest( bool hmacTest )
2 {
3 UINT32 rval, sessionCmdRval;
4 TPM2B_AUTH nvAuth;
5 SESSION nvSession, trialPolicySession;
6 TPMA_NV nvAttributes;
7 TPM2B_DIGEST authPolicy;
8 TPM2B_NAME nvName;
9 TPM2B_MAX_NV_BUFFER nvWriteData, nvReadData;
10 UINT8 dataToWrite[] = { 0x00, 0xff, 0x55, 0xaa };
11 char sharedSecret[] = "shared secret";
12 int i;
13 TPM2B_ENCRYPTED_SECRET encryptedSalt;
14 TPMT_SYM_DEF symmetric;
15 TPMA_SESSION sessionAttributes;
16 TPM_SE tpmSe;
17 char *testString;
为NV索引的创建和删除设定授权
18 // Command authorization area: one password session.
19 TPMS_AUTH_COMMAND nvCmdAuth = { TPM_RS_PW, };
20 TPMS_AUTH_COMMAND *nvCmdAuthArray[1] = { &nvCmdAuth };
21 TSS2_SYS_CMD_AUTHS nvCmdAuths = { 1, &nvCmdAuthArray[0] };
22
23 // Response authorization area.
24 TPMS_AUTH_RESPONSE nvRspAuth;
25 TPMS_AUTH_RESPONSE *nvRspAuthArray[1] = { &nvRspAuth };
26 TSS2_SYS_RSP_AUTHS nvRspAuths = { 1, &nvRspAuthArray[0] };
27
28 if( hmacTest )
29 testString = "HMAC";
30 else
31 testString = "POLICY";
32
33 printf( "\nSIMPLE %s SESSION TEST:\n", testString );
34
35 // Create sysContext structure.
36 sysContext = InitSysContext( 1000, resMgrTctiContext, &abiVersion );
37 if( sysContext == 0 )
38 {
39 InitSysContextFailure();
40 }
创建NV索引,使用HMAC或者Policy授权
41 // Setup the NV index's authorization value.
42 nvAuth.t.size = strlen( sharedSecret );
43 for( i = 0; i < nvAuth.t.size; i++ )
44 nvAuth.t.buffer[i] = sharedSecret[i];
45
46 //
47 // Create NV index.
48 //
49 if( hmacTest )
50 {
51 // Set NV index's authorization policy
52 // to zero sized policy since we won't be
53 // using policy to authorize.
54
55 authPolicy.t.size = 0;
56 }
57 else
58 {
59
60 // Zero sized encrypted salt, since the session
61 // is unsalted.
62
63 encryptedSalt.t.size = 0;
64
65 // No symmetric algorithm.
66 symmetric.algorithm = TPM_ALG_NULL;
67
68 //
69 // Create the NV index's authorization policy
70 // using a trial policy session.
71 //
72 rval = StartAuthSessionWithParams( &trialPolicySession,
73 TPM_RH_NULL, TPM_RH_NULL, &encryptedSalt,
74 TPM_SE_TRIAL,
75 &symmetric, TPM_ALG_SHA256 );
76 CheckPassed( rval );
77
78 rval = Tss2_Sys_PolicyAuthValue( sysContext,
79 trialPolicySession.sessionHandle, 0, 0 );
80 CheckPassed( rval );
81
82 // Get policy digest.
83 rval = Tss2_Sys_PolicyGetDigest( sysContext,
84 trialPolicySession.sessionHandle,
85 0, &authPolicy, 0 );
86 CheckPassed( rval );
87
88 // End the trial session by flushing it.
89 rval = Tss2_Sys_FlushContext( sysContext,
90 trialPolicySession.sessionHandle );
91 CheckPassed( rval );
92
93 // And remove the trial policy session from
94 // sessions table.
95 rval = EndAuthSession( &trialPolicySession );
96 CheckPassed( rval );
97 }
98
99 // Now set the NV index's attributes:
100 // policyRead, authWrite, and platormCreate.
101 *(UINT32 *)( &nvAttributes ) = 0;
102 if( hmacTest )
103 {
104 nvAttributes.TPMA_NV_AUTHREAD = 1;
105 nvAttributes.TPMA_NV_AUTHWRITE = 1;
106 }
107 else
108 {
109 nvAttributes.TPMA_NV_POLICYREAD = 1;
110 nvAttributes.TPMA_NV_POLICYWRITE = 1;
111 }
112 nvAttributes.TPMA_NV_PLATFORMCREATE = 1;
113
114 // Create the NV index.
115 rval = DefineNvIndex( TPM_RH_PLATFORM, TPM_RS_PW,
116 &nvAuth, &authPolicy, TPM20_INDEX_PASSWORD_TEST,
117 TPM_ALG_SHA256, nvAttributes, 32 );
118 CheckPassed( rval );
119
120 // Add index and associated authorization value to
121 // entity table. This helps when we need
122 // to calculate HMACs.
123 AddEntity( TPM20_INDEX_PASSWORD_TEST, &nvAuth );
124 CheckPassed( rval );
125
126 // Get the name of the NV index.
127 rval = (*HandleToNameFunctionPtr)(
128 TPM20_INDEX_PASSWORD_TEST,
129 &nvName );
130 CheckPassed( rval );
启动HMAC或者Policy会话
131 //
132 // Start HMAC or real (non-trial) policy authorization session:
133 // it's an unbound and unsalted session, no symmetric
134 // encryption algorithm, and SHA256 is the session's
135 // hash algorithm.
136 //
137
138 // Zero sized encrypted salt, since the session
139 // is unsalted.
140 encryptedSalt.t.size = 0;
141
142 // No symmetric algorithm.
143 symmetric.algorithm = TPM_ALG_NULL;
144
145 // Create the session, hmac or policy depending
146 // on hmacTest.
147 // Session state (session handle, nonces, etc.) gets
148 // saved into nvSession structure for later use.
149 if( hmacTest )
150 tpmSe = TPM_SE_HMAC;
151 else
152 tpmSe = TPM_SE_POLICY;
153
154 rval = StartAuthSessionWithParams( &nvSession, TPM_RH_NULL,
155 TPM_RH_NULL, &encryptedSalt, tpmSe,
156 &symmetric, TPM_ALG_SHA256 );
157 CheckPassed( rval );
158
159 // Get the name of the session and save it in
160 // the nvSession structure.
161 rval = (*HandleToNameFunctionPtr)( nvSession.sessionHandle,
162 &nvSession.name );
163 CheckPassed( rval );
使用HMAC或者Policy授权一次NV写操作
164 // Initialize NV write data.
165 nvWriteData.t.size = sizeof( dataToWrite );
166 for( i = 0; i < nvWriteData.t.size; i++ )
167 {
168 nvWriteData.t.buffer[i] = dataToWrite[i];
169 }
170
171 //
172 // Now setup for writing the NV index.
173 //
174 if( !hmacTest )
175 {
176 // Send policy command.
177 rval = Tss2_Sys_PolicyAuthValue( sysContext,
178 nvSession.sessionHandle, 0, 0 );
179 CheckPassed( rval );
180 }
181
182 // First call prepare in order to create cpBuffer.
183 rval = Tss2_Sys_NV_Write_Prepare( sysContext,
184 TPM20_INDEX_PASSWORD_TEST,
185 TPM20_INDEX_PASSWORD_TEST, &nvWriteData, 0 );
186 CheckPassed( rval );
187
188 // Configure command authorization area, except for HMAC.
189 nvCmdAuths.cmdAuths[0]->sessionHandle =
190 nvSession.sessionHandle;
191 nvCmdAuths.cmdAuths[0]->nonce.t.size = 1;
192 nvCmdAuths.cmdAuths[0]->nonce.t.buffer[0] = 0xa5;
193 *( (UINT8 *)(&sessionAttributes ) ) = 0;
194 nvCmdAuths.cmdAuths[0]->sessionAttributes = sessionAttributes;
195 nvCmdAuths.cmdAuths[0]->sessionAttributes.continueSession = 1;
196
197 // Roll nonces for command
198 RollNonces( &nvSession, &nvCmdAuths.cmdAuths[0]->nonce );
199
200 // Complete command authorization area, by computing
201 // HMAC and setting it in nvCmdAuths.
202 rval = ComputeCommandHmacs( sysContext,
203 TPM20_INDEX_PASSWORD_TEST,
204 TPM20_INDEX_PASSWORD_TEST, &nvCmdAuths,
205 TPM_RC_FAILURE );
206 CheckPassed( rval );
207
208 // Finally!! Write the data to the NV index.
209 // If the command is successful, the command
210 // HMAC was correct.
211 sessionCmdRval = Tss2_Sys_NV_Write( sysContext,
212 TPM20_INDEX_PASSWORD_TEST,
213 TPM20_INDEX_PASSWORD_TEST,
214 &nvCmdAuths, &nvWriteData, 0, &nvRspAuths );
215 CheckPassed( sessionCmdRval );
返回NV写命令的响应数据,如果是HMAC会话,验证HMAC值
216 // Roll nonces for response
217 RollNonces( &nvSession, &nvRspAuths.rspAuths[0]->nonce );
218
219 if( sessionCmdRval == TPM_RC_SUCCESS )
220 {
221 // If the command was successful, check the
222 // response HMAC to make sure that the
223 // response was received correctly.
224 rval = CheckResponseHMACs( sysContext, sessionCmdRval,
225 &nvCmdAuths, TPM20_INDEX_PASSWORD_TEST,
226 TPM20_INDEX_PASSWORD_TEST, &nvRspAuths );
227 CheckPassed( rval );
228 }
229
230 if( !hmacTest )
231 {
232 // Send policy command.
233 rval = Tss2_Sys_PolicyAuthValue( sysContext,
234 nvSession.sessionHandle, 0, 0 );
235 CheckPassed( rval );
236 }
使用HMAC或者Policy授权NV读操作。如果是HMAC会话,验证读命令响应的HMAC。最后,验证读到数据与之前写入的数据是相等的
237 // First call prepare in order to create cpBuffer.
238 rval = Tss2_Sys_NV_Read_Prepare( sysContext,
239 TPM20_INDEX_PASSWORD_TEST,
240 TPM20_INDEX_PASSWORD_TEST,
241 sizeof( dataToWrite ), 0 );
242 CheckPassed( rval );
243
244 // Roll nonces for command
245 RollNonces( &nvSession, &nvCmdAuths.cmdAuths[0]->nonce );
246
247 // End the session after next command.
248 nvCmdAuths.cmdAuths[0]->sessionAttributes.continueSession = 0;
249
250 // Complete command authorization area, by computing
251 // HMAC and setting it in nvCmdAuths.
252 rval = ComputeCommandHmacs( sysContext,
253 TPM20_INDEX_PASSWORD_TEST,
254 TPM20_INDEX_PASSWORD_TEST, &nvCmdAuths,
255 TPM_RC_FAILURE );
256 CheckPassed( rval );
257
258 // And now read the data back.
259 // If the command is successful, the command
260 // HMAC was correct.
261 sessionCmdRval = Tss2_Sys_NV_Read( sysContext,
262 TPM20_INDEX_PASSWORD_TEST,
263 TPM20_INDEX_PASSWORD_TEST,
264 &nvCmdAuths, sizeof( dataToWrite ), 0,
265 &nvReadData, &nvRspAuths );
266 CheckPassed( sessionCmdRval );
267
268 // Roll nonces for response
269 RollNonces( &nvSession, &nvRspAuths.rspAuths[0]->nonce );
270
271 if( sessionCmdRval == TPM_RC_SUCCESS )
272 {
273 // If the command was successful, check the
274 // response HMAC to make sure that the
275 // response was received correctly.
276 rval = CheckResponseHMACs( sysContext, sessionCmdRval,
277 &nvCmdAuths, TPM20_INDEX_PASSWORD_TEST,
278 TPM20_INDEX_PASSWORD_TEST, &nvRspAuths );
279 CheckPassed( rval );
280 }
281
282 // Check that write and read data are equal.
283 if( memcmp( (void *)&nvReadData.t.buffer[0],
284 (void *)&nvWriteData.t.buffer[0], nvReadData.t.size ) )
285 {
286 printf( "ERROR!! read data not equal to written data\n" );
287 Cleanup();
288 }
清理现场:删除NV索引
289
290 //
291 // Now cleanup: undefine the NV index and delete
292 // the NV index's entity table entry.
293 //
294
295 // Setup authorization for undefining the NV index.
296 nvCmdAuths.cmdAuths[0]->sessionHandle = TPM_RS_PW;
297 nvCmdAuths.cmdAuths[0]->nonce.t.size = 0;
298 nvCmdAuths.cmdAuths[0]->hmac.t.size = 0;
299
300 // Undefine the NV index.
301 rval = Tss2_Sys_NV_UndefineSpace( sysContext,
302 TPM_RH_PLATFORM, TPM20_INDEX_PASSWORD_TEST,
303 &nvCmdAuths, 0 );
304 CheckPassed( rval );
305
306 // Delete the NV index's entry in the entity table.
307 rval = DeleteEntity( TPM20_INDEX_PASSWORD_TEST );
308 CheckPassed( rval );
309 }
现在我已经向你展示了怎样使用HMAC会话向TPM设备发送一个命令。现在我们需要考虑一下在多次命令中nonce是怎样工作的。
TPM命令在一个会话中执行成功后,nonceTPM的值都会被TPM改变。如果调用者愿意,他也可以每次执行命令都改变nonceCaller。因为nonce值被包含到HMAC计算中,这样就阻止了重放攻击。HMAC的计算公式如下:
authHAMC := HMAC-sessionAlg((sessionKey||authValue), (pHash||nonceNewer||nonceOlder{||noceTPMdecrypt}{||nonceTPMencrypt}||sessionAttributes))
在上述的公式中,需要注意一下nonceNewer和nonceOlder这两个参数。在一次命令发送中,nonceNewer就是nonceCaller,nonceOlder就是上一次的nonceTPM。对于一个命令响应来说,nonceNewer是当前的nonceTPM,nonceOlder是刚刚从命令参数中传递过来的nonceCaller。现在我们需要忽略公式中的nonceTPMdecrypt和nonceTPMencrypt,因为它们仅仅会被用在加密会话中。这一小节描述了HMAC会话中多次命令之间使用nonce的机制。一个HMAC会话中的多次命令的nonce使用情况如下(参考图13-13和列表13-2):
- 当HMAC会话启动以后,nonceCaller1被发送到TPM设备中,然后从TPM设备中接收nonceTPM1。这些操作由命令StartAuthSessionWithParams函数完成,参考代码列表13-2的第72-75行。
- 每次当一个TPM命令被成功授权以后,TPM就会生成一个新的nonceTPM。这个操作叫做“翻滚”nonce值。如果调用者愿意的话,在通过会话发送命令之前也可以改变nonceCaller的值。参考列表13-2的第198,217,245和269行的RollNonces调用。
- 在下一次的会话命令中:
- 对于命令的HMAC计算,nonceOlder参数会使用nonceTPM1。nonceNewer使用nonceCaller2,它还会作为命令的授权区域参数被发送到TPM中。
- 对于命令响应的HMAC计算,nonceCaller2会作为nonceOlder。nonceNewer则使用nonceTPM2,nonceTPM2会作为命令响应中会话授权区域的参数返回给调用者用于HMAC计算。
- 对于后续的命令,复制以上的nonce使用模式,也就是nonceCaller和nonceTPM在nonceNewer和nonceOlder之间交替转换。
- 以上的这种nonce使用机制会一直重复直到会话结束。nonce值得不断改变以及将nonce值加入到命令和命令响应HMAC计算中,这些特性使得会话可以防止重放攻击。
图13-13 HMAC会话中的nonce用于阻止重放攻击
是什么让HMAC会话为TPM命令提供安全性呢?本质上来讲,TPM使用HMAC会话的三个方面来使命令安全:
- 会话密钥:与会话密钥绑定的authValue和salt应该是只有调用者和TPM设备知道的秘密信息。这两个值都是用于计算会话密钥的。不知道这些值的攻击者就不能计算出会话密钥。因为会话密钥用于创建HMAC密钥,这也就意味着攻击者不能成功地通过HMAC会话发送TPM命令和接收TPM命令响应。这个特点阻止了中间人攻击。
- HMAC:会话密钥和实体的authValue用于生成HMAC密钥。将要被授权的实体的authValue应该只能被调用者和TPM知道。因此,这同样又意味着攻击者不能执行中间人攻击。
- Nonces:nonces用于阻止重放攻击。HMAC计算会包含nonce值,如果nonce值不正确的话就没办法计算出正确的HMAC值。又因为nonce值是一直在变化的,所以一次命令的字节流是不能被重复执行的。
只要合理地维护绑定实体的authValue,salt,以及被授权实体的authValue,攻击者就不能对这个实体进行授权。然后一直在”翻滚“的nonce值又可以阻止重放攻击。
为了使用HMAC授权,调用者需要填充图13-14中命令授权块中的数据。
图13-14 命令授权区域
- Session handle:一个四字节值,表示与这个授权内容相关的会话索引值。
- Size field:2字节,用于指示Nonce的长度。
- Nonce(from caller):如果存在,就是调用者设定的一个数值。
- Session attributes:一个字节,其中的位域表示了会话的用途。
- size field = sizeof HMAC:2字节,用于指示authorization区域的大小。
- Authorizaiton=HMAC 如果存在的话,它就是一个包含HMAC值得数组。这个HMAC值由调用命令的代码产生。HMAC是由命令的参数计算而来。TPM会独立地计算HMAC值然后和这个HMAC值比较,然后确定TPM收到的命令信息没有被破坏。
填充授权数据块的代码是19-21,150,189-195(设置tpmNonce),以及202-205(设置授权区域的HMAC值)行。
命令响应的授权区域如图13-15所示。
图13-15 命令响应授权区域
- Size field:2字节,用于指示Nonce的长度。
- Nonce(from TPM):如果存在,就是TPM设定的一个数值。
- Session attributes:一个字节,其中的位域表示了会话的用途。
- size field = sizeof HMAC:2字节,用于指示acknowledgment区域的大小。
- Acknowledgment=HMAC 如果存在的话,它就是一个包含HMAC值得数组。这个HMAC值由TPM产生。HMAC是由命令响应的参数计算而来。命令调用者收到响应数据后会独立地计算HMAC值然后和这个HMAC值比较,然后确定从TPM收到的命令响应信息没有被破坏。
配置命令响应授权的代码在列表13-2的第24-26行。这个一次调用的函数会从TPM返回授权区域的内容nvRspAuths,然后接下来调用的CheckResponseHMACs会验证授权值是正确的。
以上就是我们对HMAC会话的深入分析。现在我们将进入水更深的最丰富也最复杂的授权:Policy或者说扩展的授权方式。
Policy授权又叫做扩展的授权(Extended Authorization),它是TPM授权机制的“瑞士军刀”。如果你有相关的专业知识,就可以用Policy实现任意形式的授权。这一节以及后面的第14章的目的都是为了让你获取使用这个强大EA所需的专业知识。在这一小节中,我们将从高层次的角度介绍EA是怎样工作的,以及Policy授权的生命周期,以及生命周期的每一步操作:Policy哈希的生成,实体的创建与修改,Policy会话创建,以及Policy会话的使用。我们还将探究EA的安全属性。这一节尽量避免介绍Policy相关的命令;下一章将会详细介绍相关命令的细节。
从高层次的角度来说,EA提供了很丰富的授权策略。EA,与HMAC和口令授权一样,用于授权针对一个TPM实体的动作。如下是一些使用Policy授权一个动作时可以做得控制案例:
- 要求一组特定的PCR中包含特定的值。
- 要求命令通过特定的Locality执行。
- 要求一个NV索引中包含一个或者一组特定的值。
- 要求输入一个口令。
- 要求物理存在(physical presence)。
- 要求一系列有顺序的条件。
除此之外还有很多。可以使用AND和OR组合成理论上无限多的Policy变种。Policy授权提供相当大的授权复杂性和创造性。Policy授权是“复杂授权之母”。
对于一个使用Policy授权的TPM命令来说,以下两件事情要正确:
- 每一个Policy相关的命令都会“断言”某些条件为真。如果特定Policy命令对应的条件不是真,这个授权就会失败。这个失败会有以下两种情况:
* 在Policy相关的命令执行时发生:这是一个即时断言,此时真正的授权命令执行之前就失败了。这个失败就表示Policy命令没有按照的正常的情况扩展Policy会话的policyDigest的值。正常情况下这个Policy命令会向policyDigest扩展合适的值。
- 在被授权命令执行的时候发生:这是一个延后的断言。在这种情况下,policyDigest的值会被扩展合适的值来表示特定的Policy命令已经执行。Policy命令对应的条件会被延后至被授权命令执行时检查(比如PolicyLocality)。
注意:一些命令可以组合上述两种情况,这就意味着即时断言和延后断言都必须要通过才能执行命令。
- 在授权时:
- 任何被延后的条件都将在此时被检查。如果其中的任何一个失败,这个命令都不会被授权。
- 实体的authPolicy会与Policy会话中的policyDigest比较。如果它们想等,命令就会被授权,并且接着开始执行。如果它们不相等,授权也将会失败。根本上来说,弱authPolicy与policyDigest相等,那就证明Policy授权要求的Policy命令已经被正确执行了,任何这些命令相关的即时断言也都通过了,并且这些命令是在授权以前按照特定的顺序执行的。
现在你已经看到了两个与时间相关的概念:Policy命令时间和授权时间。所有这些Policy授权相关的时间间隔都需要被精确的定义,从而让你更好地理解Policy授权。
在使用Policy授权时,有四个明显的时间段需要考虑。规范的多个地方都有暗示,但是并没有特别地说明:
- 建立实体Policy的时间:是指创建实体时需要用到的authPolicy。有以下两种方式来创建authPolicy:
- 软件复制TPM计算Policy的过程。
- 创建一个trial Policy会话。Policy相关的命令用于产生创建实体时会提供给TPM的policyDigest。在一个Trial Policy会话期间,所有的断言都会无条件通过;实际上Trial Policy会话的目的就是为了产生policyDigest,所以整个过程TPM会假设这个断言都会通过。当所有的Policy命令都已经执行后,调用者就可以用TPM2_GetPolicyDigest命令从TPM中读取policyDigest的值。
注意:Policy经常会被重复用于创建多个实体以及用于授权多次操作。
- 创建实体的时间:就是指用户向TPM发送命令来生成一个TPM实体。如果实体需要一个authPolicy,那建立实体Policy时候产生的Policy摘要就会被使用。
注意:因为实体的名称(Name)实在创建实体时生成的,所以实体的Policy摘要不能包含实体的名称。
- 建立Policy摘要的时间:当一个Policy会话被启动以后,在这个时间段内,应用软件会想TPM发送Policy相关的命令,这是为被授权命令的授权操作做准备。这些Policy相关的命令会向由TPM内部维护的会话policyDigest扩展特定的值。
- 授权时间:也就是值被授权命令发送到TPM设备执行的时间。此时会话的policyDigest必须和实体的authPolicy匹配,并且前面提到的任何延后的断言必须通过(Locality...)。
总结下来就是,一个policy计算通常会有两次——一次在创建实体的Policy时,另一次是在建立Policy摘要的时候:
这里需要注意的是,在某些情况下,一个真实的Policy会话可以同时用于生成用于创建实体的policy以及用于授权实体的动作;在这种情况下,policy只会被计算一次。比方说,下面的操作序列就可以工作:启动一个真实的Policy会话,发送TPM2_PolicyLocality命令,从TPM拿到Policy摘要,使用这个摘要创建一个实体,然后使用这个会话授权针对这个实体的动作。这个过程与前面描述的先创建实体然后再启动一个真实的Policy会话相反。
这个例子很可能没有什么实际用途,但是理解这一点有助于你理解Policy会话是怎样工作的。这种情况仅仅使用于Policy相关的断言可以在创建实体时就能满足的情况下(具体到这个例子就是,Locality是再授权时的断言,再加上locality的特殊性(每一笔命令种都有locality信息)就产生了这样的情况。)
- 第一次是为了生成一个Policy哈希值用于创建实体时的输入参数authPolicy。
- 第二次时在对一个实体授权一个动作之前:此时在TPM内部的会话上下文中建立一个Policy哈希。当被授权的命令执行时,会话的Policy哈希就会与实体的authPolicy做比较。如果匹配才算是授权成功。
所有的Policy命令都会在以下的时间段内做两件或者三件事情:
- 它们会检查一个或者多个条件(也就是前面提到的断言)。注意,这个会在建立Policy摘要的时间段或者真正授权的时间(延后的断言),或者是这两者的组合(组合断言,也就是两个时间段都有检查)。
- 它们会使用Policy命令相关的数据扩展当前会话的Policy哈希。这会在”建立实体Policy时间“和”建立Policy摘要的时间“执行(以上的第1,3步)。
- 它们可能会更新会话的状态。这些会话状态用于在延后断言或者组合断言时,指示TPM在实际授权时应该检查哪些延后的条件。这个操作会在第三步”建立Policy摘要的时间“执行。
现在你已经明白了各种各样的Policy时间段,让我们一起来看一个典型的Policy会话生命周期。
一个典型的Policy授权生命周期与口令和HMAC会话的生命周期相似,当然也有一些附加的内容,过程如下:
- 创建实体的Policy。
- 使用第一步创建的Policy摘要创建一个实体。
注意:第1,2步通常会在后续几步很早之前执行。并且后续的几个步骤可以执行多次,用于创建多个实体以及多次授权针对实体的动作。
- 启动一个Policy会话。
- 在Policy会话中执行Policy命令来满足授权的要求。
- 执行针对实体的动作。
下面让我们结合列表13-2中的代码看一下上述步骤的细节。为了简单起见,我们只会列出跟Policy会话相关的行数。
第一步就是使用一个Policy会话来决定一个实体的授权policy是什么:比如说,需要保护的实体是什么,针对这些实体的操作都有哪些限制,以及这些限制具体都是什么。然后,我们就要生成Policy的摘要;这一步对应之前刚刚描述的”建立Policy摘要的时间“。一共有两种方式来创建一个Policy摘要:使用一个Trial Policy会话,或者使用软件模拟TPM创建Policy摘要的操作来生成这个摘要。这两个我都将用一个简单的例子来说明;代码示例使用的是Trial Policy会话来生成摘要的。
一个示例Policy可能允许一个NV索引0x01400001被知道它的authValue的人读写。这种情况下,使用一个Trial Policy会话创建实体的Policy摘要可以按照以下方式来做:
- 使用TPM2_StartAuthSession启动一个Trial Policy会话。主要需要关心的参数如下:
- sessionType=TPM_SE_TRIAL。这就是配置一个会话为Trial Policy会话的方式。
- authHash=TPM_ALG_SHA256。这个操作设置了用于创建policyDigest时使用的哈希算法。这里我选择SHA256,但是实际上你可以使用任何TPM支持的哈希算法。
这个命令会返回一个Policy会话handle,我们叫作H-ps。示例代码的第63,66和72-75就是用于启动Trial Policy会话的。
- 执行TPM2_PolicyAuthValue命令,并且将输入参数(参考第78-79行代码)设置为:policySession=H-ps。
这个命令会按照以下的方式扩展会话的Policy摘要:
policyDigest-new := H(policyAlg)(policyDigest-old || TPM_CC_PolicyAuthValue)
上述公式中policyAlg就是在TPM2_StartAuthSession命令中设置的哈希算法,policyDigest-old是一个长度与policyAlg匹配并且全部为0的缓冲区。
- 执行TPM2_GetPolicyDigest命令从TPM设备中拿到Policy摘要,digest-ps。
对于另外一种方式,就是使用软件来计算digest-ps,软件需要完全复制TPM在步骤2中的Policy摘要计算方式。应用软件可以使用密码软件库如OpenSSL完成这个工作。
一旦policyDigest计算完成,就是可以使用它作为写操作授权来创建NV索引。与口令或者HMAC授权不同的是,创建NV索引以后,用于访问NV索引或者其他实体的policyDigest并不会直接变化。Policy命令会使用Policy相关的间接方式改变policyDigest,但是这个话题我们会在下一章中详细描述。
现在我们需要创建一个允许使用上述policy来授权写操作的NV索引;这个步骤与前面描述的“创建实体的时间”对应。创建索引是通过发送TPM2_NV_DefineSpace命令来完成的,命令使用的参数如下:
- auth=包含用于访问NV索引的authValue值的TPM2B(代码第42-44行)。
- publicInfo.t.nvPublic.nvIndex=0x01400001(代码第115-117行)。
- publicInfo.t.nvPublic.nameAlg=TPM_ALG_SHA256。这是用于生成NV索引的命令的哈希算法,****这个算法必须与用于计算policyDigest的policyAlg相同。****Trial会话和软件计算的方式都是如此。参考代码第115-117行。
- publicInfo.t.nvPublic.attributes.TPMA_NV_POLICYWRITE=1以及publicInfo.t.nvPublic.attributes.TPMA_NV_POLICYREAD=1。这种配置保证了只有满足policy时才可以对NV索引进行读写。参考代码第109-110。
- publicInfo.t.nvPublic.authPolicy=包含policyDigest的TPM2B数据,digest-ps。参考代码83-85,以及115-117行。
- publicInfo.t.nvPublic.dataSize=32。这指示了这个NV索引中包含数据的大小;在这个案例中,索引被配置成32字节长。参考代码第115-117行。
- 设置NV索引的授权值,参考代码第42-44行。
这个命令创建一个只有满足Policy才可以写的NV索引。下一步就是要创建一个真的——也就是说不是Trial类型的——Policy会话,并且用它来授权NV索引写操作。
启动一个真实的Policy会话也是使用TPM2_StartAuthSession这个命令。主要需要关心的输入参数如下(代码第152,以及154-156行):
- tpmKey=TPM_RH_NULL。
- bing=TPM_RH_NULL。
注意:tpmKey和bind的设置表明这是一个unbound和unsalted会话。选择设置成这样是为了尽可能地让代码示例简单;这个样的设置也是Policy会话最常用的配置方式。最终的目的是让你了解整个过程并且尽可能地避免接触底层细节。
- sessionType=TPM_SE_POLICY。用于将会话配置成真实Policy会话。
- authHash=TPM_ALG_SHA256。这用于设置生成policyDigest的哈希算法。因为之前我们在生成policyDigest时使用了SHA256算法,现在启动真实Policy会话时必须使用相同的算法。
这个命令会返回一个Policy会话handle,H-ps。现在我们可以使用Policy会话向TPM发送命令来授权对NV索引的操作。
使用前一步创建的Policy会话来发送与之前创建policyDigest时一样的Policy命令序列;这一步与之前描述的“创建Policy摘要的时间段”相对应。在这个案例中,Policy命令的序列非常简单,我们只需要发送一个命令,TPM2_PolicyAuthValue附带如下的参数(第177-179)policySession=H-ps。
为了响应这个命令,TPM设备会做以下两件事情:
- TPM会扩展Policy会话的Policy摘要,就像“建立实体的Policy时间”扩展Trial会话的Policy摘要一样。
- 因为TPM2_PolicyAuthValue是一个延后的断言,这个命令会保存policy会话上下文中的状态信息,这样一来TPM就知道要在授权时检查HMAC值。
此时,Policy授权已经“锁定并且加载”来准备授权操作。接下来就是我们要对NV索引执行写操作。
这个步骤与之前描述的“授权时间”相对应。我们将会写NV索引,并且,如果授权正确的话,写操作会成功完成。为了执行写操作,需要使用以下输入参数来执行TPM2_NV_Write命令:
- authHandle=0x01400001。这个handle表明了授权的来源。参考代码211-214。
- nvIndex=0x01400001。这表明了需要授权的对象。参考代码211-214行。
- authHandle的授权区域必须有以下的设置:
- authHandle=Policy会话handle,H-ps。参考代码第189-190行。
- nonceCaller=任何调用者想用的nonce。这个值不能是全0。参考代码191-192行。
- sessionAttributes=0。参考代码第193-194行。
- hmac.t.buffer被设置成命令的HMAC值。HMAC密钥是将会话密钥与NV索引的authValue连结之后的结果。参考代码202-205行。
- data=0xA5。参考代码第166-169和211-214行。
- offset=0。参考代码第211-214行。 TPM设备为了响应这个命令会检查policySession->policyDigest与被访问的实体的authPolicy匹配。然后它会经检查HMAC是正确的。如果者两者都通过,TPM就会继续执行写操作,然后命令就会成功完成。
注意:你可能也注意到了,在列表13-2中,因为Policy的情况使用了TPM2_PolicyAuthValue这个命令,HMAC和Policy这两中情况的操作非常像。主要的不同是policy的情况需要更多的工作。一个明显的问题是,如果需要TPM2_PolicyAuthValue命令的Policy会话需要更多的工作,为什么我们不用HMAC会话呢?答案就是Policy会话允许增加许多其他的授权因素,这就使得授权更加灵活可配,更加安全。这一点还会在下一章展开描述。
现在你已经从头到尾看到了一个完整Policy授权的过程。这只是一个非常简单的例子,但是它是我们理解下一章更复杂Policy授权的很好基础。
这一章的结尾,我们将口令,HMAC,以及Policy授权的生命周期统一成一个授权声明周期。
一个典型的授权生命周期如下:
- 对于HMAC和Policy会话,在创建一个实体之前必须确定authValue和authPolicy:
- 如果实体的操作需要使用一个Policy会话来授权,事先计算Policy摘要:authPolicy。
- 如果实体的操作需要使用一个HMAC会话来授权,需要先定好共享的秘密信息。
- 使用授权值(authValue)和/或Policy哈希(authPolicy)创建实体,或者修改已存在实体的authValue(修改一个实体的authPolicy使用不同的方式,这将在下一章中介绍):
- 实体的authValue将会被用于口令或者HMAC授权中。对于口令授权来说,authValue就是一个明文的密码。对于HMAC授权来说,authValue就是同故宫计算生成的会话HMAC值。
- 实体的authPolicy用于在命令被授权之前确定相关的Policy断言是否都已经通过。这个Policy哈希必须在创建实体以前计算;也就是第1步的第一种情况。
- 计算HMAC值。对于不会使用HMAC值的Policy会话,这一步可以跳过。
- 在使用HMAC或者Policy授权的情况下,此时需要启动一个HMAC或者Policy会话。
- 使用授权方法执行授权过的动作。满足以下条件时授权就算通过:
- 发送命令时传入的口令与实体的authValue值相同。
- 发送命令时传入的HMAC值与TPM计算的HMAC值匹配。这两种HMAC值都是部分由实体的authValue派生而来。
- policy会话的policyDigest值与实体的authPolicy相同。这个policy哈希是由多种因素派生而来,生成的方式就是使用创建policyDigest的Policy命令。另外,任何延后的断言也都必须通过才算授权成功。
- 对于HMAC会话的情况,需要计算命令响应的HMAC值,并拿这个值与TPM返回的HMAC值比较。
以上这些命令是以一定的相对时间顺序来呈现的,但是在这些步骤中间可能发生很多其他的操作。另外,一个policyDigest可以用于授权多个实体的多种动作。类似的情况是,一个HMAC会话也可以用于授权多个实体的多种操作。实际的授权机制因授权类型的不同而不同,我们之前也已经描述了这些不同,但是除了以下的例外情况,以上的所有步骤都必须执行:
- 口令授权不需要步骤3-4。
- 口令或者Policy授权不需要步骤6。
想了解更多policy会话的代码示例的话,请参考TSS SAPI测试代码的TestPolicy函数。
这一章我们总结讨论了授权和会话。祝贺你有如此大的进展!如果你已经理解了这一章,那你正在成为TPM2.0大师的路上。
这一章主要描述了授权和会话的基本概念以及它们的不同。你已经看到了口令,HMAC,以及Policy授权的命令和命令响应授权区域以及它们各自的生命周期。之后又了解了一个统一的授权生命周期。
这个过程感觉像是通过灭火水管喝水一样,没错!事实就是这样!这是TPM最难理解的地方之一;全面地理解这些内容将会极大地帮助你理解和使用TPM2.0设备。下一章我们将会详细地描述最强大的授权方式——Policy授权,这其中会介绍每一个Policy授权相关的命令及其应用案例。