From cf97c51f4115f1c352697b800f909e5c400282be Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 2 Apr 2026 16:45:02 +0100 Subject: [PATCH 1/8] tpm2: Support safely rotating the lockout hierarchy authorization value This updates the Connection.EnsureProvisioned API to safely support rotating the authorization value for the lockout hierarchy, which can be used during reprovisioning. This is particularly required during a factory reset, when secrets should be rotated. The lockout hierarchy is used on successful boots to reset the DA counter. A single authorization failure makes the lockout hierarchy unavailable for the preprogrammed recovery time (currently 24 hours). This makes it challenging to update the value because it's not possible to atomically update both the value in the TPM and the value stored in persistent storage. This PR works around this by using a transient authorization policy that permits the use of the TPM2_HierarchyChangeAuth command with a signed assertion (TPM2_PolicySigned), using a key that's derived from the original authorization value. An authorization policy is used by default for other uses of the lockout hierarchy as this provides a way to detect a transient state resulting from a previously interrupted update before using the incorrect authorization value. The WithProvisionNewLockoutAuthValue option no longer allows an authorization value to be specified directly. One is derived automatically from the supplied random source, with the length determined by the value of the TPM_PT_CONTEXT_HASH property. A callback is supplied so that the authorization data can be stored to persistent storage. This happens several times during an update. --- go.mod | 2 +- go.sum | 2 + internal/testutil/x509.go | 6 + tpm2/export_test.go | 3 + tpm2/lockoutauth.go | 528 ++++++++++++++++++++++++++++++--- tpm2/lockoutauth_test.go | 494 ++++++++++++++++++++++++------ tpm2/provisioning.go | 85 +++--- tpm2/provisioning_test.go | 610 ++++++++++++++++++++++++++++++++------ tpm2/tpm_test.go | 8 +- 9 files changed, 1473 insertions(+), 265 deletions(-) diff --git a/go.mod b/go.mod index 9770f393..9e30b2c6 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.16.0 + github.com/canonical/go-tpm2 v1.16.2 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 6e12c2c3..414b5dee 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,8 @@ github.com/canonical/go-tpm2 v1.15.0 h1:T4dVCO8qCs76vDDs4vWNpvPdh7UHuSORPH4Scq7N 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/go-tpm2 v1.16.2 h1:Jg/okfKQ1BDdRYIjq2ZrwhsDDttMn+NSnxue3XUJBZg= +github.com/canonical/go-tpm2 v1.16.2/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/internal/testutil/x509.go b/internal/testutil/x509.go index f9efb4ad..6a062326 100644 --- a/internal/testutil/x509.go +++ b/internal/testutil/x509.go @@ -39,3 +39,9 @@ func ParsePKCS1PrivateKey(c *C, data []byte) *rsa.PrivateKey { c.Assert(err, IsNil) return key } + +func ParsePKCS8PrivateKey(c *C, data []byte) any { + key, err := x509.ParsePKCS8PrivateKey(data) + c.Assert(err, IsNil) + return key +} diff --git a/tpm2/export_test.go b/tpm2/export_test.go index 7a2dae3d..e94e43ce 100644 --- a/tpm2/export_test.go +++ b/tpm2/export_test.go @@ -51,12 +51,15 @@ var ( MakeSealedKeyData = makeSealedKeyData MakeKeyDataNoAuth = makeKeyDataNoAuth MakeKeyDataWithPassphraseConstructor = makeKeyDataWithPassphraseConstructor + NewDefaultLockoutAuthPolicy = newDefaultLockoutAuthPolicy NewKeyData = newKeyData NewKeyDataPolicy = newKeyDataPolicy NewKeyDataPolicyLegacy = newKeyDataPolicyLegacy NewPolicyAuthPublicKey = newPolicyAuthPublicKey NewPolicyOrDataV0 = newPolicyOrDataV0 NewPolicyOrTree = newPolicyOrTree + NewUpdateLockoutAuthValueKey = newUpdateLockoutAuthValueKey + NewUpdateAuthValueLockoutAuthPolicy = newUpdateAuthValueLockoutAuthPolicy ReadKeyDataV0 = readKeyDataV0 ReadKeyDataV1 = readKeyDataV1 ReadKeyDataV2 = readKeyDataV2 diff --git a/tpm2/lockoutauth.go b/tpm2/lockoutauth.go index 00738836..e169f0f3 100644 --- a/tpm2/lockoutauth.go +++ b/tpm2/lockoutauth.go @@ -20,24 +20,49 @@ package tpm2 import ( + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" "encoding/json" "errors" "fmt" + "io" "github.com/canonical/go-tpm2" "github.com/canonical/go-tpm2/mu" + "github.com/canonical/go-tpm2/objectutil" "github.com/canonical/go-tpm2/policyutil" + internal_crypto "github.com/snapcore/secboot/internal/crypto" + "golang.org/x/crypto/hkdf" ) var ( - // ErrEmptyLockoutAuthValue is returned from Connection.ResetDictionaryAttackLock if - // the authorization value for the lockout hierarchy is unset. - ErrEmptyLockoutAuthValue = errors.New("the authorization value for the lockout hierarchy is empty") - - // ErrInvalidLockoutAuthPolicy is returned from Connection.ResetDictionaryAttackLock if - // the authorization policy for the lockout hierarchy is not consistent with the supplied - // data. - ErrInvalidLockoutAuthPolicy = errors.New("the authorization policy for the lockout hierarchy is invalid") + // ErrInvalidLockoutAuthPolicy is returned from [Connection.ResetDictionaryAttackLock] or + // [Connection.EnsureProvisioned] if the authorization policy for the lockout hierarchy is + // not consistent with the supplied data. [Connection.EnsureProvisioned] should be called + // with the [WithProvisionNewLockoutAuthValue] option in order to fix this. + ErrInvalidLockoutAuthPolicy = errors.New("the authorization policy for the lockout hierarchy is inconsistent with the supplied data") + + // ErrLockoutAuthNotInitialized is returned from [Connection.ResetDictionaryAttackLock] if + // the authorization parameters for the lockout hierarchy need to be initialized. + // [Connection.EnsureProvisioned] should be called with the [WithProvisionNewLockoutAuthValue] + // option in order to fix this. + ErrLockoutAuthNotInitialized = errors.New("the authorization parameters for the lockout hierarchy are not fully initialized") + + // ErrLockoutAuthUpdateInterrupted is returned from [Connection.ResetDictionaryAttackLock] or + // [Connection.EnsureProvisioned] if a previous update to the authorization value for the lockout + // hierarchy was interrupted. [Connection.EnsureProvisioned] should be called with the + // [WithProvisionNewLockoutAuthValue] option in order to fix this. + ErrLockoutAuthUpdateInterrupted = errors.New("a previous attempt to update the authorization parameters for the lockout hierarchy was interrupted") + + // ErrLockoutAuthUpdateUnsupported is returned from [Connection.EnsureProvisioned] when called + // with the [WithProvisionNewLockoutAuthValue] option if the authorization value for the + // lockout hierarchy is already set and the system does not support updating it. + ErrLockoutAuthUpdateUnsupported = errors.New("updating the authorization parameters for the lockout hierarchy is not supported") + + errLockoutAuthPolicyNotSupported = errors.New("lockout auth policies not supported") ) // InvalidLockoutAuthDataError is returned from [Connection.ResetDictionaryAttackLock] if the @@ -115,35 +140,414 @@ func (p *lockoutAuthParams) UnmarshalJSON(data []byte) error { return nil } +// newDefaultLockoutAuthPolicy returns a new policy that permits use of an authorization value with +// the TPM2_DictionaryAttackLockReset, TPM2_DictionaryAttackParameters, TPM2_Clear, TPM2_ClearControl, +// and TPM2_SetPrimaryPolicy commands. +func newDefaultLockoutAuthPolicy(alg tpm2.HashAlgorithmId) (tpm2.Digest, *policyutil.Policy, error) { + builder := policyutil.NewPolicyBuilder(alg) + builder.RootBranch().AddBranchNode(func(n *policyutil.PolicyBuilderBranchNode) { + n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { + b.PolicyCommandCode(tpm2.CommandDictionaryAttackLockReset) + }) + n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { + b.PolicyCommandCode(tpm2.CommandDictionaryAttackParameters) + }) + n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { + b.PolicyCommandCode(tpm2.CommandClearControl) + }) + n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { + b.PolicyCommandCode(tpm2.CommandClear) + }) + n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { + b.PolicyCommandCode(tpm2.CommandSetPrimaryPolicy) + }) + }) + builder.RootBranch().PolicyAuthValue() + + return builder.Policy() +} + +func newUpdateLockoutAuthValueKey(ikm []byte) (*ecdsa.PrivateKey, *tpm2.Public, error) { + r := hkdf.Expand(crypto.SHA256.New, ikm, []byte("UPDATE-AUTH-VALUE")) + key, err := internal_crypto.GenerateECDSAKey(elliptic.P256(), r) + if err != nil { + return nil, nil, err + } + pubKey, err := objectutil.NewECCPublicKey(&key.PublicKey) + if err != nil { + return nil, nil, err + } + return key, pubKey, nil +} + +// newUpdateAuthValueLockoutAuthPolicy returns a new policy that permits the use of a signed +// authorization with the TPM2_HierarchyChangeAuth command in order to change the authorization +// value to the specified value. It also supports using an authorization value with the +// TPM2_SetPrimaryPolicy command. +func newUpdateAuthValueLockoutAuthPolicy(alg tpm2.HashAlgorithmId, oldAuthValue []byte) (tpm2.Digest, *policyutil.Policy, *ecdsa.PrivateKey, *tpm2.Public, error) { + key, pubKey, err := newUpdateLockoutAuthValueKey(oldAuthValue) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("cannot create temporary signing key: %w", err) + } + builder := policyutil.NewPolicyBuilder(alg) + builder.RootBranch().AddBranchNode(func(n *policyutil.PolicyBuilderBranchNode) { + n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { + b.PolicyCommandCode(tpm2.CommandSetPrimaryPolicy) + b.PolicyAuthValue() + }) + n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { + // Note that this branch permits setting the authorization value without any + // dictionary attack protection. This is only a transient state so this is + // ok but not ideal. I would have liked to have used a TPM2_PolicyCpHash + // assertion here to bind the policy to the new authorization value so that + // this branch would require knowledge of both the old and new values. + // However, TPM2_PolicyCpHash doesn't work here because we use parameter + // encryption and the CpHash is computed after the command parameters are + // encrypted. + // + // As the policy is not hardcoded in the metadata, it would be trivial to + // update this policy branch in the future in order to make improvements. + b.PolicyCommandCode(tpm2.CommandHierarchyChangeAuth) + b.PolicySigned(pubKey, []byte("UPDATE-AUTH-VALUE")) + }) + }) + + digest, policy, err := builder.Policy() + if err != nil { + return nil, nil, nil, nil, err + } + + return digest, policy, key, pubKey, nil +} + +type lockoutAuthValueUpdateStateMachineState func() (lockoutAuthValueUpdateStateMachineState, error) + +type lockoutAuthValueUpdateStateMachine struct { + rand io.Reader + tpm *Connection + + authParams *lockoutAuthParams + + next lockoutAuthValueUpdateStateMachineState + err error + + updateAuthKey *ecdsa.PrivateKey + updateAuthPubKey *tpm2.Public +} + +func newLockoutAuthValueUpdateStateMachine(rand io.Reader, tpm *Connection, authParams *lockoutAuthParams) (*lockoutAuthValueUpdateStateMachine, error) { + m := &lockoutAuthValueUpdateStateMachine{ + rand: rand, + tpm: tpm, + authParams: authParams, + } + + switch { + default: + m.next = m.prepare + case len(authParams.NewAuthValue) > 0 && authParams.NewAuthPolicy != nil: + // prepare was completed already. + algs := authParams.NewAuthPolicy.DigestAlgs() + if len(algs) == 0 { + return nil, &InvalidLockoutAuthDataError{err: errors.New("new-auth-policy has no computed digests")} + } + digest, err := authParams.NewAuthPolicy.Digest(algs[0]) + if err != nil { + return nil, &InvalidLockoutAuthDataError{err: fmt.Errorf("cannot obtain new-auth-policy digest: %w", err)} + } + m.updateAuthKey, m.updateAuthPubKey, err = newUpdateLockoutAuthValueKey(authParams.AuthValue) + if err != nil { + return nil, fmt.Errorf("cannot create temporary signing key: %w", err) + } + m.next = func() (lockoutAuthValueUpdateStateMachineState, error) { + return m.setNewAuthValuePolicy(algs[0], digest) + } + case len(authParams.NewAuthValue) > 0: + // setNewAuthValuePolicy was completed already. + if authParams.AuthPolicy == nil { + return nil, &InvalidLockoutAuthDataError{err: errors.New("missing auth-policy")} + } + algs := authParams.AuthPolicy.DigestAlgs() + if len(algs) == 0 { + return nil, &InvalidLockoutAuthDataError{err: errors.New("auth-policy has no computed digests")} + } + var err error + m.updateAuthKey, m.updateAuthPubKey, err = newUpdateLockoutAuthValueKey(authParams.AuthValue) + if err != nil { + return nil, fmt.Errorf("cannot create temporary signing key: %w", err) + } + m.next = func() (lockoutAuthValueUpdateStateMachineState, error) { + return m.setNewAuthValue(algs[0]) + } + case authParams.NewAuthPolicy != nil: + // setNewAuthValue was completed already. + algs := authParams.NewAuthPolicy.DigestAlgs() + if len(algs) == 0 { + return nil, &InvalidLockoutAuthDataError{err: errors.New("new-auth-policy has no computed digests")} + } + digest, err := authParams.NewAuthPolicy.Digest(algs[0]) + if err != nil { + return nil, &InvalidLockoutAuthDataError{err: fmt.Errorf("cannot obtain new-auth-policy digest: %w", err)} + } + m.next = func() (lockoutAuthValueUpdateStateMachineState, error) { + return m.setDefaultPolicy(algs[0], digest) + } + } + + return m, nil +} + +func (m *lockoutAuthValueUpdateStateMachine) signedAuthorizer(sessionAlg tpm2.HashAlgorithmId, sessionNonce tpm2.Nonce, authKey tpm2.Name, policyRef tpm2.Nonce) (*policyutil.PolicySignedAuthorization, error) { + params := &policyutil.PolicySignedParams{ + HashAlg: sessionAlg, + NonceTPM: sessionNonce, + } + return policyutil.SignPolicySignedAuthorization(rand.Reader, params, m.updateAuthPubKey, policyRef, m.updateAuthKey, tpm2.HashAlgorithmSHA256) +} + +// prepare creates a new authorization value and a temporary authorization policy that can be used to +// update the lockout hierarchy's authorization value to the new value. +// +// On completion, the updated state can still be used to authorize the lockout hierarchy using the +// default policy which requires an authorization value for all supported commands. +func (m *lockoutAuthValueUpdateStateMachine) prepare() (lockoutAuthValueUpdateStateMachineState, error) { + if m.rand == nil { + return nil, errors.New("no entropy source provided") + } + + val, err := m.tpm.GetCapabilityTPMProperty(tpm2.PropertyContextHash) + if err != nil { + return nil, fmt.Errorf("cannot obtain value of TPM_PT_CONTEXT_HASH: %w", err) + } + + contextHash := tpm2.HashAlgorithmId(val) + if !contextHash.IsValid() { + return nil, fmt.Errorf("unexpected TPM_PT_CONTEXT_HASH value: %v", contextHash) + } + + m.authParams.NewAuthValue = make([]byte, contextHash.Size()) + if _, err := m.rand.Read(m.authParams.NewAuthValue); err != nil { + return nil, fmt.Errorf("cannot create new auth value") + } + + var newPolicyDigest tpm2.Digest + newPolicyDigest, m.authParams.NewAuthPolicy, m.updateAuthKey, m.updateAuthPubKey, err = newUpdateAuthValueLockoutAuthPolicy(contextHash, m.authParams.AuthValue) + if err != nil { + return nil, fmt.Errorf("cannot create temporary auth policy: %w", err) + } + + return func() (lockoutAuthValueUpdateStateMachineState, error) { + return m.setNewAuthValuePolicy(contextHash, newPolicyDigest) + }, nil +} + +// setNewAuthValuePolicy sets the authorization policy for the lockout hierarchy to the temporary +// policy that permits updating the hierarchy's authorization value. +// +// On completion, the updated state can only be used to authorize the lockout hierarchy using the +// temporary policy to update it's authorization value to a specific value, and to update it's policy +// using it's authorization value. Note that it is not safe to use the authorization value yet though. +func (m *lockoutAuthValueUpdateStateMachine) setNewAuthValuePolicy(policyAlg tpm2.HashAlgorithmId, policyDigest tpm2.Digest) (lockoutAuthValueUpdateStateMachineState, error) { + session, done, err := m.tpm.authorizeLockout(m.authParams, true, tpm2.CommandSetPrimaryPolicy, m.signedAuthorizer) + switch { + case errors.Is(err, errLockoutAuthPolicyNotSupported): + return m.setAuthValueWithoutPolicy, nil + case err != nil: + return nil, err + } + defer done() + + if err := m.tpm.SetPrimaryPolicy(m.tpm.LockoutHandleContext(), policyDigest, policyAlg, session); err != nil { + return nil, fmt.Errorf("cannot set temporary auth policy: %w", err) + } + + m.authParams.AuthPolicy = m.authParams.NewAuthPolicy + m.authParams.NewAuthPolicy = nil + + return func() (lockoutAuthValueUpdateStateMachineState, error) { + return m.setNewAuthValue(policyAlg) + }, nil +} + +func (m *lockoutAuthValueUpdateStateMachine) setAuthValueWithoutPolicy() (lockoutAuthValueUpdateStateMachineState, error) { + m.authParams.AuthPolicy = nil + m.authParams.NewAuthPolicy = nil + + session, done, err := m.tpm.authorizeLockout(m.authParams, false, tpm2.CommandHierarchyChangeAuth, m.signedAuthorizer) + if err != nil { + return nil, err + } + defer done() + + if len(m.tpm.LockoutHandleContext().AuthValue()) > 0 { + return nil, ErrLockoutAuthUpdateUnsupported + } + + // We use command parameter encryption here to protect the new authorization value. + if err := m.tpm.HierarchyChangeAuth(m.tpm.LockoutHandleContext(), m.authParams.NewAuthValue, session.IncludeAttrs(tpm2.AttrCommandEncrypt)); err != nil { + return nil, fmt.Errorf("cannot set new auth value without policy: %w", err) + } + + m.authParams.AuthValue = m.authParams.NewAuthValue + m.authParams.NewAuthValue = nil + + return nil, nil +} + +// setNewAuthValue updates the authorization value for the lockout hierarchy. +// +// On completion, the updated state can only be used to authorize the lockout hierarchy using the +// temporary policy to set a new policy using an authorization value. +func (m *lockoutAuthValueUpdateStateMachine) setNewAuthValue(policyAlg tpm2.HashAlgorithmId) (lockoutAuthValueUpdateStateMachineState, error) { + session, done, err := m.tpm.authorizeLockout(m.authParams, false, tpm2.CommandHierarchyChangeAuth, m.signedAuthorizer) + if err != nil { + return nil, err + } + defer done() + + // We use command parameter encryption here to protect the new authorization value. + switch { + case session.Handle().Type() == tpm2.HandleTypePolicySession: + // We're using policy auth so need to supply the HMAC session as an extra + // session for parameter encryption. + err = m.tpm.HierarchyChangeAuth(m.tpm.LockoutHandleContext(), m.authParams.NewAuthValue, session, m.tpm.HmacSession().IncludeAttrs(tpm2.AttrCommandEncrypt)) + default: + // We're using HMAC auth + err = m.tpm.HierarchyChangeAuth(m.tpm.LockoutHandleContext(), m.authParams.NewAuthValue, session.IncludeAttrs(tpm2.AttrCommandEncrypt)) + } + if err != nil { + return nil, fmt.Errorf("cannot set new auth value: %w", err) + } + + m.authParams.AuthValue = m.authParams.NewAuthValue + m.authParams.NewAuthValue = nil + + var newPolicyDigest tpm2.Digest + newPolicyDigest, m.authParams.NewAuthPolicy, err = newDefaultLockoutAuthPolicy(policyAlg) + if err != nil { + return nil, fmt.Errorf("cannot create default auth policy: %w", err) + } + + return func() (lockoutAuthValueUpdateStateMachineState, error) { + return m.setDefaultPolicy(policyAlg, newPolicyDigest) + }, nil +} + +// setDefaultPolicy sets the authorization policy for the lockout hierarchy to the default policy. +func (m *lockoutAuthValueUpdateStateMachine) setDefaultPolicy(policyAlg tpm2.HashAlgorithmId, policyDigest tpm2.Digest) (lockoutAuthValueUpdateStateMachineState, error) { + session, done, err := m.tpm.authorizeLockout(m.authParams, false, tpm2.CommandSetPrimaryPolicy, m.signedAuthorizer) + if err != nil { + return nil, err + } + defer done() + + if err := m.tpm.SetPrimaryPolicy(m.tpm.LockoutHandleContext(), policyDigest, policyAlg, session); err != nil { + return nil, fmt.Errorf("cannot set temporary auth policy: %w", err) + } + + m.authParams.AuthPolicy = m.authParams.NewAuthPolicy + m.authParams.NewAuthPolicy = nil + + // Done! + return nil, nil +} + +func (m *lockoutAuthValueUpdateStateMachine) hasMoreWork() bool { + return m.next != nil && m.err == nil +} + +func (m *lockoutAuthValueUpdateStateMachine) runNext() error { + if m.err != nil { + return fmt.Errorf("error occurred during previous state: %w", m.err) + } + if m.next == nil { + return errors.New("no more work to do") + } + + m.next, m.err = m.next() + return m.err +} + // 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") +func (t *Connection) authorizeLockout(authParams *lockoutAuthParams, allowFallbackToHMACSession bool, command tpm2.CommandCode, signAuthFn policyutil.SignedAuthorizer) (session tpm2.SessionContext, done func(), err error) { + // Select the correct policy based on the current policy digest for the lockout hierarchy. + var ( + policy *policyutil.Policy + policyAlg tpm2.HashAlgorithmId + ) + if authParams.AuthPolicy != nil || authParams.NewAuthPolicy != nil { + // Only do this if there is policy data. + switch currentPolicyDigest, err := t.GetCapabilityAuthPolicy(tpm2.HandleLockout); { + case tpm2.IsTPMParameterError(err, tpm2.ErrorValue, tpm2.CommandGetCapability, 1): + // TPM_CAP_AUTH_POLICIES is unsupported on TPMs older than v1.38 of the + // reference library spec. + return nil, nil, errLockoutAuthPolicyNotSupported + case err != nil: + return nil, nil, fmt.Errorf("cannot obtain current TPM lockout auth policy: %w", err) + case currentPolicyDigest.HashAlg == tpm2.HashAlgorithmNull: + // Lockout hierarchy has no policy set yet. Always fall back to HMAC + // auth in this case. This is safe because we aren't in the process of + // updating the authorization value. + default: + // We have the current policy digest for the lockout hierarchy. + for _, p := range []*policyutil.Policy{authParams.AuthPolicy, authParams.NewAuthPolicy} { + if p == nil { + continue + } + algs := p.DigestAlgs() + if len(algs) == 0 { + continue + } + if algs[0] != currentPolicyDigest.HashAlg { + continue + } + digest, err := p.Digest(algs[0]) + if err != nil { + return nil, nil, fmt.Errorf("cannot obtain computed digest from policy: %w", err) + } + if bytes.Equal(digest, currentPolicyDigest.Digest()) { + // This is the matching policy. + policy = p + policyAlg = algs[0] + break + } + } + if policy == nil && !allowFallbackToHMACSession { + return nil, nil, ErrInvalidLockoutAuthPolicy + } + } } var authValue []byte + // Determine if the lockout hierarchy has an authorization value set. This is to avoid specifying + // an invalid value if none is set, which can happen after a TPM is cleared or a system board change. val, err := t.GetCapabilityTPMProperty(tpm2.PropertyPermanent) if err != nil { - return nil, false, nil, fmt.Errorf("cannot obtain value of TPM_PT_PERMANENT: %w", err) + return nil, 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 = authParams.AuthValue } + var authValueNeeded bool switch { - case authParams.AuthPolicy == nil: + case policy == nil: + // There is no policy, fall back to using a HMAC auth. session = t.HmacSession() + authValueNeeded = true default: - session, err = t.StartAuthSession(nil, nil, tpm2.SessionTypePolicy, nil, defaultSessionHashAlgorithm) + // Execute the selected policy with the supplied command code as a constraint so that the correct + // branch executes. + session, err = t.StartAuthSession(nil, nil, tpm2.SessionTypePolicy, nil, policyAlg) if err != nil { - return nil, false, nil, fmt.Errorf("cannot start policy session: %w", err) + return nil, nil, fmt.Errorf("cannot start policy session: %w", err) } sessionInternal := session defer func() { @@ -153,39 +557,66 @@ func (t *Connection) authorizeLockout(authParams *lockoutAuthParams, command tpm t.FlushContext(sessionInternal) }() - // Execute policy session, constraining the use to the TPM2_DictionaryAttackLockReset command so - // that the correct branch executes. - _, err := authParams.AuthPolicy.Execute( - policyutil.NewPolicyExecuteSession(t.TPMContext, session), - policyutil.WithSessionUsageCommandCodeConstraint(command), + result, err := policy.Execute( + policyutil.NewPolicyExecuteSession(t.TPMContext, session), // the session to execute the policy in + policyutil.WithTPMHelper(t.TPMContext), // to execute extra TPM commands (TPM2_LoadExternal) + policyutil.WithResources(t.TPMContext, policyutil.WithSignedAuthorizer(signAuthFn)), // to obtain signed authorizations + policyutil.WithSessionUsageCommandCodeConstraint(command), // constrain to the specified command ) - if err != nil { - return nil, false, nil, ErrInvalidLockoutAuthPolicy + var pe *policyutil.NoAppropriatePathError + switch { + case errors.As(err, &pe): + // If a path cannot be selected, assume that a previous update was interrutped. + return nil, nil, ErrLockoutAuthUpdateInterrupted + case err != nil: + // Treat any other error as invalid auth data. + return nil, nil, &InvalidLockoutAuthDataError{err: fmt.Errorf("cannot execute policy: %w", err)} } + + authValueNeeded = result.AuthValueNeeded } origAuthValue := t.LockoutHandleContext().AuthValue() - t.LockoutHandleContext().SetAuthValue(authValue) - defer func() { - if err == nil { - return - } - t.LockoutHandleContext().SetAuthValue(origAuthValue) - }() + if authValueNeeded { + t.LockoutHandleContext().SetAuthValue(authValue) + } - return session, lockoutAuthSet, func() { - if authParams.AuthPolicy != nil { + return session, func() { + if policy != 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) +func (t *Connection) updateLockoutAuthValue(rand io.Reader, params *lockoutAuthParams, syncParams func() error) error { + m, err := newLockoutAuthValueUpdateStateMachine(rand, t, params) if err != nil { return err } + + for m.hasMoreWork() { + if err := m.runNext(); err != nil { + return err + } + if syncParams != nil { + if err := syncParams(); err != nil { + return fmt.Errorf("cannot sync updated auth params: %w", err) + } + } + } + + return nil +} + +func (t *Connection) resetDictionaryAttackLockImpl(params *lockoutAuthParams) error { + session, done, err := t.authorizeLockout(params, false, tpm2.CommandDictionaryAttackLockReset, nil) + switch { + case errors.Is(err, errLockoutAuthPolicyNotSupported): + return &InvalidLockoutAuthDataError{err: err} + case err != nil: + return err + } defer done() switch err := t.DictionaryAttackLockReset(t.LockoutHandleContext(), session); { @@ -193,14 +624,18 @@ func (t *Connection) resetDictionaryAttackLockImpl(params *lockoutAuthParams) er return AuthFailError{tpm2.HandleLockout} case tpm2.IsTPMWarning(err, tpm2.WarningLockout, tpm2.CommandDictionaryAttackLockReset): return ErrTPMLockout - case tpm2.IsTPMSessionError(err, tpm2.ErrorPolicyFail, tpm2.CommandDictionaryAttackLockReset, 1): - return ErrInvalidLockoutAuthPolicy case err != nil: return fmt.Errorf("cannot reset dictionary attack counter: %w", err) } - if !lockoutAuthSet { - return ErrEmptyLockoutAuthValue + switch { + case len(t.LockoutHandleContext().AuthValue()) == 0: + // authorization was performed with an empty auth value. + return ErrLockoutAuthNotInitialized + case params.AuthPolicy != nil && session.Handle().Type() == tpm2.HandleTypeHMACSession: + // authorization was performed with a HMAC session when we have policy data, + // which only happens if the lockout hierarchy has no policy set. + return ErrLockoutAuthNotInitialized } return nil } @@ -208,13 +643,13 @@ func (t *Connection) resetDictionaryAttackLockImpl(params *lockoutAuthParams) er // ResetDictionaryAttackLock resets the TPM's dictionary attack counter using the // TPM2_DictionaryAttackLockReset command. The caller supplies authorization data for the TPM's // lockout hierarchy which will have been supplied by a previous call to -// [Connection.EnsureProvisioned] (XXX: in a future PR). +// [Connection.EnsureProvisioned] with the [WithProvisionNewLockoutAuthValue] option. // // If the supplied authorization data is invalid, a *[InvalidLockoutAuthDataError] error will // be returned. // -// If the TPM indicates that the lockout hierarchy has an empty authorization value, this function -// will still succeed but will return an [ErrEmptyLockoutAuthValue] error. +// If the TPM indicates that the lockout hierarchy has an empty authorization value or policy, +// this function will still succeed but will return an [ErrLockoutAuthNotInitialized] error. // // If authorization of the TPM's lockout hierarchy fails, an [AuthFailError] error will be returned. // In this case, the lockout hierarchy will become unavailable for the current lockout recovery @@ -225,6 +660,11 @@ func (t *Connection) resetDictionaryAttackLockImpl(params *lockoutAuthParams) er // // If the authorization policy for the TPM's lockout hierarchy is invalid, an // [ErrInvalidLockoutAuthPolicy] error will be returned. +// +// If a previous call to [Connection.EnsureProvisioned] with the [WithProvisionNewLockoutAuthValue] +// option was interrupted, this may return a [ErrLockoutAuthUpdateInterrupted] error. In this case, +// Connection.EnsureProvisioned] should be called again with the [WithProvisionNewLockoutAuthValue] +// option in order to complete the previous operation. func (t *Connection) ResetDictionaryAttackLock(lockoutAuthData []byte) error { var params *lockoutAuthParams if err := json.Unmarshal(lockoutAuthData, ¶ms); err != nil { @@ -236,11 +676,11 @@ func (t *Connection) ResetDictionaryAttackLock(lockoutAuthData []byte) error { // ResetDictionaryAttackLockWithAuthValue resets the TPM's dictionary attack counter using the // TPM2_DictionaryAttackLockReset command. The caller supplies the authorization value for the // TPM's lockout hierarchy. This API 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. +// of [Connection.EnsureProvisioned] where an authorization value was chosen and supplied by the +// caller. // // If the TPM indicates that the lockout hierarchy has an empty authorization value, this function -// will still succeed but will return an [ErrEmptyLockoutAuthValue] error. +// will still succeed but will return an [ErrLockoutAuthNotInitialized] error. // // If authorization of the TPM's lockout hierarchy fails, an [AuthFailError] error will be returned. // In this case, the lockout hierarchy will become unavailable for the current lockout recovery diff --git a/tpm2/lockoutauth_test.go b/tpm2/lockoutauth_test.go index 8123a78f..49fb3dc1 100644 --- a/tpm2/lockoutauth_test.go +++ b/tpm2/lockoutauth_test.go @@ -20,18 +20,16 @@ package tpm2_test import ( - "crypto/elliptic" + "crypto/ecdsa" "crypto/rand" "encoding/json" "errors" - "golang.org/x/crypto/hkdf" . "gopkg.in/check.v1" "github.com/canonical/go-tpm2" "github.com/canonical/go-tpm2/objectutil" "github.com/canonical/go-tpm2/policyutil" - internal_crypto "github.com/snapcore/secboot/internal/crypto" "github.com/snapcore/secboot/internal/testutil" "github.com/snapcore/secboot/internal/tpm2test" . "github.com/snapcore/secboot/tpm2" @@ -40,71 +38,13 @@ import ( type lockoutauthSuiteMixin struct{} func (*lockoutauthSuiteMixin) newDefaultLockoutAuthPolicy(c *C, alg tpm2.HashAlgorithmId) (tpm2.Digest, *policyutil.Policy) { - builder := policyutil.NewPolicyBuilder(alg) - builder.RootBranch().AddBranchNode(func(n *policyutil.PolicyBuilderBranchNode) { - n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { - b.AddBranchNode(func(n *policyutil.PolicyBuilderBranchNode) { - n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { - b.PolicyCommandCode(tpm2.CommandDictionaryAttackLockReset) - }) - n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { - b.PolicyCommandCode(tpm2.CommandDictionaryAttackParameters) - }) - n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { - b.PolicyCommandCode(tpm2.CommandClearControl) - }) - 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() - }) - }) - - digest, policy, err := builder.Policy() + digest, policy, err := NewDefaultLockoutAuthPolicy(alg) c.Assert(err, IsNil) return digest, policy } -func (*lockoutauthSuiteMixin) newRotateAuthValueLockoutAuthPolicy(c *C, alg tpm2.HashAlgorithmId, oldAuthValue []byte) (tpm2.Digest, *policyutil.Policy) { - r := hkdf.Expand(alg.NewHash, oldAuthValue, []byte("CHANGE-AUTH")) - key, err := internal_crypto.GenerateECDSAKey(elliptic.P256(), r) - c.Assert(err, IsNil) - pubKey, err := objectutil.NewECCPublicKey(&key.PublicKey) - c.Assert(err, IsNil) - - builder := policyutil.NewPolicyBuilder(alg) - builder.RootBranch().AddBranchNode(func(n *policyutil.PolicyBuilderBranchNode) { - n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { - b.AddBranchNode(func(n *policyutil.PolicyBuilderBranchNode) { - n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { - b.PolicyCommandCode(tpm2.CommandDictionaryAttackLockReset) - }) - n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { - b.PolicyCommandCode(tpm2.CommandDictionaryAttackParameters) - }) - n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { - b.PolicyCommandCode(tpm2.CommandClearControl) - }) - n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { - b.PolicyCommandCode(tpm2.CommandClear) - }) - }) - b.PolicyAuthValue() - }) - n.AddBranch("", func(b *policyutil.PolicyBuilderBranch) { - b.PolicyCommandCode(tpm2.CommandHierarchyChangeAuth) - b.PolicySigned(pubKey, []byte("CHANGE-AUTH")) - }) - }) - - digest, policy, err := builder.Policy() +func (*lockoutauthSuiteMixin) newUpdateAuthValueLockoutAuthPolicy(c *C, alg tpm2.HashAlgorithmId, oldAuthValue []byte) (tpm2.Digest, *policyutil.Policy) { + digest, policy, _, _, err := NewUpdateAuthValueLockoutAuthPolicy(alg, oldAuthValue) c.Assert(err, IsNil) return digest, policy } @@ -132,8 +72,8 @@ func (s *lockoutauthSuiteNoTPM) newDefaultLockoutAuthPolicy(c *C, alg tpm2.HashA return policy } -func (s *lockoutauthSuiteNoTPM) newRotateAuthValueLockoutAuthPolicy(c *C, alg tpm2.HashAlgorithmId, oldAuthValue []byte) *policyutil.Policy { - _, policy := s.lockoutauthSuiteMixin.newRotateAuthValueLockoutAuthPolicy(c, alg, oldAuthValue) +func (s *lockoutauthSuiteNoTPM) newUpdateAuthValueLockoutAuthPolicy(c *C, alg tpm2.HashAlgorithmId, oldAuthValue []byte) *policyutil.Policy { + _, policy := s.lockoutauthSuiteMixin.newUpdateAuthValueLockoutAuthPolicy(c, alg, oldAuthValue) return policy } @@ -166,7 +106,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":"AAAAAAAAAAEAC/cbf1/nUkzaClt4ysmVL5cqWE67D7Brmd7cgdwi7ztVAAAAAAAAAAEgAQFxAAAAAQAAAAAAAQALufUnMhfDMA5sLu0OUIPoKx2NNK4laaj7SfVnqdZFDjYAAAACIAEBcQAAAAUAAAAAAAEAC7bFwF5YGQnN6n33pfkcDy7tN/128VUi7uW1X4lvLVY/AAAAAQAAAWwAAAE5AAAAAAABAAscaCd8nWVk3YG8z35Wuj7cqziPxgzpWzpEK9JyWPYN/AAAAAEAAAFsAAABOgAAAAAAAQALlAz7Qhe7Htz3+0GTfKl0qmjmmKt4uBJLBwET4hH9RvwAAAABAAABbAAAAScAAAAAAAEAC8Tfq87ajeg2yVZhlSiSsd73IDr7Rv7+xD/8/JO+VAcwAAAAAQAAAWwAAAEmAAAAAAABAAu+aY2L0UknzX7Xvdk75B8n/yYmvS2KFUDi+URfLuWrLgAAAAEAAAFsAAABKQAAAWs="}`)) + c.Check(data, DeepEquals, []byte(`{"auth-value":"x9oO1va6Pz6nQeeGOgoXSBOLbsyw4IQTKwSpyXbw0LE=","auth-policy":"AAAAAAAAAAEAC8fpxFXFnW/i+VVUXTr6s3kopn5+LbHkhqxSYqdusGu/AAAAAAAAAAIgAQFxAAAABQAAAAAAAQALtsXAXlgZCc3qffel+RwPLu03/XbxVSLu5bVfiW8tVj8AAAABAAABbAAAATkAAAAAAAEACxxoJ3ydZWTdgbzPfla6PtyrOI/GDOlbOkQr0nJY9g38AAAAAQAAAWwAAAE6AAAAAAABAAuUDPtCF7se3Pf7QZN8qXSqaOaYq3i4EksHARPiEf1G/AAAAAEAAAFsAAABJwAAAAAAAQALxN+rztqN6DbJVmGVKJKx3vcgOvtG/v7EP/z8k75UBzAAAAABAAABbAAAASYAAAAAAAEAC3G+h1vfkVM3lejs6YjXVDuULEStbQE7L3xfQ4MLi6IXAAAAAQAAAWwAAAEuAAABaw=="}`)) } func (s *lockoutauthSuiteNoTPM) TestLockoutAuthParamsMarshalJSONNoPolicy(c *C) { @@ -180,21 +120,22 @@ func (s *lockoutauthSuiteNoTPM) TestLockoutAuthParamsMarshalJSONNoPolicy(c *C) { } func (s *lockoutauthSuiteNoTPM) TestLockoutAuthParamsMarshalJSONForChangeAuth(c *C) { - authValue := testutil.DecodeHexString(c, "c7da0ed6f6ba3f3ea741e7863a0a1748138b6eccb0e084132b04a9c976f0d0b1") + oldAuthValue := testutil.DecodeHexString(c, "c7da0ed6f6ba3f3ea741e7863a0a1748138b6eccb0e084132b04a9c976f0d0b1") params := &LockoutAuthParams{ - AuthValue: authValue, + AuthValue: oldAuthValue, AuthPolicy: s.newDefaultLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256), NewAuthValue: testutil.DecodeHexString(c, "db82cbebd10ebd831b48ff8ae7275a23029074ba622c0416d97cd34dd38d8186"), - NewAuthPolicy: s.newRotateAuthValueLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256, authValue), + NewAuthPolicy: s.newUpdateAuthValueLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256, oldAuthValue), } data, err := json.Marshal(params) c.Check(err, IsNil) - 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"}`)) + c.Check(data, DeepEquals, []byte(`{"auth-value":"x9oO1va6Pz6nQeeGOgoXSBOLbsyw4IQTKwSpyXbw0LE=","auth-policy":"AAAAAAAAAAEAC8fpxFXFnW/i+VVUXTr6s3kopn5+LbHkhqxSYqdusGu/AAAAAAAAAAIgAQFxAAAABQAAAAAAAQALtsXAXlgZCc3qffel+RwPLu03/XbxVSLu5bVfiW8tVj8AAAABAAABbAAAATkAAAAAAAEACxxoJ3ydZWTdgbzPfla6PtyrOI/GDOlbOkQr0nJY9g38AAAAAQAAAWwAAAE6AAAAAAABAAuUDPtCF7se3Pf7QZN8qXSqaOaYq3i4EksHARPiEf1G/AAAAAEAAAFsAAABJwAAAAAAAQALxN+rztqN6DbJVmGVKJKx3vcgOvtG/v7EP/z8k75UBzAAAAABAAABbAAAASYAAAAAAAEAC3G+h1vfkVM3lejs6YjXVDuULEStbQE7L3xfQ4MLi6IXAAAAAQAAAWwAAAEuAAABaw==","new-auth-value":"24LL69EOvYMbSP+K5ydaIwKQdLpiLAQW2XzTTdONgYY=","new-auth-policy":"AAAAAAAAAAEAC5DfGyoZIDr7uGD9ECZEKhrZck2HJ0rF/69uTv7L2r3uAAAAAAAAAAEgAQFxAAAAAgAAAAAAAQAL/g0OavvRqALD6F4sJD+kB1TWHYxCvdViNHPYjqSJqbIAAAACAAABbAAAAS4AAAFrAAAAAAABAAsKthPA+41TP+NED+noTBLANfaL5uN0SRiSp+yGd9FacwAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIJPBCN9d627hW/UNWTj/zIeY9y/tgFeFqnhJxu3ru9obACAsVNPpCVnGvmNHgQA0M677rio72lnxr4kSG0z1nF1IVQARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA="}`)) } func (s *lockoutauthSuiteNoTPM) TestLockoutAuthParamsUnmarshalJSON(c *C) { - data := []byte(`{"auth-value":"x9oO1va6Pz6nQeeGOgoXSBOLbsyw4IQTKwSpyXbw0LE=","auth-policy":"AAAAAAAAAAEAC/cbf1/nUkzaClt4ysmVL5cqWE67D7Brmd7cgdwi7ztVAAAAAAAAAAEgAQFxAAAAAQAAAAAAAQALufUnMhfDMA5sLu0OUIPoKx2NNK4laaj7SfVnqdZFDjYAAAACIAEBcQAAAAUAAAAAAAEAC7bFwF5YGQnN6n33pfkcDy7tN/128VUi7uW1X4lvLVY/AAAAAQAAAWwAAAE5AAAAAAABAAscaCd8nWVk3YG8z35Wuj7cqziPxgzpWzpEK9JyWPYN/AAAAAEAAAFsAAABOgAAAAAAAQALlAz7Qhe7Htz3+0GTfKl0qmjmmKt4uBJLBwET4hH9RvwAAAABAAABbAAAAScAAAAAAAEAC8Tfq87ajeg2yVZhlSiSsd73IDr7Rv7+xD/8/JO+VAcwAAAAAQAAAWwAAAEmAAAAAAABAAu+aY2L0UknzX7Xvdk75B8n/yYmvS2KFUDi+URfLuWrLgAAAAEAAAFsAAABKQAAAWs="}`) + data := []byte(`{"auth-value":"x9oO1va6Pz6nQeeGOgoXSBOLbsyw4IQTKwSpyXbw0LE=","auth-policy":"AAAAAAAAAAEAC8fpxFXFnW/i+VVUXTr6s3kopn5+LbHkhqxSYqdusGu/AAAAAAAAAAIgAQFxAAAABQAAAAAAAQALtsXAXlgZCc3qffel+RwPLu03/XbxVSLu5bVfiW8tVj8AAAABAAABbAAAATkAAAAAAAEACxxoJ3ydZWTdgbzPfla6PtyrOI/GDOlbOkQr0nJY9g38AAAAAQAAAWwAAAE6AAAAAAABAAuUDPtCF7se3Pf7QZN8qXSqaOaYq3i4EksHARPiEf1G/AAAAAEAAAFsAAABJwAAAAAAAQALxN+rztqN6DbJVmGVKJKx3vcgOvtG/v7EP/z8k75UBzAAAAABAAABbAAAASYAAAAAAAEAC3G+h1vfkVM3lejs6YjXVDuULEStbQE7L3xfQ4MLi6IXAAAAAQAAAWwAAAEuAAABaw=="}`) + expected := &LockoutAuthParams{ AuthValue: testutil.DecodeHexString(c, "c7da0ed6f6ba3f3ea741e7863a0a1748138b6eccb0e084132b04a9c976f0d0b1"), AuthPolicy: s.newDefaultLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256), @@ -206,14 +147,14 @@ func (s *lockoutauthSuiteNoTPM) TestLockoutAuthParamsUnmarshalJSON(c *C) { } func (s *lockoutauthSuiteNoTPM) TestLockoutAuthParamsUnmarshalJSONForChangeAuth(c *C) { - 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"}`) + data := []byte(`{"auth-value":"x9oO1va6Pz6nQeeGOgoXSBOLbsyw4IQTKwSpyXbw0LE=","auth-policy":"AAAAAAAAAAEAC8fpxFXFnW/i+VVUXTr6s3kopn5+LbHkhqxSYqdusGu/AAAAAAAAAAIgAQFxAAAABQAAAAAAAQALtsXAXlgZCc3qffel+RwPLu03/XbxVSLu5bVfiW8tVj8AAAABAAABbAAAATkAAAAAAAEACxxoJ3ydZWTdgbzPfla6PtyrOI/GDOlbOkQr0nJY9g38AAAAAQAAAWwAAAE6AAAAAAABAAuUDPtCF7se3Pf7QZN8qXSqaOaYq3i4EksHARPiEf1G/AAAAAEAAAFsAAABJwAAAAAAAQALxN+rztqN6DbJVmGVKJKx3vcgOvtG/v7EP/z8k75UBzAAAAABAAABbAAAASYAAAAAAAEAC3G+h1vfkVM3lejs6YjXVDuULEStbQE7L3xfQ4MLi6IXAAAAAQAAAWwAAAEuAAABaw==","new-auth-value":"24LL69EOvYMbSP+K5ydaIwKQdLpiLAQW2XzTTdONgYY=","new-auth-policy":"AAAAAAAAAAEAC5DfGyoZIDr7uGD9ECZEKhrZck2HJ0rF/69uTv7L2r3uAAAAAAAAAAEgAQFxAAAAAgAAAAAAAQAL/g0OavvRqALD6F4sJD+kB1TWHYxCvdViNHPYjqSJqbIAAAACAAABbAAAAS4AAAFrAAAAAAABAAsKthPA+41TP+NED+noTBLANfaL5uN0SRiSp+yGd9FacwAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIJPBCN9d627hW/UNWTj/zIeY9y/tgFeFqnhJxu3ru9obACAsVNPpCVnGvmNHgQA0M677rio72lnxr4kSG0z1nF1IVQARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA="}`) - authValue := testutil.DecodeHexString(c, "c7da0ed6f6ba3f3ea741e7863a0a1748138b6eccb0e084132b04a9c976f0d0b1") + oldAuthValue := testutil.DecodeHexString(c, "c7da0ed6f6ba3f3ea741e7863a0a1748138b6eccb0e084132b04a9c976f0d0b1") expected := &LockoutAuthParams{ - AuthValue: authValue, + AuthValue: oldAuthValue, AuthPolicy: s.newDefaultLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256), NewAuthValue: testutil.DecodeHexString(c, "db82cbebd10ebd831b48ff8ae7275a23029074ba622c0416d97cd34dd38d8186"), - NewAuthPolicy: s.newRotateAuthValueLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256, authValue), + NewAuthPolicy: s.newUpdateAuthValueLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256, oldAuthValue), } var params *LockoutAuthParams @@ -233,6 +174,328 @@ func (s *lockoutauthSuiteNoTPM) TestLockoutAuthParamsUnmarshalJSONInvalidAuthPol `) } +func (s *lockoutauthSuiteNoTPM) TestNewDefaultLockoutAuthPolicySHA256(c *C) { + digest, policy, err := NewDefaultLockoutAuthPolicy(tpm2.HashAlgorithmSHA256) + c.Assert(err, IsNil) + c.Check(digest, DeepEquals, tpm2.Digest(testutil.DecodeHexString(c, "c7e9c455c59d6fe2f955545d3afab37928a67e7e2db1e486ac5262a76eb06bbf"))) + c.Check(policy.String(), Equals, ` +Policy { + # digest TPM_ALG_SHA256:0xc7e9c455c59d6fe2f955545d3afab37928a67e7e2db1e486ac5262a76eb06bbf + BranchNode { + Branch 0 { + # digest TPM_ALG_SHA256:0xb6c5c05e581909cdea7df7a5f91c0f2eed37fd76f15522eee5b55f896f2d563f + PolicyCommandCode(TPM_CC_DictionaryAttackLockReset) + } + Branch 1 { + # digest TPM_ALG_SHA256:0x1c68277c9d6564dd81bccf7e56ba3edcab388fc60ce95b3a442bd27258f60dfc + PolicyCommandCode(TPM_CC_DictionaryAttackParameters) + } + Branch 2 { + # digest TPM_ALG_SHA256:0x940cfb4217bb1edcf7fb41937ca974aa68e698ab78b8124b070113e211fd46fc + PolicyCommandCode(TPM_CC_ClearControl) + } + Branch 3 { + # digest TPM_ALG_SHA256:0xc4dfabceda8de836c95661952892b1def7203afb46fefec43ffcfc93be540730 + PolicyCommandCode(TPM_CC_Clear) + } + Branch 4 { + # digest TPM_ALG_SHA256:0x71be875bdf91533795e8ece988d7543b942c44ad6d013b2f7c5f43830b8ba217 + PolicyCommandCode(TPM_CC_SetPrimaryPolicy) + } + } + PolicyOR( + 0xb6c5c05e581909cdea7df7a5f91c0f2eed37fd76f15522eee5b55f896f2d563f + 0x1c68277c9d6564dd81bccf7e56ba3edcab388fc60ce95b3a442bd27258f60dfc + 0x940cfb4217bb1edcf7fb41937ca974aa68e698ab78b8124b070113e211fd46fc + 0xc4dfabceda8de836c95661952892b1def7203afb46fefec43ffcfc93be540730 + 0x71be875bdf91533795e8ece988d7543b942c44ad6d013b2f7c5f43830b8ba217 + ) + PolicyAuthValue() +}`) +} + +func (s *lockoutauthSuiteNoTPM) TestNewDefaultLockoutAuthPolicySHA384(c *C) { + digest, policy, err := NewDefaultLockoutAuthPolicy(tpm2.HashAlgorithmSHA384) + c.Assert(err, IsNil) + c.Check(digest, DeepEquals, tpm2.Digest(testutil.DecodeHexString(c, "7a99094f13bb180eeff7a54e0b86c394a8dd289e058d9d4583a98612959aefe67e2d0d6728c7645b3e6cc76da99dbce0"))) + c.Check(policy.String(), Equals, ` +Policy { + # digest TPM_ALG_SHA384:0x7a99094f13bb180eeff7a54e0b86c394a8dd289e058d9d4583a98612959aefe67e2d0d6728c7645b3e6cc76da99dbce0 + BranchNode { + Branch 0 { + # digest TPM_ALG_SHA384:0x3ba274a8092cf382fbf5cec7070e8f89043f3399fd9d5851693d0a87b0e40c35e6ac461c959ec090e35c071e2499cf90 + PolicyCommandCode(TPM_CC_DictionaryAttackLockReset) + } + Branch 1 { + # digest TPM_ALG_SHA384:0x2f61a497478e81f1d3f58d641979241378d523dd2a5d8eb006d4f662092c8b71e0793750c03e56907e1044306c9d8002 + PolicyCommandCode(TPM_CC_DictionaryAttackParameters) + } + Branch 2 { + # digest TPM_ALG_SHA384:0x2eb708fa8e860ef25c7e960c2b6814c4316eabe6d43613ee10231c7fa0467dca9c4e7abad35448c6be7a460a61ffca6f + PolicyCommandCode(TPM_CC_ClearControl) + } + Branch 3 { + # digest TPM_ALG_SHA384:0x55a038b340fb2776e89042f8e1a63f475d6ed129ce1a5da35c2d6c9225c12ba2e259242a771907bad2caa16e66e00bb0 + PolicyCommandCode(TPM_CC_Clear) + } + Branch 4 { + # digest TPM_ALG_SHA384:0x95a762e430271f6b2ce625d7855536ae395b0cd99891219be02ed906a0fe783c4ac9012bb85488e96639fcb6f8a6964d + PolicyCommandCode(TPM_CC_SetPrimaryPolicy) + } + } + PolicyOR( + 0x3ba274a8092cf382fbf5cec7070e8f89043f3399fd9d5851693d0a87b0e40c35e6ac461c959ec090e35c071e2499cf90 + 0x2f61a497478e81f1d3f58d641979241378d523dd2a5d8eb006d4f662092c8b71e0793750c03e56907e1044306c9d8002 + 0x2eb708fa8e860ef25c7e960c2b6814c4316eabe6d43613ee10231c7fa0467dca9c4e7abad35448c6be7a460a61ffca6f + 0x55a038b340fb2776e89042f8e1a63f475d6ed129ce1a5da35c2d6c9225c12ba2e259242a771907bad2caa16e66e00bb0 + 0x95a762e430271f6b2ce625d7855536ae395b0cd99891219be02ed906a0fe783c4ac9012bb85488e96639fcb6f8a6964d + ) + PolicyAuthValue() +}`) +} + +func (s *lockoutauthSuiteNoTPM) TestNewUpdateLockoutAuthValueKey1(c *C) { + expectedKey := testutil.ParsePKCS8PrivateKey(c, testutil.DecodePEMType(c, "PRIVATE KEY", []byte( + `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg1oJePIX4hsogCSC7 +j4ksaKoo2TcbpsOzFi8Lw/rGm/yhRANCAATbaksKtS3jGTcjak+iFxEQVIPuXWKS +D/DzsdvswsWgTu81WK33zdFS52uyeOmUjLYpcxLWVHGop7+jR3vTlFMj +-----END PRIVATE KEY----- +`))) + c.Assert(expectedKey, testutil.ConvertibleTo, &ecdsa.PrivateKey{}) + + expectedPubKey := &tpm2.Public{ + Type: tpm2.ObjectTypeECC, + NameAlg: tpm2.HashAlgorithmSHA256, + Attrs: tpm2.AttrSign, + Params: &tpm2.PublicParamsU{ + ECCDetail: &tpm2.ECCParams{ + Symmetric: tpm2.SymDefObject{Algorithm: tpm2.SymObjectAlgorithmNull}, + Scheme: tpm2.ECCScheme{Scheme: tpm2.ECCSchemeNull}, + KDF: tpm2.KDFScheme{Scheme: tpm2.KDFAlgorithmNull}, + CurveID: tpm2.ECCCurveNIST_P256, + }, + }, + Unique: &tpm2.PublicIDU{ + ECC: &tpm2.ECCPoint{ + X: expectedKey.(*ecdsa.PrivateKey).X.Bytes(), + Y: expectedKey.(*ecdsa.PrivateKey).Y.Bytes(), + }, + }, + } + + key, pubKey, err := NewUpdateLockoutAuthValueKey(testutil.DecodeHexString(c, "c04c673608034f3f6fdd1b2ba752daf8ae5fa9ca5d7fc21b5f5f1dbdd9427cea")) + c.Assert(err, IsNil) + c.Check(key, DeepEquals, expectedKey) + c.Check(pubKey, DeepEquals, expectedPubKey) +} + +func (s *lockoutauthSuiteNoTPM) TestNewUpdateLockoutAuthValueKey2(c *C) { + expectedKey := testutil.ParsePKCS8PrivateKey(c, testutil.DecodePEMType(c, "PRIVATE KEY", []byte( + `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWJgFCvqXoNkxelii +gSQkkQFhoB4c6wspl+bG3WZagKWhRANCAARcyMkoEYURupGLO7gkXO8VDkJUhxoh +ckDarAbDcRnWc+Smt2x6Jct+Ft/81OlYZKZTkIQlM4CMWnr3a5zGPp1Z +-----END PRIVATE KEY----- +`))) + c.Assert(expectedKey, testutil.ConvertibleTo, &ecdsa.PrivateKey{}) + + expectedPubKey := &tpm2.Public{ + Type: tpm2.ObjectTypeECC, + NameAlg: tpm2.HashAlgorithmSHA256, + Attrs: tpm2.AttrSign, + Params: &tpm2.PublicParamsU{ + ECCDetail: &tpm2.ECCParams{ + Symmetric: tpm2.SymDefObject{Algorithm: tpm2.SymObjectAlgorithmNull}, + Scheme: tpm2.ECCScheme{Scheme: tpm2.ECCSchemeNull}, + KDF: tpm2.KDFScheme{Scheme: tpm2.KDFAlgorithmNull}, + CurveID: tpm2.ECCCurveNIST_P256, + }, + }, + Unique: &tpm2.PublicIDU{ + ECC: &tpm2.ECCPoint{ + X: expectedKey.(*ecdsa.PrivateKey).X.Bytes(), + Y: expectedKey.(*ecdsa.PrivateKey).Y.Bytes(), + }, + }, + } + key, pubKey, err := NewUpdateLockoutAuthValueKey(testutil.DecodeHexString(c, "f10fa81ad01d6912916951039ed6a06c33f6995a5b6cd307f246d2dd6551edce")) + c.Assert(err, IsNil) + c.Check(key, DeepEquals, expectedKey) + c.Check(pubKey, DeepEquals, expectedPubKey) +} + +func (s *lockoutauthSuiteNoTPM) TestNewUpdateAuthValueLockoutAuthPolicySHA256_1(c *C) { + expectedKey := testutil.ParsePKCS8PrivateKey(c, testutil.DecodePEMType(c, "PRIVATE KEY", []byte( + `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg1oJePIX4hsogCSC7 +j4ksaKoo2TcbpsOzFi8Lw/rGm/yhRANCAATbaksKtS3jGTcjak+iFxEQVIPuXWKS +D/DzsdvswsWgTu81WK33zdFS52uyeOmUjLYpcxLWVHGop7+jR3vTlFMj +-----END PRIVATE KEY----- +`))) + c.Assert(expectedKey, testutil.ConvertibleTo, &ecdsa.PrivateKey{}) + + expectedPubKey := &tpm2.Public{ + Type: tpm2.ObjectTypeECC, + NameAlg: tpm2.HashAlgorithmSHA256, + Attrs: tpm2.AttrSign, + Params: &tpm2.PublicParamsU{ + ECCDetail: &tpm2.ECCParams{ + Symmetric: tpm2.SymDefObject{Algorithm: tpm2.SymObjectAlgorithmNull}, + Scheme: tpm2.ECCScheme{Scheme: tpm2.ECCSchemeNull}, + KDF: tpm2.KDFScheme{Scheme: tpm2.KDFAlgorithmNull}, + CurveID: tpm2.ECCCurveNIST_P256, + }, + }, + Unique: &tpm2.PublicIDU{ + ECC: &tpm2.ECCPoint{ + X: expectedKey.(*ecdsa.PrivateKey).X.Bytes(), + Y: expectedKey.(*ecdsa.PrivateKey).Y.Bytes(), + }, + }, + } + + digest, policy, key, pubKey, err := NewUpdateAuthValueLockoutAuthPolicy(tpm2.HashAlgorithmSHA256, testutil.DecodeHexString(c, "c04c673608034f3f6fdd1b2ba752daf8ae5fa9ca5d7fc21b5f5f1dbdd9427cea")) + c.Assert(err, IsNil) + c.Check(digest, DeepEquals, tpm2.Digest(testutil.DecodeHexString(c, "414b075b442067d58879f8bce5fcc76d2eff43d6fba5c5f1fe7fd56509f2abea"))) + c.Check(policy.String(), Equals, ` +Policy { + # digest TPM_ALG_SHA256:0x414b075b442067d58879f8bce5fcc76d2eff43d6fba5c5f1fe7fd56509f2abea + BranchNode { + Branch 0 { + # digest TPM_ALG_SHA256:0xfe0d0e6afbd1a802c3e85e2c243fa40754d61d8c42bdd5623473d88ea489a9b2 + PolicyCommandCode(TPM_CC_SetPrimaryPolicy) + PolicyAuthValue() + } + Branch 1 { + # digest TPM_ALG_SHA256:0xb1b14d96221d908f547563b6895afc7e2d0127e5dae57b3d4a77492199ceddbb + PolicyCommandCode(TPM_CC_HierarchyChangeAuth) + PolicySigned(authKey:0x000b4ec2a02411b7ee0f601465fe178d7dc02b3b9b8821b873a4486f9edc48bdcc41, policyRef:0x5550444154452d415554482d56414c5545) + } + } + PolicyOR( + 0xfe0d0e6afbd1a802c3e85e2c243fa40754d61d8c42bdd5623473d88ea489a9b2 + 0xb1b14d96221d908f547563b6895afc7e2d0127e5dae57b3d4a77492199ceddbb + ) +}`) + c.Check(key, DeepEquals, expectedKey) + c.Check(pubKey, DeepEquals, expectedPubKey) +} + +func (s *lockoutauthSuiteNoTPM) TestNewUpdateAuthValueLockoutAuthPolicySHA256_2(c *C) { + expectedKey := testutil.ParsePKCS8PrivateKey(c, testutil.DecodePEMType(c, "PRIVATE KEY", []byte( + `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWJgFCvqXoNkxelii +gSQkkQFhoB4c6wspl+bG3WZagKWhRANCAARcyMkoEYURupGLO7gkXO8VDkJUhxoh +ckDarAbDcRnWc+Smt2x6Jct+Ft/81OlYZKZTkIQlM4CMWnr3a5zGPp1Z +-----END PRIVATE KEY----- +`))) + c.Assert(expectedKey, testutil.ConvertibleTo, &ecdsa.PrivateKey{}) + + expectedPubKey := &tpm2.Public{ + Type: tpm2.ObjectTypeECC, + NameAlg: tpm2.HashAlgorithmSHA256, + Attrs: tpm2.AttrSign, + Params: &tpm2.PublicParamsU{ + ECCDetail: &tpm2.ECCParams{ + Symmetric: tpm2.SymDefObject{Algorithm: tpm2.SymObjectAlgorithmNull}, + Scheme: tpm2.ECCScheme{Scheme: tpm2.ECCSchemeNull}, + KDF: tpm2.KDFScheme{Scheme: tpm2.KDFAlgorithmNull}, + CurveID: tpm2.ECCCurveNIST_P256, + }, + }, + Unique: &tpm2.PublicIDU{ + ECC: &tpm2.ECCPoint{ + X: expectedKey.(*ecdsa.PrivateKey).X.Bytes(), + Y: expectedKey.(*ecdsa.PrivateKey).Y.Bytes(), + }, + }, + } + + digest, policy, key, pubKey, err := NewUpdateAuthValueLockoutAuthPolicy(tpm2.HashAlgorithmSHA256, testutil.DecodeHexString(c, "f10fa81ad01d6912916951039ed6a06c33f6995a5b6cd307f246d2dd6551edce")) + c.Assert(err, IsNil) + c.Check(digest, DeepEquals, tpm2.Digest(testutil.DecodeHexString(c, "f337a695eaa950db2c3a7cc0d81a32b0b3bc371ca41f2f6a674e9b30cc2a640f"))) + c.Check(policy.String(), Equals, ` +Policy { + # digest TPM_ALG_SHA256:0xf337a695eaa950db2c3a7cc0d81a32b0b3bc371ca41f2f6a674e9b30cc2a640f + BranchNode { + Branch 0 { + # digest TPM_ALG_SHA256:0xfe0d0e6afbd1a802c3e85e2c243fa40754d61d8c42bdd5623473d88ea489a9b2 + PolicyCommandCode(TPM_CC_SetPrimaryPolicy) + PolicyAuthValue() + } + Branch 1 { + # digest TPM_ALG_SHA256:0x4ab1bdea476a5b2225559debabede03de1cf73e48a5b074100ed1f7abceeb3e5 + PolicyCommandCode(TPM_CC_HierarchyChangeAuth) + PolicySigned(authKey:0x000bfaf0669b1825822c25d65ac160d9a196071cd4dcab0771ea2c2a26f7857cdc85, policyRef:0x5550444154452d415554482d56414c5545) + } + } + PolicyOR( + 0xfe0d0e6afbd1a802c3e85e2c243fa40754d61d8c42bdd5623473d88ea489a9b2 + 0x4ab1bdea476a5b2225559debabede03de1cf73e48a5b074100ed1f7abceeb3e5 + ) +}`) + c.Check(key, DeepEquals, expectedKey) + c.Check(pubKey, DeepEquals, expectedPubKey) +} + +func (s *lockoutauthSuiteNoTPM) TestNewUpdateAuthValueLockoutAuthPolicySHA384(c *C) { + expectedKey := testutil.ParsePKCS8PrivateKey(c, testutil.DecodePEMType(c, "PRIVATE KEY", []byte( + `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgAmD9n1YetaEB1uJN +NT1s5GMSSfWcYwso8RgVg+AU+4ihRANCAATy+Fer194BjW3IyIBFYg3wrpgPiTjn +GpiUnHWuinAp5fLWFgVmEbcaNRSzyqGRkq+NtgDCeDNsXUwmBj0/XVKR +-----END PRIVATE KEY----- +`))) + c.Assert(expectedKey, testutil.ConvertibleTo, &ecdsa.PrivateKey{}) + + expectedPubKey := &tpm2.Public{ + Type: tpm2.ObjectTypeECC, + NameAlg: tpm2.HashAlgorithmSHA256, + Attrs: tpm2.AttrSign, + Params: &tpm2.PublicParamsU{ + ECCDetail: &tpm2.ECCParams{ + Symmetric: tpm2.SymDefObject{Algorithm: tpm2.SymObjectAlgorithmNull}, + Scheme: tpm2.ECCScheme{Scheme: tpm2.ECCSchemeNull}, + KDF: tpm2.KDFScheme{Scheme: tpm2.KDFAlgorithmNull}, + CurveID: tpm2.ECCCurveNIST_P256, + }, + }, + Unique: &tpm2.PublicIDU{ + ECC: &tpm2.ECCPoint{ + X: expectedKey.(*ecdsa.PrivateKey).X.Bytes(), + Y: expectedKey.(*ecdsa.PrivateKey).Y.Bytes(), + }, + }, + } + + digest, policy, key, pubKey, err := NewUpdateAuthValueLockoutAuthPolicy(tpm2.HashAlgorithmSHA384, testutil.DecodeHexString(c, "c04c673608034f3f6fdd1b2ba752daf8ae5fa9ca5d7fc21b5f5f1dbdd9427ceaa6f35c0d0f98c2926a0b029296f06cc5")) + c.Assert(err, IsNil) + c.Check(digest, DeepEquals, tpm2.Digest(testutil.DecodeHexString(c, "a879336fa8f97b75ec5c599c442e40cbb5780af95cd9540627d0d013be68a6a7b57fd878f5a88a41dae7972ccb49b47a"))) + c.Check(policy.String(), Equals, ` +Policy { + # digest TPM_ALG_SHA384:0xa879336fa8f97b75ec5c599c442e40cbb5780af95cd9540627d0d013be68a6a7b57fd878f5a88a41dae7972ccb49b47a + BranchNode { + Branch 0 { + # digest TPM_ALG_SHA384:0xf0a1e926f1405be83accb5e93fa8d6df69a72c61df0082013224da28d78902bbb58e7c5bfdadf05cac8cfd115043aae8 + PolicyCommandCode(TPM_CC_SetPrimaryPolicy) + PolicyAuthValue() + } + Branch 1 { + # digest TPM_ALG_SHA384:0x17b334cb9352973354c6a7b9065d9b49ed2013522d1e056d6f9d2b1650d0b067b4064799632c4acd5085aa66c97a6396 + PolicyCommandCode(TPM_CC_HierarchyChangeAuth) + PolicySigned(authKey:0x000bd49793418a9917c56ed403a8791f8254d153b1ca1b6ea698595d4bb76f513556, policyRef:0x5550444154452d415554482d56414c5545) + } + } + PolicyOR( + 0xf0a1e926f1405be83accb5e93fa8d6df69a72c61df0082013224da28d78902bbb58e7c5bfdadf05cac8cfd115043aae8 + 0x17b334cb9352973354c6a7b9065d9b49ed2013522d1e056d6f9d2b1650d0b067b4064799632c4acd5085aa66c97a6396 + ) +}`) + c.Check(key, DeepEquals, expectedKey) + c.Check(pubKey, DeepEquals, expectedPubKey) +} + type testResetDictionaryAttackLockParams struct { authValue tpm2.Auth policyDigest tpm2.Digest @@ -267,7 +530,7 @@ func (s *lockoutauthSuite) testResetDictionaryAttackLock(c *C, params *testReset } resetErr := s.TPM().ResetDictionaryAttackLock(params.data) - if resetErr != nil && !errors.Is(resetErr, ErrEmptyLockoutAuthValue) { + if resetErr != nil && !errors.Is(resetErr, ErrLockoutAuthNotInitialized) { return resetErr } @@ -317,8 +580,8 @@ func (s *lockoutauthSuite) TestResetDictionaryAttackLockAuthValueUnset(c *C) { AuthPolicy: policy, }), }) - c.Check(err, ErrorMatches, `the authorization value for the lockout hierarchy is empty`) - c.Check(err, Equals, ErrEmptyLockoutAuthValue) + c.Check(err, ErrorMatches, `the authorization parameters for the lockout hierarchy are not fully initialized`) + c.Check(err, Equals, ErrLockoutAuthNotInitialized) cmds := s.CommandLog() c.Assert(len(cmds) > 2, testutil.IsTrue) @@ -330,6 +593,48 @@ func (s *lockoutauthSuite) TestResetDictionaryAttackLockAuthValueUnset(c *C) { c.Check(s.TPM().DoesHandleExist(cmd.CmdAuthArea[0].SessionHandle), testutil.IsFalse) } +func (s *lockoutauthSuite) TestResetDictionaryAttackLockAuthPolicyUnset(c *C) { + authValue := testutil.DecodeHexString(c, "c7da0ed6f6ba3f3ea741e7863a0a1748138b6eccb0e084132b04a9c976f0d0b1") + _, policy := s.newDefaultLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256) + + err := s.testResetDictionaryAttackLock(c, &testResetDictionaryAttackLockParams{ + policyAlg: tpm2.HashAlgorithmNull, + data: s.makeLockoutAuthData(c, &LockoutAuthParams{ + AuthValue: authValue, + AuthPolicy: policy, + }), + }) + c.Check(err, ErrorMatches, `the authorization parameters for the lockout hierarchy are not fully initialized`) + c.Check(err, Equals, ErrLockoutAuthNotInitialized) + + cmds := s.CommandLog() + c.Assert(len(cmds) > 1, testutil.IsTrue) + cmd := cmds[len(cmds)-2] + c.Check(cmd.CmdCode, Equals, tpm2.CommandDictionaryAttackLockReset) + c.Assert(cmd.CmdAuthArea, HasLen, 1) + c.Check(cmd.CmdAuthArea[0].SessionHandle.Type(), Equals, tpm2.HandleTypeHMACSession) +} + +func (s *lockoutauthSuite) TestResetDictionaryAttackLockNoAuthPolicySupport(c *C) { + authValue := testutil.DecodeHexString(c, "c7da0ed6f6ba3f3ea741e7863a0a1748138b6eccb0e084132b04a9c976f0d0b1") + + err := s.testResetDictionaryAttackLock(c, &testResetDictionaryAttackLockParams{ + authValue: authValue, + policyAlg: tpm2.HashAlgorithmNull, + data: s.makeLockoutAuthData(c, &LockoutAuthParams{ + AuthValue: authValue, + }), + }) + c.Check(err, IsNil) + + cmds := s.CommandLog() + c.Assert(len(cmds) > 1, testutil.IsTrue) + cmd := cmds[len(cmds)-2] + c.Check(cmd.CmdCode, Equals, tpm2.CommandDictionaryAttackLockReset) + c.Assert(cmd.CmdAuthArea, HasLen, 1) + c.Check(cmd.CmdAuthArea[0].SessionHandle.Type(), Equals, tpm2.HandleTypeHMACSession) +} + func (s *lockoutauthSuite) TestResetDictionaryAttackLockWithAuthValue(c *C) { authValue := testutil.DecodeHexString(c, "c7da0ed6f6ba3f3ea741e7863a0a1748138b6eccb0e084132b04a9c976f0d0b1") @@ -361,9 +666,6 @@ 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) @@ -387,9 +689,9 @@ func (s *lockoutauthSuite) TestResetDictionaryAttackLockInvalidData(c *C) { c.Check(err, testutil.ConvertibleTo, &InvalidLockoutAuthDataError{}) } -func (s *lockoutauthSuite) TestResetDictionaryAttackLockUnsupportedAuthValueRotation(c *C) { +func (s *lockoutauthSuite) TestResetDictionaryAttackLockInterruptedAuthValueRotation1(c *C) { authValue := testutil.DecodeHexString(c, "c7da0ed6f6ba3f3ea741e7863a0a1748138b6eccb0e084132b04a9c976f0d0b1") - digest, policy := s.newDefaultLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256) + digest, policy := s.newUpdateAuthValueLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256, authValue) err := s.testResetDictionaryAttackLock(c, &testResetDictionaryAttackLockParams{ authValue: authValue, @@ -401,7 +703,27 @@ func (s *lockoutauthSuite) TestResetDictionaryAttackLockUnsupportedAuthValueRota NewAuthValue: testutil.DecodeHexString(c, "db82cbebd10ebd831b48ff8ae7275a23029074ba622c0416d97cd34dd38d8186"), }), }) - c.Check(err, ErrorMatches, `lockout hierarchy auth value change not supported yet`) + c.Check(err, ErrorMatches, `a previous attempt to update the authorization parameters for the lockout hierarchy was interrupted`) + c.Check(err, Equals, ErrLockoutAuthUpdateInterrupted) +} + +func (s *lockoutauthSuite) TestResetDictionaryAttackLockInterruptedAuthValueRotation2(c *C) { + authValue := testutil.DecodeHexString(c, "db82cbebd10ebd831b48ff8ae7275a23029074ba622c0416d97cd34dd38d8186") + digest, policy1 := s.newUpdateAuthValueLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256, testutil.DecodeHexString(c, "c7da0ed6f6ba3f3ea741e7863a0a1748138b6eccb0e084132b04a9c976f0d0b1")) + _, policy2 := s.newDefaultLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256) + + err := s.testResetDictionaryAttackLock(c, &testResetDictionaryAttackLockParams{ + authValue: authValue, + policyDigest: digest, + policyAlg: tpm2.HashAlgorithmSHA256, + data: s.makeLockoutAuthData(c, &LockoutAuthParams{ + AuthValue: authValue, + AuthPolicy: policy1, + NewAuthPolicy: policy2, + }), + }) + c.Check(err, ErrorMatches, `a previous attempt to update the authorization parameters for the lockout hierarchy was interrupted`) + c.Check(err, Equals, ErrLockoutAuthUpdateInterrupted) } func (s *lockoutauthSuite) TestResetDictionaryAttackLockAuthFail(c *C) { @@ -458,6 +780,6 @@ func (s *lockoutauthSuite) TestResetDictionaryAttackLockInvalidPolicy(c *C) { AuthPolicy: policy, }), }) - c.Check(err, ErrorMatches, `the authorization policy for the lockout hierarchy is invalid`) + c.Check(err, ErrorMatches, `the authorization policy for the lockout hierarchy is inconsistent with the supplied data`) c.Check(err, Equals, ErrInvalidLockoutAuthPolicy) } diff --git a/tpm2/provisioning.go b/tpm2/provisioning.go index 33f45e92..06252cc9 100644 --- a/tpm2/provisioning.go +++ b/tpm2/provisioning.go @@ -23,6 +23,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "os" "github.com/canonical/go-tpm2" @@ -199,18 +200,20 @@ type ensureProvisionedParams struct { lockoutAuthParams *lockoutAuthParams lockoutAuthParamsErr error - mode provisionMode - newLockoutAuthValue []byte - srkTemplate *tpm2.Public - useExistingSrkTemplate bool + mode provisionMode + newLockoutAuthValue bool + newLockoutAuthValueReader io.Reader + syncLockoutAuthData func([]byte) error + srkTemplate *tpm2.Public + useExistingSrkTemplate bool } type EnsureProvisionedOption func(*ensureProvisionedParams) // 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. +// version of [Connection.EnsureProvisioned] 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. @@ -227,7 +230,7 @@ func WithLockoutAuthValue(authValue []byte) EnsureProvisionedOption { // 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). +// to [Connection.EnsureProvisioned] with the [WithProvisionNewLockoutAuthValue] option. // // If the data contains the wrong authorization value, the lockout hierarchy will become unavailable for // the pre-programmed recovery time. @@ -248,20 +251,22 @@ func WithLockoutAuthData(data []byte) EnsureProvisionedOption { // normally be used when resetting a device to factory settings (ie, performing a new installation). func WithClearBeforeProvision() EnsureProvisionedOption { return func(p *ensureProvisionedParams) { - if p.mode == provisionModeWithoutLockout { - panic("WithClearBeforeProvision conflicts with ProvisionWithoutLockout") - } p.mode = provisionModeClear } } -// WithProvisionNewLockoutAuthValue supplies the value to set the TPM's lockout hierarchy authorization -// value to. If this option is not supplied and [ProvisionWithoutLockout] is not supplied, then -// [Connection.EnsureProvisioned] will set it to an empty value. If [ProvisionWithoutLockout] is supplied, -// then this option has no effect. -func WithProvisionNewLockoutAuthValue(authValue []byte) EnsureProvisionedOption { +// WithProvisionNewLockoutAuthValue tells [Connection.EnsureProvisioned] to set or update the authorization +// value and authorization policy for the lockout hierarchy. The caller supplies a callback which is used +// to store the authorization data to persistent storage. This will be called multiple times during the +// update. +// +// This option will also resume a previously interrupted update, as long as the most recent authorization +// data is supplied to [WithLockoutAuthData]. +func WithProvisionNewLockoutAuthValue(rand io.Reader, syncData func([]byte) error) EnsureProvisionedOption { return func(p *ensureProvisionedParams) { - p.newLockoutAuthValue = authValue + p.newLockoutAuthValue = true + p.newLockoutAuthValueReader = rand + p.syncLockoutAuthData = syncData } } @@ -296,7 +301,7 @@ func WithCustomSRKTemplate(template *tpm2.Public) EnsureProvisionedOption { // // 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. +// lockout hierarchy will be set or updated if the [WithProvisionNewLockoutAuthValue] option is supplied. // // 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 @@ -321,13 +326,18 @@ func (t *Connection) EnsureProvisioned(options ...EnsureProvisionedOption) error 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.newLockoutAuthValue: + return errors.New("WithProvisionNewLockoutAuthValue 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 { + session, done, err := t.authorizeLockout(params.lockoutAuthParams, false, command, nil) + switch { + case errors.Is(err, errLockoutAuthPolicyNotSupported): + return &InvalidLockoutAuthDataError{err: err} + case err != nil: return err } defer done() @@ -337,8 +347,6 @@ func (t *Connection) EnsureProvisioned(options ...EnsureProvisionedOption) error 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) } @@ -438,6 +446,20 @@ func (t *Connection) EnsureProvisioned(options ...EnsureProvisionedOption) error // Perform actions that require the lockout hierarchy authorization. + // Set the new lockout hierarchy authorization value first, if required. This ensures that the + // authorization parameters are properly initialized for subsequent commands. + if params.newLockoutAuthValue { + if err := t.updateLockoutAuthValue(params.newLockoutAuthValueReader, params.lockoutAuthParams, func() error { + data, err := json.Marshal(params.lockoutAuthParams) + if err != nil { + return err + } + return params.syncLockoutAuthData(data) + }); err != nil { + return fmt.Errorf("cannot set new lockout hierarchy authorization value: %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) @@ -460,27 +482,6 @@ func (t *Connection) EnsureProvisioned(options ...EnsureProvisionedOption) error return err } - // XXX: Clear any policy for the lockout hierarchy first. A future PR will initialize this to something - // sensible. - if err := authorizeAndUseLockoutHierarchy(tpm2.CommandSetPrimaryPolicy, func(session tpm2.SessionContext) error { - return t.SetPrimaryPolicy(t.LockoutHandleContext(), nil, tpm2.HashAlgorithmNull, session) - }, "cannot clear the lockout hierarchy authorization policy"); err != nil { - return 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 ee6207b9..96607ef8 100644 --- a/tpm2/provisioning_test.go +++ b/tpm2/provisioning_test.go @@ -20,7 +20,9 @@ package tpm2_test import ( + "bytes" "crypto/rand" + "errors" "github.com/canonical/go-tpm2" "github.com/canonical/go-tpm2/mu" @@ -95,22 +97,48 @@ func (s *provisioningSimulatorSuite) SetUpTest(c *C) { var _ = Suite(&provisioningSuite{}) var _ = Suite(&provisioningSimulatorSuite{}) +func lockoutAuthValue(c *C, tpm *Connection, data []byte) []byte { + val, err := tpm.GetCapabilityTPMProperty(tpm2.PropertyContextHash) + c.Assert(err, IsNil) + contextHash := tpm2.HashAlgorithmId(val) + c.Assert(contextHash.IsValid(), testutil.IsTrue) + return data[:contextHash.Size()] +} + type testProvisionNewTPMData struct { - mode ProvisionMode - lockoutAuth []byte + clear bool + lockoutAuthBytes []byte + expectedLockoutAuthData [][]byte } func (s *provisioningSimulatorSuite) testProvisionNewTPM(c *C, data *testProvisionNewTPMData) { origHmacSession := s.TPM().HmacSession() - c.Check(s.TPM().EnsureProvisioned(data.mode.Option(data.lockoutAuth), WithProvisionNewLockoutAuthValue(data.lockoutAuth)), IsNil) + expectedLockoutAuthData := data.expectedLockoutAuthData + syncLockoutAuthData := func(data []byte) error { + c.Logf("%s", string(data)) + c.Assert(expectedLockoutAuthData, Not(HasLen), 0) + expected := expectedLockoutAuthData[0] + expectedLockoutAuthData = expectedLockoutAuthData[1:] + c.Check(data, DeepEquals, expected) + return nil + } + + opts := []EnsureProvisionedOption{WithLockoutAuthValue(nil), WithProvisionNewLockoutAuthValue(bytes.NewReader(data.lockoutAuthBytes), syncLockoutAuthData)} + if data.clear { + opts = append(opts, WithClearBeforeProvision()) + } + c.Check(s.TPM().EnsureProvisioned(opts...), 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(lockoutAuthValue(c, s.TPM(), data.lockoutAuthBytes)) c.Check(s.TPM().HierarchyChangeAuth(s.TPM().LockoutHandleContext(), nil, nil), IsNil) }) + c.Check(expectedLockoutAuthData, HasLen, 0) + s.validateEK(c) s.validateSRK(c) @@ -136,7 +164,7 @@ func (s *provisioningSimulatorSuite) testProvisionNewTPM(c *C, data *testProvisi c.Check(tpm2.PermanentAttributes(value)&tpm2.AttrInLockout, Equals, tpm2.PermanentAttributes(0)) // Test the lockout hierarchy auth - s.TPM().LockoutHandleContext().SetAuthValue(data.lockoutAuth) + s.TPM().LockoutHandleContext().SetAuthValue(lockoutAuthValue(c, s.TPM(), data.lockoutAuthBytes)) c.Check(s.TPM().DictionaryAttackLockReset(s.TPM().LockoutHandleContext(), nil), IsNil) c.Check(s.TPM().HmacSession(), NotNil) @@ -155,36 +183,284 @@ func (s *provisioningSimulatorSuite) testProvisionNewTPM(c *C, data *testProvisi func (s *provisioningSimulatorSuite) TestProvisionNewTPMClear(c *C) { s.testProvisionNewTPM(c, &testProvisionNewTPMData{ - mode: ProvisionModeClear, - lockoutAuth: []byte("1234")}) + clear: true, + lockoutAuthBytes: testutil.DecodeHexString(c, "c04c673608034f3f6fdd1b2ba752daf8ae5fa9ca5d7fc21b5f5f1dbdd9427ceaa6f35c0d0f98c2926a0b029296f06cc5a5a368364e3d07c6d6169c9443a70c3c"), + expectedLockoutAuthData: [][]byte{ + []byte(`{"auth-value":null,"new-auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF","new-auth-policy":"AAAAAAAAAAEADPSFreqYTJyYmYLZuV9t3FD6miDHK9Bk6csiDmxMYzssvhbvXp4XFg1FTZVRuPKb1AAAAAAAAAABIAEBcQAAAAIAAAAAAAEADPCh6SbxQFvoOsy16T+o1t9ppyxh3wCCATIk2ijXiQK7tY58W/2t8FysjP0RUEOq6AAAAAIAAAFsAAABLgAAAWsAAAAAAAEADPM/YpABRQGCbrCHesmtd7NQohItlVrJ+xFdG13xqo3ZFwpeCldZirZUOfTzZmQXPwAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIOsLyU/JRbgdKwtENNG1brDVsXEXRbQfOGc6oFCNFRuNACAIxXx8JXqfNxSy3h59UX4Jmd9nFeX85yMUGtGxB54+SwARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA="}`), + []byte(`{"auth-value":null,"auth-policy":"AAAAAAAAAAEADPSFreqYTJyYmYLZuV9t3FD6miDHK9Bk6csiDmxMYzssvhbvXp4XFg1FTZVRuPKb1AAAAAAAAAABIAEBcQAAAAIAAAAAAAEADPCh6SbxQFvoOsy16T+o1t9ppyxh3wCCATIk2ijXiQK7tY58W/2t8FysjP0RUEOq6AAAAAIAAAFsAAABLgAAAWsAAAAAAAEADPM/YpABRQGCbrCHesmtd7NQohItlVrJ+xFdG13xqo3ZFwpeCldZirZUOfTzZmQXPwAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIOsLyU/JRbgdKwtENNG1brDVsXEXRbQfOGc6oFCNFRuNACAIxXx8JXqfNxSy3h59UX4Jmd9nFeX85yMUGtGxB54+SwARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA=","new-auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF"}`), + []byte(`{"auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF","auth-policy":"AAAAAAAAAAEADPSFreqYTJyYmYLZuV9t3FD6miDHK9Bk6csiDmxMYzssvhbvXp4XFg1FTZVRuPKb1AAAAAAAAAABIAEBcQAAAAIAAAAAAAEADPCh6SbxQFvoOsy16T+o1t9ppyxh3wCCATIk2ijXiQK7tY58W/2t8FysjP0RUEOq6AAAAAIAAAFsAAABLgAAAWsAAAAAAAEADPM/YpABRQGCbrCHesmtd7NQohItlVrJ+xFdG13xqo3ZFwpeCldZirZUOfTzZmQXPwAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIOsLyU/JRbgdKwtENNG1brDVsXEXRbQfOGc6oFCNFRuNACAIxXx8JXqfNxSy3h59UX4Jmd9nFeX85yMUGtGxB54+SwARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA=","new-auth-policy":"AAAAAAAAAAEADHqZCU8TuxgO7/elTguGw5So3SieBY2dRYOphhKVmu/mfi0NZyjHZFs+bMdtqZ284AAAAAAAAAACIAEBcQAAAAUAAAAAAAEADDuidKgJLPOC+/XOxwcOj4kEPzOZ/Z1YUWk9Coew5Aw15qxGHJWewJDjXAceJJnPkAAAAAEAAAFsAAABOQAAAAAAAQAML2Gkl0eOgfHT9Y1kGXkkE3jVI90qXY6wBtT2Ygksi3HgeTdQwD5WkH4QRDBsnYACAAAAAQAAAWwAAAE6AAAAAAABAAwutwj6joYO8lx+lgwraBTEMW6r5tQ2E+4QIxx/oEZ9ypxOerrTVEjGvnpGCmH/ym8AAAABAAABbAAAAScAAAAAAAEADFWgOLNA+yd26JBC+OGmP0ddbtEpzhpdo1wtbJIlwSui4lkkKncZB7rSyqFuZuALsAAAAAEAAAFsAAABJgAAAAAAAQAMladi5DAnH2ss5iXXhVU2rjlbDNmYkSGb4C7ZBqD+eDxKyQEruFSI6WY5/Lb4ppZNAAAAAQAAAWwAAAEuAAABaw=="}`), + []byte(`{"auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF","auth-policy":"AAAAAAAAAAEADHqZCU8TuxgO7/elTguGw5So3SieBY2dRYOphhKVmu/mfi0NZyjHZFs+bMdtqZ284AAAAAAAAAACIAEBcQAAAAUAAAAAAAEADDuidKgJLPOC+/XOxwcOj4kEPzOZ/Z1YUWk9Coew5Aw15qxGHJWewJDjXAceJJnPkAAAAAEAAAFsAAABOQAAAAAAAQAML2Gkl0eOgfHT9Y1kGXkkE3jVI90qXY6wBtT2Ygksi3HgeTdQwD5WkH4QRDBsnYACAAAAAQAAAWwAAAE6AAAAAAABAAwutwj6joYO8lx+lgwraBTEMW6r5tQ2E+4QIxx/oEZ9ypxOerrTVEjGvnpGCmH/ym8AAAABAAABbAAAAScAAAAAAAEADFWgOLNA+yd26JBC+OGmP0ddbtEpzhpdo1wtbJIlwSui4lkkKncZB7rSyqFuZuALsAAAAAEAAAFsAAABJgAAAAAAAQAMladi5DAnH2ss5iXXhVU2rjlbDNmYkSGb4C7ZBqD+eDxKyQEruFSI6WY5/Lb4ppZNAAAAAQAAAWwAAAEuAAABaw=="}`), + }, + }) } func (s *provisioningSimulatorSuite) TestProvisionNewTPMFull(c *C) { s.testProvisionNewTPM(c, &testProvisionNewTPMData{ - mode: ProvisionModeFull, - lockoutAuth: []byte("1234")}) + clear: false, + lockoutAuthBytes: testutil.DecodeHexString(c, "c04c673608034f3f6fdd1b2ba752daf8ae5fa9ca5d7fc21b5f5f1dbdd9427ceaa6f35c0d0f98c2926a0b029296f06cc5a5a368364e3d07c6d6169c9443a70c3c"), + expectedLockoutAuthData: [][]byte{ + []byte(`{"auth-value":null,"new-auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF","new-auth-policy":"AAAAAAAAAAEADPSFreqYTJyYmYLZuV9t3FD6miDHK9Bk6csiDmxMYzssvhbvXp4XFg1FTZVRuPKb1AAAAAAAAAABIAEBcQAAAAIAAAAAAAEADPCh6SbxQFvoOsy16T+o1t9ppyxh3wCCATIk2ijXiQK7tY58W/2t8FysjP0RUEOq6AAAAAIAAAFsAAABLgAAAWsAAAAAAAEADPM/YpABRQGCbrCHesmtd7NQohItlVrJ+xFdG13xqo3ZFwpeCldZirZUOfTzZmQXPwAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIOsLyU/JRbgdKwtENNG1brDVsXEXRbQfOGc6oFCNFRuNACAIxXx8JXqfNxSy3h59UX4Jmd9nFeX85yMUGtGxB54+SwARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA="}`), + []byte(`{"auth-value":null,"auth-policy":"AAAAAAAAAAEADPSFreqYTJyYmYLZuV9t3FD6miDHK9Bk6csiDmxMYzssvhbvXp4XFg1FTZVRuPKb1AAAAAAAAAABIAEBcQAAAAIAAAAAAAEADPCh6SbxQFvoOsy16T+o1t9ppyxh3wCCATIk2ijXiQK7tY58W/2t8FysjP0RUEOq6AAAAAIAAAFsAAABLgAAAWsAAAAAAAEADPM/YpABRQGCbrCHesmtd7NQohItlVrJ+xFdG13xqo3ZFwpeCldZirZUOfTzZmQXPwAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIOsLyU/JRbgdKwtENNG1brDVsXEXRbQfOGc6oFCNFRuNACAIxXx8JXqfNxSy3h59UX4Jmd9nFeX85yMUGtGxB54+SwARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA=","new-auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF"}`), + []byte(`{"auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF","auth-policy":"AAAAAAAAAAEADPSFreqYTJyYmYLZuV9t3FD6miDHK9Bk6csiDmxMYzssvhbvXp4XFg1FTZVRuPKb1AAAAAAAAAABIAEBcQAAAAIAAAAAAAEADPCh6SbxQFvoOsy16T+o1t9ppyxh3wCCATIk2ijXiQK7tY58W/2t8FysjP0RUEOq6AAAAAIAAAFsAAABLgAAAWsAAAAAAAEADPM/YpABRQGCbrCHesmtd7NQohItlVrJ+xFdG13xqo3ZFwpeCldZirZUOfTzZmQXPwAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIOsLyU/JRbgdKwtENNG1brDVsXEXRbQfOGc6oFCNFRuNACAIxXx8JXqfNxSy3h59UX4Jmd9nFeX85yMUGtGxB54+SwARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA=","new-auth-policy":"AAAAAAAAAAEADHqZCU8TuxgO7/elTguGw5So3SieBY2dRYOphhKVmu/mfi0NZyjHZFs+bMdtqZ284AAAAAAAAAACIAEBcQAAAAUAAAAAAAEADDuidKgJLPOC+/XOxwcOj4kEPzOZ/Z1YUWk9Coew5Aw15qxGHJWewJDjXAceJJnPkAAAAAEAAAFsAAABOQAAAAAAAQAML2Gkl0eOgfHT9Y1kGXkkE3jVI90qXY6wBtT2Ygksi3HgeTdQwD5WkH4QRDBsnYACAAAAAQAAAWwAAAE6AAAAAAABAAwutwj6joYO8lx+lgwraBTEMW6r5tQ2E+4QIxx/oEZ9ypxOerrTVEjGvnpGCmH/ym8AAAABAAABbAAAAScAAAAAAAEADFWgOLNA+yd26JBC+OGmP0ddbtEpzhpdo1wtbJIlwSui4lkkKncZB7rSyqFuZuALsAAAAAEAAAFsAAABJgAAAAAAAQAMladi5DAnH2ss5iXXhVU2rjlbDNmYkSGb4C7ZBqD+eDxKyQEruFSI6WY5/Lb4ppZNAAAAAQAAAWwAAAEuAAABaw=="}`), + []byte(`{"auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF","auth-policy":"AAAAAAAAAAEADHqZCU8TuxgO7/elTguGw5So3SieBY2dRYOphhKVmu/mfi0NZyjHZFs+bMdtqZ284AAAAAAAAAACIAEBcQAAAAUAAAAAAAEADDuidKgJLPOC+/XOxwcOj4kEPzOZ/Z1YUWk9Coew5Aw15qxGHJWewJDjXAceJJnPkAAAAAEAAAFsAAABOQAAAAAAAQAML2Gkl0eOgfHT9Y1kGXkkE3jVI90qXY6wBtT2Ygksi3HgeTdQwD5WkH4QRDBsnYACAAAAAQAAAWwAAAE6AAAAAAABAAwutwj6joYO8lx+lgwraBTEMW6r5tQ2E+4QIxx/oEZ9ypxOerrTVEjGvnpGCmH/ym8AAAABAAABbAAAAScAAAAAAAEADFWgOLNA+yd26JBC+OGmP0ddbtEpzhpdo1wtbJIlwSui4lkkKncZB7rSyqFuZuALsAAAAAEAAAFsAAABJgAAAAAAAQAMladi5DAnH2ss5iXXhVU2rjlbDNmYkSGb4C7ZBqD+eDxKyQEruFSI6WY5/Lb4ppZNAAAAAQAAAWwAAAEuAAABaw=="}`), + }, + }) } func (s *provisioningSimulatorSuite) TestProvisionNewTPMDifferentLockoutAuth(c *C) { s.testProvisionNewTPM(c, &testProvisionNewTPMData{ - mode: ProvisionModeClear, - lockoutAuth: []byte("foo")}) + clear: true, + lockoutAuthBytes: testutil.DecodeHexString(c, "f10fa81ad01d6912916951039ed6a06c33f6995a5b6cd307f246d2dd6551edce865b7d2793cf6f2577730e4c6318b8189c5659b86bfa15703825b09359dc9cf9"), + expectedLockoutAuthData: [][]byte{ + []byte(`{"auth-value":null,"new-auth-value":"8Q+oGtAdaRKRaVEDntagbDP2mVpbbNMH8kbS3WVR7c6GW30nk89vJXdzDkxjGLgY","new-auth-policy":"AAAAAAAAAAEADPSFreqYTJyYmYLZuV9t3FD6miDHK9Bk6csiDmxMYzssvhbvXp4XFg1FTZVRuPKb1AAAAAAAAAABIAEBcQAAAAIAAAAAAAEADPCh6SbxQFvoOsy16T+o1t9ppyxh3wCCATIk2ijXiQK7tY58W/2t8FysjP0RUEOq6AAAAAIAAAFsAAABLgAAAWsAAAAAAAEADPM/YpABRQGCbrCHesmtd7NQohItlVrJ+xFdG13xqo3ZFwpeCldZirZUOfTzZmQXPwAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIOsLyU/JRbgdKwtENNG1brDVsXEXRbQfOGc6oFCNFRuNACAIxXx8JXqfNxSy3h59UX4Jmd9nFeX85yMUGtGxB54+SwARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA="}`), + []byte(`{"auth-value":null,"auth-policy":"AAAAAAAAAAEADPSFreqYTJyYmYLZuV9t3FD6miDHK9Bk6csiDmxMYzssvhbvXp4XFg1FTZVRuPKb1AAAAAAAAAABIAEBcQAAAAIAAAAAAAEADPCh6SbxQFvoOsy16T+o1t9ppyxh3wCCATIk2ijXiQK7tY58W/2t8FysjP0RUEOq6AAAAAIAAAFsAAABLgAAAWsAAAAAAAEADPM/YpABRQGCbrCHesmtd7NQohItlVrJ+xFdG13xqo3ZFwpeCldZirZUOfTzZmQXPwAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIOsLyU/JRbgdKwtENNG1brDVsXEXRbQfOGc6oFCNFRuNACAIxXx8JXqfNxSy3h59UX4Jmd9nFeX85yMUGtGxB54+SwARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA=","new-auth-value":"8Q+oGtAdaRKRaVEDntagbDP2mVpbbNMH8kbS3WVR7c6GW30nk89vJXdzDkxjGLgY"}`), + []byte(`{"auth-value":"8Q+oGtAdaRKRaVEDntagbDP2mVpbbNMH8kbS3WVR7c6GW30nk89vJXdzDkxjGLgY","auth-policy":"AAAAAAAAAAEADPSFreqYTJyYmYLZuV9t3FD6miDHK9Bk6csiDmxMYzssvhbvXp4XFg1FTZVRuPKb1AAAAAAAAAABIAEBcQAAAAIAAAAAAAEADPCh6SbxQFvoOsy16T+o1t9ppyxh3wCCATIk2ijXiQK7tY58W/2t8FysjP0RUEOq6AAAAAIAAAFsAAABLgAAAWsAAAAAAAEADPM/YpABRQGCbrCHesmtd7NQohItlVrJ+xFdG13xqo3ZFwpeCldZirZUOfTzZmQXPwAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIOsLyU/JRbgdKwtENNG1brDVsXEXRbQfOGc6oFCNFRuNACAIxXx8JXqfNxSy3h59UX4Jmd9nFeX85yMUGtGxB54+SwARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA=","new-auth-policy":"AAAAAAAAAAEADHqZCU8TuxgO7/elTguGw5So3SieBY2dRYOphhKVmu/mfi0NZyjHZFs+bMdtqZ284AAAAAAAAAACIAEBcQAAAAUAAAAAAAEADDuidKgJLPOC+/XOxwcOj4kEPzOZ/Z1YUWk9Coew5Aw15qxGHJWewJDjXAceJJnPkAAAAAEAAAFsAAABOQAAAAAAAQAML2Gkl0eOgfHT9Y1kGXkkE3jVI90qXY6wBtT2Ygksi3HgeTdQwD5WkH4QRDBsnYACAAAAAQAAAWwAAAE6AAAAAAABAAwutwj6joYO8lx+lgwraBTEMW6r5tQ2E+4QIxx/oEZ9ypxOerrTVEjGvnpGCmH/ym8AAAABAAABbAAAAScAAAAAAAEADFWgOLNA+yd26JBC+OGmP0ddbtEpzhpdo1wtbJIlwSui4lkkKncZB7rSyqFuZuALsAAAAAEAAAFsAAABJgAAAAAAAQAMladi5DAnH2ss5iXXhVU2rjlbDNmYkSGb4C7ZBqD+eDxKyQEruFSI6WY5/Lb4ppZNAAAAAQAAAWwAAAEuAAABaw=="}`), + []byte(`{"auth-value":"8Q+oGtAdaRKRaVEDntagbDP2mVpbbNMH8kbS3WVR7c6GW30nk89vJXdzDkxjGLgY","auth-policy":"AAAAAAAAAAEADHqZCU8TuxgO7/elTguGw5So3SieBY2dRYOphhKVmu/mfi0NZyjHZFs+bMdtqZ284AAAAAAAAAACIAEBcQAAAAUAAAAAAAEADDuidKgJLPOC+/XOxwcOj4kEPzOZ/Z1YUWk9Coew5Aw15qxGHJWewJDjXAceJJnPkAAAAAEAAAFsAAABOQAAAAAAAQAML2Gkl0eOgfHT9Y1kGXkkE3jVI90qXY6wBtT2Ygksi3HgeTdQwD5WkH4QRDBsnYACAAAAAQAAAWwAAAE6AAAAAAABAAwutwj6joYO8lx+lgwraBTEMW6r5tQ2E+4QIxx/oEZ9ypxOerrTVEjGvnpGCmH/ym8AAAABAAABbAAAAScAAAAAAAEADFWgOLNA+yd26JBC+OGmP0ddbtEpzhpdo1wtbJIlwSui4lkkKncZB7rSyqFuZuALsAAAAAEAAAFsAAABJgAAAAAAAQAMladi5DAnH2ss5iXXhVU2rjlbDNmYkSGb4C7ZBqD+eDxKyQEruFSI6WY5/Lb4ppZNAAAAAQAAAWwAAAEuAAABaw=="}`), + }, + }) +} + +func (s *provisioningSimulatorSuite) TestProvisionNewTPMNoLockoutAuthPolicies(c *C) { + // Test with a TPM that doesn't support TPM_CAP_AUTH_POLICIES + s.TPMTest.Transport.ResponseIntercept = func(cmdCode tpm2.CommandCode, cmdHandle tpm2.HandleList, cmdAuthArea []tpm2.AuthCommand, cpBytes []byte, rsp *bytes.Buffer) { + if cmdCode != tpm2.CommandGetCapability { + return + } + + // Unpack the command parameters + var capability tpm2.Capability + var property uint32 + var propertyCount uint32 + _, err := mu.UnmarshalFromBytes(cpBytes, &capability, &property, &propertyCount) + c.Assert(err, IsNil) + if capability != tpm2.CapabilityAuthPolicies { + return + } + + // Return a TPM_RC_VALUE + TPM_RC_P + TPM_RC_1 error + rsp.Reset() + c.Check(tpm2.WriteResponsePacket(rsp, tpm2.ResponseValue+tpm2.ResponseP+tpm2.ResponseIndex1, nil, nil, nil), IsNil) + } + + s.testProvisionNewTPM(c, &testProvisionNewTPMData{ + clear: false, + lockoutAuthBytes: testutil.DecodeHexString(c, "c04c673608034f3f6fdd1b2ba752daf8ae5fa9ca5d7fc21b5f5f1dbdd9427ceaa6f35c0d0f98c2926a0b029296f06cc5a5a368364e3d07c6d6169c9443a70c3c"), + expectedLockoutAuthData: [][]byte{ + []byte(`{"auth-value":null,"new-auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF","new-auth-policy":"AAAAAAAAAAEADPSFreqYTJyYmYLZuV9t3FD6miDHK9Bk6csiDmxMYzssvhbvXp4XFg1FTZVRuPKb1AAAAAAAAAABIAEBcQAAAAIAAAAAAAEADPCh6SbxQFvoOsy16T+o1t9ppyxh3wCCATIk2ijXiQK7tY58W/2t8FysjP0RUEOq6AAAAAIAAAFsAAABLgAAAWsAAAAAAAEADPM/YpABRQGCbrCHesmtd7NQohItlVrJ+xFdG13xqo3ZFwpeCldZirZUOfTzZmQXPwAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIOsLyU/JRbgdKwtENNG1brDVsXEXRbQfOGc6oFCNFRuNACAIxXx8JXqfNxSy3h59UX4Jmd9nFeX85yMUGtGxB54+SwARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA="}`), + []byte(`{"auth-value":null,"new-auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF","new-auth-policy":"AAAAAAAAAAEADPSFreqYTJyYmYLZuV9t3FD6miDHK9Bk6csiDmxMYzssvhbvXp4XFg1FTZVRuPKb1AAAAAAAAAABIAEBcQAAAAIAAAAAAAEADPCh6SbxQFvoOsy16T+o1t9ppyxh3wCCATIk2ijXiQK7tY58W/2t8FysjP0RUEOq6AAAAAIAAAFsAAABLgAAAWsAAAAAAAEADPM/YpABRQGCbrCHesmtd7NQohItlVrJ+xFdG13xqo3ZFwpeCldZirZUOfTzZmQXPwAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIOsLyU/JRbgdKwtENNG1brDVsXEXRbQfOGc6oFCNFRuNACAIxXx8JXqfNxSy3h59UX4Jmd9nFeX85yMUGtGxB54+SwARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA="}`), + []byte(`{"auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF"}`), + }, + }) } func (s *provisioningSuite) TestProvisionWithLockoutAuthValue(c *C) { + authValue := []byte("1234") + s.HierarchyChangeAuth(c, tpm2.HandleLockout, authValue) + + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthValue(authValue)), IsNil) + + // 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(authValue) + c.Check(s.TPM().DictionaryAttackLockReset(s.TPM().LockoutHandleContext(), nil), IsNil) +} + +func (s *provisioningSuite) TestProvisionWithLockoutAuthData(c *C) { + authValue := []byte("1234") + policyDigest, data := s.makeDefaultLockoutAuthData(c, tpm2.HashAlgorithmSHA256, authValue) + s.HierarchyChangeAuth(c, tpm2.HandleLockout, authValue) + c.Assert(s.TPM().SetPrimaryPolicy(s.TPM().LockoutHandleContext(), policyDigest, tpm2.HashAlgorithmSHA256, nil), IsNil) + + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthData(data)), IsNil) + + // 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(authValue) + c.Check(s.TPM().DictionaryAttackLockReset(s.TPM().LockoutHandleContext(), nil), IsNil) +} + +func (s *provisioningSuite) TestProvisionWithLockoutAuthDataNoAuthPolicies(c *C) { + authValue := []byte("1234") + data := s.makeLockoutAuthData(c, &LockoutAuthParams{ + AuthValue: authValue, + }) + s.HierarchyChangeAuth(c, tpm2.HandleLockout, authValue) + + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthData(data)), IsNil) + + // 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(authValue) + c.Check(s.TPM().DictionaryAttackLockReset(s.TPM().LockoutHandleContext(), nil), IsNil) +} + +func (s *provisioningSuite) TestProvisionResumeNewLockoutAuthValue1(c *C) { + // Test resuming with WithProvisionNewLockoutAuthValue after a previous attempt was interrupted + // after prepare + origValue := []byte("1234") + policyDigest, policy1 := s.newDefaultLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256) + _, policy2 := s.newUpdateAuthValueLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256, origValue) + s.HierarchyChangeAuth(c, tpm2.HandleLockout, origValue) + c.Assert(s.TPM().SetPrimaryPolicy(s.TPM().LockoutHandleContext(), policyDigest, tpm2.HashAlgorithmSHA256, nil), IsNil) + + lockoutAuthBytes := testutil.DecodeHexString(c, "c04c673608034f3f6fdd1b2ba752daf8ae5fa9ca5d7fc21b5f5f1dbdd9427ceaa6f35c0d0f98c2926a0b029296f06cc5a5a368364e3d07c6d6169c9443a70c3c") + + data := s.makeLockoutAuthData(c, &LockoutAuthParams{ + AuthValue: origValue, + AuthPolicy: policy1, + NewAuthValue: lockoutAuthValue(c, s.TPM(), lockoutAuthBytes), + NewAuthPolicy: policy2, + }) + + expectedLockoutAuthData := [][]byte{ + []byte(`{"auth-value":"MTIzNA==","auth-policy":"AAAAAAAAAAEAC3I61USvx7CvOmM61pIa40NXvY6AqinRzDx16Py3QDnvAAAAAAAAAAEgAQFxAAAAAgAAAAAAAQAL/g0OavvRqALD6F4sJD+kB1TWHYxCvdViNHPYjqSJqbIAAAACAAABbAAAAS4AAAFrAAAAAAABAAvbx93A0uFXIca/EFHCBbGmUYmB95xoVE6ZYxLqI5of2gAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIBwf0eeWXYZJ+PFN0xQ+9xaG+03+fD2SC1aOweJmzl9xACDWXojHU30aQKHFCkSWvhdsU1U0q+qTVp7hcjLvddqrLwARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA=","new-auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF"}`), + []byte(`{"auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF","auth-policy":"AAAAAAAAAAEAC3I61USvx7CvOmM61pIa40NXvY6AqinRzDx16Py3QDnvAAAAAAAAAAEgAQFxAAAAAgAAAAAAAQAL/g0OavvRqALD6F4sJD+kB1TWHYxCvdViNHPYjqSJqbIAAAACAAABbAAAAS4AAAFrAAAAAAABAAvbx93A0uFXIca/EFHCBbGmUYmB95xoVE6ZYxLqI5of2gAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIBwf0eeWXYZJ+PFN0xQ+9xaG+03+fD2SC1aOweJmzl9xACDWXojHU30aQKHFCkSWvhdsU1U0q+qTVp7hcjLvddqrLwARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA=","new-auth-policy":"AAAAAAAAAAEAC8fpxFXFnW/i+VVUXTr6s3kopn5+LbHkhqxSYqdusGu/AAAAAAAAAAIgAQFxAAAABQAAAAAAAQALtsXAXlgZCc3qffel+RwPLu03/XbxVSLu5bVfiW8tVj8AAAABAAABbAAAATkAAAAAAAEACxxoJ3ydZWTdgbzPfla6PtyrOI/GDOlbOkQr0nJY9g38AAAAAQAAAWwAAAE6AAAAAAABAAuUDPtCF7se3Pf7QZN8qXSqaOaYq3i4EksHARPiEf1G/AAAAAEAAAFsAAABJwAAAAAAAQALxN+rztqN6DbJVmGVKJKx3vcgOvtG/v7EP/z8k75UBzAAAAABAAABbAAAASYAAAAAAAEAC3G+h1vfkVM3lejs6YjXVDuULEStbQE7L3xfQ4MLi6IXAAAAAQAAAWwAAAEuAAABaw=="}`), + []byte(`{"auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF","auth-policy":"AAAAAAAAAAEAC8fpxFXFnW/i+VVUXTr6s3kopn5+LbHkhqxSYqdusGu/AAAAAAAAAAIgAQFxAAAABQAAAAAAAQALtsXAXlgZCc3qffel+RwPLu03/XbxVSLu5bVfiW8tVj8AAAABAAABbAAAATkAAAAAAAEACxxoJ3ydZWTdgbzPfla6PtyrOI/GDOlbOkQr0nJY9g38AAAAAQAAAWwAAAE6AAAAAAABAAuUDPtCF7se3Pf7QZN8qXSqaOaYq3i4EksHARPiEf1G/AAAAAEAAAFsAAABJwAAAAAAAQALxN+rztqN6DbJVmGVKJKx3vcgOvtG/v7EP/z8k75UBzAAAAABAAABbAAAASYAAAAAAAEAC3G+h1vfkVM3lejs6YjXVDuULEStbQE7L3xfQ4MLi6IXAAAAAQAAAWwAAAEuAAABaw=="}`), + } + syncLockoutAuthData := func(data []byte) error { + c.Assert(expectedLockoutAuthData, Not(HasLen), 0) + expected := expectedLockoutAuthData[0] + expectedLockoutAuthData = expectedLockoutAuthData[1:] + c.Check(data, DeepEquals, expected) + return nil + } + + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthData(data), WithProvisionNewLockoutAuthValue(bytes.NewReader(nil), syncLockoutAuthData)), 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(lockoutAuthValue(c, s.TPM(), lockoutAuthBytes)) + s.HierarchyChangeAuth(c, tpm2.HandleLockout, nil) + }) + + c.Check(expectedLockoutAuthData, HasLen, 0) + + // 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(lockoutAuthValue(c, s.TPM(), lockoutAuthBytes)) + c.Check(s.TPM().DictionaryAttackLockReset(s.TPM().LockoutHandleContext(), nil), IsNil) +} + +func (s *provisioningSuite) TestProvisionResumeNewLockoutAuthValue2(c *C) { + // Test resuming with WithProvisionNewLockoutAuthValue after a previous attempt was interrupted + // after setNewAuthValuePolicy origValue := []byte("1234") - newValue := []byte("5678") + policyDigest, policy := s.newUpdateAuthValueLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256, origValue) s.HierarchyChangeAuth(c, tpm2.HandleLockout, origValue) + c.Assert(s.TPM().SetPrimaryPolicy(s.TPM().LockoutHandleContext(), policyDigest, tpm2.HashAlgorithmSHA256, nil), IsNil) + + lockoutAuthBytes := testutil.DecodeHexString(c, "c04c673608034f3f6fdd1b2ba752daf8ae5fa9ca5d7fc21b5f5f1dbdd9427ceaa6f35c0d0f98c2926a0b029296f06cc5a5a368364e3d07c6d6169c9443a70c3c") + + data := s.makeLockoutAuthData(c, &LockoutAuthParams{ + AuthValue: origValue, + AuthPolicy: policy, + NewAuthValue: lockoutAuthValue(c, s.TPM(), lockoutAuthBytes), + }) - c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthValue(origValue), WithProvisionNewLockoutAuthValue(newValue)), IsNil) + expectedLockoutAuthData := [][]byte{ + []byte(`{"auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF","auth-policy":"AAAAAAAAAAEAC3I61USvx7CvOmM61pIa40NXvY6AqinRzDx16Py3QDnvAAAAAAAAAAEgAQFxAAAAAgAAAAAAAQAL/g0OavvRqALD6F4sJD+kB1TWHYxCvdViNHPYjqSJqbIAAAACAAABbAAAAS4AAAFrAAAAAAABAAvbx93A0uFXIca/EFHCBbGmUYmB95xoVE6ZYxLqI5of2gAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIBwf0eeWXYZJ+PFN0xQ+9xaG+03+fD2SC1aOweJmzl9xACDWXojHU30aQKHFCkSWvhdsU1U0q+qTVp7hcjLvddqrLwARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA=","new-auth-policy":"AAAAAAAAAAEAC8fpxFXFnW/i+VVUXTr6s3kopn5+LbHkhqxSYqdusGu/AAAAAAAAAAIgAQFxAAAABQAAAAAAAQALtsXAXlgZCc3qffel+RwPLu03/XbxVSLu5bVfiW8tVj8AAAABAAABbAAAATkAAAAAAAEACxxoJ3ydZWTdgbzPfla6PtyrOI/GDOlbOkQr0nJY9g38AAAAAQAAAWwAAAE6AAAAAAABAAuUDPtCF7se3Pf7QZN8qXSqaOaYq3i4EksHARPiEf1G/AAAAAEAAAFsAAABJwAAAAAAAQALxN+rztqN6DbJVmGVKJKx3vcgOvtG/v7EP/z8k75UBzAAAAABAAABbAAAASYAAAAAAAEAC3G+h1vfkVM3lejs6YjXVDuULEStbQE7L3xfQ4MLi6IXAAAAAQAAAWwAAAEuAAABaw=="}`), + []byte(`{"auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF","auth-policy":"AAAAAAAAAAEAC8fpxFXFnW/i+VVUXTr6s3kopn5+LbHkhqxSYqdusGu/AAAAAAAAAAIgAQFxAAAABQAAAAAAAQALtsXAXlgZCc3qffel+RwPLu03/XbxVSLu5bVfiW8tVj8AAAABAAABbAAAATkAAAAAAAEACxxoJ3ydZWTdgbzPfla6PtyrOI/GDOlbOkQr0nJY9g38AAAAAQAAAWwAAAE6AAAAAAABAAuUDPtCF7se3Pf7QZN8qXSqaOaYq3i4EksHARPiEf1G/AAAAAEAAAFsAAABJwAAAAAAAQALxN+rztqN6DbJVmGVKJKx3vcgOvtG/v7EP/z8k75UBzAAAAABAAABbAAAASYAAAAAAAEAC3G+h1vfkVM3lejs6YjXVDuULEStbQE7L3xfQ4MLi6IXAAAAAQAAAWwAAAEuAAABaw=="}`), + } + syncLockoutAuthData := func(data []byte) error { + c.Assert(expectedLockoutAuthData, Not(HasLen), 0) + expected := expectedLockoutAuthData[0] + expectedLockoutAuthData = expectedLockoutAuthData[1:] + c.Check(data, DeepEquals, expected) + return nil + } + + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthData(data), WithProvisionNewLockoutAuthValue(bytes.NewReader(nil), syncLockoutAuthData)), 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.TPM().LockoutHandleContext().SetAuthValue(lockoutAuthValue(c, s.TPM(), lockoutAuthBytes)) s.HierarchyChangeAuth(c, tpm2.HandleLockout, nil) }) + c.Check(expectedLockoutAuthData, HasLen, 0) + // Validate the DA parameters value, err := s.TPM().GetCapabilityTPMProperty(tpm2.PropertyMaxAuthFail) c.Check(err, IsNil) @@ -207,53 +483,115 @@ func (s *provisioningSuite) TestProvisionWithLockoutAuthValue(c *C) { c.Check(tpm2.PermanentAttributes(value)&tpm2.AttrInLockout, Equals, tpm2.PermanentAttributes(0)) // Test the lockout hierarchy auth - s.TPM().LockoutHandleContext().SetAuthValue(newValue) + s.TPM().LockoutHandleContext().SetAuthValue(lockoutAuthValue(c, s.TPM(), lockoutAuthBytes)) c.Check(s.TPM().DictionaryAttackLockReset(s.TPM().LockoutHandleContext(), nil), IsNil) } -// XXX: This is temporarily disabled because EnsureProvisioned clears the policy -//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 *provisioningSuite) TestProvisionAfterInterruptedNewLockoutAuthValue2(c *C) { + // Test that we get an appropriate error if a previous call with WithProvisionNewLockoutAuthValue + // was interrupted after setNewAuthValuePolicy. + origValue := []byte("1234") + policyDigest, policy := s.newUpdateAuthValueLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256, origValue) + s.HierarchyChangeAuth(c, tpm2.HandleLockout, origValue) + c.Assert(s.TPM().SetPrimaryPolicy(s.TPM().LockoutHandleContext(), policyDigest, tpm2.HashAlgorithmSHA256, nil), IsNil) + + lockoutAuthBytes := testutil.DecodeHexString(c, "c04c673608034f3f6fdd1b2ba752daf8ae5fa9ca5d7fc21b5f5f1dbdd9427ceaa6f35c0d0f98c2926a0b029296f06cc5a5a368364e3d07c6d6169c9443a70c3c") + + data := s.makeLockoutAuthData(c, &LockoutAuthParams{ + AuthValue: origValue, + AuthPolicy: policy, + NewAuthValue: lockoutAuthValue(c, s.TPM(), lockoutAuthBytes), + }) + + err := s.TPM().EnsureProvisioned(WithLockoutAuthData(data)) + c.Check(err, Equals, ErrLockoutAuthUpdateInterrupted) + c.Check(err, ErrorMatches, `a previous attempt to update the authorization parameters for the lockout hierarchy was interrupted`) +} + +func (s *provisioningSuite) TestProvisionResumeNewLockoutAuthValue3(c *C) { + // Test resuming with WithProvisionNewLockoutAuthValue after a previous attempt was interrupted + // after setNewAuthValue + lockoutAuthBytes := testutil.DecodeHexString(c, "c04c673608034f3f6fdd1b2ba752daf8ae5fa9ca5d7fc21b5f5f1dbdd9427ceaa6f35c0d0f98c2926a0b029296f06cc5a5a368364e3d07c6d6169c9443a70c3c") + + policyDigest, policy1 := s.newUpdateAuthValueLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256, []byte("1234")) + _, policy2 := s.newDefaultLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256) + s.HierarchyChangeAuth(c, tpm2.HandleLockout, lockoutAuthValue(c, s.TPM(), lockoutAuthBytes)) + c.Assert(s.TPM().SetPrimaryPolicy(s.TPM().LockoutHandleContext(), policyDigest, tpm2.HashAlgorithmSHA256, nil), IsNil) + + data := s.makeLockoutAuthData(c, &LockoutAuthParams{ + AuthValue: lockoutAuthValue(c, s.TPM(), lockoutAuthBytes), + AuthPolicy: policy1, + NewAuthPolicy: policy2, + }) + + expectedLockoutAuthData := [][]byte{ + []byte(`{"auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF","auth-policy":"AAAAAAAAAAEAC8fpxFXFnW/i+VVUXTr6s3kopn5+LbHkhqxSYqdusGu/AAAAAAAAAAIgAQFxAAAABQAAAAAAAQALtsXAXlgZCc3qffel+RwPLu03/XbxVSLu5bVfiW8tVj8AAAABAAABbAAAATkAAAAAAAEACxxoJ3ydZWTdgbzPfla6PtyrOI/GDOlbOkQr0nJY9g38AAAAAQAAAWwAAAE6AAAAAAABAAuUDPtCF7se3Pf7QZN8qXSqaOaYq3i4EksHARPiEf1G/AAAAAEAAAFsAAABJwAAAAAAAQALxN+rztqN6DbJVmGVKJKx3vcgOvtG/v7EP/z8k75UBzAAAAABAAABbAAAASYAAAAAAAEAC3G+h1vfkVM3lejs6YjXVDuULEStbQE7L3xfQ4MLi6IXAAAAAQAAAWwAAAEuAAABaw=="}`), + } + syncLockoutAuthData := func(data []byte) error { + c.Assert(expectedLockoutAuthData, Not(HasLen), 0) + expected := expectedLockoutAuthData[0] + expectedLockoutAuthData = expectedLockoutAuthData[1:] + c.Check(data, DeepEquals, expected) + return nil + } + + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthData(data), WithProvisionNewLockoutAuthValue(bytes.NewReader(nil), syncLockoutAuthData)), 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(lockoutAuthValue(c, s.TPM(), lockoutAuthBytes)) + s.HierarchyChangeAuth(c, tpm2.HandleLockout, nil) + }) + + c.Check(expectedLockoutAuthData, HasLen, 0) + + // 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(lockoutAuthValue(c, s.TPM(), lockoutAuthBytes)) + c.Check(s.TPM().DictionaryAttackLockReset(s.TPM().LockoutHandleContext(), nil), IsNil) +} + +func (s *provisioningSuite) TestProvisionAfterInterruptedNewLockoutAuthValue3(c *C) { + // Test that we get an appropriate error if a previous call with WithProvisionNewLockoutAuthValue + // was interrupted after setNewAuthValue. + lockoutAuthBytes := testutil.DecodeHexString(c, "c04c673608034f3f6fdd1b2ba752daf8ae5fa9ca5d7fc21b5f5f1dbdd9427ceaa6f35c0d0f98c2926a0b029296f06cc5a5a368364e3d07c6d6169c9443a70c3c") + + policyDigest, policy1 := s.newUpdateAuthValueLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256, []byte("1234")) + _, policy2 := s.newDefaultLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256) + s.HierarchyChangeAuth(c, tpm2.HandleLockout, lockoutAuthValue(c, s.TPM(), lockoutAuthBytes)) + c.Assert(s.TPM().SetPrimaryPolicy(s.TPM().LockoutHandleContext(), policyDigest, tpm2.HashAlgorithmSHA256, nil), IsNil) + + data := s.makeLockoutAuthData(c, &LockoutAuthParams{ + AuthValue: lockoutAuthValue(c, s.TPM(), lockoutAuthBytes), + AuthPolicy: policy1, + NewAuthPolicy: policy2, + }) + + err := s.TPM().EnsureProvisioned(WithLockoutAuthData(data)) + c.Check(err, Equals, ErrLockoutAuthUpdateInterrupted) + c.Check(err, ErrorMatches, `a previous attempt to update the authorization parameters for the lockout hierarchy was interrupted`) +} func (s *provisioningSimulatorSuite) TestProvisionTPMInLockout(c *C) { // Trip the DA logic by triggering an auth failure with a DA protected @@ -270,8 +608,15 @@ func (s *provisioningSimulatorSuite) TestProvisionTPMInLockout(c *C) { c.Check(s.TPM().FlushContext(key), IsNil) s.testProvisionNewTPM(c, &testProvisionNewTPMData{ - mode: ProvisionModeFull, - lockoutAuth: []byte("1234")}) + clear: false, + lockoutAuthBytes: testutil.DecodeHexString(c, "c04c673608034f3f6fdd1b2ba752daf8ae5fa9ca5d7fc21b5f5f1dbdd9427ceaa6f35c0d0f98c2926a0b029296f06cc5a5a368364e3d07c6d6169c9443a70c3c"), + expectedLockoutAuthData: [][]byte{ + []byte(`{"auth-value":null,"new-auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF","new-auth-policy":"AAAAAAAAAAEADPSFreqYTJyYmYLZuV9t3FD6miDHK9Bk6csiDmxMYzssvhbvXp4XFg1FTZVRuPKb1AAAAAAAAAABIAEBcQAAAAIAAAAAAAEADPCh6SbxQFvoOsy16T+o1t9ppyxh3wCCATIk2ijXiQK7tY58W/2t8FysjP0RUEOq6AAAAAIAAAFsAAABLgAAAWsAAAAAAAEADPM/YpABRQGCbrCHesmtd7NQohItlVrJ+xFdG13xqo3ZFwpeCldZirZUOfTzZmQXPwAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIOsLyU/JRbgdKwtENNG1brDVsXEXRbQfOGc6oFCNFRuNACAIxXx8JXqfNxSy3h59UX4Jmd9nFeX85yMUGtGxB54+SwARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA="}`), + []byte(`{"auth-value":null,"auth-policy":"AAAAAAAAAAEADPSFreqYTJyYmYLZuV9t3FD6miDHK9Bk6csiDmxMYzssvhbvXp4XFg1FTZVRuPKb1AAAAAAAAAABIAEBcQAAAAIAAAAAAAEADPCh6SbxQFvoOsy16T+o1t9ppyxh3wCCATIk2ijXiQK7tY58W/2t8FysjP0RUEOq6AAAAAIAAAFsAAABLgAAAWsAAAAAAAEADPM/YpABRQGCbrCHesmtd7NQohItlVrJ+xFdG13xqo3ZFwpeCldZirZUOfTzZmQXPwAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIOsLyU/JRbgdKwtENNG1brDVsXEXRbQfOGc6oFCNFRuNACAIxXx8JXqfNxSy3h59UX4Jmd9nFeX85yMUGtGxB54+SwARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA=","new-auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF"}`), + []byte(`{"auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF","auth-policy":"AAAAAAAAAAEADPSFreqYTJyYmYLZuV9t3FD6miDHK9Bk6csiDmxMYzssvhbvXp4XFg1FTZVRuPKb1AAAAAAAAAABIAEBcQAAAAIAAAAAAAEADPCh6SbxQFvoOsy16T+o1t9ppyxh3wCCATIk2ijXiQK7tY58W/2t8FysjP0RUEOq6AAAAAIAAAFsAAABLgAAAWsAAAAAAAEADPM/YpABRQGCbrCHesmtd7NQohItlVrJ+xFdG13xqo3ZFwpeCldZirZUOfTzZmQXPwAAAAIAAAFsAAABKQAAAWAAIwALAAQAAAAAABAAEAADABAAIOsLyU/JRbgdKwtENNG1brDVsXEXRbQfOGc6oFCNFRuNACAIxXx8JXqfNxSy3h59UX4Jmd9nFeX85yMUGtGxB54+SwARVVBEQVRFLUFVVEgtVkFMVUUAAAAAAAA=","new-auth-policy":"AAAAAAAAAAEADHqZCU8TuxgO7/elTguGw5So3SieBY2dRYOphhKVmu/mfi0NZyjHZFs+bMdtqZ284AAAAAAAAAACIAEBcQAAAAUAAAAAAAEADDuidKgJLPOC+/XOxwcOj4kEPzOZ/Z1YUWk9Coew5Aw15qxGHJWewJDjXAceJJnPkAAAAAEAAAFsAAABOQAAAAAAAQAML2Gkl0eOgfHT9Y1kGXkkE3jVI90qXY6wBtT2Ygksi3HgeTdQwD5WkH4QRDBsnYACAAAAAQAAAWwAAAE6AAAAAAABAAwutwj6joYO8lx+lgwraBTEMW6r5tQ2E+4QIxx/oEZ9ypxOerrTVEjGvnpGCmH/ym8AAAABAAABbAAAAScAAAAAAAEADFWgOLNA+yd26JBC+OGmP0ddbtEpzhpdo1wtbJIlwSui4lkkKncZB7rSyqFuZuALsAAAAAEAAAFsAAABJgAAAAAAAQAMladi5DAnH2ss5iXXhVU2rjlbDNmYkSGb4C7ZBqD+eDxKyQEruFSI6WY5/Lb4ppZNAAAAAQAAAWwAAAEuAAABaw=="}`), + []byte(`{"auth-value":"wExnNggDTz9v3Rsrp1La+K5fqcpdf8IbX18dvdlCfOqm81wND5jCkmoLApKW8GzF","auth-policy":"AAAAAAAAAAEADHqZCU8TuxgO7/elTguGw5So3SieBY2dRYOphhKVmu/mfi0NZyjHZFs+bMdtqZ284AAAAAAAAAACIAEBcQAAAAUAAAAAAAEADDuidKgJLPOC+/XOxwcOj4kEPzOZ/Z1YUWk9Coew5Aw15qxGHJWewJDjXAceJJnPkAAAAAEAAAFsAAABOQAAAAAAAQAML2Gkl0eOgfHT9Y1kGXkkE3jVI90qXY6wBtT2Ygksi3HgeTdQwD5WkH4QRDBsnYACAAAAAQAAAWwAAAE6AAAAAAABAAwutwj6joYO8lx+lgwraBTEMW6r5tQ2E+4QIxx/oEZ9ypxOerrTVEjGvnpGCmH/ym8AAAABAAABbAAAAScAAAAAAAEADFWgOLNA+yd26JBC+OGmP0ddbtEpzhpdo1wtbJIlwSui4lkkKncZB7rSyqFuZuALsAAAAAEAAAFsAAABJgAAAAAAAQAMladi5DAnH2ss5iXXhVU2rjlbDNmYkSGb4C7ZBqD+eDxKyQEruFSI6WY5/Lb4ppZNAAAAAQAAAWwAAAEuAAABaw=="}`), + }, + }) } func (s *provisioningSimulatorSuite) testProvisionErrorHandling(c *C, mode ProvisionMode) error { @@ -281,7 +626,14 @@ func (s *provisioningSimulatorSuite) testProvisionErrorHandling(c *C, mode Provi // else the test fixture fails the test. s.ClearTPMUsingPlatformHierarchy(c) }() - return s.TPM().EnsureProvisioned(mode.Option(nil)) + var opts []EnsureProvisionedOption + switch mode { + case ProvisionModeFull: + opts = append(opts, WithLockoutAuthValue(nil)) + case ProvisionModeClear: + opts = append(opts, WithLockoutAuthValue(nil), WithClearBeforeProvision()) + } + return s.TPM().EnsureProvisioned(opts...) } func (s *provisioningSuite) testProvisionErrorHandling(c *C, mode ProvisionMode) error { @@ -291,7 +643,14 @@ func (s *provisioningSuite) testProvisionErrorHandling(c *C, mode ProvisionMode) // else the test fixture fails the test. s.ClearTPMUsingPlatformHierarchy(c) }() - return s.TPM().EnsureProvisioned(mode.Option(nil)) + var opts []EnsureProvisionedOption + switch mode { + case ProvisionModeFull: + opts = append(opts, WithLockoutAuthValue(nil)) + case ProvisionModeClear: + opts = append(opts, WithLockoutAuthValue(nil), WithClearBeforeProvision()) + } + return s.TPM().EnsureProvisioned(opts...) } func (s *provisioningSuite) TestProvisionErrorHandlingClearRequiresPPI(c *C) { @@ -303,7 +662,6 @@ func (s *provisioningSuite) TestProvisionErrorHandlingClearRequiresPPI(c *C) { func (s *provisioningSuite) TestProvisionErrorHandlingLockoutAuthFail1(c *C) { s.HierarchyChangeAuth(c, tpm2.HandleLockout, []byte("1234")) - s.TPM().LockoutHandleContext().SetAuthValue(nil) err := s.testProvisionErrorHandling(c, ProvisionModeFull) c.Assert(err, testutil.ConvertibleTo, AuthFailError{}) @@ -312,7 +670,6 @@ func (s *provisioningSuite) TestProvisionErrorHandlingLockoutAuthFail1(c *C) { func (s *provisioningSuite) TestProvisionErrorHandlingLockoutAuthFail2(c *C) { s.HierarchyChangeAuth(c, tpm2.HandleLockout, []byte("1234")) - s.TPM().LockoutHandleContext().SetAuthValue(nil) err := s.testProvisionErrorHandling(c, ProvisionModeClear) c.Assert(err, testutil.ConvertibleTo, AuthFailError{}) @@ -410,15 +767,22 @@ func (s *provisioningSimulatorSuite) TestProvisionErrorHandlingRequiresLockout5( c.Check(err, Equals, ErrTPMProvisioningRequiresLockout) } -func (s *provisioningSuite) testProvisionRecreateEK(c *C, mode ProvisionMode) { - lockoutAuth := []byte("1234") - - c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthValue(nil), WithProvisionNewLockoutAuthValue(lockoutAuth)), IsNil) +func (s *provisioningSuite) testProvisionRecreateEK(c *C, full bool) { + lockoutAuthBytes := testutil.DecodeHexString(c, "c04c673608034f3f6fdd1b2ba752daf8ae5fa9ca5d7fc21b5f5f1dbdd9427ceaa6f35c0d0f98c2926a0b029296f06cc5a5a368364e3d07c6d6169c9443a70c3c") + var lockoutAuthData []byte + + c.Check(s.TPM().EnsureProvisioned( + WithLockoutAuthValue(nil), + WithProvisionNewLockoutAuthValue(bytes.NewReader(lockoutAuthBytes), func(data []byte) error { + lockoutAuthData = data + return nil + }), + ), 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.TPM().LockoutHandleContext().SetAuthValue(lockoutAuthValue(c, s.TPM(), lockoutAuthBytes)) s.HierarchyChangeAuth(c, tpm2.HandleLockout, nil) }) @@ -428,7 +792,11 @@ 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(lockoutAuth), WithProvisionNewLockoutAuthValue(lockoutAuth)), IsNil) + var opts []EnsureProvisionedOption + if full { + opts = append(opts, WithLockoutAuthData(lockoutAuthData)) + } + c.Check(s.TPM().EnsureProvisioned(opts...), IsNil) s.validateEK(c) s.validateSRK(c) @@ -440,22 +808,29 @@ func (s *provisioningSuite) testProvisionRecreateEK(c *C, mode ProvisionMode) { } func (s *provisioningSuite) TestRecreateEKFull(c *C) { - s.testProvisionRecreateEK(c, ProvisionModeFull) + s.testProvisionRecreateEK(c, true) } func (s *provisioningSuite) TestRecreateEKWithoutLockout(c *C) { - s.testProvisionRecreateEK(c, ProvisionModeWithoutLockout) + s.testProvisionRecreateEK(c, false) } -func (s *provisioningSuite) testProvisionRecreateSRK(c *C, mode ProvisionMode) { - lockoutAuth := []byte("1234") - - c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthValue(lockoutAuth), WithProvisionNewLockoutAuthValue(lockoutAuth)), IsNil) +func (s *provisioningSuite) testProvisionRecreateSRK(c *C, full bool) { + lockoutAuthBytes := testutil.DecodeHexString(c, "c04c673608034f3f6fdd1b2ba752daf8ae5fa9ca5d7fc21b5f5f1dbdd9427ceaa6f35c0d0f98c2926a0b029296f06cc5a5a368364e3d07c6d6169c9443a70c3c") + var lockoutAuthData []byte + + c.Check(s.TPM().EnsureProvisioned( + WithLockoutAuthValue(nil), + WithProvisionNewLockoutAuthValue(bytes.NewReader(lockoutAuthBytes), func(data []byte) error { + lockoutAuthData = data + return nil + }), + ), 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.TPM().LockoutHandleContext().SetAuthValue(lockoutAuthValue(c, s.TPM(), lockoutAuthBytes)) s.HierarchyChangeAuth(c, tpm2.HandleLockout, nil) }) @@ -464,7 +839,11 @@ 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(lockoutAuth), WithProvisionNewLockoutAuthValue(lockoutAuth)), IsNil) + var opts []EnsureProvisionedOption + if full { + opts = append(opts, WithLockoutAuthData(lockoutAuthData)) + } + c.Check(s.TPM().EnsureProvisioned(opts...), IsNil) s.validateEK(c) s.validateSRK(c) @@ -475,11 +854,11 @@ func (s *provisioningSuite) testProvisionRecreateSRK(c *C, mode ProvisionMode) { } func (s *provisioningSuite) TestProvisionRecreateSRKFull(c *C) { - s.testProvisionRecreateSRK(c, ProvisionModeFull) + s.testProvisionRecreateSRK(c, true) } func (s *provisioningSuite) TestProvisionRecreateSRKWithoutLockout(c *C) { - s.testProvisionRecreateSRK(c, ProvisionModeWithoutLockout) + s.testProvisionRecreateSRK(c, false) } func (s *provisioningSuite) TestProvisionWithEndorsementAuth(c *C) { @@ -500,7 +879,7 @@ func (s *provisioningSuite) TestProvisionWithOwnerAuth(c *C) { s.validateSRK(c) } -func (s *provisioningSuite) testProvisionWithCustomSRKTemplate(c *C, mode ProvisionMode) { +func (s *provisioningSuite) testProvisionWithCustomSRKTemplate(c *C, clear bool) { template := tpm2.Public{ Type: tpm2.ObjectTypeRSA, NameAlg: tpm2.HashAlgorithmSHA256, @@ -515,7 +894,12 @@ 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(nil), WithCustomSRKTemplate(&template)), IsNil) + + opts := []EnsureProvisionedOption{WithLockoutAuthValue(nil), WithCustomSRKTemplate(&template)} + if clear { + opts = append(opts, WithClearBeforeProvision()) + } + c.Check(s.TPM().EnsureProvisioned(opts...), IsNil) s.validatePrimaryKeyAgainstTemplate(c, tpm2.HandleOwner, tcg.SRKHandle, &template) @@ -532,11 +916,11 @@ func (s *provisioningSuite) testProvisionWithCustomSRKTemplate(c *C, mode Provis } func (s *provisioningSuite) TestProvisionWithCustomSRKTemplateClear(c *C) { - s.testProvisionWithCustomSRKTemplate(c, ProvisionModeClear) + s.testProvisionWithCustomSRKTemplate(c, true) } func (s *provisioningSuite) TestProvisionWithCustomSRKTemplateFull(c *C) { - s.testProvisionWithCustomSRKTemplate(c, ProvisionModeFull) + s.testProvisionWithCustomSRKTemplate(c, false) } func (s *provisioningSuite) TestProvisionWithInvalidCustomSRKTemplate(c *C) { @@ -558,7 +942,10 @@ func (s *provisioningSuite) TestProvisionWithInvalidCustomSRKTemplate(c *C) { c.Check(err, ErrorMatches, "supplied SRK template is not valid for a parent key") } -func (s *provisioningSuite) testProvisionDefaultPreservesCustomSRKTemplate(c *C, mode ProvisionMode) { +func (s *provisioningSuite) testProvisionDefaultPreservesCustomSRKTemplate(c *C, full bool) { + lockoutAuthBytes := testutil.DecodeHexString(c, "c04c673608034f3f6fdd1b2ba752daf8ae5fa9ca5d7fc21b5f5f1dbdd9427ceaa6f35c0d0f98c2926a0b029296f06cc5a5a368364e3d07c6d6169c9443a70c3c") + var lockoutAuthData []byte + template := tpm2.Public{ Type: tpm2.ObjectTypeRSA, NameAlg: tpm2.HashAlgorithmSHA256, @@ -574,13 +961,19 @@ func (s *provisioningSuite) testProvisionDefaultPreservesCustomSRKTemplate(c *C, KeyBits: 2048, Exponent: 0}}} - lockoutAuth := []byte("1234") - c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthValue(nil), WithProvisionNewLockoutAuthValue(lockoutAuth), WithCustomSRKTemplate(&template)), IsNil) + c.Check(s.TPM().EnsureProvisioned( + WithLockoutAuthValue(nil), + WithProvisionNewLockoutAuthValue(bytes.NewReader(lockoutAuthBytes), func(data []byte) error { + lockoutAuthData = data + return nil + }), + 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.TPM().LockoutHandleContext().SetAuthValue(lockoutAuthValue(c, s.TPM(), lockoutAuthBytes)) s.HierarchyChangeAuth(c, tpm2.HandleLockout, nil) }) @@ -588,17 +981,21 @@ 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(lockoutAuth), WithProvisionNewLockoutAuthValue(lockoutAuth)), IsNil) + var opts []EnsureProvisionedOption + if full { + opts = append(opts, WithLockoutAuthData(lockoutAuthData)) + } + c.Check(s.TPM().EnsureProvisioned(opts...), IsNil) s.validatePrimaryKeyAgainstTemplate(c, tpm2.HandleOwner, tcg.SRKHandle, &template) } func (s *provisioningSuite) TestProvisionDefaultPreservesCustomSRKTemplateFull(c *C) { - s.testProvisionDefaultPreservesCustomSRKTemplate(c, ProvisionModeFull) + s.testProvisionDefaultPreservesCustomSRKTemplate(c, true) } func (s *provisioningSuite) TestProvisionDefaultPreservesCustomSRKTemplateWithoutLockout(c *C) { - s.testProvisionDefaultPreservesCustomSRKTemplate(c, ProvisionModeWithoutLockout) + s.testProvisionDefaultPreservesCustomSRKTemplate(c, false) } func (s *provisioningSuite) TestProvisionDefaultClearRemovesCustomSRKTemplate(c *C) { @@ -669,3 +1066,36 @@ func (s *provisioningSuite) TestProvisionWithCustomSRKTemplateOverwritesExisting c.Check(err, IsNil) c.Check(tmplBytes, DeepEquals, mu.MustMarshalToBytes(&template2)) } + +func (s *provisioningSuite) TestProvisionNewLockoutAuthValueWithoutPolicySupport(c *C) { + // Test with a TPM that doesn't support TPM_CAP_AUTH_POLICIES + s.TPMTest.TPMTest.Transport.ResponseIntercept = func(cmdCode tpm2.CommandCode, cmdHandle tpm2.HandleList, cmdAuthArea []tpm2.AuthCommand, cpBytes []byte, rsp *bytes.Buffer) { + if cmdCode != tpm2.CommandGetCapability { + return + } + + // Unpack the command parameters + var capability tpm2.Capability + var property uint32 + var propertyCount uint32 + _, err := mu.UnmarshalFromBytes(cpBytes, &capability, &property, &propertyCount) + c.Assert(err, IsNil) + if capability != tpm2.CapabilityAuthPolicies { + return + } + + // Return a TPM_RC_VALUE + TPM_RC_P + TPM_RC_1 error + rsp.Reset() + c.Check(tpm2.WriteResponsePacket(rsp, tpm2.ResponseValue+tpm2.ResponseP+tpm2.ResponseIndex1, nil, nil, nil), IsNil) + } + + origValue := []byte("1234") + data := s.makeLockoutAuthData(c, &LockoutAuthParams{ + AuthValue: origValue, + }) + s.HierarchyChangeAuth(c, tpm2.HandleLockout, origValue) + + err := s.TPM().EnsureProvisioned(WithLockoutAuthData(data), WithProvisionNewLockoutAuthValue(rand.Reader, func(_ []byte) error { return nil })) + c.Check(err, ErrorMatches, `cannot set new lockout hierarchy authorization value: updating the authorization parameters for the lockout hierarchy is not supported`) + c.Check(errors.Is(err, ErrLockoutAuthUpdateUnsupported), testutil.IsTrue) +} diff --git a/tpm2/tpm_test.go b/tpm2/tpm_test.go index 5be75687..2b3267e0 100644 --- a/tpm2/tpm_test.go +++ b/tpm2/tpm_test.go @@ -20,7 +20,10 @@ package tpm2_test import ( + "bytes" + "crypto/rand" "crypto/x509" + "io" "github.com/canonical/go-tpm2" "github.com/canonical/go-tpm2/objectutil" @@ -104,9 +107,10 @@ 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(WithLockoutAuthValue(nil), WithProvisionNewLockoutAuthValue([]byte("1234"))), IsNil) + lockoutAuth := bytes.NewBuffer(nil) + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthValue(nil), WithProvisionNewLockoutAuthValue(io.TeeReader(rand.Reader, lockoutAuth), func(_ []byte) error { return nil })), IsNil) s.AddCleanup(func() { - s.TPM().LockoutHandleContext().SetAuthValue([]byte("1234")) + s.TPM().LockoutHandleContext().SetAuthValue(lockoutAuth.Bytes()) c.Check(s.TPM().HierarchyChangeAuth(s.TPM().LockoutHandleContext(), nil, nil), IsNil) }) c.Check(s.TPM().LockoutAuthSet(), testutil.IsTrue) From e6e8742e53394757dc72aca8a5e01b28f39303f1 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 23 Apr 2026 09:13:23 +0100 Subject: [PATCH 2/8] Fix a build failure --- tools/gen-compattest-data/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/gen-compattest-data/main.go b/tools/gen-compattest-data/main.go index 3ef9989a..375e4b1d 100644 --- a/tools/gen-compattest-data/main.go +++ b/tools/gen-compattest-data/main.go @@ -167,7 +167,7 @@ func run() int { } defer tpm.Close() - if err := tpm.EnsureProvisioned(secboot_tpm2.WithProvisionNewLockoutAuthValue([]byte("1234"))); err != nil { + if err := tpm.EnsureProvisioned(secboot_tpm2.WithProvisionNewLockoutAuthValue(rand.Reader, func(_ []byte) error { return nil })); err != nil { fmt.Fprintf(os.Stderr, "Cannot provision TPM: %v\n", err) return 1 } From 80fc58be428aefc4b2f7eb453842eb818ba48091 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 23 Apr 2026 18:48:27 +0100 Subject: [PATCH 3/8] Rename WithProvisionNewLockoutAuthValue to WithProvisionNewLockoutAuthData --- tools/gen-compattest-data/main.go | 2 +- tpm2/lockoutauth.go | 14 +++++++------- tpm2/provisioning.go | 10 +++++----- tpm2/provisioning_test.go | 26 +++++++++++++------------- tpm2/tpm_test.go | 2 +- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/tools/gen-compattest-data/main.go b/tools/gen-compattest-data/main.go index 375e4b1d..c15c8e1c 100644 --- a/tools/gen-compattest-data/main.go +++ b/tools/gen-compattest-data/main.go @@ -167,7 +167,7 @@ func run() int { } defer tpm.Close() - if err := tpm.EnsureProvisioned(secboot_tpm2.WithProvisionNewLockoutAuthValue(rand.Reader, func(_ []byte) error { return nil })); err != nil { + if err := tpm.EnsureProvisioned(secboot_tpm2.WithProvisionNewLockoutAuthData(rand.Reader, func(_ []byte) error { return nil })); err != nil { fmt.Fprintf(os.Stderr, "Cannot provision TPM: %v\n", err) return 1 } diff --git a/tpm2/lockoutauth.go b/tpm2/lockoutauth.go index e169f0f3..ca9cb038 100644 --- a/tpm2/lockoutauth.go +++ b/tpm2/lockoutauth.go @@ -42,23 +42,23 @@ var ( // ErrInvalidLockoutAuthPolicy is returned from [Connection.ResetDictionaryAttackLock] or // [Connection.EnsureProvisioned] if the authorization policy for the lockout hierarchy is // not consistent with the supplied data. [Connection.EnsureProvisioned] should be called - // with the [WithProvisionNewLockoutAuthValue] option in order to fix this. + // with the [WithProvisionNewLockoutAuthData] option in order to fix this. ErrInvalidLockoutAuthPolicy = errors.New("the authorization policy for the lockout hierarchy is inconsistent with the supplied data") // ErrLockoutAuthNotInitialized is returned from [Connection.ResetDictionaryAttackLock] if // the authorization parameters for the lockout hierarchy need to be initialized. - // [Connection.EnsureProvisioned] should be called with the [WithProvisionNewLockoutAuthValue] + // [Connection.EnsureProvisioned] should be called with the [WithProvisionNewLockoutAuthData] // option in order to fix this. ErrLockoutAuthNotInitialized = errors.New("the authorization parameters for the lockout hierarchy are not fully initialized") // ErrLockoutAuthUpdateInterrupted is returned from [Connection.ResetDictionaryAttackLock] or // [Connection.EnsureProvisioned] if a previous update to the authorization value for the lockout // hierarchy was interrupted. [Connection.EnsureProvisioned] should be called with the - // [WithProvisionNewLockoutAuthValue] option in order to fix this. + // [WithProvisionNewLockoutAuthData] option in order to fix this. ErrLockoutAuthUpdateInterrupted = errors.New("a previous attempt to update the authorization parameters for the lockout hierarchy was interrupted") // ErrLockoutAuthUpdateUnsupported is returned from [Connection.EnsureProvisioned] when called - // with the [WithProvisionNewLockoutAuthValue] option if the authorization value for the + // with the [WithProvisionNewLockoutAuthData] option if the authorization value for the // lockout hierarchy is already set and the system does not support updating it. ErrLockoutAuthUpdateUnsupported = errors.New("updating the authorization parameters for the lockout hierarchy is not supported") @@ -643,7 +643,7 @@ func (t *Connection) resetDictionaryAttackLockImpl(params *lockoutAuthParams) er // ResetDictionaryAttackLock resets the TPM's dictionary attack counter using the // TPM2_DictionaryAttackLockReset command. The caller supplies authorization data for the TPM's // lockout hierarchy which will have been supplied by a previous call to -// [Connection.EnsureProvisioned] with the [WithProvisionNewLockoutAuthValue] option. +// [Connection.EnsureProvisioned] with the [WithProvisionNewLockoutAuthData] option. // // If the supplied authorization data is invalid, a *[InvalidLockoutAuthDataError] error will // be returned. @@ -661,9 +661,9 @@ func (t *Connection) resetDictionaryAttackLockImpl(params *lockoutAuthParams) er // If the authorization policy for the TPM's lockout hierarchy is invalid, an // [ErrInvalidLockoutAuthPolicy] error will be returned. // -// If a previous call to [Connection.EnsureProvisioned] with the [WithProvisionNewLockoutAuthValue] +// If a previous call to [Connection.EnsureProvisioned] with the [WithProvisionNewLockoutAuthData] // option was interrupted, this may return a [ErrLockoutAuthUpdateInterrupted] error. In this case, -// Connection.EnsureProvisioned] should be called again with the [WithProvisionNewLockoutAuthValue] +// Connection.EnsureProvisioned] should be called again with the [WithProvisionNewLockoutAuthData] // option in order to complete the previous operation. func (t *Connection) ResetDictionaryAttackLock(lockoutAuthData []byte) error { var params *lockoutAuthParams diff --git a/tpm2/provisioning.go b/tpm2/provisioning.go index 06252cc9..dd3a0f5b 100644 --- a/tpm2/provisioning.go +++ b/tpm2/provisioning.go @@ -230,7 +230,7 @@ func WithLockoutAuthValue(authValue []byte) EnsureProvisionedOption { // 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] with the [WithProvisionNewLockoutAuthValue] option. +// to [Connection.EnsureProvisioned] with the [WithProvisionNewLockoutAuthData] option. // // If the data contains the wrong authorization value, the lockout hierarchy will become unavailable for // the pre-programmed recovery time. @@ -255,14 +255,14 @@ func WithClearBeforeProvision() EnsureProvisionedOption { } } -// WithProvisionNewLockoutAuthValue tells [Connection.EnsureProvisioned] to set or update the authorization +// WithProvisionNewLockoutAuthData tells [Connection.EnsureProvisioned] to set or update the authorization // value and authorization policy for the lockout hierarchy. The caller supplies a callback which is used // to store the authorization data to persistent storage. This will be called multiple times during the // update. // // This option will also resume a previously interrupted update, as long as the most recent authorization // data is supplied to [WithLockoutAuthData]. -func WithProvisionNewLockoutAuthValue(rand io.Reader, syncData func([]byte) error) EnsureProvisionedOption { +func WithProvisionNewLockoutAuthData(rand io.Reader, syncData func([]byte) error) EnsureProvisionedOption { return func(p *ensureProvisionedParams) { p.newLockoutAuthValue = true p.newLockoutAuthValueReader = rand @@ -301,7 +301,7 @@ func WithCustomSRKTemplate(template *tpm2.Public) EnsureProvisionedOption { // // 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 or updated if the [WithProvisionNewLockoutAuthValue] option is supplied. +// lockout hierarchy will be set or updated if the [WithProvisionNewLockoutAuthData] option is supplied. // // 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 @@ -327,7 +327,7 @@ func (t *Connection) EnsureProvisioned(options ...EnsureProvisionedOption) error case params.mode == provisionModeClear && params.lockoutAuthParams == nil: return errors.New("WithClearBeforeProvision requires WithLockoutAuthParams or WithLockoutAuthData") case params.lockoutAuthParams == nil && params.newLockoutAuthValue: - return errors.New("WithProvisionNewLockoutAuthValue requires WithLockoutAuthParams or WithLockoutAuthData") + return errors.New("WithProvisionNewLockoutAuthData requires WithLockoutAuthParams or WithLockoutAuthData") case params.lockoutAuthParams == nil: params.mode = provisionModeWithoutLockout } diff --git a/tpm2/provisioning_test.go b/tpm2/provisioning_test.go index 96607ef8..788c8732 100644 --- a/tpm2/provisioning_test.go +++ b/tpm2/provisioning_test.go @@ -124,7 +124,7 @@ func (s *provisioningSimulatorSuite) testProvisionNewTPM(c *C, data *testProvisi return nil } - opts := []EnsureProvisionedOption{WithLockoutAuthValue(nil), WithProvisionNewLockoutAuthValue(bytes.NewReader(data.lockoutAuthBytes), syncLockoutAuthData)} + opts := []EnsureProvisionedOption{WithLockoutAuthValue(nil), WithProvisionNewLockoutAuthData(bytes.NewReader(data.lockoutAuthBytes), syncLockoutAuthData)} if data.clear { opts = append(opts, WithClearBeforeProvision()) } @@ -355,7 +355,7 @@ func (s *provisioningSuite) TestProvisionWithLockoutAuthDataNoAuthPolicies(c *C) } func (s *provisioningSuite) TestProvisionResumeNewLockoutAuthValue1(c *C) { - // Test resuming with WithProvisionNewLockoutAuthValue after a previous attempt was interrupted + // Test resuming with WithProvisionNewLockoutAuthData after a previous attempt was interrupted // after prepare origValue := []byte("1234") policyDigest, policy1 := s.newDefaultLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256) @@ -385,7 +385,7 @@ func (s *provisioningSuite) TestProvisionResumeNewLockoutAuthValue1(c *C) { return nil } - c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthData(data), WithProvisionNewLockoutAuthValue(bytes.NewReader(nil), syncLockoutAuthData)), IsNil) + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthData(data), WithProvisionNewLockoutAuthData(bytes.NewReader(nil), syncLockoutAuthData)), IsNil) s.AddCleanup(func() { // github.com/canonical/go-tpm2/testutil cannot restore this because // EnsureProvisioned uses command parameter encryption. We have to do @@ -423,7 +423,7 @@ func (s *provisioningSuite) TestProvisionResumeNewLockoutAuthValue1(c *C) { } func (s *provisioningSuite) TestProvisionResumeNewLockoutAuthValue2(c *C) { - // Test resuming with WithProvisionNewLockoutAuthValue after a previous attempt was interrupted + // Test resuming with WithProvisionNewLockoutAuthData after a previous attempt was interrupted // after setNewAuthValuePolicy origValue := []byte("1234") policyDigest, policy := s.newUpdateAuthValueLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256, origValue) @@ -450,7 +450,7 @@ func (s *provisioningSuite) TestProvisionResumeNewLockoutAuthValue2(c *C) { return nil } - c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthData(data), WithProvisionNewLockoutAuthValue(bytes.NewReader(nil), syncLockoutAuthData)), IsNil) + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthData(data), WithProvisionNewLockoutAuthData(bytes.NewReader(nil), syncLockoutAuthData)), IsNil) s.AddCleanup(func() { // github.com/canonical/go-tpm2/testutil cannot restore this because // EnsureProvisioned uses command parameter encryption. We have to do @@ -488,7 +488,7 @@ func (s *provisioningSuite) TestProvisionResumeNewLockoutAuthValue2(c *C) { } func (s *provisioningSuite) TestProvisionAfterInterruptedNewLockoutAuthValue2(c *C) { - // Test that we get an appropriate error if a previous call with WithProvisionNewLockoutAuthValue + // Test that we get an appropriate error if a previous call with WithProvisionNewLockoutAuthData // was interrupted after setNewAuthValuePolicy. origValue := []byte("1234") policyDigest, policy := s.newUpdateAuthValueLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256, origValue) @@ -509,7 +509,7 @@ func (s *provisioningSuite) TestProvisionAfterInterruptedNewLockoutAuthValue2(c } func (s *provisioningSuite) TestProvisionResumeNewLockoutAuthValue3(c *C) { - // Test resuming with WithProvisionNewLockoutAuthValue after a previous attempt was interrupted + // Test resuming with WithProvisionNewLockoutAuthData after a previous attempt was interrupted // after setNewAuthValue lockoutAuthBytes := testutil.DecodeHexString(c, "c04c673608034f3f6fdd1b2ba752daf8ae5fa9ca5d7fc21b5f5f1dbdd9427ceaa6f35c0d0f98c2926a0b029296f06cc5a5a368364e3d07c6d6169c9443a70c3c") @@ -535,7 +535,7 @@ func (s *provisioningSuite) TestProvisionResumeNewLockoutAuthValue3(c *C) { return nil } - c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthData(data), WithProvisionNewLockoutAuthValue(bytes.NewReader(nil), syncLockoutAuthData)), IsNil) + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthData(data), WithProvisionNewLockoutAuthData(bytes.NewReader(nil), syncLockoutAuthData)), IsNil) s.AddCleanup(func() { // github.com/canonical/go-tpm2/testutil cannot restore this because // EnsureProvisioned uses command parameter encryption. We have to do @@ -573,7 +573,7 @@ func (s *provisioningSuite) TestProvisionResumeNewLockoutAuthValue3(c *C) { } func (s *provisioningSuite) TestProvisionAfterInterruptedNewLockoutAuthValue3(c *C) { - // Test that we get an appropriate error if a previous call with WithProvisionNewLockoutAuthValue + // Test that we get an appropriate error if a previous call with WithProvisionNewLockoutAuthData // was interrupted after setNewAuthValue. lockoutAuthBytes := testutil.DecodeHexString(c, "c04c673608034f3f6fdd1b2ba752daf8ae5fa9ca5d7fc21b5f5f1dbdd9427ceaa6f35c0d0f98c2926a0b029296f06cc5a5a368364e3d07c6d6169c9443a70c3c") @@ -773,7 +773,7 @@ func (s *provisioningSuite) testProvisionRecreateEK(c *C, full bool) { c.Check(s.TPM().EnsureProvisioned( WithLockoutAuthValue(nil), - WithProvisionNewLockoutAuthValue(bytes.NewReader(lockoutAuthBytes), func(data []byte) error { + WithProvisionNewLockoutAuthData(bytes.NewReader(lockoutAuthBytes), func(data []byte) error { lockoutAuthData = data return nil }), @@ -821,7 +821,7 @@ func (s *provisioningSuite) testProvisionRecreateSRK(c *C, full bool) { c.Check(s.TPM().EnsureProvisioned( WithLockoutAuthValue(nil), - WithProvisionNewLockoutAuthValue(bytes.NewReader(lockoutAuthBytes), func(data []byte) error { + WithProvisionNewLockoutAuthData(bytes.NewReader(lockoutAuthBytes), func(data []byte) error { lockoutAuthData = data return nil }), @@ -963,7 +963,7 @@ func (s *provisioningSuite) testProvisionDefaultPreservesCustomSRKTemplate(c *C, c.Check(s.TPM().EnsureProvisioned( WithLockoutAuthValue(nil), - WithProvisionNewLockoutAuthValue(bytes.NewReader(lockoutAuthBytes), func(data []byte) error { + WithProvisionNewLockoutAuthData(bytes.NewReader(lockoutAuthBytes), func(data []byte) error { lockoutAuthData = data return nil }), @@ -1095,7 +1095,7 @@ func (s *provisioningSuite) TestProvisionNewLockoutAuthValueWithoutPolicySupport }) s.HierarchyChangeAuth(c, tpm2.HandleLockout, origValue) - err := s.TPM().EnsureProvisioned(WithLockoutAuthData(data), WithProvisionNewLockoutAuthValue(rand.Reader, func(_ []byte) error { return nil })) + err := s.TPM().EnsureProvisioned(WithLockoutAuthData(data), WithProvisionNewLockoutAuthData(rand.Reader, func(_ []byte) error { return nil })) c.Check(err, ErrorMatches, `cannot set new lockout hierarchy authorization value: updating the authorization parameters for the lockout hierarchy is not supported`) c.Check(errors.Is(err, ErrLockoutAuthUpdateUnsupported), testutil.IsTrue) } diff --git a/tpm2/tpm_test.go b/tpm2/tpm_test.go index 2b3267e0..e13cc224 100644 --- a/tpm2/tpm_test.go +++ b/tpm2/tpm_test.go @@ -108,7 +108,7 @@ func (s *tpmSuitePlatform) TestConnectionLockoutAuthSet(c *C) { // FullProvising of the TPM puts it in DA lockout mode lockoutAuth := bytes.NewBuffer(nil) - c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthValue(nil), WithProvisionNewLockoutAuthValue(io.TeeReader(rand.Reader, lockoutAuth), func(_ []byte) error { return nil })), IsNil) + c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthValue(nil), WithProvisionNewLockoutAuthData(io.TeeReader(rand.Reader, lockoutAuth), func(_ []byte) error { return nil })), IsNil) s.AddCleanup(func() { s.TPM().LockoutHandleContext().SetAuthValue(lockoutAuth.Bytes()) c.Check(s.TPM().HierarchyChangeAuth(s.TPM().LockoutHandleContext(), nil, nil), IsNil) From c01832dd066c55fad6b81792abe42cb0c97cc15b Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 23 Apr 2026 19:32:15 +0100 Subject: [PATCH 4/8] tpm2: Add WithUnconfiguredLockoutAuth --- tpm2/lockoutauth.go | 11 +++++++++-- tpm2/provisioning.go | 31 ++++++++++++++++++++++--------- tpm2/provisioning_test.go | 28 ++++++++++++++++++---------- 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/tpm2/lockoutauth.go b/tpm2/lockoutauth.go index ca9cb038..464a9084 100644 --- a/tpm2/lockoutauth.go +++ b/tpm2/lockoutauth.go @@ -45,6 +45,11 @@ var ( // with the [WithProvisionNewLockoutAuthData] option in order to fix this. ErrInvalidLockoutAuthPolicy = errors.New("the authorization policy for the lockout hierarchy is inconsistent with the supplied data") + // ErrLockoutAuthInitialized is returned from [Connection.EnsureProvisioned] when called with + // the [WithUnconfiguredLockoutAuth] option if the authorization parameters for the lockout + // hierarchy have already been configured. + ErrLockoutAuthInitialized = errors.New("the authorization parameters for the lockout hierarchy are already initialized") + // ErrLockoutAuthNotInitialized is returned from [Connection.ResetDictionaryAttackLock] if // the authorization parameters for the lockout hierarchy need to be initialized. // [Connection.EnsureProvisioned] should be called with the [WithProvisionNewLockoutAuthData] @@ -531,8 +536,10 @@ func (t *Connection) authorizeLockout(authParams *lockoutAuthParams, allowFallba if err != nil { return nil, nil, fmt.Errorf("cannot obtain value of TPM_PT_PERMANENT: %w", err) } - lockoutAuthSet := tpm2.PermanentAttributes(val)&tpm2.AttrLockoutAuthSet > 0 - if lockoutAuthSet { + switch tpm2.PermanentAttributes(val)&tpm2.AttrLockoutAuthSet > 0 { + case true && len(authParams.AuthValue) == 0: + return nil, nil, ErrLockoutAuthInitialized + case true: authValue = authParams.AuthValue } diff --git a/tpm2/provisioning.go b/tpm2/provisioning.go index dd3a0f5b..a8c33828 100644 --- a/tpm2/provisioning.go +++ b/tpm2/provisioning.go @@ -210,6 +210,17 @@ type ensureProvisionedParams struct { type EnsureProvisionedOption func(*ensureProvisionedParams) +// WithUnconfiguredLockoutAuth tells [Connection.EnsureProvisioned] that it can use the TPM's lockout +// hierarchy before the authorization parameters for the lockout hierarchy are configured. +func WithUnconfiguredLockoutAuth() EnsureProvisionedOption { + return func(p *ensureProvisionedParams) { + if p.lockoutAuthParams != nil || p.lockoutAuthParamsErr != nil { + panic("WithLockoutAuthValue incompatible with WithLockoutAuthData and WithUnconfiguredLockoutAuth") + } + p.lockoutAuthParams = new(lockoutAuthParams) + } +} + // 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] where an authorization value was chosen and supplied by the @@ -220,7 +231,7 @@ type EnsureProvisionedOption func(*ensureProvisionedParams) func WithLockoutAuthValue(authValue []byte) EnsureProvisionedOption { return func(p *ensureProvisionedParams) { if p.lockoutAuthParams != nil || p.lockoutAuthParamsErr != nil { - panic("WithLockoutAuthValue incompatible with WithLockoutAuthData") + panic("WithLockoutAuthValue incompatible with WithLockoutAuthData and WithUnconfiguredLockoutAuth") } p.lockoutAuthParams = &lockoutAuthParams{ AuthValue: authValue, @@ -237,7 +248,7 @@ func WithLockoutAuthValue(authValue []byte) EnsureProvisionedOption { func WithLockoutAuthData(data []byte) EnsureProvisionedOption { return func(p *ensureProvisionedParams) { if p.lockoutAuthParams != nil { - panic("WithLockoutAuthData incompatible with WithLockoutAuthValue") + panic("WithLockoutAuthData incompatible with WithLockoutAuthValue and WithUnconfiguredLockoutAuth") } p.lockoutAuthParamsErr = json.Unmarshal(data, &p.lockoutAuthParams) } @@ -299,18 +310,20 @@ 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 [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 or updated if the [WithProvisionNewLockoutAuthData] option is supplied. +// If the [WithLockoutAuthValue], [WithLockoutAuthData] or [WithUnconfiguredLockoutAuth] 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 or updated if the [WithProvisionNewLockoutAuthData] option is +// supplied. // -// If the [WithLockoutAuthValue] or [WithLockoutAuthData] option is supplied with the wrong value, then a [AuthFailError] error +// If the [WithLockoutAuthValue], [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. If it is supplied, then one of [WithLockoutAuthValue] or [WithLockoutAuthData] must be supplied. +// can currently be recovered. If it is supplied, then one of [WithLockoutAuthValue], [WithLockoutAuthData] or +// [WithUnconfiguredLockoutAuth] must be supplied. func (t *Connection) EnsureProvisioned(options ...EnsureProvisionedOption) error { params := &ensureProvisionedParams{ mode: provisionModeFull, @@ -325,9 +338,9 @@ func (t *Connection) EnsureProvisioned(options ...EnsureProvisionedOption) error case params.lockoutAuthParamsErr != nil: return &InvalidLockoutAuthDataError{err: params.lockoutAuthParamsErr} case params.mode == provisionModeClear && params.lockoutAuthParams == nil: - return errors.New("WithClearBeforeProvision requires WithLockoutAuthParams or WithLockoutAuthData") + return errors.New("WithClearBeforeProvision requires WithLockoutAuthParams, WithLockoutAuthData, or WithUnconfiguredLockoutAuth") case params.lockoutAuthParams == nil && params.newLockoutAuthValue: - return errors.New("WithProvisionNewLockoutAuthData requires WithLockoutAuthParams or WithLockoutAuthData") + return errors.New("WithProvisionNewLockoutAuthData requires WithLockoutAuthParams, WithLockoutAuthData, or WithUnconfiguredLockoutAuth") case params.lockoutAuthParams == nil: params.mode = provisionModeWithoutLockout } diff --git a/tpm2/provisioning_test.go b/tpm2/provisioning_test.go index 788c8732..c4144379 100644 --- a/tpm2/provisioning_test.go +++ b/tpm2/provisioning_test.go @@ -124,7 +124,7 @@ func (s *provisioningSimulatorSuite) testProvisionNewTPM(c *C, data *testProvisi return nil } - opts := []EnsureProvisionedOption{WithLockoutAuthValue(nil), WithProvisionNewLockoutAuthData(bytes.NewReader(data.lockoutAuthBytes), syncLockoutAuthData)} + opts := []EnsureProvisionedOption{WithUnconfiguredLockoutAuth(), WithProvisionNewLockoutAuthData(bytes.NewReader(data.lockoutAuthBytes), syncLockoutAuthData)} if data.clear { opts = append(opts, WithClearBeforeProvision()) } @@ -593,6 +593,14 @@ func (s *provisioningSuite) TestProvisionAfterInterruptedNewLockoutAuthValue3(c c.Check(err, ErrorMatches, `a previous attempt to update the authorization parameters for the lockout hierarchy was interrupted`) } +func (s *provisioningSuite) TestProvisionWithUnconfiguredLockoutAuthIfTPMAlreadyConfigured(c *C) { + s.HierarchyChangeAuth(c, tpm2.HandleLockout, []byte("1234")) + + err := s.TPM().EnsureProvisioned(WithUnconfiguredLockoutAuth()) + c.Check(err, Equals, ErrLockoutAuthInitialized) + c.Check(err, ErrorMatches, `the authorization parameters for the lockout hierarchy are already initialized`) +} + func (s *provisioningSimulatorSuite) TestProvisionTPMInLockout(c *C) { // Trip the DA logic by triggering an auth failure with a DA protected // resource. @@ -629,9 +637,9 @@ func (s *provisioningSimulatorSuite) testProvisionErrorHandling(c *C, mode Provi var opts []EnsureProvisionedOption switch mode { case ProvisionModeFull: - opts = append(opts, WithLockoutAuthValue(nil)) + opts = append(opts, WithUnconfiguredLockoutAuth()) case ProvisionModeClear: - opts = append(opts, WithLockoutAuthValue(nil), WithClearBeforeProvision()) + opts = append(opts, WithUnconfiguredLockoutAuth(), WithClearBeforeProvision()) } return s.TPM().EnsureProvisioned(opts...) } @@ -646,9 +654,9 @@ func (s *provisioningSuite) testProvisionErrorHandling(c *C, mode ProvisionMode) var opts []EnsureProvisionedOption switch mode { case ProvisionModeFull: - opts = append(opts, WithLockoutAuthValue(nil)) + opts = append(opts, WithUnconfiguredLockoutAuth()) case ProvisionModeClear: - opts = append(opts, WithLockoutAuthValue(nil), WithClearBeforeProvision()) + opts = append(opts, WithUnconfiguredLockoutAuth(), WithClearBeforeProvision()) } return s.TPM().EnsureProvisioned(opts...) } @@ -772,7 +780,7 @@ func (s *provisioningSuite) testProvisionRecreateEK(c *C, full bool) { var lockoutAuthData []byte c.Check(s.TPM().EnsureProvisioned( - WithLockoutAuthValue(nil), + WithUnconfiguredLockoutAuth(), WithProvisionNewLockoutAuthData(bytes.NewReader(lockoutAuthBytes), func(data []byte) error { lockoutAuthData = data return nil @@ -820,7 +828,7 @@ func (s *provisioningSuite) testProvisionRecreateSRK(c *C, full bool) { var lockoutAuthData []byte c.Check(s.TPM().EnsureProvisioned( - WithLockoutAuthValue(nil), + WithUnconfiguredLockoutAuth(), WithProvisionNewLockoutAuthData(bytes.NewReader(lockoutAuthBytes), func(data []byte) error { lockoutAuthData = data return nil @@ -895,7 +903,7 @@ func (s *provisioningSuite) testProvisionWithCustomSRKTemplate(c *C, clear bool) KeyBits: 2048, Exponent: 0}}} - opts := []EnsureProvisionedOption{WithLockoutAuthValue(nil), WithCustomSRKTemplate(&template)} + opts := []EnsureProvisionedOption{WithUnconfiguredLockoutAuth(), WithCustomSRKTemplate(&template)} if clear { opts = append(opts, WithClearBeforeProvision()) } @@ -962,7 +970,7 @@ func (s *provisioningSuite) testProvisionDefaultPreservesCustomSRKTemplate(c *C, Exponent: 0}}} c.Check(s.TPM().EnsureProvisioned( - WithLockoutAuthValue(nil), + WithUnconfiguredLockoutAuth(), WithProvisionNewLockoutAuthData(bytes.NewReader(lockoutAuthBytes), func(data []byte) error { lockoutAuthData = data return nil @@ -1016,7 +1024,7 @@ func (s *provisioningSuite) TestProvisionDefaultClearRemovesCustomSRKTemplate(c c.Check(s.TPM().EnsureProvisioned(WithCustomSRKTemplate(&template)), Equals, ErrTPMProvisioningRequiresLockout) s.validatePrimaryKeyAgainstTemplate(c, tpm2.HandleOwner, tcg.SRKHandle, &template) - c.Check(s.TPM().EnsureProvisioned(WithLockoutAuthValue(nil), WithClearBeforeProvision()), IsNil) + c.Check(s.TPM().EnsureProvisioned(WithUnconfiguredLockoutAuth(), WithClearBeforeProvision()), IsNil) s.validateSRK(c) } From cf4c7f14656dba8729bc089d6651b2755ee59809 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 23 Apr 2026 21:31:42 +0100 Subject: [PATCH 5/8] Fix some test failures --- tpm2/lockoutauth.go | 26 +++++++++++++++++++------- tpm2/provisioning.go | 6 ++++-- tpm2/provisioning_test.go | 9 ++++----- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/tpm2/lockoutauth.go b/tpm2/lockoutauth.go index 464a9084..30421139 100644 --- a/tpm2/lockoutauth.go +++ b/tpm2/lockoutauth.go @@ -96,6 +96,12 @@ type lockoutAuthParams struct { AuthPolicy *policyutil.Policy NewAuthValue tpm2.Auth NewAuthPolicy *policyutil.Policy + + // noAuthValue is a special value only set by WithUnconfiguredLockoutAuth so + // that we can return an appropriate error rather than triggering a lockout + // if the option is supplied when the lockout hierarchy already has an + // authorization value. + noAuthValue bool } func (p *lockoutAuthParams) MarshalJSON() ([]byte, error) { @@ -302,6 +308,10 @@ func newLockoutAuthValueUpdateStateMachine(rand io.Reader, tpm *Connection, auth return m, nil } +func (m *lockoutAuthValueUpdateStateMachine) authorizeLockout(allowFallbackToHMACSession bool, command tpm2.CommandCode) (session tpm2.SessionContext, done func(), err error) { + return m.tpm.authorizeLockout(m.authParams, allowFallbackToHMACSession, command, m.signedAuthorizer) +} + func (m *lockoutAuthValueUpdateStateMachine) signedAuthorizer(sessionAlg tpm2.HashAlgorithmId, sessionNonce tpm2.Nonce, authKey tpm2.Name, policyRef tpm2.Nonce) (*policyutil.PolicySignedAuthorization, error) { params := &policyutil.PolicySignedParams{ HashAlg: sessionAlg, @@ -353,7 +363,7 @@ func (m *lockoutAuthValueUpdateStateMachine) prepare() (lockoutAuthValueUpdateSt // temporary policy to update it's authorization value to a specific value, and to update it's policy // using it's authorization value. Note that it is not safe to use the authorization value yet though. func (m *lockoutAuthValueUpdateStateMachine) setNewAuthValuePolicy(policyAlg tpm2.HashAlgorithmId, policyDigest tpm2.Digest) (lockoutAuthValueUpdateStateMachineState, error) { - session, done, err := m.tpm.authorizeLockout(m.authParams, true, tpm2.CommandSetPrimaryPolicy, m.signedAuthorizer) + session, done, err := m.authorizeLockout(true, tpm2.CommandSetPrimaryPolicy) switch { case errors.Is(err, errLockoutAuthPolicyNotSupported): return m.setAuthValueWithoutPolicy, nil @@ -378,7 +388,7 @@ func (m *lockoutAuthValueUpdateStateMachine) setAuthValueWithoutPolicy() (lockou m.authParams.AuthPolicy = nil m.authParams.NewAuthPolicy = nil - session, done, err := m.tpm.authorizeLockout(m.authParams, false, tpm2.CommandHierarchyChangeAuth, m.signedAuthorizer) + session, done, err := m.authorizeLockout(false, tpm2.CommandHierarchyChangeAuth) if err != nil { return nil, err } @@ -395,6 +405,7 @@ func (m *lockoutAuthValueUpdateStateMachine) setAuthValueWithoutPolicy() (lockou m.authParams.AuthValue = m.authParams.NewAuthValue m.authParams.NewAuthValue = nil + m.authParams.noAuthValue = false return nil, nil } @@ -404,7 +415,7 @@ func (m *lockoutAuthValueUpdateStateMachine) setAuthValueWithoutPolicy() (lockou // On completion, the updated state can only be used to authorize the lockout hierarchy using the // temporary policy to set a new policy using an authorization value. func (m *lockoutAuthValueUpdateStateMachine) setNewAuthValue(policyAlg tpm2.HashAlgorithmId) (lockoutAuthValueUpdateStateMachineState, error) { - session, done, err := m.tpm.authorizeLockout(m.authParams, false, tpm2.CommandHierarchyChangeAuth, m.signedAuthorizer) + session, done, err := m.authorizeLockout(false, tpm2.CommandHierarchyChangeAuth) if err != nil { return nil, err } @@ -426,6 +437,7 @@ func (m *lockoutAuthValueUpdateStateMachine) setNewAuthValue(policyAlg tpm2.Hash m.authParams.AuthValue = m.authParams.NewAuthValue m.authParams.NewAuthValue = nil + m.authParams.noAuthValue = false var newPolicyDigest tpm2.Digest newPolicyDigest, m.authParams.NewAuthPolicy, err = newDefaultLockoutAuthPolicy(policyAlg) @@ -440,7 +452,7 @@ func (m *lockoutAuthValueUpdateStateMachine) setNewAuthValue(policyAlg tpm2.Hash // setDefaultPolicy sets the authorization policy for the lockout hierarchy to the default policy. func (m *lockoutAuthValueUpdateStateMachine) setDefaultPolicy(policyAlg tpm2.HashAlgorithmId, policyDigest tpm2.Digest) (lockoutAuthValueUpdateStateMachineState, error) { - session, done, err := m.tpm.authorizeLockout(m.authParams, false, tpm2.CommandSetPrimaryPolicy, m.signedAuthorizer) + session, done, err := m.authorizeLockout(false, tpm2.CommandSetPrimaryPolicy) if err != nil { return nil, err } @@ -536,10 +548,10 @@ func (t *Connection) authorizeLockout(authParams *lockoutAuthParams, allowFallba if err != nil { return nil, nil, fmt.Errorf("cannot obtain value of TPM_PT_PERMANENT: %w", err) } - switch tpm2.PermanentAttributes(val)&tpm2.AttrLockoutAuthSet > 0 { - case true && len(authParams.AuthValue) == 0: + switch lockoutAuthSet := tpm2.PermanentAttributes(val)&tpm2.AttrLockoutAuthSet > 0; { + case lockoutAuthSet && authParams.noAuthValue: return nil, nil, ErrLockoutAuthInitialized - case true: + case lockoutAuthSet: authValue = authParams.AuthValue } diff --git a/tpm2/provisioning.go b/tpm2/provisioning.go index a8c33828..94ccf71b 100644 --- a/tpm2/provisioning.go +++ b/tpm2/provisioning.go @@ -217,7 +217,9 @@ func WithUnconfiguredLockoutAuth() EnsureProvisionedOption { if p.lockoutAuthParams != nil || p.lockoutAuthParamsErr != nil { panic("WithLockoutAuthValue incompatible with WithLockoutAuthData and WithUnconfiguredLockoutAuth") } - p.lockoutAuthParams = new(lockoutAuthParams) + p.lockoutAuthParams = &LockoutAuthParams{ + noAuthValue: true, + } } } @@ -337,7 +339,7 @@ func (t *Connection) EnsureProvisioned(options ...EnsureProvisionedOption) error 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: + case params.lockoutAuthParams == nil && params.mode == provisionModeClear: return errors.New("WithClearBeforeProvision requires WithLockoutAuthParams, WithLockoutAuthData, or WithUnconfiguredLockoutAuth") case params.lockoutAuthParams == nil && params.newLockoutAuthValue: return errors.New("WithProvisionNewLockoutAuthData requires WithLockoutAuthParams, WithLockoutAuthData, or WithUnconfiguredLockoutAuth") diff --git a/tpm2/provisioning_test.go b/tpm2/provisioning_test.go index c4144379..3a2f0253 100644 --- a/tpm2/provisioning_test.go +++ b/tpm2/provisioning_test.go @@ -116,7 +116,6 @@ func (s *provisioningSimulatorSuite) testProvisionNewTPM(c *C, data *testProvisi expectedLockoutAuthData := data.expectedLockoutAuthData syncLockoutAuthData := func(data []byte) error { - c.Logf("%s", string(data)) c.Assert(expectedLockoutAuthData, Not(HasLen), 0) expected := expectedLockoutAuthData[0] expectedLockoutAuthData = expectedLockoutAuthData[1:] @@ -637,9 +636,9 @@ func (s *provisioningSimulatorSuite) testProvisionErrorHandling(c *C, mode Provi var opts []EnsureProvisionedOption switch mode { case ProvisionModeFull: - opts = append(opts, WithUnconfiguredLockoutAuth()) + opts = append(opts, WithLockoutAuthValue(nil)) case ProvisionModeClear: - opts = append(opts, WithUnconfiguredLockoutAuth(), WithClearBeforeProvision()) + opts = append(opts, WithLockoutAuthValue(nil), WithClearBeforeProvision()) } return s.TPM().EnsureProvisioned(opts...) } @@ -654,9 +653,9 @@ func (s *provisioningSuite) testProvisionErrorHandling(c *C, mode ProvisionMode) var opts []EnsureProvisionedOption switch mode { case ProvisionModeFull: - opts = append(opts, WithUnconfiguredLockoutAuth()) + opts = append(opts, WithLockoutAuthValue(nil)) case ProvisionModeClear: - opts = append(opts, WithUnconfiguredLockoutAuth(), WithClearBeforeProvision()) + opts = append(opts, WithLockoutAuthValue(nil), WithClearBeforeProvision()) } return s.TPM().EnsureProvisioned(opts...) } From cfb6e6cd7d9ed0d0afe6294efbad8ab2919ad7c8 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 23 Apr 2026 21:39:35 +0100 Subject: [PATCH 6/8] tpm2: Merge ErrInvalidLockoutAuthPolicy and ErrLockoutAuthUpdateInterrupted These are merged into a new error: ErrLockoutAuthInvalid. There's no point in having 2 different errors for conditions that require the same resolution (calling EnsureProvisioned again). --- tpm2/lockoutauth.go | 27 +++++++++++---------------- tpm2/lockoutauth_test.go | 12 ++++++------ tpm2/provisioning.go | 2 +- tpm2/provisioning_test.go | 8 ++++---- 4 files changed, 22 insertions(+), 27 deletions(-) diff --git a/tpm2/lockoutauth.go b/tpm2/lockoutauth.go index 30421139..bf83ede8 100644 --- a/tpm2/lockoutauth.go +++ b/tpm2/lockoutauth.go @@ -39,29 +39,24 @@ import ( ) var ( - // ErrInvalidLockoutAuthPolicy is returned from [Connection.ResetDictionaryAttackLock] or - // [Connection.EnsureProvisioned] if the authorization policy for the lockout hierarchy is - // not consistent with the supplied data. [Connection.EnsureProvisioned] should be called - // with the [WithProvisionNewLockoutAuthData] option in order to fix this. - ErrInvalidLockoutAuthPolicy = errors.New("the authorization policy for the lockout hierarchy is inconsistent with the supplied data") - // ErrLockoutAuthInitialized is returned from [Connection.EnsureProvisioned] when called with // the [WithUnconfiguredLockoutAuth] option if the authorization parameters for the lockout // hierarchy have already been configured. ErrLockoutAuthInitialized = errors.New("the authorization parameters for the lockout hierarchy are already initialized") + // ErrLockoutAuthInvalid is returned from [Connection.ResetDictionaryAttackLock] or + // [Connection.EnsureProvisioned] if the authorization parameters for the lockout hierarchy + // are invalid for the required use or not consistent with the supplied data. + // [Connection.EnsureProvisioned] should be called with the [WithProvisionNewLockoutAuthData] + // option in order to fix this. + ErrLockoutAuthInvalid = errors.New("the authorization parameters for the lockout hierarchy are invalid") + // ErrLockoutAuthNotInitialized is returned from [Connection.ResetDictionaryAttackLock] if // the authorization parameters for the lockout hierarchy need to be initialized. // [Connection.EnsureProvisioned] should be called with the [WithProvisionNewLockoutAuthData] // option in order to fix this. ErrLockoutAuthNotInitialized = errors.New("the authorization parameters for the lockout hierarchy are not fully initialized") - // ErrLockoutAuthUpdateInterrupted is returned from [Connection.ResetDictionaryAttackLock] or - // [Connection.EnsureProvisioned] if a previous update to the authorization value for the lockout - // hierarchy was interrupted. [Connection.EnsureProvisioned] should be called with the - // [WithProvisionNewLockoutAuthData] option in order to fix this. - ErrLockoutAuthUpdateInterrupted = errors.New("a previous attempt to update the authorization parameters for the lockout hierarchy was interrupted") - // ErrLockoutAuthUpdateUnsupported is returned from [Connection.EnsureProvisioned] when called // with the [WithProvisionNewLockoutAuthData] option if the authorization value for the // lockout hierarchy is already set and the system does not support updating it. @@ -535,7 +530,7 @@ func (t *Connection) authorizeLockout(authParams *lockoutAuthParams, allowFallba } } if policy == nil && !allowFallbackToHMACSession { - return nil, nil, ErrInvalidLockoutAuthPolicy + return nil, nil, ErrLockoutAuthInvalid } } } @@ -586,7 +581,7 @@ func (t *Connection) authorizeLockout(authParams *lockoutAuthParams, allowFallba switch { case errors.As(err, &pe): // If a path cannot be selected, assume that a previous update was interrutped. - return nil, nil, ErrLockoutAuthUpdateInterrupted + return nil, nil, ErrLockoutAuthInvalid case err != nil: // Treat any other error as invalid auth data. return nil, nil, &InvalidLockoutAuthDataError{err: fmt.Errorf("cannot execute policy: %w", err)} @@ -678,10 +673,10 @@ func (t *Connection) resetDictionaryAttackLockImpl(params *lockoutAuthParams) er // [ErrTPMLockout] error will be returned. // // If the authorization policy for the TPM's lockout hierarchy is invalid, an -// [ErrInvalidLockoutAuthPolicy] error will be returned. +// [ErrLockoutAuthInvalid] error will be returned. // // If a previous call to [Connection.EnsureProvisioned] with the [WithProvisionNewLockoutAuthData] -// option was interrupted, this may return a [ErrLockoutAuthUpdateInterrupted] error. In this case, +// option was interrupted, this may return a [ErrLockoutAuthInvalid] error. In this case, // Connection.EnsureProvisioned] should be called again with the [WithProvisionNewLockoutAuthData] // option in order to complete the previous operation. func (t *Connection) ResetDictionaryAttackLock(lockoutAuthData []byte) error { diff --git a/tpm2/lockoutauth_test.go b/tpm2/lockoutauth_test.go index 49fb3dc1..7e83ed78 100644 --- a/tpm2/lockoutauth_test.go +++ b/tpm2/lockoutauth_test.go @@ -703,8 +703,8 @@ func (s *lockoutauthSuite) TestResetDictionaryAttackLockInterruptedAuthValueRota NewAuthValue: testutil.DecodeHexString(c, "db82cbebd10ebd831b48ff8ae7275a23029074ba622c0416d97cd34dd38d8186"), }), }) - c.Check(err, ErrorMatches, `a previous attempt to update the authorization parameters for the lockout hierarchy was interrupted`) - c.Check(err, Equals, ErrLockoutAuthUpdateInterrupted) + c.Check(err, ErrorMatches, `the authorization parameters for the lockout hierarchy are invalid`) + c.Check(err, Equals, ErrLockoutAuthInvalid) } func (s *lockoutauthSuite) TestResetDictionaryAttackLockInterruptedAuthValueRotation2(c *C) { @@ -722,8 +722,8 @@ func (s *lockoutauthSuite) TestResetDictionaryAttackLockInterruptedAuthValueRota NewAuthPolicy: policy2, }), }) - c.Check(err, ErrorMatches, `a previous attempt to update the authorization parameters for the lockout hierarchy was interrupted`) - c.Check(err, Equals, ErrLockoutAuthUpdateInterrupted) + c.Check(err, ErrorMatches, `the authorization parameters for the lockout hierarchy are invalid`) + c.Check(err, Equals, ErrLockoutAuthInvalid) } func (s *lockoutauthSuite) TestResetDictionaryAttackLockAuthFail(c *C) { @@ -780,6 +780,6 @@ func (s *lockoutauthSuite) TestResetDictionaryAttackLockInvalidPolicy(c *C) { AuthPolicy: policy, }), }) - c.Check(err, ErrorMatches, `the authorization policy for the lockout hierarchy is inconsistent with the supplied data`) - c.Check(err, Equals, ErrInvalidLockoutAuthPolicy) + c.Check(err, ErrorMatches, `the authorization parameters for the lockout hierarchy are invalid`) + c.Check(err, Equals, ErrLockoutAuthInvalid) } diff --git a/tpm2/provisioning.go b/tpm2/provisioning.go index 94ccf71b..9e705c2b 100644 --- a/tpm2/provisioning.go +++ b/tpm2/provisioning.go @@ -217,7 +217,7 @@ func WithUnconfiguredLockoutAuth() EnsureProvisionedOption { if p.lockoutAuthParams != nil || p.lockoutAuthParamsErr != nil { panic("WithLockoutAuthValue incompatible with WithLockoutAuthData and WithUnconfiguredLockoutAuth") } - p.lockoutAuthParams = &LockoutAuthParams{ + p.lockoutAuthParams = &lockoutAuthParams{ noAuthValue: true, } } diff --git a/tpm2/provisioning_test.go b/tpm2/provisioning_test.go index 3a2f0253..144d508c 100644 --- a/tpm2/provisioning_test.go +++ b/tpm2/provisioning_test.go @@ -503,8 +503,8 @@ func (s *provisioningSuite) TestProvisionAfterInterruptedNewLockoutAuthValue2(c }) err := s.TPM().EnsureProvisioned(WithLockoutAuthData(data)) - c.Check(err, Equals, ErrLockoutAuthUpdateInterrupted) - c.Check(err, ErrorMatches, `a previous attempt to update the authorization parameters for the lockout hierarchy was interrupted`) + c.Check(err, Equals, ErrLockoutAuthInvalid) + c.Check(err, ErrorMatches, `the authorization parameters for the lockout hierarchy are invalid`) } func (s *provisioningSuite) TestProvisionResumeNewLockoutAuthValue3(c *C) { @@ -588,8 +588,8 @@ func (s *provisioningSuite) TestProvisionAfterInterruptedNewLockoutAuthValue3(c }) err := s.TPM().EnsureProvisioned(WithLockoutAuthData(data)) - c.Check(err, Equals, ErrLockoutAuthUpdateInterrupted) - c.Check(err, ErrorMatches, `a previous attempt to update the authorization parameters for the lockout hierarchy was interrupted`) + c.Check(err, Equals, ErrLockoutAuthInvalid) + c.Check(err, ErrorMatches, `the authorization parameters for the lockout hierarchy are invalid`) } func (s *provisioningSuite) TestProvisionWithUnconfiguredLockoutAuthIfTPMAlreadyConfigured(c *C) { From a2ef3960b09a87900430f7d3e44eddfcdcd58d62 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 23 Apr 2026 21:44:56 +0100 Subject: [PATCH 7/8] Fix a unit test bug that reduced coverage --- tpm2/lockoutauth_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tpm2/lockoutauth_test.go b/tpm2/lockoutauth_test.go index 7e83ed78..72ec5ce0 100644 --- a/tpm2/lockoutauth_test.go +++ b/tpm2/lockoutauth_test.go @@ -598,6 +598,7 @@ func (s *lockoutauthSuite) TestResetDictionaryAttackLockAuthPolicyUnset(c *C) { _, policy := s.newDefaultLockoutAuthPolicy(c, tpm2.HashAlgorithmSHA256) err := s.testResetDictionaryAttackLock(c, &testResetDictionaryAttackLockParams{ + authValue: authValue, policyAlg: tpm2.HashAlgorithmNull, data: s.makeLockoutAuthData(c, &LockoutAuthParams{ AuthValue: authValue, From a37d93fa07f49cbe1757f112dcd02fb4365e0cf6 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 23 Apr 2026 21:52:46 +0100 Subject: [PATCH 8/8] Remove a dead case --- tpm2/lockoutauth.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/tpm2/lockoutauth.go b/tpm2/lockoutauth.go index bf83ede8..7956fb1b 100644 --- a/tpm2/lockoutauth.go +++ b/tpm2/lockoutauth.go @@ -417,16 +417,9 @@ func (m *lockoutAuthValueUpdateStateMachine) setNewAuthValue(policyAlg tpm2.Hash defer done() // We use command parameter encryption here to protect the new authorization value. - switch { - case session.Handle().Type() == tpm2.HandleTypePolicySession: - // We're using policy auth so need to supply the HMAC session as an extra - // session for parameter encryption. - err = m.tpm.HierarchyChangeAuth(m.tpm.LockoutHandleContext(), m.authParams.NewAuthValue, session, m.tpm.HmacSession().IncludeAttrs(tpm2.AttrCommandEncrypt)) - default: - // We're using HMAC auth - err = m.tpm.HierarchyChangeAuth(m.tpm.LockoutHandleContext(), m.authParams.NewAuthValue, session.IncludeAttrs(tpm2.AttrCommandEncrypt)) - } - if err != nil { + // We're using policy auth so need to supply the HMAC session as an extra + // session for parameter encryption. + if err := m.tpm.HierarchyChangeAuth(m.tpm.LockoutHandleContext(), m.authParams.NewAuthValue, session, m.tpm.HmacSession().IncludeAttrs(tpm2.AttrCommandEncrypt)); err != nil { return nil, fmt.Errorf("cannot set new auth value: %w", err) }