From 36e4bf226a152471730839e1802b3a7885b7de86 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Tue, 31 Mar 2026 15:30:18 +0100 Subject: [PATCH 1/5] tpm2: Add WithLockoutAuthValue and WithLockoutAuthData options for EnsureProvisioned This adds new WithLockoutAuthValue and WithLockoutAuthData options for Connection.EnsureProvisioned. These are used to supply the current authorization parameters for the lockout hierarchy. WithLockoutAuthValue is used to supply the raw authorization value, supplied to current (but soon to be older) versions of Connection.EnsureProvisioned. WithLockoutAuthData will be used to supply authorization data that will be created by newer versions of Connection.EnsureProvisioned. These options replace the ProvisionModeWithoutLockout option. To prevent Connection.EnsureProvisioned from using the lockout hierarchy, just omit both of the new options. --- go.mod | 2 +- go.sum | 2 + tpm2/export_test.go | 13 ++- tpm2/key_sealer_test.go | 2 +- tpm2/lockoutauth.go | 54 +++++++++--- tpm2/lockoutauth_test.go | 29 +++++-- tpm2/platform_legacy_test.go | 2 +- tpm2/platform_test.go | 2 +- tpm2/provisioning.go | 159 ++++++++++++++++++++++------------- tpm2/provisioning_test.go | 122 ++++++++++++++++++++++----- tpm2/seal_legacy_test.go | 3 +- tpm2/seal_test.go | 3 +- tpm2/tpm_test.go | 3 +- tpm2/unseal_legacy_test.go | 3 +- tpm2/update_legacy_test.go | 3 +- tpm2/update_test.go | 3 +- 16 files changed, 288 insertions(+), 117 deletions(-) diff --git a/go.mod b/go.mod index f031dd6d..9770f393 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/canonical/go-kbkdf v0.0.0-20250104172618-3b1308f9acf9 github.com/canonical/go-password-validator v0.0.0-20250617132709-1b205303ca54 github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 - github.com/canonical/go-tpm2 v1.15.0 + github.com/canonical/go-tpm2 v1.16.0 github.com/canonical/tcglog-parser v0.0.0-20240924110432-d15eaf652981 github.com/jessevdk/go-flags v1.5.0 github.com/snapcore/snapd v0.0.0-20220714152900-4a1f4c93fc85 diff --git a/go.sum b/go.sum index 6622f560..6e12c2c3 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,8 @@ github.com/canonical/go-tpm2 v1.13.0 h1:Ka9VmUVwoz9pJef5JXP6Gd4CIhxFE70X26K8x3Le github.com/canonical/go-tpm2 v1.13.0/go.mod h1:P50xMwC7y5/uxPikzWdK4d9pW9orKi8+ZL5sBifxoBQ= github.com/canonical/go-tpm2 v1.15.0 h1:T4dVCO8qCs76vDDs4vWNpvPdh7UHuSORPH4Scq7N2gw= github.com/canonical/go-tpm2 v1.15.0/go.mod h1:P50xMwC7y5/uxPikzWdK4d9pW9orKi8+ZL5sBifxoBQ= +github.com/canonical/go-tpm2 v1.16.0 h1:AX+hpmdPgR8i3VFe3DVgKO46S5EpnumKP0yS5ND/Tz8= +github.com/canonical/go-tpm2 v1.16.0/go.mod h1:P50xMwC7y5/uxPikzWdK4d9pW9orKi8+ZL5sBifxoBQ= github.com/canonical/tcglog-parser v0.0.0-20210824131805-69fa1e9f0ad2/go.mod h1:QoW2apR2tBl6T/4czdND/EHjL1Ia9cCmQnIj9Xe0Kt8= github.com/canonical/tcglog-parser v0.0.0-20240924110432-d15eaf652981 h1:vrUzSfbhl8mzdXPzjxq4jXZPCCNLv18jy6S7aVTS2tI= github.com/canonical/tcglog-parser v0.0.0-20240924110432-d15eaf652981/go.mod h1:ywdPBqUGkuuiitPpVWCfilf2/gq+frhq4CNiNs9KyHU= diff --git a/tpm2/export_test.go b/tpm2/export_test.go index 2a0a9272..7a2dae3d 100644 --- a/tpm2/export_test.go +++ b/tpm2/export_test.go @@ -150,16 +150,21 @@ func NewPcrPolicyData_v3(v2 *PcrPolicyData_v2) *PcrPolicyData_v3 { type PlatformKeyDataHandler = platformKeyDataHandler +// TODO: Remove this and ProvisionMode.Option in favour of just updating the tests +// to use the correct options instead. type ProvisionMode = provisionMode -func (m ProvisionMode) Option() EnsureProvisionedOption { +func (m ProvisionMode) Option(lockoutAuthValue []byte) EnsureProvisionedOption { switch m { case provisionModeWithoutLockout: - return ProvisionWithoutLockout() + return func(_ *ensureProvisionedParams) {} case provisionModeClear: - return WithClearBeforeProvision() + return func(p *ensureProvisionedParams) { + WithLockoutAuthValue(lockoutAuthValue)(p) + WithClearBeforeProvision()(p) + } default: - return func(_ *ensureProvisionedParams) {} + return WithLockoutAuthValue(lockoutAuthValue) } } diff --git a/tpm2/key_sealer_test.go b/tpm2/key_sealer_test.go index cff059fa..a4e4ed8a 100644 --- a/tpm2/key_sealer_test.go +++ b/tpm2/key_sealer_test.go @@ -50,7 +50,7 @@ func (s *sealedObjectKeySealerSuite) SetUpSuite(c *C) { func (s *sealedObjectKeySealerSuite) SetUpTest(c *C) { s.TPMTest.SetUpTest(c) - c.Assert(s.TPM().EnsureProvisioned(ProvisionWithoutLockout()), + c.Assert(s.TPM().EnsureProvisioned(), testutil.InSlice(Equals), []error{ErrTPMProvisioningRequiresLockout, nil}) } diff --git a/tpm2/lockoutauth.go b/tpm2/lockoutauth.go index 6fb4e70f..09018098 100644 --- a/tpm2/lockoutauth.go +++ b/tpm2/lockoutauth.go @@ -115,46 +115,72 @@ func (p *lockoutAuthParams) UnmarshalJSON(data []byte) error { return nil } -func (t *Connection) resetDictionaryAttackLockImpl(params *lockoutAuthParams) error { - if len(params.NewAuthValue) > 0 || params.NewAuthPolicy != nil { - return errors.New("lockout hierarchy auth value change not supported yet") +func (t *Connection) authorizeLockout(authParams *lockoutAuthParams, command tpm2.CommandCode) (session tpm2.SessionContext, lockoutAuthSet bool, done func(), err error) { + if len(authParams.NewAuthValue) > 0 || authParams.NewAuthPolicy != nil { + return nil, false, nil, errors.New("lockout hierarchy auth value change not supported yet") } var authValue []byte val, err := t.GetCapabilityTPMProperty(tpm2.PropertyPermanent) if err != nil { - return fmt.Errorf("cannot obtain value of TPM_PT_PERMANENT: %w", err) + return nil, false, nil, fmt.Errorf("cannot obtain value of TPM_PT_PERMANENT: %w", err) } - lockoutAuthSet := tpm2.PermanentAttributes(val)&tpm2.AttrLockoutAuthSet > 0 + lockoutAuthSet = tpm2.PermanentAttributes(val)&tpm2.AttrLockoutAuthSet > 0 if lockoutAuthSet { - authValue = params.AuthValue + authValue = authParams.AuthValue } - var session tpm2.SessionContext switch { - case params.AuthPolicy == nil: + case authParams.AuthPolicy == nil: session = t.HmacSession() default: session, err = t.StartAuthSession(nil, nil, tpm2.SessionTypePolicy, nil, defaultSessionHashAlgorithm) if err != nil { - return fmt.Errorf("cannot start policy session: %w", err) + return nil, false, nil, fmt.Errorf("cannot start policy session: %w", err) } - defer t.FlushContext(session) + sessionInternal := session + defer func() { + if err == nil { + return + } + t.FlushContext(sessionInternal) + }() // Execute policy session, constraining the use to the TPM2_DictionaryAttackLockReset command so // that the correct branch executes. - _, err := params.AuthPolicy.Execute( + _, err := authParams.AuthPolicy.Execute( policyutil.NewPolicyExecuteSession(t.TPMContext, session), - policyutil.WithSessionUsageCommandConstraint(tpm2.CommandDictionaryAttackLockReset, []policyutil.NamedHandle{t.LockoutHandleContext()}), + policyutil.WithSessionUsageCommandCodeConstraint(command), ) if err != nil { - return ErrInvalidLockoutAuthPolicy + return nil, false, nil, ErrInvalidLockoutAuthPolicy } } + origAuthValue := t.LockoutHandleContext().AuthValue() t.LockoutHandleContext().SetAuthValue(authValue) - defer t.LockoutHandleContext().SetAuthValue(nil) + defer func() { + if err == nil { + return + } + t.LockoutHandleContext().SetAuthValue(origAuthValue) + }() + + return session, lockoutAuthSet, func() { + if authParams.AuthPolicy != nil { + t.FlushContext(session) + } + t.LockoutHandleContext().SetAuthValue(origAuthValue) + }, nil +} + +func (t *Connection) resetDictionaryAttackLockImpl(params *lockoutAuthParams) error { + session, lockoutAuthSet, done, err := t.authorizeLockout(params, tpm2.CommandDictionaryAttackLockReset) + if err != nil { + return err + } + defer done() switch err := t.DictionaryAttackLockReset(t.LockoutHandleContext(), session); { case isAuthFailError(err, tpm2.CommandDictionaryAttackLockReset, 1): diff --git a/tpm2/lockoutauth_test.go b/tpm2/lockoutauth_test.go index af8455a3..722532c9 100644 --- a/tpm2/lockoutauth_test.go +++ b/tpm2/lockoutauth_test.go @@ -56,6 +56,12 @@ func (*lockoutauthSuiteMixin) newDefaultLockoutAuthPolicy(c *C, alg tpm2.HashAlg n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { b.PolicyCommandCode(tpm2.CommandClear) }) + + // XXX: This is here temporarily to make provisioningSuite.TestProvisionWithLockoutAuthData + // pass and will be removed in the next PR. + n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { + b.PolicyCommandCode(tpm2.CommandHierarchyChangeAuth) + }) }) b.PolicyAuthValue() }) @@ -103,6 +109,20 @@ func (*lockoutauthSuiteMixin) newRotateAuthValueLockoutAuthPolicy(c *C, alg tpm2 return digest, policy } +func (*lockoutauthSuiteMixin) makeLockoutAuthData(c *C, params *LockoutAuthParams) []byte { + data, err := json.Marshal(params) + c.Assert(err, IsNil) + return data +} + +func (m *lockoutauthSuiteMixin) makeDefaultLockoutAuthData(c *C, alg tpm2.HashAlgorithmId, val []byte) (tpm2.Digest, []byte) { + digest, policy := m.newDefaultLockoutAuthPolicy(c, alg) + return digest, m.makeLockoutAuthData(c, &LockoutAuthParams{ + AuthValue: val, + AuthPolicy: policy, + }) +} + type lockoutauthSuiteNoTPM struct { lockoutauthSuiteMixin } @@ -135,12 +155,6 @@ func (s *lockoutauthSuite) SetUpTest(c *C) { c.Assert(s.TPM().DictionaryAttackParameters(s.TPM().LockoutHandleContext(), 32, 7200, 86400, nil), IsNil) } -func (s *lockoutauthSuite) makeLockoutAuthData(c *C, params *LockoutAuthParams) []byte { - data, err := json.Marshal(params) - c.Assert(err, IsNil) - return data -} - var _ = Suite(&lockoutauthSuiteNoTPM{}) var _ = Suite(&lockoutauthSuite{}) @@ -348,6 +362,9 @@ func (s *lockoutauthSuite) TestResetDictionaryAttackLockWithAuthValue(c *C) { c.Check(s.TPM().LockoutHandleContext().AuthValue(), DeepEquals, []byte(nil)) cmds := s.CommandLog() + for _, cmd := range cmds { + c.Logf("%v", cmd.CmdCode) + } c.Assert(len(cmds) > 1, testutil.IsTrue) cmd := cmds[len(cmds)-2] c.Check(cmd.CmdCode, Equals, tpm2.CommandDictionaryAttackLockReset) diff --git a/tpm2/platform_legacy_test.go b/tpm2/platform_legacy_test.go index 9d9e3b3f..01adcbab 100644 --- a/tpm2/platform_legacy_test.go +++ b/tpm2/platform_legacy_test.go @@ -49,7 +49,7 @@ func (s *platformLegacySuite) SetUpSuite(c *C) { func (s *platformLegacySuite) SetUpTest(c *C) { s.TPMTest.SetUpTest(c) - c.Check(s.TPM().EnsureProvisioned(ProvisionWithoutLockout()), Equals, ErrTPMProvisioningRequiresLockout) + c.Check(s.TPM().EnsureProvisioned(), Equals, ErrTPMProvisioningRequiresLockout) } var _ = Suite(&platformLegacySuite{}) diff --git a/tpm2/platform_test.go b/tpm2/platform_test.go index b9760269..e93a46b5 100644 --- a/tpm2/platform_test.go +++ b/tpm2/platform_test.go @@ -59,7 +59,7 @@ func (s *platformSuite) SetUpSuite(c *C) { func (s *platformSuite) SetUpTest(c *C) { s.TPMTest.SetUpTest(c) - c.Check(s.TPM().EnsureProvisioned(ProvisionWithoutLockout()), Equals, ErrTPMProvisioningRequiresLockout) + c.Check(s.TPM().EnsureProvisioned(), Equals, ErrTPMProvisioningRequiresLockout) s.lastEncryptedPayload = nil s.AddCleanup(MockSecbootNewKeyData(func(params *secboot.KeyParams) (*secboot.KeyData, error) { diff --git a/tpm2/provisioning.go b/tpm2/provisioning.go index e9bf008c..8fe2a921 100644 --- a/tpm2/provisioning.go +++ b/tpm2/provisioning.go @@ -20,6 +20,7 @@ package tpm2 import ( + "encoding/json" "errors" "fmt" "os" @@ -195,6 +196,9 @@ func removeStoredSrkTemplate(tpm *tpm2.TPMContext, session tpm2.SessionContext) } type ensureProvisionedParams struct { + lockoutAuthParams *lockoutAuthParams + lockoutAuthParamsErr error + mode provisionMode newLockoutAuthValue []byte srkTemplate *tpm2.Public @@ -203,19 +207,36 @@ type ensureProvisionedParams struct { type EnsureProvisionedOption func(*ensureProvisionedParams) -// ProvisionModeWithoutLockout tells [Connection.EnsureProvisioned] to not perform any actions -// that require use of the TPM's lockout hierarchy. If 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, -// [Connection.EnsureProvisioned] will complete all operations that can be completed without using -// the lockout hierarchy, but it should be called again without this option. -func ProvisionWithoutLockout() EnsureProvisionedOption { +// WithLockoutAuthValue tells [Connection.EnsureProvisioned] that it can use the TPM's lockout hierarchy +// with the supplied authorization value. This option is for systems that were configured with an older +// version of [Connection.EnsureProvisioned] (XXX: not yet) where an authorization value was chosen and +// supplied by the caller. +// +// If the wrong value is supplied, the lockout hierarchy will become unavailable for the pre-programmed +// recovery time. +func WithLockoutAuthValue(authValue []byte) EnsureProvisionedOption { + return func(p *ensureProvisionedParams) { + if p.lockoutAuthParams != nil || p.lockoutAuthParamsErr != nil { + panic("WithLockoutAuthValue incompatible with WithLockoutAuthData") + } + p.lockoutAuthParams = &lockoutAuthParams{ + AuthValue: authValue, + } + } +} + +// WithLockoutAuthData tells [Connection.EnsureProvisioned] that it can use the TPM's lockout hierarchy +// with the supplied authorization data. The authorization data will have been supplied by a previous call +// to [Connection.EnsureProvisioned] (XXX: in a future PR). +// +// If the data contains the wrong authorization value, the lockout hierarchy will become unavailable for +// the pre-programmed recovery time. +func WithLockoutAuthData(data []byte) EnsureProvisionedOption { return func(p *ensureProvisionedParams) { - if p.mode == provisionModeClear { - panic("ProvisionWithoutLockout conflicts with WithClearBeforeProvision") + if p.lockoutAuthParams != nil { + panic("WithLockoutAuthData incompatible with WithLockoutAuthValue") } - p.mode = provisionModeWithoutLockout + p.lockoutAuthParamsErr = json.Unmarshal(data, &p.lockoutAuthParams) } } @@ -273,21 +294,18 @@ func WithCustomSRKTemplate(template *tpm2.Public) EnsureProvisionedOption { // only way to recover from this is to clear the TPM either by calling this function with the [WithClearBeforeProvision] // option (and providing the correct authorization value for the lockout hierarchy), or by using the physical presence interface. // -// If the [ProvisionWithoutLockout] option is not supplied, then owner clear will be disabled, and the parameters of the TPM's -// dictionary attack logic will be configured to appropriate values. The authorization value for the lockout hierarchy will -// be set to the value supplied to [WithProvisionNewLockoutAuthValue], or the empty value if not supplied. +// If the [WithLockoutAuthValue] or [WithLockoutAuthData] option is supplied, then owner clear will be disabled, and the +// parameters of the TPM's dictionary attack logic will be configured to appropriate values. The authorization value for the +// lockout hierarchy will be set to the value supplied to [WithProvisionNewLockoutAuthValue], or the empty value if not supplied. // -// If the [ProvisionWithoutLockout] option is not supplied, 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 -// Connection.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 then the -// [ProvisionWithoutLockout] option should be supplied, with the caveat that this mode cannot fully provision the TPM. +// If the [WithLockoutAuthValue] or [WithLockoutAuthData] option is supplied with the wrong value, then a [AuthFailError] error +// may 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 [WithClearBeforeProvision] is not supplied, this function will not affect the ability to recover sealed keys that -// can currently be recovered. +// can currently be recovered. If it is supplied, then one of [WithLockoutAuthValue] or [WithLockoutAuthData] must be supplied. func (t *Connection) EnsureProvisioned(options ...EnsureProvisionedOption) error { params := &ensureProvisionedParams{ mode: provisionModeFull, @@ -296,8 +314,36 @@ func (t *Connection) EnsureProvisioned(options ...EnsureProvisionedOption) error for _, opt := range options { opt(params) } - if params.srkTemplate != nil && !params.srkTemplate.IsStorageParent() { + switch { + case params.srkTemplate != nil && !params.srkTemplate.IsStorageParent(): return errors.New("supplied SRK template is not valid for a parent key") + case params.lockoutAuthParamsErr != nil: + return &InvalidLockoutAuthDataError{err: params.lockoutAuthParamsErr} + case params.mode == provisionModeClear && params.lockoutAuthParams == nil: + return errors.New("WithClearBeforeProvision requires WithLockoutAuthParams or WithLockoutAuthData") + case params.lockoutAuthParams == nil: + params.mode = provisionModeWithoutLockout + } + + authorizeAndUseLockoutHierarchy := func(command tpm2.CommandCode, fn func(tpm2.SessionContext) error, msg string) error { + session, _, done, err := t.authorizeLockout(params.lockoutAuthParams, command) + if err != nil { + return err + } + defer done() + + switch err := fn(session); { + case isAuthFailError(err, command, 1): + return AuthFailError{tpm2.HandleLockout} + case tpm2.IsTPMWarning(err, tpm2.WarningLockout, command): + return ErrTPMLockout + case tpm2.IsTPMSessionError(err, tpm2.ErrorPolicyFail, command, 1): + return ErrInvalidLockoutAuthPolicy + case err != nil: + return fmt.Errorf("%s: %w", msg, err) + } + + return nil } session := t.HmacSession() @@ -306,20 +352,16 @@ func (t *Connection) EnsureProvisioned(options ...EnsureProvisionedOption) error if err != nil { return fmt.Errorf("cannot fetch permanent properties: %w", err) } + if params.mode == provisionModeClear { if tpm2.PermanentAttributes(val)&tpm2.AttrDisableClear > 0 { 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): - return AuthFailError{tpm2.HandleLockout} - case tpm2.IsTPMWarning(err, tpm2.WarningLockout, tpm2.CommandClear): - return ErrTPMLockout - } - return fmt.Errorf("cannot clear the TPM: %w", err) + if err := authorizeAndUseLockoutHierarchy(tpm2.CommandClear, func(session tpm2.SessionContext) error { + return t.Clear(t.LockoutHandleContext(), session) + }, "cannot clear the TPM"); err != nil { + return err } } @@ -396,41 +438,42 @@ func (t *Connection) EnsureProvisioned(options ...EnsureProvisionedOption) error // Perform actions that require the lockout hierarchy authorization. - // 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): - return AuthFailError{tpm2.HandleLockout} - case tpm2.IsTPMWarning(err, tpm2.WarningLockout, tpm2.CommandDictionaryAttackParameters): - return ErrTPMLockout - } - return fmt.Errorf("cannot configure dictionary attack parameters: %w", err) + // Set the DA parameters. + if err := authorizeAndUseLockoutHierarchy(tpm2.CommandDictionaryAttackParameters, func(session tpm2.SessionContext) error { + return t.DictionaryAttackParameters(t.LockoutHandleContext(), maxTries, recoveryTime, lockoutRecovery, session) + }, "cannot configure dictionary attack parameters"); err != nil { + return err } // Clear any lockout if there is one. This has to happen after setting the DA parameters // because we can't clear a lockout if maxTries is 0. - if err := t.DictionaryAttackLockReset(t.LockoutHandleContext(), session); err != nil { - switch { - case isAuthFailError(err, tpm2.CommandDictionaryAttackLockReset, 1): - return AuthFailError{tpm2.HandleLockout} - case tpm2.IsTPMWarning(err, tpm2.WarningLockout, tpm2.CommandDictionaryAttackLockReset): - return ErrTPMLockout - } - return fmt.Errorf("cannot reset dictionary attack protection: %w", err) + if err := authorizeAndUseLockoutHierarchy(tpm2.CommandDictionaryAttackLockReset, func(session tpm2.SessionContext) error { + return t.DictionaryAttackLockReset(t.LockoutHandleContext(), session) + }, "cannot reset dictionary attack protection"); err != nil { + return err } - // 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 fmt.Errorf("cannot disable owner clear: %w", err) + // Disable owner clear. + if err := authorizeAndUseLockoutHierarchy(tpm2.CommandClearControl, func(session tpm2.SessionContext) error { + return t.ClearControl(t.LockoutHandleContext(), true, session) + }, "cannot disable owner clear"); err != nil { + return err } // 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(), params.newLockoutAuthValue, session.IncludeAttrs(tpm2.AttrCommandEncrypt)); err != nil { - return xerrors.Errorf("cannot set the lockout hierarchy authorization value: %w", err) + if err := authorizeAndUseLockoutHierarchy(tpm2.CommandHierarchyChangeAuth, func(authSession tpm2.SessionContext) error { + switch { + case authSession.Handle().Type() == tpm2.HandleTypePolicySession: + // We're using policy auth so need to supply the HMAC session as an extra + // session for parameter encryption. + return t.HierarchyChangeAuth(t.LockoutHandleContext(), params.newLockoutAuthValue, authSession, session.IncludeAttrs(tpm2.AttrCommandEncrypt)) + default: + // We're using HMAC auth + return t.HierarchyChangeAuth(t.LockoutHandleContext(), params.newLockoutAuthValue, authSession.IncludeAttrs(tpm2.AttrCommandEncrypt)) + } + }, "cannot set the lockout hierarchy authorization value"); err != nil { + return err } return nil diff --git a/tpm2/provisioning_test.go b/tpm2/provisioning_test.go index 9134a752..62551c08 100644 --- a/tpm2/provisioning_test.go +++ b/tpm2/provisioning_test.go @@ -63,6 +63,7 @@ func (m *primaryKeyMixin) validateEK(c *C) { type provisioningSuite struct { tpm2test.TPMTest primaryKeyMixin + lockoutauthSuiteMixin } func (s *provisioningSuite) SetUpSuite(c *C) { @@ -102,7 +103,7 @@ type testProvisionNewTPMData struct { func (s *provisioningSimulatorSuite) testProvisionNewTPM(c *C, data *testProvisionNewTPMData) { origHmacSession := s.TPM().HmacSession() - c.Check(s.TPM().EnsureProvisioned(data.mode.Option(), WithProvisionNewLockoutAuthValue(data.lockoutAuth)), IsNil) + c.Check(s.TPM().EnsureProvisioned(data.mode.Option(data.lockoutAuth), WithProvisionNewLockoutAuthValue(data.lockoutAuth)), IsNil) s.AddCleanup(func() { // github.com/canonical/go-tpm2/testutil cannot restore this because // EnsureProvisioned uses command parameter encryption. We have to do @@ -170,6 +171,89 @@ func (s *provisioningSimulatorSuite) TestProvisionNewTPMDifferentLockoutAuth(c * lockoutAuth: []byte("foo")}) } +func (s *provisioningSuite) TestProvisionWithLockoutAuthValue(c *C) { + origValue := []byte("1234") + newValue := []byte("5678") + s.HierarchyChangeAuth(c, tpm2.HandleLockout, origValue) + + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthValue(origValue), WithProvisionNewLockoutAuthValue(newValue)), IsNil) + s.AddCleanup(func() { + // github.com/canonical/go-tpm2/testutil cannot restore this because + // EnsureProvisioned uses command parameter encryption. We have to do + // this manually else the test fixture fails the test. + s.TPM().LockoutHandleContext().SetAuthValue(newValue) + s.HierarchyChangeAuth(c, tpm2.HandleLockout, nil) + }) + + // Validate the DA parameters + value, err := s.TPM().GetCapabilityTPMProperty(tpm2.PropertyMaxAuthFail) + c.Check(err, IsNil) + c.Check(value, Equals, uint32(32)) + value, err = s.TPM().GetCapabilityTPMProperty(tpm2.PropertyLockoutInterval) + c.Check(err, IsNil) + c.Check(value, Equals, uint32(7200)) + value, err = s.TPM().GetCapabilityTPMProperty(tpm2.PropertyLockoutRecovery) + c.Check(err, IsNil) + c.Check(value, Equals, uint32(86400)) + + // Verify that owner control is disabled, that the lockout hierarchy auth is set, no + // other hierarchy auth is set, and there is no lockout. + value, err = s.TPM().GetCapabilityTPMProperty(tpm2.PropertyPermanent) + c.Check(err, IsNil) + c.Check(tpm2.PermanentAttributes(value)&tpm2.AttrLockoutAuthSet, Equals, tpm2.AttrLockoutAuthSet) + c.Check(tpm2.PermanentAttributes(value)&tpm2.AttrDisableClear, Equals, tpm2.AttrDisableClear) + c.Check(tpm2.PermanentAttributes(value)&tpm2.AttrOwnerAuthSet, Equals, tpm2.PermanentAttributes(0)) + c.Check(tpm2.PermanentAttributes(value)&tpm2.AttrEndorsementAuthSet, Equals, tpm2.PermanentAttributes(0)) + c.Check(tpm2.PermanentAttributes(value)&tpm2.AttrInLockout, Equals, tpm2.PermanentAttributes(0)) + + // Test the lockout hierarchy auth + s.TPM().LockoutHandleContext().SetAuthValue(newValue) + c.Check(s.TPM().DictionaryAttackLockReset(s.TPM().LockoutHandleContext(), nil), IsNil) +} + +func (s *provisioningSuite) TestProvisionWithLockoutAuthData(c *C) { + origValue := []byte("1234") + newValue := []byte("5678") + + policyDigest, data := s.makeDefaultLockoutAuthData(c, tpm2.HashAlgorithmSHA256, origValue) + s.HierarchyChangeAuth(c, tpm2.HandleLockout, origValue) + c.Assert(s.TPM().SetPrimaryPolicy(s.TPM().LockoutHandleContext(), policyDigest, tpm2.HashAlgorithmSHA256, nil), IsNil) + + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthData(data), WithProvisionNewLockoutAuthValue(newValue)), IsNil) + s.AddCleanup(func() { + // github.com/canonical/go-tpm2/testutil cannot restore this because + // EnsureProvisioned uses command parameter encryption. We have to do + // this manually else the test fixture fails the test. + s.TPM().LockoutHandleContext().SetAuthValue(newValue) + s.HierarchyChangeAuth(c, tpm2.HandleLockout, nil) + }) + + // Validate the DA parameters + value, err := s.TPM().GetCapabilityTPMProperty(tpm2.PropertyMaxAuthFail) + c.Check(err, IsNil) + c.Check(value, Equals, uint32(32)) + value, err = s.TPM().GetCapabilityTPMProperty(tpm2.PropertyLockoutInterval) + c.Check(err, IsNil) + c.Check(value, Equals, uint32(7200)) + value, err = s.TPM().GetCapabilityTPMProperty(tpm2.PropertyLockoutRecovery) + c.Check(err, IsNil) + c.Check(value, Equals, uint32(86400)) + + // Verify that owner control is disabled, that the lockout hierarchy auth is set, no + // other hierarchy auth is set, and there is no lockout. + value, err = s.TPM().GetCapabilityTPMProperty(tpm2.PropertyPermanent) + c.Check(err, IsNil) + c.Check(tpm2.PermanentAttributes(value)&tpm2.AttrLockoutAuthSet, Equals, tpm2.AttrLockoutAuthSet) + c.Check(tpm2.PermanentAttributes(value)&tpm2.AttrDisableClear, Equals, tpm2.AttrDisableClear) + c.Check(tpm2.PermanentAttributes(value)&tpm2.AttrOwnerAuthSet, Equals, tpm2.PermanentAttributes(0)) + c.Check(tpm2.PermanentAttributes(value)&tpm2.AttrEndorsementAuthSet, Equals, tpm2.PermanentAttributes(0)) + c.Check(tpm2.PermanentAttributes(value)&tpm2.AttrInLockout, Equals, tpm2.PermanentAttributes(0)) + + // Test the lockout hierarchy auth + s.TPM().LockoutHandleContext().SetAuthValue(newValue) + c.Check(s.TPM().DictionaryAttackLockReset(s.TPM().LockoutHandleContext(), nil), IsNil) +} + func (s *provisioningSimulatorSuite) TestProvisionTPMInLockout(c *C) { // Trip the DA logic by triggering an auth failure with a DA protected // resource. @@ -196,7 +280,7 @@ func (s *provisioningSimulatorSuite) testProvisionErrorHandling(c *C, mode Provi // else the test fixture fails the test. s.ClearTPMUsingPlatformHierarchy(c) }() - return s.TPM().EnsureProvisioned(mode.Option()) + return s.TPM().EnsureProvisioned(mode.Option(nil)) } func (s *provisioningSuite) testProvisionErrorHandling(c *C, mode ProvisionMode) error { @@ -206,7 +290,7 @@ func (s *provisioningSuite) testProvisionErrorHandling(c *C, mode ProvisionMode) // else the test fixture fails the test. s.ClearTPMUsingPlatformHierarchy(c) }() - return s.TPM().EnsureProvisioned(mode.Option()) + return s.TPM().EnsureProvisioned(mode.Option(nil)) } func (s *provisioningSuite) TestProvisionErrorHandlingClearRequiresPPI(c *C) { @@ -328,11 +412,12 @@ func (s *provisioningSimulatorSuite) TestProvisionErrorHandlingRequiresLockout5( func (s *provisioningSuite) testProvisionRecreateEK(c *C, mode ProvisionMode) { lockoutAuth := []byte("1234") - c.Check(s.TPM().EnsureProvisioned(WithProvisionNewLockoutAuthValue(lockoutAuth)), IsNil) + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthValue(nil), WithProvisionNewLockoutAuthValue(lockoutAuth)), IsNil) s.AddCleanup(func() { // github.com/canonical/go-tpm2/testutil cannot restore this because // EnsureProvisioned uses command parameter encryption. We have to do // this manually else the test fixture fails the test. + s.TPM().LockoutHandleContext().SetAuthValue(lockoutAuth) s.HierarchyChangeAuth(c, tpm2.HandleLockout, nil) }) @@ -342,7 +427,7 @@ func (s *provisioningSuite) testProvisionRecreateEK(c *C, mode ProvisionMode) { c.Assert(err, IsNil) s.EvictControl(c, tpm2.HandleOwner, ek, ek.Handle()) - c.Check(s.TPM().EnsureProvisioned(mode.Option(), WithProvisionNewLockoutAuthValue(lockoutAuth)), IsNil) + c.Check(s.TPM().EnsureProvisioned(mode.Option(lockoutAuth), WithProvisionNewLockoutAuthValue(lockoutAuth)), IsNil) s.validateEK(c) s.validateSRK(c) @@ -364,11 +449,12 @@ func (s *provisioningSuite) TestRecreateEKWithoutLockout(c *C) { func (s *provisioningSuite) testProvisionRecreateSRK(c *C, mode ProvisionMode) { lockoutAuth := []byte("1234") - c.Check(s.TPM().EnsureProvisioned(WithProvisionNewLockoutAuthValue(lockoutAuth)), IsNil) + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthValue(lockoutAuth), WithProvisionNewLockoutAuthValue(lockoutAuth)), IsNil) s.AddCleanup(func() { // github.com/canonical/go-tpm2/testutil cannot restore this because // EnsureProvisioned uses command parameter encryption. We have to do // this manually else the test fixture fails the test. + s.TPM().LockoutHandleContext().SetAuthValue(lockoutAuth) s.HierarchyChangeAuth(c, tpm2.HandleLockout, nil) }) @@ -377,7 +463,7 @@ func (s *provisioningSuite) testProvisionRecreateSRK(c *C, mode ProvisionMode) { expectedName := srk.Name() s.EvictControl(c, tpm2.HandleOwner, srk, srk.Handle()) - c.Check(s.TPM().EnsureProvisioned(mode.Option(), WithProvisionNewLockoutAuthValue(lockoutAuth)), IsNil) + c.Check(s.TPM().EnsureProvisioned(mode.Option(lockoutAuth), WithProvisionNewLockoutAuthValue(lockoutAuth)), IsNil) s.validateEK(c) s.validateSRK(c) @@ -398,8 +484,7 @@ func (s *provisioningSuite) TestProvisionRecreateSRKWithoutLockout(c *C) { func (s *provisioningSuite) TestProvisionWithEndorsementAuth(c *C) { s.HierarchyChangeAuth(c, tpm2.HandleEndorsement, []byte("1234")) - c.Check(s.TPM().EnsureProvisioned(ProvisionWithoutLockout()), - testutil.InSlice(Equals), []error{ErrTPMProvisioningRequiresLockout, nil}) + c.Check(s.TPM().EnsureProvisioned(), Equals, ErrTPMProvisioningRequiresLockout) s.validateEK(c) s.validateSRK(c) @@ -408,8 +493,7 @@ func (s *provisioningSuite) TestProvisionWithEndorsementAuth(c *C) { func (s *provisioningSuite) TestProvisionWithOwnerAuth(c *C) { s.HierarchyChangeAuth(c, tpm2.HandleOwner, []byte("1234")) - c.Check(s.TPM().EnsureProvisioned(ProvisionWithoutLockout()), - testutil.InSlice(Equals), []error{ErrTPMProvisioningRequiresLockout, nil}) + c.Check(s.TPM().EnsureProvisioned(), Equals, ErrTPMProvisioningRequiresLockout) s.validateEK(c) s.validateSRK(c) @@ -430,7 +514,7 @@ func (s *provisioningSuite) testProvisionWithCustomSRKTemplate(c *C, mode Provis Scheme: tpm2.RSAScheme{Scheme: tpm2.RSASchemeNull}, KeyBits: 2048, Exponent: 0}}} - c.Check(s.TPM().EnsureProvisioned(mode.Option(), WithCustomSRKTemplate(&template)), IsNil) + c.Check(s.TPM().EnsureProvisioned(mode.Option(nil), WithCustomSRKTemplate(&template)), IsNil) s.validatePrimaryKeyAgainstTemplate(c, tpm2.HandleOwner, tcg.SRKHandle, &template) @@ -490,11 +574,12 @@ func (s *provisioningSuite) testProvisionDefaultPreservesCustomSRKTemplate(c *C, Exponent: 0}}} lockoutAuth := []byte("1234") - c.Check(s.TPM().EnsureProvisioned(WithProvisionNewLockoutAuthValue(lockoutAuth), WithCustomSRKTemplate(&template)), IsNil) + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthValue(nil), WithProvisionNewLockoutAuthValue(lockoutAuth), WithCustomSRKTemplate(&template)), IsNil) s.AddCleanup(func() { // github.com/canonical/go-tpm2/testutil cannot restore this because // EnsureProvisioned uses command parameter encryption. We have to do // this manually else the test fixture fails the test. + s.TPM().LockoutHandleContext().SetAuthValue(lockoutAuth) s.HierarchyChangeAuth(c, tpm2.HandleLockout, nil) }) @@ -502,7 +587,7 @@ func (s *provisioningSuite) testProvisionDefaultPreservesCustomSRKTemplate(c *C, c.Assert(err, IsNil) s.EvictControl(c, tpm2.HandleOwner, srk, srk.Handle()) - c.Check(s.TPM().EnsureProvisioned(mode.Option(), WithProvisionNewLockoutAuthValue(lockoutAuth)), IsNil) + c.Check(s.TPM().EnsureProvisioned(mode.Option(lockoutAuth), WithProvisionNewLockoutAuthValue(lockoutAuth)), IsNil) s.validatePrimaryKeyAgainstTemplate(c, tpm2.HandleOwner, tcg.SRKHandle, &template) } @@ -530,11 +615,10 @@ func (s *provisioningSuite) TestProvisionDefaultClearRemovesCustomSRKTemplate(c Scheme: tpm2.RSAScheme{Scheme: tpm2.RSASchemeNull}, KeyBits: 2048, Exponent: 0}}} - c.Check(s.TPM().EnsureProvisioned(ProvisionWithoutLockout(), WithCustomSRKTemplate(&template)), - testutil.InSlice(Equals), []error{ErrTPMProvisioningRequiresLockout, nil}) + c.Check(s.TPM().EnsureProvisioned(WithCustomSRKTemplate(&template)), Equals, ErrTPMProvisioningRequiresLockout) s.validatePrimaryKeyAgainstTemplate(c, tpm2.HandleOwner, tcg.SRKHandle, &template) - c.Check(s.TPM().EnsureProvisioned(WithClearBeforeProvision()), IsNil) + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthValue(nil), WithClearBeforeProvision()), IsNil) s.validateSRK(c) } @@ -553,7 +637,7 @@ func (s *provisioningSuite) TestProvisionWithCustomSRKTemplateOverwritesExisting Scheme: tpm2.RSAScheme{Scheme: tpm2.RSASchemeNull}, KeyBits: 2048, Exponent: 0}}} - c.Check(s.TPM().EnsureProvisioned(WithCustomSRKTemplate(&template1)), IsNil) + c.Check(s.TPM().EnsureProvisioned(WithCustomSRKTemplate(&template1)), Equals, ErrTPMProvisioningRequiresLockout) s.validatePrimaryKeyAgainstTemplate(c, tpm2.HandleOwner, tcg.SRKHandle, &template1) template2 := tpm2.Public{ @@ -570,7 +654,7 @@ func (s *provisioningSuite) TestProvisionWithCustomSRKTemplateOverwritesExisting Scheme: tpm2.RSAScheme{Scheme: tpm2.RSASchemeNull}, KeyBits: 2048, Exponent: 0}}} - c.Check(s.TPM().EnsureProvisioned(WithCustomSRKTemplate(&template2)), IsNil) + c.Check(s.TPM().EnsureProvisioned(WithCustomSRKTemplate(&template2)), Equals, ErrTPMProvisioningRequiresLockout) s.validatePrimaryKeyAgainstTemplate(c, tpm2.HandleOwner, tcg.SRKHandle, &template2) nv, err := s.TPM().NewResourceContext(0x01810001) diff --git a/tpm2/seal_legacy_test.go b/tpm2/seal_legacy_test.go index d4489a0e..003164e1 100644 --- a/tpm2/seal_legacy_test.go +++ b/tpm2/seal_legacy_test.go @@ -56,8 +56,7 @@ func (s *sealLegacySuite) SetUpTest(c *C) { s.TPMTest.SetUpTest(c) s.primaryKeyMixin.tpmTest = &s.TPMTest.TPMTest - c.Assert(s.TPM().EnsureProvisioned(ProvisionWithoutLockout()), - testutil.InSlice(Equals), []error{ErrTPMProvisioningRequiresLockout, nil}) + c.Assert(s.TPM().EnsureProvisioned(), testutil.InSlice(Equals), []error{ErrTPMProvisioningRequiresLockout, nil}) } var _ = Suite(&sealLegacySuite{}) diff --git a/tpm2/seal_test.go b/tpm2/seal_test.go index 8555fa38..9cbb203b 100644 --- a/tpm2/seal_test.go +++ b/tpm2/seal_test.go @@ -83,8 +83,7 @@ func (s *sealSuite) SetUpTest(c *C) { s.TPMTest.SetUpTest(c) s.primaryKeyMixin.tpmTest = &s.TPMTest.TPMTest - c.Assert(s.TPM().EnsureProvisioned(ProvisionWithoutLockout()), - testutil.InSlice(Equals), []error{ErrTPMProvisioningRequiresLockout, nil}) + c.Assert(s.TPM().EnsureProvisioned(), testutil.InSlice(Equals), []error{ErrTPMProvisioningRequiresLockout, nil}) origKdf := secboot.SetArgon2KDF(&testutil.MockArgon2KDF{}) s.AddCleanup(func() { secboot.SetArgon2KDF(origKdf) }) } diff --git a/tpm2/tpm_test.go b/tpm2/tpm_test.go index b19c08b6..96dee531 100644 --- a/tpm2/tpm_test.go +++ b/tpm2/tpm_test.go @@ -139,8 +139,7 @@ func (s *tpmSuiteSimulator) TestConnectToDefaultTPMUnprovisioned(c *C) { } func (s *tpmSuite) TestConnectToDefaultTPMProvisioned(c *C) { - c.Check(s.TPM().EnsureProvisioned(ProvisionWithoutLockout()), - testutil.InSlice(Equals), []error{ErrTPMProvisioningRequiresLockout, nil}) + c.Check(s.TPM().EnsureProvisioned(), Equals, ErrTPMProvisioningRequiresLockout) s.AddCleanup(s.CloseMockConnection(c)) s.testConnectToDefaultTPM(c, true) } diff --git a/tpm2/unseal_legacy_test.go b/tpm2/unseal_legacy_test.go index 6723600d..9877fd0a 100644 --- a/tpm2/unseal_legacy_test.go +++ b/tpm2/unseal_legacy_test.go @@ -48,8 +48,7 @@ func (s *unsealSuite) SetUpSuite(c *C) { func (s *unsealSuite) SetUpTest(c *C) { s.TPMTest.SetUpTest(c) - c.Assert(s.TPM().EnsureProvisioned(ProvisionWithoutLockout()), - testutil.InSlice(Equals), []error{ErrTPMProvisioningRequiresLockout, nil}) + c.Assert(s.TPM().EnsureProvisioned(), testutil.InSlice(Equals), []error{ErrTPMProvisioningRequiresLockout, nil}) } var _ = Suite(&unsealSuite{}) diff --git a/tpm2/update_legacy_test.go b/tpm2/update_legacy_test.go index 3e31d59b..a8f1aea8 100644 --- a/tpm2/update_legacy_test.go +++ b/tpm2/update_legacy_test.go @@ -53,8 +53,7 @@ func (s *updateLegacySuite) SetUpTest(c *C) { s.TPMTest.SetUpTest(c) s.primaryKeyMixin.tpmTest = &s.TPMTest.TPMTest - c.Assert(s.TPM().EnsureProvisioned(ProvisionWithoutLockout()), - testutil.InSlice(Equals), []error{ErrTPMProvisioningRequiresLockout, nil}) + c.Assert(s.TPM().EnsureProvisioned(), testutil.InSlice(Equals), []error{ErrTPMProvisioningRequiresLockout, nil}) } var _ = Suite(&updateLegacySuite{}) diff --git a/tpm2/update_test.go b/tpm2/update_test.go index 1f08551f..361d9a1a 100644 --- a/tpm2/update_test.go +++ b/tpm2/update_test.go @@ -49,8 +49,7 @@ func (s *updateSuite) SetUpTest(c *C) { s.TPMTest.SetUpTest(c) s.primaryKeyMixin.tpmTest = &s.TPMTest.TPMTest - c.Assert(s.TPM().EnsureProvisioned(ProvisionWithoutLockout()), - testutil.InSlice(Equals), []error{ErrTPMProvisioningRequiresLockout, nil}) + c.Assert(s.TPM().EnsureProvisioned(), testutil.InSlice(Equals), []error{ErrTPMProvisioningRequiresLockout, nil}) } var _ = Suite(&updateSuite{}) From 3c93e10c2f057b0a4d32c90ef6ff0bfdb89c7a23 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 9 Apr 2026 09:22:55 +0100 Subject: [PATCH 2/5] tpm2: Fix a couple of test failures --- tpm2/lockoutauth_test.go | 9 ++++----- tpm2/tpm_test.go | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/tpm2/lockoutauth_test.go b/tpm2/lockoutauth_test.go index 722532c9..8123a78f 100644 --- a/tpm2/lockoutauth_test.go +++ b/tpm2/lockoutauth_test.go @@ -166,7 +166,7 @@ func (s *lockoutauthSuiteNoTPM) TestLockoutAuthParamsMarshalJSON(c *C) { data, err := json.Marshal(params) c.Check(err, IsNil) - c.Check(data, DeepEquals, []byte(`{"auth-value":"x9oO1va6Pz6nQeeGOgoXSBOLbsyw4IQTKwSpyXbw0LE=","auth-policy":"AAAAAAAAAAEAC5xRENPNjPxvymnylptEkkmB67kMJSALrpC4PA2joYWCAAAAAAAAAAEgAQFxAAAAAQAAAAAAAQAL+21OPQovgBAFA+/1biwvpZu8ItTlnZBiGL/DKXTgoIIAAAACIAEBcQAAAAQAAAAAAAEAC7bFwF5YGQnN6n33pfkcDy7tN/128VUi7uW1X4lvLVY/AAAAAQAAAWwAAAE5AAAAAAABAAscaCd8nWVk3YG8z35Wuj7cqziPxgzpWzpEK9JyWPYN/AAAAAEAAAFsAAABOgAAAAAAAQALlAz7Qhe7Htz3+0GTfKl0qmjmmKt4uBJLBwET4hH9RvwAAAABAAABbAAAAScAAAAAAAEAC8Tfq87ajeg2yVZhlSiSsd73IDr7Rv7+xD/8/JO+VAcwAAAAAQAAAWwAAAEmAAABaw=="}`)) + c.Check(data, DeepEquals, []byte(`{"auth-value":"x9oO1va6Pz6nQeeGOgoXSBOLbsyw4IQTKwSpyXbw0LE=","auth-policy":"AAAAAAAAAAEAC/cbf1/nUkzaClt4ysmVL5cqWE67D7Brmd7cgdwi7ztVAAAAAAAAAAEgAQFxAAAAAQAAAAAAAQALufUnMhfDMA5sLu0OUIPoKx2NNK4laaj7SfVnqdZFDjYAAAACIAEBcQAAAAUAAAAAAAEAC7bFwF5YGQnN6n33pfkcDy7tN/128VUi7uW1X4lvLVY/AAAAAQAAAWwAAAE5AAAAAAABAAscaCd8nWVk3YG8z35Wuj7cqziPxgzpWzpEK9JyWPYN/AAAAAEAAAFsAAABOgAAAAAAAQALlAz7Qhe7Htz3+0GTfKl0qmjmmKt4uBJLBwET4hH9RvwAAAABAAABbAAAAScAAAAAAAEAC8Tfq87ajeg2yVZhlSiSsd73IDr7Rv7+xD/8/JO+VAcwAAAAAQAAAWwAAAEmAAAAAAABAAu+aY2L0UknzX7Xvdk75B8n/yYmvS2KFUDi+URfLuWrLgAAAAEAAAFsAAABKQAAAWs="}`)) } func (s *lockoutauthSuiteNoTPM) TestLockoutAuthParamsMarshalJSONNoPolicy(c *C) { @@ -190,12 +190,11 @@ func (s *lockoutauthSuiteNoTPM) TestLockoutAuthParamsMarshalJSONForChangeAuth(c data, err := json.Marshal(params) c.Check(err, IsNil) - c.Check(data, DeepEquals, []byte(`{"auth-value":"x9oO1va6Pz6nQeeGOgoXSBOLbsyw4IQTKwSpyXbw0LE=","auth-policy":"AAAAAAAAAAEAC5xRENPNjPxvymnylptEkkmB67kMJSALrpC4PA2joYWCAAAAAAAAAAEgAQFxAAAAAQAAAAAAAQAL+21OPQovgBAFA+/1biwvpZu8ItTlnZBiGL/DKXTgoIIAAAACIAEBcQAAAAQAAAAAAAEAC7bFwF5YGQnN6n33pfkcDy7tN/128VUi7uW1X4lvLVY/AAAAAQAAAWwAAAE5AAAAAAABAAscaCd8nWVk3YG8z35Wuj7cqziPxgzpWzpEK9JyWPYN/AAAAAEAAAFsAAABOgAAAAAAAQALlAz7Qhe7Htz3+0GTfKl0qmjmmKt4uBJLBwET4hH9RvwAAAABAAABbAAAAScAAAAAAAEAC8Tfq87ajeg2yVZhlSiSsd73IDr7Rv7+xD/8/JO+VAcwAAAAAQAAAWwAAAEmAAABaw==","new-auth-value":"24LL69EOvYMbSP+K5ydaIwKQdLpiLAQW2XzTTdONgYY=","new-auth-policy":"AAAAAAAAAAEAC8iuOzJsfCEvz5HdnLSO98fhopBFpLgo9fX7/1TF/6KqAAAAAAAAAAEgAQFxAAAAAgAAAAAAAQAL+21OPQovgBAFA+/1biwvpZu8ItTlnZBiGL/DKXTgoIIAAAACIAEBcQAAAAQAAAAAAAEAC7bFwF5YGQnN6n33pfkcDy7tN/128VUi7uW1X4lvLVY/AAAAAQAAAWwAAAE5AAAAAAABAAscaCd8nWVk3YG8z35Wuj7cqziPxgzpWzpEK9JyWPYN/AAAAAEAAAFsAAABOgAAAAAAAQALlAz7Qhe7Htz3+0GTfKl0qmjmmKt4uBJLBwET4hH9RvwAAAABAAABbAAAAScAAAAAAAEAC8Tfq87ajeg2yVZhlSiSsd73IDr7Rv7+xD/8/JO+VAcwAAAAAQAAAWwAAAEmAAABawAAAAAAAQALDDnMvDFtHshfTn3M6F3KHOta8q5u4GWsqsqB8JnLJCYAAAACAAABbAAAASkAAAFgACMACwAEAAAAAAAQABAAAwAQACC2BaF5zNUOUWsO9Vxdw5PNDslawcvHjS3x54a1VHxZfAAgaOCKN2rpEFpajypuc/XSGSr0LnK/e8W9IyZMM8DufpUAC0NIQU5HRS1BVVRIAAAAAAAA"}`)) + c.Check(data, DeepEquals, []byte(`{"auth-value":"x9oO1va6Pz6nQeeGOgoXSBOLbsyw4IQTKwSpyXbw0LE=","auth-policy":"AAAAAAAAAAEAC/cbf1/nUkzaClt4ysmVL5cqWE67D7Brmd7cgdwi7ztVAAAAAAAAAAEgAQFxAAAAAQAAAAAAAQALufUnMhfDMA5sLu0OUIPoKx2NNK4laaj7SfVnqdZFDjYAAAACIAEBcQAAAAUAAAAAAAEAC7bFwF5YGQnN6n33pfkcDy7tN/128VUi7uW1X4lvLVY/AAAAAQAAAWwAAAE5AAAAAAABAAscaCd8nWVk3YG8z35Wuj7cqziPxgzpWzpEK9JyWPYN/AAAAAEAAAFsAAABOgAAAAAAAQALlAz7Qhe7Htz3+0GTfKl0qmjmmKt4uBJLBwET4hH9RvwAAAABAAABbAAAAScAAAAAAAEAC8Tfq87ajeg2yVZhlSiSsd73IDr7Rv7+xD/8/JO+VAcwAAAAAQAAAWwAAAEmAAAAAAABAAu+aY2L0UknzX7Xvdk75B8n/yYmvS2KFUDi+URfLuWrLgAAAAEAAAFsAAABKQAAAWs=","new-auth-value":"24LL69EOvYMbSP+K5ydaIwKQdLpiLAQW2XzTTdONgYY=","new-auth-policy":"AAAAAAAAAAEAC8iuOzJsfCEvz5HdnLSO98fhopBFpLgo9fX7/1TF/6KqAAAAAAAAAAEgAQFxAAAAAgAAAAAAAQAL+21OPQovgBAFA+/1biwvpZu8ItTlnZBiGL/DKXTgoIIAAAACIAEBcQAAAAQAAAAAAAEAC7bFwF5YGQnN6n33pfkcDy7tN/128VUi7uW1X4lvLVY/AAAAAQAAAWwAAAE5AAAAAAABAAscaCd8nWVk3YG8z35Wuj7cqziPxgzpWzpEK9JyWPYN/AAAAAEAAAFsAAABOgAAAAAAAQALlAz7Qhe7Htz3+0GTfKl0qmjmmKt4uBJLBwET4hH9RvwAAAABAAABbAAAAScAAAAAAAEAC8Tfq87ajeg2yVZhlSiSsd73IDr7Rv7+xD/8/JO+VAcwAAAAAQAAAWwAAAEmAAABawAAAAAAAQALDDnMvDFtHshfTn3M6F3KHOta8q5u4GWsqsqB8JnLJCYAAAACAAABbAAAASkAAAFgACMACwAEAAAAAAAQABAAAwAQACC2BaF5zNUOUWsO9Vxdw5PNDslawcvHjS3x54a1VHxZfAAgaOCKN2rpEFpajypuc/XSGSr0LnK/e8W9IyZMM8DufpUAC0NIQU5HRS1BVVRIAAAAAAAA"}`)) } func (s *lockoutauthSuiteNoTPM) TestLockoutAuthParamsUnmarshalJSON(c *C) { - data := []byte(`{"auth-value":"x9oO1va6Pz6nQeeGOgoXSBOLbsyw4IQTKwSpyXbw0LE=","auth-policy":"AAAAAAAAAAEAC5xRENPNjPxvymnylptEkkmB67kMJSALrpC4PA2joYWCAAAAAAAAAAEgAQFxAAAAAQAAAAAAAQAL+21OPQovgBAFA+/1biwvpZu8ItTlnZBiGL/DKXTgoIIAAAACIAEBcQAAAAQAAAAAAAEAC7bFwF5YGQnN6n33pfkcDy7tN/128VUi7uW1X4lvLVY/AAAAAQAAAWwAAAE5AAAAAAABAAscaCd8nWVk3YG8z35Wuj7cqziPxgzpWzpEK9JyWPYN/AAAAAEAAAFsAAABOgAAAAAAAQALlAz7Qhe7Htz3+0GTfKl0qmjmmKt4uBJLBwET4hH9RvwAAAABAAABbAAAAScAAAAAAAEAC8Tfq87ajeg2yVZhlSiSsd73IDr7Rv7+xD/8/JO+VAcwAAAAAQAAAWwAAAEmAAABaw=="}`) - + data := []byte(`{"auth-value":"x9oO1va6Pz6nQeeGOgoXSBOLbsyw4IQTKwSpyXbw0LE=","auth-policy":"AAAAAAAAAAEAC/cbf1/nUkzaClt4ysmVL5cqWE67D7Brmd7cgdwi7ztVAAAAAAAAAAEgAQFxAAAAAQAAAAAAAQALufUnMhfDMA5sLu0OUIPoKx2NNK4laaj7SfVnqdZFDjYAAAACIAEBcQAAAAUAAAAAAAEAC7bFwF5YGQnN6n33pfkcDy7tN/128VUi7uW1X4lvLVY/AAAAAQAAAWwAAAE5AAAAAAABAAscaCd8nWVk3YG8z35Wuj7cqziPxgzpWzpEK9JyWPYN/AAAAAEAAAFsAAABOgAAAAAAAQALlAz7Qhe7Htz3+0GTfKl0qmjmmKt4uBJLBwET4hH9RvwAAAABAAABbAAAAScAAAAAAAEAC8Tfq87ajeg2yVZhlSiSsd73IDr7Rv7+xD/8/JO+VAcwAAAAAQAAAWwAAAEmAAAAAAABAAu+aY2L0UknzX7Xvdk75B8n/yYmvS2KFUDi+URfLuWrLgAAAAEAAAFsAAABKQAAAWs="}`) expected := &LockoutAuthParams{ AuthValue: testutil.DecodeHexString(c, "c7da0ed6f6ba3f3ea741e7863a0a1748138b6eccb0e084132b04a9c976f0d0b1"), AuthPolicy: s.newDefaultLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256), @@ -207,7 +206,7 @@ func (s *lockoutauthSuiteNoTPM) TestLockoutAuthParamsUnmarshalJSON(c *C) { } func (s *lockoutauthSuiteNoTPM) TestLockoutAuthParamsUnmarshalJSONForChangeAuth(c *C) { - data := []byte(`{"auth-value":"x9oO1va6Pz6nQeeGOgoXSBOLbsyw4IQTKwSpyXbw0LE=","auth-policy":"AAAAAAAAAAEAC5xRENPNjPxvymnylptEkkmB67kMJSALrpC4PA2joYWCAAAAAAAAAAEgAQFxAAAAAQAAAAAAAQAL+21OPQovgBAFA+/1biwvpZu8ItTlnZBiGL/DKXTgoIIAAAACIAEBcQAAAAQAAAAAAAEAC7bFwF5YGQnN6n33pfkcDy7tN/128VUi7uW1X4lvLVY/AAAAAQAAAWwAAAE5AAAAAAABAAscaCd8nWVk3YG8z35Wuj7cqziPxgzpWzpEK9JyWPYN/AAAAAEAAAFsAAABOgAAAAAAAQALlAz7Qhe7Htz3+0GTfKl0qmjmmKt4uBJLBwET4hH9RvwAAAABAAABbAAAAScAAAAAAAEAC8Tfq87ajeg2yVZhlSiSsd73IDr7Rv7+xD/8/JO+VAcwAAAAAQAAAWwAAAEmAAABaw==","new-auth-value":"24LL69EOvYMbSP+K5ydaIwKQdLpiLAQW2XzTTdONgYY=","new-auth-policy":"AAAAAAAAAAEAC8iuOzJsfCEvz5HdnLSO98fhopBFpLgo9fX7/1TF/6KqAAAAAAAAAAEgAQFxAAAAAgAAAAAAAQAL+21OPQovgBAFA+/1biwvpZu8ItTlnZBiGL/DKXTgoIIAAAACIAEBcQAAAAQAAAAAAAEAC7bFwF5YGQnN6n33pfkcDy7tN/128VUi7uW1X4lvLVY/AAAAAQAAAWwAAAE5AAAAAAABAAscaCd8nWVk3YG8z35Wuj7cqziPxgzpWzpEK9JyWPYN/AAAAAEAAAFsAAABOgAAAAAAAQALlAz7Qhe7Htz3+0GTfKl0qmjmmKt4uBJLBwET4hH9RvwAAAABAAABbAAAAScAAAAAAAEAC8Tfq87ajeg2yVZhlSiSsd73IDr7Rv7+xD/8/JO+VAcwAAAAAQAAAWwAAAEmAAABawAAAAAAAQALDDnMvDFtHshfTn3M6F3KHOta8q5u4GWsqsqB8JnLJCYAAAACAAABbAAAASkAAAFgACMACwAEAAAAAAAQABAAAwAQACC2BaF5zNUOUWsO9Vxdw5PNDslawcvHjS3x54a1VHxZfAAgaOCKN2rpEFpajypuc/XSGSr0LnK/e8W9IyZMM8DufpUAC0NIQU5HRS1BVVRIAAAAAAAA"}`) + data := []byte(`{"auth-value":"x9oO1va6Pz6nQeeGOgoXSBOLbsyw4IQTKwSpyXbw0LE=","auth-policy":"AAAAAAAAAAEAC/cbf1/nUkzaClt4ysmVL5cqWE67D7Brmd7cgdwi7ztVAAAAAAAAAAEgAQFxAAAAAQAAAAAAAQALufUnMhfDMA5sLu0OUIPoKx2NNK4laaj7SfVnqdZFDjYAAAACIAEBcQAAAAUAAAAAAAEAC7bFwF5YGQnN6n33pfkcDy7tN/128VUi7uW1X4lvLVY/AAAAAQAAAWwAAAE5AAAAAAABAAscaCd8nWVk3YG8z35Wuj7cqziPxgzpWzpEK9JyWPYN/AAAAAEAAAFsAAABOgAAAAAAAQALlAz7Qhe7Htz3+0GTfKl0qmjmmKt4uBJLBwET4hH9RvwAAAABAAABbAAAAScAAAAAAAEAC8Tfq87ajeg2yVZhlSiSsd73IDr7Rv7+xD/8/JO+VAcwAAAAAQAAAWwAAAEmAAAAAAABAAu+aY2L0UknzX7Xvdk75B8n/yYmvS2KFUDi+URfLuWrLgAAAAEAAAFsAAABKQAAAWs=","new-auth-value":"24LL69EOvYMbSP+K5ydaIwKQdLpiLAQW2XzTTdONgYY=","new-auth-policy":"AAAAAAAAAAEAC8iuOzJsfCEvz5HdnLSO98fhopBFpLgo9fX7/1TF/6KqAAAAAAAAAAEgAQFxAAAAAgAAAAAAAQAL+21OPQovgBAFA+/1biwvpZu8ItTlnZBiGL/DKXTgoIIAAAACIAEBcQAAAAQAAAAAAAEAC7bFwF5YGQnN6n33pfkcDy7tN/128VUi7uW1X4lvLVY/AAAAAQAAAWwAAAE5AAAAAAABAAscaCd8nWVk3YG8z35Wuj7cqziPxgzpWzpEK9JyWPYN/AAAAAEAAAFsAAABOgAAAAAAAQALlAz7Qhe7Htz3+0GTfKl0qmjmmKt4uBJLBwET4hH9RvwAAAABAAABbAAAAScAAAAAAAEAC8Tfq87ajeg2yVZhlSiSsd73IDr7Rv7+xD/8/JO+VAcwAAAAAQAAAWwAAAEmAAABawAAAAAAAQALDDnMvDFtHshfTn3M6F3KHOta8q5u4GWsqsqB8JnLJCYAAAACAAABbAAAASkAAAFgACMACwAEAAAAAAAQABAAAwAQACC2BaF5zNUOUWsO9Vxdw5PNDslawcvHjS3x54a1VHxZfAAgaOCKN2rpEFpajypuc/XSGSr0LnK/e8W9IyZMM8DufpUAC0NIQU5HRS1BVVRIAAAAAAAA"}`) authValue := testutil.DecodeHexString(c, "c7da0ed6f6ba3f3ea741e7863a0a1748138b6eccb0e084132b04a9c976f0d0b1") expected := &LockoutAuthParams{ diff --git a/tpm2/tpm_test.go b/tpm2/tpm_test.go index 96dee531..5ae77702 100644 --- a/tpm2/tpm_test.go +++ b/tpm2/tpm_test.go @@ -104,7 +104,7 @@ func (s *tpmSuitePlatform) TestConnectionLockoutAuthSet(c *C) { c.Check(s.TPM().LockoutAuthSet(), testutil.IsFalse) // FullProvising of the TPM puts it in DA lockout mode - c.Check(s.TPM().EnsureProvisioned(WithProvisionNewLockoutAuthValue([]byte("1234"))), IsNil) + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthValue(nil), WithProvisionNewLockoutAuthValue([]byte("1234"))), IsNil) s.AddCleanup(func() { c.Check(s.TPM().HierarchyChangeAuth(s.TPM().LockoutHandleContext(), nil, nil), IsNil) }) From e8589f41c55f3d53be47115f9d35161fd3e93c6b Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 9 Apr 2026 09:26:07 +0100 Subject: [PATCH 3/5] tpm2: add a comment to authorizeLockout --- tpm2/lockoutauth.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tpm2/lockoutauth.go b/tpm2/lockoutauth.go index 09018098..00738836 100644 --- a/tpm2/lockoutauth.go +++ b/tpm2/lockoutauth.go @@ -115,6 +115,12 @@ func (p *lockoutAuthParams) UnmarshalJSON(data []byte) error { return nil } +// authorizeLockout authorizes the use of the lockout hierarchy using the supplied parameters for the +// specified command code. On success, a session is returned that can be used to authorize the specified +// command. The session is either a newly created policy session or the HMAC session returned from +// Connection.HmacSession. +// +// After using the authorization, the caller must execute the returned callback. func (t *Connection) authorizeLockout(authParams *lockoutAuthParams, command tpm2.CommandCode) (session tpm2.SessionContext, lockoutAuthSet bool, done func(), err error) { if len(authParams.NewAuthValue) > 0 || authParams.NewAuthPolicy != nil { return nil, false, nil, errors.New("lockout hierarchy auth value change not supported yet") From 13bdb0109bc344c678367e15ebf30b1cd81aa40f Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 9 Apr 2026 12:39:41 +0100 Subject: [PATCH 4/5] tpm2: Fix one remaining test failure --- tpm2/tpm_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tpm2/tpm_test.go b/tpm2/tpm_test.go index 5ae77702..5be75687 100644 --- a/tpm2/tpm_test.go +++ b/tpm2/tpm_test.go @@ -106,6 +106,7 @@ func (s *tpmSuitePlatform) TestConnectionLockoutAuthSet(c *C) { // FullProvising of the TPM puts it in DA lockout mode c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthValue(nil), WithProvisionNewLockoutAuthValue([]byte("1234"))), IsNil) s.AddCleanup(func() { + s.TPM().LockoutHandleContext().SetAuthValue([]byte("1234")) c.Check(s.TPM().HierarchyChangeAuth(s.TPM().LockoutHandleContext(), nil, nil), IsNil) }) c.Check(s.TPM().LockoutAuthSet(), testutil.IsTrue) From 910ac3031f3a6c286d5ff5d4f7cd29e394221ac3 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Wed, 22 Apr 2026 15:19:49 +0100 Subject: [PATCH 5/5] internal/compattest: Fix test failure --- internal/compattest/v0_test.go | 2 +- internal/compattest/v1_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/compattest/v0_test.go b/internal/compattest/v0_test.go index 3fba4318..0bd9b707 100644 --- a/internal/compattest/v0_test.go +++ b/internal/compattest/v0_test.go @@ -62,7 +62,7 @@ func (s *compatTestV0Suite) TestUnseal2(c *C) { func (s *compatTestV0Suite) TestUnsealAfterReprovision(c *C) { // Test that reprovisioning doesn't touch the legacy lock NV index if it is valid - c.Assert(s.TPM().EnsureProvisioned(secboot_tpm2.ProvisionWithoutLockout()), IsNil) + c.Assert(s.TPM().EnsureProvisioned(), IsNil) s.testUnseal(c, s.absPath("pcrSequence.1")) } diff --git a/internal/compattest/v1_test.go b/internal/compattest/v1_test.go index 5838a78f..36e3b3e2 100644 --- a/internal/compattest/v1_test.go +++ b/internal/compattest/v1_test.go @@ -51,7 +51,7 @@ func (s *compatTestV1Suite) TestUnseal2(c *C) { func (s *compatTestV1Suite) TestUnsealAfterReprovision(c *C) { // This should still work because the primary key doesn't change. - c.Assert(s.TPM().EnsureProvisioned(secboot_tpm2.ProvisionWithoutLockout()), IsNil) + c.Assert(s.TPM().EnsureProvisioned(), IsNil) s.testUnseal(c, s.absPath("pcrSequence.1")) }