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..b3010450 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) @@ -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,21 +361,53 @@ 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) - 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 +435,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 +459,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,21 +508,30 @@ 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) 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) } - 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) + } + + 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) @@ -512,8 +554,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 +577,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 +600,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 +610,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_")