Skip to content

Commit

Permalink
Have a single ActivateVolumeOptions struct for the ActivateVolumeWith…
Browse files Browse the repository at this point in the history
…* funcs

Also rename PIN in its definition to UserPassprase to move toward
better and also more abstract naming.

Same in the corresponding RecoveryKeyUsageReason code.

Se also issue #113
  • Loading branch information
pedronis committed Sep 25, 2020
1 parent 9fd6c3e commit fbec76e
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 78 deletions.
115 changes: 59 additions & 56 deletions crypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,10 @@ const (
// is also not correctly provisioned.
RecoveryKeyUsageReasonInvalidKeyFile

// RecoveryKeyUsageReasonPINFail indicates that a volume had to be activated with the fallback recovery key because the correct PIN
// was not provided.
RecoveryKeyUsageReasonPINFail
// RecoveryKeyUsageReasonUserPassphraseFail indicates that a
// volume had to be activated with the fallback recovery key
// because the correct user passphrase/PIN was not provided.
RecoveryKeyUsageReasonUserPassphraseFail
)

func activateWithRecoveryKey(volumeName, sourceDevicePath string, keyReader io.Reader, tries int, reason RecoveryKeyUsageReason, activateOptions []string, keyringPrefix string) error {
Expand Down Expand Up @@ -367,7 +368,7 @@ func isLockAccessError(err error) bool {
return xerrors.As(err, &e)
}

func activateWithTPMKey(tpm *TPMConnection, volumeName, sourceDevicePath, keyPath string, pinReader io.Reader, pinTries int, lock bool, activateOptions []string, keyringPrefix string) error {
func activateWithTPMKey(tpm *TPMConnection, volumeName, sourceDevicePath, keyPath string, passphraseReader io.Reader, passphraseTries int, lock bool, activateOptions []string, keyringPrefix string) error {
var lockErr error
sealedKey, authPrivateKey, err := func() ([]byte, TPMPolicyAuthKey, error) {
defer func() {
Expand All @@ -383,20 +384,20 @@ func activateWithTPMKey(tpm *TPMConnection, volumeName, sourceDevicePath, keyPat
}

switch {
case pinTries == 0 && k.AuthMode2F() == AuthModePIN:
case passphraseTries == 0 && k.AuthMode2F() == AuthModePIN:
return nil, nil, requiresPinErr
case pinTries == 0:
pinTries = 1
case passphraseTries == 0:
passphraseTries = 1
}

var sealedKey []byte
var authPrivateKey []byte

for ; pinTries > 0; pinTries-- {
for ; passphraseTries > 0; passphraseTries-- {
var pin string
if k.AuthMode2F() == AuthModePIN {
r := pinReader
pinReader = nil
r := passphraseReader
passphraseReader = nil
pin, err = getPassword(sourceDevicePath, "PIN", r)
if err != nil {
return nil, nil, xerrors.Errorf("cannot obtain PIN: %w", err)
Expand Down Expand Up @@ -454,40 +455,55 @@ func makeActivateOptions(in []string) ([]string, error) {
return append(out, "tries=1"), nil
}

// 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
// falling back to activating with the recovery key if RecoveryKeyTries is greater than zero. Setting this to zero disables unsealing
// with a PIN - in this case, an error will be returned if the sealed key object indicates that a PIN has been set. Attempts to
// unseal with a PIN will stop if the TPM enters dictionary attack lockout mode before this limit is reached.
PINTries int

// RecoveryKeyTries specifies the maximum number of times that activation with the fallback recovery key should be attempted
// if activation with the TPM sealed key fails, before failing with an error. Setting this to zero will disable attempts to activate
// with the fallback recovery key.
// ActivateVolumeOptions provides options to the ActivateVolumeWith*
// family of functions.
type ActivateVolumeOptions struct {
// UserPassphraseTries specifies the maximum number of times
// that unsealing with a user passphrase should be attempted
// before failing with an error and falling back to activating
// with the recovery key if RecoveryKeyTries is greater than
// zero. Setting this to zero disables unsealing with a user
// passphrase - in this case, an error will be returned if the
// sealed key object indicates that a user passphrase has been
// set. With a TPM attempts to unseal with a user passphrase
// will stop if the TPM enters dictionary attack lockout mode
// before this limit is reached.
// It is unused by ActivateWithRecoveryKey.
UserPassphraseTries int

// RecoveryKeyTries specifies the maximum number of times that
// activation with the fallback recovery key should be
// attempted. It is considered directly by
// ActivateWithRecoveryKey and when falling in the other
// cases, for example failed TPM unsealing. Setting this to
// zero will disable attempts to activate with the fallback
// recovery key.
RecoveryKeyTries int

// ActivateOptions provides a mechanism to pass additional options to systemd-cryptsetup.
// ActivateOptions provides a mechanism to pass additional
// options to systemd-cryptsetup.
ActivateOptions []string

// LockSealedKeyAccess controls whether LockAccessToSealedKeys should be called after unsealing the TPM sealed key. It is called if
// this is set to true, and not called if this is set to false.
LockSealedKeyAccess bool
// LockSealedKeys controls whether access to the keys locked
// after unsealing. That means calling LockAccessToSealedKeys
// in TPM case.
LockSealedKeys bool

// KeyringPrefix is the prefix used for the description of any kernel keys created during activation.
// KeyringPrefix is the prefix used for the description of any
// kernel keys created during activation.
KeyringPrefix string
}

// ActivateVolumeWithTPMSealedKey attempts to activate the LUKS encrypted volume at sourceDevicePath and create a mapping with the
// name volumeName, using the TPM sealed key object at the specified keyPath. This makes use of systemd-cryptsetup.
//
// If the TPM sealed key object has a PIN defined, then this function will use systemd-ask-password to request it. If pinReader is not
// nil, then an attempt to read the PIN from this will be made instead by reading all characters until the first newline. The PINTries
// field of options defines how many attempts should be made to obtain the correct PIN before failing.
// If the TPM sealed key object has a user passphrase/PIN defined, then this function will use systemd-ask-password to request it. If passphraseReader is not
// nil, then an attempt to read the user passphrase/PIN from this will be made instead by reading all characters until the first newline. The UserPassphraseTries
// field of options defines how many attempts should be made to obtain the correct passphrase before failing.
//
// The ActivateOptions field of options can be used to specify additional options to pass to systemd-cryptsetup.
//
// If the LockSealedKeyAccess field of options is true, then this function will call LockAccessToSealedKeys after unsealing the key
// If the LockSealedKeys field of options is true, then this function will call LockAccessToSealedKeys after unsealing the key
// and before activating the LUKS volume.
//
// If activation with the TPM sealed key object fails, this function will attempt to activate it with the fallback recovery key
Expand All @@ -497,7 +513,7 @@ type ActivateWithTPMSealedKeyOptions struct {
// calling GetActivationDataFromKernel will return a *RecoveryActivationData containing the recovery key and the reason that the
// recovery key was requested.
//
// If either the PINTries or RecoveryKeyTries fields of options are less than zero, an error will be returned. If the ActivateOptions
// If either the UserPassphraseTries or RecoveryKeyTries fields of options are less than zero, an error will be returned. If the ActivateOptions
// field of options contains the "tries=" option, then an error will be returned. This option cannot be used with this function.
//
// If the LockSealedKeyAccess field of options is true and the call to LockAccessToSealedKeys fails, a LockAccessToSealedKeysError
Expand All @@ -514,9 +530,9 @@ type ActivateWithTPMSealedKeyOptions struct {
//
// If the volume is successfully activated, either with the TPM sealed key or the fallback recovery key, this function returns true.
// If it is not successfully activated, then this function returns false.
func ActivateVolumeWithTPMSealedKey(tpm *TPMConnection, volumeName, sourceDevicePath, keyPath string, pinReader io.Reader, options *ActivateWithTPMSealedKeyOptions) (bool, error) {
if options.PINTries < 0 {
return false, errors.New("invalid PINTries")
func ActivateVolumeWithTPMSealedKey(tpm *TPMConnection, volumeName, sourceDevicePath, keyPath string, passphraseReader io.Reader, options *ActivateVolumeOptions) (bool, error) {
if options.UserPassphraseTries < 0 {
return false, errors.New("invalid UserPassphraseTries")
}
if options.RecoveryKeyTries < 0 {
return false, errors.New("invalid RecoveryKeyTries")
Expand All @@ -527,7 +543,7 @@ func ActivateVolumeWithTPMSealedKey(tpm *TPMConnection, volumeName, sourceDevice
return false, err
}

if err := activateWithTPMKey(tpm, volumeName, sourceDevicePath, keyPath, pinReader, options.PINTries, options.LockSealedKeyAccess, activateOptions, options.KeyringPrefix); err != nil {
if err := activateWithTPMKey(tpm, volumeName, sourceDevicePath, keyPath, passphraseReader, options.UserPassphraseTries, options.LockSealedKeys, activateOptions, options.KeyringPrefix); err != nil {
reason := RecoveryKeyUsageReasonUnexpectedError
switch {
case isLockAccessError(err):
Expand All @@ -539,9 +555,9 @@ func ActivateVolumeWithTPMSealedKey(tpm *TPMConnection, volumeName, sourceDevice
case isInvalidKeyFileError(err):
reason = RecoveryKeyUsageReasonInvalidKeyFile
case xerrors.Is(err, requiresPinErr):
reason = RecoveryKeyUsageReasonPINFail
reason = RecoveryKeyUsageReasonUserPassphraseFail
case xerrors.Is(err, ErrPINFail):
reason = RecoveryKeyUsageReasonPINFail
reason = RecoveryKeyUsageReasonUserPassphraseFail
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.
Expand All @@ -554,44 +570,31 @@ func ActivateVolumeWithTPMSealedKey(tpm *TPMConnection, volumeName, sourceDevice
return true, nil
}

// ActivateWithRecoveryKeyOptions provides options to ActivateVolumeWithRecoveryKey.
type ActivateWithRecoveryKeyOptions struct {
// Tries specifies the maximum number of times that activation with the fallback recovery key should be attempted before failing
// with an error.
Tries int

// ActivateOptions provides a mechanism to pass additional options to systemd-cryptsetup.
ActivateOptions []string

// KeyringPrefix is the prefix used for the description of any kernel keys created during activation.
KeyringPrefix string
}

// ActivateVolumeWithRecoveryKey attempts to activate the LUKS encrypted volume at sourceDevicePath and create a mapping with the
// name volumeName, using the fallback recovery key. This makes use of systemd-cryptsetup.
//
// This function will use systemd-ask-password to request the recovery key. If keyReader is not nil, then an attempt to read the key
// from this will be made instead by reading all characters until the first newline. The Tries field of options defines how many
// from this will be made instead by reading all characters until the first newline. The RecoveryKeyTries field of options defines how many
// attempts should be made to activate the volume with the recovery key before failing.
//
// The ActivateOptions field of options can be used to specify additional options to pass to systemd-cryptsetup.
//
// If activation with the recovery key is successful, calling GetActivationDataFromKernel will return a *RecoveryActivationData
// containing the recovery key and RecoveryKeyUsageReasonRequested as the recovery reason.
//
// If the Tries field of options is less than zero, an error will be returned. If the ActivateOptions field of options contains the
// If the RecoveryKeyTries field of options is less than zero, an error will be returned. If the ActivateOptions field of options contains the
// "tries=" option, then an error will be returned. This option cannot be used with this function.
func ActivateVolumeWithRecoveryKey(volumeName, sourceDevicePath string, keyReader io.Reader, options *ActivateWithRecoveryKeyOptions) error {
if options.Tries < 0 {
return errors.New("invalid Tries")
func ActivateVolumeWithRecoveryKey(volumeName, sourceDevicePath string, keyReader io.Reader, options *ActivateVolumeOptions) error {
if options.RecoveryKeyTries < 0 {
return errors.New("invalid RecoveryKeyTries")
}

activateOptions, err := makeActivateOptions(options.ActivateOptions)
if err != nil {
return err
}

return activateWithRecoveryKey(volumeName, sourceDevicePath, keyReader, options.Tries, RecoveryKeyUsageReasonRequested, activateOptions, options.KeyringPrefix)
return activateWithRecoveryKey(volumeName, sourceDevicePath, keyReader, options.RecoveryKeyTries, RecoveryKeyUsageReasonRequested, activateOptions, options.KeyringPrefix)
}

// ActivationData corresponds to some data added to the user keyring by one of the ActivateVolume functions.
Expand Down
Loading

0 comments on commit fbec76e

Please sign in to comment.