Skip to content

Commit

Permalink
tpm2: Create TPM2 keys with noDA attribute if no user auth is required
Browse files Browse the repository at this point in the history
By default, all TPM2 keys are protected by the TPM's dictionary attack
protection logic. Given that the user auth mode is immutable, we should
take advantage of this to create keys that require no user auth with the
noDA attribute, so that they aren't protected by the TPM's dictionary
attack protection logic. This means that these keys will still be
recoverable, even if the TPM is in lockout mode.
  • Loading branch information
chrisccoulson committed Dec 2, 2024
1 parent b5ca480 commit b5882cd
Show file tree
Hide file tree
Showing 16 changed files with 294 additions and 50 deletions.
13 changes: 12 additions & 1 deletion tpm2/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ type KeyData_v2 = keyData_v2
type KeyData_v3 = keyData_v3
type AdditionalData_v3 = additionalData_v3
type KeyDataError = keyDataError
type SealedKeyDataParams = makeSealedKeyDataParams
type MakeSealedKeyDataParams = makeSealedKeyDataParams
type KeyDataPolicy = keyDataPolicy
type KeyDataPolicy_v0 = keyDataPolicy_v0
type KeyDataPolicy_v1 = keyDataPolicy_v1
Expand All @@ -83,6 +83,9 @@ func NewSealedObjectKeySealer(tpm *Connection) keySealer {
return &sealedObjectKeySealer{tpm}
}

type KeyDataConstructor = keyDataConstructor
type KeySealer = keySealer

type PcrPolicyVersionOption = pcrPolicyVersionOption
type PolicyDataError = policyDataError
type PolicyOrData_v0 = policyOrData_v0
Expand Down Expand Up @@ -191,6 +194,14 @@ func MockEnsurePcrPolicyCounter(fn func(*tpm2.TPMContext, tpm2.Handle, *tpm2.Pub
}
}

func MockMakeSealedKeyData(fn func(*tpm2.TPMContext, *MakeSealedKeyDataParams, KeySealer, KeyDataConstructor, tpm2.SessionContext) (*secboot.KeyData, secboot.PrimaryKey, secboot.DiskUnlockKey, error)) (restore func()) {
orig := makeSealedKeyData
makeSealedKeyData = fn
return func() {
makeSealedKeyData = orig
}
}

func MockNewKeyDataPolicy(fn func(tpm2.HashAlgorithmId, *tpm2.Public, string, *tpm2.NVPublic, bool) (KeyDataPolicy, tpm2.Digest, error)) (restore func()) {
orig := newKeyDataPolicy
newKeyDataPolicy = fn
Expand Down
12 changes: 9 additions & 3 deletions tpm2/key_sealer.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type keySealer interface {
// and with the specified name algorithm and authorization policy. It returns
// the private and public parts of the object, and an optional secret value if
// the returned object has to be imported.
CreateSealedObject(data []byte, nameAlg tpm2.HashAlgorithmId, policy tpm2.Digest) (tpm2.Private, *tpm2.Public, tpm2.EncryptedSecret, error)
CreateSealedObject(data []byte, nameAlg tpm2.HashAlgorithmId, policy tpm2.Digest, noDA bool) (tpm2.Private, *tpm2.Public, tpm2.EncryptedSecret, error)
}

// sealedObjectKeySealer is an implementation of keySealer that seals data to
Expand All @@ -42,7 +42,7 @@ type sealedObjectKeySealer struct {
tpm *Connection
}

func (s *sealedObjectKeySealer) CreateSealedObject(data []byte, nameAlg tpm2.HashAlgorithmId, policy tpm2.Digest) (tpm2.Private, *tpm2.Public, tpm2.EncryptedSecret, error) {
func (s *sealedObjectKeySealer) CreateSealedObject(data []byte, nameAlg tpm2.HashAlgorithmId, policy tpm2.Digest, noDA bool) (tpm2.Private, *tpm2.Public, tpm2.EncryptedSecret, error) {
// Obtain a context for the SRK now. If we're called immediately after ProvisionTPM without
// closing the Connection, we use the context cached by ProvisionTPM, which corresponds to
// the object provisioned. If not, we just unconditionally provision a new SRK as this function
Expand Down Expand Up @@ -79,6 +79,9 @@ func (s *sealedObjectKeySealer) CreateSealedObject(data []byte, nameAlg tpm2.Has
// Define the template
template := templates.NewSealedObject(nameAlg)
template.Attrs &^= tpm2.AttrUserWithAuth
if noDA {
template.Attrs |= tpm2.AttrNoDA
}
template.AuthPolicy = policy

// Now create the sealed key object. The command is integrity protected so if the object
Expand All @@ -101,9 +104,12 @@ type importableObjectKeySealer struct {
tpmKey *tpm2.Public
}

func (s *importableObjectKeySealer) CreateSealedObject(data []byte, nameAlg tpm2.HashAlgorithmId, policy tpm2.Digest) (tpm2.Private, *tpm2.Public, tpm2.EncryptedSecret, error) {
func (s *importableObjectKeySealer) CreateSealedObject(data []byte, nameAlg tpm2.HashAlgorithmId, policy tpm2.Digest, noDA bool) (tpm2.Private, *tpm2.Public, tpm2.EncryptedSecret, error) {
pub, sensitive := util.NewExternalSealedObject(nameAlg, nil, data)
pub.Attrs &^= tpm2.AttrUserWithAuth
if noDA {
pub.Attrs |= tpm2.AttrNoDA
}
pub.AuthPolicy = policy

// Now create the importable sealed key object (duplication object).
Expand Down
52 changes: 44 additions & 8 deletions tpm2/key_sealer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,24 @@ type testCreateSealedObjectData struct {
data tpm2.SensitiveData
nameAlg tpm2.HashAlgorithmId
policyDigest tpm2.Digest
noDA bool
session tpm2.SessionContext
}

func (s *sealedObjectKeySealerSuite) testCreateSealedObject(c *C, data *testCreateSealedObjectData) {
sealer := NewSealedObjectKeySealer(s.TPM())

priv, pub, importSymSeed, err := sealer.CreateSealedObject(data.data, data.nameAlg, data.policyDigest)
priv, pub, importSymSeed, err := sealer.CreateSealedObject(data.data, data.nameAlg, data.policyDigest, data.noDA)
c.Assert(err, IsNil)
c.Check(importSymSeed, IsNil)

c.Check(pub.Type, Equals, tpm2.ObjectTypeKeyedHash)
c.Check(pub.NameAlg, Equals, data.nameAlg)
c.Check(pub.Attrs, Equals, tpm2.AttrFixedParent|tpm2.AttrFixedTPM)
expectedAttrs := tpm2.AttrFixedParent | tpm2.AttrFixedTPM
if data.noDA {
expectedAttrs |= tpm2.AttrNoDA
}
c.Check(pub.Attrs, Equals, expectedAttrs)
c.Check(pub.AuthPolicy, DeepEquals, data.policyDigest)
c.Check(pub.Params, DeepEquals,
&tpm2.PublicParamsU{
Expand All @@ -96,6 +101,7 @@ func (s *sealedObjectKeySealerSuite) TestCreateSealedObject(c *C) {
data: []byte("foo"),
nameAlg: tpm2.HashAlgorithmSHA256,
policyDigest: make([]byte, 32),
noDA: true,
session: s.StartAuthSession(c, nil, nil, tpm2.SessionTypePolicy, nil, tpm2.HashAlgorithmSHA256)})
}

Expand All @@ -108,6 +114,7 @@ func (s *sealedObjectKeySealerSuite) TestCreateSealedObjectWithNewConnection(c *
data: []byte("foo"),
nameAlg: tpm2.HashAlgorithmSHA256,
policyDigest: make([]byte, 32),
noDA: true,
session: s.StartAuthSession(c, nil, nil, tpm2.SessionTypePolicy, nil, tpm2.HashAlgorithmSHA256)})
}

Expand All @@ -122,6 +129,7 @@ func (s *sealedObjectKeySealerSuite) TestCreateSealedObjectMissingSRK(c *C) {
data: []byte("foo"),
nameAlg: tpm2.HashAlgorithmSHA256,
policyDigest: make([]byte, 32),
noDA: true,
session: s.StartAuthSession(c, nil, nil, tpm2.SessionTypePolicy, nil, tpm2.HashAlgorithmSHA256)})
}

Expand All @@ -130,6 +138,7 @@ func (s *sealedObjectKeySealerSuite) TestCreateSealedObjectDifferentData(c *C) {
data: []byte("bar"),
nameAlg: tpm2.HashAlgorithmSHA256,
policyDigest: make([]byte, 32),
noDA: true,
session: s.StartAuthSession(c, nil, nil, tpm2.SessionTypePolicy, nil, tpm2.HashAlgorithmSHA256)})
}

Expand All @@ -138,6 +147,7 @@ func (s *sealedObjectKeySealerSuite) TestCreateSealedObjectDifferentNameAlg(c *C
data: []byte("foo"),
nameAlg: tpm2.HashAlgorithmSHA1,
policyDigest: make([]byte, 20),
noDA: true,
session: s.StartAuthSession(c, nil, nil, tpm2.SessionTypePolicy, nil, tpm2.HashAlgorithmSHA1)})
}

Expand All @@ -152,9 +162,19 @@ func (s *sealedObjectKeySealerSuite) TestCreateSealedObjectDifferentPolicy(c *C)
data: []byte("foo"),
nameAlg: tpm2.HashAlgorithmSHA256,
policyDigest: trial.GetDigest(),
noDA: true,
session: session})
}

func (s *sealedObjectKeySealerSuite) TestCreateSealedObjectWithDA(c *C) {
s.testCreateSealedObject(c, &testCreateSealedObjectData{
data: []byte("foo"),
nameAlg: tpm2.HashAlgorithmSHA256,
policyDigest: make([]byte, 32),
noDA: false,
session: s.StartAuthSession(c, nil, nil, tpm2.SessionTypePolicy, nil, tpm2.HashAlgorithmSHA256)})
}

type importableObjectKeySealerSuite struct{}

var _ = Suite(&importableObjectKeySealerSuite{})
Expand All @@ -167,12 +187,16 @@ func (s *importableObjectKeySealerSuite) testCreateSealedObject(c *C, data *test

sealer := NewImportableObjectKeySealer(srk)

priv, pub, importSymSeed, err := sealer.CreateSealedObject(data.data, data.nameAlg, data.policyDigest)
priv, pub, importSymSeed, err := sealer.CreateSealedObject(data.data, data.nameAlg, data.policyDigest, data.noDA)
c.Assert(err, IsNil)

c.Check(pub.Type, Equals, tpm2.ObjectTypeKeyedHash)
c.Check(pub.NameAlg, Equals, data.nameAlg)
c.Check(pub.Attrs, Equals, tpm2.ObjectAttributes(0))
expectedAttrs := tpm2.ObjectAttributes(0)
if data.noDA {
expectedAttrs |= tpm2.AttrNoDA
}
c.Check(pub.Attrs, Equals, expectedAttrs)
c.Check(pub.AuthPolicy, DeepEquals, data.policyDigest)
c.Check(pub.Params, DeepEquals,
&tpm2.PublicParamsU{
Expand All @@ -191,21 +215,24 @@ func (s *importableObjectKeySealerSuite) TestCreateSealedObject(c *C) {
s.testCreateSealedObject(c, &testCreateSealedObjectData{
data: []byte("foo"),
nameAlg: tpm2.HashAlgorithmSHA256,
policyDigest: make([]byte, 32)})
policyDigest: make([]byte, 32),
noDA: true})
}

func (s *importableObjectKeySealerSuite) TestCreateSealedObjectDifferentData(c *C) {
s.testCreateSealedObject(c, &testCreateSealedObjectData{
data: []byte("bar"),
nameAlg: tpm2.HashAlgorithmSHA256,
policyDigest: make([]byte, 32)})
policyDigest: make([]byte, 32),
noDA: true})
}

func (s *importableObjectKeySealerSuite) TestCreateSealedObjectiDifferentNameAlg(c *C) {
s.testCreateSealedObject(c, &testCreateSealedObjectData{
data: []byte("foo"),
nameAlg: tpm2.HashAlgorithmSHA1,
policyDigest: make([]byte, 20)})
policyDigest: make([]byte, 20),
noDA: true})
}

func (s *importableObjectKeySealerSuite) TestCreateSealedObjectWithDifferentPolicy(c *C) {
Expand All @@ -215,5 +242,14 @@ func (s *importableObjectKeySealerSuite) TestCreateSealedObjectWithDifferentPoli
s.testCreateSealedObject(c, &testCreateSealedObjectData{
data: []byte("foo"),
nameAlg: tpm2.HashAlgorithmSHA256,
policyDigest: trial.GetDigest()})
policyDigest: trial.GetDigest(),
noDA: true})
}

func (s *importableObjectKeySealerSuite) TestCreateSealedObjectWithDA(c *C) {
s.testCreateSealedObject(c, &testCreateSealedObjectData{
data: []byte("foo"),
nameAlg: tpm2.HashAlgorithmSHA256,
policyDigest: make([]byte, 32),
noDA: false})
}
26 changes: 23 additions & 3 deletions tpm2/keydata.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (

"github.com/canonical/go-tpm2"
"github.com/canonical/go-tpm2/mu"
"github.com/canonical/go-tpm2/objectutil"

"golang.org/x/xerrors"

Expand Down Expand Up @@ -162,14 +163,33 @@ func (k *sealedKeyDataBase) load(tpm *tpm2.TPMContext, parent tpm2.ResourceConte

// validateData performs correctness checks on this object.
func (k *sealedKeyDataBase) validateData(tpm *tpm2.TPMContext, role string) (*tpm2.NVPublic, error) {
sealedKeyTemplate := makeImportableSealedKeyTemplate()
optsInternal := []objectutil.PublicTemplateOption{
objectutil.WithUserAuthMode(objectutil.RequirePolicy),
objectutil.WithProtectionGroupMode(objectutil.NonDuplicable),
objectutil.WithDuplicationMode(objectutil.FixedParent),
}
optsExternal := []objectutil.PublicTemplateOption{
objectutil.WithUserAuthMode(objectutil.RequirePolicy),
objectutil.WithProtectionGroupMode(objectutil.NonDuplicable),
objectutil.WithDuplicationMode(objectutil.DuplicationRoot),
}
if k.data.Policy().RequireUserAuth() {
optsInternal = append(optsInternal, objectutil.WithDictionaryAttackProtection())
optsExternal = append(optsExternal, objectutil.WithDictionaryAttackProtection())
} else {
optsInternal = append(optsInternal, objectutil.WithoutDictionaryAttackProtection())
optsExternal = append(optsExternal, objectutil.WithoutDictionaryAttackProtection())
}
internalSealedKeyTemplate := objectutil.NewSealedObjectTemplate(optsInternal...)
externalSealedKeyTemplate := objectutil.NewSealedObjectTemplate(optsExternal...)

// Perform some initial checks on the sealed data object's public area to
// make sure it's a sealed data object.
if k.data.Public().Type != sealedKeyTemplate.Type {
if k.data.Public().Type != internalSealedKeyTemplate.Type {
return nil, keyDataError{errors.New("sealed key object has the wrong type")}
}
if k.data.Public().Attrs&^(tpm2.AttrFixedTPM|tpm2.AttrFixedParent) != sealedKeyTemplate.Attrs {
attrs := k.data.Public().Attrs
if attrs != internalSealedKeyTemplate.Attrs && attrs != externalSealedKeyTemplate.Attrs {
return nil, keyDataError{errors.New("sealed key object has the wrong attributes")}
}

Expand Down
Loading

0 comments on commit b5882cd

Please sign in to comment.