Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add validate api and clean up unseal errors #112

Open
wants to merge 13 commits into
base: v1-wip
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 31 additions & 15 deletions crypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -536,16 +550,18 @@ 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):
reason = RecoveryKeyUsageReasonPINFail
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}
Expand Down
18 changes: 9 additions & 9 deletions crypt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down Expand Up @@ -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"},
})
}

Expand All @@ -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"},
})
}

Expand Down
20 changes: 10 additions & 10 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 15 additions & 11 deletions export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"bytes"
"crypto/ecdsa"
"fmt"
"os"

"github.com/canonical/go-tpm2"
"github.com/chrisccoulson/tcglog-parser"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion internal/compattest/v0_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions internal/testutil/suites.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions internal/testutil/tpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading