Skip to content

Commit 93c86e8

Browse files
committed
tpm2: Introduce PCRPolicyDataError for PCR policy errors.
This makes it possible to distinguish between errors that can be rectified by calling SealedKeyData.UpdatePCRProtectionPolicy, and errors that require a keyslot to be replaced (which should be handled by reprovisioning). For v3 keys, PCR policy signature verification can fail as a result of an incorrect approved digest, invalid signature or invalid PCR policy reference. The approved digest and signature are updated when the PCR policy is updated. This PR also ensures that the stored PCR policy reference is refreshed whenever the PCR policy is updated. As a consequence, the validity of the PCR policy reference is no longer verified in keyData_v3.ValidateData. Note that PCR policy signature verification could also fail because the public key is invalid. This would still result in a PCRPolicyDataError error being returned, but a subsequent attempt to update the PCR policy would result in an InvalidKeyDataError being returned. In this case, the keyslot would need to be replaced (likely by reprovisioning). A future PR may improve this so that the stored public key is refreshed from the supplied primary key.
1 parent e218f4c commit 93c86e8

27 files changed

+271
-142
lines changed

internal/compattest/v0_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func (s *compatTestV0Suite) TestRevokeOldPCRProtectionPolicies(c *C) {
8989
c.Check(k.UpdatePCRProtectionPolicyV0(s.TPM(), s.absPath("pud"), profile), IsNil)
9090
c.Check(k.RevokeOldPCRProtectionPoliciesV0(s.TPM(), s.absPath("pud")), IsNil)
9191
s.replayPCRSequenceFromFile(c, s.absPath("pcrSequence.1"))
92-
s.testUnsealErrorMatchesCommon(c, "invalid key data: cannot complete authorization policy assertions: the PCR policy has been revoked")
92+
s.testUnsealErrorMatchesCommon(c, "invalid PCR policy data: cannot complete authorization policy assertions: the PCR policy has been revoked")
9393
}
9494

9595
func (s *compatTestV0Suite) TestUpdateKeyPCRProtectionPolicyAndUnseal(c *C) {
@@ -130,5 +130,5 @@ func (s *compatTestV0Suite) TestUnsealAfterLock(c *C) {
130130
// but keep this here just to make sure.
131131
s.replayPCRSequenceFromFile(c, s.absPath("pcrSequence.1"))
132132
c.Assert(secboot_tpm2.BlockPCRProtectionPolicies(s.TPM(), []int{12}), IsNil)
133-
s.testUnsealErrorMatchesCommon(c, "invalid key data: cannot complete authorization policy assertions: cannot execute PCR assertions: cannot execute PolicyOR assertions: current session digest not found in policy data")
133+
s.testUnsealErrorMatchesCommon(c, "invalid PCR policy data: cannot complete authorization policy assertions: cannot execute PCR assertions: cannot execute PolicyOR assertions: current session digest not found in policy data")
134134
}

internal/compattest/v1_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func (s *compatTestV1Suite) TestRevokeOldPCRProtectionPolicies(c *C) {
7878
c.Check(k.UpdatePCRProtectionPolicy(s.TPM(), s.readFile(c, "authKey"), profile), IsNil)
7979
c.Check(k.RevokeOldPCRProtectionPolicies(s.TPM(), s.readFile(c, "authKey")), IsNil)
8080
s.replayPCRSequenceFromFile(c, s.absPath("pcrSequence.1"))
81-
s.testUnsealErrorMatchesCommon(c, "invalid key data: cannot complete authorization policy assertions: the PCR policy has been revoked")
81+
s.testUnsealErrorMatchesCommon(c, "invalid PCR policy data: cannot complete authorization policy assertions: the PCR policy has been revoked")
8282
}
8383

8484
func (s *compatTestV1Suite) TestUpdateKeyPCRProtectionPolicyAndUnseal(c *C) {
@@ -116,5 +116,5 @@ func (s *compatTestV1Suite) TestUpdateKeyPCRProtectionPolicyAfterLock(c *C) {
116116
func (s *compatTestV1Suite) TestUnsealAfterLock(c *C) {
117117
s.replayPCRSequenceFromFile(c, s.absPath("pcrSequence.1"))
118118
c.Assert(secboot_tpm2.BlockPCRProtectionPolicies(s.TPM(), []int{12}), IsNil)
119-
s.testUnsealErrorMatchesCommon(c, "invalid key data: cannot complete authorization policy assertions: cannot execute PCR assertions: cannot execute PolicyOR assertions: current session digest not found in policy data")
119+
s.testUnsealErrorMatchesCommon(c, "invalid PCR policy data: cannot complete authorization policy assertions: cannot execute PCR assertions: cannot execute PolicyOR assertions: current session digest not found in policy data")
120120
}

tpm2/errors.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,10 @@ func (e AuthFailError) Error() string {
7777
return fmt.Sprintf("cannot access resource at handle %v because an authorization check failed", e.Handle)
7878
}
7979

80-
// InvalidKeyDataError indicates that the provided key data file is invalid. This error may also be returned in some
81-
// scenarious where the TPM is incorrectly provisioned, but it isn't possible to determine whether the error is with
82-
// the provisioning status or because the key data file is invalid.
80+
// InvalidKeyDataError indicates that the [SealedKeyData] or [SealedKeyObject] is invalid. This error may also
81+
// be returned in some scenarious where the TPM is incorrectly provisioned, but it isn't possible to determine
82+
// whether the error is with the provisioning status or because the key data is invalid. This error generally
83+
// means that the key cannot be recovered.
8384
type InvalidKeyDataError struct {
8485
msg string
8586
}
@@ -92,3 +93,25 @@ func isInvalidKeyDataError(err error) bool {
9293
var e InvalidKeyDataError
9394
return xerrors.As(err, &e)
9495
}
96+
97+
// PCRPolicyDataError may be returned from one of the [secboot.PlatformKeyDataHandler] methods or
98+
// [SealedKeyObject.UnsealFromTPM] if the PCR policy is invalid, either because the current PCR values
99+
// are not part of the currently approved policy, or the approved policy digest or signature is
100+
// invalid. This can generally be repaired by calling [SealedKeyData.UpdatePCRProtectionPolicy] or
101+
// [SealedKeyObject.UpdatePCRProtectionPolicy].
102+
//
103+
// Note that in some cases, this error may occur because the public key used to verify the PCR
104+
// policy is invalid. If this is the case, an attempt to update the PCR policy will fail with an
105+
// [InvalidKeyDataError]. A future update may improve this so that updating the PCR policy also refreshes
106+
// the public key.
107+
type PCRPolicyDataError struct {
108+
err error
109+
}
110+
111+
func (e *PCRPolicyDataError) Error() string {
112+
return fmt.Sprintf("invalid PCR policy data: %v", e.err)
113+
}
114+
115+
func (e *PCRPolicyDataError) Unwrap() error {
116+
return e.err
117+
}

tpm2/export_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ var (
4242
ComputeV3PcrPolicyRef = computeV3PcrPolicyRef
4343
DeriveV3PolicyAuthKey = deriveV3PolicyAuthKey
4444
ErrSessionDigestNotFound = errSessionDigestNotFound
45+
IsPCRPolicyDataError = isPCRPolicyDataError
4546
IsPolicyDataError = isPolicyDataError
4647
MakeSealedKeyData = makeSealedKeyData
4748
MakeKeyDataNoAuth = makeKeyDataNoAuth
@@ -127,9 +128,10 @@ type PcrPolicyData_v3 = pcrPolicyData_v3
127128

128129
type PcrPolicyParams = pcrPolicyParams
129130

130-
func NewPcrPolicyParams(key secboot.PrimaryKey, pcrs tpm2.PCRSelectionList, pcrDigests tpm2.DigestList, policyCounter *tpm2.NVPublic, policySequence uint64) *PcrPolicyParams {
131+
func NewPcrPolicyParams(key secboot.PrimaryKey, role []byte, pcrs tpm2.PCRSelectionList, pcrDigests tpm2.DigestList, policyCounter *tpm2.NVPublic, policySequence uint64) *PcrPolicyParams {
131132
return &PcrPolicyParams{
132133
key: key,
134+
role: role,
133135
pcrs: pcrs,
134136
pcrDigests: pcrDigests,
135137
policyCounter: policyCounter,
@@ -238,7 +240,7 @@ func MockSecbootNewKeyDataWithPassphrase(fn func(*secboot.KeyWithPassphraseParam
238240
}
239241
}
240242

241-
func MockSkdbUpdatePCRProtectionPolicyNoValidate(fn func(*sealedKeyDataBase, *tpm2.TPMContext, secboot.PrimaryKey, *tpm2.NVPublic, *PCRProtectionProfile, PcrPolicyVersionOption) error) (restore func()) {
243+
func MockSkdbUpdatePCRProtectionPolicyNoValidate(fn func(*sealedKeyDataBase, *tpm2.TPMContext, secboot.PrimaryKey, string, *tpm2.NVPublic, *PCRProtectionProfile, PcrPolicyVersionOption) error) (restore func()) {
242244
orig := skdbUpdatePCRProtectionPolicyNoValidate
243245
skdbUpdatePCRProtectionPolicyNoValidate = fn
244246
return func() {

tpm2/keydata_v3.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,15 @@ func (d *keyData_v3) ValidateData(tpm *tpm2.TPMContext, role []byte) (tpm2.Resou
139139
return nil, keyDataError{fmt.Errorf("name algorithm for signing key is invalid or not available: %v", authPublicKey.NameAlg)}
140140
}
141141
pcrPolicyRef := computeV3PcrPolicyRefFromCounterContext(authPublicKey.NameAlg, role, pcrPolicyCounter)
142-
if !bytes.Equal(pcrPolicyRef, d.PolicyData.StaticData.PCRPolicyRef) {
143-
return nil, keyDataError{errors.New("unexpected PCR policy ref")}
144-
}
142+
// We don't check p.PolicyData.StaticData.PCRPolicyRef here because it gets updated
143+
// in UpdatePCRProtectionPolicy.
145144

146145
// Make sure that the static authorization policy data is consistent with the sealed key object's policy.
147146
if !d.KeyPublic.NameAlg.Available() {
148147
return nil, keyDataError{fmt.Errorf("cannot determine if static authorization policy matches sealed key object: algorithm %v unavailable", d.KeyPublic.NameAlg)}
149148
}
150149
builder := policyutil.NewPolicyBuilder(d.KeyPublic.NameAlg)
151-
builder.RootBranch().PolicyAuthorize(d.PolicyData.StaticData.PCRPolicyRef, authPublicKey)
150+
builder.RootBranch().PolicyAuthorize(pcrPolicyRef, authPublicKey)
152151
if d.PolicyData.StaticData.RequireAuthValue {
153152
builder.RootBranch().PolicyAuthValue()
154153
}

tpm2/keydata_v3_test.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,19 @@ func (s *keyDataV3Suite) TestValidateOK3(c *C) {
225225
c.Check(pcrPolicyCounter.Name(), DeepEquals, pcrPolicyCounterName)
226226
}
227227

228+
func (s *keyDataV3Suite) TestValidateOK4(c *C) {
229+
role := "foo"
230+
data, pcrPolicyCounterName := s.newMockKeyData(c, s.NextAvailableHandle(c, 0x0180ff00), role, false)
231+
232+
// An invalid PCR policy ref shouldn't trigger an error because it will be
233+
// corrected when updating the PCR policy.
234+
data.(*KeyData_v3).PolicyData.StaticData.PCRPolicyRef = []byte("1234")
235+
236+
pcrPolicyCounter, err := data.ValidateData(s.TPM().TPMContext, []byte(role))
237+
c.Check(err, IsNil)
238+
c.Check(pcrPolicyCounter.Name(), DeepEquals, pcrPolicyCounterName)
239+
}
240+
228241
func (s *keyDataV3Suite) TestValidateImportedOK(c *C) {
229242
role := "foo"
230243
data := s.newMockImportableKeyData(c, role, false)
@@ -351,7 +364,7 @@ func (s *keyDataV3Suite) TestValidateWrongPolicyCounter1(c *C) {
351364

352365
_, err = data.ValidateData(s.TPM().TPMContext, []byte(role))
353366
c.Check(err, testutil.ConvertibleTo, KeyDataError{})
354-
c.Check(err, ErrorMatches, "unexpected PCR policy ref")
367+
c.Check(err, ErrorMatches, "the sealed key object's authorization policy is inconsistent with the associated metadata or persistent TPM resources")
355368
}
356369

357370
func (s *keyDataV3Suite) TestValidateWrongPolicyCounter2(c *C) {
@@ -362,7 +375,7 @@ func (s *keyDataV3Suite) TestValidateWrongPolicyCounter2(c *C) {
362375

363376
_, err := data.ValidateData(s.TPM().TPMContext, []byte(role))
364377
c.Check(err, testutil.ConvertibleTo, KeyDataError{})
365-
c.Check(err, ErrorMatches, "unexpected PCR policy ref")
378+
c.Check(err, ErrorMatches, "the sealed key object's authorization policy is inconsistent with the associated metadata or persistent TPM resources")
366379
}
367380

368381
func (s *keyDataV3Suite) TestValidateWrongPolicyCounter3(c *C) {
@@ -379,7 +392,7 @@ func (s *keyDataV3Suite) TestValidateWrongPolicyCounter3(c *C) {
379392

380393
_, err := data.ValidateData(s.TPM().TPMContext, []byte(role))
381394
c.Check(err, testutil.ConvertibleTo, KeyDataError{})
382-
c.Check(err, ErrorMatches, "unexpected PCR policy ref")
395+
c.Check(err, ErrorMatches, "the sealed key object's authorization policy is inconsistent with the associated metadata or persistent TPM resources")
383396
}
384397

385398
func (s *keyDataV3Suite) TestSerialization(c *C) {
@@ -399,7 +412,7 @@ func (s *keyDataV3Suite) TestValidateInvalidRole(c *C) {
399412
data, _ := s.newMockKeyData(c, tpm2.HandleNull, authRole, false)
400413

401414
_, err := data.ValidateData(s.TPM().TPMContext, []byte(validationRole))
402-
c.Check(err, ErrorMatches, "unexpected PCR policy ref")
415+
c.Check(err, ErrorMatches, "the sealed key object's authorization policy is inconsistent with the associated metadata or persistent TPM resources")
403416
}
404417

405418
func (s *keyDataV3Suite) TestValidateWrongAuthValueRequirement(c *C) {

tpm2/platform.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ func (h *platformKeyDataHandler) recoverKeysCommon(data *secboot.PlatformKeyData
8181
symKey, err := k.unsealDataFromTPM(tpm.TPMContext, authKey, tpm.HmacSession())
8282
if err != nil {
8383
var e InvalidKeyDataError
84+
var p *PCRPolicyDataError
8485
switch {
8586
case xerrors.As(err, &e):
8687
return nil, &secboot.PlatformHandlerError{
@@ -98,6 +99,10 @@ func (h *platformKeyDataHandler) recoverKeysCommon(data *secboot.PlatformKeyData
9899
return nil, &secboot.PlatformHandlerError{
99100
Type: secboot.PlatformHandlerErrorInvalidAuthKey,
100101
Err: err}
102+
case errors.As(err, &p):
103+
return nil, &secboot.PlatformHandlerError{
104+
Type: secboot.PlatformHandlerErrorIncompatibleRole,
105+
Err: err}
101106
}
102107
return nil, xerrors.Errorf("cannot unseal key: %w", err)
103108
}

tpm2/platform_legacy.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ func (h *legacyPlatformKeyDataHandler) RecoverKeys(data *secboot.PlatformKeyData
8585
key, authKey, err := k.UnsealFromTPM(tpm)
8686
if err != nil {
8787
var e InvalidKeyDataError
88+
var p *PCRPolicyDataError
8889
switch {
8990
case xerrors.As(err, &e):
9091
return nil, &secboot.PlatformHandlerError{
@@ -98,6 +99,10 @@ func (h *legacyPlatformKeyDataHandler) RecoverKeys(data *secboot.PlatformKeyData
9899
return nil, &secboot.PlatformHandlerError{
99100
Type: secboot.PlatformHandlerErrorUnavailable,
100101
Err: err}
102+
case errors.As(err, &p):
103+
return nil, &secboot.PlatformHandlerError{
104+
Type: secboot.PlatformHandlerErrorIncompatibleRole,
105+
Err: err}
101106
}
102107
return nil, xerrors.Errorf("cannot unseal key: %w", err)
103108
}

tpm2/platform_legacy_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ func (s *platformLegacySuite) TestRecoverKeysInvalidPCRPolicy(c *C) {
116116
c.Assert(err, IsNil)
117117

118118
_, _, err = k.RecoverKeys()
119-
c.Check(err, ErrorMatches, "invalid key data: cannot complete authorization policy assertions: "+
120-
"cannot execute PCR assertions: cannot execute PolicyOR assertions: current session digest not found in policy data")
119+
c.Check(err, ErrorMatches, "incompatible key data role params: invalid PCR policy data: cannot complete authorization "+
120+
"policy assertions: cannot execute PCR assertions: cannot execute PolicyOR assertions: current session digest not found in policy data")
121121
}
122122

123123
func (s *platformLegacySuite) TestRecoverKeysTPMLockout(c *C) {

tpm2/platform_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -465,8 +465,8 @@ func (s *platformSuite) TestRecoverKeysUnsealErrorHandlingInvalidPCRProfile(c *C
465465
return ""
466466
})
467467
c.Assert(err, testutil.ConvertibleTo, &secboot.PlatformHandlerError{})
468-
c.Check(err.(*secboot.PlatformHandlerError).Type, Equals, secboot.PlatformHandlerErrorInvalidData)
469-
c.Check(err, ErrorMatches, "cannot complete authorization policy assertions: "+
468+
c.Check(err.(*secboot.PlatformHandlerError).Type, Equals, secboot.PlatformHandlerErrorIncompatibleRole)
469+
c.Check(err, ErrorMatches, "invalid PCR policy data: cannot complete authorization policy assertions: "+
470470
"cannot execute PCR assertions: cannot execute PolicyOR assertions: current session digest not found in policy data")
471471
}
472472

@@ -498,8 +498,8 @@ func (s *platformSuite) TestRecoverKeysUnsealErrorHandlingSealedKeyAccessLocked(
498498
return ""
499499
})
500500
c.Assert(err, testutil.ConvertibleTo, &secboot.PlatformHandlerError{})
501-
c.Check(err.(*secboot.PlatformHandlerError).Type, Equals, secboot.PlatformHandlerErrorInvalidData)
502-
c.Check(err, ErrorMatches, "cannot complete authorization policy assertions: "+
501+
c.Check(err.(*secboot.PlatformHandlerError).Type, Equals, secboot.PlatformHandlerErrorIncompatibleRole)
502+
c.Check(err, ErrorMatches, "invalid PCR policy data: cannot complete authorization policy assertions: "+
503503
"cannot execute PCR assertions: cannot execute PolicyOR assertions: current session digest not found in policy data")
504504
}
505505

0 commit comments

Comments
 (0)