From 2d39fa82c20616c8c13c6c9da01777698acb682d Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Fri, 11 Sep 2020 13:44:30 +0100 Subject: [PATCH 1/2] Remove ProvisionStatus and refactor provision API ProvisionStatus is a problematic API - AttrValidSRK and AttrValidEK indicate that objects with the expected public areas exist at the expected handles, but can't indicate whether those objects were created with the correct templates and are actually valid objects. These attributes can't be used to make a decision about whether ProvisionTPM should be called or not, so just remove the ProvisionStatus API entirely. Rename ProvisionTPM to EnsureProvisioned (and make it a method of TPMConnection), and introduce a new error (ErrTPMProvisioningRequiresLockout) which is returned from EnsureProvisioned if it is called with ProvisionModeWithoutLockout but use of the lockout hierarchy is required to fully provision the TPM. This new error is an indication that EnsureProvisioned needs to be called again with a different mode in order to fully provision it. --- crypt.go | 2 +- crypt_test.go | 8 +- errors.go | 7 +- keydata_test.go | 2 +- pin_test.go | 2 +- provisioning.go | 240 ++++++++++-------------------- provisioning_test.go | 191 +++--------------------- seal_test.go | 12 +- tools/gen-compattest-data/main.go | 2 +- tpm_test.go | 8 +- unseal_test.go | 8 +- 11 files changed, 129 insertions(+), 353 deletions(-) diff --git a/crypt.go b/crypt.go index 9c3ca4a3..9521d951 100644 --- a/crypt.go +++ b/crypt.go @@ -321,7 +321,7 @@ func unsealKeyFromTPM(tpm *TPMConnection, k *SealedKeyObject, pin string) ([]byt // has a null authorization value, then this will allow us to unseal the key without requiring any type of manual recovery. If the // storage hierarchy has a non-null authorization value, ProvionTPM will fail. If the TPM owner has changed, ProvisionTPM might // succeed, but UnsealFromTPM will fail with InvalidKeyFileError when retried. - if pErr := ProvisionTPM(tpm, ProvisionModeWithoutLockout, nil); pErr == nil { + if pErr := tpm.EnsureProvisioned(ProvisionModeWithoutLockout, nil); pErr == nil || pErr == ErrTPMProvisioningRequiresLockout { key, err = k.UnsealFromTPM(tpm, pin) } } diff --git a/crypt_test.go b/crypt_test.go index 6f35eed0..aeefa14a 100644 --- a/crypt_test.go +++ b/crypt_test.go @@ -244,7 +244,7 @@ type cryptTPMTestBase struct { func (ctb *cryptTPMTestBase) setUpTestBase(c *C, ttb *testutil.TPMTestBase) { ctb.cryptTestBase.setUpTestBase(c, &ttb.BaseTest) - c.Assert(ProvisionTPM(ttb.TPM, ProvisionModeFull, nil), IsNil) + c.Assert(ttb.TPM.EnsureProvisioned(ProvisionModeFull, nil), IsNil) dir := c.MkDir() ctb.keyFile = dir + "/keydata" @@ -558,7 +558,7 @@ func (s *cryptTPMSuite) TestActivateVolumeWithTPMSealedKeyErrorHandling4(c *C) { // Test that recovery fallback works with the TPM in DA lockout mode. c.Assert(s.TPM.DictionaryAttackParameters(s.TPM.LockoutHandleContext(), 0, 7200, 86400, nil), IsNil) defer func() { - c.Check(ProvisionTPM(s.TPM, ProvisionModeFull, nil), IsNil) + c.Check(s.TPM.EnsureProvisioned(ProvisionModeFull, nil), IsNil) }() s.testActivateVolumeWithTPMSealedKeyErrorHandling(c, &testActivateVolumeWithTPMSealedKeyErrorHandlingData{ @@ -620,7 +620,7 @@ func (s *cryptTPMSuite) TestActivateVolumeWithTPMSealedKeyErrorHandling7(c *C) { // Test that activation fails if RecoveryKeyTries is zero. c.Assert(s.TPM.DictionaryAttackParameters(s.TPM.LockoutHandleContext(), 0, 7200, 86400, nil), IsNil) defer func() { - c.Check(ProvisionTPM(s.TPM, ProvisionModeFull, nil), IsNil) + c.Check(s.TPM.EnsureProvisioned(ProvisionModeFull, nil), IsNil) }() s.testActivateVolumeWithTPMSealedKeyErrorHandling(c, &testActivateVolumeWithTPMSealedKeyErrorHandlingData{ @@ -635,7 +635,7 @@ func (s *cryptTPMSuite) TestActivateVolumeWithTPMSealedKeyErrorHandling8(c *C) { // Test that activation fails if the wrong recovery key is provided. c.Assert(s.TPM.DictionaryAttackParameters(s.TPM.LockoutHandleContext(), 0, 7200, 86400, nil), IsNil) defer func() { - c.Check(ProvisionTPM(s.TPM, ProvisionModeFull, nil), IsNil) + c.Check(s.TPM.EnsureProvisioned(ProvisionModeFull, nil), IsNil) }() s.testActivateVolumeWithTPMSealedKeyErrorHandling(c, &testActivateVolumeWithTPMSealedKeyErrorHandlingData{ diff --git a/errors.go b/errors.go index d65758d2..b39721eb 100644 --- a/errors.go +++ b/errors.go @@ -29,10 +29,15 @@ import ( ) var ( - // ErrTPMClearRequiresPPI is returned from ProvisionTPM and indicates that clearing the TPM must be performed via + // ErrTPMClearRequiresPPI is returned from TPMConnection.EnsureProvisioned and indicates that clearing the TPM must be performed via // the Physical Presence Interface. ErrTPMClearRequiresPPI = errors.New("clearing the TPM requires the use of the Physical Presence Interface") + // ErrTPMProvisioningRequiresLockout is returned from TPMConnection.EnsureProvisioned when fully provisioning the TPM requires + // the use of the lockout hierarchy. In this case, the provisioning steps that can be performed without the use of the lockout + // hierarchy are completed. + ErrTPMProvisioningRequiresLockout = errors.New("provisioning the TPM requires the use of the lockout hierarchy") + // ErrTPMProvisioning indicates that the TPM is not provisioned correctly for the requested operation. Please note that other errors // that can be returned may also be caused by incomplete provisioning, as it is not always possible to detect incomplete or // incorrect provisioning in all contexts. diff --git a/keydata_test.go b/keydata_test.go index 13cb62a6..fbf299f1 100644 --- a/keydata_test.go +++ b/keydata_test.go @@ -36,7 +36,7 @@ type keyDataSuite struct { var _ = Suite(&keyDataSuite{}) func (s *keyDataSuite) TestValidateAfterLock(c *C) { - c.Assert(ProvisionTPM(s.TPM, ProvisionModeFull, nil), IsNil) + c.Assert(s.TPM.EnsureProvisioned(ProvisionModeFull, nil), IsNil) key := make([]byte, 64) rand.Read(key) diff --git a/pin_test.go b/pin_test.go index 99751501..3caac814 100644 --- a/pin_test.go +++ b/pin_test.go @@ -172,7 +172,7 @@ func (s *pinSuite) SetUpSuite(c *C) { func (s *pinSuite) SetUpTest(c *C) { s.TPMTestBase.SetUpTest(c) - c.Assert(ProvisionTPM(s.TPM, ProvisionModeFull, nil), IsNil) + c.Assert(s.TPM.EnsureProvisioned(ProvisionModeFull, nil), IsNil) dir := c.MkDir() s.keyFile = dir + "/keydata" diff --git a/provisioning.go b/provisioning.go index 16433b2e..a103fc1e 100644 --- a/provisioning.go +++ b/provisioning.go @@ -20,7 +20,7 @@ package secboot import ( - "bytes" + "errors" "fmt" "os" @@ -43,47 +43,22 @@ const ( lockoutRecovery uint32 = 86400 ) -// ProvisionStatusAttributes correspond to the state of the TPM with regards to provisioning for full disk encryption. -type ProvisionStatusAttributes int - -const ( - // AttrValidSRK indicates that the TPM contains a valid primary storage key with the expected properties at the - // expected location. Note that this does not mean that the object was created with the same template that ProvisionTPM - // uses, and is no guarantee that a call to ProvisionTPM wouldn't result in a different key being created. - AttrValidSRK ProvisionStatusAttributes = 1 << iota - - // AttrValidEK indicates that the TPM contains a valid endorsement key at the expected location. On a TPMConnection created - // with SecureConnectToDefaultTPM, it means that the TPM contains the key associated with the verified endorsement certificate. - // On a TPMConnection created with ConnectToDefaultTPM, it means that the TPM contains a valid primary key with the expected - // properties at the expected location, but does not mean that the object was created with the the same template that - // ProvisionTPM uses, and is no guarantee that a call to ProvisionTPM wouldn't result in a different key being created. - AttrValidEK - - AttrDAParamsOK // The dictionary attack lockout parameters are configured correctly. - AttrOwnerClearDisabled // The ability to clear the TPM with owner authorization is disabled. - - // AttrLockoutAuthSet indicates that the lockout hierarchy has an authorization value defined. This - // doesn't necessarily mean that the authorization value is the same one that was originally provided - // to ProvisionTPM - it could have been changed outside of our control. - AttrLockoutAuthSet - - AttrValidLockNVIndex // The TPM has a valid NV index used for locking access to keys sealed with SealKeyToTPM -) - -// ProvisionMode is used to control the behaviour of ProvisionTPM. +// ProvisionMode is used to control the behaviour of TPMConnection.EnsureProvisioned. type ProvisionMode int const ( - // ProvisionModeClear specifies that the TPM should be fully provisioned after clearing it. - ProvisionModeClear ProvisionMode = iota + // ProvisionModeWithoutLockout specifies that the TPM should be refreshed without performing operations that require the use of the + // lockout hierarchy. Operations that won't be performed in this mode are disabling owner clear, configuring the dictionary attack + // parameters, and setting the authorization value for the lockout hierarchy. + ProvisionModeWithoutLockout ProvisionMode = iota - // ProvisionModeWithoutLockout specifies that the TPM should be refreshed without performing operations that require knowledge of - // the lockout hierarchy authorization value. Operations that won't be performed in this mode are disabling owner clear, configuring - // the dictionary attack parameters and setting the authorization value for the lockout hierarchy. - ProvisionModeWithoutLockout - - // ProvisionModeFull specifies that the TPM should be fully provisioned without clearing it. + // ProvisionModeFull specifies that the TPM should be fully provisioned without clearing it. This requires use of the lockout + // hierarchy. ProvisionModeFull + + // ProvisionModeClear specifies that the TPM should be fully provisioned after clearing it. This requires use of the lockout + // hierarchy. + ProvisionModeClear ) func provisionPrimaryKey(tpm *tpm2.TPMContext, hierarchy tpm2.ResourceContext, template *tpm2.Public, handle tpm2.Handle, session tpm2.SessionContext) (tpm2.ResourceContext, error) { @@ -115,31 +90,38 @@ func provisionPrimaryKey(tpm *tpm2.TPMContext, hierarchy tpm2.ResourceContext, t return obj, nil } -// ProvisionTPM prepares the TPM associated with the tpm parameter for full disk encryption. The mode parameter specifies the -// behaviour of this function. +// EnsureProvisioned prepares the TPM for full disk encryption. The mode parameter specifies the behaviour of this function. // // If mode is ProvisionModeClear, this function will attempt to clear the TPM before provisioning it. If owner clear has been // disabled (which will be the case if the TPM has previously been provisioned with this function), then ErrTPMClearRequiresPPI // will be returned. In this case, the TPM must be cleared via the physical presence interface by calling RequestTPMClearUsingPPI -// and performing a system restart. +// and performing a system restart. Note that clearing the TPM makes all previously sealed keys permanently unrecoverable. This +// mode should normally be used when resetting a device to factory settings (ie, performing a new installation). +// +// If mode is ProvisionModeClear or ProvisionModeFull, then the authorization value for the lockout hierarchy will be set to +// newLockoutAuth, owner clear will be disabled, and the parameters of the TPM's dictionary attack logic will be configured to +// appropriate values. // -// If mode is ProvisionModeClear or ProvisionModeFull then the authorization value for the lockout hierarchy will be set to -// newLockoutAuth, owner clear will be disabled, and the parameters of the TPM's dictionary attack logic will be configured. These -// operations require knowledge of the lockout hierarchy authorization value, which must be provided by calling +// If mode is ProvisionModeClear or ProvisionModeFull, this function performs operations that require the use of the lockout +// hierarchy (detailed above), and knowledge of the lockout hierarchy's authorization value. This must be provided by calling // TPMConnection.LockoutHandleContext().SetAuthValue() prior to this call. If the wrong lockout hierarchy authorization value is // provided, then a AuthFailError error will be returned. If this happens, the TPM will have entered dictionary attack lockout mode // for the lockout hierarchy. Further calls will result in a ErrTPMLockout error being returned. The only way to recover from this is // to either wait for the pre-programmed recovery time to expire, or to clear the TPM via the physical presence interface by calling -// RequestTPMClearUsingPPI. If the lockout hierarchy authorization value is not known or the caller wants to skip the operations that -// require use of the lockout hierarchy, then mode can be set to ProvisionModeWithoutLockout. +// RequestTPMClearUsingPPI. If the lockout hierarchy authorization value is not known then mode should be set to +// ProvisionModeWithoutLockout, with the caveat that this mode cannot fully provision the TPM. // -// If mode is ProvisionModeFull or ProvisionModeWithoutLockout, this function performs operations that require knowledge of the -// storage and endorsement hierarchies (creation of primary keys and NV indices, detailed below). Whilst these will be empty after -// clearing the TPM, if they have been set since clearing the TPM then they will need to be provided by calling -// TPMConnection.EndorsementHandleContext().SetAuthValue() and TPMConnection.OwnerHandleContext().SetAuthValue() prior to calling -// this function. If the wrong value is provided for either authorization, then a AuthFailError error will be returned. If the correct -// authorization values are not known, then the only way to recover from this is to clear the TPM either by calling this function with -// mode set to ProvisionModeClear, or by using the physical presence interface. +// If mode is ProvisionModeFull or ProvisionModeWithoutLockout, this function will not affect the ability to recover sealed keys that +// can currently be recovered. +// +// In all modes, this function performs operations that require the use of the storage and endorsement hierarchies (creation of +// primary keys and NV indices, detailed below). If mode is ProvisionModeFull or ProvisionModeWithoutLockout, then knowledge of the +// authorization values for those hierarchies is required. Whilst these will be empty after clearing the TPM, if they have been set +// since clearing the TPM then they will need to be provided by calling TPMConnection.EndorsementHandleContext().SetAuthValue() and +// TPMConnection.OwnerHandleContext().SetAuthValue() prior to calling this function. If the wrong value is provided for either +// authorization, then a AuthFailError error will be returned. If the correct authorization values are not known, then the only way +// to recover from this is to clear the TPM either by calling this function with mode set to ProvisionModeClear (and providing the +// correct authorization value for the lockout hierarchy), or by using the physical presence interface. // // In all modes, this function will create and persist both a storage root key and an endorsement key. Both of these will be created // using the RSA templates defined in and persisted at the handles specified in the "TCG EK Credential Profile for TPM Family 2.0" @@ -150,27 +132,28 @@ func provisionPrimaryKey(tpm *tpm2.TPMContext, hierarchy tpm2.ResourceContext, t // These indices will be created at handles 0x01801100 and 0x01801101. If there are already NV indices defined at either of the // required handles but they don't meet the requirements of this function, a TPMResourceExistsError error will be returned. In this // case, the caller will either need to manually undefine these using TPMConnection.NVUndefineSpace, or clear the TPM. -func ProvisionTPM(tpm *TPMConnection, mode ProvisionMode, newLockoutAuth []byte) error { - status, err := ProvisionStatus(tpm) +// +// If mode is ProvisionModeWithoutLockout but the TPM indicates that use of the lockout hierarchy is required to fully provision the +// TPM (eg, to disable owner clear, set the lockout hierarchy authorization value or configure the DA lockout parameters), then a +// ErrTPMProvisioningRequiresLockout error will be returned. In this scenario, the function will complete all operations that can be +// completed without using the lockout hierarchy, but the function should be called again either with mode set to ProvisionModeFull +// (if the authorization value for the lockout hierarchy is known), or ProvisionModeClear. +func (t *TPMConnection) EnsureProvisioned(mode ProvisionMode, newLockoutAuth []byte) error { + session := t.HmacSession() + + props, err := t.GetCapabilityTPMProperties(tpm2.PropertyPermanent, 1, session.IncludeAttrs(tpm2.AttrAudit)) if err != nil { - return xerrors.Errorf("cannot determine the current TPM status: %w", err) + return xerrors.Errorf("cannot fetch permanent properties: %w", err) } - - // Create an initial session for HMAC authorizations - session, err := tpm.StartAuthSession(nil, nil, tpm2.SessionTypeHMAC, nil, defaultSessionHashAlgorithm, nil) - if err != nil { - return xerrors.Errorf("cannot start session: %w", err) + if props[0].Property != tpm2.PropertyPermanent { + return errors.New("TPM returned value for the wrong property") } - defer tpm.FlushContext(session) - - session.SetAttrs(tpm2.AttrContinueSession) - if mode == ProvisionModeClear { - if status&AttrOwnerClearDisabled > 0 { + if tpm2.PermanentAttributes(props[0].Value)&tpm2.AttrDisableClear > 0 { return ErrTPMClearRequiresPPI } - if err := tpm.Clear(tpm.LockoutHandleContext(), session); err != nil { + if err := t.Clear(t.LockoutHandleContext(), session); err != nil { switch { case isAuthFailError(err, tpm2.CommandClear, 1): return AuthFailError{tpm2.HandleLockout} @@ -179,12 +162,10 @@ func ProvisionTPM(tpm *TPMConnection, mode ProvisionMode, newLockoutAuth []byte) } return xerrors.Errorf("cannot clear the TPM: %w", err) } - - status = 0 } // Provision an endorsement key - if _, err := provisionPrimaryKey(tpm.TPMContext, tpm.EndorsementHandleContext(), tcg.EKTemplate, tcg.EKHandle, session); err != nil { + if _, err := provisionPrimaryKey(t.TPMContext, t.EndorsementHandleContext(), tcg.EKTemplate, tcg.EKHandle, session); err != nil { switch { case isAuthFailError(err, tpm2.CommandEvictControl, 1): return AuthFailError{tpm2.HandleOwner} @@ -195,20 +176,19 @@ func ProvisionTPM(tpm *TPMConnection, mode ProvisionMode, newLockoutAuth []byte) } } - // Close the existing session and create a new session that's salted with a value protected with the newly provisioned EK. + // Reinitialize the connection, which creates a new session that's salted with a value protected with the newly provisioned EK. // This will have a symmetric algorithm for parameter encryption during HierarchyChangeAuth. - tpm.FlushContext(session) - if err := tpm.init(); err != nil { + if err := t.init(); err != nil { var verifyErr verificationError if xerrors.As(err, &verifyErr) { return TPMVerificationError{fmt.Sprintf("cannot reinitialize TPM connection after provisioning endorsement key: %v", err)} } return xerrors.Errorf("cannot reinitialize TPM connection after provisioning endorsement key: %w", err) } - session = tpm.HmacSession() + session = t.HmacSession() // Provision a storage root key - srk, err := provisionPrimaryKey(tpm.TPMContext, tpm.OwnerHandleContext(), tcg.SRKTemplate, tcg.SRKHandle, session) + srk, err := provisionPrimaryKey(t.TPMContext, t.OwnerHandleContext(), tcg.SRKTemplate, tcg.SRKHandle, session) if err != nil { switch { case isAuthFailError(err, tpm2.AnyCommandCode, 1): @@ -217,10 +197,10 @@ func ProvisionTPM(tpm *TPMConnection, mode ProvisionMode, newLockoutAuth []byte) return xerrors.Errorf("cannot provision storage root key: %w", err) } } - tpm.provisionedSrk = srk + t.provisionedSrk = srk // Provision a lock NV index - if err := ensureLockNVIndex(tpm.TPMContext, session); err != nil { + if err := ensureLockNVIndex(t.TPMContext, session); err != nil { var e *tpmErrorWithHandle if tpm2.IsTPMError(err, tpm2.ErrorNVDefined, tpm2.AnyCommandCode) && xerrors.As(err, &e) { return TPMResourceExistsError{e.handle} @@ -229,13 +209,36 @@ func ProvisionTPM(tpm *TPMConnection, mode ProvisionMode, newLockoutAuth []byte) } if mode == ProvisionModeWithoutLockout { + props, err := t.GetCapabilityTPMProperties(tpm2.PropertyPermanent, 1, session.IncludeAttrs(tpm2.AttrAudit)) + if err != nil { + return xerrors.Errorf("cannot fetch permanent properties to determine if lockout hierarchy is required: %w", err) + } + if props[0].Property != tpm2.PropertyPermanent { + return errors.New("TPM returned value for the wrong property") + } + required := tpm2.AttrLockoutAuthSet | tpm2.AttrDisableClear + if tpm2.PermanentAttributes(props[0].Value)&required != required { + return ErrTPMProvisioningRequiresLockout + } + + props, err = t.GetCapabilityTPMProperties(tpm2.PropertyMaxAuthFail, 3, session.IncludeAttrs(tpm2.AttrAudit)) + if err != nil { + return xerrors.Errorf("cannot fetch DA parameters to determine if lockout hierarchy is required: %w", err) + } + if props[0].Property != tpm2.PropertyMaxAuthFail || props[1].Property != tpm2.PropertyLockoutInterval || props[2].Property != tpm2.PropertyLockoutRecovery { + return errors.New("TPM returned values for the wrong properties") + } + if props[0].Value > maxTries || props[1].Value < recoveryTime || props[2].Value < lockoutRecovery { + return ErrTPMProvisioningRequiresLockout + } + return nil } // Perform actions that require the lockout hierarchy authorization. // Set the DA parameters. - if err := tpm.DictionaryAttackParameters(tpm.LockoutHandleContext(), maxTries, recoveryTime, lockoutRecovery, session); err != nil { + if err := t.DictionaryAttackParameters(t.LockoutHandleContext(), maxTries, recoveryTime, lockoutRecovery, session); err != nil { switch { case isAuthFailError(err, tpm2.CommandDictionaryAttackParameters, 1): return AuthFailError{tpm2.HandleLockout} @@ -246,14 +249,13 @@ func ProvisionTPM(tpm *TPMConnection, mode ProvisionMode, newLockoutAuth []byte) } // Disable owner clear - if err := tpm.ClearControl(tpm.LockoutHandleContext(), true, session); err != nil { + 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. - if err := tpm.HierarchyChangeAuth(tpm.LockoutHandleContext(), tpm2.Auth(newLockoutAuth), - session.IncludeAttrs(tpm2.AttrCommandEncrypt)); err != nil { + 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) } @@ -276,81 +278,3 @@ func RequestTPMClearUsingPPI() error { return nil } - -// ProvisionStatus returns the provisioning status for the specified TPM. -func ProvisionStatus(tpm *TPMConnection) (ProvisionStatusAttributes, error) { - var out ProvisionStatusAttributes - - session := tpm.HmacSession().IncludeAttrs(tpm2.AttrAudit) - - ek, err := tpm.CreateResourceContextFromTPM(tcg.EKHandle, session) - switch { - case err != nil && !tpm2.IsResourceUnavailableError(err, tcg.EKHandle): - // Unexpected error - return 0, err - case tpm2.IsResourceUnavailableError(err, tcg.EKHandle): - // Nothing to do - default: - if ekInit, err := tpm.EndorsementKey(); err == nil && bytes.Equal(ekInit.Name(), ek.Name()) { - out |= AttrValidEK - } - } - - srk, err := tpm.CreateResourceContextFromTPM(tcg.SRKHandle, session) - switch { - case err != nil && !tpm2.IsResourceUnavailableError(err, tcg.SRKHandle): - // Unexpected error - return 0, err - case tpm2.IsResourceUnavailableError(err, tcg.SRKHandle): - // Nothing to do - case tpm.provisionedSrk != nil: - // ProvisionTPM has been called with this TPMConnection. Make sure it's the same object - if bytes.Equal(tpm.provisionedSrk.Name(), srk.Name()) { - out |= AttrValidSRK - } - default: - // ProvisionTPM hasn't been called with this TPMConnection, but there is an object at tcg.SRKHandle. Make sure it looks like a storage - // primary key. - ok, err := isObjectPrimaryKeyWithTemplate(tpm.TPMContext, tpm.OwnerHandleContext(), srk, tcg.SRKTemplate, tpm.HmacSession()) - switch { - case err != nil: - return 0, xerrors.Errorf("cannot determine if object at %v is a primary key in the storage hierarchy: %w", tcg.SRKHandle, err) - case ok: - out |= AttrValidSRK - } - } - - props, err := tpm.GetCapabilityTPMProperties(tpm2.PropertyMaxAuthFail, 3) - if err != nil { - return 0, xerrors.Errorf("cannot fetch DA parameters: %w", err) - } - if props[0].Value <= maxTries && props[1].Value >= recoveryTime && props[2].Value >= lockoutRecovery { - out |= AttrDAParamsOK - } - - props, err = tpm.GetCapabilityTPMProperties(tpm2.PropertyPermanent, 1) - if err != nil { - return 0, xerrors.Errorf("cannot fetch permanent properties: %w", err) - } - if tpm2.PermanentAttributes(props[0].Value)&tpm2.AttrDisableClear > 0 { - out |= AttrOwnerClearDisabled - } - if tpm2.PermanentAttributes(props[0].Value)&tpm2.AttrLockoutAuthSet > 0 { - out |= AttrLockoutAuthSet - } - - lockIndex, err := tpm.CreateResourceContextFromTPM(lockNVHandle, session) - switch { - case err != nil && !tpm2.IsResourceUnavailableError(err, lockNVHandle): - // Unexpected error - return 0, err - case tpm2.IsResourceUnavailableError(err, lockNVHandle): - // Nothing to do - default: - if _, err := readAndValidateLockNVIndexPublic(tpm.TPMContext, lockIndex, session); err == nil { - out |= AttrValidLockNVIndex - } - } - - return out, nil -} diff --git a/provisioning_test.go b/provisioning_test.go index 9fa2075b..e700a8c4 100644 --- a/provisioning_test.go +++ b/provisioning_test.go @@ -150,8 +150,8 @@ func TestProvisionNewTPM(t *testing.T) { origEk, _ := tpm.EndorsementKey() origHmacSession := tpm.HmacSession() - if err := ProvisionTPM(tpm, data.mode, lockoutAuth); err != nil { - t.Fatalf("ProvisionTPM failed: %v", err) + if err := tpm.EnsureProvisioned(data.mode, lockoutAuth); err != nil { + t.Fatalf("EnsureProvisioned failed: %v", err) } validateEK(t, tpm.TPMContext) @@ -369,12 +369,12 @@ func TestProvisionErrorHandling(t *testing.T) { tpm.OwnerHandleContext().SetAuthValue(nil) tpm.EndorsementHandleContext().SetAuthValue(nil) - err := ProvisionTPM(tpm, data.mode, nil) + err := tpm.EnsureProvisioned(data.mode, nil) if err == nil { - t.Fatalf("ProvisionTPM should have returned an error") + t.Fatalf("EnsureProvisioned should have returned an error") } if err != data.err { - t.Errorf("ProvisionTPM returned an unexpected error: %v", err) + t.Errorf("EnsureProvisioned returned an unexpected error: %v", err) } }) } @@ -402,8 +402,8 @@ func TestRecreateEK(t *testing.T) { lockoutAuth := []byte("1234") - if err := ProvisionTPM(tpm, ProvisionModeFull, lockoutAuth); err != nil { - t.Fatalf("ProvisionTPM failed: %v", err) + if err := tpm.EnsureProvisioned(ProvisionModeFull, lockoutAuth); err != nil { + t.Fatalf("EnsureProvisioned failed: %v", err) } ek, err := tpm.EndorsementKey() @@ -426,8 +426,8 @@ func TestRecreateEK(t *testing.T) { t.Errorf("EvictControl failed: %v", err) } - if err := ProvisionTPM(tpm, data.mode, lockoutAuth); err != nil { - t.Fatalf("ProvisionTPM failed: %v", err) + if err := tpm.EnsureProvisioned(data.mode, lockoutAuth); err != nil { + t.Fatalf("EnsureProvisioned failed: %v", err) } validateEK(t, tpm.TPMContext) @@ -475,8 +475,8 @@ func TestRecreateSRK(t *testing.T) { lockoutAuth := []byte("1234") - if err := ProvisionTPM(tpm, ProvisionModeFull, lockoutAuth); err != nil { - t.Fatalf("ProvisionTPM failed: %v", err) + if err := tpm.EnsureProvisioned(ProvisionModeFull, lockoutAuth); err != nil { + t.Fatalf("EnsureProvisioned failed: %v", err) } srk, err := tpm.CreateResourceContextFromTPM(tcg.SRKHandle) @@ -488,8 +488,8 @@ func TestRecreateSRK(t *testing.T) { t.Errorf("EvictControl failed: %v", err) } - if err := ProvisionTPM(tpm, data.mode, lockoutAuth); err != nil { - t.Fatalf("ProvisionTPM failed: %v", err) + if err := tpm.EnsureProvisioned(data.mode, lockoutAuth); err != nil { + t.Fatalf("EnsureProvisioned failed: %v", err) } validateSRK(t, tpm.TPMContext) @@ -512,8 +512,8 @@ func TestProvisionWithEndorsementAuth(t *testing.T) { t.Fatalf("HierarchyChangeAuth failed: %v", err) } - if err := ProvisionTPM(tpm, ProvisionModeFull, nil); err != nil { - t.Fatalf("ProvisionTPM failed: %v", err) + if err := tpm.EnsureProvisioned(ProvisionModeFull, nil); err != nil { + t.Fatalf("EnsureProvisioned failed: %v", err) } validateEK(t, tpm.TPMContext) @@ -535,8 +535,8 @@ func TestProvisionWithOwnerAuth(t *testing.T) { t.Fatalf("HierarchyChangeAuth failed: %v", err) } - if err := ProvisionTPM(tpm, ProvisionModeClear, nil); err != nil { - t.Fatalf("ProvisionTPM failed: %v", err) + if err := tpm.EnsureProvisioned(ProvisionModeFull, nil); err != nil { + t.Fatalf("EnsureProvisioned failed: %v", err) } validateEK(t, tpm.TPMContext) @@ -558,9 +558,9 @@ func TestProvisionWithInvalidEkCert(t *testing.T) { restore := testutil.MockEKTemplate(ekTemplate) defer restore() - err := ProvisionTPM(tpm, ProvisionModeFull, nil) + err := tpm.EnsureProvisioned(ProvisionModeFull, nil) if err == nil { - t.Fatalf("ProvisionTPM should have returned an error") + t.Fatalf("EnsureProvisioned should have returned an error") } var ve TPMVerificationError if !xerrors.As(err, &ve) && err.Error() != "verification of the TPM failed: cannot verify TPM: endorsement key returned from the "+ @@ -568,156 +568,3 @@ func TestProvisionWithInvalidEkCert(t *testing.T) { t.Errorf("ProvisionTPM returned an unexpected error: %v", err) } } - -func TestProvisionStatus(t *testing.T) { - tpm, _ := openTPMSimulatorForTesting(t) - defer func() { - clearTPMWithPlatformAuth(t, tpm) - closeTPM(t, tpm) - }() - - clearTPMWithPlatformAuth(t, tpm) - - status, err := ProvisionStatus(tpm) - if err != nil { - t.Errorf("ProvisionStatus failed: %v", err) - } - if status != 0 { - t.Errorf("Unexpected status") - } - - lockoutAuth := []byte("1234") - - if err := ProvisionTPM(tpm, ProvisionModeClear, lockoutAuth); err != nil { - t.Fatalf("ProvisionTPM failed: %v", err) - } - - status, err = ProvisionStatus(tpm) - if err != nil { - t.Errorf("ProvisionStatus failed: %v", err) - } - expected := AttrValidEK | AttrValidSRK | AttrDAParamsOK | AttrOwnerClearDisabled | AttrLockoutAuthSet | AttrValidLockNVIndex - if status != expected { - t.Errorf("Unexpected status %d", status) - } - - lockIndex, err := tpm.CreateResourceContextFromTPM(LockNVHandle) - if err != nil { - t.Fatalf("CreateResourceContextFromTPM failed: %v", err) - } - if err := tpm.NVUndefineSpace(tpm.OwnerHandleContext(), lockIndex, nil); err != nil { - t.Errorf("NVUndefineSpace failed: %v", err) - } - - status, err = ProvisionStatus(tpm) - if err != nil { - t.Errorf("ProvisionStatus failed: %v", err) - } - expected = AttrValidEK | AttrValidSRK | AttrDAParamsOK | AttrOwnerClearDisabled | AttrLockoutAuthSet - if status != expected { - t.Errorf("Unexpected status %d", status) - } - - if err := tpm.HierarchyChangeAuth(tpm.LockoutHandleContext(), nil, nil); err != nil { - t.Errorf("HierarchyChangeAuth failed: %v", err) - } - - status, err = ProvisionStatus(tpm) - if err != nil { - t.Errorf("ProvisionStatus failed: %v", err) - } - expected = AttrValidEK | AttrValidSRK | AttrDAParamsOK | AttrOwnerClearDisabled - if status != expected { - t.Errorf("Unexpected status %d", status) - } - - if err := tpm.ClearControl(tpm.PlatformHandleContext(), false, nil); err != nil { - t.Errorf("ClearControl failed: %v", err) - } - - status, err = ProvisionStatus(tpm) - if err != nil { - t.Errorf("ProvisionStatus failed: %v", err) - } - expected = AttrValidEK | AttrValidSRK | AttrDAParamsOK - if status != expected { - t.Errorf("Unexpected status %d", status) - } - - if err := tpm.DictionaryAttackParameters(tpm.LockoutHandleContext(), 3, 0, 0, nil); err != nil { - t.Errorf("DictionaryAttackParameters failed: %v", err) - } - - status, err = ProvisionStatus(tpm) - if err != nil { - t.Errorf("ProvisionStatus failed: %v", err) - } - expected = AttrValidEK | AttrValidSRK - if status != expected { - t.Errorf("Unexpected status %d", status) - } - - srkContext, err := tpm.CreateResourceContextFromTPM(tcg.SRKHandle) - if err != nil { - t.Fatalf("No SRK context: %v", err) - } - if _, err := tpm.EvictControl(tpm.OwnerHandleContext(), srkContext, srkContext.Handle(), nil); err != nil { - t.Errorf("EvictControl failed: %v", err) - } - - status, err = ProvisionStatus(tpm) - if err != nil { - t.Errorf("ProvisionStatus failed: %v", err) - } - expected = AttrValidEK - if status != expected { - t.Errorf("Unexpected status %d", status) - } - - ekContext, err := tpm.CreateResourceContextFromTPM(tcg.EKHandle) - if err != nil { - t.Fatalf("No EK context: %v", err) - } - if _, err := tpm.EvictControl(tpm.OwnerHandleContext(), ekContext, ekContext.Handle(), nil); err != nil { - t.Errorf("EvictControl failed: %v", err) - } - - status, err = ProvisionStatus(tpm) - if err != nil { - t.Errorf("ProvisionStatus failed: %v", err) - } - expected = 0 - if status != expected { - t.Errorf("Unexpected status %d", status) - } - - primary, _, _, _, _, err := tpm.CreatePrimary(tpm.OwnerHandleContext(), nil, tcg.SRKTemplate, nil, nil, nil) - if err != nil { - t.Fatalf("CreatePrimary failed: %v", err) - } - defer flushContext(t, tpm, primary) - - priv, pub, _, _, _, err := tpm.Create(primary, nil, tcg.SRKTemplate, nil, nil, nil) - if err != nil { - t.Fatalf("Create failed: %v", err) - } - - context, err := tpm.Load(primary, priv, pub, nil) - if err != nil { - t.Fatalf("Load failed: %v", err) - } - defer flushContext(t, tpm, context) - - if _, err := tpm.EvictControl(tpm.OwnerHandleContext(), context, tcg.SRKHandle, nil); err != nil { - t.Errorf("EvictControl failed: %v", err) - } - - status, err = ProvisionStatus(tpm) - if err != nil { - t.Errorf("ProvisionStatus failed: %v", err) - } - expected = 0 - if status != expected { - t.Errorf("Unexpected status %d", status) - } -} diff --git a/seal_test.go b/seal_test.go index 1720a257..4a59354d 100644 --- a/seal_test.go +++ b/seal_test.go @@ -43,7 +43,7 @@ func TestSealKeyToTPM(t *testing.T) { tpm := openTPMForTesting(t) defer closeTPM(t, tpm) - if err := ProvisionTPM(tpm, ProvisionModeFull, nil); err != nil { + if err := tpm.EnsureProvisioned(ProvisionModeFull, nil); err != nil { t.Errorf("Failed to provision TPM for test: %v", err) } }() @@ -93,10 +93,10 @@ func TestSealKeyToTPM(t *testing.T) { }) t.Run("SealAfterProvision", func(t *testing.T) { - // SealKeyToTPM behaves slightly different if called immediately after ProvisionTPM with the same TPMConnection + // SealKeyToTPM behaves slightly different if called immediately after EnsureProvisioned with the same TPMConnection tpm := openTPMForTesting(t) defer closeTPM(t, tpm) - if err := ProvisionTPM(tpm, ProvisionModeFull, nil); err != nil { + if err := tpm.EnsureProvisioned(ProvisionModeFull, nil); err != nil { t.Errorf("Failed to provision TPM for test: %v", err) } run(t, tpm, true, &KeyCreationParams{PCRProfile: getTestPCRProfile(), PINHandle: 0x01810000}) @@ -128,7 +128,7 @@ func TestSealKeyToTPMErrorHandling(t *testing.T) { tpm := openTPMForTesting(t) defer closeTPM(t, tpm) - if err := ProvisionTPM(tpm, ProvisionModeFull, nil); err != nil { + if err := tpm.EnsureProvisioned(ProvisionModeFull, nil); err != nil { t.Errorf("Failed to provision TPM for test: %v", err) } @@ -212,7 +212,7 @@ func TestSealKeyToTPMErrorHandling(t *testing.T) { if err := tpm.NVUndefineSpace(tpm.OwnerHandleContext(), index, nil); err != nil { t.Errorf("NVUndefineSpace failed: %v", err) } - if err := ProvisionTPM(tpm, ProvisionModeFull, nil); err != nil { + if err := tpm.EnsureProvisioned(ProvisionModeFull, nil); err != nil { t.Errorf("Failed to re-provision TPM after test: %v", err) } }() @@ -237,7 +237,7 @@ func TestSealKeyToTPMErrorHandling(t *testing.T) { if err := tpm.NVUndefineSpace(tpm.OwnerHandleContext(), index, nil); err != nil { t.Errorf("NVUndefineSpace failed: %v", err) } - if err := ProvisionTPM(tpm, ProvisionModeFull, nil); err != nil { + if err := tpm.EnsureProvisioned(ProvisionModeFull, nil); err != nil { t.Errorf("Failed to re-provision TPM after test: %v", err) } }() diff --git a/tools/gen-compattest-data/main.go b/tools/gen-compattest-data/main.go index 68e99719..addfa0e4 100644 --- a/tools/gen-compattest-data/main.go +++ b/tools/gen-compattest-data/main.go @@ -169,7 +169,7 @@ func run() int { return 1 } - if err := secboot.ProvisionTPM(tpm, secboot.ProvisionModeFull, []byte("1234")); err != nil { + if err := tpm.EnsureProvisioned(secboot.ProvisionModeFull, []byte("1234")); err != nil { fmt.Fprintf(os.Stderr, "Cannot provision TPM: %v\n", err) return 1 } diff --git a/tpm_test.go b/tpm_test.go index d5cea339..1a05b0e6 100644 --- a/tpm_test.go +++ b/tpm_test.go @@ -138,8 +138,8 @@ func TestConnectToDefaultTPM(t *testing.T) { tpm := connectAndClear(t) defer closeTPM(t, tpm) - if err := ProvisionTPM(tpm, ProvisionModeFull, nil); err != nil { - t.Fatalf("ProvisionTPM failed: %v", err) + if err := tpm.EnsureProvisioned(ProvisionModeFull, nil); err != nil { + t.Fatalf("EnsureProvisioned failed: %v", err) } }() @@ -345,8 +345,8 @@ func TestSecureConnectToDefaultTPM(t *testing.T) { tpm := connectAndClear(t) defer closeTPM(t, tpm) - if err := ProvisionTPM(tpm, ProvisionModeFull, nil); err != nil { - t.Fatalf("ProvisionTPM failed: %v", err) + if err := tpm.EnsureProvisioned(ProvisionModeFull, nil); err != nil { + t.Fatalf("EnsureProvisioned failed: %v", err) } }() diff --git a/unseal_test.go b/unseal_test.go index e979a9a2..6b038129 100644 --- a/unseal_test.go +++ b/unseal_test.go @@ -35,7 +35,7 @@ func TestUnsealWithNo2FA(t *testing.T) { tpm := openTPMForTesting(t) defer closeTPM(t, tpm) - if err := ProvisionTPM(tpm, ProvisionModeFull, nil); err != nil { + if err := tpm.EnsureProvisioned(ProvisionModeFull, nil); err != nil { t.Fatalf("Failed to provision TPM for test: %v", err) } @@ -84,7 +84,7 @@ func TestUnsealWithPIN(t *testing.T) { tpm := openTPMForTesting(t) defer closeTPM(t, tpm) - if err := ProvisionTPM(tpm, ProvisionModeFull, nil); err != nil { + if err := tpm.EnsureProvisioned(ProvisionModeFull, nil); err != nil { t.Fatalf("Failed to provision TPM for test: %v", err) } @@ -130,8 +130,8 @@ func TestUnsealErrorHandling(t *testing.T) { rand.Read(key) run := func(t *testing.T, tpm *TPMConnection, fn func(string, string)) error { - if err := ProvisionTPM(tpm, ProvisionModeFull, nil); err != nil { - t.Errorf("ProvisionTPM failed: %v", err) + if err := tpm.EnsureProvisioned(ProvisionModeFull, nil); err != nil { + t.Errorf("EnsureProvisioned failed: %v", err) } tmpDir, err := ioutil.TempDir("", "_TestUnsealErrorHandling_") From f55740a0643c2de68851b1dedeb7d12600513c0b Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Fri, 11 Sep 2020 14:09:24 +0100 Subject: [PATCH 2/2] Add extra tests --- provisioning_test.go | 64 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/provisioning_test.go b/provisioning_test.go index e700a8c4..b3010450 100644 --- a/provisioning_test.go +++ b/provisioning_test.go @@ -250,6 +250,11 @@ func TestProvisionErrorHandling(t *testing.T) { t.Fatalf("HierarchyChangeAuth failed: %v", err) } } + disableOwnerClear := func(t *testing.T) { + if err := tpm.ClearControl(tpm.LockoutHandleContext(), true, nil); err != nil { + t.Fatalf("ClearControl failed: %v", err) + } + } for _, data := range []struct { desc string @@ -262,16 +267,12 @@ func TestProvisionErrorHandling(t *testing.T) { desc: "ErrTPMClearRequiresPPI", mode: ProvisionModeClear, prepare: func(t *testing.T) { - if err := tpm.ClearControl(tpm.LockoutHandleContext(), true, nil); err != nil { - t.Fatalf("ClearControl failed: %v", err) - } - setLockoutAuth(t) + disableOwnerClear(t) }, - lockoutAuth: authValue, - err: ErrTPMClearRequiresPPI, + err: ErrTPMClearRequiresPPI, }, { - desc: "ErrTPMLockoutAuthFail1", + desc: "ErrTPMLockoutAuthFail/1", mode: ProvisionModeFull, prepare: func(t *testing.T) { setLockoutAuth(t) @@ -280,7 +281,7 @@ func TestProvisionErrorHandling(t *testing.T) { err: errLockoutAuthFail, }, { - desc: "ErrTPMLockoutAuthFail2", + desc: "ErrTPMLockoutAuthFail/2", mode: ProvisionModeClear, prepare: func(t *testing.T) { setLockoutAuth(t) @@ -289,7 +290,7 @@ func TestProvisionErrorHandling(t *testing.T) { err: errLockoutAuthFail, }, { - desc: "ErrInLockout1", + desc: "ErrInLockout/1", mode: ProvisionModeFull, prepare: func(t *testing.T) { setLockoutAuth(t) @@ -300,7 +301,7 @@ func TestProvisionErrorHandling(t *testing.T) { err: ErrTPMLockout, }, { - desc: "ErrInLockout2", + desc: "ErrInLockout/2", mode: ProvisionModeClear, prepare: func(t *testing.T) { setLockoutAuth(t) @@ -360,11 +361,43 @@ func TestProvisionErrorHandling(t *testing.T) { }, err: errLockNVDataHandleExists, }, + { + desc: "ErrTPMProvisioningRequiresLockout/1", + mode: ProvisionModeWithoutLockout, + err: ErrTPMProvisioningRequiresLockout, + }, + { + desc: "ErrTPMProvisioningRequiresLockout/2", + mode: ProvisionModeWithoutLockout, + prepare: func(t *testing.T) { + disableOwnerClear(t) + }, + err: ErrTPMProvisioningRequiresLockout, + }, + { + desc: "ErrTPMProvisioningRequiresLockout/3", + mode: ProvisionModeWithoutLockout, + prepare: func(t *testing.T) { + setLockoutAuth(t) + }, + err: ErrTPMProvisioningRequiresLockout, + }, + { + desc: "ErrTPMProvisioningRequiresLockout/4", + mode: ProvisionModeWithoutLockout, + prepare: func(t *testing.T) { + setLockoutAuth(t) + disableOwnerClear(t) + }, + err: ErrTPMProvisioningRequiresLockout, + }, } { t.Run(data.desc, func(t *testing.T) { clearTPMWithPlatformAuth(t, tpm) - data.prepare(t) + if data.prepare != nil { + data.prepare(t) + } tpm.LockoutHandleContext().SetAuthValue(data.lockoutAuth) tpm.OwnerHandleContext().SetAuthValue(nil) tpm.EndorsementHandleContext().SetAuthValue(nil) @@ -483,6 +516,7 @@ func TestRecreateSRK(t *testing.T) { if err != nil { t.Fatalf("No SRK context: %v", err) } + expectedName := srk.Name() if _, err := tpm.EvictControl(tpm.OwnerHandleContext(), srk, srk.Handle(), nil); err != nil { t.Errorf("EvictControl failed: %v", err) @@ -492,6 +526,14 @@ func TestRecreateSRK(t *testing.T) { t.Fatalf("EnsureProvisioned failed: %v", err) } + srk, err = tpm.CreateResourceContextFromTPM(tcg.SRKHandle) + if err != nil { + t.Fatalf("No SRK context: %v", err) + } + if !bytes.Equal(srk.Name(), expectedName) { + t.Errorf("Unexpected SRK name") + } + validateSRK(t, tpm.TPMContext) }) }