// This file was extracted from the TCG Published
// Trusted Platform Module Library
// Part 4: Supporting Routines
// Family "2.0"
// Level 00 Revision 01.16
// October 30, 2014
#define SESSION_PROCESS_C
#include "InternalRoutines.h"
#include "SessionProcess_fp.h"
#include "Platform.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 Value Meaning
//
// TRUE handle is exempted from DA logic
// FALSE 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:
{
OBJECT *object;
object = ObjectGet(handle);
result = (object->publicArea.objectAttributes.noDA == SET);
break;
}
case TPM_HT_NV_INDEX:
{
NV_INDEX nvIndex;
NvGetIndexInfo(handle, &nvIndex);
result = (nvIndex.publicArea.attributes.TPMA_NV_NO_DA == SET);
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.
//
// Error Returns Meaning
//
// 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];
TPM_RC result;
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 auth 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)
)
// 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);
gp.lockOutAuthEnabled = FALSE;
// For TPM_RH_LOCKOUT, if lockoutRecovery is 0, no need to update NV since
// the lockout auth will be reset at startup.
if(gp.lockoutRecovery != 0)
{
result = NvIsAvailable();
if(result != TPM_RC_SUCCESS)
{
// No NV access for now. Put the TPM in pending mode.
s_DAPendingOnNV = TRUE;
}
else
{
// Update NV.
NvWriteReserved(NV_LOCKOUT_AUTH_ENABLED, &gp.lockOutAuthEnabled);
g_updateNV = TRUE;
}
}
}
else
{
if(gp.recoveryTime != 0)
{
gp.failedTries++;
result = NvIsAvailable();
if(result != TPM_RC_SUCCESS)
{
// No NV access for now. Put the TPM in pending mode.
s_DAPendingOnNV = TRUE;
}
else
{
// Record changes to NV.
NvWriteReserved(NV_FAILED_TRIES, &gp.failedTries);
g_updateNV = 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 Value Meaning
//
// TRUE handle points to the session start entity
// FALSE 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)
return FALSE;
// Compute the bind value for the entity.
SessionComputeBoundEntity(associatedHandle, &entity);
// Compare to the bind value in the session.
session->attributes.requestWasBound =
Memory2BEqual(&entity.b, &session->u1.boundEntity.b);
return session->attributes.requestWasBound;
}
//
//
// 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, or
// c) the command requires the ADMIN role and the authorized entity is a permanent handle or an NV
// Index.
// d) The authorized entity is a PCR belonging to a policy group, and has its policy initialized
//
// Return Value Meaning
//
// TRUE policy session is required
// FALSE policy session is not required
//
static BOOL
IsPolicySessionRequired(
TPM_CC commandCode, // IN: command code
UINT32 sessionIndex // IN: session index
)
{
AUTH_ROLE role = CommandAuthRole(commandCode, sessionIndex);
TPM_HT type = HandleGetType(s_associatedHandles[sessionIndex]);
if(role == AUTH_DUP)
return TRUE;
if(role == AUTH_ADMIN)
{
if(type == TPM_HT_TRANSIENT)
{
OBJECT *object = ObjectGet(s_associatedHandles[sessionIndex]);
if(object->publicArea.objectAttributes.adminWithPolicy == CLEAR)
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 auth, 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 Value Meaning
//
// TRUE authValue is available
// FALSE authValue is not available
//
static BOOL
IsAuthValueAvailable(
TPM_HANDLE handle, // IN: handle of entity
TPM_CC commandCode, // IN: commandCode
UINT32 sessionIndex // IN: session index
)
{
BOOL result = FALSE;
// If a policy session is required, the entity can not be authorized by
// authValue. However, at this point, the policy session requirement should
// already have been checked.
pAssert(!IsPolicySessionRequired(commandCode, sessionIndex));
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:
#ifdef VENDOR_PERMANENT
// This vendor defined handle associated with the
// manufacturer's shared secret
case VENDOR_PERMANENT:
#endif
// NullAuth is always available.
case TPM_RH_NULL:
// At the point when authValue availability is checked, control
// path has already passed the DA check so LockOut auth is
// always available here
case TPM_RH_LOCKOUT:
result = TRUE;
break;
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;
object = ObjectGet(handle);
// authValue is always available for a sequence object.
if(ObjectIsSequence(object))
{
result = TRUE;
break;
}
// authValue is available for an object if it has its sensitive
// portion loaded and
// 1. userWithAuth bit is SET, or
// 2. ADMIN role is required
if( object->attributes.publicOnly == CLEAR
&& (object->publicArea.objectAttributes.userWithAuth == SET
|| (CommandAuthRole(commandCode, sessionIndex) == AUTH_ADMIN
&& object->publicArea.objectAttributes.adminWithPolicy
== CLEAR)))
result = TRUE;
}
break;
case TPM_HT_NV_INDEX:
// NV Index.
{
NV_INDEX nvIndex;
NvGetIndexInfo(handle, &nvIndex);
if(IsWriteOperation(commandCode))
{
if (nvIndex.publicArea.attributes.TPMA_NV_AUTHWRITE == SET)
result = TRUE;
}
else
{
if (nvIndex.publicArea.attributes.TPMA_NV_AUTHREAD == SET)
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 Value Meaning
//
// TRUE authPolicy is available
// FALSE authPolicy is not available
//
static BOOL
IsAuthPolicyAvailable(
TPM_HANDLE handle, // IN: handle of entity
TPM_CC commandCode, // IN: commandCode
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;
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 = ObjectGet(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, &nvIndex);
// 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(commandCode, sessionIndex))
result = TRUE;
// Otherwise, the presence of the policy depends on the NV
// attributes.
else if(IsWriteOperation(commandCode))
{
if ( nvIndex.publicArea.attributes.TPMA_NV_POLICYWRITE
== SET)
result = TRUE;
}
else
{
if ( nvIndex.publicArea.attributes.TPMA_NV_POLICYREAD
== SET)
result = TRUE;
}
}
}
break;
case TPM_HT_PCR:
// PCR handle.
if(PCRPolicyIsAvailable(handle))
result = TRUE;
break;
default:
break;
}
return result;
}
//
//
// Session Parsing Functions
//
// ComputeCpHash()
//
// This function computes the cpHash as defined in Part 2 and described in Part 1.
//
static void
ComputeCpHash(
TPMI_ALG_HASH hashAlg, // IN: hash algorithm
TPM_CC commandCode, // IN: command code
UINT32 handleNum, // IN: number of handle
TPM_HANDLE handles[], // IN: array of handle
UINT32 parmBufferSize, // IN: size of input parameter area
BYTE *parmBuffer, // IN: input parameter area
TPM2B_DIGEST *cpHash, // OUT: cpHash
TPM2B_DIGEST *nameHash // OUT: name hash of command
)
{
UINT32 i;
HASH_STATE hashState;
TPM2B_NAME name;
//
// cpHash = hash(commandCode [ || authName1
// [ || authName2
// [ || authName 3 ]]]
// [ || parameters])
// A cpHash can contain just a commandCode only if the lone session is
// an audit session.
// Start cpHash.
cpHash->t.size = CryptStartHash(hashAlg, &hashState);
// Add commandCode.
CryptUpdateDigestInt(&hashState, sizeof(TPM_CC), &commandCode);
// Add authNames for each of the handles.
for(i = 0; i < handleNum; i++)
{
name.t.size = EntityGetName(handles[i], &name.t.name);
CryptUpdateDigest2B(&hashState, &name.b);
}
// Add the parameters.
CryptUpdateDigest(&hashState, parmBufferSize, parmBuffer);
// Complete the hash.
CryptCompleteHash2B(&hashState, &cpHash->b);
// If the nameHash is needed, compute it here.
if(nameHash != NULL)
{
// Start name hash. hashState may be reused.
nameHash->t.size = CryptStartHash(hashAlg, &hashState);
// Adding names.
for(i = 0; i < handleNum; i++)
{
name.t.size = EntityGetName(handles[i], &name.t.name);
CryptUpdateDigest2B(&hashState, &name.b);
}
// Complete hash.
CryptCompleteHash2B(&hashState, &nameHash->b);
}
return;
}
//
//
// 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[].
//
// Error Returns Meaning
//
// TPM_RC_AUTH_FAIL auth fails and increments DA failure count
// TPM_RC_BAD_AUTH auth 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 auth value and size.
authValue.t.size = EntityGetAuthValue(associatedHandle, &authValue.t.buffer);
// Success if the digests are identical.
if(Memory2BEqual(&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.
//
static void
ComputeCommandHMAC(
UINT32 sessionIndex, // IN: index of session to be processed
TPM2B_DIGEST *cpHash, // IN: cpHash
TPM2B_DIGEST *hmac // OUT: authorization HMAC
)
{
TPM2B_TYPE(KEY, (sizeof(AUTH_VALUE) * 2));
TPM2B_KEY key;
BYTE marshalBuffer[sizeof(TPMA_SESSION)];
BYTE *buffer;
INT32 bufferSize;
UINT32 marshalSize;
HMAC_STATE hmacState;
TPM2B_NONCE *nonceDecrypt;
TPM2B_NONCE *nonceEncrypt;
SESSION *session;
TPM_HT sessionHandleType =
HandleGetType(s_sessionHandles[sessionIndex]);
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.
// If the session isn't used for authorization, then there is no auth value
// to add
if(s_associatedHandles[sessionIndex] != TPM_RH_UNASSIGNED)
{
// used for auth so see if this is a policy session with authValue needed
// or an hmac session that is not bound
if (((sessionHandleType == TPM_HT_POLICY_SESSION)
&& (session->attributes.isAuthValueNeeded == SET))
|| ((sessionHandleType == TPM_HT_HMAC_SESSION)
&& !IsSessionBindEntity(s_associatedHandles[sessionIndex], session))
)
{
// add the authValue to the HMAC key
pAssert((sizeof(AUTH_VALUE) + key.t.size) <= sizeof(key.t.buffer));
key.t.size = key.t.size
+ EntityGetAuthValue(s_associatedHandles[sessionIndex],
(AUTH_VALUE *)&(key.t.buffer[key.t.size]));
}
}
// 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;
}
// Start HMAC
hmac->t.size = CryptStartHMAC2B(session->authHashAlg, &key.b, &hmacState);
// Add cpHash
CryptUpdateDigest2B(&hmacState, &cpHash->b);
// Add nonceCaller
CryptUpdateDigest2B(&hmacState, &s_nonceCaller[sessionIndex].b);
// Add nonceTPM
CryptUpdateDigest2B(&hmacState, &session->nonceTPM.b);
// If needed, add nonceTPM for decrypt session
if(nonceDecrypt != NULL)
CryptUpdateDigest2B(&hmacState, &nonceDecrypt->b);
// If needed, add nonceTPM for encrypt session
if(nonceEncrypt != NULL)
CryptUpdateDigest2B(&hmacState, &nonceEncrypt->b);
// Add sessionAttributes
buffer = marshalBuffer;
bufferSize = sizeof(TPMA_SESSION);
marshalSize = TPMA_SESSION_Marshal(&(s_attributes[sessionIndex]),
&buffer, &bufferSize);
CryptUpdateDigest(&hmacState, marshalSize, marshalBuffer);
// Complete the HMAC computation
CryptCompleteHMAC2B(&hmacState, &hmac->b);
return;
}
//
//
// 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.
//
// Error Returns Meaning
//
// TPM_RC_AUTH_FAIL auth failure caused failureCount increment
// TPM_RC_BAD_AUTH auth failure did not cause failureCount increment
//
static TPM_RC
CheckSessionHMAC(
UINT32 sessionIndex, // IN: index of session to be processed
TPM2B_DIGEST *cpHash // IN: cpHash of the command
)
{
TPM2B_DIGEST hmac; // authHMAC for comparing
// Compute authHMAC
ComputeCommandHMAC(sessionIndex, cpHash, &hmac);
// Compare the input HMAC with the authHMAC computed above.
if(!Memory2BEqual(&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.
//
// Error Returns Meaning
//
// 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(
UINT32 sessionIndex, // IN: index of session to be processed
TPM_CC commandCode, // IN: command code
TPM2B_DIGEST *cpHash, // IN: cpHash using the algorithm of this
// session
TPM2B_DIGEST *nameHash // IN: nameHash using the session algorithm
)
{
TPM_RC result = TPM_RC_SUCCESS;
SESSION *session;
TPM2B_DIGEST authPolicy;
TPMI_ALG_HASH policyAlg;
UINT8 locality;
// Initialize pointer to the auth session.
session = SessionGet(s_sessionHandles[sessionIndex]);
// If the command is TPM_RC_PolicySecret(), make sure that
// either password or authValue is required
if( commandCode == 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(s_sessionHandles[sessionIndex]) )
return TPM_RC_PCR_CHANGED;
// Get authPolicy.
policyAlg = EntityGetAuthPolicy(s_associatedHandles[sessionIndex],
&authPolicy);
// Compare authPolicy.
if(!Memory2BEqual(&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.
result = NvIsAvailable();
if(result != TPM_RC_SUCCESS)
return result;
if(session->timeOut < go.clock)
return TPM_RC_EXPIRED;
}
// If command code is provided it must match
if(session->commandCode != 0)
{
if(session->commandCode != commandCode)
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(commandCode, sessionIndex);
if(role == AUTH_ADMIN || role == AUTH_DUP)
return TPM_RC_POLICY_FAIL;
}
// Check command locality.
{
BYTE sessionLocality[sizeof(TPMA_LOCALITY)];
BYTE *buffer = sessionLocality;
INT32 bufferSize = sizeof(TPMA_LOCALITY);
// Get existing locality setting in canonical form
TPMA_LOCALITY_Marshal(&session->commandLocality, &buffer, &bufferSize);
// 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 if defined, or if the command requires an ADMIN or
// DUP role for this handle.
if(session->u1.cpHash.b.size != 0)
{
if(session->attributes.iscpHashDefined)
{
// Compare cpHash.
if(!Memory2BEqual(&session->u1.cpHash.b, &cpHash->b))
return TPM_RC_POLICY_FAIL;
}
else
{
// Compare nameHash.
// When cpHash is not defined, nameHash is placed in its space.
if(!Memory2BEqual(&session->u1.cpHash.b, &nameHash->b))
return TPM_RC_POLICY_FAIL;
}
}
if(session->attributes.checkNvWritten)
{
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
NvGetIndexInfo(s_associatedHandles[sessionIndex], &nvIndex);
// Make sure that the TPMA_WRITTEN_ATTRIBUTE has the desired state
if( (nvIndex.publicArea.attributes.TPMA_NV_WRITTEN == SET)
!= (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.
//
// Error Returns Meaning
//
// 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 (
TPM_CC commandCode, // IN: command code
UINT32 *sessionCount, // OUT: number of sessions found
BYTE *sessionBuffer, // IN: pointer to the session buffer
INT32 bufferSize // IN: size of the session buffer
)
{
int sessionIndex;
int i;
TPM_RC result;
SESSION *session;
TPM_HT sessionType;
s_decryptSessionIndex = UNDEFINED_INDEX;
s_encryptSessionIndex = UNDEFINED_INDEX;
s_auditSessionIndex = UNDEFINED_INDEX;
for(sessionIndex = 0; bufferSize > 0; 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_RC_SIZE + TPM_RC_S + g_rcIndex[sessionIndex+1];
// 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],
&sessionBuffer, &bufferSize, TRUE);
if(result != TPM_RC_SUCCESS)
return result + TPM_RC_S + g_rcIndex[sessionIndex];
// Second parameter: Nonce.
result = TPM2B_NONCE_Unmarshal(&s_nonceCaller[sessionIndex],
&sessionBuffer, &bufferSize);
if(result != TPM_RC_SUCCESS)
return result + TPM_RC_S + g_rcIndex[sessionIndex];
// Third parameter: sessionAttributes.
result = TPMA_SESSION_Unmarshal(&s_attributes[sessionIndex],
&sessionBuffer, &bufferSize);
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],
&sessionBuffer, &bufferSize);
if(result != TPM_RC_SUCCESS)
return result + TPM_RC_S + g_rcIndex[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( s_attributes[sessionIndex].encrypt
|| s_attributes[sessionIndex].decrypt
|| s_attributes[sessionIndex].audit
|| s_attributes[sessionIndex].auditExclusive
|| s_attributes[sessionIndex].auditReset
)
return TPM_RC_ATTRIBUTES + TPM_RC_S + g_rcIndex[sessionIndex];
// The nonce size must be zero.
if(s_nonceCaller[sessionIndex].t.size != 0)
return TPM_RC_NONCE + TPM_RC_S + g_rcIndex[sessionIndex];
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_RC_HANDLE + TPM_RC_S + g_rcIndex[sessionIndex];
// Check that this handle has not previously been used.
for(i = 0; i < sessionIndex; i++)
{
if(s_sessionHandles[i] == s_sessionHandles[sessionIndex])
return TPM_RC_HANDLE + TPM_RC_S + g_rcIndex[sessionIndex];
}
// If the session is used for parameter encryption or audit as well, set
// the corresponding indices.
// First process decrypt.
if(s_attributes[sessionIndex].decrypt)
{
// Check if the commandCode allows command parameter encryption.
if(DecryptSize(commandCode) == 0)
return TPM_RC_ATTRIBUTES + TPM_RC_S + g_rcIndex[sessionIndex];
// Encrypt attribute can only appear in one session
if(s_decryptSessionIndex != UNDEFINED_INDEX)
return TPM_RC_ATTRIBUTES + TPM_RC_S + g_rcIndex[sessionIndex];
// Can't decrypt if the session's symmetric algorithm is TPM_ALG_NULL
if(session->symmetric.algorithm == TPM_ALG_NULL)
return TPM_RC_SYMMETRIC + TPM_RC_S + g_rcIndex[sessionIndex];
// All checks passed, so set the index for the session used to decrypt
// a command parameter.
s_decryptSessionIndex = sessionIndex;
}
// Now process encrypt.
if(s_attributes[sessionIndex].encrypt)
{
// Check if the commandCode allows response parameter encryption.
if(EncryptSize(commandCode) == 0)
return TPM_RC_ATTRIBUTES + TPM_RC_S + g_rcIndex[sessionIndex];
// Encrypt attribute can only appear in one session.
if(s_encryptSessionIndex != UNDEFINED_INDEX)
return TPM_RC_ATTRIBUTES + TPM_RC_S + g_rcIndex[sessionIndex];
// Can't encrypt if the session's symmetric algorithm is TPM_ALG_NULL
if(session->symmetric.algorithm == TPM_ALG_NULL)
return TPM_RC_SYMMETRIC + TPM_RC_S + g_rcIndex[sessionIndex];
// All checks passed, so set the index for the session used to encrypt
// a response parameter.
s_encryptSessionIndex = sessionIndex;
}
// At last process audit.
if(s_attributes[sessionIndex].audit)
{
// Audit attribute can only appear in one session.
if(s_auditSessionIndex != UNDEFINED_INDEX)
return TPM_RC_ATTRIBUTES + TPM_RC_S + g_rcIndex[sessionIndex];
// An audit session can not be policy session.
if( HandleGetType(s_sessionHandles[sessionIndex])
== TPM_HT_POLICY_SESSION)
return TPM_RC_ATTRIBUTES + TPM_RC_S + g_rcIndex[sessionIndex];
// 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( s_attributes[sessionIndex].auditReset == CLEAR
&& session->attributes.isAudit == SET)
{
// Not first use or reset. If auditExlusive is SET, then this
// session must be the current exclusive session.
if( s_attributes[sessionIndex].auditExclusive == SET
&& 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;
}
// Set the number of sessions found.
*sessionCount = 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
//
// Error Returns Meaning
//
// 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
)
{
TPM_RC result;
// If NV is unavailable, and current cycle state recorded in NV is not
// SHUTDOWN_NONE, refuse to check any authorization because we would
// not be able to handle a DA failure.
result = NvIsAvailable();
if(result != TPM_RC_SUCCESS && gp.orderlyState != SHUTDOWN_NONE)
return result;
// Check if DA info needs to be updated in NV.
if(s_DAPendingOnNV)
{
// If NV is accessible, ...
if(result == TPM_RC_SUCCESS)
{
// ... write the pending DA data and proceed.
NvWriteReserved(NV_LOCKOUT_AUTH_ENABLED,
&gp.lockOutAuthEnabled);
NvWriteReserved(NV_FAILED_TRIES, &gp.failedTries);
g_updateNV = TRUE;
s_DAPendingOnNV = FALSE;
}
else
{
// Otherwise no authorization can be checked.
return result;
}
}
// 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;
}
return TPM_RC_SUCCESS;
}
//
//
// CheckAuthSession()
//
// This function checks that the authorization session properly authorizes the use of the associated handle.
//
// Error Returns Meaning
//
// 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(
TPM_CC commandCode, // IN: commandCode
UINT32 sessionIndex, // IN: index of session to be processed
TPM2B_DIGEST *cpHash, // IN: cpHash
TPM2B_DIGEST *nameHash // IN: nameHash
//
)
{
TPM_RC result;
SESSION *session = NULL;
TPM_HANDLE sessionHandle = s_sessionHandles[sessionIndex];
TPM_HANDLE associatedHandle = s_associatedHandles[sessionIndex];
TPM_HT sessionHandleType = HandleGetType(sessionHandle);
pAssert(sessionHandle != TPM_RH_UNASSIGNED);
if(sessionHandle != TPM_RS_PW)
session = SessionGet(sessionHandle);
pAssert(sessionHandleType != TPM_HT_POLICY_SESSION || session != NULL);
// If the authorization session is not a policy session, or if the policy
// session requires authorization, then check lockout.
if( sessionHandleType != TPM_HT_POLICY_SESSION
|| session->attributes.isAuthValueNeeded
|| session->attributes.isPasswordNeeded)
{
// See if entity is subject to lockout.
if(!IsDAExempted(associatedHandle))
{
// If NV is unavailable, and current cycle state recorded in NV is not
// SHUTDOWN_NONE, refuse to check any authorization because we would
// not be able to handle a DA failure.
result = CheckLockedOut(associatedHandle == TPM_RH_LOCKOUT);
if(result != TPM_RC_SUCCESS)
return result;
}
}
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(commandCode)
&& !_plat__PhysicalPresenceAsserted()
)
return TPM_RC_PP;
}
// If a policy session is required, make sure that it is being used.
if( IsPolicySessionRequired(commandCode, sessionIndex)
&& sessionHandleType != TPM_HT_POLICY_SESSION)
return TPM_RC_AUTH_TYPE;
// If this is a PW authorization, check it and return.
if(sessionHandle == TPM_RS_PW)
{
if(IsAuthValueAvailable(associatedHandle, commandCode, sessionIndex))
return CheckPWAuthSession(sessionIndex);
else
return TPM_RC_AUTH_UNAVAILABLE;
}
// If this is a policy session, ...
if(sessionHandleType == TPM_HT_POLICY_SESSION)
{
// ... see if the entity has a policy, ...
if( !IsAuthPolicyAvailable(associatedHandle, commandCode, sessionIndex))
return TPM_RC_AUTH_UNAVAILABLE;
// ... and check the policy session.
result = CheckPolicyAuthSession(sessionIndex, commandCode,
cpHash, nameHash);
if (result != TPM_RC_SUCCESS)
return result;
}
else
{
// For non policy, the entity being accessed must allow authorization
// with an auth value. This is required even if the auth value is not
// going to be used in an HMAC because it is bound.
if(!IsAuthValueAvailable(associatedHandle, commandCode, sessionIndex))
return TPM_RC_AUTH_UNAVAILABLE;
}
// At this point, the session must be either a policy or an HMAC session.
session = SessionGet(s_sessionHandles[sessionIndex]);
if( sessionHandleType == TPM_HT_POLICY_SESSION
&& session->attributes.isPasswordNeeded == SET)
{
// For policy session that requires a password, check it as PWAP session.
return CheckPWAuthSession(sessionIndex);
}
else
{
// For other policy or HMAC sessions, have its HMAC checked.
return CheckSessionHMAC(sessionIndex, cpHash);
}
}
#ifdef TPM_CC_GetCommandAuditDigest
//
//
// CheckCommandAudit()
//
// This function checks if the current command may trigger command audit, and if it is safe to perform the
// action.
//
// Error Returns Meaning
//
// TPM_RC_NV_UNAVAILABLE NV is not available for write
// TPM_RC_NV_RATE NV is rate limiting
//
static TPM_RC
CheckCommandAudit(
TPM_CC commandCode, // IN: Command code
UINT32 handleNum, // IN: number of element in handle array
TPM_HANDLE handles[], // IN: array of handle
BYTE *parmBufferStart, // IN: start of parameter buffer
UINT32 parmBufferSize // IN: size of parameter buffer
)
{
TPM_RC result = TPM_RC_SUCCESS;
// If audit is implemented, need to check to see if auditing is being done
// for this command.
if(CommandAuditIsRequired(commandCode))
{
// 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
|| commandCode == TPM_CC_GetCommandAuditDigest)
{
result = NvIsAvailable();
if(result != TPM_RC_SUCCESS)
return result;
}
ComputeCpHash(gp.auditHashAlg, commandCode, handleNum,
handles, parmBufferSize, parmBufferStart,
&s_cpHashForCommandAudit, NULL);
}
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.
//
// Error Returns Meaning
//
// various parsing failure or authorization failure
//
TPM_RC
ParseSessionBuffer(
TPM_CC commandCode, // IN: Command code
UINT32 handleNum, // IN: number of element in handle array
TPM_HANDLE handles[], // IN: array of handle
BYTE *sessionBufferStart, // IN: start of session buffer
UINT32 sessionBufferSize, // IN: size of session buffer
BYTE *parmBufferStart, // IN: start of parameter buffer
UINT32 parmBufferSize // IN: size of parameter buffer
)
{
TPM_RC result;
UINT32 i;
INT32 size = 0;
TPM2B_AUTH extraKey;
UINT32 sessionIndex;
SESSION *session;
TPM2B_DIGEST cpHash;
TPM2B_DIGEST nameHash;
TPM_ALG_ID cpHashAlg = TPM_ALG_NULL; // algID for the last computed
// cpHash
// Check if a command allows any session in its session area.
if(!IsSessionAllowed(commandCode))
return TPM_RC_AUTH_CONTEXT;
// Default-initialization.
s_sessionNum = 0;
cpHash.t.size = 0;
result = RetrieveSessionData(commandCode, &s_sessionNum,
sessionBufferStart, sessionBufferSize);
if(result != TPM_RC_SUCCESS)
return result;
// There is no command in the TPM spec that has more handles than
// MAX_SESSION_NUM.
pAssert(handleNum <= MAX_SESSION_NUM);
// Associate the session with an authorization handle.
for(i = 0; i < handleNum; i++)
{
if(CommandAuthRole(commandCode, i) != AUTH_NONE)
{
// If the received session number is less than the number of handle
// 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.
if(i > (s_sessionNum - 1))
return TPM_RC_AUTH_MISSING;
// Record the handle associated with the authorization session
s_associatedHandles[i] = handles[i];
}
}
// Consistency checks are done first to avoid auth failure when the command
// will not be executed anyway.
for(sessionIndex = 0; sessionIndex < s_sessionNum; sessionIndex++)
{
// PW session must be an authorization session
if(s_sessionHandles[sessionIndex] == TPM_RS_PW )
{
if(s_associatedHandles[sessionIndex] == TPM_RH_UNASSIGNED)
return TPM_RC_HANDLE + g_rcIndex[sessionIndex];
}
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_RC_ATTRIBUTES + TPM_RC_S + g_rcIndex[sessionIndex];
// 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 the current cpHash is the right one, don't re-compute.
if(cpHashAlg != session->authHashAlg) // different so compute
{
cpHashAlg = session->authHashAlg; // save this new algID
ComputeCpHash(session->authHashAlg, commandCode, handleNum,
handles, parmBufferSize, parmBufferStart,
&cpHash, &nameHash);
}
// If this session is for auditing, save the cpHash.
if(s_attributes[sessionIndex].audit)
s_cpHashForAudit = cpHash;
}
// if the session has an associated handle, check the auth
if(s_associatedHandles[sessionIndex] != TPM_RH_UNASSIGNED)
{
result = CheckAuthSession(commandCode, sessionIndex,
&cpHash, &nameHash);
if(result != TPM_RC_SUCCESS)
return RcSafeAddToResult(result,
TPM_RC_S + g_rcIndex[sessionIndex]);
}
else
{
// a session that is not for authorization must either be encrypt,
// decrypt, or audit
if( s_attributes[sessionIndex].audit == CLEAR
&& s_attributes[sessionIndex].encrypt == CLEAR
&& s_attributes[sessionIndex].decrypt == CLEAR)
return TPM_RC_ATTRIBUTES + TPM_RC_S + g_rcIndex[sessionIndex];
// check HMAC for encrypt/decrypt/audit only sessions
result = CheckSessionHMAC(sessionIndex, &cpHash);
if(result != TPM_RC_SUCCESS)
return RcSafeAddToResult(result,
TPM_RC_S + g_rcIndex[sessionIndex]);
}
}
#ifdef TPM_CC_GetCommandAuditDigest
// Check if the command should be audited.
result = CheckCommandAudit(commandCode, handleNum, handles,
parmBufferStart, parmBufferSize);
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 sessionAuth to
// generate encryption key, no matter if the handle is the session bound entity
// or not.
if(s_decryptSessionIndex != UNDEFINED_INDEX)
{
// Get size of the leading size field in decrypt parameter
if( s_associatedHandles[s_decryptSessionIndex] != TPM_RH_UNASSIGNED
&& IsAuthValueAvailable(s_associatedHandles[s_decryptSessionIndex],
commandCode,
s_decryptSessionIndex)
)
{
extraKey.b.size=
EntityGetAuthValue(s_associatedHandles[s_decryptSessionIndex],
&extraKey.t.buffer);
}
else
{
extraKey.b.size = 0;
}
size = DecryptSize(commandCode);
result = CryptParameterDecryption(
s_sessionHandles[s_decryptSessionIndex],
&s_nonceCaller[s_decryptSessionIndex].b,
parmBufferSize, (UINT16)size,
&extraKey,
parmBufferStart);
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.
//
//
//
// Error Returns Meaning
//
// TPM_RC_AUTH_MISSING failure - one or more handles require auth
//
TPM_RC
CheckAuthNoSession(
TPM_CC commandCode, // IN: Command Code
UINT32 handleNum, // IN: number of handles in command
TPM_HANDLE handles[], // IN: array of handle
BYTE *parmBufferStart, // IN: start of parameter buffer
UINT32 parmBufferSize // IN: size of parameter buffer
)
{
UINT32 i;
TPM_RC result = TPM_RC_SUCCESS;
// Check if the commandCode requires authorization
for(i = 0; i < handleNum; i++)
{
if(CommandAuthRole(commandCode, i) != AUTH_NONE)
return TPM_RC_AUTH_MISSING;
}
#ifdef TPM_CC_GetCommandAuditDigest
// Check if the command should be audited.
result = CheckCommandAudit(commandCode, handleNum, handles,
parmBufferStart, parmBufferSize);
if(result != TPM_RC_SUCCESS) return result;
#endif
// Initialize number of sessions to be 0
s_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 void
ComputeRpHash(
TPM_ALG_ID hashAlg, // IN: hash algorithm to compute rpHash
TPM_CC commandCode, // IN: commandCode
UINT32 resParmBufferSize, // IN: size of response parameter buffer
BYTE *resParmBuffer, // IN: response parameter buffer
TPM2B_DIGEST *rpHash // OUT: rpHash
)
{
// The command result in rpHash is always TPM_RC_SUCCESS.
TPM_RC responseCode = TPM_RC_SUCCESS;
HASH_STATE hashState;
// rpHash := hash(responseCode || commandCode || parameters)
// Initiate hash creation.
rpHash->t.size = CryptStartHash(hashAlg, &hashState);
// Add hash constituents.
CryptUpdateDigestInt(&hashState, sizeof(TPM_RC), &responseCode);
CryptUpdateDigestInt(&hashState, sizeof(TPM_CC), &commandCode);
CryptUpdateDigest(&hashState, resParmBufferSize, resParmBuffer);
// Complete hash computation.
CryptCompleteHash2B(&hashState, &rpHash->b);
return;
}
//
//
// 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 = CryptGetHashDigestSize(session->authHashAlg);
// Set the original digest value to be 0.
MemorySet(&session->u2.auditDigest.t.buffer,
0,
session->u2.auditDigest.t.size);
return;
}
//
//
// Audit()
//
// This function updates the audit digest in an audit session.
//
static void
Audit(
SESSION *auditSession, // IN: loaded audit session
TPM_CC commandCode, // IN: commandCode
UINT32 resParmBufferSize, // IN: size of response parameter buffer
BYTE *resParmBuffer // IN: response parameter buffer
)
{
TPM2B_DIGEST rpHash; // rpHash for response
HASH_STATE hashState;
// Compute rpHash
ComputeRpHash(auditSession->authHashAlg,
commandCode,
resParmBufferSize,
resParmBuffer,
&rpHash);
// auditDigestnew := hash (auditDigestold || cpHash || rpHash)
// Start hash computation.
CryptStartHash(auditSession->authHashAlg, &hashState);
// Add old digest.
CryptUpdateDigest2B(&hashState, &auditSession->u2.auditDigest.b);
// Add cpHash and rpHash.
CryptUpdateDigest2B(&hashState, &s_cpHashForAudit.b);
CryptUpdateDigest2B(&hashState, &rpHash.b);
// Finalize the hash.
CryptCompleteHash2B(&hashState, &auditSession->u2.auditDigest.b);
return;
}
#ifdef TPM_CC_GetCommandAuditDigest
//
//
// CommandAudit()
//
// This function updates the command audit digest.
//
static void
CommandAudit(
TPM_CC commandCode, // IN: commandCode
UINT32 resParmBufferSize, // IN: size of response parameter buffer
BYTE *resParmBuffer // IN: response parameter buffer
)
{
if(CommandAuditIsRequired(commandCode))
{
TPM2B_DIGEST rpHash; // rpHash for response
HASH_STATE hashState;
// Compute rpHash.
ComputeRpHash(gp.auditHashAlg, commandCode, resParmBufferSize,
resParmBuffer, &rpHash);
// 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 = CryptGetHashDigestSize(gp.auditHashAlg);
MemorySet(gr.commandAuditDigest.t.buffer,
0,
gr.commandAuditDigest.t.size);
// Bump the counter and save its value to NV.
gp.auditCounter++;
NvWriteReserved(NV_AUDIT_COUNTER, &gp.auditCounter);
g_updateNV = TRUE;
//
}
// auditDigestnew := hash (auditDigestold || cpHash || rpHash)
// Start hash computation.
CryptStartHash(gp.auditHashAlg, &hashState);
// Add old digest.
CryptUpdateDigest2B(&hashState, &gr.commandAuditDigest.b);
// Add cpHash
CryptUpdateDigest2B(&hashState, &s_cpHashForCommandAudit.b);
// Add rpHash
CryptUpdateDigest2B(&hashState, &rpHash.b);
// Finalize the hash.
CryptCompleteHash2B(&hashState, &gr.commandAuditDigest.b);
}
return;
}
#endif
//
//
// UpdateAuditSessionStatus()
//
// Function to update the internal audit related states of a session. It
// a) initializes the session as audit session and sets it to be exclusive if this is the first time it is used for
// audit or audit reset was requested;
// b) reports exclusive audit session;
// c) extends audit log; and
// d) clears exclusive audit session if no audit session found in the command.
//
static void
UpdateAuditSessionStatus(
TPM_CC commandCode, // IN: commandCode
UINT32 resParmBufferSize, // IN: size of response parameter buffer
BYTE *resParmBuffer // IN: response parameter buffer
)
{
UINT32 i;
TPM_HANDLE auditSession = TPM_RH_UNASSIGNED;
// Iterate through sessions
for (i = 0; i < s_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(s_attributes[i].audit == SET)
{
// 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
|| s_attributes[i].auditReset == SET
)
{
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)
{
s_attributes[i].auditExclusive = SET;
}
else
{
s_attributes[i].auditExclusive = CLEAR;
}
// Extend audit log.
Audit(session, commandCode, resParmBufferSize, resParmBuffer);
}
}
// 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(commandCode))
{
g_exclusiveAuditSession = TPM_RH_UNASSIGNED;
}
return;
}
//
//
// ComputeResponseHMAC()
//
// Function to compute HMAC for authorization session in a response.
//
static void
ComputeResponseHMAC(
UINT32 sessionIndex, // IN: session index to be processed
SESSION *session, // IN: loaded session
TPM_CC commandCode, // IN: commandCode
TPM2B_NONCE *nonceTPM, // IN: nonceTPM
UINT32 resParmBufferSize, // IN: size of response parameter buffer
BYTE *resParmBuffer, // IN: response parameter buffer
TPM2B_DIGEST *hmac // OUT: authHMAC
)
{
TPM2B_TYPE(KEY, (sizeof(AUTH_VALUE) * 2));
TPM2B_KEY key; // HMAC key
BYTE marshalBuffer[sizeof(TPMA_SESSION)];
BYTE *buffer;
INT32 bufferSize;
UINT32 marshalSize;
HMAC_STATE hmacState;
TPM2B_DIGEST rp_hash;
//
// Compute rpHash.
ComputeRpHash(session->authHashAlg, commandCode, resParmBufferSize,
resParmBuffer, &rp_hash);
// Generate HMAC key
MemoryCopy2B(&key.b, &session->sessionKey.b, sizeof(key.t.buffer));
// Check if the session has an associated handle and the associated entity is
// the one that the session is bound to.
// If not bound, add the authValue of this entity to the HMAC key.
if( s_associatedHandles[sessionIndex] != TPM_RH_UNASSIGNED
&& !( HandleGetType(s_sessionHandles[sessionIndex])
== TPM_HT_POLICY_SESSION
&& session->attributes.isAuthValueNeeded == CLEAR)
&& !session->attributes.requestWasBound)
{
pAssert((sizeof(AUTH_VALUE) + key.t.size) <= sizeof(key.t.buffer));
key.t.size = key.t.size +
EntityGetAuthValue(s_associatedHandles[sessionIndex],
(AUTH_VALUE *)&key.t.buffer[key.t.size]);
}
// if the HMAC key size for a policy session is 0, the response HMAC is
// computed according to the input HMAC
if(HandleGetType(s_sessionHandles[sessionIndex]) == TPM_HT_POLICY_SESSION
&& key.t.size == 0
&& s_inputAuthValues[sessionIndex].t.size == 0)
{
hmac->t.size = 0;
return;
}
// Start HMAC computation.
hmac->t.size = CryptStartHMAC2B(session->authHashAlg, &key.b, &hmacState);
// Add hash components.
CryptUpdateDigest2B(&hmacState, &rp_hash.b);
CryptUpdateDigest2B(&hmacState, &nonceTPM->b);
CryptUpdateDigest2B(&hmacState, &s_nonceCaller[sessionIndex].b);
// Add session attributes.
buffer = marshalBuffer;
bufferSize = sizeof(TPMA_SESSION);
marshalSize = TPMA_SESSION_Marshal(&s_attributes[sessionIndex], &buffer, &bufferSize);
CryptUpdateDigest(&hmacState, marshalSize, marshalBuffer);
// Finalize HMAC.
CryptCompleteHMAC2B(&hmacState, &hmac->b);
return;
}
//
//
// BuildSingleResponseAuth()
//
// Function to compute response for an authorization session.
//
static void
BuildSingleResponseAuth(
UINT32 sessionIndex, // IN: session index to be processed
TPM_CC commandCode, // IN: commandCode
UINT32 resParmBufferSize, // IN: size of response parameter buffer
BYTE *resParmBuffer, // IN: response parameter buffer
TPM2B_AUTH *auth // OUT: authHMAC
)
//
{
// For password authorization, field is empty.
if(s_sessionHandles[sessionIndex] == TPM_RS_PW)
{
auth->t.size = 0;
}
else
{
// Fill in policy/HMAC based session response.
SESSION *session = SessionGet(s_sessionHandles[sessionIndex]);
// If the session is a policy session with isPasswordNeeded SET, the auth
// 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(sessionIndex,
session,
commandCode,
&session->nonceTPM,
resParmBufferSize,
resParmBuffer,
auth);
}
return;
}
//
//
// UpdateTPMNonce()
//
// Updates TPM nonce in both internal session or response if applicable.
//
static void
UpdateTPMNonce(
UINT16 noncesSize, // IN: number of elements in 'nonces' array
TPM2B_NONCE nonces[] // OUT: nonceTPM
)
{
UINT32 i;
pAssert(noncesSize >= s_sessionNum);
for(i = 0; i < s_sessionNum; i++)
{
SESSION *session;
// For PW session, nonce is 0.
if(s_sessionHandles[i] == TPM_RS_PW)
{
nonces[i].t.size = 0;
continue;
}
session = SessionGet(s_sessionHandles[i]);
// Update nonceTPM in both internal session and response.
CryptGenerateRandom(session->nonceTPM.t.size, session->nonceTPM.t.buffer);
nonces[i] = session->nonceTPM;
}
return;
}
//
//
// UpdateInternalSession()
//
// Updates internal sessions:
//
//
// a) Restarts session time, and
// b) Clears a policy session since nonce is rolling.
//
static void
UpdateInternalSession(
void
)
{
UINT32 i;
for(i = 0; i < s_sessionNum; i++)
{
// For PW session, no update.
if(s_sessionHandles[i] == TPM_RS_PW) continue;
if(s_attributes[i].continueSession == CLEAR)
{
// Close internal session.
SessionFlush(s_sessionHandles[i]);
}
else
{
// 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)
{
SESSION *session = SessionGet(s_sessionHandles[i]);
// When the nonce rolls it starts a new timing interval for the
// policy session.
SessionResetPolicyData(session);
session->startTime = go.clock;
}
}
}
return;
}
//
//
// BuildResponseSession()
//
// Function to build Session buffer in a response.
//
void
BuildResponseSession(
TPM_ST tag, // IN: tag
TPM_CC commandCode, // IN: commandCode
UINT32 resHandleSize, // IN: size of response handle buffer
UINT32 resParmSize, // IN: size of response parameter buffer
UINT32 *resSessionSize // OUT: response session area
)
{
BYTE *resParmBuffer;
INT32 bufferSize;
TPM2B_NONCE responseNonces[MAX_SESSION_NUM];
// Compute response parameter buffer start.
resParmBuffer = MemoryGetResponseBuffer(commandCode) + sizeof(TPM_ST) +
sizeof(UINT32) + sizeof(TPM_RC) + resHandleSize;
bufferSize = MAX_RESPONSE_SIZE - sizeof(TPM_ST) - sizeof(UINT32) -
sizeof(TPM_RC) - resHandleSize;
// For TPM_ST_SESSIONS, there is parameterSize field.
if(tag == TPM_ST_SESSIONS) {
resParmBuffer += sizeof(UINT32);
bufferSize -= sizeof(UINT32);
}
// Session nonce should be updated before parameter encryption
if(tag == TPM_ST_SESSIONS)
{
UpdateTPMNonce(MAX_SESSION_NUM, responseNonces);
// 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 sessionAuth to generate
// encryption key, no matter if the handle is the session bound entity
// or not. The authValue is added to sessionAuth only when the authValue
// is available.
if(s_encryptSessionIndex != UNDEFINED_INDEX)
{
UINT32 size;
TPM2B_AUTH extraKey;
// Get size of the leading size field
if( s_associatedHandles[s_encryptSessionIndex] != TPM_RH_UNASSIGNED
&& IsAuthValueAvailable(s_associatedHandles[s_encryptSessionIndex],
commandCode, s_encryptSessionIndex)
)
{
extraKey.b.size =
EntityGetAuthValue(s_associatedHandles[s_encryptSessionIndex],
&extraKey.t.buffer);
}
else
{
extraKey.b.size = 0;
}
size = EncryptSize(commandCode);
CryptParameterEncryption(s_sessionHandles[s_encryptSessionIndex],
&s_nonceCaller[s_encryptSessionIndex].b,
(UINT16)size,
&extraKey,
resParmBuffer);
}
}
// Audit session should be updated first regardless of the tag.
// A command with no session may trigger a change of the exclusivity state.
UpdateAuditSessionStatus(commandCode, resParmSize, resParmBuffer);
// Audit command.
CommandAudit(commandCode, resParmSize, resParmBuffer);
// Process command with sessions.
if(tag == TPM_ST_SESSIONS)
{
UINT32 i;
BYTE *buffer;
TPM2B_DIGEST responseAuths[MAX_SESSION_NUM];
pAssert(s_sessionNum > 0);
// Iterate over each session in the command session area, and create
// corresponding sessions for response.
for(i = 0; i < s_sessionNum; i++)
{
BuildSingleResponseAuth(
i,
commandCode,
resParmSize,
resParmBuffer,
&responseAuths[i]);
// 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_attributes[i].continueSession == CLEAR
&& s_sessionHandles[i] == TPM_RS_PW)
{
s_attributes[i].continueSession = SET;
}
}
// Assemble Response Sessions.
*resSessionSize = 0;
buffer = resParmBuffer + resParmSize;
bufferSize -= resParmSize;
for(i = 0; i < s_sessionNum; i++)
{
*resSessionSize += TPM2B_NONCE_Marshal(&responseNonces[i],
&buffer, &bufferSize);
*resSessionSize += TPMA_SESSION_Marshal(&s_attributes[i],
&buffer, &bufferSize);
*resSessionSize += TPM2B_DIGEST_Marshal(&responseAuths[i],
&buffer, &bufferSize);
}
// Update internal sessions after completing response buffer computation.
UpdateInternalSession();
}
else
{
// Process command with no session.
*resSessionSize = 0;
}
return;
}