Skip to content

Commit

Permalink
tpm2: update some doc comments
Browse files Browse the repository at this point in the history
This also adds an explanation of what a session is used for, for some
functions that still accept a HMAC session.
  • Loading branch information
chrisccoulson committed Jul 8, 2024
1 parent e39b2d2 commit 416fbe2
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 23 deletions.
8 changes: 0 additions & 8 deletions tpm2/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,6 @@ func MockSecbootNewKeyData(fn func(*secboot.KeyParams) (*secboot.KeyData, error)
}
}

func MockSkdbUpdatePCRProtectionPolicyImpl(fn func(*sealedKeyDataBase, *tpm2.TPMContext, secboot.AuxiliaryKey, *tpm2.NVPublic, *PCRProtectionProfile) error) (restore func()) {
orig := skdbUpdatePCRProtectionPolicyImpl
skdbUpdatePCRProtectionPolicyImpl = fn
return func() {
skdbUpdatePCRProtectionPolicyImpl = orig
}
}

func MockSecbootNewKeyDataWithPassphrase(fn func(*secboot.KeyWithPassphraseParams, string) (*secboot.KeyData, error)) (restore func()) {
orig := secbootNewKeyDataWithPassphrase
secbootNewKeyDataWithPassphrase = fn
Expand Down
5 changes: 5 additions & 0 deletions tpm2/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ func (h *platformKeyDataHandler) ChangeAuthKey(data *secboot.PlatformKeyData, ol
defer tpm.FlushContext(session)

keyObject.SetAuthValue(old)

// Use a HMAC session for authentication. This avoids sending the old value in the clear.
// It also encrypts the new value, although this only provides protection against passive
// interposers as we don't verify the key that is used to salt the session is actually a
// TPM protected key.
priv, err := tpm.ObjectChangeAuth(keyObject, srk, new, tpm.HmacSession().IncludeAttrs(tpm2.AttrCommandEncrypt))
if err != nil {
if tpm2.IsTPMSessionError(err, tpm2.ErrorAuthFail, tpm2.CommandObjectChangeAuth, 1) {
Expand Down
10 changes: 8 additions & 2 deletions tpm2/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ type keyDataPolicy interface {
//
// The NV index will be created with attributes that allow anyone to read the index, and an authorization
// policy that permits TPM2_NV_Increment with a signed authorization policy.
//
// If hmacSession is supplied, it is used for authenticating with the storage hierarchy, in order to avoid
// transmitting the cleartext auth value, and must have the AttrContinueSession attribute set
func createPcrPolicyCounterLegacy(tpm *tpm2.TPMContext, handle tpm2.Handle, updateKey *tpm2.Public, hmacSession tpm2.SessionContext) (public *tpm2.NVPublic, value uint64, err error) {
nameAlg := tpm2.HashAlgorithmSHA256

Expand Down Expand Up @@ -198,6 +201,9 @@ func createPcrPolicyCounterLegacy(tpm *tpm2.TPMContext, handle tpm2.Handle, upda
//
// The NV index will be created with attributes that allow anyone to read the index, and an authorization
// policy that permits TPM2_NV_Increment with a signed authorization policy.
//
// If hmacSession is supplied, it is used for authenticating with the storage hierarchy, in order to avoid
// transmitting the cleartext auth value, and must have the AttrContinueSession attribute set
var ensurePcrPolicyCounter = func(tpm *tpm2.TPMContext, handle tpm2.Handle, updateKey *tpm2.Public, hmacSession tpm2.SessionContext) (public *tpm2.NVPublic, err error) {
nameAlg := tpm2.HashAlgorithmSHA256

Expand All @@ -213,7 +219,7 @@ var ensurePcrPolicyCounter = func(tpm *tpm2.TPMContext, handle tpm2.Handle, upda
AuthPolicy: trial.GetDigest(),
Size: 8}

index, err := tpm.CreateResourceContextFromTPM(handle, hmacSession.IncludeAttrs(tpm2.AttrAudit))
index, err := tpm.CreateResourceContextFromTPM(handle)
switch {
case tpm2.IsResourceUnavailableError(err, handle):
// ok, need to create
Expand Down Expand Up @@ -247,7 +253,7 @@ var ensurePcrPolicyCounter = func(tpm *tpm2.TPMContext, handle tpm2.Handle, upda
}

// Initialize the index
if err := tpm.NVIncrement(index, index, policySession, hmacSession.IncludeAttrs(tpm2.AttrAudit)); err != nil {
if err := tpm.NVIncrement(index, index, policySession); err != nil {
return nil, err
}
case err != nil:
Expand Down
2 changes: 1 addition & 1 deletion tpm2/policy_v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func (p *keyDataPolicy_v1) SetPCRPolicyFrom(src keyDataPolicy) {
p.PCRData = src.(*keyDataPolicy_v1).PCRData
}

func (p *keyDataPolicy_v1) ExecutePCRPolicy(tpm *tpm2.TPMContext, policySession, hmacSession tpm2.SessionContext) error {
func (p *keyDataPolicy_v1) ExecutePCRPolicy(tpm *tpm2.TPMContext, policySession, _ tpm2.SessionContext) error {
if err := p.PCRData.executePcrAssertions(tpm, policySession); err != nil {
return xerrors.Errorf("cannot execute PCR assertions: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion tpm2/policy_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ func (p *keyDataPolicy_v3) SetPCRPolicyFrom(src keyDataPolicy) {
p.PCRData = src.(*keyDataPolicy_v3).PCRData
}

func (p *keyDataPolicy_v3) ExecutePCRPolicy(tpm *tpm2.TPMContext, policySession, hmacSession tpm2.SessionContext) error {
func (p *keyDataPolicy_v3) ExecutePCRPolicy(tpm *tpm2.TPMContext, policySession, _ tpm2.SessionContext) error {
if err := p.PCRData.executePcrAssertions(tpm, policySession); err != nil {
return xerrors.Errorf("cannot execute PCR assertions: %w", err)
}
Expand Down
29 changes: 26 additions & 3 deletions tpm2/provisioning.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ const (
ProvisionModeClear
)

// provisionPrimaryKey provisions a primary key in the specified hierarchy at the specified persistent
// handle. If session is supplied, it is expected to be a HMAC session with the AttrContinueSession
// attribute set, and is used for authenticating with the relevant hierarchies to avoid sending the
// authorization value in the clear.
func provisionPrimaryKey(tpm *tpm2.TPMContext, hierarchy tpm2.ResourceContext, template *tpm2.Public, handle tpm2.Handle, session tpm2.SessionContext) (tpm2.ResourceContext, error) {
obj, err := tpm.CreateResourceContextFromTPM(handle)
switch {
Expand Down Expand Up @@ -97,6 +101,11 @@ func provisionPrimaryKey(tpm *tpm2.TPMContext, hierarchy tpm2.ResourceContext, t
return obj, nil
}

// selectSrkTemplate chooses a template to use for the storage primary key. Either the default
// template will be returned or a custom one stored in a decidcated NV index. The supplied
// HMAC session is used for authenticating with the storage hierarchy and is used to avoid sending
// the authorization value in the clear.
// XXX: The NV index should be created with the TPMA_NV_AUTHREAD attribute to avoid this entirely.
func selectSrkTemplate(tpm *tpm2.TPMContext, session tpm2.SessionContext) *tpm2.Public {
nv, err := tpm.CreateResourceContextFromTPM(srkTemplateHandle)
if err != nil {
Expand Down Expand Up @@ -125,10 +134,17 @@ func selectSrkTemplate(tpm *tpm2.TPMContext, session tpm2.SessionContext) *tpm2.
return tmpl
}

// provisionStoragePrimaryKey provisions a storage primary key at the well known persistent
// handle. If session is supplied, it is expected to be a HMAC session with the AttrContinueSession
// attribute set, and is used for authenticating with the relevant hierarchies to avoid sending
// authorization values in the clear.
func provisionStoragePrimaryKey(tpm *tpm2.TPMContext, session tpm2.SessionContext) (tpm2.ResourceContext, error) {
return provisionPrimaryKey(tpm, tpm.OwnerHandleContext(), selectSrkTemplate(tpm, session), tcg.SRKHandle, session)
}

// storeSrkTemplate stores the supplied template at a well known handle. If session is supplied,
// it must be a HMAC session and is used for authenticating with the storage hierarchy to avoid sending
// authorization values in the clear.
func storeSrkTemplate(tpm *tpm2.TPMContext, template *tpm2.Public, session tpm2.SessionContext) error {
tmplB, err := mu.MarshalToBytes(template)
if err != nil {
Expand Down Expand Up @@ -156,6 +172,9 @@ func storeSrkTemplate(tpm *tpm2.TPMContext, template *tpm2.Public, session tpm2.
return nil
}

// removeStoredSrkTemplate removes the SRK template stored at the well known handle, if there
// is one. If a session is supplied, it must be a HMAC session and is used for authenticating
// with the storage hierarchy to avoid sending the authorization value in the clear.
func removeStoredSrkTemplate(tpm *tpm2.TPMContext, session tpm2.SessionContext) error {
nv, err := tpm.CreateResourceContextFromTPM(srkTemplateHandle)
switch {
Expand Down Expand Up @@ -189,6 +208,7 @@ func (t *Connection) ensureProvisionedInternal(mode ProvisionMode, newLockoutAut
return ErrTPMClearRequiresPPI
}

// Use HMAC session to authenticate with lockout hierarchy.
if err := t.Clear(t.LockoutHandleContext(), session); err != nil {
switch {
case isAuthFailError(err, tpm2.CommandClear, 1):
Expand Down Expand Up @@ -275,7 +295,8 @@ func (t *Connection) ensureProvisionedInternal(mode ProvisionMode, newLockoutAut

// Perform actions that require the lockout hierarchy authorization.

// Set the DA parameters.
// Set the DA parameters. Pass the HMAC session here so we don't supply the cleartext auth
// value for the lockout hierarchy.
if err := t.DictionaryAttackParameters(t.LockoutHandleContext(), maxTries, recoveryTime, lockoutRecovery, session); err != nil {
switch {
case isAuthFailError(err, tpm2.CommandDictionaryAttackParameters, 1):
Expand All @@ -286,13 +307,15 @@ func (t *Connection) ensureProvisionedInternal(mode ProvisionMode, newLockoutAut
return xerrors.Errorf("cannot configure dictionary attack parameters: %w", err)
}

// Disable owner clear
// Disable owner clear. Pass the HMAC session here so we don't supply the cleartext auth
// value for the lockout hierarchy.
if err := t.ClearControl(t.LockoutHandleContext(), true, session); err != nil {
// Lockout auth failure or lockout mode would have been caught by DictionaryAttackParameters
return xerrors.Errorf("cannot disable owner clear: %w", err)
}

// Set the lockout hierarchy authorization.
// Set the lockout hierarchy authorization. Use command parameter encryption here for the new value.
// Note that this only offers protections against passive interposers.
if err := t.HierarchyChangeAuth(t.LockoutHandleContext(), newLockoutAuth, session.IncludeAttrs(tpm2.AttrCommandEncrypt)); err != nil {
return xerrors.Errorf("cannot set the lockout hierarchy authorization value: %w", err)
}
Expand Down
6 changes: 6 additions & 0 deletions tpm2/seal.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ type makeSealedKeyDataParams struct {
AuthMode secboot.AuthMode
}

// makeSealedKeyData makes a sealed key data using the supplied parameters, keySealer implementation,
// and keyDataConstructor implementation.
//
// If supplied, the session must be a HMAC session with the AttrContinueSession attribute set and is
// used for authenticating the storage hierarchy in order to avoid trasmitting the cleartext authorization
// value.
func makeSealedKeyData(tpm *tpm2.TPMContext, params *makeSealedKeyDataParams, sealer keySealer, constructor keyDataConstructor, session tpm2.SessionContext) (*secboot.KeyData, secboot.PrimaryKey, secboot.DiskUnlockKey, error) {
// Create a primary key, if required.
primaryKey := params.PrimaryKey
Expand Down
20 changes: 12 additions & 8 deletions tpm2/tpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,18 @@ func (t *Connection) LockoutAuthSet() bool {
return tpm2.PermanentAttributes(value)&tpm2.AttrLockoutAuthSet > 0
}

// HmacSession returns a HMAC session instance which was created in order to conduct a proof-of-ownership check of the private part
// of the endorsement key on the TPM. It is retained in order to reduce the number of sessions that need to be created during unseal
// operations, and is created with a symmetric algorithm so that it is suitable for parameter encryption.
// If the connection was created with SecureConnectToDefaultTPM, the session is salted with a value protected by the public part
// of the key associated with the verified endorsement key certificate. The session key can only be retrieved by and used on the TPM
// for which the endorsement certificate was issued. If the connection was created with ConnectToDefaultTPM, the session may be
// salted with a value protected by the public part of the endorsement key if one exists or one is able to be created, but as the key
// is not associated with a verified credential, there is no guarantee that only the TPM is able to retrieve the session key.
// HmacSession returns a HMAC session with the AttrContinueSession attribute
// set. If an endorsement key exists, it is also salted with this and configured
// with parameter encryption. Note that this relies on reading the public area
// from the TPM and there is no validation of the endorsement key against the
// supplied manufacturer certificate, so this is vulnerable to active interposer
// type attacks where an adversary could provide a public area for a non-TPM protected
// key to us, whilst making it look like a TPM protected key, in order to perform MITM
// attacks. If used for parameter encryption, this only provides protection against
// passive interposer attacks. Other types of attacks are outside of the scope of this
// package due to limitations in the way that TPM2_Unseal works, and the fact that the
// platform firmware doesn't integrity protect commands that are critical to measured
// boot such as PCR extends.
func (t *Connection) HmacSession() tpm2.SessionContext {
if t.hmacSession == nil {
return nil
Expand Down
38 changes: 38 additions & 0 deletions tpm2/unseal.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ const (
//
// If a transient SRK is created, it is flushed from the TPM before this function
// returns.
//
// If session is supplied, it must be a HMAC session with the AttrContinueSession attribute
// set, used for authenticating the use of the storage hirearchy if a transient strorage
// primary key needs to be created, in order to avoid transmitting the cleartext authorzation
// value.
func (k *sealedKeyDataBase) loadForUnseal(tpm *tpm2.TPMContext, session tpm2.SessionContext) (keyObject tpm2.ResourceContext, policySession tpm2.SessionContext, err error) {
for try := tryPersistentSRK; try <= tryMax; try++ {
var srk tpm2.ResourceContext
Expand Down Expand Up @@ -98,6 +103,11 @@ func (k *sealedKeyDataBase) loadForUnseal(tpm *tpm2.TPMContext, session tpm2.Ses
}()

// Begin policy session with parameter encryption support and salted with the SRK.
// Note that this only provides protection against passive interposers, as active
// interposers can modify session attributes supplied in the TPM2_Unseal command and
// then redirect the command to an adversary supplied session for which they can calculate
// the HMAC for. Whilst the host CPU can detect this tampering (because the response
// HMAC will be invalid), this happens after the TPM has already provided the unsealed data.
symmetric := &tpm2.SymDef{
Algorithm: tpm2.SymAlgorithmAES,
KeyBits: &tpm2.SymKeyBitsU{Sym: 128},
Expand All @@ -114,6 +124,34 @@ func (k *sealedKeyDataBase) loadForUnseal(tpm *tpm2.TPMContext, session tpm2.Ses
return nil, nil, err
}

// unsealDataFromTPM unseals the data from this sealed object.
//
// Unsealing uses a policy session that is salted with the SRK that is used to protect the key
// and uses the same session for response encryption. Note that this only provides protection
// against passive interposer attacks. There is no effort to protection against active
// interposer attacks (where an advesary can modify communications) for a few reasons:
// - An adversary can remove the encrypt attribute from the session and redirect the command
// to a session for which they can compute the HMAC. The host CPU will detect this because the
// response HMAC will be invalid, but it's too late at this point. Using TPM2_EncryptDecrypt2
// with both command and response parameter encryption could mitigate this attack.
// - An adversary can modify and spoof PCR extends earlier in the boot chain, unless all
// critical commands like these are integrity protected with a session that is salted with a
// verified TPM key. This is not the case with any firmware today.
// - Protecting against an active interposer would require the use of a verified key. There is
// one - the endorsement key, but what happens if we don't have access to and can't download
// intermediate certs for verification? Does the boot fail? How is this communicated securely
// to the TPM?
// - The use of go's crypto/x509 for certificate verification doesn't permit disabling validity
// period valdidation. Hardware certificates will generally expire during the lifetime of a
// device and ignoring validity periods is generally quite normal here (eg, such as in UEFI
// secure boot).
// - An adversary could go one step further and just unsolder the discrete TPM and place
// it into a malcious platform that spoofs a specific host.
//
// If a session is supplied, it should be a HMAC session with the AttrContinueSession
// attribute set, used for authenticating use of the storage hierarchy if a transient
// storage primary key needs to be created, in order to avoid transmitting the cleartext
// authorization value.
func (k *sealedKeyDataBase) unsealDataFromTPM(tpm *tpm2.TPMContext, authValue []byte, hmacSession tpm2.SessionContext) (data []byte, err error) {
// Check if the TPM is in lockout mode
props, err := tpm.GetCapabilityTPMProperties(tpm2.PropertyPermanent, 1)
Expand Down

0 comments on commit 416fbe2

Please sign in to comment.