aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md8
-rw-r--r--actions/context.go4
-rw-r--r--actions/policy.go27
-rw-r--r--cmd/fscrypt/commands.go35
-rw-r--r--cmd/fscrypt/errors.go8
-rw-r--r--cmd/fscrypt/flags.go10
-rw-r--r--cmd/fscrypt/status.go2
-rw-r--r--keyring/fs_keyring.go100
-rw-r--r--keyring/keyring.go35
-rw-r--r--keyring/keyring_test.go163
-rw-r--r--pam_fscrypt/pam_fscrypt.go15
11 files changed, 363 insertions, 44 deletions
diff --git a/README.md b/README.md
index ee8e389..4130962 100644
--- a/README.md
+++ b/README.md
@@ -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
}