/********************************************************************************/ /* */ /* Process the Authorization Sessions */ /* Written by Ken Goldman */ /* IBM Thomas J. Watson Research Center */ /* */ /* Licenses and Notices */ /* */ /* 1. Copyright Licenses: */ /* */ /* - Trusted Computing Group (TCG) grants to the user of the source code in */ /* this specification (the "Source Code") a worldwide, irrevocable, */ /* nonexclusive, royalty free, copyright license to reproduce, create */ /* derivative works, distribute, display and perform the Source Code and */ /* derivative works thereof, and to grant others the rights granted herein. */ /* */ /* - The TCG grants to the user of the other parts of the specification */ /* (other than the Source Code) the rights to reproduce, distribute, */ /* display, and perform the specification solely for the purpose of */ /* developing products based on such documents. */ /* */ /* 2. Source Code Distribution Conditions: */ /* */ /* - Redistributions of Source Code must retain the above copyright licenses, */ /* this list of conditions and the following disclaimers. */ /* */ /* - Redistributions in binary form must reproduce the above copyright */ /* licenses, this list of conditions and the following disclaimers in the */ /* documentation and/or other materials provided with the distribution. */ /* */ /* 3. Disclaimers: */ /* */ /* - THE COPYRIGHT LICENSES SET FORTH ABOVE DO NOT REPRESENT ANY FORM OF */ /* LICENSE OR WAIVER, EXPRESS OR IMPLIED, BY ESTOPPEL OR OTHERWISE, WITH */ /* RESPECT TO PATENT RIGHTS HELD BY TCG MEMBERS (OR OTHER THIRD PARTIES) */ /* THAT MAY BE NECESSARY TO IMPLEMENT THIS SPECIFICATION OR OTHERWISE. */ /* Contact TCG Administration (admin@trustedcomputinggroup.org) for */ /* information on specification licensing rights available through TCG */ /* membership agreements. */ /* */ /* - THIS SPECIFICATION IS PROVIDED "AS IS" WITH NO EXPRESS OR IMPLIED */ /* WARRANTIES WHATSOEVER, INCLUDING ANY WARRANTY OF MERCHANTABILITY OR */ /* FITNESS FOR A PARTICULAR PURPOSE, ACCURACY, COMPLETENESS, OR */ /* NONINFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS, OR ANY WARRANTY */ /* OTHERWISE ARISING OUT OF ANY PROPOSAL, SPECIFICATION OR SAMPLE. */ /* */ /* - Without limitation, TCG and its members and licensors disclaim all */ /* liability, including liability for infringement of any proprietary */ /* rights, relating to use of information in this specification and to the */ /* implementation of this specification, and TCG disclaims all liability for */ /* cost of procurement of substitute goods or services, lost profits, loss */ /* of use, loss of data or any incidental, consequential, direct, indirect, */ /* or special damages, whether under contract, tort, warranty or otherwise, */ /* arising in any way out of use or reliance upon this specification or any */ /* information herein. */ /* */ /* (c) Copyright IBM Corp. and others, 2016 - 2023 */ /* */ /********************************************************************************/ //** Introduction // This file contains the subsystem that process the authorization sessions // including implementation of the Dictionary Attack logic. ExecCommand() uses // ParseSessionBuffer() to process the authorization session area of a command and // BuildResponseSession() to create the authorization session area of a response. //** Includes and Data Definitions #define SESSION_PROCESS_C #include "Tpm.h" #include "ACT.h" #include "Marshal.h" // //** Authorization Support Functions // //*** IsDAExempted() // This function indicates if a handle is exempted from DA logic. // A handle is exempted if it is: // a) a primary seed handle; // b) an object with noDA bit SET; // c) an NV Index with TPMA_NV_NO_DA bit SET; or // d) a PCR handle. // // Return Type: BOOL // TRUE(1) handle is exempted from DA logic // FALSE(0) handle is not exempted from DA logic BOOL IsDAExempted(TPM_HANDLE handle // IN: entity handle ) { BOOL result = FALSE; // switch(HandleGetType(handle)) { case TPM_HT_PERMANENT: // All permanent handles, other than TPM_RH_LOCKOUT, are exempt from // DA protection. result = (handle != TPM_RH_LOCKOUT); break; // When this function is called, a persistent object will have been loaded // into an object slot and assigned a transient handle. case TPM_HT_TRANSIENT: { TPMA_OBJECT attributes = ObjectGetPublicAttributes(handle); result = IS_ATTRIBUTE(attributes, TPMA_OBJECT, noDA); break; } case TPM_HT_NV_INDEX: { NV_INDEX* nvIndex = NvGetIndexInfo(handle, NULL); result = IS_ATTRIBUTE(nvIndex->publicArea.attributes, TPMA_NV, NO_DA); break; } case TPM_HT_PCR: // PCRs are always exempted from DA. result = TRUE; break; default: break; } return result; } //*** IncrementLockout() // This function is called after an authorization failure that involves use of // an authValue. If the entity referenced by the handle is not exempt from DA // protection, then the failedTries counter will be incremented. // // Return Type: TPM_RC // TPM_RC_AUTH_FAIL authorization failure that caused DA lockout to increment // TPM_RC_BAD_AUTH authorization failure did not cause DA lockout to // increment static TPM_RC IncrementLockout(UINT32 sessionIndex) { TPM_HANDLE handle = s_associatedHandles[sessionIndex]; TPM_HANDLE sessionHandle = s_sessionHandles[sessionIndex]; SESSION* session = NULL; // // Don't increment lockout unless the handle associated with the session // is DA protected or the session is bound to a DA protected entity. if(sessionHandle == TPM_RS_PW) { if(IsDAExempted(handle)) return TPM_RC_BAD_AUTH; } else { session = SessionGet(sessionHandle); // If the session is bound to lockout, then use that as the relevant // handle. This means that an authorization failure with a bound session // bound to lockoutAuth will take precedence over any other // lockout check if(session->attributes.isLockoutBound == SET) handle = TPM_RH_LOCKOUT; if(session->attributes.isDaBound == CLEAR && (IsDAExempted(handle) || session->attributes.includeAuth == CLEAR)) // If the handle was changed to TPM_RH_LOCKOUT, this will not return // TPM_RC_BAD_AUTH return TPM_RC_BAD_AUTH; } if(handle == TPM_RH_LOCKOUT) { pAssert(gp.lockOutAuthEnabled == TRUE); // lockout is no longer enabled gp.lockOutAuthEnabled = FALSE; // For TPM_RH_LOCKOUT, if lockoutRecovery is 0, no need to update NV since // the lockout authorization will be reset at startup. if(gp.lockoutRecovery != 0) { if(NV_IS_AVAILABLE) // Update NV. NV_SYNC_PERSISTENT(lockOutAuthEnabled); else // No NV access for now. Put the TPM in pending mode. s_DAPendingOnNV = TRUE; } } else { if(gp.recoveryTime != 0) { gp.failedTries++; if(NV_IS_AVAILABLE) // Record changes to NV. NvWrite will SET g_updateNV NV_SYNC_PERSISTENT(failedTries); else // No NV access for now. Put the TPM in pending mode. s_DAPendingOnNV = TRUE; } } // Register a DA failure and reset the timers. DARegisterFailure(handle); return TPM_RC_AUTH_FAIL; } //*** IsSessionBindEntity() // This function indicates if the entity associated with the handle is the entity, // to which this session is bound. The binding would occur by making the "bind" // parameter in TPM2_StartAuthSession() not equal to TPM_RH_NULL. The binding only // occurs if the session is an HMAC session. The bind value is a combination of // the Name and the authValue of the entity. // // Return Type: BOOL // TRUE(1) handle points to the session start entity // FALSE(0) handle does not point to the session start entity static BOOL IsSessionBindEntity( TPM_HANDLE associatedHandle, // IN: handle to be authorized SESSION* session // IN: associated session ) { TPM2B_NAME entity; // The bind value for the entity // // If the session is not bound, return FALSE. if(session->attributes.isBound) { // Compute the bind value for the entity. SessionComputeBoundEntity(associatedHandle, &entity); // Compare to the bind value in the session. return MemoryEqual2B(&entity.b, &session->u1.boundEntity.b); } return FALSE; } //*** IsPolicySessionRequired() // Checks if a policy session is required for a command. If a command requires // DUP or ADMIN role authorization, then the handle that requires that role is the // first handle in the command. This simplifies this checking. If a new command // is created that requires multiple ADMIN role authorizations, then it will // have to be special-cased in this function. // A policy session is required if: // a) the command requires the DUP role; // b) the command requires the ADMIN role and the authorized entity // is an object and its adminWithPolicy bit is SET; // c) the command requires the ADMIN role and the authorized entity // is a permanent handle or an NV Index; or // d) the authorized entity is a PCR belonging to a policy group, and // has its policy initialized // Return Type: BOOL // TRUE(1) policy session is required // FALSE(0) policy session is not required static BOOL IsPolicySessionRequired(COMMAND_INDEX commandIndex, // IN: command index UINT32 sessionIndex // IN: session index ) { AUTH_ROLE role = CommandAuthRole(commandIndex, sessionIndex); TPM_HT type = HandleGetType(s_associatedHandles[sessionIndex]); // if(role == AUTH_DUP) return TRUE; if(role == AUTH_ADMIN) { // We allow an exception for ADMIN role in a transient object. If the object // allows ADMIN role actions with authorization, then policy is not // required. For all other cases, there is no way to override the command // requirement that a policy be used if(type == TPM_HT_TRANSIENT) { OBJECT* object = HandleToObject(s_associatedHandles[sessionIndex]); if(!IS_ATTRIBUTE(object->publicArea.objectAttributes, TPMA_OBJECT, adminWithPolicy)) return FALSE; } return TRUE; } if(type == TPM_HT_PCR) { if(PCRPolicyIsAvailable(s_associatedHandles[sessionIndex])) { TPM2B_DIGEST policy; TPMI_ALG_HASH policyAlg; policyAlg = PCRGetAuthPolicy(s_associatedHandles[sessionIndex], &policy); if(policyAlg != TPM_ALG_NULL) return TRUE; } } return FALSE; } //*** IsAuthValueAvailable() // This function indicates if authValue is available and allowed for USER role // authorization of an entity. // // This function is similar to IsAuthPolicyAvailable() except that it does not // check the size of the authValue as IsAuthPolicyAvailable() does (a null // authValue is a valid authorization, but a null policy is not a valid policy). // // This function does not check that the handle reference is valid or if the entity // is in an enabled hierarchy. Those checks are assumed to have been performed // during the handle unmarshaling. // // Return Type: BOOL // TRUE(1) authValue is available // FALSE(0) authValue is not available static BOOL IsAuthValueAvailable(TPM_HANDLE handle, // IN: handle of entity COMMAND_INDEX commandIndex, // IN: command index UINT32 sessionIndex // IN: session index ) { BOOL result = FALSE; // switch(HandleGetType(handle)) { case TPM_HT_PERMANENT: switch(handle) { // At this point hierarchy availability has already been // checked so primary seed handles are always available here case TPM_RH_OWNER: case TPM_RH_ENDORSEMENT: case TPM_RH_PLATFORM: #if VENDOR_PERMANENT_AUTH_ENABLED == YES // This vendor defined handle associated with the // manufacturer's shared secret case VENDOR_PERMANENT_AUTH_HANDLE: #endif // The DA checking has been performed on LockoutAuth but we // bypass the DA logic if we are using lockout policy. The // policy would allow execution to continue an lockoutAuth // could be used, even if direct use of lockoutAuth is disabled case TPM_RH_LOCKOUT: // NullAuth is always available. case TPM_RH_NULL: result = TRUE; break; #if ACT_SUPPORT FOR_EACH_ACT(CASE_ACT_HANDLE) { // The ACT auth value is not available if the platform is disabled result = g_phEnable == SET; break; } #endif // ACT_SUPPORT default: // Otherwise authValue is not available. break; } break; case TPM_HT_TRANSIENT: // A persistent object has already been loaded and the internal // handle changed. { OBJECT* object; TPMA_OBJECT attributes; // object = HandleToObject(handle); attributes = object->publicArea.objectAttributes; // authValue is always available for a sequence object. // An alternative for this is to // SET_ATTRIBUTE(object->publicArea, TPMA_OBJECT, userWithAuth) when the // sequence is started. if(ObjectIsSequence(object)) { result = TRUE; break; } // authValue is available for an object if it has its sensitive // portion loaded and // a) userWithAuth bit is SET, or // b) ADMIN role is required if(object->attributes.publicOnly == CLEAR && (IS_ATTRIBUTE(attributes, TPMA_OBJECT, userWithAuth) || (CommandAuthRole(commandIndex, sessionIndex) == AUTH_ADMIN && !IS_ATTRIBUTE( attributes, TPMA_OBJECT, adminWithPolicy)))) result = TRUE; } break; case TPM_HT_NV_INDEX: // NV Index. { NV_REF locator; NV_INDEX* nvIndex = NvGetIndexInfo(handle, &locator); TPMA_NV nvAttributes; // pAssert(nvIndex != 0); nvAttributes = nvIndex->publicArea.attributes; if(IsWriteOperation(commandIndex)) { // AuthWrite can't be set for a PIN index if(IS_ATTRIBUTE(nvAttributes, TPMA_NV, AUTHWRITE)) result = TRUE; } else { // A "read" operation // For a PIN Index, the authValue is available as long as the // Index has been written and the pinCount is less than pinLimit if(IsNvPinFailIndex(nvAttributes) || IsNvPinPassIndex(nvAttributes)) { NV_PIN pin; if(!IS_ATTRIBUTE(nvAttributes, TPMA_NV, WRITTEN)) break; // return false // get the index values pin.intVal = NvGetUINT64Data(nvIndex, locator); if(pin.pin.pinCount < pin.pin.pinLimit) result = TRUE; } // For non-PIN Indexes, need to allow use of the authValue else if(IS_ATTRIBUTE(nvAttributes, TPMA_NV, AUTHREAD)) result = TRUE; } } break; case TPM_HT_PCR: // PCR handle. // authValue is always allowed for PCR result = TRUE; break; default: // Otherwise, authValue is not available break; } return result; } //*** IsAuthPolicyAvailable() // This function indicates if an authPolicy is available and allowed. // // This function does not check that the handle reference is valid or if the entity // is in an enabled hierarchy. Those checks are assumed to have been performed // during the handle unmarshaling. // // Return Type: BOOL // TRUE(1) authPolicy is available // FALSE(0) authPolicy is not available static BOOL IsAuthPolicyAvailable(TPM_HANDLE handle, // IN: handle of entity COMMAND_INDEX commandIndex, // IN: command index UINT32 sessionIndex // IN: session index ) { BOOL result = FALSE; // switch(HandleGetType(handle)) { case TPM_HT_PERMANENT: switch(handle) { // At this point hierarchy availability has already been checked. case TPM_RH_OWNER: if(gp.ownerPolicy.t.size != 0) result = TRUE; break; case TPM_RH_ENDORSEMENT: if(gp.endorsementPolicy.t.size != 0) result = TRUE; break; case TPM_RH_PLATFORM: if(gc.platformPolicy.t.size != 0) result = TRUE; break; #if ACT_SUPPORT # define ACT_GET_POLICY(N) \ case TPM_RH_ACT_##N: \ if(go.ACT_##N.authPolicy.t.size != 0) \ result = TRUE; \ break; FOR_EACH_ACT(ACT_GET_POLICY) #endif // ACT_SUPPORT case TPM_RH_LOCKOUT: if(gp.lockoutPolicy.t.size != 0) result = TRUE; break; default: break; } break; case TPM_HT_TRANSIENT: { // Object handle. // An evict object would already have been loaded and given a // transient object handle by this point. OBJECT* object = HandleToObject(handle); // Policy authorization is not available for an object with only // public portion loaded. if(object->attributes.publicOnly == CLEAR) { // Policy authorization is always available for an object but // is never available for a sequence. if(!ObjectIsSequence(object)) result = TRUE; } break; } case TPM_HT_NV_INDEX: // An NV Index. { NV_INDEX* nvIndex = NvGetIndexInfo(handle, NULL); TPMA_NV nvAttributes = nvIndex->publicArea.attributes; // // If the policy size is not zero, check if policy can be used. if(nvIndex->publicArea.authPolicy.t.size != 0) { // If policy session is required for this handle, always // uses policy regardless of the attributes bit setting if(IsPolicySessionRequired(commandIndex, sessionIndex)) result = TRUE; // Otherwise, the presence of the policy depends on the NV // attributes. else if(IsWriteOperation(commandIndex)) { if(IS_ATTRIBUTE(nvAttributes, TPMA_NV, POLICYWRITE)) result = TRUE; } else { if(IS_ATTRIBUTE(nvAttributes, TPMA_NV, POLICYREAD)) result = TRUE; } } } break; case TPM_HT_PCR: // PCR handle. if(PCRPolicyIsAvailable(handle)) result = TRUE; break; default: break; } return result; } //** Session Parsing Functions //*** ClearCpRpHashes() void ClearCpRpHashes(COMMAND* command) { // The macros expand according to the implemented hash algorithms. An IDE may // complain that COMMAND does not contain SHA1CpHash or SHA1RpHash because of the // complexity of the macro expansion where the data space is defined; but, if SHA1 // is implemented, it actually does and the compiler is happy. #define CLEAR_CP_HASH(HASH, Hash) command->Hash##CpHash.b.size = 0; FOR_EACH_HASH(CLEAR_CP_HASH) #define CLEAR_RP_HASH(HASH, Hash) command->Hash##RpHash.b.size = 0; FOR_EACH_HASH(CLEAR_RP_HASH) } //*** GetCpHashPointer() // Function to get a pointer to the cpHash of the command static TPM2B_DIGEST* GetCpHashPointer(COMMAND* command, TPMI_ALG_HASH hashAlg) { TPM2B_DIGEST* retVal; // // Define the macro that will expand for each implemented algorithm in the switch // statement below. #define GET_CP_HASH_POINTER(HASH, Hash) \ case ALG_##HASH##_VALUE: \ retVal = (TPM2B_DIGEST*)&command->Hash##CpHash; \ break; switch(hashAlg) { // For each implemented hash, this will expand as defined above // by GET_CP_HASH_POINTER. Your IDE may complain that // 'struct "COMMAND" has no field "SHA1CpHash"' but the compiler says // it does, so... FOR_EACH_HASH(GET_CP_HASH_POINTER) default: retVal = NULL; break; } return retVal; } //*** GetRpHashPointer() // Function to get a pointer to the RpHash of the command static TPM2B_DIGEST* GetRpHashPointer(COMMAND* command, TPMI_ALG_HASH hashAlg) { TPM2B_DIGEST* retVal; // // Define the macro that will expand for each implemented algorithm in the switch // statement below. #define GET_RP_HASH_POINTER(HASH, Hash) \ case ALG_##HASH##_VALUE: \ retVal = (TPM2B_DIGEST*)&command->Hash##RpHash; \ break; switch(hashAlg) { // For each implemented hash, this will expand as defined above // by GET_RP_HASH_POINTER. Your IDE may complain that // 'struct "COMMAND" has no field 'SHA1RpHash'" but the compiler says // it does, so... FOR_EACH_HASH(GET_RP_HASH_POINTER) default: retVal = NULL; break; } return retVal; } //*** ComputeCpHash() // This function computes the cpHash as defined in Part 2 and described in Part 1. static TPM2B_DIGEST* ComputeCpHash(COMMAND* command, // IN: command parsing structure TPMI_ALG_HASH hashAlg // IN: hash algorithm ) { UINT32 i; HASH_STATE hashState; TPM2B_NAME name; TPM2B_DIGEST* cpHash; // // cpHash = hash(commandCode [ || authName1 // [ || authName2 // [ || authName 3 ]]] // [ || parameters]) // A cpHash can contain just a commandCode only if the lone session is // an audit session. // Get pointer to the hash value cpHash = GetCpHashPointer(command, hashAlg); if(cpHash->t.size == 0) { cpHash->t.size = CryptHashStart(&hashState, hashAlg); // Add commandCode. CryptDigestUpdateInt(&hashState, sizeof(TPM_CC), command->code); // Add authNames for each of the handles. for(i = 0; i < command->handleNum; i++) CryptDigestUpdate2B(&hashState, &EntityGetName(command->handles[i], &name)->b); // Add the parameters. CryptDigestUpdate( &hashState, command->parameterSize, command->parameterBuffer); // Complete the hash. CryptHashEnd2B(&hashState, &cpHash->b); } return cpHash; } //*** GetCpHash() // This function is used to access a precomputed cpHash. static TPM2B_DIGEST* GetCpHash(COMMAND* command, TPMI_ALG_HASH hashAlg) { TPM2B_DIGEST* cpHash = GetCpHashPointer(command, hashAlg); // pAssert(cpHash->t.size != 0); return cpHash; } //*** CompareTemplateHash() // This function computes the template hash and compares it to the session // templateHash. It is the hash of the second parameter // assuming that the command is TPM2_Create(), TPM2_CreatePrimary(), or // TPM2_CreateLoaded() // Return Type: BOOL // TRUE(1) template hash equal to session->templateHash // FALSE(0) template hash not equal to session->templateHash static BOOL CompareTemplateHash(COMMAND* command, // IN: parsing structure SESSION* session // IN: session data ) { BYTE* pBuffer = command->parameterBuffer; INT32 pSize = command->parameterSize; TPM2B_DIGEST tHash; UINT16 size; // // Only try this for the three commands for which it is intended if(command->code != TPM_CC_Create && command->code != TPM_CC_CreatePrimary #if CC_CreateLoaded && command->code != TPM_CC_CreateLoaded #endif ) return FALSE; // Assume that the first parameter is a TPM2B and unmarshal the size field // Note: this will not affect the parameter buffer and size in the calling // function. if(UINT16_Unmarshal(&size, &pBuffer, &pSize) != TPM_RC_SUCCESS) return FALSE; // reduce the space in the buffer. // NOTE: this could make pSize go negative if the parameters are not correct but // the unmarshaling code does not try to unmarshal if the remaining size is // negative. pSize -= size; // Advance the pointer pBuffer += size; // Get the size of what should be the template if(UINT16_Unmarshal(&size, &pBuffer, &pSize) != TPM_RC_SUCCESS) return FALSE; // See if this is reasonable if(size > pSize) return FALSE; // Hash the template data tHash.t.size = CryptHashBlock( session->authHashAlg, size, pBuffer, sizeof(tHash.t.buffer), tHash.t.buffer); return (MemoryEqual2B(&session->u1.templateHash.b, &tHash.b)); } //*** CompareNameHash() // This function computes the name hash and compares it to the nameHash in the // session data, returning true if they are equal. BOOL CompareNameHash(COMMAND* command, // IN: main parsing structure SESSION* session // IN: session structure with nameHash ) { HASH_STATE hashState; TPM2B_DIGEST nameHash; UINT32 i; TPM2B_NAME name; // nameHash.t.size = CryptHashStart(&hashState, session->authHashAlg); // Add names. for(i = 0; i < command->handleNum; i++) CryptDigestUpdate2B(&hashState, &EntityGetName(command->handles[i], &name)->b); // Complete hash. CryptHashEnd2B(&hashState, &nameHash.b); // and compare return MemoryEqual( session->u1.nameHash.t.buffer, nameHash.t.buffer, nameHash.t.size); } //*** CompareParametersHash() // This function computes the parameters hash and compares it to the pHash in // the session data, returning true if they are equal. BOOL CompareParametersHash(COMMAND* command, // IN: main parsing structure SESSION* session // IN: session structure with pHash ) { HASH_STATE hashState; TPM2B_DIGEST pHash; // pHash.t.size = CryptHashStart(&hashState, session->authHashAlg); // Add commandCode. CryptDigestUpdateInt(&hashState, sizeof(TPM_CC), command->code); // Add the parameters. CryptDigestUpdate(&hashState, command->parameterSize, command->parameterBuffer); // Complete hash. CryptHashEnd2B(&hashState, &pHash.b); // and compare return MemoryEqual2B(&session->u1.pHash.b, &pHash.b); } //*** CheckPWAuthSession() // This function validates the authorization provided in a PWAP session. It // compares the input value to authValue of the authorized entity. Argument // sessionIndex is used to get handles handle of the referenced entities from // s_inputAuthValues[] and s_associatedHandles[]. // // Return Type: TPM_RC // TPM_RC_AUTH_FAIL authorization fails and increments DA failure // count // TPM_RC_BAD_AUTH authorization fails but DA does not apply // static TPM_RC CheckPWAuthSession( UINT32 sessionIndex // IN: index of session to be processed ) { TPM2B_AUTH authValue; TPM_HANDLE associatedHandle = s_associatedHandles[sessionIndex]; // // Strip trailing zeros from the password. MemoryRemoveTrailingZeros(&s_inputAuthValues[sessionIndex]); // Get the authValue with trailing zeros removed EntityGetAuthValue(associatedHandle, &authValue); // Success if the values are identical. if(MemoryEqual2B(&s_inputAuthValues[sessionIndex].b, &authValue.b)) { return TPM_RC_SUCCESS; } else // if the digests are not identical { // Invoke DA protection if applicable. return IncrementLockout(sessionIndex); } } //*** ComputeCommandHMAC() // This function computes the HMAC for an authorization session in a command. /*(See part 1 specification -- this tag keeps this comment from showing up in // merged document which is probably good because this comment doesn't look right. // The sessionAuth value // authHMAC := HMACsHash((sessionKey | authValue), // (pHash | nonceNewer | nonceOlder | nonceTPMencrypt-only // | nonceTPMaudit | sessionAttributes)) // Where: // HMACsHash() The HMAC algorithm using the hash algorithm specified // when the session was started. // // sessionKey A value that is computed in a protocol-dependent way, // using KDFa. When used in an HMAC or KDF, the size field // for this value is not included. // // authValue A value that is found in the sensitive area of an entity. // When used in an HMAC or KDF, the size field for this // value is not included. // // pHash Hash of the command (cpHash) using the session hash. // When using a pHash in an HMAC computation, only the // digest is used. // // nonceNewer A value that is generated by the entity using the // session. A new nonce is generated on each use of the // session. For a command, this will be nonceCaller. // When used in an HMAC or KDF, the size field is not used. // // nonceOlder A TPM2B_NONCE that was received the previous time the // session was used. For a command, this is nonceTPM. // When used in an HMAC or KDF, the size field is not used. // // nonceTPMdecrypt The nonceTPM of the decrypt session is included in // the HMAC, but only in the command. // // nonceTPMencrypt The nonceTPM of the encrypt session is included in // the HMAC but only in the command. // // sessionAttributes A byte indicating the attributes associated with the // particular use of the session. */ static TPM2B_DIGEST* ComputeCommandHMAC( COMMAND* command, // IN: primary control structure UINT32 sessionIndex, // IN: index of session to be processed TPM2B_DIGEST* hmac // OUT: authorization HMAC ) { TPM2B_TYPE(KEY, (sizeof(AUTH_VALUE) * 2)); TPM2B_KEY key; BYTE marshalBuffer[sizeof(TPMA_SESSION)]; BYTE* buffer; UINT32 marshalSize; HMAC_STATE hmacState; TPM2B_NONCE* nonceDecrypt; TPM2B_NONCE* nonceEncrypt; SESSION* session; // nonceDecrypt = NULL; nonceEncrypt = NULL; // Determine if extra nonceTPM values are going to be required. // If this is the first session (sessionIndex = 0) and it is an authorization // session that uses an HMAC, then check if additional session nonces are to be // included. if(sessionIndex == 0 && s_associatedHandles[sessionIndex] != TPM_RH_UNASSIGNED) { // If there is a decrypt session and if this is not the decrypt session, // then an extra nonce may be needed. if(s_decryptSessionIndex != UNDEFINED_INDEX && s_decryptSessionIndex != sessionIndex) { // Will add the nonce for the decrypt session. SESSION* decryptSession = SessionGet(s_sessionHandles[s_decryptSessionIndex]); nonceDecrypt = &decryptSession->nonceTPM; } // Now repeat for the encrypt session. if(s_encryptSessionIndex != UNDEFINED_INDEX && s_encryptSessionIndex != sessionIndex && s_encryptSessionIndex != s_decryptSessionIndex) { // Have to have the nonce for the encrypt session. SESSION* encryptSession = SessionGet(s_sessionHandles[s_encryptSessionIndex]); nonceEncrypt = &encryptSession->nonceTPM; } } // Continue with the HMAC processing. session = SessionGet(s_sessionHandles[sessionIndex]); // Generate HMAC key. MemoryCopy2B(&key.b, &session->sessionKey.b, sizeof(key.t.buffer)); // Check if the session has an associated handle and if the associated entity // is the one to which the session is bound. If not, add the authValue of // this entity to the HMAC key. // If the session is bound to the object or the session is a policy session // with no authValue required, do not include the authValue in the HMAC key. // Note: For a policy session, its isBound attribute is CLEARED. // // Include the entity authValue if it is needed if(session->attributes.includeAuth == SET) { TPM2B_AUTH authValue; // Get the entity authValue with trailing zeros removed EntityGetAuthValue(s_associatedHandles[sessionIndex], &authValue); // add the authValue to the HMAC key MemoryConcat2B(&key.b, &authValue.b, sizeof(key.t.buffer)); } // if the HMAC key size is 0, a NULL string HMAC is allowed if(key.t.size == 0 && s_inputAuthValues[sessionIndex].t.size == 0) { hmac->t.size = 0; return hmac; } // Start HMAC hmac->t.size = CryptHmacStart2B(&hmacState, session->authHashAlg, &key.b); // Add cpHash CryptDigestUpdate2B(&hmacState.hashState, &ComputeCpHash(command, session->authHashAlg)->b); // Add nonces as required CryptDigestUpdate2B(&hmacState.hashState, &s_nonceCaller[sessionIndex].b); CryptDigestUpdate2B(&hmacState.hashState, &session->nonceTPM.b); if(nonceDecrypt != NULL) CryptDigestUpdate2B(&hmacState.hashState, &nonceDecrypt->b); if(nonceEncrypt != NULL) CryptDigestUpdate2B(&hmacState.hashState, &nonceEncrypt->b); // Add sessionAttributes buffer = marshalBuffer; marshalSize = TPMA_SESSION_Marshal(&(s_attributes[sessionIndex]), &buffer, NULL); CryptDigestUpdate(&hmacState.hashState, marshalSize, marshalBuffer); // Complete the HMAC computation CryptHmacEnd2B(&hmacState, &hmac->b); return hmac; } //*** CheckSessionHMAC() // This function checks the HMAC of in a session. It uses ComputeCommandHMAC() // to compute the expected HMAC value and then compares the result with the // HMAC in the authorization session. The authorization is successful if they // are the same. // // If the authorizations are not the same, IncrementLockout() is called. It will // return TPM_RC_AUTH_FAIL if the failure caused the failureCount to increment. // Otherwise, it will return TPM_RC_BAD_AUTH. // // Return Type: TPM_RC // TPM_RC_AUTH_FAIL authorization failure caused failureCount increment // TPM_RC_BAD_AUTH authorization failure did not cause failureCount // increment // static TPM_RC CheckSessionHMAC( COMMAND* command, // IN: primary control structure UINT32 sessionIndex // IN: index of session to be processed ) { TPM2B_DIGEST hmac; // authHMAC for comparing // // Compute authHMAC ComputeCommandHMAC(command, sessionIndex, &hmac); // Compare the input HMAC with the authHMAC computed above. if(!MemoryEqual2B(&s_inputAuthValues[sessionIndex].b, &hmac.b)) { // If an HMAC session has a failure, invoke the anti-hammering // if it applies to the authorized entity or the session. // Otherwise, just indicate that the authorization is bad. return IncrementLockout(sessionIndex); } return TPM_RC_SUCCESS; } //*** CheckPolicyAuthSession() // This function is used to validate the authorization in a policy session. // This function performs the following comparisons to see if a policy // authorization is properly provided. The check are: // a) compare policyDigest in session with authPolicy associated with // the entity to be authorized; // b) compare timeout if applicable; // c) compare commandCode if applicable; // d) compare cpHash if applicable; and // e) see if PCR values have changed since computed. // // If all the above checks succeed, the handle is authorized. // The order of these comparisons is not important because any failure will // result in the same error code. // // Return Type: TPM_RC // TPM_RC_PCR_CHANGED PCR value is not current // TPM_RC_POLICY_FAIL policy session fails // TPM_RC_LOCALITY command locality is not allowed // TPM_RC_POLICY_CC CC doesn't match // TPM_RC_EXPIRED policy session has expired // TPM_RC_PP PP is required but not asserted // TPM_RC_NV_UNAVAILABLE NV is not available for write // TPM_RC_NV_RATE NV is rate limiting static TPM_RC CheckPolicyAuthSession( COMMAND* command, // IN: primary parsing structure UINT32 sessionIndex // IN: index of session to be processed ) { SESSION* session; TPM2B_DIGEST authPolicy; TPMI_ALG_HASH policyAlg; UINT8 locality; // // Initialize pointer to the authorization session. session = SessionGet(s_sessionHandles[sessionIndex]); // If the command is TPM2_PolicySecret(), make sure that // either password or authValue is required if(command->code == TPM_CC_PolicySecret && session->attributes.isPasswordNeeded == CLEAR && session->attributes.isAuthValueNeeded == CLEAR) return TPM_RC_MODE; // See if the PCR counter for the session is still valid. if(!SessionPCRValueIsCurrent(session)) return TPM_RC_PCR_CHANGED; // Get authPolicy. policyAlg = EntityGetAuthPolicy(s_associatedHandles[sessionIndex], &authPolicy); // Compare authPolicy. if(!MemoryEqual2B(&session->u2.policyDigest.b, &authPolicy.b)) return TPM_RC_POLICY_FAIL; // Policy is OK so check if the other factors are correct // Compare policy hash algorithm. if(policyAlg != session->authHashAlg) return TPM_RC_POLICY_FAIL; // Compare timeout. if(session->timeout != 0) { // Cannot compare time if clock stop advancing. An TPM_RC_NV_UNAVAILABLE // or TPM_RC_NV_RATE error may be returned here. This doesn't mean that // a new nonce will be created just that, because TPM time can't advance // we can't do time-based operations. RETURN_IF_NV_IS_NOT_AVAILABLE; if((session->timeout < g_time) || (session->epoch != g_timeEpoch)) return TPM_RC_EXPIRED; } // If command code is provided it must match if(session->commandCode != 0) { if(session->commandCode != command->code) return TPM_RC_POLICY_CC; } else { // If command requires a DUP or ADMIN authorization, the session must have // command code set. AUTH_ROLE role = CommandAuthRole(command->index, sessionIndex); if(role == AUTH_ADMIN || role == AUTH_DUP) return TPM_RC_POLICY_FAIL; } // Check command locality. { BYTE sessionLocality[sizeof(TPMA_LOCALITY)]; BYTE* buffer = sessionLocality; // Get existing locality setting in canonical form sessionLocality[0] = 0; TPMA_LOCALITY_Marshal(&session->commandLocality, &buffer, NULL); // See if the locality has been set if(sessionLocality[0] != 0) { // If so, get the current locality locality = _plat__LocalityGet(); if(locality < 5) { if(((sessionLocality[0] & (1 << locality)) == 0) || sessionLocality[0] > 31) return TPM_RC_LOCALITY; } else if(locality > 31) { if(sessionLocality[0] != locality) return TPM_RC_LOCALITY; } else { // Could throw an assert here but a locality error is just // as good. It just means that, whatever the locality is, it isn't // the locality requested so... return TPM_RC_LOCALITY; } } } // end of locality check // Check physical presence. if(session->attributes.isPPRequired == SET && !_plat__PhysicalPresenceAsserted()) return TPM_RC_PP; // Compare cpHash/nameHash/pHash/templateHash if defined. if(session->u1.cpHash.b.size != 0) { BOOL OK = FALSE; if(session->attributes.isCpHashDefined) // Compare cpHash. OK = MemoryEqual2B(&session->u1.cpHash.b, &ComputeCpHash(command, session->authHashAlg)->b); else if(session->attributes.isNameHashDefined) OK = CompareNameHash(command, session); else if(session->attributes.isParametersHashDefined) OK = CompareParametersHash(command, session); else if(session->attributes.isTemplateHashDefined) OK = CompareTemplateHash(command, session); if(!OK) return TPM_RCS_POLICY_FAIL; } if(session->attributes.checkNvWritten) { NV_REF locator; NV_INDEX* nvIndex; // // If this is not an NV index, the policy makes no sense so fail it. if(HandleGetType(s_associatedHandles[sessionIndex]) != TPM_HT_NV_INDEX) return TPM_RC_POLICY_FAIL; // Get the index data nvIndex = NvGetIndexInfo(s_associatedHandles[sessionIndex], &locator); // Make sure that the TPMA_WRITTEN_ATTRIBUTE has the desired state if((IS_ATTRIBUTE(nvIndex->publicArea.attributes, TPMA_NV, WRITTEN)) != (session->attributes.nvWrittenState == SET)) return TPM_RC_POLICY_FAIL; } return TPM_RC_SUCCESS; } //*** RetrieveSessionData() // This function will unmarshal the sessions in the session area of a command. The // values are placed in the arrays that are defined at the beginning of this file. // The normal unmarshaling errors are possible. // // Return Type: TPM_RC // TPM_RC_SUCCSS unmarshaled without error // TPM_RC_SIZE the number of bytes unmarshaled is not the same // as the value for authorizationSize in the command // static TPM_RC RetrieveSessionData( COMMAND* command // IN: main parsing structure for command ) { int i; TPM_RC result; SESSION* session; TPMA_SESSION sessionAttributes; TPM_HT sessionType; INT32 sessionIndex; TPM_RC errorIndex; // s_decryptSessionIndex = UNDEFINED_INDEX; s_encryptSessionIndex = UNDEFINED_INDEX; s_auditSessionIndex = UNDEFINED_INDEX; for(sessionIndex = 0; command->authSize > 0; sessionIndex++) { errorIndex = TPM_RC_S + g_rcIndex[sessionIndex]; // If maximum allowed number of sessions has been parsed, return a size // error with a session number that is larger than the number of allowed // sessions if(sessionIndex == MAX_SESSION_NUM) return TPM_RCS_SIZE + errorIndex; // make sure that the associated handle for each session starts out // unassigned s_associatedHandles[sessionIndex] = TPM_RH_UNASSIGNED; // First parameter: Session handle. result = TPMI_SH_AUTH_SESSION_Unmarshal(&s_sessionHandles[sessionIndex], &command->parameterBuffer, &command->authSize, TRUE); if(result != TPM_RC_SUCCESS) return result + TPM_RC_S + g_rcIndex[sessionIndex]; // Second parameter: Nonce. result = TPM2B_NONCE_Unmarshal(&s_nonceCaller[sessionIndex], &command->parameterBuffer, &command->authSize); if(result != TPM_RC_SUCCESS) return result + TPM_RC_S + g_rcIndex[sessionIndex]; // Third parameter: sessionAttributes. result = TPMA_SESSION_Unmarshal(&s_attributes[sessionIndex], &command->parameterBuffer, &command->authSize); if(result != TPM_RC_SUCCESS) return result + TPM_RC_S + g_rcIndex[sessionIndex]; // Fourth parameter: authValue (PW or HMAC). result = TPM2B_AUTH_Unmarshal(&s_inputAuthValues[sessionIndex], &command->parameterBuffer, &command->authSize); if(result != TPM_RC_SUCCESS) return result + errorIndex; sessionAttributes = s_attributes[sessionIndex]; if(s_sessionHandles[sessionIndex] == TPM_RS_PW) { // A PWAP session needs additional processing. // Can't have any attributes set other than continueSession bit if(IS_ATTRIBUTE(sessionAttributes, TPMA_SESSION, encrypt) || IS_ATTRIBUTE(sessionAttributes, TPMA_SESSION, decrypt) || IS_ATTRIBUTE(sessionAttributes, TPMA_SESSION, audit) || IS_ATTRIBUTE(sessionAttributes, TPMA_SESSION, auditExclusive) || IS_ATTRIBUTE(sessionAttributes, TPMA_SESSION, auditReset)) return TPM_RCS_ATTRIBUTES + errorIndex; // The nonce size must be zero. if(s_nonceCaller[sessionIndex].t.size != 0) return TPM_RCS_NONCE + errorIndex; continue; } // For not password sessions... // Find out if the session is loaded. if(!SessionIsLoaded(s_sessionHandles[sessionIndex])) return TPM_RC_REFERENCE_S0 + sessionIndex; sessionType = HandleGetType(s_sessionHandles[sessionIndex]); session = SessionGet(s_sessionHandles[sessionIndex]); // Check if the session is an HMAC/policy session. if((session->attributes.isPolicy == SET && sessionType == TPM_HT_HMAC_SESSION) || (session->attributes.isPolicy == CLEAR && sessionType == TPM_HT_POLICY_SESSION)) return TPM_RCS_HANDLE + errorIndex; // Check that this handle has not previously been used. for(i = 0; i < sessionIndex; i++) { if(s_sessionHandles[i] == s_sessionHandles[sessionIndex]) return TPM_RCS_HANDLE + errorIndex; } // If the session is used for parameter encryption or audit as well, set // the corresponding Indexes. // First process decrypt. if(IS_ATTRIBUTE(sessionAttributes, TPMA_SESSION, decrypt)) { // Check if the commandCode allows command parameter encryption. if(DecryptSize(command->index) == 0) return TPM_RCS_ATTRIBUTES + errorIndex; // Encrypt attribute can only appear in one session if(s_decryptSessionIndex != UNDEFINED_INDEX) return TPM_RCS_ATTRIBUTES + errorIndex; // Can't decrypt if the session's symmetric algorithm is TPM_ALG_NULL if(session->symmetric.algorithm == TPM_ALG_NULL) return TPM_RCS_SYMMETRIC + errorIndex; // All checks passed, so set the index for the session used to decrypt // a command parameter. s_decryptSessionIndex = sessionIndex; } // Now process encrypt. if(IS_ATTRIBUTE(sessionAttributes, TPMA_SESSION, encrypt)) { // Check if the commandCode allows response parameter encryption. if(EncryptSize(command->index) == 0) return TPM_RCS_ATTRIBUTES + errorIndex; // Encrypt attribute can only appear in one session. if(s_encryptSessionIndex != UNDEFINED_INDEX) return TPM_RCS_ATTRIBUTES + errorIndex; // Can't encrypt if the session's symmetric algorithm is TPM_ALG_NULL if(session->symmetric.algorithm == TPM_ALG_NULL) return TPM_RCS_SYMMETRIC + errorIndex; // All checks passed, so set the index for the session used to encrypt // a response parameter. s_encryptSessionIndex = sessionIndex; } // At last process audit. if(IS_ATTRIBUTE(sessionAttributes, TPMA_SESSION, audit)) { // Audit attribute can only appear in one session. if(s_auditSessionIndex != UNDEFINED_INDEX) return TPM_RCS_ATTRIBUTES + errorIndex; // An audit session can not be policy session. if(HandleGetType(s_sessionHandles[sessionIndex]) == TPM_HT_POLICY_SESSION) return TPM_RCS_ATTRIBUTES + errorIndex; // If this is a reset of the audit session, or the first use // of the session as an audit session, it doesn't matter what // the exclusive state is. The session will become exclusive. if(!IS_ATTRIBUTE(sessionAttributes, TPMA_SESSION, auditReset) && session->attributes.isAudit == SET) { // Not first use or reset. If auditExlusive is SET, then this // session must be the current exclusive session. if(IS_ATTRIBUTE(sessionAttributes, TPMA_SESSION, auditExclusive) && g_exclusiveAuditSession != s_sessionHandles[sessionIndex]) return TPM_RC_EXCLUSIVE; } s_auditSessionIndex = sessionIndex; } // Initialize associated handle as undefined. This will be changed when // the handles are processed. s_associatedHandles[sessionIndex] = TPM_RH_UNASSIGNED; } command->sessionNum = sessionIndex; return TPM_RC_SUCCESS; } //*** CheckLockedOut() // This function checks to see if the TPM is in lockout. This function should only // be called if the entity being checked is subject to DA protection. The TPM // is in lockout if the NV is not available and a DA write is pending. Otherwise // the TPM is locked out if checking for lockoutAuth ('lockoutAuthCheck' == TRUE) // and use of lockoutAuth is disabled, or 'failedTries' >= 'maxTries' // Return Type: TPM_RC // TPM_RC_NV_RATE NV is rate limiting // TPM_RC_NV_UNAVAILABLE NV is not available at this time // TPM_RC_LOCKOUT TPM is in lockout static TPM_RC CheckLockedOut( BOOL lockoutAuthCheck // IN: TRUE if checking is for lockoutAuth ) { // If NV is unavailable, and current cycle state recorded in NV is not // SU_NONE_VALUE, refuse to check any authorization because we would // not be able to handle a DA failure. if(!NV_IS_AVAILABLE && NV_IS_ORDERLY) return g_NvStatus; // Check if DA info needs to be updated in NV. if(s_DAPendingOnNV) { // If NV is accessible, RETURN_IF_NV_IS_NOT_AVAILABLE; // ... write the pending DA data and proceed. NV_SYNC_PERSISTENT(lockOutAuthEnabled); NV_SYNC_PERSISTENT(failedTries); s_DAPendingOnNV = FALSE; } // Lockout is in effect if checking for lockoutAuth and use of lockoutAuth // is disabled... if(lockoutAuthCheck) { if(gp.lockOutAuthEnabled == FALSE) return TPM_RC_LOCKOUT; } else { // ... or if the number of failed tries has been maxed out. if(gp.failedTries >= gp.maxTries) return TPM_RC_LOCKOUT; #if USE_DA_USED // If the daUsed flag is not SET, then no DA validation until the // daUsed state is written to NV if(!g_daUsed) { RETURN_IF_NV_IS_NOT_AVAILABLE; g_daUsed = TRUE; gp.orderlyState = SU_DA_USED_VALUE; NV_SYNC_PERSISTENT(orderlyState); return TPM_RC_RETRY; } #endif } return TPM_RC_SUCCESS; } //*** CheckAuthSession() // This function checks that the authorization session properly authorizes the // use of the associated handle. // // Return Type: TPM_RC // TPM_RC_LOCKOUT entity is protected by DA and TPM is in // lockout, or TPM is locked out on NV update // pending on DA parameters // // TPM_RC_PP Physical Presence is required but not provided // TPM_RC_AUTH_FAIL HMAC or PW authorization failed // with DA side-effects (can be a policy session) // // TPM_RC_BAD_AUTH HMAC or PW authorization failed without DA // side-effects (can be a policy session) // // TPM_RC_POLICY_FAIL if policy session fails // TPM_RC_POLICY_CC command code of policy was wrong // TPM_RC_EXPIRED the policy session has expired // TPM_RC_PCR // TPM_RC_AUTH_UNAVAILABLE authValue or authPolicy unavailable static TPM_RC CheckAuthSession( COMMAND* command, // IN: primary parsing structure UINT32 sessionIndex // IN: index of session to be processed ) { TPM_RC result = TPM_RC_SUCCESS; SESSION* session = NULL; TPM_HANDLE sessionHandle = s_sessionHandles[sessionIndex]; TPM_HANDLE associatedHandle = s_associatedHandles[sessionIndex]; TPM_HT sessionHandleType = HandleGetType(sessionHandle); BOOL authUsed; // pAssert(sessionHandle != TPM_RH_UNASSIGNED); // Take care of physical presence if(associatedHandle == TPM_RH_PLATFORM) { // If the physical presence is required for this command, check for PP // assertion. If it isn't asserted, no point going any further. if(PhysicalPresenceIsRequired(command->index) && !_plat__PhysicalPresenceAsserted()) return TPM_RC_PP; } if(sessionHandle != TPM_RS_PW) { session = SessionGet(sessionHandle); // Set includeAuth to indicate if DA checking will be required and if the // authValue will be included in any HMAC. if(sessionHandleType == TPM_HT_POLICY_SESSION) { // For a policy session, will check the DA status of the entity if either // isAuthValueNeeded or isPasswordNeeded is SET. session->attributes.includeAuth = session->attributes.isAuthValueNeeded || session->attributes.isPasswordNeeded; } else { // For an HMAC session, need to check unless the session // is bound. session->attributes.includeAuth = !IsSessionBindEntity(s_associatedHandles[sessionIndex], session); } authUsed = session->attributes.includeAuth; } else // Password session authUsed = TRUE; // If the authorization session is going to use an authValue, then make sure // that access to that authValue isn't locked out. if(authUsed) { // See if entity is subject to lockout. if(!IsDAExempted(associatedHandle)) { // See if in lockout result = CheckLockedOut(associatedHandle == TPM_RH_LOCKOUT); if(result != TPM_RC_SUCCESS) return result; } } // Policy or HMAC+PW? if(sessionHandleType != TPM_HT_POLICY_SESSION) { // for non-policy session make sure that a policy session is not required if(IsPolicySessionRequired(command->index, sessionIndex)) return TPM_RC_AUTH_TYPE; // The authValue must be available. // Note: The authValue is going to be "used" even if it is an EmptyAuth. // and the session is bound. if(!IsAuthValueAvailable(associatedHandle, command->index, sessionIndex)) return TPM_RC_AUTH_UNAVAILABLE; } else { // ... see if the entity has a policy, ... // Note: IsAuthPolicyAvalable will return FALSE if the sensitive area of the // object is not loaded if(!IsAuthPolicyAvailable(associatedHandle, command->index, sessionIndex)) return TPM_RC_AUTH_UNAVAILABLE; // ... and check the policy session. result = CheckPolicyAuthSession(command, sessionIndex); if(result != TPM_RC_SUCCESS) return result; } // Check authorization according to the type if((TPM_RS_PW == sessionHandle) || (session->attributes.isPasswordNeeded == SET)) result = CheckPWAuthSession(sessionIndex); else result = CheckSessionHMAC(command, sessionIndex); // Do processing for PIN Indexes are only three possibilities for 'result' at // this point: TPM_RC_SUCCESS, TPM_RC_AUTH_FAIL, and TPM_RC_BAD_AUTH. // For all these cases, we would have to process a PIN index if the // authValue of the index was used for authorization. if((TPM_HT_NV_INDEX == HandleGetType(associatedHandle)) && authUsed) { NV_REF locator; NV_INDEX* nvIndex = NvGetIndexInfo(associatedHandle, &locator); NV_PIN pinData; TPMA_NV nvAttributes; // pAssert(nvIndex != NULL); nvAttributes = nvIndex->publicArea.attributes; // If this is a PIN FAIL index and the value has been written // then we can update the counter (increment or clear) if(IsNvPinFailIndex(nvAttributes) && IS_ATTRIBUTE(nvAttributes, TPMA_NV, WRITTEN)) { pinData.intVal = NvGetUINT64Data(nvIndex, locator); if(result != TPM_RC_SUCCESS) pinData.pin.pinCount++; else pinData.pin.pinCount = 0; NvWriteUINT64Data(nvIndex, pinData.intVal); } // If this is a PIN PASS Index, increment if we have used the // authorization value. // NOTE: If the counter has already hit the limit, then we // would not get here because the authorization value would not // be available and the TPM would have returned before it gets here else if(IsNvPinPassIndex(nvAttributes) && IS_ATTRIBUTE(nvAttributes, TPMA_NV, WRITTEN) && result == TPM_RC_SUCCESS) { // If the access is valid, then increment the use counter pinData.intVal = NvGetUINT64Data(nvIndex, locator); pinData.pin.pinCount++; NvWriteUINT64Data(nvIndex, pinData.intVal); } } return result; } #if CC_GetCommandAuditDigest //*** CheckCommandAudit() // This function is called before the command is processed if audit is enabled // for the command. It will check to see if the audit can be performed and // will ensure that the cpHash is available for the audit. // Return Type: TPM_RC // TPM_RC_NV_UNAVAILABLE NV is not available for write // TPM_RC_NV_RATE NV is rate limiting static TPM_RC CheckCommandAudit(COMMAND* command) { // If the audit digest is clear and command audit is required, NV must be // available so that TPM2_GetCommandAuditDigest() is able to increment // audit counter. If NV is not available, the function bails out to prevent // the TPM from attempting an operation that would fail anyway. if(gr.commandAuditDigest.t.size == 0 || GetCommandCode(command->index) == TPM_CC_GetCommandAuditDigest) { RETURN_IF_NV_IS_NOT_AVAILABLE; } // Make sure that the cpHash is computed for the algorithm ComputeCpHash(command, gp.auditHashAlg); return TPM_RC_SUCCESS; } #endif //*** ParseSessionBuffer() // This function is the entry function for command session processing. // It iterates sessions in session area and reports if the required authorization // has been properly provided. It also processes audit session and passes the // information of encryption sessions to parameter encryption module. // // Return Type: TPM_RC // various parsing failure or authorization failure // TPM_RC ParseSessionBuffer(COMMAND* command // IN: the structure that contains ) { TPM_RC result; UINT32 i; INT32 size = 0; TPM2B_AUTH extraKey; UINT32 sessionIndex; TPM_RC errorIndex; SESSION* session = NULL; // // Check if a command allows any session in its session area. if(!IsSessionAllowed(command->index)) return TPM_RC_AUTH_CONTEXT; // Default-initialization. command->sessionNum = 0; result = RetrieveSessionData(command); if(result != TPM_RC_SUCCESS) return result; // There is no command in the TPM spec that has more handles than // MAX_SESSION_NUM. pAssert(command->handleNum <= MAX_SESSION_NUM); // Associate the session with an authorization handle. for(i = 0; i < command->handleNum; i++) { if(CommandAuthRole(command->index, i) != AUTH_NONE) { // If the received session number is less than the number of handles // that requires authorization, an error should be returned. // Note: for all the TPM 2.0 commands, handles requiring // authorization come first in a command input and there are only ever // two values requiring authorization if(i > (command->sessionNum - 1)) return TPM_RC_AUTH_MISSING; // Record the handle associated with the authorization session s_associatedHandles[i] = HierarchyNormalizeHandle(command->handles[i]); } } // Consistency checks are done first to avoid authorization failure when the // command will not be executed anyway. for(sessionIndex = 0; sessionIndex < command->sessionNum; sessionIndex++) { errorIndex = TPM_RC_S + g_rcIndex[sessionIndex]; // PW session must be an authorization session if(s_sessionHandles[sessionIndex] == TPM_RS_PW) { if(s_associatedHandles[sessionIndex] == TPM_RH_UNASSIGNED) return TPM_RCS_HANDLE + errorIndex; // a password session can't be audit, encrypt or decrypt if(IS_ATTRIBUTE(s_attributes[sessionIndex], TPMA_SESSION, audit) || IS_ATTRIBUTE(s_attributes[sessionIndex], TPMA_SESSION, encrypt) || IS_ATTRIBUTE(s_attributes[sessionIndex], TPMA_SESSION, decrypt)) return TPM_RCS_ATTRIBUTES + errorIndex; session = NULL; } else { session = SessionGet(s_sessionHandles[sessionIndex]); // A trial session can not appear in session area, because it cannot // be used for authorization, audit or encrypt/decrypt. if(session->attributes.isTrialPolicy == SET) return TPM_RCS_ATTRIBUTES + errorIndex; // See if the session is bound to a DA protected entity // NOTE: Since a policy session is never bound, a policy is still // usable even if the object is DA protected and the TPM is in // lockout. if(session->attributes.isDaBound == SET) { result = CheckLockedOut(session->attributes.isLockoutBound == SET); if(result != TPM_RC_SUCCESS) return result; } // If this session is for auditing, make sure the cpHash is computed. if(IS_ATTRIBUTE(s_attributes[sessionIndex], TPMA_SESSION, audit)) ComputeCpHash(command, session->authHashAlg); } // if the session has an associated handle, check the authorization if(s_associatedHandles[sessionIndex] != TPM_RH_UNASSIGNED) { result = CheckAuthSession(command, sessionIndex); if(result != TPM_RC_SUCCESS) return RcSafeAddToResult(result, errorIndex); } else { // a session that is not for authorization must either be encrypt, // decrypt, or audit if(!IS_ATTRIBUTE(s_attributes[sessionIndex], TPMA_SESSION, audit) && !IS_ATTRIBUTE(s_attributes[sessionIndex], TPMA_SESSION, encrypt) && !IS_ATTRIBUTE(s_attributes[sessionIndex], TPMA_SESSION, decrypt)) return TPM_RCS_ATTRIBUTES + errorIndex; // no authValue included in any of the HMAC computations pAssert(session != NULL); session->attributes.includeAuth = CLEAR; // check HMAC for encrypt/decrypt/audit only sessions result = CheckSessionHMAC(command, sessionIndex); if(result != TPM_RC_SUCCESS) return RcSafeAddToResult(result, errorIndex); } } #if CC_GetCommandAuditDigest // Check if the command should be audited. Need to do this before any parameter // encryption so that the cpHash for the audit is correct if(CommandAuditIsRequired(command->index)) { result = CheckCommandAudit(command); if(result != TPM_RC_SUCCESS) return result; // No session number to reference } #endif // Decrypt the first parameter if applicable. This should be the last operation // in session processing. // If the encrypt session is associated with a handle and the handle's // authValue is available, then authValue is concatenated with sessionKey to // generate encryption key, no matter if the handle is the session bound entity // or not. if(s_decryptSessionIndex != UNDEFINED_INDEX) { // If this is an authorization session, include the authValue in the // generation of the decryption key if(s_associatedHandles[s_decryptSessionIndex] != TPM_RH_UNASSIGNED) { EntityGetAuthValue(s_associatedHandles[s_decryptSessionIndex], &extraKey); } else { extraKey.b.size = 0; } size = DecryptSize(command->index); result = CryptParameterDecryption(s_sessionHandles[s_decryptSessionIndex], &s_nonceCaller[s_decryptSessionIndex].b, command->parameterSize, (UINT16)size, &extraKey, command->parameterBuffer); if(result != TPM_RC_SUCCESS) return RcSafeAddToResult(result, TPM_RC_S + g_rcIndex[s_decryptSessionIndex]); } return TPM_RC_SUCCESS; } //*** CheckAuthNoSession() // Function to process a command with no session associated. // The function makes sure all the handles in the command require no authorization. // // Return Type: TPM_RC // TPM_RC_AUTH_MISSING failure - one or more handles require // authorization TPM_RC CheckAuthNoSession(COMMAND* command // IN: command parsing structure ) { UINT32 i; #if CC_GetCommandAuditDigest TPM_RC result = TPM_RC_SUCCESS; #endif // // Check if the command requires authorization for(i = 0; i < command->handleNum; i++) { if(CommandAuthRole(command->index, i) != AUTH_NONE) return TPM_RC_AUTH_MISSING; } #if CC_GetCommandAuditDigest // Check if the command should be audited. if(CommandAuditIsRequired(command->index)) { result = CheckCommandAudit(command); if(result != TPM_RC_SUCCESS) return result; } #endif // Initialize number of sessions to be 0 command->sessionNum = 0; return TPM_RC_SUCCESS; } //** Response Session Processing //*** Introduction // // The following functions build the session area in a response and handle // the audit sessions (if present). // //*** ComputeRpHash() // Function to compute rpHash (Response Parameter Hash). The rpHash is only // computed if there is an HMAC authorization session and the return code is // TPM_RC_SUCCESS. static TPM2B_DIGEST* ComputeRpHash( COMMAND* command, // IN: command structure TPM_ALG_ID hashAlg // IN: hash algorithm to compute rpHash ) { TPM2B_DIGEST* rpHash = GetRpHashPointer(command, hashAlg); HASH_STATE hashState; // if(rpHash->t.size == 0) { // rpHash := hash(responseCode || commandCode || parameters) // Initiate hash creation. rpHash->t.size = CryptHashStart(&hashState, hashAlg); // Add hash constituents. CryptDigestUpdateInt(&hashState, sizeof(TPM_RC), TPM_RC_SUCCESS); CryptDigestUpdateInt(&hashState, sizeof(TPM_CC), command->code); CryptDigestUpdate( &hashState, command->parameterSize, command->parameterBuffer); // Complete hash computation. CryptHashEnd2B(&hashState, &rpHash->b); } return rpHash; } //*** InitAuditSession() // This function initializes the audit data in an audit session. static void InitAuditSession(SESSION* session // session to be initialized ) { // Mark session as an audit session. session->attributes.isAudit = SET; // Audit session can not be bound. session->attributes.isBound = CLEAR; // Size of the audit log is the size of session hash algorithm digest. session->u2.auditDigest.t.size = CryptHashGetDigestSize(session->authHashAlg); // Set the original digest value to be 0. MemorySet(&session->u2.auditDigest.t.buffer, 0, session->u2.auditDigest.t.size); return; } //*** UpdateAuditDigest // Function to update an audit digest static void UpdateAuditDigest( COMMAND* command, TPMI_ALG_HASH hashAlg, TPM2B_DIGEST* digest) { HASH_STATE hashState; TPM2B_DIGEST* cpHash = GetCpHash(command, hashAlg); TPM2B_DIGEST* rpHash = ComputeRpHash(command, hashAlg); // pAssert(cpHash != NULL); // digestNew := hash (digestOld || cpHash || rpHash) // Start hash computation. digest->t.size = CryptHashStart(&hashState, hashAlg); // Add old digest. CryptDigestUpdate2B(&hashState, &digest->b); // Add cpHash CryptDigestUpdate2B(&hashState, &cpHash->b); // Add rpHash CryptDigestUpdate2B(&hashState, &rpHash->b); // Finalize the hash. CryptHashEnd2B(&hashState, &digest->b); } //*** Audit() //This function updates the audit digest in an audit session. static void Audit(COMMAND* command, // IN: primary control structure SESSION* auditSession // IN: loaded audit session ) { UpdateAuditDigest( command, auditSession->authHashAlg, &auditSession->u2.auditDigest); return; } #if CC_GetCommandAuditDigest //*** CommandAudit() // This function updates the command audit digest. static void CommandAudit(COMMAND* command // IN: ) { // If the digest.size is one, it indicates the special case of changing // the audit hash algorithm. For this case, no audit is done on exit. // NOTE: When the hash algorithm is changed, g_updateNV is set in order to // force an update to the NV on exit so that the change in digest will // be recorded. So, it is safe to exit here without setting any flags // because the digest change will be written to NV when this code exits. if(gr.commandAuditDigest.t.size == 1) { gr.commandAuditDigest.t.size = 0; return; } // If the digest size is zero, need to start a new digest and increment // the audit counter. if(gr.commandAuditDigest.t.size == 0) { gr.commandAuditDigest.t.size = CryptHashGetDigestSize(gp.auditHashAlg); MemorySet(gr.commandAuditDigest.t.buffer, 0, gr.commandAuditDigest.t.size); // Bump the counter and save its value to NV. gp.auditCounter++; NV_SYNC_PERSISTENT(auditCounter); } UpdateAuditDigest(command, gp.auditHashAlg, &gr.commandAuditDigest); return; } #endif //*** UpdateAuditSessionStatus() // This function updates the internal audit related states of a session. It will: // a) initialize the session as audit session and set it to be exclusive if this // is the first time it is used for audit or audit reset was requested; // b) report exclusive audit session; // c) extend audit log; and // d) clear exclusive audit session if no audit session found in the command. static void UpdateAuditSessionStatus( COMMAND* command // IN: primary control structure ) { UINT32 i; TPM_HANDLE auditSession = TPM_RH_UNASSIGNED; // // Iterate through sessions for(i = 0; i < command->sessionNum; i++) { SESSION* session; // // PW session do not have a loaded session and can not be an audit // session either. Skip it. if(s_sessionHandles[i] == TPM_RS_PW) continue; session = SessionGet(s_sessionHandles[i]); // If a session is used for audit if(IS_ATTRIBUTE(s_attributes[i], TPMA_SESSION, audit)) { // An audit session has been found auditSession = s_sessionHandles[i]; // If the session has not been an audit session yet, or // the auditSetting bits indicate a reset, initialize it and set // it to be the exclusive session if(session->attributes.isAudit == CLEAR || IS_ATTRIBUTE(s_attributes[i], TPMA_SESSION, auditReset)) { InitAuditSession(session); g_exclusiveAuditSession = auditSession; } else { // Check if the audit session is the current exclusive audit // session and, if not, clear previous exclusive audit session. if(g_exclusiveAuditSession != auditSession) g_exclusiveAuditSession = TPM_RH_UNASSIGNED; } // Report audit session exclusivity. if(g_exclusiveAuditSession == auditSession) { SET_ATTRIBUTE(s_attributes[i], TPMA_SESSION, auditExclusive); } else { CLEAR_ATTRIBUTE(s_attributes[i], TPMA_SESSION, auditExclusive); } // Extend audit log. Audit(command, session); } } // If no audit session is found in the command, and the command allows // a session then, clear the current exclusive // audit session. if(auditSession == TPM_RH_UNASSIGNED && IsSessionAllowed(command->index)) { g_exclusiveAuditSession = TPM_RH_UNASSIGNED; } return; } //*** ComputeResponseHMAC() // Function to compute HMAC for authorization session in a response. /*(See part 1 specification) // Function: Compute HMAC for response sessions // The sessionAuth value // authHMAC := HMACsHASH((sessionAuth | authValue), // (pHash | nonceTPM | nonceCaller | sessionAttributes)) // Where: // HMACsHASH() The HMAC algorithm using the hash algorithm specified when // the session was started. // // sessionAuth A TPMB_MEDIUM computed in a protocol-dependent way, using // KDFa. In an HMAC or KDF, only sessionAuth.buffer is used. // // authValue A TPM2B_AUTH that is found in the sensitive area of an // object. In an HMAC or KDF, only authValue.buffer is used // and all trailing zeros are removed. // // pHash Response parameters (rpHash) using the session hash. When // using a pHash in an HMAC computation, both the algorithm ID // and the digest are included. // // nonceTPM A TPM2B_NONCE that is generated by the entity using the // session. In an HMAC or KDF, only nonceTPM.buffer is used. // // nonceCaller a TPM2B_NONCE that was received the previous time the // session was used. In an HMAC or KDF, only // nonceCaller.buffer is used. // // sessionAttributes A TPMA_SESSION that indicates the attributes associated // with a particular use of the session. */ static void ComputeResponseHMAC( COMMAND* command, // IN: command structure UINT32 sessionIndex, // IN: session index to be processed SESSION* session, // IN: loaded session TPM2B_DIGEST* hmac // OUT: authHMAC ) { TPM2B_TYPE(KEY, (sizeof(AUTH_VALUE) * 2)); TPM2B_KEY key; // HMAC key BYTE marshalBuffer[sizeof(TPMA_SESSION)]; BYTE* buffer; UINT32 marshalSize; HMAC_STATE hmacState; TPM2B_DIGEST* rpHash = ComputeRpHash(command, session->authHashAlg); // // Generate HMAC key MemoryCopy2B(&key.b, &session->sessionKey.b, sizeof(key.t.buffer)); // Add the object authValue if required if(session->attributes.includeAuth == SET) { // Note: includeAuth may be SET for a policy that is used in // UndefineSpaceSpecial(). At this point, the Index has been deleted // so the includeAuth will have no meaning. However, the // s_associatedHandles[] value for the session is now set to TPM_RH_NULL so // this will return the authValue associated with TPM_RH_NULL and that is // and empty buffer. TPM2B_AUTH authValue; // // Get the authValue with trailing zeros removed EntityGetAuthValue(s_associatedHandles[sessionIndex], &authValue); // Add it to the key MemoryConcat2B(&key.b, &authValue.b, sizeof(key.t.buffer)); } // if the HMAC key size is 0, the response HMAC is computed according to the // input HMAC if(key.t.size == 0 && s_inputAuthValues[sessionIndex].t.size == 0) { hmac->t.size = 0; return; } // Start HMAC computation. hmac->t.size = CryptHmacStart2B(&hmacState, session->authHashAlg, &key.b); // Add hash components. CryptDigestUpdate2B(&hmacState.hashState, &rpHash->b); CryptDigestUpdate2B(&hmacState.hashState, &session->nonceTPM.b); CryptDigestUpdate2B(&hmacState.hashState, &s_nonceCaller[sessionIndex].b); // Add session attributes. buffer = marshalBuffer; marshalSize = TPMA_SESSION_Marshal(&s_attributes[sessionIndex], &buffer, NULL); CryptDigestUpdate(&hmacState.hashState, marshalSize, marshalBuffer); // Finalize HMAC. CryptHmacEnd2B(&hmacState, &hmac->b); return; } //*** UpdateInternalSession() // This function updates internal sessions by: // a) restarting session time; and // b) clearing a policy session since nonce is rolling. static void UpdateInternalSession(SESSION* session, // IN: the session structure UINT32 i // IN: session number ) { // If nonce is rolling in a policy session, the policy related data // will be re-initialized. if(HandleGetType(s_sessionHandles[i]) == TPM_HT_POLICY_SESSION && IS_ATTRIBUTE(s_attributes[i], TPMA_SESSION, continueSession)) { // When the nonce rolls it starts a new timing interval for the // policy session. SessionResetPolicyData(session); SessionSetStartTime(session); } return; } //*** BuildSingleResponseAuth() // Function to compute response HMAC value for a policy or HMAC session. static TPM2B_NONCE* BuildSingleResponseAuth( COMMAND* command, // IN: command structure UINT32 sessionIndex, // IN: session index to be processed TPM2B_AUTH* auth // OUT: authHMAC ) { // Fill in policy/HMAC based session response. SESSION* session = SessionGet(s_sessionHandles[sessionIndex]); // // If the session is a policy session with isPasswordNeeded SET, the // authorization field is empty. if(HandleGetType(s_sessionHandles[sessionIndex]) == TPM_HT_POLICY_SESSION && session->attributes.isPasswordNeeded == SET) auth->t.size = 0; else // Compute response HMAC. ComputeResponseHMAC(command, sessionIndex, session, auth); UpdateInternalSession(session, sessionIndex); return &session->nonceTPM; } //*** UpdateAllNonceTPM() // Updates TPM nonce for all sessions in command. static void UpdateAllNonceTPM(COMMAND* command // IN: controlling structure ) { UINT32 i; SESSION* session; // for(i = 0; i < command->sessionNum; i++) { // If not a PW session, compute the new nonceTPM. if(s_sessionHandles[i] != TPM_RS_PW) { session = SessionGet(s_sessionHandles[i]); // Update nonceTPM in both internal session and response. CryptRandomGenerate(session->nonceTPM.t.size, session->nonceTPM.t.buffer); } } return; } //*** BuildResponseSession() // Function to build Session buffer in a response. The authorization data is added // to the end of command->responseBuffer. The size of the authorization area is // accumulated in command->authSize. // When this is called, command->responseBuffer is pointing at the next location // in the response buffer to be filled. This is where the authorization sessions // will go, if any. command->parameterSize is the number of bytes that have been // marshaled as parameters in the output buffer. TPM_RC BuildResponseSession(COMMAND* command // IN: structure that has relevant command // information ) { TPM_RC result = TPM_RC_SUCCESS; pAssert(command->authSize == 0); // Reset the parameter buffer to point to the start of the parameters so that // there is a starting point for any rpHash that might be generated and so there // is a place where parameter encryption would start command->parameterBuffer = command->responseBuffer - command->parameterSize; // Session nonces should be updated before parameter encryption if(command->tag == TPM_ST_SESSIONS) { UpdateAllNonceTPM(command); // Encrypt first parameter if applicable. Parameter encryption should // happen after nonce update and before any rpHash is computed. // If the encrypt session is associated with a handle, the authValue of // this handle will be concatenated with sessionKey to generate // encryption key, no matter if the handle is the session bound entity // or not. The authValue is added to sessionKey only when the authValue // is available. if(s_encryptSessionIndex != UNDEFINED_INDEX) { UINT32 size; TPM2B_AUTH extraKey; // extraKey.b.size = 0; // If this is an authorization session, include the authValue in the // generation of the encryption key if(s_associatedHandles[s_encryptSessionIndex] != TPM_RH_UNASSIGNED) { EntityGetAuthValue(s_associatedHandles[s_encryptSessionIndex], &extraKey); } size = EncryptSize(command->index); // This function operates on internally-generated data that is // expected to be well-formed for parameter encryption. // In the event that there is a bug elsewhere in the code and the // input data is not well-formed, CryptParameterEncryption will // put the TPM into failure mode instead of allowing the out-of- // band write. CryptParameterEncryption(s_sessionHandles[s_encryptSessionIndex], &s_nonceCaller[s_encryptSessionIndex].b, command->parameterSize, (UINT16)size, &extraKey, command->parameterBuffer); if(g_inFailureMode) { result = TPM_RC_FAILURE; goto Cleanup; } } } // Audit sessions should be processed regardless of the tag because // a command with no session may cause a change of the exclusivity state. UpdateAuditSessionStatus(command); #if CC_GetCommandAuditDigest // Command Audit if(CommandAuditIsRequired(command->index)) CommandAudit(command); #endif // Process command with sessions. if(command->tag == TPM_ST_SESSIONS) { UINT32 i; // pAssert(command->sessionNum > 0); // Iterate over each session in the command session area, and create // corresponding sessions for response. for(i = 0; i < command->sessionNum; i++) { TPM2B_NONCE* nonceTPM; TPM2B_DIGEST responseAuth; // Make sure that continueSession is SET on any Password session. // This makes it marginally easier for the management software // to keep track of the closed sessions. if(s_sessionHandles[i] == TPM_RS_PW) { SET_ATTRIBUTE(s_attributes[i], TPMA_SESSION, continueSession); responseAuth.t.size = 0; nonceTPM = (TPM2B_NONCE*)&responseAuth; } else { // Compute the response HMAC and get a pointer to the nonce used. // This function will also update the values if needed. Note, the nonceTPM = BuildSingleResponseAuth(command, i, &responseAuth); } command->authSize += TPM2B_NONCE_Marshal(nonceTPM, &command->responseBuffer, NULL); command->authSize += TPMA_SESSION_Marshal( &s_attributes[i], &command->responseBuffer, NULL); command->authSize += TPM2B_DIGEST_Marshal(&responseAuth, &command->responseBuffer, NULL); if(!IS_ATTRIBUTE(s_attributes[i], TPMA_SESSION, continueSession)) SessionFlush(s_sessionHandles[i]); } } Cleanup: return result; } //*** SessionRemoveAssociationToHandle() // This function deals with the case where an entity associated with an authorization // is deleted during command processing. The primary use of this is to support // UndefineSpaceSpecial(). void SessionRemoveAssociationToHandle(TPM_HANDLE handle) { UINT32 i; // for(i = 0; i < MAX_SESSION_NUM; i++) { if(s_associatedHandles[i] == HierarchyNormalizeHandle(handle)) { s_associatedHandles[i] = TPM_RH_NULL; } } }