目录

  • 9 启动
  • 9.1 介绍
  • 9.2 _TPM_Init
  • 9.3 TPM启动事件
  • 相关源码


9 启动

启动在这里被定义为每次系统启动时需要做的软件操作。系统启动可以是冷启动,或者是PC相关的从睡眠中唤醒,或者从休眠中重新启动。TPM内部有几种易失性的状态,包括PCR值,已加载的会话和密钥,使能配置,授权和Policy信息,混合NV索引,以及时钟状态。根据TPM电源周期类型的不同,这些易失性状态可能必须被保留或者重新初始化。TPM提供了两个命令,它们可以有各种组合,这允许外部软件来处理TPM电源周期相关的要求

TPM的启动和关闭都由底层软件来管理。在一个PC平台上,底层软件是指BIOS(UEFI)和操作系统。管理的主要目的是相关的状态需要按照要求被复位或者恢复,从而使得应用不用关心这些事件的发生。举例来说,一个应用不希望已经加载过的密钥或者会话突然消失了。这个应用可能不能重新加载密钥,并且它也不希望因为会话突然消失而重新运行一个Policy检查。在启动软件和操作系统的支持下,TPM可以让电源周期对应用软件透明,具体的做法就是在掉电时将易失性状态保存到非易失性内存中,重新上电后将这些状态恢复

9.1 介绍

本章主要介绍用于管理TPM的启动和重新启动状态的命令。

9.2 _TPM_Init

_TPM_Init初始化TPM

初始化操作包括测试执行下一个预期命令所需的代码。如果TPM处于

升级模式 Field Upgrade mode (FUM),下一个预期命令是TPM2_FieldUpgradeData();否则,下一个预期命令是TPM2_Startup()

Note 1 如果TPM在收到_TPM_Init后执行自检,并且TPM在之前已经能进入故障模式
接收到TPM2_Startup()或TPM2_FieldUpgradeData(),则TPM可能能够接受
TPM2_GetTestResult() or TPM2_GetCapability()。

应在特定于平台的规范中定义信号_TPM_Init的方式,该规范定义了TPM的物理接口。每当平台启动启动时,平台应发送此指示并且仅当平台启动其引导过程时。生成该指示的软件方法不得同时重置平台和开始执行CRTM。

Note 2 在参考实现中,此信号导致清除内部标志(g_initialized)。当此标志清除时,TPM将只接受上述下一个预期命令。

// This function is used to process a _TPM_Init indication.
LIB_EXPORT void
_TPM_Init(
	  void
	  )
{
    BOOL restored = FALSE;  /* libtpms added */

    g_powerWasLost = g_powerWasLost | _plat__WasPowerLost();
#if SIMULATION && !defined NDEBUG  /* libtpms changed */
    // If power was lost and this was a simulation, 
    // put canary in RAM used by NV
    // so that uninitialized memory can be detected more easily
    if(g_powerWasLost)
	{
	    memset(&gc, 0xbb, sizeof(gc));
	    memset(&gr, 0xbb, sizeof(gr));
	    memset(&gp, 0xbb, sizeof(gp));
	    memset(&go, 0xbb, sizeof(go));
	}
#endif
#if SIMULATION
    // Clear the flag that forces failure on self-test
    g_forceFailureMode = FALSE;
#endif
    // Disable the tick processing
    _plat__ACT_EnableTicks(FALSE);
    // Set initialization state
    TPMInit();
    // Set g_DRTMHandle as unassigned
    g_DRTMHandle = TPM_RH_UNASSIGNED;
    // No H-CRTM, yet.
    g_DrtmPreStartup = FALSE;
    // Initialize the NvEnvironment.
    g_nvOk = NvPowerOn();
    // Initialize cryptographic functions
    g_inFailureMode |= (CryptInit() == FALSE); /* libtpms changed */
    if(!g_inFailureMode)
	{
	    // Load the persistent data
	    NvReadPersistent();
	    // Load the orderly data (clock and DRBG state).
	    // If this is not done here, things break
	    NvRead(&go, NV_ORDERLY_DATA, sizeof(go));
	    // Start clock. Need to do this after NV has been restored.
	    TimePowerOn();

            /* libtpms added begin */
            VolatileLoad(&restored);
            if (restored)
                NVShadowRestore();
	    /* libtpms added end */
	}
    return;
}

_TPM_Init 主要初始化uninitialized memory、初始化加解密函数、加载非易失性存储数据,读取orderly data 数据。

9.3 TPM启动事件

TPM 2_Startup前面总是有_TPM_Init调用,TPM 2_Startup仅在_TPM_Init之后有效。

TPM收到TPM 2_Startup和另一个命令,或者如果TPM收到TPM 2_Startup,而不是符合预期的指令时,TPM应返回TPM_RC_INITIALIZE

如果TPM处于故障模式,TPM应接受TPM2_GetTestResultTPM2_GetCapability调用,即使TPM 2_Startup未成功完成或根本未处理。

特定于平台的规范可能会限制接收TPM 2_Startup的位置。
关机/启动顺序决定TPM的运行方式,以响应TPM 2_StartupTPM规范定义了三种启动事件:TPM复位,TPM恢复,以及TPM重启。这些信号在平台复位时的TPM_Init信号之后发生。在一个典型的硬件TPM实现中,初始化就是释放TPM的复位引脚,这很可能是在一个电源周期之后。这时候,我们假设TPM的易失性状态都会丢失,只有之前保存的非易失性状态仍存在于TPM中。

TPM复位通常在平台上电后的启动阶段或者不断电重启时发生。TPM会收到一个启动命令,这个命令用于复位TPM的易失性状态。在这种情况下,复位可以理解为将一些状态设置为指定的初始化值或者为nonce分配新的随机值。TPM复位将会建立一个新的可信平台状态。所有需要的软件都会被测量并扩展到一组复位PCR中。所有的TPM资源都会被复位到默认的初始配置状态。

TPM恢复通常平台由挂起状态恢复的时候发生,有时候挂起又叫做睡眠或者低功耗状态。因为平台是在继续执行而不是重新启动,所以是所有的状态,包括PCR值,都会被恢复。TPM恢复会将TPM恢复到因系统睡眠或者重启而掉电前的状态,因为从系统复位或者掉电的时刻起,它的可信状态就没有变化了

TPM重启通常在平台由休眠状态被唤醒的时候发生。系统休眠时会在TPM断电之前向TPM发送命令来保存状态,大部分的状态都会在TPM启动的时候恢复。例外是PCR的值,PCRTPM启动时只做了初始化,而不是状态恢复。这个操作就允许平台在启动(由休眠状态恢复)时向PCR扩展新的测量值,而不是将操作系统和应用使用的TPM状态重新恢复到PCR中。TPM重启在PCR操作上是比较特殊的,在这个过程中平台是在重新建立可信状态(会创建新的测量值),而不是恢复相关的可信状态(在这个上下文环境中可以说这些状态就是操作系统和应用程序使用的PCR值)

TPM提供两个命令来支持上述的启动事件:TPM2_ShutdownTPM2_Startup命令。关机命令通常是由操作系统在复位或者断电前执行的。

  • TPM2_Shutdown有两个选项:CLEARSTATE
  • TPM2_Startup命令通常是在用于系统初始化的固件(比如PC上的BIOS(UEFI))在做RTM(Root of Trust for Measurement)时执行的。TPM2_Startup同样也有两个选项:CLEARSTATE

以下是两个命令配合使用的情况:

  • TPM复位(重新启动):TPM2_Shutdown(CLEAR)或者没有TPM2_Shutdown命令,然后执行TPM2_Startup(CLEAR)
  • TPM重启(休眠唤醒):TPM2_Shutdown(STATE),然后执行TPM2_Startup(CLEAR)
  • TPM恢复(挂起,睡眠):TPM2_Shutdown(STATE),然后执行TPM2_Startup(STATE)

以下是命令行为的简要描述。这些命令的行为包含很多与时钟,时间,计数器,会话上下文,混合NV索引相关的细节,后续补充介绍。

  • TPM2_Shutdown(CLEAR)是平台在关机或者重启时顺序性的关机行为。TPM会将一些易失性的值保存到非易失性内存中:时钟和有顺序属性的NV索引通常会在易失性内存中有一个副本。
  • TPM2_Shutdown(STATE)通常是由于系统需要休眠或者挂起而执行的关机操作。TPM会保存之前记录的会话上下文,平台规范强制要求保存的PCR值,一些特定的NV索引标志位,以及审计相关的状态。
  • TPM2_Startup(CLEAR)会初始化TPM易失性状态,包括PCR和NV易失性状态;它还会使能三个组织架构;以及清除平台授权和Policy。
  • TPM2_Startup(STATE)仅在使用TPM2_Shutdown(STATE)命令关机之后才可以使用。此时PCR会被恢复,或者按照平台相关的规范初始化(0-15保存,16-23初始化)。
    举例来说,PCR在上述三种电源周期中的详细行为和原理概括如下:
  • 在系统重启时,也就是TPM复位,所有的PCR都必须被初始化。
  • TPM2_Startup(CLEAR)命令总是会初始化PCR的值,不考虑关机的类型。当系统由休眠状态被唤醒时,也就是TPM重启,平台会重新运行BIOS代码并做测量,所以PCR值也必须被初始化。此时使用TPM2_Startup(CLEAR)来初始化PCR,尽管在系统休眠的时候PCR的状态通过TPM2_Shutdown(STATE)被保存了。
  • 在系统由睡眠状态被唤醒时,也就是TPM恢复,PCR的值可能在断电时丢失(基本确认会丢失啊,作者是什么意思呢?)。但是因为系统在这种情况下恢复时并不会重新运行BIOS,启动,或者OS初始化代码。所以PCR的值必须被恢复。TPM2_Shutdown(STATE)命令会在系统睡眠之前保存易失性的PCR值。TPM2_Startup(STATE)命令则会恢复这些值。

相关源码

#if CC_Startup	 // Conditional expansion of this file
TPM_RC
TPM2_Startup(
	     Startup_In      *in             // IN: input parameter list
	     )
{
    STARTUP_TYPE         startup;
    BYTE                 locality = _plat__LocalityGet();
    BOOL                 OK = TRUE;    // The command needs NV update.
    RETURN_IF_NV_IS_NOT_AVAILABLE;
    // Get the flags for the current startup locality and the H-CRTM.
    // Rather than generalizing the locality setting, this code takes advantage
    // of the fact that the PC Client specification only allows Startup()
    // from locality 0 and 3. To generalize this probably would require a
    // redo of the NV space and since this is a feature that is hardly ever used
    // outside of the PC Client, this code just support the PC Client needs.
    // Input Validation
    // Check that the locality is a supported value
    if(locality != 0 && locality != 3)
	return TPM_RC_LOCALITY;
    // If there was a H-CRTM, then treat the locality as being 3
    // regardless of what the Startup() was. This is done to preserve the
    // H-CRTM PCR so that they don't get overwritten with the normal
    // PCR startup initialization. This basically means that g_StartupLocality3
    // and g_DrtmPreStartup can't both be SET at the same time.
    if(g_DrtmPreStartup)
	locality = 0;
    g_StartupLocality3 = (locality == 3);
#if USE_DA_USED
    // If there was no orderly shutdown, then there might have been a write to
    // failedTries that didn't get recorded but only if g_daUsed was SET in the
    // shutdown state
    g_daUsed = (gp.orderlyState == SU_DA_USED_VALUE);
    if(g_daUsed)
	gp.orderlyState = SU_NONE_VALUE;
#endif
    g_prevOrderlyState = gp.orderlyState; // TPM2_ShutdownState时候会设置这个值
    // If there was a proper shutdown, then the startup modifiers are in the
    // orderlyState. Turn them off in the copy.
    if(IS_ORDERLY(g_prevOrderlyState))
	g_prevOrderlyState &=  ~(PRE_STARTUP_FLAG | STARTUP_LOCALITY_3);
    // If this is a Resume,
    if(in->startupType == TPM_SU_STATE) // TPM恢复流程
	{
	    // then there must have been a prior TPM2_ShutdownState(STATE)
	    if(g_prevOrderlyState != TPM_SU_STATE) // 检查上一次OrderlyState状态
		return TPM_RCS_VALUE + RC_Startup_startupType;
	    // and the part of NV used for state save must have been recovered
	    // correctly.
	    // NOTE: if this fails, then the caller will need to do Startup(CLEAR). The
	    // code for Startup(Clear) cannot fail if the NV can't be read correctly
	    // because that would prevent the TPM from ever getting unstuck.
	    if(g_nvOk == FALSE)
		return TPM_RC_NV_UNINITIALIZED;
	    // For Resume, the H-CRTM has to be the same as the previous boot
	    if(g_DrtmPreStartup != ((gp.orderlyState & PRE_STARTUP_FLAG) != 0))
		return TPM_RCS_VALUE + RC_Startup_startupType;
	    if(g_StartupLocality3 != ((gp.orderlyState & STARTUP_LOCALITY_3) != 0))
		return TPM_RC_LOCALITY;
	}
    // Clean up the gp state
    gp.orderlyState = g_prevOrderlyState;

    // Internal Date Update
    // TPM恢复流程且nv数据有效
    if((gp.orderlyState == TPM_SU_STATE) && (g_nvOk == TRUE))
	{
	    // Always read the data that is only cleared on a Reset because this is not
	    // a reset
	    NvRead(&gr, NV_STATE_RESET_DATA, sizeof(gr));
	    if(in->startupType == TPM_SU_STATE)
	        {
	            // If this is a startup STATE (a Resume) need to read the data
	            // that is cleared on a startup CLEAR because this is not a Reset
	            // or Restart.
	            NvRead(&gc, NV_STATE_CLEAR_DATA, sizeof(gc)); // 恢复NV数据到全局变量
	            startup = SU_RESUME;
	        }
	    else
		startup = SU_RESTART;
	}
    else
	// Will do a TPM reset if Shutdown(CLEAR) and Startup(CLEAR) or no shutdown
	// or there was a failure reading the NV data.
	startup = SU_RESET // 复位模式;
    // Startup for cryptographic library. Don't do this until after the orderly
    // state has been read in from NV.
    OK = OK && CryptStartup(startup); // 初始化算法库相关服务
    // When the cryptographic library has been started, indicate that a TPM2_Startup
    // command has been received.
    OK = OK && TPMRegisterStartup(); // 初始化算法库相关服务
    // Read the platform unique value that is used as VENDOR_PERMANENT
    // authorization value
    g_platformUniqueDetails.t.size
	= (UINT16)_plat__GetUnique(1, sizeof(g_platformUniqueDetails.t.buffer),
				   g_platformUniqueDetails.t.buffer);
    // Start up subsystems
    // Start set the safe flag
    OK = OK && TimeStartup(startup);
    // Start dictionary attack subsystem
    OK = OK && DAStartup(startup);
    // Enable hierarchies
    OK = OK && HierarchyStartup(startup);
    // Restore/Initialize PCR
    OK = OK && PCRStartup(startup, locality); // 重置PCR
    // Restore/Initialize command audit information
    OK = OK && CommandAuditStartup(startup);
    // Restore the ACT
    OK = OK && ActStartup(startup);
     The following code was moved from Time.c where it made no sense
    if (OK)
	{
	    switch (startup)
		{
		  case SU_RESUME: // TPM恢复模式
		    // Resume sequence
		    gr.restartCount++;
		    break;
		  case SU_RESTART: // 重启模式
		    // Hibernate sequence
		    gr.clearCount++;
		    gr.restartCount++;
		    break;
		  default: // TPM 复位模式
		    // Reset object context ID to 0
		    gr.objectContextID = 0;
		    // Reset clearCount to 0
		    gr.clearCount = 0;
		    // Reset sequence
		    // Increase resetCount
		    gp.resetCount++;
		    // Write resetCount to NV
		    NV_SYNC_PERSISTENT(resetCount);
		    gp.totalResetCount++;
		    // We do not expect the total reset counter overflow during the life
		    // time of TPM.  if it ever happens, TPM will be put to failure mode
		    // and there is no way to recover it.
		    // The reason that there is no recovery is that we don't increment
		    // the NV totalResetCount when incrementing would make it 0. When the
		    // TPM starts up again, the old value of totalResetCount will be read
		    // and we will get right back to here with the increment failing.
#if 0    // libtpms added
		    if(gp.totalResetCount == 0)
			FAIL(FATAL_ERROR_INTERNAL);
#endif   // libtpms added
		    // Write total reset counter to NV
		    NV_SYNC_PERSISTENT(totalResetCount);
		    // Reset restartCount
		    gr.restartCount = 0;
		    break;
		}
	}
    // Initialize session table
    OK = OK && SessionStartup(startup);
    // Initialize object table
    OK = OK && ObjectStartup();
    // Initialize index/evict data.  This function clears read/write locks
    // in NV index
    OK = OK && NvEntityStartup(startup);
    // Initialize the orderly shut down flag for this cycle to SU_NONE_VALUE.
    gp.orderlyState = SU_NONE_VALUE;
    OK = OK && NV_SYNC_PERSISTENT(orderlyState);
    // This can be reset after the first completion of a TPM2_Startup() after
    // a power loss. It can probably be reset earlier but this is an OK place.
    if (OK) 
	g_powerWasLost = FALSE;
    return (OK) ? TPM_RC_SUCCESS : TPM_RC_FAILURE;
}