diff options
| -rw-r--r-- | README.md | 8 | ||||
| -rw-r--r-- | actions/context.go | 4 | ||||
| -rw-r--r-- | actions/policy.go | 27 | ||||
| -rw-r--r-- | cmd/fscrypt/commands.go | 35 | ||||
| -rw-r--r-- | cmd/fscrypt/errors.go | 8 | ||||
| -rw-r--r-- | cmd/fscrypt/flags.go | 10 | ||||
| -rw-r--r-- | cmd/fscrypt/status.go | 2 | ||||
| -rw-r--r-- | keyring/fs_keyring.go | 100 | ||||
| -rw-r--r-- | keyring/keyring.go | 35 | ||||
| -rw-r--r-- | keyring/keyring_test.go | 163 | ||||
| -rw-r--r-- | pam_fscrypt/pam_fscrypt.go | 15 |
11 files changed, 363 insertions, 44 deletions
@@ -279,8 +279,9 @@ after `pam_unix.so` in `/etc/pam.d/common-session` or similar. The `lock_policies` option locks the directories protected with the user's login passphrase when the last session ends. The `drop_caches` option tells fscrypt to clear the filesystem caches when the last session closes, ensuring all the -locked data is inaccessible. All the types also support the `debug` option which -prints additional debug information to the syslog. +locked data is inaccessible; this only needed for v1 encryption policies. +All the types also support the `debug` option which prints additional +debug information to the syslog. ## Note about stability @@ -368,7 +369,8 @@ Protected with 1 protector: PROTECTOR LINKED DESCRIPTION 7626382168311a9d No custom protector "Super Secret" -# Lock the directory. +# Lock the directory. 'sudo' and the '--user' argument are only +# required if the directory uses a v1 encryption policy. >>>>> sudo fscrypt lock /mnt/disk/dir1 --user=$USER Encrypted data removed from filesystem cache. "/mnt/disk/dir1" is now locked. diff --git a/actions/context.go b/actions/context.go index f7e98cf..f07f225 100644 --- a/actions/context.go +++ b/actions/context.go @@ -62,7 +62,9 @@ type Context struct { // the filesystem keyring are provisioned. Mount *filesystem.Mount // TargetUser is the user for whom protectors are created, and to whose - // keyring policies using the user keyring are provisioned. + // keyring policies using the user keyring are provisioned. It's also + // the user for whom the keys are claimed in the filesystem keyring when + // v2 policies are provisioned. TargetUser *user.User } diff --git a/actions/policy.go b/actions/policy.go index f6d3ea9..f448620 100644 --- a/actions/policy.go +++ b/actions/policy.go @@ -63,6 +63,9 @@ func PurgeAllPolicies(ctx *Context) error { case keyring.ErrKeyFilesOpen: log.Printf("Key for policy %s couldn't be fully removed because some files are still in-use", policyDescriptor) + case keyring.ErrKeyAddedByOtherUsers: + log.Printf("Key for policy %s couldn't be fully removed because other user(s) have added it too", + policyDescriptor) default: return err } @@ -200,6 +203,11 @@ func (policy *Policy) Options() *metadata.EncryptionOptions { return policy.data.Options } +// Version returns the version of this policy. +func (policy *Policy) Version() int64 { + return policy.data.Options.PolicyVersion +} + // Destroy removes a policy from the filesystem. The internal key should still // be wiped with Lock(). func (policy *Policy) Destroy() error { @@ -382,14 +390,15 @@ func (policy *Policy) GetProvisioningStatus() keyring.KeyStatus { return status } -// IsProvisioned returns a boolean indicating if the policy has its key in the -// keyring, meaning files and directories using this policy are accessible. -func (policy *Policy) IsProvisioned() bool { +// IsProvisionedByTargetUser returns true if the policy's key is present in the +// target kernel keyring, but not if that keyring is a filesystem keyring and +// the key only been added by users other than Context.TargetUser. +func (policy *Policy) IsProvisionedByTargetUser() bool { return policy.GetProvisioningStatus() == keyring.KeyPresent } // IsFullyDeprovisioned returns true if the policy has been fully deprovisioned, -// including all files protected by it having been closed. +// including by all users and with all files protected by it having been closed. func (policy *Policy) IsFullyDeprovisioned() bool { return policy.GetProvisioningStatus() == keyring.KeyAbsent } @@ -415,13 +424,19 @@ func (policy *Policy) Deprovision() error { // NeedsUserKeyring returns true if Provision and Deprovision for this policy // will use a user keyring, not a filesystem keyring. func (policy *Policy) NeedsUserKeyring() bool { - return !policy.Context.Config.GetUseFsKeyringForV1Policies() + return policy.Version() == 1 && !policy.Context.Config.GetUseFsKeyringForV1Policies() } // NeedsRootToProvision returns true if Provision and Deprovision will require // root for this policy in the current configuration. func (policy *Policy) NeedsRootToProvision() bool { - return policy.Context.Config.GetUseFsKeyringForV1Policies() + return policy.Version() == 1 && policy.Context.Config.GetUseFsKeyringForV1Policies() +} + +// CanBeAppliedWithoutProvisioning returns true if this process can apply this +// policy to a directory without first calling Provision. +func (policy *Policy) CanBeAppliedWithoutProvisioning() bool { + return policy.Version() == 1 || util.IsUserRoot() } // commitData writes the Policy's current data to the filesystem. diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go index 621651e..0bf0a4c 100644 --- a/cmd/fscrypt/commands.go +++ b/cmd/fscrypt/commands.go @@ -139,6 +139,18 @@ func encryptAction(c *cli.Context) error { // of the key for the given encryption policy (if policy != nil) or for the // current default encryption policy (if policy == nil). func validateKeyringPrereqs(ctx *actions.Context, policy *actions.Policy) error { + var policyVersion int64 + if policy == nil { + policyVersion = ctx.Config.Options.PolicyVersion + } else { + policyVersion = policy.Version() + } + // If it's a v2 policy, we're good to go, since non-root users can + // add/remove v2 policy keys directly to/from the filesystem, where they + // are usable by the filesystem on behalf of any process. + if policyVersion != 1 { + return nil + } if ctx.Config.GetUseFsKeyringForV1Policies() { // We'll be using the filesystem keyring, but it's a v1 // encryption policy so root is required. @@ -225,14 +237,19 @@ func encryptPath(path string) (err error) { } }() - // Unlock() first, so if the Unlock() fails the directory isn't changed. - if !skipUnlockFlag.Value { + // Unlock() and Provision() first, so if that if these fail the + // directory isn't changed, and also because v2 policies can't be + // applied while deprovisioned unless the process is running as root. + if !skipUnlockFlag.Value || !policy.CanBeAppliedWithoutProvisioning() { if err = policy.Unlock(optionFn, existingKeyFn); err != nil { return } if err = policy.Provision(); err != nil { return } + if skipUnlockFlag.Value { + defer policy.Deprovision() + } } if err = policy.Apply(path); os.IsPermission(errors.Cause(err)) { // EACCES at this point indicates ownership issues. @@ -352,8 +369,9 @@ func unlockAction(c *cli.Context) error { return newExitError(c, err) } // Check if directory is already unlocked - if policy.IsProvisioned() { - log.Printf("policy %s is already provisioned", policy.Descriptor()) + if policy.IsProvisionedByTargetUser() { + log.Printf("policy %s is already provisioned by %v", + policy.Descriptor(), ctx.TargetUser.Username) return newExitError(c, errors.Wrapf(ErrPolicyUnlocked, path)) } @@ -395,8 +413,13 @@ var Lock = cli.Command{ For this to be effective, all files in the directory must first be closed. - The %s=true option may be needed to properly lock the directory. - Root is required for this. + If the directory uses a v1 encryption policy, then the %s=true + option may be needed to properly lock it. Root is required for + this. + + If the directory uses a v2 encryption policy, then a non-root + user can lock it, but only if it's the same user who unlocked it + originally and if no other users have unlocked it too. WARNING: even after the key has been removed, decrypted data may still be present in freed memory, where it may still be diff --git a/cmd/fscrypt/errors.go b/cmd/fscrypt/errors.go index ac969f6..ba9ec7a 100644 --- a/cmd/fscrypt/errors.go +++ b/cmd/fscrypt/errors.go @@ -101,6 +101,9 @@ func getErrorSuggestions(err error) string { still open. These files remain accessible. Try killing any processes using files in the directory, then re-running 'fscrypt lock'.` + case keyring.ErrKeyAddedByOtherUsers: + return `Directory couldn't be fully locked because other user(s) + have unlocked it.` case keyring.ErrSessionUserKeying: return `This is usually the result of a bad PAM configuration. Either correct the problem in your PAM stack, enable @@ -145,7 +148,10 @@ func getErrorSuggestions(err error) string { case ErrFsKeyringPerm: return `Either this command should be run as root, or you should set '"use_fs_keyring_for_v1_policies": false' in - /etc/fscrypt.conf.` + /etc/fscrypt.conf, or you should re-create your + encrypted directories using v2 encryption policies + rather than v1 (this requires setting '"policy_version": + "2"' in the "options" section of /etc/fscrypt.conf).` case ErrSpecifyUser: return fmt.Sprintf(`When running this command as root, you usually still want to provision/remove keys for a normal diff --git a/cmd/fscrypt/flags.go b/cmd/fscrypt/flags.go index 361732c..a22ec05 100644 --- a/cmd/fscrypt/flags.go +++ b/cmd/fscrypt/flags.go @@ -162,10 +162,12 @@ var ( } dropCachesFlag = &boolFlag{ Name: "drop-caches", - Usage: `After purging the keys from the keyring, drop the - associated caches for the purge to take effect. Without - this flag, cached encrypted files may still have their - plaintext visible. Requires root privileges.`, + Usage: `After removing the key(s) from the keyring, drop the + kernel's filesystem caches if needed. Without this flag, + files encrypted with v1 encryption policies may still be + accessible. This flag is not needed for v2 encryption + policies. This flag, if actually needed, requires root + privileges.`, Default: true, } ) diff --git a/cmd/fscrypt/status.go b/cmd/fscrypt/status.go index 3f7f577..bf11495 100644 --- a/cmd/fscrypt/status.go +++ b/cmd/fscrypt/status.go @@ -68,7 +68,7 @@ func yesNoString(b bool) string { func policyUnlockedStatus(policy *actions.Policy) string { switch policy.GetProvisioningStatus() { - case keyring.KeyPresent: + case keyring.KeyPresent, keyring.KeyPresentButOnlyOtherUsers: return "Yes" case keyring.KeyAbsent: return "No" diff --git a/keyring/fs_keyring.go b/keyring/fs_keyring.go index bed23af..970105e 100644 --- a/keyring/fs_keyring.go +++ b/keyring/fs_keyring.go @@ -37,6 +37,8 @@ import ( "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/filesystem" + "github.com/google/fscrypt/security" + "github.com/google/fscrypt/util" ) var ( @@ -99,6 +101,8 @@ func buildKeySpecifier(spec *unix.FscryptKeySpecifier, descriptor string) error switch len(descriptorBytes) { case unix.FSCRYPT_KEY_DESCRIPTOR_SIZE: spec.Type = unix.FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR + case unix.FSCRYPT_KEY_IDENTIFIER_SIZE: + spec.Type = unix.FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER default: return errors.Errorf("key descriptor %q has unknown length", descriptor) } @@ -106,6 +110,64 @@ func buildKeySpecifier(spec *unix.FscryptKeySpecifier, descriptor string) error return nil } +type savedPrivs struct { + ruid, euid, suid int +} + +// dropPrivsIfNeeded drops privileges (UIDs only) to the given user if we're +// working with a v2 policy key, and if the user is different from the user the +// process is currently running as. +// +// This is needed to change the effective UID so that FS_IOC_ADD_ENCRYPTION_KEY +// and FS_IOC_REMOVE_ENCRYPTION_KEY will add/remove a claim to the key for the +// intended user, and so that FS_IOC_GET_ENCRYPTION_KEY_STATUS will return the +// correct status flags for the user. +func dropPrivsIfNeeded(user *user.User, spec *unix.FscryptKeySpecifier) (*savedPrivs, error) { + if spec.Type == unix.FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR { + // v1 policy keys don't have any concept of user claims. + return nil, nil + } + targetUID := util.AtoiOrPanic(user.Uid) + ruid, euid, suid := security.GetUids() + if euid == targetUID { + return nil, nil + } + if err := security.SetUids(targetUID, targetUID, euid); err != nil { + return nil, err + } + return &savedPrivs{ruid, euid, suid}, nil +} + +// restorePrivs restores root privileges if needed. +func restorePrivs(privs *savedPrivs) error { + if privs != nil { + return security.SetUids(privs.ruid, privs.euid, privs.suid) + } + return nil +} + +// validateKeyDescriptor validates that the correct key descriptor was provided. +// This isn't really necessary; this is just an extra sanity check. +func validateKeyDescriptor(spec *unix.FscryptKeySpecifier, descriptor string) (string, error) { + if spec.Type != unix.FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER { + // v1 policy key: the descriptor is chosen arbitrarily by + // userspace, so there's nothing to validate. + return descriptor, nil + } + // v2 policy key. The descriptor ("identifier" in the kernel UAPI) is + // calculated as a cryptographic hash of the key itself. The kernel + // ignores the provided value, and calculates and returns it itself. So + // verify that the returned value is as expected. If it's not, the key + // doesn't actually match the encryption policy we thought it was for. + actual := hex.EncodeToString(spec.U[:unix.FSCRYPT_KEY_IDENTIFIER_SIZE]) + if descriptor == actual { + return descriptor, nil + } + return actual, + errors.Errorf("provided and actual key descriptors differ (%q != %q)", + descriptor, actual) +} + // fsAddEncryptionKey adds the specified encryption key to the specified filesystem. func fsAddEncryptionKey(key *crypto.Key, descriptor string, mount *filesystem.Mount, user *user.User) error { @@ -131,12 +193,22 @@ func fsAddEncryptionKey(key *crypto.Key, descriptor string, arg.Raw_size = uint32(key.Len()) C.memcpy(raw, key.UnsafePtr(), C.size_t(key.Len())) + savedPrivs, err := dropPrivsIfNeeded(user, &arg.Key_spec) + if err != nil { + return err + } _, _, errno := unix.Syscall(unix.SYS_IOCTL, dir.Fd(), unix.FS_IOC_ADD_ENCRYPTION_KEY, uintptr(argKey.UnsafePtr())) + restorePrivs(savedPrivs) + log.Printf("FS_IOC_ADD_ENCRYPTION_KEY(%q, %s, <raw>) = %v", mount.Path, descriptor, errno) if errno != 0 { return errors.Wrap(ErrKeyAdd, errno.Error()) } + if descriptor, err = validateKeyDescriptor(&arg.Key_spec, descriptor); err != nil { + fsRemoveEncryptionKey(descriptor, mount, user) + return err + } return nil } @@ -156,17 +228,33 @@ func fsRemoveEncryptionKey(descriptor string, mount *filesystem.Mount, return err } + savedPrivs, err := dropPrivsIfNeeded(user, &arg.Key_spec) + if err != nil { + return err + } _, _, errno := unix.Syscall(unix.SYS_IOCTL, dir.Fd(), unix.FS_IOC_REMOVE_ENCRYPTION_KEY, uintptr(unsafe.Pointer(&arg))) + restorePrivs(savedPrivs) + log.Printf("FS_IOC_REMOVE_ENCRYPTION_KEY(%q, %s) = %v, removal_status_flags=0x%x", mount.Path, descriptor, errno, arg.Removal_status_flags) switch errno { case 0: - if arg.Removal_status_flags&unix.FSCRYPT_KEY_REMOVAL_STATUS_FLAG_FILES_BUSY != 0 { + switch { + case arg.Removal_status_flags&unix.FSCRYPT_KEY_REMOVAL_STATUS_FLAG_OTHER_USERS != 0: + return ErrKeyAddedByOtherUsers + case arg.Removal_status_flags&unix.FSCRYPT_KEY_REMOVAL_STATUS_FLAG_FILES_BUSY != 0: return ErrKeyFilesOpen } return nil case unix.ENOKEY: + // ENOKEY means either the key is completely missing or that the + // current user doesn't have a claim to it. Distinguish between + // these two cases by getting the key status. + status, _ := fsGetEncryptionKeyStatus(descriptor, mount, user) + if status == KeyPresentButOnlyOtherUsers { + return ErrKeyAddedByOtherUsers + } return ErrKeyNotPresent default: return errors.Wrap(ErrKeyRemove, errno.Error()) @@ -190,8 +278,14 @@ func fsGetEncryptionKeyStatus(descriptor string, mount *filesystem.Mount, return KeyStatusUnknown, err } + savedPrivs, err := dropPrivsIfNeeded(user, &arg.Key_spec) + if err != nil { + return KeyStatusUnknown, err + } _, _, errno := unix.Syscall(unix.SYS_IOCTL, dir.Fd(), unix.FS_IOC_GET_ENCRYPTION_KEY_STATUS, uintptr(unsafe.Pointer(&arg))) + restorePrivs(savedPrivs) + log.Printf("FS_IOC_GET_ENCRYPTION_KEY_STATUS(%q, %s) = %v, status=%d, status_flags=0x%x", mount.Path, descriptor, errno, arg.Status, arg.Status_flags) if errno != 0 { @@ -201,6 +295,10 @@ func fsGetEncryptionKeyStatus(descriptor string, mount *filesystem.Mount, case unix.FSCRYPT_KEY_STATUS_ABSENT: return KeyAbsent, nil case unix.FSCRYPT_KEY_STATUS_PRESENT: + if arg.Key_spec.Type != unix.FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR && + (arg.Status_flags&unix.FSCRYPT_KEY_STATUS_FLAG_ADDED_BY_SELF) == 0 { + return KeyPresentButOnlyOtherUsers, nil + } return KeyPresent, nil case unix.FSCRYPT_KEY_STATUS_INCOMPLETELY_REMOVED: return KeyAbsentButFilesBusy, nil diff --git a/keyring/keyring.go b/keyring/keyring.go index f0bd1b7..925d059 100644 --- a/keyring/keyring.go +++ b/keyring/keyring.go @@ -22,15 +22,18 @@ // keyring.go, and they delegate to either user_keyring.go or fs_keyring.go, // depending on whether a user keyring or a filesystem keyring is being used. // +// v2 encryption policies always use the filesystem keyring. // v1 policies use the user keyring by default, but can be configured to use the // filesystem keyring instead (requires root and kernel v5.4+). package keyring import ( + "encoding/hex" "os/user" "strconv" "github.com/pkg/errors" + "golang.org/x/sys/unix" "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/filesystem" @@ -40,14 +43,15 @@ import ( // Keyring error values var ( - ErrKeyAdd = util.SystemError("could not add key to the keyring") - ErrKeyRemove = util.SystemError("could not remove key from the keyring") - ErrKeyNotPresent = errors.New("key not present or already removed") - ErrKeyFilesOpen = errors.New("some files using the key are still open") - ErrKeySearch = errors.New("could not find key with descriptor") - ErrSessionUserKeying = errors.New("user keyring not linked into session keyring") - ErrAccessUserKeyring = errors.New("could not access user keyring") - ErrLinkUserKeyring = util.SystemError("could not link user keyring into root keyring") + ErrKeyAdd = util.SystemError("could not add key to the keyring") + ErrKeyRemove = util.SystemError("could not remove key from the keyring") + ErrKeyNotPresent = errors.New("key not present or already removed") + ErrKeyFilesOpen = errors.New("some files using the key are still open") + ErrKeyAddedByOtherUsers = errors.New("other users have added the key too") + ErrKeySearch = errors.New("could not find key with descriptor") + ErrSessionUserKeying = errors.New("user keyring not linked into session keyring") + ErrAccessUserKeyring = errors.New("could not access user keyring") + ErrLinkUserKeyring = util.SystemError("could not link user keyring into root keyring") ) // Options are the options which specify *which* keyring the key should be @@ -69,9 +73,15 @@ type Options struct { } func shouldUseFsKeyring(descriptor string, options *Options) bool { - // Use the filesystem keyring if use_fs_keyring_for_v1_policies is set - // in /etc/fscrypt.conf and the kernel supports it. - return options.UseFsKeyringForV1Policies && isFsKeyringSupported(options.Mount) + // For v1 encryption policy keys, use the filesystem keyring if + // use_fs_keyring_for_v1_policies is set in /etc/fscrypt.conf and the + // kernel supports it. + if len(descriptor) == hex.EncodedLen(unix.FSCRYPT_KEY_DESCRIPTOR_SIZE) { + return options.UseFsKeyringForV1Policies && isFsKeyringSupported(options.Mount) + } + // For v2 encryption policy keys, always use the filesystem keyring; the + // kernel doesn't support any other way. + return true } // AddEncryptionKey adds an encryption policy key to a kernel keyring. It uses @@ -106,6 +116,7 @@ const ( KeyAbsent KeyAbsentButFilesBusy KeyPresent + KeyPresentButOnlyOtherUsers ) func (status KeyStatus) String() string { @@ -118,6 +129,8 @@ func (status KeyStatus) String() string { return "AbsentButFilesBusy" case KeyPresent: return "Present" + case KeyPresentButOnlyOtherUsers: + return "PresentButOnlyOtherUsers" default: return strconv.Itoa(int(status)) } diff --git a/keyring/keyring_test.go b/keyring/keyring_test.go index 9a4570b..a675a70 100644 --- a/keyring/keyring_test.go +++ b/keyring/keyring_test.go @@ -19,6 +19,8 @@ package keyring import ( + "os/user" + "strconv" "testing" "golang.org/x/sys/unix" @@ -50,6 +52,7 @@ var ( fakeValidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen) fakeInvalidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen-1) fakeV1Descriptor = "0123456789abcdef" + fakeV2Descriptor, _ = crypto.ComputeKeyDescriptor(fakeValidPolicyKey, 2) ) func assertKeyStatus(t *testing.T, descriptor string, options *Options, @@ -77,8 +80,8 @@ func getTestMount(t *testing.T) *filesystem.Mount { return mount } -// getTestMountV2 is like getTestMount, but it also checks that the filesystem -// keyring is supported. +// getTestMountV2 is like getTestMount, but it also checks that the +// filesystem keyring and v2 encryption policies are supported. func getTestMountV2(t *testing.T) *filesystem.Mount { mount := getTestMount(t) if !isFsKeyringSupported(mount) { @@ -93,9 +96,42 @@ func requireRoot(t *testing.T) { } } +// getNonRootUsers checks for root permission, then returns the users for uids +// 1000...1000+count-1. If this fails, the test is skipped. +func getNonRootUsers(t *testing.T, count int) []*user.User { + requireRoot(t) + users := make([]*user.User, count) + for i := 0; i < count; i++ { + uid := 1000 + i + user, err := user.LookupId(strconv.Itoa(uid)) + if err != nil { + t.Skip(err) + } + users[i] = user + } + return users +} + +func getOptionsForFsKeyringUsers(t *testing.T, numNonRootUsers int) (rootOptions *Options, userOptions []*Options) { + mount := getTestMountV2(t) + nonRootUsers := getNonRootUsers(t, numNonRootUsers) + rootOptions = &Options{ + Mount: mount, + User: testUser, + } + userOptions = make([]*Options, numNonRootUsers) + for i := 0; i < numNonRootUsers; i++ { + userOptions[i] = &Options{ + Mount: mount, + User: nonRootUsers[i], + } + } + return +} + // testAddAndRemoveKey does the common tests for adding+removing keys that are -// run in multiple configurations (v1 policies with user keyring and v1 policies -// with fs keyring). +// run in multiple configurations (v1 policies with user keyring, v1 policies +// with fs keyring, and v2 policies with fs keyring). func testAddAndRemoveKey(t *testing.T, descriptor string, options *Options) { // Basic add, get status, and remove @@ -167,3 +203,122 @@ func TestFsKeyringV1PolicyKey(t *testing.T) { } testAddAndRemoveKey(t, fakeV1Descriptor, options) } + +func TestV2PolicyKey(t *testing.T) { + mount := getTestMountV2(t) + options := &Options{ + Mount: mount, + User: testUser, + } + testAddAndRemoveKey(t, fakeV2Descriptor, options) +} + +func TestV2PolicyKeyCannotBeRemovedByAnotherUser(t *testing.T) { + rootOptions, userOptions := getOptionsForFsKeyringUsers(t, 2) + user1Options := userOptions[0] + user2Options := userOptions[1] + + // Add key as non-root user. + if err := AddEncryptionKey(fakeValidPolicyKey, fakeV2Descriptor, user1Options); err != nil { + t.Error(err) + } + assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyPresent) + assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyPresentButOnlyOtherUsers) + assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers) + + // Key shouldn't be removable by another user, even root. + err := RemoveEncryptionKey(fakeV2Descriptor, user2Options) + if err != ErrKeyAddedByOtherUsers { + t.Error(err) + } + assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyPresent) + assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyPresentButOnlyOtherUsers) + assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers) + err = RemoveEncryptionKey(fakeV2Descriptor, rootOptions) + if err != ErrKeyAddedByOtherUsers { + t.Error(err) + } + assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyPresent) + assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyPresentButOnlyOtherUsers) + assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers) + + if err := RemoveEncryptionKey(fakeV2Descriptor, user1Options); err != nil { + t.Error(err) + } + assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyAbsent) + assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyAbsent) + assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyAbsent) +} + +func TestV2PolicyKeyMultipleUsers(t *testing.T) { + rootOptions, userOptions := getOptionsForFsKeyringUsers(t, 2) + user1Options := userOptions[0] + user2Options := userOptions[1] + + // Add key as two non-root users. + if err := AddEncryptionKey(fakeValidPolicyKey, fakeV2Descriptor, user1Options); err != nil { + t.Error(err) + } + if err := AddEncryptionKey(fakeValidPolicyKey, fakeV2Descriptor, user2Options); err != nil { + t.Error(err) + } + assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyPresent) + assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyPresent) + assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers) + + // Remove key as one user. + err := RemoveEncryptionKey(fakeV2Descriptor, user1Options) + if err != ErrKeyAddedByOtherUsers { + t.Error(err) + } + assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyPresentButOnlyOtherUsers) + assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyPresent) + assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers) + + // Remove key as the other user. + err = RemoveEncryptionKey(fakeV2Descriptor, user2Options) + if err != nil { + t.Error(err) + } + assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyAbsent) + assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyAbsent) + assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyAbsent) +} + +func TestV2PolicyKeyWrongDescriptor(t *testing.T) { + mount := getTestMountV2(t) + options := &Options{ + Mount: mount, + User: testUser, + } + // one wrong but valid hex, and one not valid hex + wrongV2Descriptors := []string{"abcdabcdabcdabcdabcdabcdabcdabcd", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"} + + for _, desc := range wrongV2Descriptors { + if err := AddEncryptionKey(fakeValidPolicyKey, desc, options); err == nil { + RemoveEncryptionKey(desc, options) + t.Error("For v2 policy keys, AddEncryptionKey should fail if the descriptor is wrong") + } + } +} + +func TestV2PolicyKeyBadMount(t *testing.T) { + options := &Options{ + Mount: &filesystem.Mount{Path: "/NONEXISTENT_MOUNT"}, + User: testUser, + } + if err := AddEncryptionKey(fakeValidPolicyKey, fakeV2Descriptor, options); err == nil { + RemoveEncryptionKey(fakeV2Descriptor, options) + t.Error("AddEncryptionKey should have failed with bad mount!") + } + if err := RemoveEncryptionKey(fakeV2Descriptor, options); err == nil { + t.Error("RemoveEncryptionKey should have failed with bad mount!") + } + status, err := GetEncryptionKeyStatus(fakeV2Descriptor, options) + if err == nil { + t.Error("GetEncryptionKeyStatus should have failed with bad mount!") + } + if status != KeyStatusUnknown { + t.Error("GetEncryptionKeyStatus should have returned unknown status!") + } +} diff --git a/pam_fscrypt/pam_fscrypt.go b/pam_fscrypt/pam_fscrypt.go index b3c7a0e..5f573e3 100644 --- a/pam_fscrypt/pam_fscrypt.go +++ b/pam_fscrypt/pam_fscrypt.go @@ -176,8 +176,9 @@ func OpenSession(handle *pam.Handle, _ map[string]bool) error { // We don't stop provisioning polices on error, we try all of them. for _, policy := range policies { - if policy.IsProvisioned() { - log.Printf("policy %s already provisioned", policy.Descriptor()) + if policy.IsProvisionedByTargetUser() { + log.Printf("policy %s already provisioned by %v", + policy.Descriptor(), handle.PamUser.Username) continue } if err := policy.UnlockWithProtector(protector); err != nil { @@ -197,7 +198,8 @@ func OpenSession(handle *pam.Handle, _ map[string]bool) error { log.Printf("provisioning policy %s: %s", policy.Descriptor(), provisionErr) continue } - log.Printf("policy %s provisioned", policy.Descriptor()) + log.Printf("policy %s provisioned by %v", policy.Descriptor(), + handle.PamUser.Username) } return nil } @@ -256,8 +258,9 @@ func lockLoginPolicies(handle *pam.Handle) error { // We will try to deprovision all of the policies. for _, policy := range policies { - if !policy.IsProvisioned() { - log.Printf("policy %s not provisioned", policy.Descriptor()) + if !policy.IsProvisionedByTargetUser() { + log.Printf("policy %s not provisioned by %v", + policy.Descriptor(), handle.PamUser.Username) continue } if err := beginProvisioningOp(handle, policy); err != nil { @@ -271,7 +274,7 @@ func lockLoginPolicies(handle *pam.Handle) error { log.Printf("deprovisioning policy %s: %s", policy.Descriptor(), deprovisionErr) continue } - log.Printf("policy %s deprovisioned", policy.Descriptor()) + log.Printf("policy %s deprovisioned by %v", policy.Descriptor(), handle.PamUser.Username) } return nil } |