diff --git a/crypt.go b/crypt.go index f0a0dc73..de5377eb 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. @@ -333,17 +333,21 @@ func activateWithRecoveryKey(volumeName, sourceDevicePath string, keyReader io.R return lastErr } +func isTPMLoadError(err error) bool { + var e InvalidKeyDataError + return xerrors.As(err, &e) && e.RetryProvision +} + 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) - } + if err == ErrTPMProvisioning || isTPMLoadError(err) { + // 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 - + // 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 } @@ -454,6 +458,16 @@ func makeActivateOptions(in []string) ([]string, error) { return append(out, "tries=1"), nil } +func isInvalidKeyDataError(err error) bool { + var e InvalidKeyDataError + 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 @@ -536,8 +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 = RecoveryKeyUsageReasonInvalidKeyData case xerrors.Is(err, requiresPinErr): reason = RecoveryKeyUsageReasonPINFail case xerrors.Is(err, ErrPINFail): @@ -545,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 f94f7627..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,11 +795,11 @@ 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 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"}, }) } @@ -815,11 +815,11 @@ 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 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..67333671 100644 --- a/errors.go +++ b/errors.go @@ -113,20 +113,20 @@ 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. -type InvalidKeyFileError struct { - msg string +// InvalidKeyDataError indicates that the provided key data is invalid. +type InvalidKeyDataError struct { + RetryProvision bool // a hint that the error might be resolved by calling ProvisionTPM + msg string } -func (e InvalidKeyFileError) Error() string { - return fmt.Sprintf("invalid key data file: %s", e.msg) +func (e InvalidKeyDataError) Error() string { + return "invalid key data: " + 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 61069dab..901ab054 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" @@ -59,8 +58,8 @@ var ( ExecutePolicySession = executePolicySession IdentifyInitialOSLaunchVerificationEvent = identifyInitialOSLaunchVerificationEvent IncrementPcrPolicyCounter = incrementPcrPolicyCounter - IsDynamicPolicyDataError = isDynamicPolicyDataError - IsStaticPolicyDataError = isStaticPolicyDataError + IsKeyDataError = isKeyDataError + IsPolicyDataError = isPolicyDataError LockNVIndexAttrs = lockNVIndexAttrs PerformPinChange = performPinChange ReadAndValidateLockNVIndexPublic = readAndValidateLockNVIndexPublic @@ -262,13 +261,18 @@ 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() +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 +} - _, _, _, err = decodeAndValidateKeyData(tpm, kf, authPrivateKey, session) - return err +func (k *SealedKeyObject) AuthPublicKey() *tpm2.Public { + return k.data.staticPolicyData.authPublicKey } 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/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.go b/keydata.go index a6f419d9..d6ef60cb 100644 --- a/keydata.go +++ b/keydata.go @@ -357,6 +357,25 @@ 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) +} + +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. func (d *keyData) load(tpm *tpm2.TPMContext, session tpm2.SessionContext) (tpm2.ResourceContext, error) { @@ -375,7 +394,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")} + err = errInvalidTPMSealedObject } return nil, xerrors.Errorf("cannot load sealed key object in to TPM: %w", err) } @@ -387,7 +406,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 +415,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 @@ -412,15 +431,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 { @@ -433,7 +453,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 +461,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 +476,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 +489,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 +517,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 +534,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 +561,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 +606,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 +653,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 +660,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)} + return nil, nil, nil, keyDataError{xerrors.Errorf("cannot decode dynamic auth policy signing key data: %w", err)} } - if policyUpdateData.version != data.version { - return nil, nil, nil, keyFileError{errors.New("mismatched metadata versions")} - } - 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,8 +711,29 @@ 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 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 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 xerrors.Is(err, errInvalidTPMSealedObject): + return InvalidKeyDataError{RetryProvision: true, msg: err.Error()} + case isKeyDataError(err): + return InvalidKeyDataError{RetryProvision: false, 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 @@ -702,7 +745,7 @@ func ReadSealedKeyObject(path string) (*SealedKeyObject, error) { data, err := decodeKeyData(f) if err != nil { - return nil, InvalidKeyFileError{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 8c1856aa..f2d5c76c 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,30 +33,238 @@ 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) +} + +var _ = Suite(&keyDataSuite{}) - c.Check(ValidateKeyDataFile(s.TPM.TPMContext, keyFile, authPrivateKey, s.TPM.HmacSession()), IsNil) +func (s *keyDataSuite) TestValidateAfterLock(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) + c.Assert(err, IsNil) + c.Check(k.Validate(s.TPM, s.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(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.RetryProvision, Equals, false) +} + +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.RetryProvision, Equals, false) +} + +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.RetryProvision, Equals, false) +} + +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.RetryProvision, Equals, true) +} + +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.RetryProvision, Equals, false) +} + +func (s *keyDataSuite) TestValidateInvalidPCRPolicyCounterHandle2(c *C) { + k, err := ReadSealedKeyObject(s.keyFile) + c.Assert(err, 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.RetryProvision, Equals, false) +} + +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.RetryProvision, Equals, false) +} + +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.RetryProvision, Equals, false) +} + +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.RetryProvision, Equals, false) +} + +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.RetryProvision, Equals, false) +} + +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.RetryProvision, Equals, false) +} + +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) + defer s.ClearTPMWithPlatformAuth(c) + + 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.RetryProvision, Equals, false) +} + +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.RetryProvision, Equals, false) } diff --git a/pin.go b/pin.go index 88e2581c..9c8752cd 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 { @@ -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 xerrors.Is(err, errInvalidTPMSealedObject): + return InvalidKeyDataError{RetryProvision: true, msg: err.Error()} + case isKeyDataError(err): + 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/policy.go b/policy.go index 74bacee3..ddb283fa 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,26 +962,23 @@ 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 policy digests is invalid + return policyDataError{errors.New("cannot complete OR assertions for PCR policy: invalid metadata")} } - return dynamicPolicyDataError{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 staticPolicyDataError{errors.New("invalid handle for PCR policy counter")} + return keyDataError{errors.New("invalid handle type for PCR policy counter")} } var policyCounter tpm2.ResourceContext 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 file is invalid and must be recreated. - return staticPolicyDataError{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) } @@ -1009,8 +989,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 staticPolicyDataError{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) @@ -1028,7 +1008,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 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) } @@ -1039,11 +1019,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. - return dynamicPolicyDataError{errors.New("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 staticPolicyDataError{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) } @@ -1051,13 +1032,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 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 staticPolicyDataError{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) } @@ -1078,10 +1059,9 @@ 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. - // 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")} + // 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) } @@ -1089,7 +1069,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..f328aa0d 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 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 !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 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 !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 for PCR policy: 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 !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,8 @@ 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 !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) { @@ -1935,7 +1936,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 !IsKeyDataError(err) || err.Error() != "public area of dynamic authorization policy signing key has an unsupported "+ "name algorithm" { t.Errorf("Unexpected error: %v", err) } @@ -1984,9 +1985,9 @@ 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 !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 +2050,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 +2161,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 +2257,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 +2309,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 +2391,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/seal.go b/seal.go index 6762651f..9a86ab41 100644 --- a/seal.go +++ b/seal.go @@ -321,11 +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()} - } - // FIXME: Turn the missing lock NV index in to ErrProvisioning + switch { + case xerrors.Is(err, errInvalidTPMSealedObject): + return InvalidKeyDataError{RetryProvision: true, msg: err.Error()} + case isKeyDataError(err): + return InvalidKeyDataError{RetryProvision: false, msg: err.Error()} + case err != nil: return xerrors.Errorf("cannot read and validate key data file: %w", err) } @@ -366,7 +367,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. @@ -386,7 +387,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/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) } } 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 be852210..1a876340 100644 --- a/unseal.go +++ b/unseal.go @@ -36,27 +36,24 @@ 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. +// 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). // -// 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 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 any of the metadata in this key file is invalid, 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 InvalidKeyDataError error will be returned. In this case, there isn't a way to recover this key. // -// If the TPM is missing any persistent resources associated with this key file, then a InvalidKeyFileError error will be returned. -// -// 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,55 +61,23 @@ 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 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. 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() // Load the key data keyObject, err := k.data.load(tpm.TPMContext, hmacSession) switch { - 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 - srk, err2 := tpm.CreateResourceContextFromTPM(tcg.SRKHandle) - switch { - case tpm2.IsResourceUnavailableError(err2, tcg.SRKHandle): - return nil, nil, ErrTPMProvisioning - case 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) - 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 xerrors.Is(err, errInvalidTPMSealedObject): + return nil, nil, InvalidKeyDataError{RetryProvision: true, msg: err.Error()} 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) @@ -126,15 +91,20 @@ 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): - return nil, nil, InvalidKeyFileError{err.Error()} + case isKeyDataError(err): + return nil, nil, InvalidKeyDataError{RetryProvision: false, msg: err.Error()} + case isPolicyDataError(err): + 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, k.data.staticPolicyData.pcrPolicyCounterHandle): + return nil, nil, InvalidKeyDataError{RetryProvision: false, msg: "no PCR policy counter found"} 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, 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 } @@ -149,9 +119,14 @@ 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, 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): + return nil, nil, ErrTPMLockout case err != nil: return nil, nil, xerrors.Errorf("cannot unseal key: %w", err) } @@ -162,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, InvalidKeyDataError{RetryProvision: false, msg: err.Error()} } return sealedData.Key, sealedData.AuthPrivateKey, nil diff --git a/unseal_test.go b/unseal_test.go index d4e191e5..d1fc2334 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.(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) } }) @@ -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) } }) @@ -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).RetryProvision || + 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).RetryProvision || + 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).RetryProvision || + err.Error() != "invalid key data: no PCR policy counter found" { + t.Errorf("Unexpected error: %v", err) + } + }) }