不论使用什么方法,TPM2.0规范都是一个很难阅读的文档。尽管大部分的技术文档都很难阅读,但是TPM2.0还是有一些特别的挑战。首先这个规范很长,上次统计的时候已经超过1000页,并且因为使用过分简单和正式的语法,导致常常在一些看起来不重要的标点符号中包含了重要的功能说明。规范的第二部分能够被代码生成工具解析从而生成C语言的头文件,序列化和反序列化函数。这也解释了为什么规范中表格强调使用特殊标点符号和特别的样式。有时候一些重要的概念解释太简单并且不容易被发现。尽管从技术上来说没有问题,但是这些解释还是很难理解。文档描述的简洁和尽力避免冗余增强了文档的可维护性,但是无疑也降低了文档的可读性。实际上规范的主要目标就是可维护性,因此我们认为规范制定在这一点上做得很成功,甚至有点过了!
但是从技术的角度考虑,这个规范又是很健壮的;你需要的所有信息都在里面,问题是你要努力去找到它们。这就像是把一大堆拼图拼在一起,你有所有的拼图片,但是它们不总是在你预想的位置。这一章的目的主要是帮助你越过一些障碍从而更快地完成拼图。这些障碍是我们这些作者在这一领域研讨过的。你需要一直牢记在心的是,一旦你适应之后就会发现这个文档是很有逻辑性的。可能你并不相信,但是终有一天你会的。
总之,学习TPM2.0并不是一个简单的任务。但是幸运的是我们将帮助你大大加速学习进度。
这一章将介绍如下的主题:
- TPM2.0规范中较高层级的数据结构。
- 理解规范所必须的定义。
- 命令简要说明列表。
- 列表的修饰符。
- 命令的简要语法。
- 查找重要信息和常用信息的小窍门。
- 你需要了解的其他与TPM2.0相关的规范。
- 学习TPM2.0规范的策略。
注:这章并没有按照顺序介绍TPM规范的四个部分。顺序介绍看起来是合理的,但是我们在尝试以后得出的结论就是,这样不利于新手理解规范文档。为了提升你的理解,我们按照有助于加快你学习进程的方式修改了顺序。
TPM2.0规范是TPM2.0最重要也是最基础的规范。这个规范描述了TPM2.0的核心功能。
规范一共包含如下四部分:
- 第一部分,架构:正如它的名字一样,这是规范中最重要的一部分,所以值得仔细研读。这部分描述了TPM的基本操作及其背后的原理。它还包括了很多关于TPM操作的实践细节。比如说,这部分包含了会话(session)如何创建的描述。会话被用于授权,审计,加密等命令。所以描述包含了各种类型会话的详细信息。
- 第二部分,数据结构:这部分主要描述了TPM2.0用到的数据类型,结构体和联合。这相当于一个变成手册关于数据类型的介绍。同时它还包含了命令失败时错误编码的定义。
- 第三部分,命令:这部分包含了TPM2.0所有的命令描述,相当于编程手册中函数的描述。它包含了TPM2.0所有命令的输入参数,输出参数以及错误产生的条件。同时每一个命令所做的操作细节都用C代码详细的描述。但是这些C代码会调用第三部分没有的辅助性函数,这些函数以及相关的错误编码被包含在第四部分中。
- 第四部分,辅助性函数:这部分主要包含了会被第三部分C代码调用的函数描述以及相关的错误代码定义。这些代码包含了很多TPM2.0很多特别细致的操作。如果你需要深入TPM模拟器源码来找到一个错误码产生的原因,你应该仔细研读这一部分。所以不管用什么样的方式,你最终会在调试过程中逐渐熟悉其中重要的内容。
在正式介绍每一部分之前,我们先介绍一些有助于理解规范的定义。 我们建议读者把这一部分打上标签,因为以后你在阅读规范和这本书时一定还会经常用到这些定义的。如果你在第一次学习这些定义的时候没有完全理解,也要灰心。第一次阅读的时候有一个全局性的理解就好,然后打上标记以备以后引用。
以下的定义使用于命令和命令的响应:
- 授权:用于证明拥有一个TPM资源实体的访问权限,TPM2.0有三种授权类型:
- 口令授权:这是一种单次操作的明文授权方式:
- HMAC 授权:使用一个HMAC来做授权操作。HMAC密钥是使用一个共享秘密生成的,共享秘密也是授权的基础。
- 策略授权或者增强的授权:当一个授权策略的条件被满足时才能授权使用TPM的一个对象。在TPM命令被执行之前,使用策略断言命令来判断授权条件是否满足。
- 会话(session):TPM2.0将会话定义为:一个因TPM操作产生的状态的集合。不幸的是,这个定义太抽象并不能体现有价值的信息。最好的理解会话的方法就是看看会话是怎么用的。会话被用于授权操作,并且单次命令的操作如加密,解密,审计等等都是在一个会话中完成的。对于HMAC和Policy(策略,后面都将以Policy的形式体现)会话来说,首先创建会话,然后在这个会话中执行多个命令。然而口令授权却是会话的一个特殊情况,这个会话并不包含任何TPM状态信息。不同类型的会话及其应用场景将在本章后面部分介绍。现在为止,我们已经具备足够知识来理解高层次的概念。
- Handle:它用于标识TPM资源,这个资源占用了TPM的内存。
- 字节流:对于一个TPM命令来说,就是实际发送给TPM设备的一串数据。对于命令相应来说就是从TPM设备收到的一串数据。(TPM2.0 BitProtocol了解一下)
- 标准化的数据:规范第三部分以C语言结构体的方式描述了TPM命令的输入和输出。然而这个结构体的大小通常比实际发送到TPM的数据要大。比如说,一些结构体中包含一些大小变化很大的联合体,实际运行中只有真正用到的类型才会被用到。另外,与TPM交互时数据是以大端的形式存储的。复合以上特点的数据就是标准化的数据(就是真正进出TPM设备的数据)。一个命令的字节流就是组合标准化的输入参数后形成的,命令响应也是一样的。
- 反序列化的数据:就是以C语言结构体形式存在的数据。
- 序列化的数据:标准化形式的数据,也就是进出TPM设备的数据。
以下内容将按照它们在命令字节流中出现的顺序来介绍:
- Command header:是所有命令都有的区域。它包括tag,commandSize和commandCode这三个域:
- tag:用户标识这个命令是不是有session,也就是说标识命令是否包含是授权区域。
- commandSize:整个命令字节流的大小,包括这个header本身。
- commandCode:标识了命令本身,它决定TPM对后续命令流如何解析。
- Handle area:包含0-3个handle
- Parameter area:包含命令相关的参数信息。
- Authorizaiton area:包含了命令的会话数据。因为一个命令可以和多大3个session相关联,所以这个区域可以包含0-3这session。session中包含了授权信息,修饰符和在应用与TPM之间传输的session状态信息。
以下内容将按照它们在命令响应字节流中出现的顺序来介绍:
- Response header:是所有命令响应都有的区域。它包括tag,responseSize和responseCode这三个域:
- tag:用户标识这个命令是不是有session,也就是说标识命令响应是否包含是授权区域。
- responseSize:整个命令响应字节流的大小,包括这个header本身。
- responseCode:标识了命令本身,它决定TPM对后续命令响应字节流如何解析。
- Handle area:包含0-3个handle
- Parameter area:包含命令响应相关的参数信息。
- Authorizaiton area:包含了命令响应的会话数据。因为一个命令可以和多大3个session相关联,所以这个区域可以包含0-3这session。session中包含了授权信息,修饰符和在应用与TPM之间传输的session状态信息。
如果你跟大部分程序员一样,那你将会从第三部分开始。说真的,谁有时间去读一本如此之长的规范文档呢?毕竟我们的目标就是完成任务,对吗?(真的对吗?)事实上这对于忙碌的工程师来说就是如此。实际上这是一种很好的方法,当然你马上就会不得不面对TPM2.0规范确实难以阅读这个残酷的现实。为了帮助你越过我们在使用规范时已经经历过的障碍,你首先要了解以下关于第三部分的事情。
TPM2.0通用的命令和命令响应相关的数据结构是在规范第一部分介绍的,章节的名称叫做“Command/Response Structure”。其中的分割列表,命令结构和命令响应结构列表都非常有用。我们建议将规范的这个位置加上标签,因为以后你会经常用到的。
为了能让我们介绍的内容更具实践性,我们首先详细介绍两个命令。不需要授权的TPM2_Startup和需要授权的TPM2_Create。并且说明这两种类型命令在字节流中的差别。我们首先介绍TPM2_Startup,因为这个命令是TPM第一个必须要执行的命令,也是TPM2.0中最简单的命令之一。
如果你查看第三部分有关TPM2_Startup这个命令的内容,你将看到以下三个部分:
- “一般描述”:简要说明命令的功能,详细描述一些重要的输入约束,以及相关的错误条件。
- “命令和响应”:一个描述命令输入和输出数据的列表。我们马上就会介绍这个。
- “详细的动作”:包含命令的实现代码和标识错误条件的列表。(不包含辅助性代码的错误信息)
所有命令都使用这种三段式的描述。我们现在只看命令和响应列表。对于TPM2_Startup来说,这些列表分别是表5-1和表5-2。
表5-1
最左边的类型(Type)一列表示命令相关参数的数据类型。这些数据类型是在规范的第二部分定义的。名称(Name)这一列是自注释的,主要包含了进出TPM设备的参数名称。同时这也名称与第三部分的源码相对应。描述(Description)这一列简要描了这个域的功能及其特殊要求。TPM2_Startup有两个跟域相关的要求:tag域必须是TPM_ST_NO_SESSIONS,commandSize域必须是TPM_CC_Startup。“{NV}”是一个列表修饰符,它表示这个命令有可能会更新TPM内部的NVRAM。(列表修饰符在规范第三部分比较靠前的位置描述)
所有命令的前三个域,也就是tag,commandSize和commandCode,是相同的。这三个域一起构成了命令的头。
下面是这个命令各个域的详细解释:
- tag:标示了这个命令是否有会话。因为TPM2_Startup从来不使用会话,所以这个值必须是TPM_ST_NO_SESSIONS。
- commandSize:整个命令数据流的大小。
- commandCode:标示要执行哪一个命令。它决定TPM怎样处理后续的数据。
现在注意看一下commandCode之后的这条线:
线
这条线之后的域都是命令的参数区域。在这个例子中,startupType是这个区域中唯一的参数。通常情况下,这个区域包含了一些和具体命令相关的配置参数。这个区域内的其他线条修饰和列表修饰符分别在规范第三部分的“Table Decorations”和“Handle and Parameter Demaraction”中描述。当你阅读第三部分的时候会经常饮用这一部分的。
表5-2
下面是这个命令响应各个域的详细解释:
- tag:标示了这个命令响应是否有会话。因为TPM2_Startup从来不使用会话,所以这个值必须是TPM_ST_NO_SESSIONS。
- commandSize:整个命令响应数据流的大小。
- commandCode:标示了这个命令执行成功与否。TPM_RC_SUCCESS表示成功,其他值表示失败。
主要注意的是TPM2_Startup没有参数返回,所以列表中也就 没有用于标示参数的线。
现在我们将了解比前一个复杂的多的命令TPM2_Create。这个命令用于创建TPM对象,比如密钥和其他数据对象。表5-3就是它的命令描述表。
表5-3
下面是这个命令各个域的详细解释:
- tag:在这个例子中,它的值是TPM_ST_SESSIONS。它表示这个命令必须包含会话。另外列表中parentHandle前面的@符号也说明了这一点,同时也就意味着这个命令的授权会话与这个handle相关。后续会更详细地介绍。
- commandSize:整个命令数据流的大小,包括授权数据。
- commandCode:标示要执行哪一个命令。它决定TPM怎样处理后续的数据。
现在需要注意下面这个分隔符,有趣的是,TPM2_Startup中没有这样的分隔符。
分隔符
这个分隔符后面的区域就是handle区域,规范的第三部分“Handle and Parameter Demacration”这一节会介绍。Handle就是一个32位的整数引用,用于标示TPM内部的资源实体。对于这个命令来说,parentHandle是它用到的唯一的handle。一个命令最多可以用两个handle。
这里再次注意一下parentHandle前面的@字符。它是一个列表修饰符,它表示parentHandle要求这个命令的授权数据区域中包含它的授权信息。
同样注意一下在描述这一列中的“Auth Index: 1”。它标示了这个handle相关的授权信息在授权区域内的顺序。在这个例子中,parentHandle的授权信息必须出现在授权区域的第1个位置。所有需要授权信息的命令最多可以包含3个授权信息。强调一下,当命令的描述表中的handle使用@修饰时就表示这个命令需要这个handle相关的授权信息。在这个例子中只需要一个授权信息。
然后我们再看一下Auth Role: USER,这是授权的进一步限制,在增强的授权一章会有详细的介绍。Auth Role与操作系统中的权限级别类似。它控制着谁可以访问TPM特定的资源实体。
Handle和命令参数区域的使用方式不同,handle不会用于计算cpHash,cpHash就是输入参数的哈希。这个哈希值又会用于计算HMAC值,有时候也会用于计算Policy摘要。正因为handle不用于计算cpHash,用于TPM的资源管理软件就可以将handle虚拟化,从而实现TPM对象的换入换出。这一点与虚拟内存在磁盘和物理内存之间换入换出原理类似。后续的章节将详细介绍HMAC,Policy摘要以及资源管理。现在为止,我们需要理解,handle和参数在最终的字节流中是分开来放的,这样一来就可以实现一些重要的(管理)功能。
接下来我们又看到了与Startup命令一样的分割线,它同样也是表示参数区域的开始。
线
但是在这个示例中,因为tag的值是TPM_ST_SESSIONS,就是说这个命令需要授权会话。所以这个分割线也同事代表了授权数据在命令字节流中的位置。这个命令的授权区域可以包含1-3个会话。我们会在第13章详细介绍授权区域。
这个命令需要四个参数:insensitive,inPublic,outsideInfo和creationPCR。规范的第二部分描述了参数对应的数据结构。
表5-4是TPM2_Create命令的响应描述表。
表5-4
下面是这个命令相应的各个域的详细解释:
- tag,responseSize和responseCode已经在之前的示例中说明了。当然不同的是,如果命令成功了,tag的值是TPM_RC_SESSIONS而不是TPM_ST_SESSIONS,它用于表示在命令响应的会话。实际上,抛开本例,总结一下一共有以下三种情况:
- 如果命令没有会话,那么响应也不会有。这样的命令里,tag的值永远是TPM_ST_NO_SESSIONS。
- 如果命令有会话,并且命令也执行成功了,那么命令相应的tag值就是TPM_ST_NO_SESSIONS。这表示命令的相应也包含会话信息。
- 如果命令有会话,但命令执行失败了,那么响应的tag值就是TPM_ST_NO_SESSIONS。进一步说,一个失败的命令响应从来都不会包含会话或者响应参数。
当命令的响应描述中包含下面的分割线时,就表示之后是handle区域。在这个命令中,没有命令响应handle,所以也没有这个分割线。
分割线
现在有看到用于表示参数开始的老朋友了。
参数分割线
但是需要特别注意的是,命令响应中的分割线仅仅用于表示它后面的内容是命令响应的参数。命令相应的授权信息不在这里,而是在字节流靠后的位置。这个命令一共有5个返回参数:outPrivate,outPublic,creationData,creationHash,和creationTicketNotice。
命令响应的授权信息出现在所有参数之后的最后一个分割线之后。这也就意味着命令响应的授权数据在整个响应字节流的最后位置。
到现在为止我们介绍了命令和命令响应的整体结构,下面我们讨论一下常用的数据类型。
如果你正在编写底层的TPM代码,理解这一小节很重要,因为代码大部分的bug将来自这里。当调试TPM2.0底层代码时,你需要理解这一节,从而对进出TPM设备的字节流解码。通用数据结构的构造,字节流的标准化,和大小端是理解这一节的重要数据概念。
这一小节将介绍一些常用的数据结构。因为我们以后将经常看到这些结构,所以理解它们是很重要的。
所有以“TPM2B_”开始的数据结构都是以字节为单位的buffer。每个这样的buffer都由size和array[size]构成。表5-5就是一个典型的结构。
表5-5
与它相对应的C语言结构如下:
typedef struct {
UINT16 size; /* size in octets of the buffer field;
may be 0 */
BYTE buffer[sizeof(TPMT_HA)]; /* the buffer area that contains the
algorithm ID and
the digest */
} TPM2B_DATA;
一个联合(Union)通常包含在一个结构体中,在他之前有一个Union选择子。表5-6就是一个例子
表5-6
这个结构体有两个元素:hashAlg,它用于选择digest这个Union中具体的数据。digest前面用中括号括起来的hashAlg也说明了它的功能。在表5-6中,hashAlg就是digest的选择子。
表5-7描述了TPMU_HA这个联合的定义。
表5-7
通常来说,在一个结构体中,当出现[A]B这样的形式时,A就是参数B的选择子。在表5-7中,如果hashAlg被设置成TPM_ALG_SHA1,那这个Union中实际的元素就是sha1[SHA1_DIEST_SIZE]。
表5-6和表5-7生成的C代码如下:
typedef struct {
TPMI_ALG_HASH hashAlg;
TPMU_HA digest;
} TPMT_HA;
typedef union {
BYTE sha1 [SHA1_DIGEST_SIZE]; /* TPM_ALG_SHA1 */
BYTE sha256 [SHA256_DIGEST_SIZE]; /* TPM_ALG_SHA256 */
BYTE sm3_256 [SM3_256_DIGEST_SIZE]; /* TPM_ALG_SM3_256 */
BYTE sha384 [SHA384_DIGEST_SIZE]; /* TPM_ALG_SHA384 */
BYTE sha512 [SHA512_DIGEST_SIZE]; /* TPM_ALG_SHA512 */
} TPMU_HA;
进出TPM的数据都是经过最小化处理的,数据剔除了一些没有必要的字节。这样一来就保证了TPM字节流的最大传输率。因为通常情况下TPM是通过速率较低的LPC和SPI接口连接到主板的。最小化之后的数据叫做标准的数据,它和规范第二部分描述的像C一样的数据结构是不同的。在你试图去解析数据流的时候理解这一点很重要。
我们以MAX_NV_BUFFER_2B这个结构体为例来说明:
typedef struct {
UINT16 size;
BYTE buffer[MAX_NV_BUFFER_SIZE];
} MAX_NV_BUFFER_2B;
这个结构体有一个size域和一个长度为MAX_NV_BUFFER_SIZE的buffer。在一个参考实现中,MAX_NV_BUFFER_SIZE的值是1024.但是实际发送到TPM的数据量是size的值,也就是实际上在总线上出现的数据只有size个字节。举例来说,如果size的值是10,那么一共发送12个字节:size本身的2个字节加10个字节的buffer数据。
在C语言中,联合用于在相同的存储区域覆盖不同的数据类型的数据结构。union的大小与包含在union中最大的数据结构的大小相同。在包含union的TPM2.0数据结构中通常包含一个选择子,它用于指示union中实际存在的数据类型。一个标准化的数据只包含选择子和union中被选择的数据结构。举例来说,在TPMT_HA这个结构体中,如果选择子hashAlg被设置成TPM_ALG_SHA1,那么digest的大小就是SHA1_DIEST_SIZE,这个值要远远小于union中最大数据结构的大小。
进出TPM的数据总是以大端的形式传输。这也就意味着像x86架构这样的小端机器在与TPM交互时,需要进行相应的数据转换。
规范第二部分中“Notation”这一节对理解规范非常重要。我们在学习TPM2.0数据结构的时候会经常引用这个地方。我们鼓励大家完整地阅读这一节,所以我们在这里就不再重复全部的内容。但是我们将着重强调一些非常重要的地方(这也是需要打标签的地方)。
- 在一个枚举表中,一个#字符表示当一个枚举值标准化失败以后的返回值,这里的失败就意味着传递的数据不匹配任何一个枚举允许的值。
- 一个$字符代表一个参数可以是之前定义的一组值中的一个。(没有太明白)
- 一个+字符放在一个值的前面表示这个值是可选的:在一个枚举中,这个值是可选的。枚举中的可选值是否被允许使用取决于类型的定义前面是否有+符号。(没有太明白)
- 一个null出现在Union的定义中表示这个Union可以是空的。
- 如果一个Union的成员没有选择子,这表示这个成员是通用的。没有选择子的成员是有选择子成员的超集。
- {}表示参数的限制。参考规范的“Parameter Limits"这一节来获取更详细的信息。
规范第三部分中”Command Modifiers and Table Decorations“这一节描述了命令描述中会用到的特殊符号。这同样也是一个需要打标签的地方。我们在这里仅仅介绍一些相对更常用的符号,想了解全部的内容还需要参考规范。
- +:与第二部分中表示可选类型的符号类似。当它被放到一个类型的前面时,表示相应的变量可以是null。
- @:当这个符号用于修饰一个handle的名字时,这表示这个handle需要授权。同时这也表示命令描述列表的tag值必须TPM_ST_SESSIONS。
- +PP,+{PP}:作为TPM_RH_PLATFORM的后缀,分别表示使用TPM_RH_PLATFORM这个handle的授权时,需要和可能要求实际存在断言为真(Physical Presence)。
- Auth Index:表示命令所需handle的序号。(我们认为这个有点多余,因为在命令表述表中的handle就是按照相应顺序出现的)
- Auth Role:标识所需handle的授权角色,可以是USER,ADMIN或者DUP。这些角色将在第13章详细描述。
以下列出了一些最长用到的规范章节:
- 命令代码在规范的第二部分中,相应的章节名字叫做”TPM_CC Listing“。
- 错误代码出现以下不同的地方:
- 规范第二部分,”TPM_RC(Response Codes)“这一节列出了所有的命令响应代码。
- 规范第一部分,”Response Code Details“这一节列出了解析错误代码的流程图。强烈推荐按照此流程图实现一个自动化的软件解码器。手动解码几个月后,一位作者写了这样一个软件,发现特别有用。注:tpm2_rc_decode了解一下。
- 规范第三,四部分描述了TPM命令和它调用的子程序返回的错误代码。需要再次强调的是第三部分没有描述一个TPM命令所有可能返回的错误码。第三部分中命令代码会调用第四部分中的辅助性程序,这些支撑性程序同样也会返回错误代码。这让很多大意的程序员非常困扰。
- 规范第三部分开始的“Table Decorations”和“Handle and Parameter Demarcation”这两节内容非常有助于理解命令描述表。不要忽略它们。
- 为了理解规范第二部分描述的数据结构,理解“Notation”这一节很重要。它主要描述了很多让人费解的字符和它们的修饰意义。千万不要忽略他们。
- 规范第一部分的“Authorizations and Acknowledgements”描述了各种各样的会话和授权。规范的其他地方没有比这个更详细的介绍了。知道这一节的位置很重要,因为我们经常会在解析命令和命令响应的时候用到它。
- 规范第一部分的“TPM Handles”描述了Handle的类型。需要特别注意的是handle用于引用资源的数字。同时,规范第二部分的“TPM_HT”列出了各种类型的handle。
- 规范第一部分中的“Names”一节描述了各种各样的资源实体,以及它们是怎样派生出来的。这对于理解如何创建会话的HMAC和Policy的摘要很重要。
- 为了理解policy session的操作,理解规范第一部分的以下章节很有帮助:
- “Policy Example”
- “Trial Policy Modification of Policies”
- “TPM2_PolicySigned(),TPM2_PolicySecret(),and TPM2_PolicyTicket()”。这一节提供了很多这个复杂的Policy命令的详细信息。
可以使用PDF阅读器查阅PDF版本的规范。有时候你找的信息不在你想象的位置。比如说,尽管规范第一部分看起来并不是很正式,也不像是描述数据结构的地方;但是事实上第一部分对一些数据结构的功能和相应数据域的描述是最详细的。
如果你能看到TPM软件库的头文件,也许会发现可以通过头文件找到一个数据结构是怎样被使用的。最简单的方式如下:
- 在描述TPM2.0数据结构的C头文件中找到相应的数据结构定义。
- 一个写作良好的头文件会在一个结构体或者类型的定义上面的注释里标明它在规范中的列表标号。所以我们通过这个标号就可以在规范第二部分找到这个数据结构表格。表格上方的描述性文字提供了有助于我们理解的数据结构的信息。这是目前为止最有效的方法。
工程师有各种各样自己喜欢的学习方法,因此我们有很多方法来吃透TPM2.0这样的规范。作为作者,我们有不同的性格,我们使用了不同的策略来完成这个规范。在这一节描述了我们是怎样加速TPM2.0的开发,和那些方法最适用于我们。你可以选择其中之一或者全部的方法来加速你的学习开发进程。但愿我们的经历对你有用。
我是这个领域的新手。我在2012年5月开始TPM2.0相关的工作。我之前有使用过TPM1.2,但仅仅是用到了在TXT会用的功能。这就意味着,我从来没有学过会话(sessions)以及会话在TPM1.2中怎样工作,同时我也不太了解密钥和密钥管理。
作为一个需要满足产品计划的工程师,我的目标就是完成要做的事情。开始我尝试阅读规范文档,但是很快我就被大量不熟悉的内容和对我来说迷惑性的术语难住了。所以我首先总结出我需要的TPM2.0功能以及如何实现它们。这引导我从规范的第三部分开始学习。当我尝试在模拟器上运行我写的代码时,我很快就遇到了我不能解释的错误,然后我就对模拟器进行单步调试。此时我第一次明白了TPM设备能理解的标准化数据流和TPM2.0命令中相关的C语言数据结构之间的不同。为了完成我的任务计划,我痛苦地调试了所有TXT用到的TPM2.0功能。与此同时,我开始开发TPM2.0的SystemAPI代码。这需要更深入地了解TPM2.0,所以我就一边写代码看规范,一边在模拟器上调试。坦白说,仍有一部分规范文档我不愿意去尝试理解透彻,唯一的补救方法就是打电话请教TPM工作组的主席(尼玛,惊呆了,这待遇),主席很好地帮助我解决了HMAC和Policy Session相关的问题。当我通过咨询TPM工作组主席慢慢理解之后,我们做了一些图表来说明不同session的关联,这本书也包含其中的一些图;同时我也产生了写这本书的想法。
当我完成大部分的TSS SAPI开发工作后,我已经具备了足够多的知识回过头来深入理解规范;但是深入阅读规范的动力主要来自于我要为接下来的TPM2.0训练营准备一个演示幻灯片,我当时是Intel的代表。三个月以来,我一页一页地阅读规范文档,我理解了大部分的内容。试想一下,如果是我一开始就这样深入阅读,一定没有这样的效果。这里还有一个听起来很奇怪的小窍门:我发现在健身房锻炼的同时在Kindle上阅读规范很高效。我想这大概是因为体育锻炼让我时刻保持警惕和清醒;如果我在工作桌前阅读的话,我可能会为了保持清醒而挣扎。每天阅读30到45分钟是保证进度的合理时间,重要的是阅读时保持清醒,而且不要让大脑过载。
总结一下我的学习策略:
- 开始阅读了规范第1,2,3部分的一些内容,但是不能全面深入地理解它们。抛开理解的困难,这一次阅读让我对TPM2.0和TPM1.2的不同有了大概的了解。
- 为了交付工作任务,集中精力开发TPM2.0重要的基本功能,由此也开始阅读主要描述TPM2.0功能的第三部分。
- 开发TSS SAPI的同时,阅读规范并在TPM模拟器上调试代码,同时向专家时不时地请教问题。
- 每天深入阅读规范30到45分钟。
最后一点建议:一定要先开始做,不要试图一开始就搞懂所有的细节。首先有一个全局性的了解,然后逐步深入理解。同时当你遇到困难时千万不要忘记寻求帮助。我现在也一直在自己学习。
我从一开始就参与了规范的制定。我将给出一些建议而不是我自己的经验。
TPM规范由用户手册(第一部分)和技术参考手册(第二,三部分)组成。如果你之前没有TPM2.0使用经验或者仅仅有TPM1.2的使用经验,我建议你首先阅读第一部分,或者至少是跟你的应用密切相关的章节。尽管你不会马上理解它的复杂性,但是你将会熟悉相关的术语和TPM特性,以及这两者之间有什么联系。
一旦你清楚了你想做什么以及了解了命令的调用顺序,就可以通过第三部分了解每个命令的详细操作。对于一个命令来说,具有一般性描述,命令和命令响应列表就足够了。用户通常情况下不需要去阅读第三,四部分中的代码。
我猜大多数用户不会自己去构造命令的数据流。像TSS这样的中间层软件库通常会帮你做这些事情。如果你需要使用这样的软件库编写或者调试软件,可以参考规范的第二部分来了解相关的数据结构,这部分包含了数据结构的成员名称,成员数据类型,和可能的参数等信息。更进一步地,平台相关的规范描述了一个TPM实现的参数。
规范的第四部分以C的形式描述了TPM的操作。应用软件和中间件开发者应该很少会参考第四部分。
我一开始就参与了TPM1.2和TPM2.0部分规范的制定工作。我通过阅读第三部分加快了我对TPM的理解。除了命令到底如何实现授权没有讲清楚之外,其他部分我都理解了。因此对于不需要授权的命令来说,比如TPM2_GetRandom,规范第三部分提供了所有我们需要知道的信息:需要发送什么样的参数,参数的大小是多少等等。开始时,第一个参数让我很困惑,后来我发现它应该一直都是NO_SESSIONS,因为我也不会去对TPM2_GetRandom这个命令做审计。命令参数的详细信息在规范第二部分描述的很详细,并且对于不需要授权的命令来说也很容易理解。
接下来我使用简单的口令会话尝试授权操作。这个比较简单,因为口令会话是一直存在的,所以我不需要对会话做任何的加解密,salting,或者审计。这仅仅就是一个以明文形式存在的口令。规范第一部分的”Password Authorizaiton“这一节很好地解释了这个功能。我通过”获得TPM的所有权“这个操作来使用口令授权。
下一步我实现了创建一个密钥。这是一个比前要复杂的工作,因为我需要理解union从而选择密钥的算法,还需要理解其他跟密钥相关的参数。我首先创建了一个只需要口令授权的密钥,因为它相对于HMAC和policy授权来说要简单。这个密钥就作为TPM的根存储密钥(SRK)。
之后我又攻克了policy授权。因为我想创建一个签名密钥,同时我可以修改这个密钥的口令。我还创建了一些其他密钥,他们被锁定到一个NV序号的口令中。这样一来我就需要创建一个NV序号;并且我希望这个NV不能以不同的口令重复创建。本书的第14章介绍了我具体是怎么做的。
后来我又想尝试一下各种各样的会话,所以我就创建了一个使用HMAC授权的密钥。然后我对命令做了审计,审计成功以后,我用一个解密过的会话来传递口令(这个地方没有太懂)。后来我又尝试了加盐的(slated)HMAC会话。
最后我尝试了更加复杂的policy,实验了TPM2_PolicyOr和TPM2_PolicyAuthorize。这个时候我感觉我已经对TPM如何工作驾轻就熟了。
平台相关的规范简要概括了TPM2.0规范用于创建平台相关的定义。它们列出了那些内容是必须的,可选的,和剔除的;定义了相关的最大最小值;增加了初始化和初始设置的要求;以及物理接口的细节。
你有可能会参考以下平台相关的规范:
- TCG PC Client Platform TPM Profile(PTP) Specification:定义了PC和服务器平台上的相关内容。
- TPM2.0 Mobile Reference Architecture:定义了在移动平台上受保护环境下实现TPM的参考架构。
尽管前路艰险,我们还是可以有效地通过对规范的整体浏览和前辈的建议来加快我们对TPM2.0的学习。这一章,我们分享了一些不容易获得的经验来帮助你学习。现在,”消化“过程将正式开始。
下一章我们将介绍一些常用的软件执行环境。这主要是为我们在后续章节中的代码示例做准备。