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/tools/gen-compattest-data/main.go b/tools/gen-compattest-data/main.go index 3ef9989a..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([]byte("1234"))); 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/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..7956fb1b 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") + // 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") + + // 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. + 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 @@ -66,6 +91,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) { @@ -115,35 +146,415 @@ 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) 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, + 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.authorizeLockout(true, tpm2.CommandSetPrimaryPolicy) + 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.authorizeLockout(false, tpm2.CommandHierarchyChangeAuth) + 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 + m.authParams.noAuthValue = false + + 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.authorizeLockout(false, tpm2.CommandHierarchyChangeAuth) + if err != nil { + return nil, err + } + defer done() + + // We use command parameter encryption here to protect the new authorization value. + // 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) + } + + 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) + 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.authorizeLockout(false, tpm2.CommandSetPrimaryPolicy) + 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, ErrLockoutAuthInvalid + } + } } 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 - if lockoutAuthSet { + switch lockoutAuthSet := tpm2.PermanentAttributes(val)&tpm2.AttrLockoutAuthSet > 0; { + case lockoutAuthSet && authParams.noAuthValue: + return nil, nil, ErrLockoutAuthInitialized + case 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 +564,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, ErrLockoutAuthInvalid + 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 +631,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 +650,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 [WithProvisionNewLockoutAuthData] 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 @@ -224,7 +666,12 @@ 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 [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 { var params *lockoutAuthParams if err := json.Unmarshal(lockoutAuthData, ¶ms); err != nil { @@ -236,11 +683,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..72ec5ce0 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,49 @@ 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{ + authValue: authValue, + 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 +667,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 +690,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 +704,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, `the authorization parameters for the lockout hierarchy are invalid`) + c.Check(err, Equals, ErrLockoutAuthInvalid) +} + +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, `the authorization parameters for the lockout hierarchy are invalid`) + c.Check(err, Equals, ErrLockoutAuthInvalid) } func (s *lockoutauthSuite) TestResetDictionaryAttackLockAuthFail(c *C) { @@ -458,6 +781,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, 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 33f45e92..9e705c2b 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,25 +200,40 @@ 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) +// 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 = &lockoutAuthParams{ + noAuthValue: true, + } + } +} + // 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. 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, @@ -227,14 +243,14 @@ 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 [WithProvisionNewLockoutAuthData] option. // // If the data contains the wrong authorization value, the lockout hierarchy will become unavailable for // the pre-programmed recovery time. func WithLockoutAuthData(data []byte) EnsureProvisionedOption { return func(p *ensureProvisionedParams) { if p.lockoutAuthParams != nil { - panic("WithLockoutAuthData incompatible with WithLockoutAuthValue") + panic("WithLockoutAuthData incompatible with WithLockoutAuthValue and WithUnconfiguredLockoutAuth") } p.lockoutAuthParamsErr = json.Unmarshal(data, &p.lockoutAuthParams) } @@ -248,20 +264,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 { +// 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 WithProvisionNewLockoutAuthData(rand io.Reader, syncData func([]byte) error) EnsureProvisionedOption { return func(p *ensureProvisionedParams) { - p.newLockoutAuthValue = authValue + p.newLockoutAuthValue = true + p.newLockoutAuthValueReader = rand + p.syncLockoutAuthData = syncData } } @@ -294,18 +312,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 to the value supplied to [WithProvisionNewLockoutAuthValue], or the empty value if not 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, @@ -319,15 +339,20 @@ 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: - return errors.New("WithClearBeforeProvision requires WithLockoutAuthParams or WithLockoutAuthData") + 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") 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 +362,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 +461,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 +497,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..144d508c 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,47 @@ 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.Assert(expectedLockoutAuthData, Not(HasLen), 0) + expected := expectedLockoutAuthData[0] + expectedLockoutAuthData = expectedLockoutAuthData[1:] + c.Check(data, DeepEquals, expected) + return nil + } + + opts := []EnsureProvisionedOption{WithUnconfiguredLockoutAuth(), WithProvisionNewLockoutAuthData(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 +163,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 +182,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 WithProvisionNewLockoutAuthData 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), 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 + // 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 WithProvisionNewLockoutAuthData 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), 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 // 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 +482,123 @@ 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 WithProvisionNewLockoutAuthData + // 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, ErrLockoutAuthInvalid) + c.Check(err, ErrorMatches, `the authorization parameters for the lockout hierarchy are invalid`) +} + +func (s *provisioningSuite) TestProvisionResumeNewLockoutAuthValue3(c *C) { + // Test resuming with WithProvisionNewLockoutAuthData 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), 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 + // 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 WithProvisionNewLockoutAuthData + // 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, ErrLockoutAuthInvalid) + c.Check(err, ErrorMatches, `the authorization parameters for the lockout hierarchy are invalid`) +} + +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 @@ -270,8 +615,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 +633,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 +650,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 +669,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 +677,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 +774,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( + WithUnconfiguredLockoutAuth(), + WithProvisionNewLockoutAuthData(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 +799,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 +815,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( + WithUnconfiguredLockoutAuth(), + WithProvisionNewLockoutAuthData(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 +846,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 +861,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 +886,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 +901,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{WithUnconfiguredLockoutAuth(), 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 +923,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 +949,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 +968,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( + WithUnconfiguredLockoutAuth(), + WithProvisionNewLockoutAuthData(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 +988,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) { @@ -619,7 +1023,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) } @@ -669,3 +1073,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), 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 5be75687..e13cc224 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), WithProvisionNewLockoutAuthData(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)