From a2cee86384c78219a973ee08c2b63198a356193f Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 17 Sep 2020 17:45:56 +0100 Subject: [PATCH 01/13] Don't read the TPM_PT_PERMANENT property to detect whether the TPM is in DA lockout mode --- unseal.go | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/unseal.go b/unseal.go index be852210..6cc5c213 100644 --- a/unseal.go +++ b/unseal.go @@ -71,16 +71,6 @@ import ( // On success, the unsealed cleartext key is returned as the first return value, and the private part of the key used for // authorizing PCR policy updates with UpdateKeyPCRProtectionPolicy is returned as the second return value. func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []byte, authKey TPMPolicyAuthKey, err error) { - // Check if the TPM is in lockout mode - props, err := tpm.GetCapabilityTPMProperties(tpm2.PropertyPermanent, 1) - if err != nil { - return nil, nil, xerrors.Errorf("cannot fetch properties from TPM: %w", err) - } - - if tpm2.PermanentAttributes(props[0].Value)&tpm2.AttrInLockout > 0 { - return nil, nil, ErrTPMLockout - } - // Use the HMAC session created when the connection was opened for parameter encryption rather than creating a new one. hmacSession := tpm.HmacSession() @@ -133,6 +123,8 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b return nil, nil, InvalidKeyFileError{err.Error()} case isAuthFailError(err, tpm2.CommandPolicySecret, 1): return nil, nil, ErrPINFail + case tpm2.IsTPMWarning(err, tpm2.WarningLockout, tpm2.CommandPolicySecret): + return nil, nil, ErrTPMLockout case tpm2.IsResourceUnavailableError(err, lockNVHandle): return nil, nil, ErrTPMProvisioning case tpm2.IsTPMError(err, tpm2.ErrorNVLocked, tpm2.CommandPolicyNV): From 8057440c926d67dbf93c20193ec8accb790f10d4 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 17 Sep 2020 17:47:50 +0100 Subject: [PATCH 02/13] Remove an unncessary IsResourceUnavailableError check in UnsealFromTPM --- unseal.go | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/unseal.go b/unseal.go index 6cc5c213..9ce07a64 100644 --- a/unseal.go +++ b/unseal.go @@ -77,32 +77,28 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b // Load the key data keyObject, err := k.data.load(tpm.TPMContext, hmacSession) switch { + case tpm2.IsResourceUnavailableError(err, tcg.SRKHandle): + return nil, nil, ErrTPMProvisioning case isKeyFileError(err): // A keyFileError can be as a result of an improperly provisioned TPM - detect if the object at tcg.SRKHandle is a valid primary key - // with the correct attributes. If it's not, then it's definitely a provisioning error. If it is, then it could still be a - // provisioning error because we don't know if the object was created with the same template that ProvisionTPM uses. In that case, - // we'll just assume an invalid key file + // with the correct attributes. If it's not, then it's definitely a provisioning error because the object at tcg.SRKHandle is not + // the one that we provisioned the TPM with. If it is, then it could still be a provisioning error because we don't know if the + // object was created with the same template that ProvisionTPM uses, but in this case, we can't tell the difference between an + // invalid key file or a provisioning error. srk, err2 := tpm.CreateResourceContextFromTPM(tcg.SRKHandle) - switch { - case tpm2.IsResourceUnavailableError(err2, tcg.SRKHandle): - return nil, nil, ErrTPMProvisioning - case err2 != nil: + if err2 != nil { return nil, nil, xerrors.Errorf("cannot create context for SRK: %w", err2) } ok, err2 := isObjectPrimaryKeyWithTemplate(tpm.TPMContext, tpm.OwnerHandleContext(), srk, tcg.SRKTemplate, tpm.HmacSession()) switch { case err2 != nil: - return nil, nil, xerrors.Errorf("cannot determine if object at 0x%08x is a primary key in the storage hierarchy: %w", tcg.SRKHandle, err2) + return nil, nil, xerrors.Errorf("cannot determine if object at %v is a primary key in the storage hierarchy: %w", tcg.SRKHandle, err2) case !ok: return nil, nil, ErrTPMProvisioning } - // This is probably a broken key file, but it could still be a provisioning error because we don't know if the SRK object was - // created with the same template that ProvisionTPM uses. return nil, nil, InvalidKeyFileError{err.Error()} - case tpm2.IsResourceUnavailableError(err, tcg.SRKHandle): - return nil, nil, ErrTPMProvisioning case err != nil: - return nil, nil, err + return nil, nil, xerrors.Errorf("cannot load sealed key in to TPM: %w", err) } defer tpm.FlushContext(keyObject) From 7a516b01ee84db6d6900249846f904d6d511a513 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 17 Sep 2020 17:56:49 +0100 Subject: [PATCH 03/13] Merge dynamicPolicyDataError and staticPolicyDataError --- export_test.go | 3 +-- policy.go | 55 +++++++++++++++++--------------------------------- policy_test.go | 28 ++++++++++++------------- unseal.go | 5 +---- 4 files changed, 34 insertions(+), 57 deletions(-) diff --git a/export_test.go b/export_test.go index 61069dab..86732325 100644 --- a/export_test.go +++ b/export_test.go @@ -59,8 +59,7 @@ var ( ExecutePolicySession = executePolicySession IdentifyInitialOSLaunchVerificationEvent = identifyInitialOSLaunchVerificationEvent IncrementPcrPolicyCounter = incrementPcrPolicyCounter - IsDynamicPolicyDataError = isDynamicPolicyDataError - IsStaticPolicyDataError = isStaticPolicyDataError + IsPolicyDataError = isPolicyDataError LockNVIndexAttrs = lockNVIndexAttrs PerformPinChange = performPinChange ReadAndValidateLockNVIndexPublic = readAndValidateLockNVIndexPublic diff --git a/policy.go b/policy.go index 74bacee3..1d1748e1 100644 --- a/policy.go +++ b/policy.go @@ -886,37 +886,20 @@ func computeDynamicPolicy(version uint32, alg tpm2.HashAlgorithmId, input *dynam authorizedPolicySignature: &signature}, nil } -type staticPolicyDataError struct { +type policyDataError struct { err error } -func (e staticPolicyDataError) Error() string { +func (e policyDataError) Error() string { return e.err.Error() } -func (e staticPolicyDataError) Unwrap() error { +func (e policyDataError) Unwrap() error { return e.err } -func isStaticPolicyDataError(err error) bool { - var e staticPolicyDataError - return xerrors.As(err, &e) -} - -type dynamicPolicyDataError struct { - err error -} - -func (e dynamicPolicyDataError) Error() string { - return e.err.Error() -} - -func (e dynamicPolicyDataError) Unwrap() error { - return e.err -} - -func isDynamicPolicyDataError(err error) bool { - var e dynamicPolicyDataError +func isPolicyDataError(err error) bool { + var e policyDataError return xerrors.As(err, &e) } @@ -979,15 +962,15 @@ func executePolicySession(tpm *tpm2.TPMContext, policySession tpm2.SessionContex case tpm2.IsTPMError(err, tpm2.AnyErrorCode, tpm2.CommandPolicyGetDigest): return xerrors.Errorf("cannot execute OR assertions: %w", err) case tpm2.IsTPMParameterError(err, tpm2.ErrorValue, tpm2.CommandPolicyOR, 1): - // The dynamic authorization policy data is invalid. - return dynamicPolicyDataError{errors.New("cannot complete OR assertions: invalid data")} + // The tree of PCR policy digests is invalid (it doesn't have a value that matches the current state) + return policyDataError{errors.New("cannot complete OR assertions for PCR policy: unexpected PCR values")} } - return dynamicPolicyDataError{xerrors.Errorf("cannot complete OR assertions: %w", err)} + return policyDataError{xerrors.Errorf("cannot complete OR assertions: %w", err)} } pcrPolicyCounterHandle := staticInput.pcrPolicyCounterHandle if (pcrPolicyCounterHandle != tpm2.HandleNull || version == 0) && pcrPolicyCounterHandle.Type() != tpm2.HandleTypeNVIndex { - return staticPolicyDataError{errors.New("invalid handle for PCR policy counter")} + return policyDataError{errors.New("invalid handle type for PCR policy counter")} } var policyCounter tpm2.ResourceContext @@ -997,7 +980,7 @@ func executePolicySession(tpm *tpm2.TPMContext, policySession tpm2.SessionContex switch { case tpm2.IsResourceUnavailableError(err, pcrPolicyCounterHandle): // If there is no NV index at the expected handle then the key file is invalid and must be recreated. - return staticPolicyDataError{errors.New("no PCR policy counter found")} + return policyDataError{errors.New("no PCR policy counter found")} case err != nil: return xerrors.Errorf("cannot obtain context for PCR policy counter: %w", err) } @@ -1010,7 +993,7 @@ func executePolicySession(tpm *tpm2.TPMContext, policySession tpm2.SessionContex } if !policyCounterPub.NameAlg.Supported() { //If the NV index has an unsupported name algorithm, then this key file is invalid and must be recreated. - return staticPolicyDataError{errors.New("PCR policy counter has an unsupported name algorithm")} + return policyDataError{errors.New("PCR policy counter has an unsupported name algorithm")} } revocationCheckSession, err = tpm.StartAuthSession(nil, nil, tpm2.SessionTypePolicy, nil, policyCounterPub.NameAlg) @@ -1028,7 +1011,7 @@ func executePolicySession(tpm *tpm2.TPMContext, policySession tpm2.SessionContex if err := tpm.PolicyOR(revocationCheckSession, staticInput.v0PinIndexAuthPolicies); err != nil { if tpm2.IsTPMParameterError(err, tpm2.ErrorValue, tpm2.CommandPolicyOR, 1) { // staticInput.v0PinIndexAuthPolicies is invalid. - return staticPolicyDataError{errors.New("authorization policy metadata for PCR policy counter is invalid")} + return policyDataError{errors.New("authorization policy metadata for PCR policy counter is invalid")} } return xerrors.Errorf("cannot execute assertion for PCR policy revocation check: %w", err) } @@ -1040,10 +1023,10 @@ func executePolicySession(tpm *tpm2.TPMContext, policySession tpm2.SessionContex switch { case tpm2.IsTPMError(err, tpm2.ErrorPolicy, tpm2.CommandPolicyNV): // The PCR policy has been revoked. - return dynamicPolicyDataError{errors.New("the PCR policy has been revoked")} + return policyDataError{errors.New("the PCR policy has been revoked")} case tpm2.IsTPMSessionError(err, tpm2.ErrorPolicyFail, tpm2.CommandPolicyNV, 1): // Either staticInput.v0PinIndexAuthPolicies is invalid or the NV index isn't what's expected, so the key file is invalid. - return staticPolicyDataError{errors.New("invalid PCR policy counter or associated authorization policy metadata")} + return policyDataError{errors.New("invalid PCR policy counter or associated authorization policy metadata")} } return xerrors.Errorf("PCR policy revocation check failed: %w", err) } @@ -1051,13 +1034,13 @@ func executePolicySession(tpm *tpm2.TPMContext, policySession tpm2.SessionContex authPublicKey := staticInput.authPublicKey if !authPublicKey.NameAlg.Supported() { - return staticPolicyDataError{errors.New("public area of dynamic authorization policy signing key has an unsupported name algorithm")} + return policyDataError{errors.New("public area of dynamic authorization policy signing key has an unsupported name algorithm")} } authorizeKey, err := tpm.LoadExternal(nil, authPublicKey, tpm2.HandleOwner) if err != nil { if tpm2.IsTPMParameterError(err, tpm2.AnyErrorCode, tpm2.CommandLoadExternal, 2) { // staticInput.AuthPublicKey is invalid - return staticPolicyDataError{errors.New("public area of dynamic authorization policy signing key is invalid")} + return policyDataError{errors.New("public area of dynamic authorization policy signing key is invalid")} } return xerrors.Errorf("cannot load public area for dynamic authorization policy signing key: %w", err) } @@ -1079,9 +1062,7 @@ func executePolicySession(tpm *tpm2.TPMContext, policySession tpm2.SessionContex if err != nil { if tpm2.IsTPMParameterError(err, tpm2.AnyErrorCode, tpm2.CommandVerifySignature, 2) { // dynamicInput.AuthorizedPolicySignature or the computed policy ref is invalid. - // XXX: It's not possible to determine whether this is broken dynamic or static metadata - - // we should just do away with the distinction here tbh - return dynamicPolicyDataError{errors.New("cannot verify PCR policy signature")} + return policyDataError{errors.New("cannot verify PCR policy signature")} } return xerrors.Errorf("cannot verify PCR policy signature: %w", err) } @@ -1089,7 +1070,7 @@ func executePolicySession(tpm *tpm2.TPMContext, policySession tpm2.SessionContex if err := tpm.PolicyAuthorize(policySession, dynamicInput.authorizedPolicy, pcrPolicyRef, authorizeKey.Name(), authorizeTicket); err != nil { if tpm2.IsTPMParameterError(err, tpm2.ErrorValue, tpm2.CommandPolicyAuthorize, 1) { // dynamicInput.AuthorizedPolicy is invalid. - return dynamicPolicyDataError{errors.New("the PCR policy is invalid")} + return policyDataError{errors.New("the PCR policy is invalid")} } return xerrors.Errorf("PCR policy check failed: %w", err) } diff --git a/policy_test.go b/policy_test.go index 943373ee..e970a683 100644 --- a/policy_test.go +++ b/policy_test.go @@ -1451,7 +1451,7 @@ func TestExecutePolicy(t *testing.T) { data: "foo", }, }}, nil) - if !IsDynamicPolicyDataError(err) || err.Error() != "cannot complete OR assertions: current session digest not found in policy data" { + if !IsPolicyDataError(err) || err.Error() != "cannot complete OR assertions: current session digest not found in policy data" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { @@ -1493,7 +1493,7 @@ func TestExecutePolicy(t *testing.T) { data: "xxx", }, }}, nil) - if !IsDynamicPolicyDataError(err) || err.Error() != "cannot complete OR assertions: current session digest not found in policy data" { + if !IsPolicyDataError(err) || err.Error() != "cannot complete OR assertions: current session digest not found in policy data" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { @@ -1635,7 +1635,7 @@ func TestExecutePolicy(t *testing.T) { data: "foo", }, }}, nil) - if !IsDynamicPolicyDataError(err) || err.Error() != "cannot complete OR assertions: current session digest not found in policy data" { + if !IsPolicyDataError(err) || err.Error() != "cannot complete OR assertions: current session digest not found in policy data" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { @@ -1756,7 +1756,7 @@ func TestExecutePolicy(t *testing.T) { data: "foo", }, }}, nil) - if !IsDynamicPolicyDataError(err) || err.Error() != "the PCR policy has been revoked" { + if !IsPolicyDataError(err) || err.Error() != "the PCR policy has been revoked" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { @@ -1805,7 +1805,7 @@ func TestExecutePolicy(t *testing.T) { t.Fatalf("NVReadLock failed: %v", err) } }) - if !tpm2.IsTPMError(err, tpm2.ErrorNVLocked, tpm2.CommandPolicyNV) || IsStaticPolicyDataError(err) || IsDynamicPolicyDataError(err) { + if !tpm2.IsTPMError(err, tpm2.ErrorNVLocked, tpm2.CommandPolicyNV) { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { @@ -1848,7 +1848,7 @@ func TestExecutePolicy(t *testing.T) { }}, func(s *StaticPolicyData, d *DynamicPolicyData) { s.SetPcrPolicyCounterHandle(0x40ffffff) }) - if !IsStaticPolicyDataError(err) || err.Error() != "invalid handle for PCR policy counter" { + if !IsPolicyDataError(err) || err.Error() != "invalid handle for PCR policy counter" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { @@ -1891,7 +1891,7 @@ func TestExecutePolicy(t *testing.T) { }}, func(s *StaticPolicyData, d *DynamicPolicyData) { s.SetPcrPolicyCounterHandle(s.PcrPolicyCounterHandle() + 1) }) - if !IsStaticPolicyDataError(err) || err.Error() != "no PCR policy counter found" { + if !IsPolicyDataError(err) || err.Error() != "no PCR policy counter found" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { @@ -1935,7 +1935,7 @@ func TestExecutePolicy(t *testing.T) { }}, func(s *StaticPolicyData, d *DynamicPolicyData) { s.AuthPublicKey().NameAlg = tpm2.HashAlgorithmId(tpm2.AlgorithmSM4) }) - if !IsStaticPolicyDataError(err) || err.Error() != "public area of dynamic authorization policy signing key has an unsupported "+ + if !IsPolicyDataError(err) || err.Error() != "public area of dynamic authorization policy signing key has an unsupported "+ "name algorithm" { t.Errorf("Unexpected error: %v", err) } @@ -1986,7 +1986,7 @@ func TestExecutePolicy(t *testing.T) { }) // Even though this error is caused by broken static metadata, we get a dynamicPolicyDataError error because the signature // verification fails. Validation with validateKeyData will detect the real issue though. - if !IsDynamicPolicyDataError(err) || err.Error() != "cannot verify PCR policy signature" { + if !IsPolicyDataError(err) || err.Error() != "cannot verify PCR policy signature" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { @@ -2049,7 +2049,7 @@ func TestExecutePolicy(t *testing.T) { d.AuthorizedPolicySignature().Signature.ECDSA().SignatureR = sigR.Bytes() d.AuthorizedPolicySignature().Signature.ECDSA().SignatureS = sigS.Bytes() }) - if !IsDynamicPolicyDataError(err) || err.Error() != "cannot verify PCR policy signature" { + if !IsPolicyDataError(err) || err.Error() != "cannot verify PCR policy signature" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { @@ -2160,7 +2160,7 @@ func TestExecutePolicy(t *testing.T) { }}, func(s *StaticPolicyData, d *DynamicPolicyData) { d.SetPolicyCount(d.PolicyCount() + 1) }) - if !IsDynamicPolicyDataError(err) || err.Error() != "the PCR policy is invalid" { + if !IsPolicyDataError(err) || err.Error() != "the PCR policy is invalid" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { @@ -2256,7 +2256,7 @@ func TestExecutePolicy(t *testing.T) { }}, func(s *StaticPolicyData, d *DynamicPolicyData) { d.PCROrData()[0].Next = 1000 }) - if !IsDynamicPolicyDataError(err) || err.Error() != "the PCR policy is invalid" { + if !IsPolicyDataError(err) || err.Error() != "the PCR policy is invalid" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { @@ -2308,7 +2308,7 @@ func TestExecutePolicy(t *testing.T) { x := int32(-10) d.PCROrData()[0].Next = *(*uint32)(unsafe.Pointer(&x)) }) - if !IsDynamicPolicyDataError(err) || err.Error() != "the PCR policy is invalid" { + if !IsPolicyDataError(err) || err.Error() != "the PCR policy is invalid" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { @@ -2390,7 +2390,7 @@ func TestExecutePolicy(t *testing.T) { s.SetPcrPolicyCounterHandle(policyCounterPub.Index) d.SetPolicyCount(policyCount) }) - if !IsDynamicPolicyDataError(err) || err.Error() != "cannot verify PCR policy signature" { + if !IsPolicyDataError(err) || err.Error() != "cannot verify PCR policy signature" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { diff --git a/unseal.go b/unseal.go index 9ce07a64..b2a10ced 100644 --- a/unseal.go +++ b/unseal.go @@ -112,10 +112,7 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b if err := executePolicySession(tpm.TPMContext, policySession, k.data.version, k.data.staticPolicyData, k.data.dynamicPolicyData, pin, hmacSession); err != nil { err = xerrors.Errorf("cannot complete authorization policy assertions: %w", err) switch { - case isDynamicPolicyDataError(err): - // TODO: Add a separate error for this - return nil, nil, InvalidKeyFileError{err.Error()} - case isStaticPolicyDataError(err): + case isPolicyDataError(err): return nil, nil, InvalidKeyFileError{err.Error()} case isAuthFailError(err, tpm2.CommandPolicySecret, 1): return nil, nil, ErrPINFail From 0511eb3d559d3f366e1476dbae3b7f1265ec8fc0 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 17 Sep 2020 17:58:22 +0100 Subject: [PATCH 04/13] ActivateVolumeWithTPMSealedKey should attempt repair if UnsealFromTPM fails with InvalidKeyFileError --- crypt.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/crypt.go b/crypt.go index f0a0dc73..011a0076 100644 --- a/crypt.go +++ b/crypt.go @@ -336,14 +336,13 @@ func activateWithRecoveryKey(volumeName, sourceDevicePath string, keyReader io.R func unsealKeyFromTPM(tpm *TPMConnection, k *SealedKeyObject, pin string) ([]byte, []byte, error) { sealedKey, authPrivateKey, err := k.UnsealFromTPM(tpm, pin) if err == ErrTPMProvisioning { - // ErrTPMProvisioning in this context might indicate that there isn't a valid persistent SRK. Have a go at creating one now and then - // retrying the unseal operation - if the previous SRK was evicted, the TPM owner hasn't changed and the storage hierarchy still - // has a null authorization value, then this will allow us to unseal the key without requiring any type of manual recovery. If the - // storage hierarchy has a non-null authorization value, ProvionTPM will fail. If the TPM owner has changed, ProvisionTPM might - // succeed, but UnsealFromTPM will fail with InvalidKeyFileError when retried. - if pErr := ProvisionTPM(tpm, ProvisionModeWithoutLockout, nil); pErr == nil { - sealedKey, authPrivateKey, err = k.UnsealFromTPM(tpm, pin) - } + // ErrTPMProvisioning or InvalidKeyFileError in this context might indicate that there isn't a valid persistent SRK. Have a go + // at creating one now and then retrying the unseal operation - if the proper SRK was evicted, the TPM owner hasn't changed, the + // storage hierarchy still has a null authorization value, and the TPM sealed object is still valid, then this will allow us to + // unseal the key without requiring any type of manual recovery. Ignore provisioning errors here and retry unsealing afterwards - + // it's worth retrying even if provisioning failed, just in case it did enough to allow unsealing to succeed. + ProvisionTPM(tpm, ProvisionModeWithoutLockout, nil) + sealedKey, authPrivateKey, err = k.UnsealFromTPM(tpm, pin) } return sealedKey, authPrivateKey, err } From 635ee3cb3a8e89cc7c820d2d57044ba79cb150d6 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 17 Sep 2020 18:01:37 +0100 Subject: [PATCH 05/13] Test fixes --- policy_test.go | 2 +- unseal.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/policy_test.go b/policy_test.go index e970a683..e8d9ce71 100644 --- a/policy_test.go +++ b/policy_test.go @@ -1848,7 +1848,7 @@ func TestExecutePolicy(t *testing.T) { }}, func(s *StaticPolicyData, d *DynamicPolicyData) { s.SetPcrPolicyCounterHandle(0x40ffffff) }) - if !IsPolicyDataError(err) || err.Error() != "invalid handle for PCR policy counter" { + if !IsPolicyDataError(err) || err.Error() != "invalid handle type for PCR policy counter" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { diff --git a/unseal.go b/unseal.go index b2a10ced..de584598 100644 --- a/unseal.go +++ b/unseal.go @@ -137,6 +137,8 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b return nil, nil, InvalidKeyFileError{"the authorization policy check failed during unsealing"} case isAuthFailError(err, tpm2.CommandUnseal, 1): return nil, nil, ErrPINFail + case tpm2.IsTPMWarning(err, tpm2.WarningLockout, tpm2.CommandUnseal): + return nil, nil, ErrTPMLockout case err != nil: return nil, nil, xerrors.Errorf("cannot unseal key: %w", err) } From ce827879c2034cb8f518edf2b4ed6a0f84a75307 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 17 Sep 2020 19:09:55 +0100 Subject: [PATCH 06/13] Add SealedKeyObject.Validate --- crypt.go | 19 +++- crypt_test.go | 12 +-- errors.go | 29 ++++-- export_test.go | 1 + internal/compattest/v0_test.go | 2 +- keydata.go | 165 ++++++++++++++++++++++----------- pin.go | 10 +- policy.go | 33 ++++--- policy_test.go | 14 +-- seal.go | 10 +- unseal.go | 68 ++++++-------- unseal_test.go | 12 +-- 12 files changed, 229 insertions(+), 146 deletions(-) diff --git a/crypt.go b/crypt.go index 011a0076..1e126b70 100644 --- a/crypt.go +++ b/crypt.go @@ -333,9 +333,14 @@ func activateWithRecoveryKey(volumeName, sourceDevicePath string, keyReader io.R return lastErr } +func isTPMLoadError(err error) bool { + var e InvalidKeyFileError + return xerrors.As(err, &e) && e.Type == InvalidKeyFileErrorTPMLoad +} + func unsealKeyFromTPM(tpm *TPMConnection, k *SealedKeyObject, pin string) ([]byte, []byte, error) { sealedKey, authPrivateKey, err := k.UnsealFromTPM(tpm, pin) - if err == ErrTPMProvisioning { + if err == ErrTPMProvisioning || isTPMLoadError(err) { // ErrTPMProvisioning or InvalidKeyFileError in this context might indicate that there isn't a valid persistent SRK. Have a go // at creating one now and then retrying the unseal operation - if the proper SRK was evicted, the TPM owner hasn't changed, the // storage hierarchy still has a null authorization value, and the TPM sealed object is still valid, then this will allow us to @@ -453,6 +458,16 @@ func makeActivateOptions(in []string) ([]string, error) { return append(out, "tries=1"), nil } +func isInvalidKeyFileError(err error) bool { + var e InvalidKeyFileError + return xerrors.As(err, &e) +} + +func isInvalidPolicyDataError(err error) bool { + var e InvalidPolicyDataError + return xerrors.As(err, &e) +} + // ActivateWithTPMSealedKeyOptions provides options to ActivateVolumeWtthTPMSealedKey. type ActivateWithTPMSealedKeyOptions struct { // PINTries specifies the maximum number of times that unsealing with a PIN should be attempted before failing with an error and @@ -537,6 +552,8 @@ func ActivateVolumeWithTPMSealedKey(tpm *TPMConnection, volumeName, sourceDevice reason = RecoveryKeyUsageReasonTPMProvisioningError case isInvalidKeyFileError(err): reason = RecoveryKeyUsageReasonInvalidKeyFile + case isInvalidPolicyDataError(err): + reason = RecoveryKeyUsageReasonInvalidKeyFile case xerrors.Is(err, requiresPinErr): reason = RecoveryKeyUsageReasonPINFail case xerrors.Is(err, ErrPINFail): diff --git a/crypt_test.go b/crypt_test.go index f94f7627..faf156f5 100644 --- a/crypt_test.go +++ b/crypt_test.go @@ -797,9 +797,9 @@ func (s *cryptTPMSimulatorSuite) TestActivateVolumeWithTPMSealedKeyErrorHandling success: true, recoveryReason: RecoveryKeyUsageReasonInvalidKeyFile, errChecker: ErrorMatches, - errCheckerArgs: []interface{}{"cannot activate with TPM sealed key \\(cannot unseal key: invalid key data file: cannot complete " + - "authorization policy assertions: cannot complete OR assertions: current session digest not found in policy data\\) but " + - "activation with recovery key was successful"}, + errCheckerArgs: []interface{}{"cannot activate with TPM sealed key \\(cannot unseal key: invalid authorization policy data: cannot " + + "complete authorization policy assertions: cannot complete OR assertions for PCR policy: current session digest not found in " + + "policy data\\) but activation with recovery key was successful"}, }) } @@ -817,9 +817,9 @@ func (s *cryptTPMSimulatorSuite) TestActivateVolumeWithTPMSealedKeyErrorHandling success: true, recoveryReason: RecoveryKeyUsageReasonInvalidKeyFile, errChecker: ErrorMatches, - errCheckerArgs: []interface{}{"cannot activate with TPM sealed key \\(cannot unseal key: invalid key data file: cannot complete " + - "authorization policy assertions: cannot complete OR assertions: current session digest not found in policy data\\) but " + - "activation with recovery key was successful"}, + errCheckerArgs: []interface{}{"cannot activate with TPM sealed key \\(cannot unseal key: invalid authorization policy data: cannot " + + "complete authorization policy assertions: cannot complete OR assertions for PCR policy: current session digest not found in " + + "policy data\\) but activation with recovery key was successful"}, }) } diff --git a/errors.go b/errors.go index 287e4415..d1373b03 100644 --- a/errors.go +++ b/errors.go @@ -113,20 +113,33 @@ func isTPMVerificationError(err error) bool { return xerrors.As(err, &e) } -// InvalidKeyFileError indicates that the provided key data file is invalid. This error may also be returned in some -// scenarious where the TPM is incorrectly provisioned, but it isn't possible to determine whether the error is with -// the provisioning status or because the key data file is invalid. +// InvalidKeyFileErrorType corresponds to the type of error detailed by InvalidKeyFileError. +type InvalidKeyFileErrorType int + +const ( + // InvalidKeyFileErrorFatal indicates that the error was detected by software and is definitely caused by invalid key data. + InvalidKeyFileErrorFatal = iota + 1 + + // InvalidKeyFileErrorTPMLoad indicates that the error was detected when loading the sealed key object in to the TPM, and may + // indicate that the key data is invalid, or may indicate that the object at the persistent handle reserved for the storage + // root key is not the creation parent of the sealed key object. In this case, calling ProvisionTPM may rectify this problem. + InvalidKeyFileErrorTPMLoad +) + +// InvalidKeyFileError indicates that the provided key data file is invalid. type InvalidKeyFileError struct { - msg string + Type InvalidKeyFileErrorType + msg string } func (e InvalidKeyFileError) Error() string { - return fmt.Sprintf("invalid key data file: %s", e.msg) + return "invalid key data file: " + e.msg } -func isInvalidKeyFileError(err error) bool { - var e InvalidKeyFileError - return xerrors.As(err, &e) +type InvalidPolicyDataError string + +func (e InvalidPolicyDataError) Error() string { + return "invalid authorization policy data: " + string(e) } // LockAccessToSealedKeysError is returned from ActivateVolumeWithTPMSealedKey if an error occurred whilst trying to lock access diff --git a/export_test.go b/export_test.go index 86732325..2a90765b 100644 --- a/export_test.go +++ b/export_test.go @@ -59,6 +59,7 @@ var ( ExecutePolicySession = executePolicySession IdentifyInitialOSLaunchVerificationEvent = identifyInitialOSLaunchVerificationEvent IncrementPcrPolicyCounter = incrementPcrPolicyCounter + IsKeyDataError = isKeyDataError IsPolicyDataError = isPolicyDataError LockNVIndexAttrs = lockNVIndexAttrs PerformPinChange = performPinChange diff --git a/internal/compattest/v0_test.go b/internal/compattest/v0_test.go index 85abc623..28cf5cf6 100644 --- a/internal/compattest/v0_test.go +++ b/internal/compattest/v0_test.go @@ -73,7 +73,7 @@ func (s *compatTestV0Suite) TestUpdateKeyPCRProtectionPolicyRevokes(c *C) { c.Check(secboot.UpdateKeyPCRProtectionPolicyV0(s.TPM, key2, s.absPath("pud"), profile), IsNil) s.replayPCRSequenceFromFile(c, s.absPath("pcrSequence.1")) - s.testUnsealErrorMatchesCommon(c, "invalid key data file: cannot complete authorization policy assertions: the PCR policy has been revoked") + s.testUnsealErrorMatchesCommon(c, "invalid authorization policy data: cannot complete authorization policy assertions: the PCR policy has been revoked") } func (s *compatTestV0Suite) TestUpdateKeyPCRProtectionPolicyAndUnseal(c *C) { diff --git a/keydata.go b/keydata.go index a6f419d9..08a08d07 100644 --- a/keydata.go +++ b/keydata.go @@ -357,6 +357,40 @@ func (d *keyData) Unmarshal(r io.Reader) (nbytes int, err error) { return } +type keyDataError struct { + err error +} + +func (e keyDataError) Error() string { + return e.err.Error() +} + +func (e keyDataError) Unwrap() error { + return e.err +} + +func isKeyDataError(err error) bool { + var e keyDataError + return xerrors.As(err, &e) +} + +type keyDataLoadError struct { + err error +} + +func (e keyDataLoadError) Error() string { + return e.err.Error() +} + +func (e keyDataLoadError) Unwrap() error { + return e.err +} + +func isKeyDataLoadError(err error) bool { + var e keyDataLoadError + return xerrors.As(err, &e) +} + // load loads the TPM sealed object associated with this keyData in to the storage hierarchy of the TPM, and returns the newly // created tpm2.ResourceContext. func (d *keyData) load(tpm *tpm2.TPMContext, session tpm2.SessionContext) (tpm2.ResourceContext, error) { @@ -375,7 +409,7 @@ func (d *keyData) load(tpm *tpm2.TPMContext, session tpm2.SessionContext) (tpm2. invalidObject = true } if invalidObject { - return nil, keyFileError{errors.New("cannot load sealed key object in to TPM: bad sealed key object or TPM owner changed")} + return nil, keyDataLoadError{errors.New("cannot load sealed key object in to TPM: bad sealed key object or TPM owner changed")} } return nil, xerrors.Errorf("cannot load sealed key object in to TPM: %w", err) } @@ -387,7 +421,7 @@ func (d *keyData) load(tpm *tpm2.TPMContext, session tpm2.SessionContext) (tpm2. // for the PCR policy counter. func (d *keyData) validate(tpm *tpm2.TPMContext, authKey crypto.PrivateKey, session tpm2.SessionContext) (*tpm2.NVPublic, error) { if d.version > currentMetadataVersion { - return nil, keyFileError{errors.New("invalid metadata version")} + return nil, keyDataError{errors.New("invalid metadata version")} } sealedKeyTemplate := makeSealedKeyTemplate() @@ -396,10 +430,10 @@ func (d *keyData) validate(tpm *tpm2.TPMContext, authKey crypto.PrivateKey, sess // Perform some initial checks on the sealed data object's public area if keyPublic.Type != sealedKeyTemplate.Type { - return nil, keyFileError{errors.New("sealed key object has the wrong type")} + return nil, keyDataError{errors.New("sealed key object has the wrong type")} } if keyPublic.Attrs != sealedKeyTemplate.Attrs { - return nil, keyFileError{errors.New("sealed key object has the wrong attributes")} + return nil, keyDataError{errors.New("sealed key object has the wrong attributes")} } // Load the sealed data object in to the TPM for integrity checking @@ -433,7 +467,7 @@ func (d *keyData) validate(tpm *tpm2.TPMContext, authKey crypto.PrivateKey, sess // revocation, and also for PIN integration with v0 metadata only. pcrPolicyCounterHandle := d.staticPolicyData.pcrPolicyCounterHandle if (pcrPolicyCounterHandle != tpm2.HandleNull || d.version == 0) && pcrPolicyCounterHandle.Type() != tpm2.HandleTypeNVIndex { - return nil, keyFileError{errors.New("PCR policy counter handle is invalid")} + return nil, keyDataError{errors.New("PCR policy counter handle is invalid")} } var pcrPolicyCounter tpm2.ResourceContext @@ -441,7 +475,7 @@ func (d *keyData) validate(tpm *tpm2.TPMContext, authKey crypto.PrivateKey, sess pcrPolicyCounter, err = tpm.CreateResourceContextFromTPM(pcrPolicyCounterHandle, session.IncludeAttrs(tpm2.AttrAudit)) if err != nil { if tpm2.IsResourceUnavailableError(err, pcrPolicyCounterHandle) { - return nil, keyFileError{errors.New("PCR policy counter is unavailable")} + return nil, keyDataError{errors.New("PCR policy counter is unavailable")} } return nil, xerrors.Errorf("cannot create context for PCR policy counter: %w", err) } @@ -456,7 +490,7 @@ func (d *keyData) validate(tpm *tpm2.TPMContext, authKey crypto.PrivateKey, sess authPublicKey := d.staticPolicyData.authPublicKey authKeyName, err := authPublicKey.Name() if err != nil { - return nil, keyFileError{xerrors.Errorf("cannot compute name of dynamic authorization policy key: %w", err)} + return nil, keyDataError{xerrors.Errorf("cannot compute name of dynamic authorization policy key: %w", err)} } var expectedAuthKeyType tpm2.ObjectTypeId var expectedAuthKeyScheme tpm2.AsymSchemeId @@ -469,22 +503,22 @@ func (d *keyData) validate(tpm *tpm2.TPMContext, authKey crypto.PrivateKey, sess expectedAuthKeyScheme = tpm2.AsymSchemeECDSA } if authPublicKey.Type != expectedAuthKeyType { - return nil, keyFileError{errors.New("public area of dynamic authorization policy signing key has the wrong type")} + return nil, keyDataError{errors.New("public area of dynamic authorization policy signing key has the wrong type")} } authKeyScheme := authPublicKey.Params.AsymDetail().Scheme if authKeyScheme.Scheme != tpm2.AsymSchemeNull { if authKeyScheme.Scheme != expectedAuthKeyScheme { - return nil, keyFileError{errors.New("dynamic authorization policy signing key has unexpected scheme")} + return nil, keyDataError{errors.New("dynamic authorization policy signing key has unexpected scheme")} } if authKeyScheme.Details.Any().HashAlg != authPublicKey.NameAlg { - return nil, keyFileError{errors.New("dynamic authorization policy signing key algorithm must match name algorithm")} + return nil, keyDataError{errors.New("dynamic authorization policy signing key algorithm must match name algorithm")} } } // Make sure that the static authorization policy data is consistent with the sealed key object's policy. trial, err := tpm2.ComputeAuthPolicy(keyPublic.NameAlg) if err != nil { - return nil, keyFileError{xerrors.Errorf("cannot determine if static authorization policy matches sealed key object: %w", err)} + return nil, keyDataError{xerrors.Errorf("cannot determine if static authorization policy matches sealed key object: %w", err)} } trial.PolicyAuthorize(pcrPolicyRef, authKeyName) @@ -497,7 +531,7 @@ func (d *keyData) validate(tpm *tpm2.TPMContext, authKey crypto.PrivateKey, sess trial.PolicyNV(lockIndexName, nil, 0, tpm2.OpEq) if !bytes.Equal(trial.GetDigest(), keyPublic.AuthPolicy) { - return nil, keyFileError{errors.New("the sealed key object's authorization policy is inconsistent with the associated metadata or persistent TPM resources")} + return nil, keyDataError{errors.New("the sealed key object's authorization policy is inconsistent with the associated metadata or persistent TPM resources")} } // Read the public area of the PCR policy counter @@ -514,21 +548,21 @@ func (d *keyData) validate(tpm *tpm2.TPMContext, authKey crypto.PrivateKey, sess pcrPolicyCounterAuthPolicies := d.staticPolicyData.v0PinIndexAuthPolicies expectedPcrPolicyCounterAuthPolicies, err := computeV0PinNVIndexPostInitAuthPolicies(pcrPolicyCounterPub.NameAlg, authKeyName) if err != nil { - return nil, keyFileError{xerrors.Errorf("cannot determine if PCR policy counter has a valid authorization policy: %w", err)} + return nil, keyDataError{xerrors.Errorf("cannot determine if PCR policy counter has a valid authorization policy: %w", err)} } if len(pcrPolicyCounterAuthPolicies)-1 != len(expectedPcrPolicyCounterAuthPolicies) { - return nil, keyFileError{errors.New("unexpected number of OR policy digests for PCR policy counter")} + return nil, keyDataError{errors.New("unexpected number of OR policy digests for PCR policy counter")} } for i, expected := range expectedPcrPolicyCounterAuthPolicies { if !bytes.Equal(expected, pcrPolicyCounterAuthPolicies[i+1]) { - return nil, keyFileError{errors.New("unexpected OR policy digest for PCR policy counter")} + return nil, keyDataError{errors.New("unexpected OR policy digest for PCR policy counter")} } } trial, _ = tpm2.ComputeAuthPolicy(pcrPolicyCounterPub.NameAlg) trial.PolicyOR(pcrPolicyCounterAuthPolicies) if !bytes.Equal(pcrPolicyCounterPub.AuthPolicy, trial.GetDigest()) { - return nil, keyFileError{errors.New("PCR policy counter has unexpected authorization policy")} + return nil, keyDataError{errors.New("PCR policy counter has unexpected authorization policy")} } } @@ -541,19 +575,19 @@ func (d *keyData) validate(tpm *tpm2.TPMContext, authKey crypto.PrivateKey, sess N: new(big.Int).SetBytes(authPublicKey.Unique.RSA()), E: int(authPublicKey.Params.RSADetail().Exponent)} if k.E != goAuthPublicKey.E || k.N.Cmp(goAuthPublicKey.N) != 0 { - return nil, keyFileError{errors.New("dynamic authorization policy signing private key doesn't match public key")} + return nil, keyDataError{errors.New("dynamic authorization policy signing private key doesn't match public key")} } case *ecdsa.PrivateKey: if d.version == 0 { - return nil, keyFileError{errors.New("unexpected dynamic authorization policy signing private key type")} + return nil, keyDataError{errors.New("unexpected dynamic authorization policy signing private key type")} } expectedX, expectedY := k.Curve.ScalarBaseMult(k.D.Bytes()) if expectedX.Cmp(k.X) != 0 || expectedY.Cmp(k.Y) != 0 { - return nil, keyFileError{errors.New("dynamic authorization policy signing private key doesn't match public key")} + return nil, keyDataError{errors.New("dynamic authorization policy signing private key doesn't match public key")} } case nil: default: - return nil, keyFileError{errors.New("unexpected dynamic authorization policy signing private key type")} + return nil, keyDataError{errors.New("unexpected dynamic authorization policy signing private key type")} } return pcrPolicyCounterPub, nil @@ -586,6 +620,35 @@ func (d *keyData) writeToFileAtomic(dest string) error { return nil } +// decodeAuthKeyFromReader decodes the dynamic authorization policy signing key from r. It expects that the data +// read from r is deserialized in to a keyPolicyUpdateData structure. +func (d *keyData) decodeAuthKeyFromReader(r io.Reader) (crypto.PrivateKey, error) { + if r == nil { + return nil, nil + } + policyUpdateData, err := decodeKeyPolicyUpdateData(r) + if err != nil { + return nil, err + } + if policyUpdateData.version != d.version { + return nil, errors.New("mismatched metadata versions") + } + return policyUpdateData.authKey, nil +} + +// decodeAuthKeyFromBytes decodes the dynamic authorization policy signing key from r. It expects that this contains +// the private part of an elliptic curve key. +func (d *keyData) decodeAuthKeyFromBytes(authKey TPMPolicyAuthKey) (crypto.PrivateKey, error) { + if len(authKey) == 0 { + return nil, nil + } + key, err := createECDSAPrivateKeyFromTPM(d.staticPolicyData.authPublicKey, tpm2.ECCParameter(authKey)) + if err != nil { + return nil, err + } + return key, nil +} + // decodeKeyData deserializes keyData from the provided io.Reader. func decodeKeyData(r io.Reader) (*keyData, error) { var header uint32 @@ -604,23 +667,6 @@ func decodeKeyData(r io.Reader) (*keyData, error) { return &d, nil } -type keyFileError struct { - err error -} - -func (e keyFileError) Error() string { - return e.err.Error() -} - -func (e keyFileError) Unwrap() error { - return e.err -} - -func isKeyFileError(err error) bool { - var e keyFileError - return xerrors.As(err, &e) -} - // decodeAndValidateKeyData will deserialize keyData from the provided io.Reader and then perform some correctness checking. On // success, it returns the keyData, dynamic authorization policy signing key (if authData is provided) and the validated public area // of the PCR policy counter index. @@ -628,31 +674,21 @@ func decodeAndValidateKeyData(tpm *tpm2.TPMContext, keyFile io.Reader, authData // Read the key data data, err := decodeKeyData(keyFile) if err != nil { - return nil, nil, nil, keyFileError{xerrors.Errorf("cannot read key data: %w", err)} + return nil, nil, nil, keyDataError{xerrors.Errorf("cannot read key data: %w", err)} } var authKey crypto.PrivateKey switch a := authData.(type) { case io.Reader: - // If we were called with an io.Reader, then we're expecting to load a legacy version-0 keydata and associated - // private key file. - policyUpdateData, err := decodeKeyPolicyUpdateData(a) + authKey, err = data.decodeAuthKeyFromReader(a) if err != nil { - return nil, nil, nil, keyFileError{xerrors.Errorf("cannot read dynamic policy update data: %w", err)} - } - if policyUpdateData.version != data.version { - return nil, nil, nil, keyFileError{errors.New("mismatched metadata versions")} + return nil, nil, nil, keyDataError{xerrors.Errorf("cannot decode dynamic auth policy signing key data: %w", err)} } - authKey = policyUpdateData.authKey case TPMPolicyAuthKey: - if len(a) > 0 { - // If we were called with a byte slice, then we're expecting to load the current keydata version and the byte - // slice is the private part of the elliptic auth key. - authKey, err = createECDSAPrivateKeyFromTPM(data.staticPolicyData.authPublicKey, tpm2.ECCParameter(a)) - if err != nil { - return nil, nil, nil, keyFileError{xerrors.Errorf("cannot create auth key: %w", err)} - } + authKey, err = data.decodeAuthKeyFromBytes(a) + if err != nil { + return nil, nil, nil, keyDataError{xerrors.Errorf("cannot decode dynamic auth policy signing key: %w", err)} } case nil: default: @@ -689,6 +725,27 @@ func (k *SealedKeyObject) PCRPolicyCounterHandle() tpm2.Handle { return k.data.staticPolicyData.pcrPolicyCounterHandle } +// Validate performs some checks on the SealedKeyObject. The key used for authorizing PCR policy updates can be optionally provided as +// authKey in order to validate that it is associated with this sealed key object. +// +// On success, no error is returned. If a InvalidKeyFileError error is returned, then this indicates that the SealedKeyObject is +// invalid and must be recreated in order to perform some operations on it. +func (k *SealedKeyObject) Validate(tpm *TPMConnection, authKey TPMPolicyAuthKey) error { + key, err := k.data.decodeAuthKeyFromBytes(authKey) + if err != nil { + return InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: fmt.Sprintf("cannot decode dynamic auth policy signing key: %v", err)} + } + _, err = k.data.validate(tpm.TPMContext, key, tpm.HmacSession()) + switch { + case isKeyDataLoadError(err): + return InvalidKeyFileError{Type: InvalidKeyFileErrorTPMLoad, msg: err.Error()} + case isKeyDataError(err): + return InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: err.Error()} + default: + return err + } +} + // ReadSealedKeyObject loads a sealed key data file created by SealKeyToTPM from the specified path. If the file cannot be opened, // a wrapped *os.PathError error is returned. If the key data file cannot be deserialized successfully, a InvalidKeyFileError error // will be returned. @@ -702,7 +759,7 @@ func ReadSealedKeyObject(path string) (*SealedKeyObject, error) { data, err := decodeKeyData(f) if err != nil { - return nil, InvalidKeyFileError{err.Error()} + return nil, InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: err.Error()} } return &SealedKeyObject{data: data}, nil diff --git a/pin.go b/pin.go index 88e2581c..8be980f2 100644 --- a/pin.go +++ b/pin.go @@ -169,10 +169,12 @@ func ChangePIN(tpm *TPMConnection, path string, oldPIN, newPIN string) error { // Read and validate the key data file data, _, pcrPolicyCounterPub, err := decodeAndValidateKeyData(tpm.TPMContext, keyFile, nil, tpm.HmacSession()) - if err != nil { - if isKeyFileError(err) { - return InvalidKeyFileError{err.Error()} - } + switch { + case isKeyDataLoadError(err): + return InvalidKeyFileError{Type: InvalidKeyFileErrorTPMLoad, msg: err.Error()} + case isKeyDataError(err): + return InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: err.Error()} + case err != nil: return xerrors.Errorf("cannot read and validate key data file: %w", err) } diff --git a/policy.go b/policy.go index 1d1748e1..1a0ae07b 100644 --- a/policy.go +++ b/policy.go @@ -962,15 +962,16 @@ func executePolicySession(tpm *tpm2.TPMContext, policySession tpm2.SessionContex case tpm2.IsTPMError(err, tpm2.AnyErrorCode, tpm2.CommandPolicyGetDigest): return xerrors.Errorf("cannot execute OR assertions: %w", err) case tpm2.IsTPMParameterError(err, tpm2.ErrorValue, tpm2.CommandPolicyOR, 1): - // The tree of PCR policy digests is invalid (it doesn't have a value that matches the current state) - return policyDataError{errors.New("cannot complete OR assertions for PCR policy: unexpected PCR values")} + // The tree of policy digests is invalid + return policyDataError{errors.New("cannot complete OR assertions for PCR policy: invalid metadata")} } - return policyDataError{xerrors.Errorf("cannot complete OR assertions: %w", err)} + // The tree of policy digests doesn't contain an entry for the current digest + return policyDataError{xerrors.Errorf("cannot complete OR assertions for PCR policy: %w", err)} } pcrPolicyCounterHandle := staticInput.pcrPolicyCounterHandle if (pcrPolicyCounterHandle != tpm2.HandleNull || version == 0) && pcrPolicyCounterHandle.Type() != tpm2.HandleTypeNVIndex { - return policyDataError{errors.New("invalid handle type for PCR policy counter")} + return keyDataError{errors.New("invalid handle type for PCR policy counter")} } var policyCounter tpm2.ResourceContext @@ -979,8 +980,8 @@ func executePolicySession(tpm *tpm2.TPMContext, policySession tpm2.SessionContex policyCounter, err = tpm.CreateResourceContextFromTPM(pcrPolicyCounterHandle) switch { case tpm2.IsResourceUnavailableError(err, pcrPolicyCounterHandle): - // If there is no NV index at the expected handle then the key file is invalid and must be recreated. - return policyDataError{errors.New("no PCR policy counter found")} + // If there is no NV index at the expected handle then the key data is invalid. + return keyDataError{errors.New("no PCR policy counter found")} case err != nil: return xerrors.Errorf("cannot obtain context for PCR policy counter: %w", err) } @@ -992,8 +993,8 @@ func executePolicySession(tpm *tpm2.TPMContext, policySession tpm2.SessionContex return xerrors.Errorf("cannot read public area for PCR policy counter: %w", err) } if !policyCounterPub.NameAlg.Supported() { - //If the NV index has an unsupported name algorithm, then this key file is invalid and must be recreated. - return policyDataError{errors.New("PCR policy counter has an unsupported name algorithm")} + // If the NV index has an unsupported name algorithm, then the key data is invalid. + return keyDataError{errors.New("PCR policy counter has an unsupported name algorithm")} } revocationCheckSession, err = tpm.StartAuthSession(nil, nil, tpm2.SessionTypePolicy, nil, policyCounterPub.NameAlg) @@ -1011,7 +1012,7 @@ func executePolicySession(tpm *tpm2.TPMContext, policySession tpm2.SessionContex if err := tpm.PolicyOR(revocationCheckSession, staticInput.v0PinIndexAuthPolicies); err != nil { if tpm2.IsTPMParameterError(err, tpm2.ErrorValue, tpm2.CommandPolicyOR, 1) { // staticInput.v0PinIndexAuthPolicies is invalid. - return policyDataError{errors.New("authorization policy metadata for PCR policy counter is invalid")} + return keyDataError{errors.New("authorization policy metadata for PCR policy counter is invalid")} } return xerrors.Errorf("cannot execute assertion for PCR policy revocation check: %w", err) } @@ -1022,11 +1023,12 @@ func executePolicySession(tpm *tpm2.TPMContext, policySession tpm2.SessionContex if err := tpm.PolicyNV(policyCounter, policyCounter, policySession, operandB, 0, tpm2.OpUnsignedLE, revocationCheckSession); err != nil { switch { case tpm2.IsTPMError(err, tpm2.ErrorPolicy, tpm2.CommandPolicyNV): - // The PCR policy has been revoked. + // The PCR policy has been revoked. Note that this could happen if the keydata / NV index is invalid, but it's worth + // not assuming that for now (SealedKeyObject.Validate can detect this) return policyDataError{errors.New("the PCR policy has been revoked")} case tpm2.IsTPMSessionError(err, tpm2.ErrorPolicyFail, tpm2.CommandPolicyNV, 1): - // Either staticInput.v0PinIndexAuthPolicies is invalid or the NV index isn't what's expected, so the key file is invalid. - return policyDataError{errors.New("invalid PCR policy counter or associated authorization policy metadata")} + // Either staticInput.v0PinIndexAuthPolicies is invalid or the NV index isn't what's expected, so the key data is invalid. + return keyDataError{errors.New("invalid PCR policy counter or associated authorization policy metadata")} } return xerrors.Errorf("PCR policy revocation check failed: %w", err) } @@ -1034,13 +1036,13 @@ func executePolicySession(tpm *tpm2.TPMContext, policySession tpm2.SessionContex authPublicKey := staticInput.authPublicKey if !authPublicKey.NameAlg.Supported() { - return policyDataError{errors.New("public area of dynamic authorization policy signing key has an unsupported name algorithm")} + return keyDataError{errors.New("public area of dynamic authorization policy signing key has an unsupported name algorithm")} } authorizeKey, err := tpm.LoadExternal(nil, authPublicKey, tpm2.HandleOwner) if err != nil { if tpm2.IsTPMParameterError(err, tpm2.AnyErrorCode, tpm2.CommandLoadExternal, 2) { // staticInput.AuthPublicKey is invalid - return policyDataError{errors.New("public area of dynamic authorization policy signing key is invalid")} + return keyDataError{errors.New("public area of dynamic authorization policy signing key is invalid")} } return xerrors.Errorf("cannot load public area for dynamic authorization policy signing key: %w", err) } @@ -1061,7 +1063,8 @@ func executePolicySession(tpm *tpm2.TPMContext, policySession tpm2.SessionContex authorizeTicket, err := tpm.VerifySignature(authorizeKey, h.Sum(nil), dynamicInput.authorizedPolicySignature) if err != nil { if tpm2.IsTPMParameterError(err, tpm2.AnyErrorCode, tpm2.CommandVerifySignature, 2) { - // dynamicInput.AuthorizedPolicySignature or the computed policy ref is invalid. + // dynamicInput.AuthorizedPolicySignature, the signing key, or the PCR policy counter is invalid. Assume it is the former + // for now (SealedKeyObject.Validate can detect the others). return policyDataError{errors.New("cannot verify PCR policy signature")} } return xerrors.Errorf("cannot verify PCR policy signature: %w", err) diff --git a/policy_test.go b/policy_test.go index e8d9ce71..6e432b0a 100644 --- a/policy_test.go +++ b/policy_test.go @@ -1451,7 +1451,7 @@ func TestExecutePolicy(t *testing.T) { data: "foo", }, }}, nil) - if !IsPolicyDataError(err) || err.Error() != "cannot complete OR assertions: current session digest not found in policy data" { + if !IsPolicyDataError(err) || err.Error() != "cannot complete OR assertions for PCR policy: current session digest not found in policy data" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { @@ -1493,7 +1493,7 @@ func TestExecutePolicy(t *testing.T) { data: "xxx", }, }}, nil) - if !IsPolicyDataError(err) || err.Error() != "cannot complete OR assertions: current session digest not found in policy data" { + if !IsPolicyDataError(err) || err.Error() != "cannot complete OR assertions for PCR policy: current session digest not found in policy data" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { @@ -1635,7 +1635,7 @@ func TestExecutePolicy(t *testing.T) { data: "foo", }, }}, nil) - if !IsPolicyDataError(err) || err.Error() != "cannot complete OR assertions: current session digest not found in policy data" { + if !IsPolicyDataError(err) || err.Error() != "cannot complete OR assertions for PCR policy: current session digest not found in policy data" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { @@ -1848,7 +1848,7 @@ func TestExecutePolicy(t *testing.T) { }}, func(s *StaticPolicyData, d *DynamicPolicyData) { s.SetPcrPolicyCounterHandle(0x40ffffff) }) - if !IsPolicyDataError(err) || err.Error() != "invalid handle type for PCR policy counter" { + if !IsKeyDataError(err) || err.Error() != "invalid handle type for PCR policy counter" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { @@ -1891,7 +1891,7 @@ func TestExecutePolicy(t *testing.T) { }}, func(s *StaticPolicyData, d *DynamicPolicyData) { s.SetPcrPolicyCounterHandle(s.PcrPolicyCounterHandle() + 1) }) - if !IsPolicyDataError(err) || err.Error() != "no PCR policy counter found" { + if !IsKeyDataError(err) || err.Error() != "no PCR policy counter found" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { @@ -1935,7 +1935,7 @@ func TestExecutePolicy(t *testing.T) { }}, func(s *StaticPolicyData, d *DynamicPolicyData) { s.AuthPublicKey().NameAlg = tpm2.HashAlgorithmId(tpm2.AlgorithmSM4) }) - if !IsPolicyDataError(err) || err.Error() != "public area of dynamic authorization policy signing key has an unsupported "+ + if !IsKeyDataError(err) || err.Error() != "public area of dynamic authorization policy signing key has an unsupported "+ "name algorithm" { t.Errorf("Unexpected error: %v", err) } @@ -1984,7 +1984,7 @@ func TestExecutePolicy(t *testing.T) { } s.AuthPublicKey().Unique.Data = &tpm2.ECCPoint{X: key.X.Bytes(), Y: key.Y.Bytes()} }) - // Even though this error is caused by broken static metadata, we get a dynamicPolicyDataError error because the signature + // Even though this error is caused by broken static metadata, we get a policyDataError error because the signature // verification fails. Validation with validateKeyData will detect the real issue though. if !IsPolicyDataError(err) || err.Error() != "cannot verify PCR policy signature" { t.Errorf("Unexpected error: %v", err) diff --git a/seal.go b/seal.go index 6762651f..de4802e8 100644 --- a/seal.go +++ b/seal.go @@ -321,10 +321,12 @@ func updateKeyPCRProtectionPolicyCommon(tpm *tpm2.TPMContext, keyPath string, au defer keyFile.Close() data, authKey, pcrPolicyCounterPub, err := decodeAndValidateKeyData(tpm, keyFile, authData, session) - if err != nil { - if isKeyFileError(err) { - return InvalidKeyFileError{err.Error()} - } + switch { + case isKeyDataLoadError(err): + return InvalidKeyFileError{Type: InvalidKeyFileErrorTPMLoad, msg: err.Error()} + case isKeyDataError(err): + return InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: err.Error()} + case err != nil: // FIXME: Turn the missing lock NV index in to ErrProvisioning return xerrors.Errorf("cannot read and validate key data file: %w", err) } diff --git a/unseal.go b/unseal.go index de584598..6a18949a 100644 --- a/unseal.go +++ b/unseal.go @@ -36,27 +36,25 @@ import ( // called to attempt to resolve this. // // If the TPM sealed object cannot be loaded in to the TPM for reasons other than the lack of a storage root key, then a -// InvalidKeyFileError error will be returned. This could be caused because the sealed object data is invalid in some way, or because -// the sealed object is associated with another TPM owner (the TPM has been cleared since the sealed key data file was created with -// SealKeyToTPM), or because the TPM object at the persistent handle reserved for the storage root key has a public area that looks -// like a valid storage root key but it was created with the wrong template. This latter case is really caused by an incorrectly -// provisioned TPM, but it isn't possible to detect this. A subsequent call to SealKeyToTPM or ProvisionTPM will rectify this. +// InvalidKeyFileError error will be returned with Type set to InvalidKeyFileErrorTPMLoad. This could be caused because the sealed +// object data is invalid in some way, or because the TPM object at the persistent handle reserved for the storage root key is not +// the creation parent of the TPM sealed object. In the latter case, it may be possible to recover access to the sealed key by +// calling ProvisionTPM again as long as the TPM owner hasn't changed (ie, the TPM has not been cleared). // -// If the TPM's current PCR values are not consistent with the PCR protection policy for this key file, a InvalidKeyFileError error -// will be returned. -// -// If any of the metadata in this key file is invalid, a InvalidKeyFileError error will be returned. +// If the TPM's current PCR values are not consistent with the PCR protection policy for this key object, a InvalidPolicyDataError +// error will be returned. // -// If the TPM is missing any persistent resources associated with this key file, then a InvalidKeyFileError error will be returned. +// If any of the metadata in this key object is invalid, or if the TPM is missing any persistent resources associated with this key +// object, a InvalidKeyFileError error with Type set to InvalidKeyFileErrorFatal will be returned. In this case, there isn't a way +// to recover this key. // -// If the key file has been superceded (eg, by a call to UpdateKeyPCRProtectionPolicy), then a InvalidKeyFileError error will be +// If the PCR policy has been superceded (eg, by a call to UpdateKeyPCRProtectionPolicy), then a InvalidPolicyDataError error will be // returned. // -// If the signature of the updatable part of the key file's authorization policy is invalid, then a InvalidKeyFileError error will -// be returned. +// If the signature of the key object's authorized PCR policy or any metadata associated with it is invalid, then a +// InvalidPolicyDataError error will be returned. // -// If the metadata for the updatable part of the key file's authorization policy is not consistent with the approved policy, then a -// InvalidKeyFileError error will be returned. +// If the PCR policy is not consistent with the current PCR values, then a InvalidPolicyDataError error will be returned. // // If the provided PIN is incorrect, then a ErrPINFail error will be returned and the TPM's dictionary attack counter will be // incremented. @@ -64,9 +62,8 @@ import ( // If access to sealed key objects created by this package is disallowed until the next TPM reset or TPM restart, then a // ErrSealedKeyAccessLocked error will be returned. // -// If the authorization policy check fails during unsealing, then a InvalidKeyFileError error will be returned. Note that this -// condition can also occur as the result of an incorrectly provisioned TPM, which will be detected during a subsequent call to -// SealKeyToTPM. +// If the authorization policy check fails during unsealing, then a InvalidKeyFileError with Type set to InvalidKeyFileErrorFatal +// will be returned. // // On success, the unsealed cleartext key is returned as the first return value, and the private part of the key used for // authorizing PCR policy updates with UpdateKeyPCRProtectionPolicy is returned as the second return value. @@ -79,24 +76,8 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b switch { case tpm2.IsResourceUnavailableError(err, tcg.SRKHandle): return nil, nil, ErrTPMProvisioning - case isKeyFileError(err): - // A keyFileError can be as a result of an improperly provisioned TPM - detect if the object at tcg.SRKHandle is a valid primary key - // with the correct attributes. If it's not, then it's definitely a provisioning error because the object at tcg.SRKHandle is not - // the one that we provisioned the TPM with. If it is, then it could still be a provisioning error because we don't know if the - // object was created with the same template that ProvisionTPM uses, but in this case, we can't tell the difference between an - // invalid key file or a provisioning error. - srk, err2 := tpm.CreateResourceContextFromTPM(tcg.SRKHandle) - if err2 != nil { - return nil, nil, xerrors.Errorf("cannot create context for SRK: %w", err2) - } - ok, err2 := isObjectPrimaryKeyWithTemplate(tpm.TPMContext, tpm.OwnerHandleContext(), srk, tcg.SRKTemplate, tpm.HmacSession()) - switch { - case err2 != nil: - return nil, nil, xerrors.Errorf("cannot determine if object at %v is a primary key in the storage hierarchy: %w", tcg.SRKHandle, err2) - case !ok: - return nil, nil, ErrTPMProvisioning - } - return nil, nil, InvalidKeyFileError{err.Error()} + case isKeyDataLoadError(err): + return nil, nil, InvalidKeyFileError{Type: InvalidKeyFileErrorTPMLoad, msg: err.Error()} case err != nil: return nil, nil, xerrors.Errorf("cannot load sealed key in to TPM: %w", err) } @@ -112,14 +93,18 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b if err := executePolicySession(tpm.TPMContext, policySession, k.data.version, k.data.staticPolicyData, k.data.dynamicPolicyData, pin, hmacSession); err != nil { err = xerrors.Errorf("cannot complete authorization policy assertions: %w", err) switch { + case isKeyDataError(err): + return nil, nil, InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: err.Error()} case isPolicyDataError(err): - return nil, nil, InvalidKeyFileError{err.Error()} + return nil, nil, InvalidPolicyDataError(err.Error()) case isAuthFailError(err, tpm2.CommandPolicySecret, 1): return nil, nil, ErrPINFail case tpm2.IsTPMWarning(err, tpm2.WarningLockout, tpm2.CommandPolicySecret): return nil, nil, ErrTPMLockout case tpm2.IsResourceUnavailableError(err, lockNVHandle): - return nil, nil, ErrTPMProvisioning + // This is technically a provisioning error, but the current index can't be recreated. This key + // can never be recovered even after re-provisioning. + return nil, nil, InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: "a required NV index is missing from the TPM"} case tpm2.IsTPMError(err, tpm2.ErrorNVLocked, tpm2.CommandPolicyNV): return nil, nil, ErrSealedKeyAccessLocked } @@ -134,7 +119,10 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b keyData, err := tpm.Unseal(keyObject, policySession, hmacSession.IncludeAttrs(tpm2.AttrResponseEncrypt)) switch { case tpm2.IsTPMSessionError(err, tpm2.ErrorPolicyFail, tpm2.CommandUnseal, 1): - return nil, nil, InvalidKeyFileError{"the authorization policy check failed during unsealing"} + // We could get here if some of the metadata required to execute the policy session is invalid + // (eg, staticPolicyData.AuthPublicKey) or if lockNVHandle has a name that is different to the + // one that existed when the key was sealed. + return nil, nil, InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: "the authorization policy check failed during unsealing"} case isAuthFailError(err, tpm2.CommandUnseal, 1): return nil, nil, ErrPINFail case tpm2.IsTPMWarning(err, tpm2.WarningLockout, tpm2.CommandUnseal): @@ -149,7 +137,7 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b var sealedData sealedData if _, err := tpm2.UnmarshalFromBytes(keyData, &sealedData); err != nil { - return nil, nil, InvalidKeyFileError{err.Error()} + return nil, nil, InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: err.Error()} } return sealedData.Key, sealedData.AuthPrivateKey, nil diff --git a/unseal_test.go b/unseal_test.go index d4e191e5..5aa6ab9b 100644 --- a/unseal_test.go +++ b/unseal_test.go @@ -225,8 +225,8 @@ func TestUnsealErrorHandling(t *testing.T) { t.Errorf("EvictControl failed: %v", err) } }) - if _, ok := err.(InvalidKeyFileError); !ok || err.Error() != "invalid key data file: cannot load sealed key object in to TPM: bad "+ - "sealed key object or TPM owner changed" { + if _, ok := err.(InvalidKeyFileError); !ok || err.(InvalidKeyFileError).Type != InvalidKeyFileErrorTPMLoad || + err.Error() != "invalid key data file: cannot load sealed key object in to TPM: bad sealed key object or TPM owner changed" { t.Errorf("Unexpected error: %v", err) } }) @@ -243,8 +243,8 @@ func TestUnsealErrorHandling(t *testing.T) { if err == nil { t.Fatalf("Expected an error") } - if _, ok := err.(InvalidKeyFileError); !ok || err.Error() != "invalid key data file: cannot complete authorization policy "+ - "assertions: cannot complete OR assertions: current session digest not found in policy data" { + if _, ok := err.(InvalidPolicyDataError); !ok || err.Error() != "invalid authorization policy data: cannot complete authorization "+ + "policy assertions: cannot complete OR assertions for PCR policy: current session digest not found in policy data" { t.Errorf("Unexpected error: %v", err) } }) @@ -273,8 +273,8 @@ func TestUnsealErrorHandling(t *testing.T) { t.Fatalf("UpdateKeyPCRProtectionPolicy failed: %v", err) } }) - if _, ok := err.(InvalidKeyFileError); !ok || err.Error() != "invalid key data file: cannot complete authorization policy "+ - "assertions: the PCR policy has been revoked" { + if _, ok := err.(InvalidPolicyDataError); !ok || err.Error() != "invalid authorization policy data: cannot complete authorization "+ + "policy assertions: the PCR policy has been revoked" { t.Errorf("Unexpected error: %v", err) } }) From 4a10ed619bd452d5e1cbf1911318b0509de32ffe Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 17 Sep 2020 22:20:08 +0100 Subject: [PATCH 07/13] Rename InvalidKeyFileError to InvalidKeyDataError --- crypt.go | 24 ++++++++++++------------ crypt_test.go | 6 +++--- errors.go | 22 +++++++++++----------- keydata.go | 12 ++++++------ pin.go | 6 +++--- seal.go | 8 ++++---- unseal.go | 16 ++++++++-------- unseal_test.go | 4 ++-- 8 files changed, 49 insertions(+), 49 deletions(-) diff --git a/crypt.go b/crypt.go index 1e126b70..b3355bbd 100644 --- a/crypt.go +++ b/crypt.go @@ -270,10 +270,10 @@ const ( // TPM is not correctly provisioned. RecoveryKeyUsageReasonTPMProvisioningError - // RecoveryKeyUsageReasonInvalidKeyFile indicates that a volume had to be activated with the fallback recovery key because the TPM - // sealed key file is invalid. Note that attempts to resolve this by creating a new file with SealKeyToTPM may indicate that the TPM + // RecoveryKeyUsageReasonInvalidKeyData indicates that a volume had to be activated with the fallback recovery key because the TPM + // sealed key data is invalid. Note that attempts to resolve this by creating a new file with SealKeyToTPM may indicate that the TPM // is also not correctly provisioned. - RecoveryKeyUsageReasonInvalidKeyFile + RecoveryKeyUsageReasonInvalidKeyData // RecoveryKeyUsageReasonPINFail indicates that a volume had to be activated with the fallback recovery key because the correct PIN // was not provided. @@ -334,14 +334,14 @@ func activateWithRecoveryKey(volumeName, sourceDevicePath string, keyReader io.R } func isTPMLoadError(err error) bool { - var e InvalidKeyFileError - return xerrors.As(err, &e) && e.Type == InvalidKeyFileErrorTPMLoad + var e InvalidKeyDataError + return xerrors.As(err, &e) && e.Type == InvalidKeyDataErrorTPMLoad } func unsealKeyFromTPM(tpm *TPMConnection, k *SealedKeyObject, pin string) ([]byte, []byte, error) { sealedKey, authPrivateKey, err := k.UnsealFromTPM(tpm, pin) if err == ErrTPMProvisioning || isTPMLoadError(err) { - // ErrTPMProvisioning or InvalidKeyFileError in this context might indicate that there isn't a valid persistent SRK. Have a go + // ErrTPMProvisioning or InvalidKeyDataError in this context might indicate that there isn't a valid persistent SRK. Have a go // at creating one now and then retrying the unseal operation - if the proper SRK was evicted, the TPM owner hasn't changed, the // storage hierarchy still has a null authorization value, and the TPM sealed object is still valid, then this will allow us to // unseal the key without requiring any type of manual recovery. Ignore provisioning errors here and retry unsealing afterwards - @@ -458,8 +458,8 @@ func makeActivateOptions(in []string) ([]string, error) { return append(out, "tries=1"), nil } -func isInvalidKeyFileError(err error) bool { - var e InvalidKeyFileError +func isInvalidKeyDataError(err error) bool { + var e InvalidKeyDataError return xerrors.As(err, &e) } @@ -550,10 +550,10 @@ func ActivateVolumeWithTPMSealedKey(tpm *TPMConnection, volumeName, sourceDevice reason = RecoveryKeyUsageReasonTPMLockout case xerrors.Is(err, ErrTPMProvisioning): reason = RecoveryKeyUsageReasonTPMProvisioningError - case isInvalidKeyFileError(err): - reason = RecoveryKeyUsageReasonInvalidKeyFile + case isInvalidKeyDataError(err): + reason = RecoveryKeyUsageReasonInvalidKeyData case isInvalidPolicyDataError(err): - reason = RecoveryKeyUsageReasonInvalidKeyFile + reason = RecoveryKeyUsageReasonInvalidKeyData case xerrors.Is(err, requiresPinErr): reason = RecoveryKeyUsageReasonPINFail case xerrors.Is(err, ErrPINFail): @@ -561,7 +561,7 @@ func ActivateVolumeWithTPMSealedKey(tpm *TPMConnection, volumeName, sourceDevice case isExecError(err, systemdCryptsetupPath): // systemd-cryptsetup only provides 2 exit codes - success or fail - so we don't know the reason it failed yet. If activation // with the recovery key is successful, then it's safe to assume that it failed because the key unsealed from the TPM is incorrect. - reason = RecoveryKeyUsageReasonInvalidKeyFile + reason = RecoveryKeyUsageReasonInvalidKeyData } rErr := activateWithRecoveryKey(volumeName, sourceDevicePath, nil, options.RecoveryKeyTries, reason, activateOptions, options.KeyringPrefix) return rErr == nil, &ActivateWithTPMSealedKeyError{err, rErr} diff --git a/crypt_test.go b/crypt_test.go index faf156f5..6977c76a 100644 --- a/crypt_test.go +++ b/crypt_test.go @@ -660,7 +660,7 @@ func (s *cryptTPMSuite) TestActivateVolumeWithTPMSealedKeyErrorHandling6(c *C) { passphrases: []string{strings.Join(s.recoveryKeyAscii, "-")}, sdCryptsetupCalls: 2, success: true, - recoveryReason: RecoveryKeyUsageReasonInvalidKeyFile, + recoveryReason: RecoveryKeyUsageReasonInvalidKeyData, errChecker: ErrorMatches, errCheckerArgs: []interface{}{"cannot activate with TPM sealed key \\(cannot activate volume: " + s.mockSdCryptsetup.Exe() + " failed: exit status 1\\) but activation with recovery key was successful"}, @@ -795,7 +795,7 @@ func (s *cryptTPMSimulatorSuite) TestActivateVolumeWithTPMSealedKeyErrorHandling passphrases: []string{strings.Join(s.recoveryKeyAscii, "-")}, sdCryptsetupCalls: 1, success: true, - recoveryReason: RecoveryKeyUsageReasonInvalidKeyFile, + recoveryReason: RecoveryKeyUsageReasonInvalidKeyData, errChecker: ErrorMatches, errCheckerArgs: []interface{}{"cannot activate with TPM sealed key \\(cannot unseal key: invalid authorization policy data: cannot " + "complete authorization policy assertions: cannot complete OR assertions for PCR policy: current session digest not found in " + @@ -815,7 +815,7 @@ func (s *cryptTPMSimulatorSuite) TestActivateVolumeWithTPMSealedKeyErrorHandling passphrases: []string{strings.Join(s.recoveryKeyAscii, "-")}, sdCryptsetupCalls: 1, success: true, - recoveryReason: RecoveryKeyUsageReasonInvalidKeyFile, + recoveryReason: RecoveryKeyUsageReasonInvalidKeyData, errChecker: ErrorMatches, errCheckerArgs: []interface{}{"cannot activate with TPM sealed key \\(cannot unseal key: invalid authorization policy data: cannot " + "complete authorization policy assertions: cannot complete OR assertions for PCR policy: current session digest not found in " + diff --git a/errors.go b/errors.go index d1373b03..7be509ab 100644 --- a/errors.go +++ b/errors.go @@ -113,27 +113,27 @@ func isTPMVerificationError(err error) bool { return xerrors.As(err, &e) } -// InvalidKeyFileErrorType corresponds to the type of error detailed by InvalidKeyFileError. -type InvalidKeyFileErrorType int +// InvalidKeyDataErrorType corresponds to the type of error detailed by InvalidKeyDataError. +type InvalidKeyDataErrorType int const ( - // InvalidKeyFileErrorFatal indicates that the error was detected by software and is definitely caused by invalid key data. - InvalidKeyFileErrorFatal = iota + 1 + // InvalidKeyDataErrorFatal indicates that the error was detected by software and is definitely caused by invalid key data. + InvalidKeyDataErrorFatal = iota + 1 - // InvalidKeyFileErrorTPMLoad indicates that the error was detected when loading the sealed key object in to the TPM, and may + // InvalidKeyDataErrorTPMLoad indicates that the error was detected when loading the sealed key object in to the TPM, and may // indicate that the key data is invalid, or may indicate that the object at the persistent handle reserved for the storage // root key is not the creation parent of the sealed key object. In this case, calling ProvisionTPM may rectify this problem. - InvalidKeyFileErrorTPMLoad + InvalidKeyDataErrorTPMLoad ) -// InvalidKeyFileError indicates that the provided key data file is invalid. -type InvalidKeyFileError struct { - Type InvalidKeyFileErrorType +// InvalidKeyDataError indicates that the provided key data is invalid. +type InvalidKeyDataError struct { + Type InvalidKeyDataErrorType msg string } -func (e InvalidKeyFileError) Error() string { - return "invalid key data file: " + e.msg +func (e InvalidKeyDataError) Error() string { + return "invalid key data: " + e.msg } type InvalidPolicyDataError string diff --git a/keydata.go b/keydata.go index 08a08d07..2c72a9df 100644 --- a/keydata.go +++ b/keydata.go @@ -728,26 +728,26 @@ func (k *SealedKeyObject) PCRPolicyCounterHandle() tpm2.Handle { // Validate performs some checks on the SealedKeyObject. The key used for authorizing PCR policy updates can be optionally provided as // authKey in order to validate that it is associated with this sealed key object. // -// On success, no error is returned. If a InvalidKeyFileError error is returned, then this indicates that the SealedKeyObject is +// On success, no error is returned. If a InvalidKeyDataError error is returned, then this indicates that the SealedKeyObject is // invalid and must be recreated in order to perform some operations on it. func (k *SealedKeyObject) Validate(tpm *TPMConnection, authKey TPMPolicyAuthKey) error { key, err := k.data.decodeAuthKeyFromBytes(authKey) if err != nil { - return InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: fmt.Sprintf("cannot decode dynamic auth policy signing key: %v", err)} + return InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: fmt.Sprintf("cannot decode dynamic auth policy signing key: %v", err)} } _, err = k.data.validate(tpm.TPMContext, key, tpm.HmacSession()) switch { case isKeyDataLoadError(err): - return InvalidKeyFileError{Type: InvalidKeyFileErrorTPMLoad, msg: err.Error()} + return InvalidKeyDataError{Type: InvalidKeyDataErrorTPMLoad, msg: err.Error()} case isKeyDataError(err): - return InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: err.Error()} + return InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: err.Error()} default: return err } } // ReadSealedKeyObject loads a sealed key data file created by SealKeyToTPM from the specified path. If the file cannot be opened, -// a wrapped *os.PathError error is returned. If the key data file cannot be deserialized successfully, a InvalidKeyFileError error +// a wrapped *os.PathError error is returned. If the key data file cannot be deserialized successfully, a InvalidKeyDataError error // will be returned. func ReadSealedKeyObject(path string) (*SealedKeyObject, error) { // Open the key data file @@ -759,7 +759,7 @@ func ReadSealedKeyObject(path string) (*SealedKeyObject, error) { data, err := decodeKeyData(f) if err != nil { - return nil, InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: err.Error()} + return nil, InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: err.Error()} } return &SealedKeyObject{data: data}, nil diff --git a/pin.go b/pin.go index 8be980f2..c86d2e2b 100644 --- a/pin.go +++ b/pin.go @@ -146,7 +146,7 @@ func performPinChange(tpm *tpm2.TPMContext, keyPrivate tpm2.Private, keyPublic * // // If the file at the specified path cannot be opened, then a wrapped *os.PathError error will be returned. // -// If the supplied key data file fails validation checks, an InvalidKeyFileError error will be returned. +// If the supplied key data file fails validation checks, an InvalidKeyDataError error will be returned. // // If oldPIN is incorrect, then a ErrPINFail error will be returned and the TPM's dictionary attack counter will be incremented. func ChangePIN(tpm *TPMConnection, path string, oldPIN, newPIN string) error { @@ -171,9 +171,9 @@ func ChangePIN(tpm *TPMConnection, path string, oldPIN, newPIN string) error { data, _, pcrPolicyCounterPub, err := decodeAndValidateKeyData(tpm.TPMContext, keyFile, nil, tpm.HmacSession()) switch { case isKeyDataLoadError(err): - return InvalidKeyFileError{Type: InvalidKeyFileErrorTPMLoad, msg: err.Error()} + return InvalidKeyDataError{Type: InvalidKeyDataErrorTPMLoad, msg: err.Error()} case isKeyDataError(err): - return InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: err.Error()} + return InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: err.Error()} case err != nil: return xerrors.Errorf("cannot read and validate key data file: %w", err) } diff --git a/seal.go b/seal.go index de4802e8..123638ec 100644 --- a/seal.go +++ b/seal.go @@ -323,9 +323,9 @@ func updateKeyPCRProtectionPolicyCommon(tpm *tpm2.TPMContext, keyPath string, au data, authKey, pcrPolicyCounterPub, err := decodeAndValidateKeyData(tpm, keyFile, authData, session) switch { case isKeyDataLoadError(err): - return InvalidKeyFileError{Type: InvalidKeyFileErrorTPMLoad, msg: err.Error()} + return InvalidKeyDataError{Type: InvalidKeyDataErrorTPMLoad, msg: err.Error()} case isKeyDataError(err): - return InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: err.Error()} + return InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: err.Error()} case err != nil: // FIXME: Turn the missing lock NV index in to ErrProvisioning return xerrors.Errorf("cannot read and validate key data file: %w", err) @@ -368,7 +368,7 @@ func updateKeyPCRProtectionPolicyCommon(tpm *tpm2.TPMContext, keyPath string, au // // If either file cannot be opened, a wrapped *os.PathError error will be returned. // -// If either file cannot be deserialized correctly or validation of the files fails, a InvalidKeyFileError error will be returned. +// If either file cannot be deserialized correctly or validation of the files fails, a InvalidKeyDataError error will be returned. // // On success, the sealed key data file is updated atomically with an updated authorization policy that includes a PCR policy // computed from the supplied PCRProtectionProfile. @@ -388,7 +388,7 @@ func UpdateKeyPCRProtectionPolicyV0(tpm *TPMConnection, keyPath, policyUpdatePat // // If the file cannot be opened, a wrapped *os.PathError error will be returned. // -// If the file cannot be deserialized correctly or validation of the file fails, a InvalidKeyFileError error will be returned. +// If the file cannot be deserialized correctly or validation of the file fails, a InvalidKeyDataError error will be returned. // // On success, the sealed key data file is updated atomically with an updated authorization policy that includes a PCR policy // computed from the supplied PCRProtectionProfile. diff --git a/unseal.go b/unseal.go index 6a18949a..1115338b 100644 --- a/unseal.go +++ b/unseal.go @@ -36,7 +36,7 @@ import ( // called to attempt to resolve this. // // If the TPM sealed object cannot be loaded in to the TPM for reasons other than the lack of a storage root key, then a -// InvalidKeyFileError error will be returned with Type set to InvalidKeyFileErrorTPMLoad. This could be caused because the sealed +// InvalidKeyDataError error will be returned with Type set to InvalidKeyDataErrorTPMLoad. This could be caused because the sealed // object data is invalid in some way, or because the TPM object at the persistent handle reserved for the storage root key is not // the creation parent of the TPM sealed object. In the latter case, it may be possible to recover access to the sealed key by // calling ProvisionTPM again as long as the TPM owner hasn't changed (ie, the TPM has not been cleared). @@ -45,7 +45,7 @@ import ( // error will be returned. // // If any of the metadata in this key object is invalid, or if the TPM is missing any persistent resources associated with this key -// object, a InvalidKeyFileError error with Type set to InvalidKeyFileErrorFatal will be returned. In this case, there isn't a way +// object, a InvalidKeyDataError error with Type set to InvalidKeyDataErrorFatal will be returned. In this case, there isn't a way // to recover this key. // // If the PCR policy has been superceded (eg, by a call to UpdateKeyPCRProtectionPolicy), then a InvalidPolicyDataError error will be @@ -62,7 +62,7 @@ import ( // If access to sealed key objects created by this package is disallowed until the next TPM reset or TPM restart, then a // ErrSealedKeyAccessLocked error will be returned. // -// If the authorization policy check fails during unsealing, then a InvalidKeyFileError with Type set to InvalidKeyFileErrorFatal +// If the authorization policy check fails during unsealing, then a InvalidKeyDataError with Type set to InvalidKeyDataErrorFatal // will be returned. // // On success, the unsealed cleartext key is returned as the first return value, and the private part of the key used for @@ -77,7 +77,7 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b case tpm2.IsResourceUnavailableError(err, tcg.SRKHandle): return nil, nil, ErrTPMProvisioning case isKeyDataLoadError(err): - return nil, nil, InvalidKeyFileError{Type: InvalidKeyFileErrorTPMLoad, msg: err.Error()} + return nil, nil, InvalidKeyDataError{Type: InvalidKeyDataErrorTPMLoad, msg: err.Error()} case err != nil: return nil, nil, xerrors.Errorf("cannot load sealed key in to TPM: %w", err) } @@ -94,7 +94,7 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b err = xerrors.Errorf("cannot complete authorization policy assertions: %w", err) switch { case isKeyDataError(err): - return nil, nil, InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: err.Error()} + return nil, nil, InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: err.Error()} case isPolicyDataError(err): return nil, nil, InvalidPolicyDataError(err.Error()) case isAuthFailError(err, tpm2.CommandPolicySecret, 1): @@ -104,7 +104,7 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b case tpm2.IsResourceUnavailableError(err, lockNVHandle): // This is technically a provisioning error, but the current index can't be recreated. This key // can never be recovered even after re-provisioning. - return nil, nil, InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: "a required NV index is missing from the TPM"} + return nil, nil, InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: "a required NV index is missing from the TPM"} case tpm2.IsTPMError(err, tpm2.ErrorNVLocked, tpm2.CommandPolicyNV): return nil, nil, ErrSealedKeyAccessLocked } @@ -122,7 +122,7 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b // We could get here if some of the metadata required to execute the policy session is invalid // (eg, staticPolicyData.AuthPublicKey) or if lockNVHandle has a name that is different to the // one that existed when the key was sealed. - return nil, nil, InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: "the authorization policy check failed during unsealing"} + return nil, nil, InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: "the authorization policy check failed during unsealing"} case isAuthFailError(err, tpm2.CommandUnseal, 1): return nil, nil, ErrPINFail case tpm2.IsTPMWarning(err, tpm2.WarningLockout, tpm2.CommandUnseal): @@ -137,7 +137,7 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b var sealedData sealedData if _, err := tpm2.UnmarshalFromBytes(keyData, &sealedData); err != nil { - return nil, nil, InvalidKeyFileError{Type: InvalidKeyFileErrorFatal, msg: err.Error()} + return nil, nil, InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: err.Error()} } return sealedData.Key, sealedData.AuthPrivateKey, nil diff --git a/unseal_test.go b/unseal_test.go index 5aa6ab9b..d4429461 100644 --- a/unseal_test.go +++ b/unseal_test.go @@ -225,8 +225,8 @@ func TestUnsealErrorHandling(t *testing.T) { t.Errorf("EvictControl failed: %v", err) } }) - if _, ok := err.(InvalidKeyFileError); !ok || err.(InvalidKeyFileError).Type != InvalidKeyFileErrorTPMLoad || - err.Error() != "invalid key data file: cannot load sealed key object in to TPM: bad sealed key object or TPM owner changed" { + if _, ok := err.(InvalidKeyDataError); !ok || err.(InvalidKeyDataError).Type != InvalidKeyDataErrorTPMLoad || + err.Error() != "invalid key data: cannot load sealed key object in to TPM: bad sealed key object or TPM owner changed" { t.Errorf("Unexpected error: %v", err) } }) From ca2418e193cacc64846664f5e4df3650d79d2c7b Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 17 Sep 2020 22:28:34 +0100 Subject: [PATCH 08/13] Make use of SealedKeyObject.Validate in tests --- export_test.go | 12 ------------ keydata_test.go | 9 +++++++-- seal_test.go | 8 ++++++-- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/export_test.go b/export_test.go index 2a90765b..54b09aca 100644 --- a/export_test.go +++ b/export_test.go @@ -23,7 +23,6 @@ import ( "bytes" "crypto/ecdsa" "fmt" - "os" "github.com/canonical/go-tpm2" "github.com/chrisccoulson/tcglog-parser" @@ -261,14 +260,3 @@ func (p *PCRProtectionProfile) DumpValues(tpm *tpm2.TPMContext) string { } return s.String() } - -func ValidateKeyDataFile(tpm *tpm2.TPMContext, keyFile string, authPrivateKey TPMPolicyAuthKey, session tpm2.SessionContext) error { - kf, err := os.Open(keyFile) - if err != nil { - return err - } - defer kf.Close() - - _, _, _, err = decodeAndValidateKeyData(tpm, kf, authPrivateKey, session) - return err -} diff --git a/keydata_test.go b/keydata_test.go index 8c1856aa..c9e2392c 100644 --- a/keydata_test.go +++ b/keydata_test.go @@ -52,9 +52,14 @@ func (s *keyDataSuite) TestValidateAfterLock(c *C) { c.Assert(err, IsNil) s.AddCleanupNVSpace(c, s.TPM.OwnerHandleContext(), pcrPolicyCounter) - c.Check(ValidateKeyDataFile(s.TPM.TPMContext, keyFile, authPrivateKey, s.TPM.HmacSession()), IsNil) + k, err := ReadSealedKeyObject(keyFile) + c.Assert(err, IsNil) + c.Check(k.Validate(s.TPM, authPrivateKey), IsNil) c.Assert(LockAccessToSealedKeys(s.TPM), IsNil) defer s.ResetTPMSimulator(c) - c.Check(ValidateKeyDataFile(s.TPM.TPMContext, keyFile, authPrivateKey, s.TPM.HmacSession()), IsNil) + + k, err = ReadSealedKeyObject(keyFile) + c.Assert(err, IsNil) + c.Check(k.Validate(s.TPM, authPrivateKey), IsNil) } diff --git a/seal_test.go b/seal_test.go index 07cd8762..31de7340 100644 --- a/seal_test.go +++ b/seal_test.go @@ -66,8 +66,12 @@ func TestSealKeyToTPM(t *testing.T) { } defer undefineKeyNVSpace(t, tpm, keyFile) - if err := ValidateKeyDataFile(tpm.TPMContext, keyFile, authPrivateKey, tpm.HmacSession()); err != nil { - t.Errorf("ValidateKeyDataFile failed: %v", err) + k, err := ReadSealedKeyObject(keyFile) + if err != nil { + t.Fatalf("ReadSealedKeyObject failed: %v", err) + } + if err := k.Validate(tpm, authPrivateKey); err != nil { + t.Errorf("Validate failed: %v", err) } } From 5f6122abdeac2d6c529098ffc73b01fc0ae2c918 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 17 Sep 2020 23:12:26 +0100 Subject: [PATCH 09/13] Add tests for SealedKeyObject.Validate --- errors.go | 2 +- export_test.go | 16 ++++ keydata.go | 2 +- keydata_test.go | 193 +++++++++++++++++++++++++++++++++++++++++++++--- unseal_test.go | 2 +- 5 files changed, 201 insertions(+), 14 deletions(-) diff --git a/errors.go b/errors.go index 7be509ab..370e9486 100644 --- a/errors.go +++ b/errors.go @@ -118,7 +118,7 @@ type InvalidKeyDataErrorType int const ( // InvalidKeyDataErrorFatal indicates that the error was detected by software and is definitely caused by invalid key data. - InvalidKeyDataErrorFatal = iota + 1 + InvalidKeyDataErrorFatal InvalidKeyDataErrorType = iota + 1 // InvalidKeyDataErrorTPMLoad indicates that the error was detected when loading the sealed key object in to the TPM, and may // indicate that the key data is invalid, or may indicate that the object at the persistent handle reserved for the storage diff --git a/export_test.go b/export_test.go index 54b09aca..901ab054 100644 --- a/export_test.go +++ b/export_test.go @@ -260,3 +260,19 @@ func (p *PCRProtectionProfile) DumpValues(tpm *tpm2.TPMContext) string { } return s.String() } + +func (k *SealedKeyObject) SetVersion(version uint32) { + k.data.version = version +} + +func (k *SealedKeyObject) KeyPublic() *tpm2.Public { + return k.data.keyPublic +} + +func (k *SealedKeyObject) SetPCRPolicyCounterHandle(h tpm2.Handle) { + k.data.staticPolicyData.pcrPolicyCounterHandle = h +} + +func (k *SealedKeyObject) AuthPublicKey() *tpm2.Public { + return k.data.staticPolicyData.authPublicKey +} diff --git a/keydata.go b/keydata.go index 2c72a9df..c435bd46 100644 --- a/keydata.go +++ b/keydata.go @@ -409,7 +409,7 @@ func (d *keyData) load(tpm *tpm2.TPMContext, session tpm2.SessionContext) (tpm2. invalidObject = true } if invalidObject { - return nil, keyDataLoadError{errors.New("cannot load sealed key object in to TPM: bad sealed key object or TPM owner changed")} + return nil, keyDataLoadError{errors.New("cannot load sealed key object in to TPM: bad sealed key object or parent object")} } return nil, xerrors.Errorf("cannot load sealed key object in to TPM: %w", err) } diff --git a/keydata_test.go b/keydata_test.go index c9e2392c..8f23bfa4 100644 --- a/keydata_test.go +++ b/keydata_test.go @@ -20,6 +20,8 @@ package secboot_test import ( + "crypto/ecdsa" + "crypto/elliptic" "math/rand" "github.com/canonical/go-tpm2" @@ -31,35 +33,204 @@ import ( type keyDataSuite struct { testutil.TPMSimulatorTestBase -} -var _ = Suite(&keyDataSuite{}) + keyFile string + authPrivateKey TPMPolicyAuthKey +} -func (s *keyDataSuite) TestValidateAfterLock(c *C) { +func (s *keyDataSuite) SetUpTest(c *C) { + s.TPMSimulatorTestBase.SetUpTest(c) c.Assert(ProvisionTPM(s.TPM, ProvisionModeFull, nil), IsNil) key := make([]byte, 64) rand.Read(key) - dir := c.MkDir() - keyFile := dir + "/keydata" - + s.keyFile = c.MkDir() + "/keydata" pcrPolicyCounterHandle := tpm2.Handle(0x0181fff0) - authPrivateKey, err := SealKeyToTPM(s.TPM, key, keyFile, &KeyCreationParams{PCRProfile: getTestPCRProfile(), PCRPolicyCounterHandle: pcrPolicyCounterHandle}) + authPrivateKey, err := SealKeyToTPM(s.TPM, key, s.keyFile, &KeyCreationParams{PCRProfile: getTestPCRProfile(), PCRPolicyCounterHandle: pcrPolicyCounterHandle}) + s.authPrivateKey = authPrivateKey c.Assert(err, IsNil) + pcrPolicyCounter, err := s.TPM.CreateResourceContextFromTPM(pcrPolicyCounterHandle) c.Assert(err, IsNil) s.AddCleanupNVSpace(c, s.TPM.OwnerHandleContext(), pcrPolicyCounter) +} - k, err := ReadSealedKeyObject(keyFile) +var _ = Suite(&keyDataSuite{}) + +func (s *keyDataSuite) TestValidateAfterLock(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) c.Assert(err, IsNil) - c.Check(k.Validate(s.TPM, authPrivateKey), IsNil) + c.Check(k.Validate(s.TPM, s.authPrivateKey), IsNil) c.Assert(LockAccessToSealedKeys(s.TPM), IsNil) defer s.ResetTPMSimulator(c) - k, err = ReadSealedKeyObject(keyFile) + k, err = ReadSealedKeyObject(s.keyFile) + c.Assert(err, IsNil) + c.Check(k.Validate(s.TPM, s.authPrivateKey), IsNil) +} + +func (s *keyDataSuite) TestValidateGood(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) + c.Assert(err, IsNil) + c.Check(k.Validate(s.TPM, s.authPrivateKey), IsNil) +} + +func (s *keyDataSuite) TestValidateGoodNoAuthKey(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) + c.Assert(err, IsNil) + c.Check(k.Validate(s.TPM, nil), IsNil) +} + +func (s *keyDataSuite) TestValidateInvalidVersion(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) + c.Assert(err, IsNil) + + k.SetVersion(10) + + err = k.Validate(s.TPM, s.authPrivateKey) + c.Check(err, ErrorMatches, "invalid key data: invalid metadata version") + ierr, ok := err.(InvalidKeyDataError) + c.Assert(ok, Equals, true) + c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) +} + +func (s *keyDataSuite) TestValidateInvalidKeyPublic1(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) + c.Assert(err, IsNil) + + k.KeyPublic().Type = tpm2.ObjectTypeECC + + err = k.Validate(s.TPM, s.authPrivateKey) + c.Check(err, ErrorMatches, "invalid key data: sealed key object has the wrong type") + ierr, ok := err.(InvalidKeyDataError) + c.Assert(ok, Equals, true) + c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) +} + +func (s *keyDataSuite) TestValidateInvalidKeyPublic2(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) + c.Assert(err, IsNil) + + k.KeyPublic().Attrs |= tpm2.AttrUserWithAuth + + err = k.Validate(s.TPM, s.authPrivateKey) + c.Check(err, ErrorMatches, "invalid key data: sealed key object has the wrong attributes") + ierr, ok := err.(InvalidKeyDataError) + c.Assert(ok, Equals, true) + c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) +} + +func (s *keyDataSuite) TestValidateLoadFail(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) + c.Assert(err, IsNil) + + k.KeyPublic().AuthPolicy = make(tpm2.Digest, 32) + + err = k.Validate(s.TPM, s.authPrivateKey) + c.Check(err, ErrorMatches, "invalid key data: cannot load sealed key object in to TPM: bad sealed key object or parent object") + ierr, ok := err.(InvalidKeyDataError) + c.Assert(ok, Equals, true) + c.Check(ierr.Type, Equals, InvalidKeyDataErrorTPMLoad) +} + +func (s *keyDataSuite) TestValidateInvalidPCRPolicyCounterHandle1(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) + c.Assert(err, IsNil) + + k.SetPCRPolicyCounterHandle(tpm2.HandleEndorsement) + + err = k.Validate(s.TPM, s.authPrivateKey) + c.Check(err, ErrorMatches, "invalid key data: PCR policy counter handle is invalid") + ierr, ok := err.(InvalidKeyDataError) + c.Assert(ok, Equals, true) + c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) +} + +func (s *keyDataSuite) TestValidateInvalidPCRPolicyCounterHandle2(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) c.Assert(err, IsNil) - c.Check(k.Validate(s.TPM, authPrivateKey), IsNil) + + // NULL is valid, but it should still be detected because the name is encoded in the authorization policy digest. + // The same mechanism would catch the actual NV index on the TPM being changed. + k.SetPCRPolicyCounterHandle(tpm2.HandleNull) + + err = k.Validate(s.TPM, s.authPrivateKey) + c.Check(err, ErrorMatches, "invalid key data: the sealed key object's authorization policy is inconsistent with the associated metadata or persistent TPM resources") + ierr, ok := err.(InvalidKeyDataError) + c.Assert(ok, Equals, true) + c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) +} + +func (s *keyDataSuite) TestValidateInvalidPCRPolicyCounterHandle3(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) + c.Assert(err, IsNil) + + index, err := s.TPM.CreateResourceContextFromTPM(k.PCRPolicyCounterHandle()) + c.Assert(err, IsNil) + c.Assert(s.TPM.NVUndefineSpace(s.TPM.OwnerHandleContext(), index, nil), IsNil) + + err = k.Validate(s.TPM, s.authPrivateKey) + c.Check(err, ErrorMatches, "invalid key data: PCR policy counter is unavailable") + ierr, ok := err.(InvalidKeyDataError) + c.Assert(ok, Equals, true) + c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) +} + +func (s *keyDataSuite) TestValidateInvalidAuthPublicKeyNameAlg(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) + c.Assert(err, IsNil) + + k.AuthPublicKey().NameAlg = tpm2.HashAlgorithmNull + + err = k.Validate(s.TPM, s.authPrivateKey) + c.Check(err, ErrorMatches, "invalid key data: cannot compute name of dynamic authorization policy key: unsupported name algorithm: TPM_ALG_NULL") + ierr, ok := err.(InvalidKeyDataError) + c.Assert(ok, Equals, true) + c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) +} + +func (s *keyDataSuite) TestValidateInvalidAuthPublicKeyType(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) + c.Assert(err, IsNil) + + k.AuthPublicKey().Type = tpm2.ObjectTypeRSA + + err = k.Validate(s.TPM, s.authPrivateKey) + c.Check(err, ErrorMatches, "invalid key data: cannot decode dynamic auth policy signing key: unsupported type") + ierr, ok := err.(InvalidKeyDataError) + c.Assert(ok, Equals, true) + c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) +} + +func (s *keyDataSuite) TestValidateInvalidAuthPublicKey(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) + c.Assert(err, IsNil) + + badAuthKey, err := ecdsa.GenerateKey(elliptic.P256(), testutil.RandReader) + c.Assert(err, IsNil) + k.AuthPublicKey().Unique.ECC().X = badAuthKey.X.Bytes() + k.AuthPublicKey().Unique.ECC().Y = badAuthKey.Y.Bytes() + + err = k.Validate(s.TPM, s.authPrivateKey) + c.Check(err, ErrorMatches, "invalid key data: the sealed key object's authorization policy is inconsistent with the associated metadata or persistent TPM resources") + ierr, ok := err.(InvalidKeyDataError) + c.Assert(ok, Equals, true) + c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) +} + +func (s *keyDataSuite) TestValidateInvalidAuthPrivateKey(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) + c.Assert(err, IsNil) + + badAuthKey, err := ecdsa.GenerateKey(elliptic.P256(), testutil.RandReader) + c.Assert(err, IsNil) + + err = k.Validate(s.TPM, badAuthKey.D.Bytes()) + c.Check(err, ErrorMatches, "invalid key data: dynamic authorization policy signing private key doesn't match public key") + ierr, ok := err.(InvalidKeyDataError) + c.Assert(ok, Equals, true) + c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) } diff --git a/unseal_test.go b/unseal_test.go index d4429461..c4173c2e 100644 --- a/unseal_test.go +++ b/unseal_test.go @@ -226,7 +226,7 @@ func TestUnsealErrorHandling(t *testing.T) { } }) if _, ok := err.(InvalidKeyDataError); !ok || err.(InvalidKeyDataError).Type != InvalidKeyDataErrorTPMLoad || - err.Error() != "invalid key data: cannot load sealed key object in to TPM: bad sealed key object or TPM owner changed" { + err.Error() != "invalid key data: cannot load sealed key object in to TPM: bad sealed key object or parent object" { t.Errorf("Unexpected error: %v", err) } }) From 579939bb5721b09e8dc7837eef43d6be1e9cc7e5 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 17 Sep 2020 23:18:44 +0100 Subject: [PATCH 10/13] Add some more test cases --- keydata.go | 11 ++++++----- keydata_test.go | 33 +++++++++++++++++++++++++++++++++ seal.go | 1 - 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/keydata.go b/keydata.go index c435bd46..1cd14345 100644 --- a/keydata.go +++ b/keydata.go @@ -446,15 +446,16 @@ func (d *keyData) validate(tpm *tpm2.TPMContext, authKey crypto.PrivateKey, sess // Obtain a ResourceContext for the lock NV index and validate it. lockIndex, err := tpm.CreateResourceContextFromTPM(lockNVHandle) - if err != nil { + switch { + case tpm2.IsResourceUnavailableError(err, lockNVHandle): + return nil, keyDataError{errors.New("no lock NV index")} + case err != nil: return nil, xerrors.Errorf("cannot create context for lock NV index: %v", err) } lockIndexPub, err := readAndValidateLockNVIndexPublic(tpm, lockIndex, session) if err != nil { - return nil, xerrors.Errorf("cannot determine if NV index at %v is global lock index: %w", lockNVHandle, err) - } - if lockIndexPub == nil { - return nil, xerrors.Errorf("NV index at %v is not a valid global lock index", lockNVHandle) + // The TPM needs provisioning, but this key is unrecoverable anyway + return nil, keyDataError{xerrors.Errorf("cannot determine if NV index at %v is global lock index: %w", lockNVHandle, err)} } lockIndexName, err := lockIndexPub.Name() if err != nil { diff --git a/keydata_test.go b/keydata_test.go index 8f23bfa4..f51ee66b 100644 --- a/keydata_test.go +++ b/keydata_test.go @@ -234,3 +234,36 @@ func (s *keyDataSuite) TestValidateInvalidAuthPrivateKey(c *C) { c.Assert(ok, Equals, true) c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) } + +func (s *keyDataSuite) TestValidateNoLockIndex(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) + c.Assert(err, IsNil) + + index, err := s.TPM.CreateResourceContextFromTPM(LockNVHandle) + c.Assert(err, IsNil) + c.Assert(s.TPM.NVUndefineSpace(s.TPM.OwnerHandleContext(), index, nil), IsNil) + + err = k.Validate(s.TPM, s.authPrivateKey) + c.Check(err, ErrorMatches, "invalid key data: no lock NV index") + ierr, ok := err.(InvalidKeyDataError) + c.Assert(ok, Equals, true) + c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) +} + +func (s *keyDataSuite) TestValidateInvalidLockIndex(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) + c.Assert(err, IsNil) + + for _, h := range []tpm2.Handle{LockNVHandle, LockNVDataHandle} { + index, err := s.TPM.CreateResourceContextFromTPM(h) + c.Assert(err, IsNil) + c.Assert(s.TPM.NVUndefineSpace(s.TPM.OwnerHandleContext(), index, nil), IsNil) + } + c.Assert(ProvisionTPM(s.TPM, ProvisionModeWithoutLockout, nil), IsNil) + + err = k.Validate(s.TPM, s.authPrivateKey) + c.Check(err, ErrorMatches, "invalid key data: the sealed key object's authorization policy is inconsistent with the associated metadata or persistent TPM resources") + ierr, ok := err.(InvalidKeyDataError) + c.Assert(ok, Equals, true) + c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) +} diff --git a/seal.go b/seal.go index 123638ec..152959c5 100644 --- a/seal.go +++ b/seal.go @@ -327,7 +327,6 @@ func updateKeyPCRProtectionPolicyCommon(tpm *tpm2.TPMContext, keyPath string, au case isKeyDataError(err): return InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: err.Error()} case err != nil: - // FIXME: Turn the missing lock NV index in to ErrProvisioning return xerrors.Errorf("cannot read and validate key data file: %w", err) } From 3b3cc8276bb15f13e6d13862df4022d874bb998d Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 17 Sep 2020 23:56:14 +0100 Subject: [PATCH 11/13] Add some more Unseal tests --- internal/testutil/suites.go | 4 +++ internal/testutil/tpm.go | 11 +++++++ keydata_test.go | 1 + policy.go | 6 +--- policy_test.go | 3 +- secboot_test.go | 9 ++--- unseal.go | 2 ++ unseal_test.go | 65 +++++++++++++++++++++++++++++++++++++ 8 files changed, 89 insertions(+), 12 deletions(-) diff --git a/internal/testutil/suites.go b/internal/testutil/suites.go index 785899b2..5678ba7c 100644 --- a/internal/testutil/suites.go +++ b/internal/testutil/suites.go @@ -112,6 +112,10 @@ func (b *TPMTestBase) SetHierarchyAuth(c *C, hierarchy tpm2.Handle) { }) } +func (b *TPMTestBase) ClearTPMWithPlatformAuth(c *C) { + c.Assert(ClearTPMWithPlatformAuth(b.TPM), IsNil) +} + type TPMSimulatorTestBase struct { TPMTestBase tcti *tpm2.TctiMssim diff --git a/internal/testutil/tpm.go b/internal/testutil/tpm.go index 9942efd0..37159c9c 100644 --- a/internal/testutil/tpm.go +++ b/internal/testutil/tpm.go @@ -455,6 +455,17 @@ func ResetTPMSimulator(tpm *secboot.TPMConnection, tcti *tpm2.TctiMssim) (*secbo return OpenTPMSimulatorForTesting() } +// ClearTPMWithPlatformAuth clear the TPM with the platform hierarchy. +func ClearTPMWithPlatformAuth(tpm *secboot.TPMConnection) error { + if err := tpm.ClearControl(tpm.PlatformHandleContext(), false, nil); err != nil { + return fmt.Errorf("ClearControl failed: %v", err) + } + if err := tpm.Clear(tpm.PlatformHandleContext(), nil); err != nil { + return fmt.Errorf("Clear failed: %v", err) + } + return nil +} + func OpenTPMSimulatorForTesting() (*secboot.TPMConnection, *tpm2.TctiMssim, error) { if !UseMssim { return nil, nil, nil diff --git a/keydata_test.go b/keydata_test.go index f51ee66b..137dae2f 100644 --- a/keydata_test.go +++ b/keydata_test.go @@ -242,6 +242,7 @@ func (s *keyDataSuite) TestValidateNoLockIndex(c *C) { index, err := s.TPM.CreateResourceContextFromTPM(LockNVHandle) c.Assert(err, IsNil) c.Assert(s.TPM.NVUndefineSpace(s.TPM.OwnerHandleContext(), index, nil), IsNil) + defer s.ClearTPMWithPlatformAuth(c) err = k.Validate(s.TPM, s.authPrivateKey) c.Check(err, ErrorMatches, "invalid key data: no lock NV index") diff --git a/policy.go b/policy.go index 1a0ae07b..ddb283fa 100644 --- a/policy.go +++ b/policy.go @@ -978,11 +978,7 @@ func executePolicySession(tpm *tpm2.TPMContext, policySession tpm2.SessionContex if pcrPolicyCounterHandle != tpm2.HandleNull { var err error policyCounter, err = tpm.CreateResourceContextFromTPM(pcrPolicyCounterHandle) - switch { - case tpm2.IsResourceUnavailableError(err, pcrPolicyCounterHandle): - // If there is no NV index at the expected handle then the key data is invalid. - return keyDataError{errors.New("no PCR policy counter found")} - case err != nil: + if err != nil { return xerrors.Errorf("cannot obtain context for PCR policy counter: %w", err) } diff --git a/policy_test.go b/policy_test.go index 6e432b0a..f328aa0d 100644 --- a/policy_test.go +++ b/policy_test.go @@ -1891,7 +1891,8 @@ func TestExecutePolicy(t *testing.T) { }}, func(s *StaticPolicyData, d *DynamicPolicyData) { s.SetPcrPolicyCounterHandle(s.PcrPolicyCounterHandle() + 1) }) - if !IsKeyDataError(err) || err.Error() != "no PCR policy counter found" { + if !tpm2.IsResourceUnavailableError(err, policyCounterPub.Index+1) || + err.Error() != "cannot obtain context for PCR policy counter: a resource at handle 0x0181ff01 is not available on the TPM" { t.Errorf("Unexpected error: %v", err) } if bytes.Equal(digest, expected) { diff --git a/secboot_test.go b/secboot_test.go index 7bedb318..142e1209 100644 --- a/secboot_test.go +++ b/secboot_test.go @@ -128,18 +128,15 @@ func undefineKeyNVSpace(t *testing.T, tpm *TPMConnection, path string) { } rc, err := tpm.CreateResourceContextFromTPM(h) if err != nil { - t.Fatalf("CreateResourceContextFromTPM failed: %v", err) + return } undefineNVSpace(t, tpm, rc, tpm.OwnerHandleContext()) } // clearTPMWithPlatformAuth clears the TPM with platform hierarchy authorization - something that we can only do on the simulator func clearTPMWithPlatformAuth(t *testing.T, tpm *TPMConnection) { - if err := tpm.ClearControl(tpm.PlatformHandleContext(), false, nil); err != nil { - t.Fatalf("ClearControl failed: %v", err) - } - if err := tpm.Clear(tpm.PlatformHandleContext(), nil); err != nil { - t.Fatalf("Clear failed: %v", err) + if err := testutil.ClearTPMWithPlatformAuth(tpm); err != nil { + t.Fatalf("ClearTPMWithPlatformAuth failed: %v", err) } } diff --git a/unseal.go b/unseal.go index 1115338b..662ad492 100644 --- a/unseal.go +++ b/unseal.go @@ -101,6 +101,8 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b return nil, nil, ErrPINFail case tpm2.IsTPMWarning(err, tpm2.WarningLockout, tpm2.CommandPolicySecret): return nil, nil, ErrTPMLockout + case tpm2.IsResourceUnavailableError(err, k.data.staticPolicyData.pcrPolicyCounterHandle): + return nil, nil, InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: "no PCR policy counter found"} case tpm2.IsResourceUnavailableError(err, lockNVHandle): // This is technically a provisioning error, but the current index can't be recreated. This key // can never be recovered even after re-provisioning. diff --git a/unseal_test.go b/unseal_test.go index c4173c2e..d8148be0 100644 --- a/unseal_test.go +++ b/unseal_test.go @@ -309,4 +309,69 @@ func TestUnsealErrorHandling(t *testing.T) { t.Errorf("Unexpected error: %v", err) } }) + + t.Run("NoLockNVIndex", func(t *testing.T) { + tpm, _ := openTPMSimulatorForTesting(t) + defer func() { + clearTPMWithPlatformAuth(t, tpm) + closeTPM(t, tpm) + }() + + err := run(t, tpm, func(keyFile string, _ []byte) { + index, err := tpm.CreateResourceContextFromTPM(LockNVHandle) + if err != nil { + t.Fatalf("CreateResourceContextFromTPM failed: %v", err) + } + if tpm.NVUndefineSpace(tpm.OwnerHandleContext(), index, nil); err != nil { + t.Fatalf("NVUndefineSpace failed: %v", err) + } + }) + if _, ok := err.(InvalidKeyDataError); !ok || err.(InvalidKeyDataError).Type != InvalidKeyDataErrorFatal || + err.Error() != "invalid key data: a required NV index is missing from the TPM" { + t.Errorf("Unexpected error: %v", err) + } + }) + + t.Run("InvalidLockNVIndex", func(t *testing.T) { + tpm := openTPMForTesting(t) + defer closeTPM(t, tpm) + + err := run(t, tpm, func(keyFile string, _ []byte) { + for _, h := range []tpm2.Handle{LockNVHandle, LockNVDataHandle} { + index, err := tpm.CreateResourceContextFromTPM(h) + if err != nil { + t.Fatalf("CreateResourceContextFromTPM failed: %v", err) + } + if tpm.NVUndefineSpace(tpm.OwnerHandleContext(), index, nil); err != nil { + t.Fatalf("NVUndefineSpace failed: %v", err) + } + } + if err := ProvisionTPM(tpm, ProvisionModeWithoutLockout, nil); err != nil { + t.Fatalf("ProvisionTPM failed: %v", err) + } + }) + if _, ok := err.(InvalidKeyDataError); !ok || err.(InvalidKeyDataError).Type != InvalidKeyDataErrorFatal || + err.Error() != "invalid key data: the authorization policy check failed during unsealing" { + t.Errorf("Unexpected error: %v", err) + } + }) + + t.Run("NoPCRPolicyCounter", func(t *testing.T) { + tpm := openTPMForTesting(t) + defer closeTPM(t, tpm) + + err := run(t, tpm, func(keyFile string, _ []byte) { + index, err := tpm.CreateResourceContextFromTPM(0x0181fff0) + if err != nil { + t.Fatalf("CreateResourceContextFromTPM failed: %v", err) + } + if tpm.NVUndefineSpace(tpm.OwnerHandleContext(), index, nil); err != nil { + t.Fatalf("NVUndefineSpace failed: %v", err) + } + }) + if _, ok := err.(InvalidKeyDataError); !ok || err.(InvalidKeyDataError).Type != InvalidKeyDataErrorFatal || + err.Error() != "invalid key data: no PCR policy counter found" { + t.Errorf("Unexpected error: %v", err) + } + }) } From f376e0785e17b6f084eb6ee8b266bb812059bcf8 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Tue, 22 Sep 2020 13:48:54 +0100 Subject: [PATCH 12/13] Rename InvalidKeyDataError.Type to InvalidKeyDataError.RetryProvision --- crypt.go | 2 +- errors.go | 15 +-------------- keydata.go | 8 ++++---- keydata_test.go | 26 +++++++++++++------------- pin.go | 4 ++-- seal.go | 4 ++-- unseal.go | 22 ++++++++++------------ unseal_test.go | 8 ++++---- 8 files changed, 37 insertions(+), 52 deletions(-) diff --git a/crypt.go b/crypt.go index b3355bbd..de5377eb 100644 --- a/crypt.go +++ b/crypt.go @@ -335,7 +335,7 @@ func activateWithRecoveryKey(volumeName, sourceDevicePath string, keyReader io.R func isTPMLoadError(err error) bool { var e InvalidKeyDataError - return xerrors.As(err, &e) && e.Type == InvalidKeyDataErrorTPMLoad + return xerrors.As(err, &e) && e.RetryProvision } func unsealKeyFromTPM(tpm *TPMConnection, k *SealedKeyObject, pin string) ([]byte, []byte, error) { diff --git a/errors.go b/errors.go index 370e9486..67333671 100644 --- a/errors.go +++ b/errors.go @@ -113,22 +113,9 @@ func isTPMVerificationError(err error) bool { return xerrors.As(err, &e) } -// InvalidKeyDataErrorType corresponds to the type of error detailed by InvalidKeyDataError. -type InvalidKeyDataErrorType int - -const ( - // InvalidKeyDataErrorFatal indicates that the error was detected by software and is definitely caused by invalid key data. - InvalidKeyDataErrorFatal InvalidKeyDataErrorType = iota + 1 - - // InvalidKeyDataErrorTPMLoad indicates that the error was detected when loading the sealed key object in to the TPM, and may - // indicate that the key data is invalid, or may indicate that the object at the persistent handle reserved for the storage - // root key is not the creation parent of the sealed key object. In this case, calling ProvisionTPM may rectify this problem. - InvalidKeyDataErrorTPMLoad -) - // InvalidKeyDataError indicates that the provided key data is invalid. type InvalidKeyDataError struct { - Type InvalidKeyDataErrorType + RetryProvision bool // a hint that the error might be resolved by calling ProvisionTPM msg string } diff --git a/keydata.go b/keydata.go index 1cd14345..ae4868e3 100644 --- a/keydata.go +++ b/keydata.go @@ -734,14 +734,14 @@ func (k *SealedKeyObject) PCRPolicyCounterHandle() tpm2.Handle { func (k *SealedKeyObject) Validate(tpm *TPMConnection, authKey TPMPolicyAuthKey) error { key, err := k.data.decodeAuthKeyFromBytes(authKey) if err != nil { - return InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: fmt.Sprintf("cannot decode dynamic auth policy signing key: %v", err)} + return InvalidKeyDataError{RetryProvision: false, msg: fmt.Sprintf("cannot decode dynamic auth policy signing key: %v", err)} } _, err = k.data.validate(tpm.TPMContext, key, tpm.HmacSession()) switch { case isKeyDataLoadError(err): - return InvalidKeyDataError{Type: InvalidKeyDataErrorTPMLoad, msg: err.Error()} + return InvalidKeyDataError{RetryProvision: true, msg: err.Error()} case isKeyDataError(err): - return InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: err.Error()} + return InvalidKeyDataError{RetryProvision: false, msg: err.Error()} default: return err } @@ -760,7 +760,7 @@ func ReadSealedKeyObject(path string) (*SealedKeyObject, error) { data, err := decodeKeyData(f) if err != nil { - return nil, InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: err.Error()} + return nil, InvalidKeyDataError{RetryProvision: false, msg: err.Error()} } return &SealedKeyObject{data: data}, nil diff --git a/keydata_test.go b/keydata_test.go index 137dae2f..f2d5c76c 100644 --- a/keydata_test.go +++ b/keydata_test.go @@ -94,7 +94,7 @@ func (s *keyDataSuite) TestValidateInvalidVersion(c *C) { c.Check(err, ErrorMatches, "invalid key data: invalid metadata version") ierr, ok := err.(InvalidKeyDataError) c.Assert(ok, Equals, true) - c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) + c.Check(ierr.RetryProvision, Equals, false) } func (s *keyDataSuite) TestValidateInvalidKeyPublic1(c *C) { @@ -107,7 +107,7 @@ func (s *keyDataSuite) TestValidateInvalidKeyPublic1(c *C) { c.Check(err, ErrorMatches, "invalid key data: sealed key object has the wrong type") ierr, ok := err.(InvalidKeyDataError) c.Assert(ok, Equals, true) - c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) + c.Check(ierr.RetryProvision, Equals, false) } func (s *keyDataSuite) TestValidateInvalidKeyPublic2(c *C) { @@ -120,7 +120,7 @@ func (s *keyDataSuite) TestValidateInvalidKeyPublic2(c *C) { c.Check(err, ErrorMatches, "invalid key data: sealed key object has the wrong attributes") ierr, ok := err.(InvalidKeyDataError) c.Assert(ok, Equals, true) - c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) + c.Check(ierr.RetryProvision, Equals, false) } func (s *keyDataSuite) TestValidateLoadFail(c *C) { @@ -133,7 +133,7 @@ func (s *keyDataSuite) TestValidateLoadFail(c *C) { c.Check(err, ErrorMatches, "invalid key data: cannot load sealed key object in to TPM: bad sealed key object or parent object") ierr, ok := err.(InvalidKeyDataError) c.Assert(ok, Equals, true) - c.Check(ierr.Type, Equals, InvalidKeyDataErrorTPMLoad) + c.Check(ierr.RetryProvision, Equals, true) } func (s *keyDataSuite) TestValidateInvalidPCRPolicyCounterHandle1(c *C) { @@ -146,7 +146,7 @@ func (s *keyDataSuite) TestValidateInvalidPCRPolicyCounterHandle1(c *C) { c.Check(err, ErrorMatches, "invalid key data: PCR policy counter handle is invalid") ierr, ok := err.(InvalidKeyDataError) c.Assert(ok, Equals, true) - c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) + c.Check(ierr.RetryProvision, Equals, false) } func (s *keyDataSuite) TestValidateInvalidPCRPolicyCounterHandle2(c *C) { @@ -161,7 +161,7 @@ func (s *keyDataSuite) TestValidateInvalidPCRPolicyCounterHandle2(c *C) { c.Check(err, ErrorMatches, "invalid key data: the sealed key object's authorization policy is inconsistent with the associated metadata or persistent TPM resources") ierr, ok := err.(InvalidKeyDataError) c.Assert(ok, Equals, true) - c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) + c.Check(ierr.RetryProvision, Equals, false) } func (s *keyDataSuite) TestValidateInvalidPCRPolicyCounterHandle3(c *C) { @@ -176,7 +176,7 @@ func (s *keyDataSuite) TestValidateInvalidPCRPolicyCounterHandle3(c *C) { c.Check(err, ErrorMatches, "invalid key data: PCR policy counter is unavailable") ierr, ok := err.(InvalidKeyDataError) c.Assert(ok, Equals, true) - c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) + c.Check(ierr.RetryProvision, Equals, false) } func (s *keyDataSuite) TestValidateInvalidAuthPublicKeyNameAlg(c *C) { @@ -189,7 +189,7 @@ func (s *keyDataSuite) TestValidateInvalidAuthPublicKeyNameAlg(c *C) { c.Check(err, ErrorMatches, "invalid key data: cannot compute name of dynamic authorization policy key: unsupported name algorithm: TPM_ALG_NULL") ierr, ok := err.(InvalidKeyDataError) c.Assert(ok, Equals, true) - c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) + c.Check(ierr.RetryProvision, Equals, false) } func (s *keyDataSuite) TestValidateInvalidAuthPublicKeyType(c *C) { @@ -202,7 +202,7 @@ func (s *keyDataSuite) TestValidateInvalidAuthPublicKeyType(c *C) { c.Check(err, ErrorMatches, "invalid key data: cannot decode dynamic auth policy signing key: unsupported type") ierr, ok := err.(InvalidKeyDataError) c.Assert(ok, Equals, true) - c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) + c.Check(ierr.RetryProvision, Equals, false) } func (s *keyDataSuite) TestValidateInvalidAuthPublicKey(c *C) { @@ -218,7 +218,7 @@ func (s *keyDataSuite) TestValidateInvalidAuthPublicKey(c *C) { c.Check(err, ErrorMatches, "invalid key data: the sealed key object's authorization policy is inconsistent with the associated metadata or persistent TPM resources") ierr, ok := err.(InvalidKeyDataError) c.Assert(ok, Equals, true) - c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) + c.Check(ierr.RetryProvision, Equals, false) } func (s *keyDataSuite) TestValidateInvalidAuthPrivateKey(c *C) { @@ -232,7 +232,7 @@ func (s *keyDataSuite) TestValidateInvalidAuthPrivateKey(c *C) { c.Check(err, ErrorMatches, "invalid key data: dynamic authorization policy signing private key doesn't match public key") ierr, ok := err.(InvalidKeyDataError) c.Assert(ok, Equals, true) - c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) + c.Check(ierr.RetryProvision, Equals, false) } func (s *keyDataSuite) TestValidateNoLockIndex(c *C) { @@ -248,7 +248,7 @@ func (s *keyDataSuite) TestValidateNoLockIndex(c *C) { c.Check(err, ErrorMatches, "invalid key data: no lock NV index") ierr, ok := err.(InvalidKeyDataError) c.Assert(ok, Equals, true) - c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) + c.Check(ierr.RetryProvision, Equals, false) } func (s *keyDataSuite) TestValidateInvalidLockIndex(c *C) { @@ -266,5 +266,5 @@ func (s *keyDataSuite) TestValidateInvalidLockIndex(c *C) { c.Check(err, ErrorMatches, "invalid key data: the sealed key object's authorization policy is inconsistent with the associated metadata or persistent TPM resources") ierr, ok := err.(InvalidKeyDataError) c.Assert(ok, Equals, true) - c.Check(ierr.Type, Equals, InvalidKeyDataErrorFatal) + c.Check(ierr.RetryProvision, Equals, false) } diff --git a/pin.go b/pin.go index c86d2e2b..9e1eddf8 100644 --- a/pin.go +++ b/pin.go @@ -171,9 +171,9 @@ func ChangePIN(tpm *TPMConnection, path string, oldPIN, newPIN string) error { data, _, pcrPolicyCounterPub, err := decodeAndValidateKeyData(tpm.TPMContext, keyFile, nil, tpm.HmacSession()) switch { case isKeyDataLoadError(err): - return InvalidKeyDataError{Type: InvalidKeyDataErrorTPMLoad, msg: err.Error()} + return InvalidKeyDataError{RetryProvision: true, msg: err.Error()} case isKeyDataError(err): - return InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: err.Error()} + return InvalidKeyDataError{RetryProvision: false, msg: err.Error()} case err != nil: return xerrors.Errorf("cannot read and validate key data file: %w", err) } diff --git a/seal.go b/seal.go index 152959c5..f07a2700 100644 --- a/seal.go +++ b/seal.go @@ -323,9 +323,9 @@ func updateKeyPCRProtectionPolicyCommon(tpm *tpm2.TPMContext, keyPath string, au data, authKey, pcrPolicyCounterPub, err := decodeAndValidateKeyData(tpm, keyFile, authData, session) switch { case isKeyDataLoadError(err): - return InvalidKeyDataError{Type: InvalidKeyDataErrorTPMLoad, msg: err.Error()} + return InvalidKeyDataError{RetryProvision: true, msg: err.Error()} case isKeyDataError(err): - return InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: err.Error()} + return InvalidKeyDataError{RetryProvision: false, msg: err.Error()} case err != nil: return xerrors.Errorf("cannot read and validate key data file: %w", err) } diff --git a/unseal.go b/unseal.go index 662ad492..3c968007 100644 --- a/unseal.go +++ b/unseal.go @@ -36,8 +36,8 @@ import ( // called to attempt to resolve this. // // If the TPM sealed object cannot be loaded in to the TPM for reasons other than the lack of a storage root key, then a -// InvalidKeyDataError error will be returned with Type set to InvalidKeyDataErrorTPMLoad. This could be caused because the sealed -// object data is invalid in some way, or because the TPM object at the persistent handle reserved for the storage root key is not +// InvalidKeyDataError error will be returned with RetryProvision set to true. This could be caused because the sealed object data +// is invalid in some way, or because the TPM object at the persistent handle reserved for the storage root key is not // the creation parent of the TPM sealed object. In the latter case, it may be possible to recover access to the sealed key by // calling ProvisionTPM again as long as the TPM owner hasn't changed (ie, the TPM has not been cleared). // @@ -45,8 +45,7 @@ import ( // error will be returned. // // If any of the metadata in this key object is invalid, or if the TPM is missing any persistent resources associated with this key -// object, a InvalidKeyDataError error with Type set to InvalidKeyDataErrorFatal will be returned. In this case, there isn't a way -// to recover this key. +// object, a InvalidKeyDataError error will be returned. In this case, there isn't a way to recover this key. // // If the PCR policy has been superceded (eg, by a call to UpdateKeyPCRProtectionPolicy), then a InvalidPolicyDataError error will be // returned. @@ -62,8 +61,7 @@ import ( // If access to sealed key objects created by this package is disallowed until the next TPM reset or TPM restart, then a // ErrSealedKeyAccessLocked error will be returned. // -// If the authorization policy check fails during unsealing, then a InvalidKeyDataError with Type set to InvalidKeyDataErrorFatal -// will be returned. +// If the authorization policy check fails during unsealing, then a InvalidKeyDataError error will be returned. // // On success, the unsealed cleartext key is returned as the first return value, and the private part of the key used for // authorizing PCR policy updates with UpdateKeyPCRProtectionPolicy is returned as the second return value. @@ -77,7 +75,7 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b case tpm2.IsResourceUnavailableError(err, tcg.SRKHandle): return nil, nil, ErrTPMProvisioning case isKeyDataLoadError(err): - return nil, nil, InvalidKeyDataError{Type: InvalidKeyDataErrorTPMLoad, msg: err.Error()} + return nil, nil, InvalidKeyDataError{RetryProvision: true, msg: err.Error()} case err != nil: return nil, nil, xerrors.Errorf("cannot load sealed key in to TPM: %w", err) } @@ -94,7 +92,7 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b err = xerrors.Errorf("cannot complete authorization policy assertions: %w", err) switch { case isKeyDataError(err): - return nil, nil, InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: err.Error()} + return nil, nil, InvalidKeyDataError{RetryProvision: false, msg: err.Error()} case isPolicyDataError(err): return nil, nil, InvalidPolicyDataError(err.Error()) case isAuthFailError(err, tpm2.CommandPolicySecret, 1): @@ -102,11 +100,11 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b case tpm2.IsTPMWarning(err, tpm2.WarningLockout, tpm2.CommandPolicySecret): return nil, nil, ErrTPMLockout case tpm2.IsResourceUnavailableError(err, k.data.staticPolicyData.pcrPolicyCounterHandle): - return nil, nil, InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: "no PCR policy counter found"} + return nil, nil, InvalidKeyDataError{RetryProvision: false, msg: "no PCR policy counter found"} case tpm2.IsResourceUnavailableError(err, lockNVHandle): // This is technically a provisioning error, but the current index can't be recreated. This key // can never be recovered even after re-provisioning. - return nil, nil, InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: "a required NV index is missing from the TPM"} + return nil, nil, InvalidKeyDataError{RetryProvision: false, msg: "a required NV index is missing from the TPM"} case tpm2.IsTPMError(err, tpm2.ErrorNVLocked, tpm2.CommandPolicyNV): return nil, nil, ErrSealedKeyAccessLocked } @@ -124,7 +122,7 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b // We could get here if some of the metadata required to execute the policy session is invalid // (eg, staticPolicyData.AuthPublicKey) or if lockNVHandle has a name that is different to the // one that existed when the key was sealed. - return nil, nil, InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: "the authorization policy check failed during unsealing"} + return nil, nil, InvalidKeyDataError{RetryProvision: false, msg: "the authorization policy check failed during unsealing"} case isAuthFailError(err, tpm2.CommandUnseal, 1): return nil, nil, ErrPINFail case tpm2.IsTPMWarning(err, tpm2.WarningLockout, tpm2.CommandUnseal): @@ -139,7 +137,7 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b var sealedData sealedData if _, err := tpm2.UnmarshalFromBytes(keyData, &sealedData); err != nil { - return nil, nil, InvalidKeyDataError{Type: InvalidKeyDataErrorFatal, msg: err.Error()} + return nil, nil, InvalidKeyDataError{RetryProvision: false, msg: err.Error()} } return sealedData.Key, sealedData.AuthPrivateKey, nil diff --git a/unseal_test.go b/unseal_test.go index d8148be0..d1fc2334 100644 --- a/unseal_test.go +++ b/unseal_test.go @@ -225,7 +225,7 @@ func TestUnsealErrorHandling(t *testing.T) { t.Errorf("EvictControl failed: %v", err) } }) - if _, ok := err.(InvalidKeyDataError); !ok || err.(InvalidKeyDataError).Type != InvalidKeyDataErrorTPMLoad || + if _, ok := err.(InvalidKeyDataError); !ok || !err.(InvalidKeyDataError).RetryProvision || err.Error() != "invalid key data: cannot load sealed key object in to TPM: bad sealed key object or parent object" { t.Errorf("Unexpected error: %v", err) } @@ -326,7 +326,7 @@ func TestUnsealErrorHandling(t *testing.T) { t.Fatalf("NVUndefineSpace failed: %v", err) } }) - if _, ok := err.(InvalidKeyDataError); !ok || err.(InvalidKeyDataError).Type != InvalidKeyDataErrorFatal || + if _, ok := err.(InvalidKeyDataError); !ok || err.(InvalidKeyDataError).RetryProvision || err.Error() != "invalid key data: a required NV index is missing from the TPM" { t.Errorf("Unexpected error: %v", err) } @@ -350,7 +350,7 @@ func TestUnsealErrorHandling(t *testing.T) { t.Fatalf("ProvisionTPM failed: %v", err) } }) - if _, ok := err.(InvalidKeyDataError); !ok || err.(InvalidKeyDataError).Type != InvalidKeyDataErrorFatal || + if _, ok := err.(InvalidKeyDataError); !ok || err.(InvalidKeyDataError).RetryProvision || err.Error() != "invalid key data: the authorization policy check failed during unsealing" { t.Errorf("Unexpected error: %v", err) } @@ -369,7 +369,7 @@ func TestUnsealErrorHandling(t *testing.T) { t.Fatalf("NVUndefineSpace failed: %v", err) } }) - if _, ok := err.(InvalidKeyDataError); !ok || err.(InvalidKeyDataError).Type != InvalidKeyDataErrorFatal || + if _, ok := err.(InvalidKeyDataError); !ok || err.(InvalidKeyDataError).RetryProvision || err.Error() != "invalid key data: no PCR policy counter found" { t.Errorf("Unexpected error: %v", err) } From 4b7b6114898601aede7a89c8723826a584363bf0 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Tue, 22 Sep 2020 14:54:19 +0100 Subject: [PATCH 13/13] Change keyDataLoadError to errInvalidTPMSealedObject --- keydata.go | 21 +++------------------ pin.go | 2 +- seal.go | 2 +- unseal.go | 2 +- 4 files changed, 6 insertions(+), 21 deletions(-) diff --git a/keydata.go b/keydata.go index ae4868e3..d6ef60cb 100644 --- a/keydata.go +++ b/keydata.go @@ -374,22 +374,7 @@ func isKeyDataError(err error) bool { return xerrors.As(err, &e) } -type keyDataLoadError struct { - err error -} - -func (e keyDataLoadError) Error() string { - return e.err.Error() -} - -func (e keyDataLoadError) Unwrap() error { - return e.err -} - -func isKeyDataLoadError(err error) bool { - var e keyDataLoadError - return xerrors.As(err, &e) -} +var errInvalidTPMSealedObject = errors.New("bad sealed key object or parent object") // load loads the TPM sealed object associated with this keyData in to the storage hierarchy of the TPM, and returns the newly // created tpm2.ResourceContext. @@ -409,7 +394,7 @@ func (d *keyData) load(tpm *tpm2.TPMContext, session tpm2.SessionContext) (tpm2. invalidObject = true } if invalidObject { - return nil, keyDataLoadError{errors.New("cannot load sealed key object in to TPM: bad sealed key object or parent object")} + err = errInvalidTPMSealedObject } return nil, xerrors.Errorf("cannot load sealed key object in to TPM: %w", err) } @@ -738,7 +723,7 @@ func (k *SealedKeyObject) Validate(tpm *TPMConnection, authKey TPMPolicyAuthKey) } _, err = k.data.validate(tpm.TPMContext, key, tpm.HmacSession()) switch { - case isKeyDataLoadError(err): + case xerrors.Is(err, errInvalidTPMSealedObject): return InvalidKeyDataError{RetryProvision: true, msg: err.Error()} case isKeyDataError(err): return InvalidKeyDataError{RetryProvision: false, msg: err.Error()} diff --git a/pin.go b/pin.go index 9e1eddf8..9c8752cd 100644 --- a/pin.go +++ b/pin.go @@ -170,7 +170,7 @@ func ChangePIN(tpm *TPMConnection, path string, oldPIN, newPIN string) error { // Read and validate the key data file data, _, pcrPolicyCounterPub, err := decodeAndValidateKeyData(tpm.TPMContext, keyFile, nil, tpm.HmacSession()) switch { - case isKeyDataLoadError(err): + case xerrors.Is(err, errInvalidTPMSealedObject): return InvalidKeyDataError{RetryProvision: true, msg: err.Error()} case isKeyDataError(err): return InvalidKeyDataError{RetryProvision: false, msg: err.Error()} diff --git a/seal.go b/seal.go index f07a2700..9a86ab41 100644 --- a/seal.go +++ b/seal.go @@ -322,7 +322,7 @@ func updateKeyPCRProtectionPolicyCommon(tpm *tpm2.TPMContext, keyPath string, au data, authKey, pcrPolicyCounterPub, err := decodeAndValidateKeyData(tpm, keyFile, authData, session) switch { - case isKeyDataLoadError(err): + case xerrors.Is(err, errInvalidTPMSealedObject): return InvalidKeyDataError{RetryProvision: true, msg: err.Error()} case isKeyDataError(err): return InvalidKeyDataError{RetryProvision: false, msg: err.Error()} diff --git a/unseal.go b/unseal.go index 3c968007..1a876340 100644 --- a/unseal.go +++ b/unseal.go @@ -74,7 +74,7 @@ func (k *SealedKeyObject) UnsealFromTPM(tpm *TPMConnection, pin string) (key []b switch { case tpm2.IsResourceUnavailableError(err, tcg.SRKHandle): return nil, nil, ErrTPMProvisioning - case isKeyDataLoadError(err): + case xerrors.Is(err, errInvalidTPMSealedObject): return nil, nil, InvalidKeyDataError{RetryProvision: true, msg: err.Error()} case err != nil: return nil, nil, xerrors.Errorf("cannot load sealed key in to TPM: %w", err)