From 462d166d5355d33a05271d24de4d52f30dd62f67 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 15 Dec 2019 19:31:39 -0800 Subject: Add keyring package In preparation for introducing support for the new filesystem-level keyrings, move the existing user keyring management code from security/keyring.go and crypto/crypto.go into a new package, 'keyring'. This package provides functions AddEncryptionKey, RemoveEncryptionKey, and GetEncryptionKeyStatus which delegate to either the filesystem keyring (added by a later patch) or to the user keyring. This provides a common interface to both types of keyrings, to the extent possible. --- keyring/keyring_test.go | 95 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 keyring/keyring_test.go (limited to 'keyring/keyring_test.go') diff --git a/keyring/keyring_test.go b/keyring/keyring_test.go new file mode 100644 index 0000000..10ff874 --- /dev/null +++ b/keyring/keyring_test.go @@ -0,0 +1,95 @@ +/* + * keyring_test.go - tests for the keyring package + * + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package keyring + +import ( + "testing" + + "golang.org/x/sys/unix" + + "github.com/google/fscrypt/crypto" + "github.com/google/fscrypt/metadata" + "github.com/google/fscrypt/util" +) + +// Reader that always returns the same byte +type ConstReader byte + +func (r ConstReader) Read(b []byte) (n int, err error) { + for i := range b { + b[i] = byte(r) + } + return len(b), nil +} + +// Makes a key of the same repeating byte +func makeKey(b byte, n int) (*crypto.Key, error) { + return crypto.NewFixedLengthKeyFromReader(ConstReader(b), n) +} + +var ( + fakeValidDescriptor = "0123456789abcdef" + defaultService = unix.FSCRYPT_KEY_DESC_PREFIX + testUser, _ = util.EffectiveUser() + fakeValidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen) + fakeInvalidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen-1) +) + +// Adds and removes a key with various services. +func TestAddRemoveKeys(t *testing.T) { + for _, service := range []string{defaultService, "ext4:", "f2fs:"} { + options := &Options{ + User: testUser, + Service: service, + } + if err := AddEncryptionKey(fakeValidPolicyKey, fakeValidDescriptor, options); err != nil { + t.Error(err) + } + if err := RemoveEncryptionKey(fakeValidDescriptor, options); err != nil { + t.Error(err) + } + } +} + +// Adds a key twice (both should succeed) +func TestAddTwice(t *testing.T) { + options := &Options{ + User: testUser, + Service: defaultService, + } + if err := AddEncryptionKey(fakeValidPolicyKey, fakeValidDescriptor, options); err != nil { + t.Error(err) + } + if err := AddEncryptionKey(fakeValidPolicyKey, fakeValidDescriptor, options); err != nil { + t.Error("AddEncryptionKey should not fail if key already exists") + } + RemoveEncryptionKey(fakeValidDescriptor, options) +} + +// Makes sure trying to add a key of the wrong length fails +func TestAddWrongLengthKey(t *testing.T) { + options := &Options{ + User: testUser, + Service: defaultService, + } + if err := AddEncryptionKey(fakeInvalidPolicyKey, fakeValidDescriptor, options); err == nil { + RemoveEncryptionKey(fakeValidDescriptor, options) + t.Error("AddEncryptionKey should fail with wrong-length key") + } +} -- cgit v1.3 From 6ffc9457945a9484d2757cc4b01de35426502d0a Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 15 Dec 2019 19:31:39 -0800 Subject: keyring: support filesystem keyring with v1 encryption policies Linux v5.4 and later allows fscrypt keys to be added/removed directly to/from the filesystem via the new ioctls FS_IOC_ADD_ENCRYPTION_KEY and FS_IOC_REMOVE_ENCRYPTION_KEY. Among other benefits, these fix the key visibility problems that many users have been running into, where system services and containers can't access encrypted files. Allow the user to opt-in to using these new ioctls for their existing encrypted directories by setting in their /etc/fscrypt.conf: "use_fs_keyring_for_v1_policies": true Note that it can't really be on by default, since for v1 policies the ioctls require root, whereas user keyrings don't. I.e., setting this to true means that users will need to use 'sudo fscrypt unlock', not 'fscrypt unlock'. v2 policies won't have this restriction. --- actions/context.go | 13 +-- actions/policy.go | 16 +++- cmd/fscrypt/status.go | 18 ++++- keyring/fs_keyring.go | 211 ++++++++++++++++++++++++++++++++++++++++++++++++ keyring/keyring.go | 46 ++++++++++- keyring/keyring_test.go | 132 +++++++++++++++++++++++------- keyring/user_keyring.go | 3 +- metadata/config_test.go | 3 +- metadata/metadata.pb.go | 133 ++++++++++++++++-------------- metadata/metadata.proto | 1 + 10 files changed, 470 insertions(+), 106 deletions(-) create mode 100644 keyring/fs_keyring.go (limited to 'keyring/keyring_test.go') diff --git a/actions/context.go b/actions/context.go index 7703db5..f7e98cf 100644 --- a/actions/context.go +++ b/actions/context.go @@ -58,10 +58,11 @@ type Context struct { // modified after being loaded to customise parameters. Config *metadata.Config // Mount is the filesystem relative to which all Protectors and Policies - // are added, edited, removed, and applied. + // are added, edited, removed, and applied, and to which policies using + // the filesystem keyring are provisioned. Mount *filesystem.Mount - // TargetUser is the user for which protectors are created and to whose - // keyring policies are provisioned. + // TargetUser is the user for whom protectors are created, and to whose + // keyring policies using the user keyring are provisioned. TargetUser *user.User } @@ -148,8 +149,10 @@ func (ctx *Context) getService() string { func (ctx *Context) getKeyringOptions() *keyring.Options { return &keyring.Options{ - User: ctx.TargetUser, - Service: ctx.getService(), + Mount: ctx.Mount, + User: ctx.TargetUser, + Service: ctx.getService(), + UseFsKeyringForV1Policies: ctx.Config.GetUseFsKeyringForV1Policies(), } } diff --git a/actions/policy.go b/actions/policy.go index 5bc2c5c..6ef83ce 100644 --- a/actions/policy.go +++ b/actions/policy.go @@ -44,8 +44,8 @@ var ( ) // PurgeAllPolicies removes all policy keys on the filesystem from the kernel -// keyring. In order for this removal to have an effect, the filesystem should -// also be unmounted. +// keyring. In order for this to fully take effect, the filesystem may also need +// to be unmounted or caches dropped. func PurgeAllPolicies(ctx *Context) error { if err := ctx.checkContext(); err != nil { return err @@ -60,6 +60,9 @@ func PurgeAllPolicies(ctx *Context) error { switch errors.Cause(err) { case nil, keyring.ErrKeyNotPresent: // We don't care if the key has already been removed + case keyring.ErrKeyFilesOpen: + log.Printf("Key for policy %s couldn't be fully removed because some files are still in-use", + policyDescriptor) default: return err } @@ -379,6 +382,12 @@ func (policy *Policy) IsProvisioned() 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. +func (policy *Policy) IsFullyDeprovisioned() bool { + return policy.GetProvisioningStatus() == keyring.KeyAbsent +} + // Provision inserts the Policy key into the kernel keyring. This allows reading // and writing of files encrypted with this directory. Requires unlocked Policy. func (policy *Policy) Provision() error { @@ -390,7 +399,8 @@ func (policy *Policy) Provision() error { } // Deprovision removes the Policy key from the kernel keyring. This prevents -// reading and writing to the directory once the caches are cleared. +// reading and writing to the directory --- unless the target keyring is a user +// keyring, in which case caches must be dropped too. func (policy *Policy) Deprovision() error { return keyring.RemoveEncryptionKey(policy.Descriptor(), policy.Context.getKeyringOptions()) diff --git a/cmd/fscrypt/status.go b/cmd/fscrypt/status.go index 375899b..3f7f577 100644 --- a/cmd/fscrypt/status.go +++ b/cmd/fscrypt/status.go @@ -31,6 +31,7 @@ import ( "github.com/google/fscrypt/actions" "github.com/google/fscrypt/filesystem" + "github.com/google/fscrypt/keyring" "github.com/google/fscrypt/metadata" ) @@ -65,6 +66,19 @@ func yesNoString(b bool) string { return "No" } +func policyUnlockedStatus(policy *actions.Policy) string { + switch policy.GetProvisioningStatus() { + case keyring.KeyPresent: + return "Yes" + case keyring.KeyAbsent: + return "No" + case keyring.KeyAbsentButFilesBusy: + return "Partially (incompletely locked)" + default: + return "Unknown" + } +} + // writeGlobalStatus prints all the filesystems that use (or could use) fscrypt. func writeGlobalStatus(w io.Writer) error { mounts, err := filesystem.AllFilesystems() @@ -160,7 +174,7 @@ func writeFilesystemStatus(w io.Writer, ctx *actions.Context) error { continue } - fmt.Fprintf(t, "%s\t%s\t%s\n", descriptor, yesNoString(policy.IsProvisioned()), + fmt.Fprintf(t, "%s\t%s\t%s\n", descriptor, policyUnlockedStatus(policy), strings.Join(policy.ProtectorDescriptors(), ", ")) } return t.Flush() @@ -180,7 +194,7 @@ func writePathStatus(w io.Writer, path string) error { fmt.Fprintln(w) fmt.Fprintf(w, "Policy: %s\n", policy.Descriptor()) fmt.Fprintf(w, "Options: %s\n", policy.Options()) - fmt.Fprintf(w, "Unlocked: %s\n", yesNoString(policy.IsProvisioned())) + fmt.Fprintf(w, "Unlocked: %s\n", policyUnlockedStatus(policy)) fmt.Fprintln(w) options := policy.ProtectorOptions() diff --git a/keyring/fs_keyring.go b/keyring/fs_keyring.go new file mode 100644 index 0000000..bed23af --- /dev/null +++ b/keyring/fs_keyring.go @@ -0,0 +1,211 @@ +/* + * fs_keyring.go - Add/remove encryption policy keys to/from filesystem + * + * Copyright 2019 Google LLC + * Author: Eric Biggers (ebiggers@google.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package keyring + +/* +#include +*/ +import "C" + +import ( + "encoding/hex" + "log" + "os" + "os/user" + "sync" + "unsafe" + + "github.com/pkg/errors" + "golang.org/x/sys/unix" + + "github.com/google/fscrypt/crypto" + "github.com/google/fscrypt/filesystem" +) + +var ( + fsKeyringSupported bool + fsKeyringSupportedKnown bool + fsKeyringSupportedLock sync.Mutex +) + +func checkForFsKeyringSupport(mount *filesystem.Mount) bool { + dir, err := os.Open(mount.Path) + if err != nil { + log.Printf("Unexpected error opening %q. Assuming filesystem keyring is unsupported.", + mount.Path) + return false + } + defer dir.Close() + + // FS_IOC_ADD_ENCRYPTION_KEY with a NULL argument will fail with ENOTTY + // if the ioctl isn't supported. Otherwise it should fail with EFAULT. + // + // Note that there's no need to check for FS_IOC_REMOVE_ENCRYPTION_KEY + // support separately, since it's guaranteed to be available if + // FS_IOC_ADD_ENCRYPTION_KEY is. There's also no need to check for + // support on every filesystem separately, since either the kernel + // supports the ioctls on all fscrypt-capable filesystems or it doesn't. + _, _, errno := unix.Syscall(unix.SYS_IOCTL, dir.Fd(), unix.FS_IOC_ADD_ENCRYPTION_KEY, 0) + if errno == unix.ENOTTY { + log.Printf("Kernel doesn't support filesystem keyring. Falling back to user keyring.") + return false + } + if errno == unix.EFAULT { + log.Printf("Detected support for filesystem keyring") + } else { + // EFAULT is expected, but as long as we didn't get ENOTTY the + // ioctl should be available. + log.Printf("Unexpected error from FS_IOC_ADD_ENCRYPTION_KEY(%q, NULL): %v", mount.Path, errno) + } + return true +} + +// isFsKeyringSupported returns true if the kernel supports the ioctls to +// add/remove fscrypt keys directly to/from the filesystem. For support to be +// detected, the given Mount must be for a filesystem that supports fscrypt. +func isFsKeyringSupported(mount *filesystem.Mount) bool { + fsKeyringSupportedLock.Lock() + defer fsKeyringSupportedLock.Unlock() + if !fsKeyringSupportedKnown { + fsKeyringSupported = checkForFsKeyringSupport(mount) + fsKeyringSupportedKnown = true + } + return fsKeyringSupported +} + +// buildKeySpecifier converts the key descriptor string to an FscryptKeySpecifier. +func buildKeySpecifier(spec *unix.FscryptKeySpecifier, descriptor string) error { + descriptorBytes, err := hex.DecodeString(descriptor) + if err != nil { + return errors.Errorf("key descriptor %q is invalid", descriptor) + } + switch len(descriptorBytes) { + case unix.FSCRYPT_KEY_DESCRIPTOR_SIZE: + spec.Type = unix.FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR + default: + return errors.Errorf("key descriptor %q has unknown length", descriptor) + } + copy(spec.U[:], descriptorBytes) + return nil +} + +// fsAddEncryptionKey adds the specified encryption key to the specified filesystem. +func fsAddEncryptionKey(key *crypto.Key, descriptor string, + mount *filesystem.Mount, user *user.User) error { + + dir, err := os.Open(mount.Path) + if err != nil { + return err + } + defer dir.Close() + + argKey, err := crypto.NewBlankKey(int(unsafe.Sizeof(unix.FscryptAddKeyArg{})) + key.Len()) + if err != nil { + return err + } + defer argKey.Wipe() + arg := (*unix.FscryptAddKeyArg)(argKey.UnsafePtr()) + + if err = buildKeySpecifier(&arg.Key_spec, descriptor); err != nil { + return err + } + + raw := unsafe.Pointer(uintptr(argKey.UnsafePtr()) + unsafe.Sizeof(*arg)) + arg.Raw_size = uint32(key.Len()) + C.memcpy(raw, key.UnsafePtr(), C.size_t(key.Len())) + + _, _, errno := unix.Syscall(unix.SYS_IOCTL, dir.Fd(), + unix.FS_IOC_ADD_ENCRYPTION_KEY, uintptr(argKey.UnsafePtr())) + log.Printf("FS_IOC_ADD_ENCRYPTION_KEY(%q, %s, ) = %v", mount.Path, descriptor, errno) + if errno != 0 { + return errors.Wrap(ErrKeyAdd, errno.Error()) + } + return nil +} + +// fsRemoveEncryptionKey removes the specified encryption key from the specified +// filesystem. +func fsRemoveEncryptionKey(descriptor string, mount *filesystem.Mount, + user *user.User) error { + + dir, err := os.Open(mount.Path) + if err != nil { + return err + } + defer dir.Close() + + var arg unix.FscryptRemoveKeyArg + if err = buildKeySpecifier(&arg.Key_spec, descriptor); err != nil { + return err + } + + _, _, errno := unix.Syscall(unix.SYS_IOCTL, dir.Fd(), + unix.FS_IOC_REMOVE_ENCRYPTION_KEY, uintptr(unsafe.Pointer(&arg))) + 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 { + return ErrKeyFilesOpen + } + return nil + case unix.ENOKEY: + return ErrKeyNotPresent + default: + return errors.Wrap(ErrKeyRemove, errno.Error()) + } +} + +// fsGetEncryptionKeyStatus gets the status of the specified encryption key on +// the specified filesystem. +func fsGetEncryptionKeyStatus(descriptor string, mount *filesystem.Mount, + user *user.User) (KeyStatus, error) { + + dir, err := os.Open(mount.Path) + if err != nil { + return KeyStatusUnknown, err + } + defer dir.Close() + + var arg unix.FscryptGetKeyStatusArg + err = buildKeySpecifier(&arg.Key_spec, descriptor) + 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))) + 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 { + return KeyStatusUnknown, errors.Wrap(ErrKeySearch, errno.Error()) + } + switch arg.Status { + case unix.FSCRYPT_KEY_STATUS_ABSENT: + return KeyAbsent, nil + case unix.FSCRYPT_KEY_STATUS_PRESENT: + return KeyPresent, nil + case unix.FSCRYPT_KEY_STATUS_INCOMPLETELY_REMOVED: + return KeyAbsentButFilesBusy, nil + default: + return KeyStatusUnknown, + errors.Wrapf(ErrKeySearch, "unknown key status (%d)", arg.Status) + } +} diff --git a/keyring/keyring.go b/keyring/keyring.go index 7a5fd64..f0bd1b7 100644 --- a/keyring/keyring.go +++ b/keyring/keyring.go @@ -19,7 +19,11 @@ // Package keyring manages adding, removing, and getting the status of // encryption policy keys to/from the kernel. Most public functions are in -// keyring.go, and they delegate to user_keyring.go. +// 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. +// +// 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 ( @@ -29,6 +33,7 @@ import ( "github.com/pkg/errors" "github.com/google/fscrypt/crypto" + "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/util" ) @@ -38,6 +43,7 @@ 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") @@ -47,22 +53,47 @@ var ( // Options are the options which specify *which* keyring the key should be // added/removed/gotten to, and how. type Options struct { + // Mount is the filesystem to which the key should be + // added/removed/gotten. + Mount *filesystem.Mount // User is the user for whom the key should be added/removed/gotten. User *user.User - // Service is the prefix to prepend to the description of the keys. + // Service is the prefix to prepend to the description of the keys in + // user keyrings. Not relevant for filesystem keyrings. Service string + // UseFsKeyringForV1Policies is true if keys for v1 encryption policies + // should be put in the filesystem's keyring (if supported) rather than + // in the user's keyring. Note that this makes AddEncryptionKey and + // RemoveEncryptionKey require root privileges. + UseFsKeyringForV1Policies bool } -// AddEncryptionKey adds an encryption policy key to a kernel keyring. +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) +} + +// AddEncryptionKey adds an encryption policy key to a kernel keyring. It uses +// either the filesystem keyring for the target Mount or the user keyring for +// the target User. func AddEncryptionKey(key *crypto.Key, descriptor string, options *Options) error { if err := util.CheckValidLength(metadata.PolicyKeyLen, key.Len()); err != nil { return errors.Wrap(err, "policy key") } + if shouldUseFsKeyring(descriptor, options) { + return fsAddEncryptionKey(key, descriptor, options.Mount, options.User) + } return userAddKey(key, options.Service+descriptor, options.User) } // RemoveEncryptionKey removes an encryption policy key from a kernel keyring. +// It uses either the filesystem keyring for the target Mount or the user +// keyring for the target User. func RemoveEncryptionKey(descriptor string, options *Options) error { + if shouldUseFsKeyring(descriptor, options) { + return fsRemoveEncryptionKey(descriptor, options.Mount, options.User) + } return userRemoveKey(options.Service+descriptor, options.User) } @@ -73,6 +104,7 @@ type KeyStatus int const ( KeyStatusUnknown = 0 + iota KeyAbsent + KeyAbsentButFilesBusy KeyPresent ) @@ -82,6 +114,8 @@ func (status KeyStatus) String() string { return "Unknown" case KeyAbsent: return "Absent" + case KeyAbsentButFilesBusy: + return "AbsentButFilesBusy" case KeyPresent: return "Present" default: @@ -90,8 +124,12 @@ func (status KeyStatus) String() string { } // GetEncryptionKeyStatus gets the status of an encryption policy key in a -// kernel keyring. +// kernel keyring. It uses either the filesystem keyring for the target Mount +// or the user keyring for the target User. func GetEncryptionKeyStatus(descriptor string, options *Options) (KeyStatus, error) { + if shouldUseFsKeyring(descriptor, options) { + return fsGetEncryptionKeyStatus(descriptor, options.Mount, options.User) + } _, err := userFindKey(options.Service+descriptor, options.User) if err != nil { return KeyAbsent, nil diff --git a/keyring/keyring_test.go b/keyring/keyring_test.go index 10ff874..9a4570b 100644 --- a/keyring/keyring_test.go +++ b/keyring/keyring_test.go @@ -24,6 +24,7 @@ import ( "golang.org/x/sys/unix" "github.com/google/fscrypt/crypto" + "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/util" ) @@ -44,52 +45,125 @@ func makeKey(b byte, n int) (*crypto.Key, error) { } var ( - fakeValidDescriptor = "0123456789abcdef" defaultService = unix.FSCRYPT_KEY_DESC_PREFIX testUser, _ = util.EffectiveUser() fakeValidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen) fakeInvalidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen-1) + fakeV1Descriptor = "0123456789abcdef" ) -// Adds and removes a key with various services. -func TestAddRemoveKeys(t *testing.T) { - for _, service := range []string{defaultService, "ext4:", "f2fs:"} { - options := &Options{ - User: testUser, - Service: service, - } - if err := AddEncryptionKey(fakeValidPolicyKey, fakeValidDescriptor, options); err != nil { - t.Error(err) - } - if err := RemoveEncryptionKey(fakeValidDescriptor, options); err != nil { - t.Error(err) - } +func assertKeyStatus(t *testing.T, descriptor string, options *Options, + expectedStatus KeyStatus) { + status, err := GetEncryptionKeyStatus(descriptor, options) + if err != nil { + t.Error(err) + } + if status != expectedStatus { + t.Errorf("Expected key status %v but got key status %v", expectedStatus, status) } } -// Adds a key twice (both should succeed) -func TestAddTwice(t *testing.T) { - options := &Options{ - User: testUser, - Service: defaultService, +// getTestMount retrieves the Mount for a test filesystem, or skips the test if +// no test filesystem is available. +func getTestMount(t *testing.T) *filesystem.Mount { + root, err := util.TestRoot() + if err != nil { + t.Skip(err) + } + mount, err := filesystem.GetMount(root) + if err != nil { + t.Skip(err) + } + return mount +} + +// getTestMountV2 is like getTestMount, but it also checks that the filesystem +// keyring is supported. +func getTestMountV2(t *testing.T) *filesystem.Mount { + mount := getTestMount(t) + if !isFsKeyringSupported(mount) { + t.Skip("No support for fs keyring, skipping test.") + } + return mount +} + +func requireRoot(t *testing.T) { + if !util.IsUserRoot() { + t.Skip("Not root, skipping test.") } - if err := AddEncryptionKey(fakeValidPolicyKey, fakeValidDescriptor, options); err != nil { +} + +// 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). +func testAddAndRemoveKey(t *testing.T, descriptor string, options *Options) { + + // Basic add, get status, and remove + if err := AddEncryptionKey(fakeValidPolicyKey, descriptor, options); err != nil { t.Error(err) } - if err := AddEncryptionKey(fakeValidPolicyKey, fakeValidDescriptor, options); err != nil { + assertKeyStatus(t, descriptor, options, KeyPresent) + if err := RemoveEncryptionKey(descriptor, options); err != nil { + t.Error(err) + } + assertKeyStatus(t, descriptor, options, KeyAbsent) + err := RemoveEncryptionKey(descriptor, options) + if err != ErrKeyNotPresent { + t.Error(err) + } + + // Adding a key twice should succeed + if err := AddEncryptionKey(fakeValidPolicyKey, descriptor, options); err != nil { + t.Error(err) + } + if err := AddEncryptionKey(fakeValidPolicyKey, descriptor, options); err != nil { t.Error("AddEncryptionKey should not fail if key already exists") } - RemoveEncryptionKey(fakeValidDescriptor, options) + RemoveEncryptionKey(descriptor, options) + assertKeyStatus(t, descriptor, options, KeyAbsent) + + // Adding a key with wrong length should fail + if err := AddEncryptionKey(fakeInvalidPolicyKey, descriptor, options); err == nil { + RemoveEncryptionKey(descriptor, options) + t.Error("AddEncryptionKey should fail with wrong-length key") + } + assertKeyStatus(t, descriptor, options, KeyAbsent) } -// Makes sure trying to add a key of the wrong length fails -func TestAddWrongLengthKey(t *testing.T) { +func TestUserKeyringDefaultService(t *testing.T) { options := &Options{ - User: testUser, - Service: defaultService, + User: testUser, + Service: defaultService, + UseFsKeyringForV1Policies: false, } - if err := AddEncryptionKey(fakeInvalidPolicyKey, fakeValidDescriptor, options); err == nil { - RemoveEncryptionKey(fakeValidDescriptor, options) - t.Error("AddEncryptionKey should fail with wrong-length key") + testAddAndRemoveKey(t, fakeV1Descriptor, options) +} + +func TestUserKeyringExt4Service(t *testing.T) { + options := &Options{ + User: testUser, + Service: "ext4:", + UseFsKeyringForV1Policies: false, + } + testAddAndRemoveKey(t, fakeV1Descriptor, options) +} + +func TestUserKeyringF2fsService(t *testing.T) { + options := &Options{ + User: testUser, + Service: "f2fs:", + UseFsKeyringForV1Policies: false, + } + testAddAndRemoveKey(t, fakeV1Descriptor, options) +} + +func TestFsKeyringV1PolicyKey(t *testing.T) { + requireRoot(t) + mount := getTestMountV2(t) + options := &Options{ + Mount: mount, + User: testUser, + UseFsKeyringForV1Policies: true, } + testAddAndRemoveKey(t, fakeV1Descriptor, options) } diff --git a/keyring/user_keyring.go b/keyring/user_keyring.go index adac71a..71f519d 100644 --- a/keyring/user_keyring.go +++ b/keyring/user_keyring.go @@ -1,5 +1,6 @@ /* - * user_keyring.go - Add/remove encryption policy keys to/from user keyrings + * user_keyring.go - Add/remove encryption policy keys to/from user keyrings. + * This is the deprecated mechanism; see fs_keyring.go for the new mechanism. * * Copyright 2017 Google Inc. * Author: Joe Richey (joerichey@google.com) diff --git a/metadata/config_test.go b/metadata/config_test.go index d184a87..3c20c51 100644 --- a/metadata/config_test.go +++ b/metadata/config_test.go @@ -49,7 +49,8 @@ var testConfigString = `{ "padding": "32", "contents": "AES_256_XTS", "filenames": "AES_256_CTS" - } + }, + "use_fs_keyring_for_v1_policies": false } ` diff --git a/metadata/metadata.pb.go b/metadata/metadata.pb.go index bd3ee2a..aa4eeff 100644 --- a/metadata/metadata.pb.go +++ b/metadata/metadata.pb.go @@ -45,7 +45,7 @@ func (x SourceType) String() string { return proto.EnumName(SourceType_name, int32(x)) } func (SourceType) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_metadata_fa046c95c3cd6aa1, []int{0} + return fileDescriptor_metadata_ff272c42a9f0f3d3, []int{0} } // Type of encryption; should match declarations of unix.FSCRYPT_MODE @@ -87,7 +87,7 @@ func (x EncryptionOptions_Mode) String() string { return proto.EnumName(EncryptionOptions_Mode_name, int32(x)) } func (EncryptionOptions_Mode) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_metadata_fa046c95c3cd6aa1, []int{3, 0} + return fileDescriptor_metadata_ff272c42a9f0f3d3, []int{3, 0} } // Cost parameters to be used in our hashing functions. @@ -104,7 +104,7 @@ func (m *HashingCosts) Reset() { *m = HashingCosts{} } func (m *HashingCosts) String() string { return proto.CompactTextString(m) } func (*HashingCosts) ProtoMessage() {} func (*HashingCosts) Descriptor() ([]byte, []int) { - return fileDescriptor_metadata_fa046c95c3cd6aa1, []int{0} + return fileDescriptor_metadata_ff272c42a9f0f3d3, []int{0} } func (m *HashingCosts) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_HashingCosts.Unmarshal(m, b) @@ -159,7 +159,7 @@ func (m *WrappedKeyData) Reset() { *m = WrappedKeyData{} } func (m *WrappedKeyData) String() string { return proto.CompactTextString(m) } func (*WrappedKeyData) ProtoMessage() {} func (*WrappedKeyData) Descriptor() ([]byte, []int) { - return fileDescriptor_metadata_fa046c95c3cd6aa1, []int{1} + return fileDescriptor_metadata_ff272c42a9f0f3d3, []int{1} } func (m *WrappedKeyData) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_WrappedKeyData.Unmarshal(m, b) @@ -219,7 +219,7 @@ func (m *ProtectorData) Reset() { *m = ProtectorData{} } func (m *ProtectorData) String() string { return proto.CompactTextString(m) } func (*ProtectorData) ProtoMessage() {} func (*ProtectorData) Descriptor() ([]byte, []int) { - return fileDescriptor_metadata_fa046c95c3cd6aa1, []int{2} + return fileDescriptor_metadata_ff272c42a9f0f3d3, []int{2} } func (m *ProtectorData) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ProtectorData.Unmarshal(m, b) @@ -302,7 +302,7 @@ func (m *EncryptionOptions) Reset() { *m = EncryptionOptions{} } func (m *EncryptionOptions) String() string { return proto.CompactTextString(m) } func (*EncryptionOptions) ProtoMessage() {} func (*EncryptionOptions) Descriptor() ([]byte, []int) { - return fileDescriptor_metadata_fa046c95c3cd6aa1, []int{3} + return fileDescriptor_metadata_ff272c42a9f0f3d3, []int{3} } func (m *EncryptionOptions) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_EncryptionOptions.Unmarshal(m, b) @@ -355,7 +355,7 @@ func (m *WrappedPolicyKey) Reset() { *m = WrappedPolicyKey{} } func (m *WrappedPolicyKey) String() string { return proto.CompactTextString(m) } func (*WrappedPolicyKey) ProtoMessage() {} func (*WrappedPolicyKey) Descriptor() ([]byte, []int) { - return fileDescriptor_metadata_fa046c95c3cd6aa1, []int{4} + return fileDescriptor_metadata_ff272c42a9f0f3d3, []int{4} } func (m *WrappedPolicyKey) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_WrappedPolicyKey.Unmarshal(m, b) @@ -403,7 +403,7 @@ func (m *PolicyData) Reset() { *m = PolicyData{} } func (m *PolicyData) String() string { return proto.CompactTextString(m) } func (*PolicyData) ProtoMessage() {} func (*PolicyData) Descriptor() ([]byte, []int) { - return fileDescriptor_metadata_fa046c95c3cd6aa1, []int{5} + return fileDescriptor_metadata_ff272c42a9f0f3d3, []int{5} } func (m *PolicyData) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PolicyData.Unmarshal(m, b) @@ -446,20 +446,21 @@ func (m *PolicyData) GetWrappedPolicyKeys() []*WrappedPolicyKey { // Data stored in the config file type Config struct { - Source SourceType `protobuf:"varint,1,opt,name=source,proto3,enum=metadata.SourceType" json:"source,omitempty"` - HashCosts *HashingCosts `protobuf:"bytes,2,opt,name=hash_costs,json=hashCosts,proto3" json:"hash_costs,omitempty"` - Compatibility string `protobuf:"bytes,3,opt,name=compatibility,proto3" json:"compatibility,omitempty"` - Options *EncryptionOptions `protobuf:"bytes,4,opt,name=options,proto3" json:"options,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Source SourceType `protobuf:"varint,1,opt,name=source,proto3,enum=metadata.SourceType" json:"source,omitempty"` + HashCosts *HashingCosts `protobuf:"bytes,2,opt,name=hash_costs,json=hashCosts,proto3" json:"hash_costs,omitempty"` + Compatibility string `protobuf:"bytes,3,opt,name=compatibility,proto3" json:"compatibility,omitempty"` + Options *EncryptionOptions `protobuf:"bytes,4,opt,name=options,proto3" json:"options,omitempty"` + UseFsKeyringForV1Policies bool `protobuf:"varint,5,opt,name=use_fs_keyring_for_v1_policies,json=useFsKeyringForV1Policies,proto3" json:"use_fs_keyring_for_v1_policies,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *Config) Reset() { *m = Config{} } func (m *Config) String() string { return proto.CompactTextString(m) } func (*Config) ProtoMessage() {} func (*Config) Descriptor() ([]byte, []int) { - return fileDescriptor_metadata_fa046c95c3cd6aa1, []int{6} + return fileDescriptor_metadata_ff272c42a9f0f3d3, []int{6} } func (m *Config) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Config.Unmarshal(m, b) @@ -507,6 +508,13 @@ func (m *Config) GetOptions() *EncryptionOptions { return nil } +func (m *Config) GetUseFsKeyringForV1Policies() bool { + if m != nil { + return m.UseFsKeyringForV1Policies + } + return false +} + func init() { proto.RegisterType((*HashingCosts)(nil), "metadata.HashingCosts") proto.RegisterType((*WrappedKeyData)(nil), "metadata.WrappedKeyData") @@ -519,49 +527,52 @@ func init() { proto.RegisterEnum("metadata.EncryptionOptions_Mode", EncryptionOptions_Mode_name, EncryptionOptions_Mode_value) } -func init() { proto.RegisterFile("metadata/metadata.proto", fileDescriptor_metadata_fa046c95c3cd6aa1) } - -var fileDescriptor_metadata_fa046c95c3cd6aa1 = []byte{ - // 656 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0xef, 0x6a, 0xdb, 0x3c, - 0x14, 0xc6, 0x5f, 0xdb, 0x69, 0xd2, 0x9c, 0xfc, 0x79, 0x5d, 0xb5, 0x6f, 0x5f, 0xb3, 0x7d, 0x09, - 0xde, 0x06, 0x65, 0x94, 0x8e, 0x66, 0x74, 0x6c, 0x30, 0x06, 0x5d, 0x5a, 0xb6, 0xae, 0x94, 0x75, - 0x4a, 0xe8, 0x36, 0x18, 0x04, 0xd5, 0x56, 0x1b, 0x51, 0xdb, 0x12, 0x96, 0x42, 0xf0, 0xb7, 0x7d, - 0xdb, 0x05, 0xec, 0x5a, 0xb6, 0x8b, 0xd8, 0x55, 0x0d, 0xc9, 0xb1, 0xe3, 0xb4, 0x50, 0xba, 0x7d, - 0x31, 0x47, 0x8f, 0xa4, 0xf3, 0x1c, 0xfd, 0xa4, 0x63, 0xf8, 0x3f, 0xa6, 0x8a, 0x84, 0x44, 0x91, - 0x27, 0x45, 0xb0, 0x23, 0x52, 0xae, 0x38, 0x5a, 0x2d, 0xc6, 0xfe, 0x17, 0x68, 0xbf, 0x25, 0x72, - 0xc2, 0x92, 0xcb, 0x01, 0x97, 0x4a, 0x22, 0x04, 0x35, 0xc5, 0x62, 0xea, 0xd9, 0x3d, 0x6b, 0xcb, - 0xc1, 0x26, 0x46, 0x9b, 0x50, 0x8f, 0x69, 0xcc, 0xd3, 0xcc, 0x73, 0x8c, 0x3a, 0x1f, 0xa1, 0x1e, - 0xb4, 0x04, 0x49, 0x49, 0x14, 0xd1, 0x88, 0xc9, 0xd8, 0xab, 0x99, 0xc9, 0xaa, 0xe4, 0x7f, 0x86, - 0xee, 0xc7, 0x94, 0x08, 0x41, 0xc3, 0x63, 0x9a, 0x1d, 0x10, 0x45, 0x50, 0x17, 0xec, 0xa3, 0x33, - 0xcf, 0xea, 0x59, 0x5b, 0x6d, 0x6c, 0x1f, 0x9d, 0xa1, 0x07, 0xd0, 0xa1, 0x49, 0x90, 0x66, 0x42, - 0xd1, 0x70, 0x7c, 0x45, 0x33, 0x63, 0xdc, 0xc6, 0xed, 0x52, 0x3c, 0xa6, 0x99, 0x2e, 0x6a, 0x12, - 0x93, 0xc0, 0xd8, 0xb7, 0xb1, 0x89, 0xfd, 0xef, 0x36, 0x74, 0x4e, 0x53, 0xae, 0x68, 0xa0, 0x78, - 0x6a, 0x52, 0xef, 0xc2, 0x86, 0x28, 0x84, 0x71, 0x48, 0x65, 0x90, 0x32, 0xa1, 0x78, 0x6a, 0xcc, - 0x9a, 0x78, 0xbd, 0x9c, 0x3b, 0x28, 0xa7, 0xd0, 0x36, 0xd4, 0x25, 0x9f, 0xa6, 0x41, 0x7e, 0xde, - 0x6e, 0x7f, 0x63, 0xa7, 0x04, 0x35, 0x34, 0xfa, 0x28, 0x13, 0x14, 0xcf, 0xd7, 0xe8, 0x32, 0x12, - 0x12, 0x53, 0x53, 0x46, 0x13, 0x9b, 0x18, 0x6d, 0xc3, 0x4a, 0xa0, 0xc1, 0x99, 0xd3, 0xb7, 0xfa, - 0x9b, 0x8b, 0x04, 0x55, 0xac, 0x38, 0x5f, 0xa4, 0x33, 0x48, 0x12, 0x29, 0x6f, 0x25, 0x3f, 0x88, - 0x8e, 0x91, 0x0b, 0xce, 0x94, 0x85, 0x5e, 0xdd, 0xd0, 0xd3, 0x21, 0x7a, 0x01, 0xad, 0x59, 0x4e, - 0xcd, 0x10, 0x69, 0x98, 0xcc, 0xde, 0x22, 0xf3, 0x32, 0x52, 0x0c, 0xb3, 0x72, 0xec, 0xff, 0xb0, - 0x61, 0xed, 0x30, 0x47, 0xc7, 0x78, 0xf2, 0xde, 0x7c, 0x25, 0xf2, 0xa0, 0x21, 0x48, 0x18, 0xb2, - 0xe4, 0xd2, 0xc0, 0x70, 0x70, 0x31, 0x44, 0x2f, 0x61, 0x35, 0xe0, 0x89, 0xa2, 0x89, 0x92, 0x73, - 0x04, 0xbd, 0x85, 0xcf, 0x8d, 0x44, 0x3b, 0x27, 0x3c, 0xa4, 0xb8, 0xdc, 0x81, 0x5e, 0x41, 0xf3, - 0x82, 0x45, 0x54, 0x83, 0x90, 0x86, 0xca, 0x5d, 0xb6, 0x2f, 0xb6, 0xf8, 0xdf, 0x2c, 0xa8, 0x69, - 0x0d, 0xb5, 0xa0, 0x11, 0xd2, 0x0b, 0x32, 0x8d, 0x94, 0xfb, 0x0f, 0xfa, 0x17, 0x5a, 0xfb, 0x87, - 0xc3, 0x71, 0x7f, 0xef, 0xd9, 0xf8, 0xd3, 0x68, 0xe8, 0x5a, 0x55, 0xe1, 0xcd, 0xe0, 0xc4, 0xb5, - 0xab, 0xc2, 0xe0, 0xf5, 0xc0, 0x75, 0x96, 0x84, 0xd1, 0xd0, 0xad, 0x15, 0xc2, 0x6e, 0xff, 0xb9, - 0x59, 0xb1, 0xb2, 0x24, 0x8c, 0x86, 0x6e, 0x1d, 0xb5, 0x61, 0x75, 0x3f, 0x64, 0x24, 0x51, 0xd3, - 0xd8, 0x6d, 0xfa, 0x5f, 0x2d, 0x70, 0xe7, 0x58, 0x4f, 0x79, 0xc4, 0x82, 0x4c, 0x3f, 0xbb, 0xbf, - 0x78, 0x50, 0xd7, 0xae, 0xce, 0xfe, 0x83, 0xab, 0xfb, 0x69, 0x01, 0xe4, 0xde, 0xe6, 0x35, 0x3f, - 0x82, 0xee, 0x15, 0xcd, 0x6e, 0xda, 0x76, 0xae, 0x68, 0x56, 0x31, 0xdc, 0x83, 0x06, 0xcf, 0xe9, - 0xce, 0xcd, 0xee, 0xdf, 0x72, 0x01, 0xb8, 0x58, 0x8b, 0xde, 0xc1, 0x7a, 0x51, 0xa7, 0x30, 0x9e, - 0xba, 0x5c, 0x7d, 0x87, 0xce, 0x56, 0xab, 0x7f, 0xef, 0x46, 0xbd, 0x25, 0x13, 0xbc, 0x36, 0xbb, - 0xa6, 0x48, 0xff, 0x97, 0x05, 0xf5, 0x01, 0x4f, 0x2e, 0xd8, 0x65, 0xa5, 0x9f, 0xac, 0x3b, 0xf4, - 0xd3, 0x1e, 0xc0, 0x84, 0xc8, 0xc9, 0x38, 0x6f, 0x20, 0xfb, 0xd6, 0x06, 0x6a, 0xea, 0x95, 0xf9, - 0x2f, 0xea, 0x21, 0x74, 0x02, 0x1e, 0x0b, 0xa2, 0xd8, 0x39, 0x8b, 0x98, 0xca, 0xe6, 0xfd, 0xb8, - 0x2c, 0x56, 0xc1, 0xd4, 0xee, 0x0e, 0xe6, 0xf1, 0x07, 0x80, 0x45, 0xa5, 0xcb, 0xef, 0x12, 0x41, - 0x57, 0x90, 0x78, 0x2c, 0x88, 0x94, 0x62, 0x92, 0x12, 0x49, 0x5d, 0x0b, 0xfd, 0x07, 0x6b, 0xc1, - 0x54, 0x2a, 0xbe, 0x24, 0xdb, 0x7a, 0x5f, 0x4a, 0x66, 0x9a, 0xa9, 0xeb, 0x9c, 0xd7, 0xcd, 0x3f, - 0xf7, 0xe9, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4b, 0xbe, 0x84, 0xbc, 0x8e, 0x05, 0x00, 0x00, +func init() { proto.RegisterFile("metadata/metadata.proto", fileDescriptor_metadata_ff272c42a9f0f3d3) } + +var fileDescriptor_metadata_ff272c42a9f0f3d3 = []byte{ + // 693 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0xdf, 0x6b, 0x13, 0x41, + 0x10, 0xc7, 0xbd, 0x4b, 0x9a, 0x1f, 0x93, 0x1f, 0x5e, 0xb7, 0xb5, 0x9e, 0x0a, 0x12, 0xa2, 0x42, + 0x91, 0x52, 0x49, 0xa4, 0xa2, 0x20, 0x42, 0x4d, 0x5b, 0xad, 0xa5, 0x58, 0x2f, 0xa1, 0x2a, 0x08, + 0xc7, 0xf6, 0x6e, 0x93, 0x2c, 0xbd, 0xbb, 0x5d, 0x76, 0x37, 0x86, 0x7b, 0xf3, 0xcd, 0x27, 0x9f, + 0xfc, 0x5b, 0xf4, 0xef, 0x93, 0xdd, 0xcb, 0xcf, 0x16, 0x4a, 0xeb, 0xcb, 0x31, 0xfb, 0xdd, 0xd9, + 0x99, 0xd9, 0xcf, 0xce, 0x1c, 0xdc, 0x8d, 0x89, 0xc2, 0x21, 0x56, 0xf8, 0xd9, 0xd4, 0xd8, 0xe6, + 0x82, 0x29, 0x86, 0x4a, 0xd3, 0x75, 0xf3, 0x1b, 0x54, 0xdf, 0x63, 0x39, 0xa4, 0xc9, 0xa0, 0xc3, + 0xa4, 0x92, 0x08, 0x41, 0x5e, 0xd1, 0x98, 0xb8, 0x76, 0xc3, 0xda, 0xcc, 0x79, 0xc6, 0x46, 0x1b, + 0x50, 0x88, 0x49, 0xcc, 0x44, 0xea, 0xe6, 0x8c, 0x3a, 0x59, 0xa1, 0x06, 0x54, 0x38, 0x16, 0x38, + 0x8a, 0x48, 0x44, 0x65, 0xec, 0xe6, 0xcd, 0xe6, 0xa2, 0xd4, 0xfc, 0x0a, 0xf5, 0xcf, 0x02, 0x73, + 0x4e, 0xc2, 0x23, 0x92, 0xee, 0x61, 0x85, 0x51, 0x1d, 0xec, 0xc3, 0x53, 0xd7, 0x6a, 0x58, 0x9b, + 0x55, 0xcf, 0x3e, 0x3c, 0x45, 0x8f, 0xa0, 0x46, 0x92, 0x40, 0xa4, 0x5c, 0x91, 0xd0, 0x3f, 0x27, + 0xa9, 0x49, 0x5c, 0xf5, 0xaa, 0x33, 0xf1, 0x88, 0xa4, 0xba, 0xa8, 0x61, 0x8c, 0x03, 0x93, 0xbe, + 0xea, 0x19, 0xbb, 0xf9, 0xdb, 0x86, 0xda, 0x89, 0x60, 0x8a, 0x04, 0x8a, 0x09, 0x13, 0xba, 0x05, + 0xeb, 0x7c, 0x2a, 0xf8, 0x21, 0x91, 0x81, 0xa0, 0x5c, 0x31, 0x61, 0x92, 0x95, 0xbd, 0xb5, 0xd9, + 0xde, 0xde, 0x6c, 0x0b, 0x6d, 0x41, 0x41, 0xb2, 0x91, 0x08, 0xb2, 0xfb, 0xd6, 0xdb, 0xeb, 0xdb, + 0x33, 0x50, 0x5d, 0xa3, 0xf7, 0x52, 0x4e, 0xbc, 0x89, 0x8f, 0x2e, 0x23, 0xc1, 0x31, 0x31, 0x65, + 0x94, 0x3d, 0x63, 0xa3, 0x2d, 0x58, 0x09, 0x34, 0x38, 0x73, 0xfb, 0x4a, 0x7b, 0x63, 0x1e, 0x60, + 0x11, 0xab, 0x97, 0x39, 0xe9, 0x08, 0x12, 0x47, 0xca, 0x5d, 0xc9, 0x2e, 0xa2, 0x6d, 0xe4, 0x40, + 0x6e, 0x44, 0x43, 0xb7, 0x60, 0xe8, 0x69, 0x13, 0xbd, 0x82, 0xca, 0x38, 0xa3, 0x66, 0x88, 0x14, + 0x4d, 0x64, 0x77, 0x1e, 0x79, 0x19, 0xa9, 0x07, 0xe3, 0xd9, 0xba, 0xf9, 0xc7, 0x86, 0xd5, 0xfd, + 0x0c, 0x1d, 0x65, 0xc9, 0x47, 0xf3, 0x95, 0xc8, 0x85, 0x22, 0xc7, 0x61, 0x48, 0x93, 0x81, 0x81, + 0x91, 0xf3, 0xa6, 0x4b, 0xf4, 0x1a, 0x4a, 0x01, 0x4b, 0x14, 0x49, 0x94, 0x9c, 0x20, 0x68, 0xcc, + 0xf3, 0x5c, 0x0a, 0xb4, 0x7d, 0xcc, 0x42, 0xe2, 0xcd, 0x4e, 0xa0, 0x37, 0x50, 0xee, 0xd3, 0x88, + 0x68, 0x10, 0xd2, 0x50, 0xb9, 0xce, 0xf1, 0xf9, 0x91, 0xe6, 0x4f, 0x0b, 0xf2, 0x5a, 0x43, 0x15, + 0x28, 0x86, 0xa4, 0x8f, 0x47, 0x91, 0x72, 0x6e, 0xa1, 0xdb, 0x50, 0xd9, 0xdd, 0xef, 0xfa, 0xed, + 0x9d, 0x17, 0xfe, 0x97, 0x5e, 0xd7, 0xb1, 0x16, 0x85, 0x77, 0x9d, 0x63, 0xc7, 0x5e, 0x14, 0x3a, + 0x6f, 0x3b, 0x4e, 0x6e, 0x49, 0xe8, 0x75, 0x9d, 0xfc, 0x54, 0x68, 0xb5, 0x5f, 0x1a, 0x8f, 0x95, + 0x25, 0xa1, 0xd7, 0x75, 0x0a, 0xa8, 0x0a, 0xa5, 0xdd, 0x90, 0xe2, 0x44, 0x8d, 0x62, 0xa7, 0xdc, + 0xfc, 0x61, 0x81, 0x33, 0xc1, 0x7a, 0xc2, 0x22, 0x1a, 0xa4, 0xba, 0xed, 0xfe, 0xa3, 0xa1, 0x2e, + 0x3c, 0x9d, 0x7d, 0x83, 0xa7, 0xfb, 0x6b, 0x01, 0x64, 0xb9, 0x4d, 0x37, 0x3f, 0x81, 0xfa, 0x39, + 0x49, 0x2f, 0xa7, 0xad, 0x9d, 0x93, 0x74, 0x21, 0xe1, 0x0e, 0x14, 0x59, 0x46, 0x77, 0x92, 0xec, + 0xc1, 0x15, 0x0f, 0xe0, 0x4d, 0x7d, 0xd1, 0x07, 0x58, 0x9b, 0xd6, 0xc9, 0x4d, 0x4e, 0x5d, 0xae, + 0x7e, 0xc3, 0xdc, 0x66, 0xa5, 0x7d, 0xff, 0x52, 0xbd, 0x33, 0x26, 0xde, 0xea, 0xf8, 0x82, 0x22, + 0x9b, 0xbf, 0x6c, 0x28, 0x74, 0x58, 0xd2, 0xa7, 0x83, 0x85, 0x79, 0xb2, 0xae, 0x31, 0x4f, 0x3b, + 0x00, 0x43, 0x2c, 0x87, 0x7e, 0x36, 0x40, 0xf6, 0x95, 0x03, 0x54, 0xd6, 0x9e, 0xd9, 0x2f, 0xea, + 0x31, 0xd4, 0x02, 0x16, 0x73, 0xac, 0xe8, 0x19, 0x8d, 0xa8, 0x4a, 0x27, 0xf3, 0xb8, 0x2c, 0x2e, + 0x82, 0xc9, 0xdf, 0x00, 0xcc, 0x2e, 0x3c, 0x1c, 0x49, 0xe2, 0xf7, 0xa5, 0x06, 0x22, 0x68, 0x32, + 0xf0, 0xfb, 0x4c, 0xf8, 0xdf, 0x5b, 0x19, 0x26, 0x4a, 0xa4, 0x99, 0xdd, 0x92, 0x77, 0x6f, 0x24, + 0xc9, 0x81, 0x3c, 0xca, 0x7c, 0x0e, 0x98, 0x38, 0x6d, 0x9d, 0x4c, 0x1c, 0x9e, 0x7e, 0x02, 0x98, + 0x5f, 0x76, 0xb9, 0xb5, 0x11, 0xd4, 0x39, 0x8e, 0x7d, 0x8e, 0xa5, 0xe4, 0x43, 0x81, 0x25, 0x71, + 0x2c, 0x74, 0x07, 0x56, 0x83, 0x91, 0x54, 0x6c, 0x49, 0xb6, 0xf5, 0x39, 0x81, 0xc7, 0xba, 0x0a, + 0x27, 0x77, 0x56, 0x30, 0xbf, 0xed, 0xe7, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0xd3, 0x31, 0x82, + 0x34, 0xd1, 0x05, 0x00, 0x00, } diff --git a/metadata/metadata.proto b/metadata/metadata.proto index 735181e..e682212 100644 --- a/metadata/metadata.proto +++ b/metadata/metadata.proto @@ -97,4 +97,5 @@ message Config { HashingCosts hash_costs = 2; string compatibility = 3; EncryptionOptions options = 4; + bool use_fs_keyring_for_v1_policies = 5; } -- cgit v1.3 From 42e0dfe85ec7a75a2fa30c417d57eae60b5a881d Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 15 Dec 2019 19:31:39 -0800 Subject: Keyring support for v2 encryption policies Implement adding/removing v2 encryption policy keys to/from the kernel. The kernel requires that the new ioctls FS_IOC_ADD_ENCRYPTION_KEY and FS_IOC_REMOVE_ENCRYPTION_KEY be used for this. Root is not required. However, non-root support brings an extra complication: the kernel keeps track of which users have called FS_IOC_ADD_ENCRYPTION_KEY for the same key. FS_IOC_REMOVE_ENCRYPTION_KEY only works as one of these users, and it only removes the calling user's claim to the key; the key is only truly removed when the last claim is removed. Implement the following behavior: - 'fscrypt unlock' and pam_fscrypt add the key for the user, even if other user(s) have it added already. This behavior is needed so that another user can't remove the key out from under the user. - 'fscrypt lock' and pam_fscrypt remove the key for the user. However, if the key wasn't truly removed because other users still have it added, 'fscrypt lock' prints a warning. - 'fscrypt status' shows whether the directory is unlocked for anyone. --- README.md | 8 ++- actions/context.go | 4 +- actions/policy.go | 27 ++++++-- cmd/fscrypt/commands.go | 35 ++++++++-- cmd/fscrypt/errors.go | 8 ++- cmd/fscrypt/flags.go | 10 +-- cmd/fscrypt/status.go | 2 +- keyring/fs_keyring.go | 100 ++++++++++++++++++++++++++- keyring/keyring.go | 35 +++++++--- keyring/keyring_test.go | 163 +++++++++++++++++++++++++++++++++++++++++++-- pam_fscrypt/pam_fscrypt.go | 15 +++-- 11 files changed, 363 insertions(+), 44 deletions(-) (limited to 'keyring/keyring_test.go') 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, ) = %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 } -- cgit v1.3 From 068879664efd8a0f983cbc3e8115571047fe9edd Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 15 Dec 2019 19:31:39 -0800 Subject: cmd/fscrypt, keyring: add --all-users option to 'fscrypt lock' Allow root to provide the --all-users option to 'fscrypt lock' to force an encryption key to be removed from the filesystem (i.e., force an encrypted directory to be locked), even if other users have added it. To implement this option, we just need to use the FS_IOC_REMOVE_ENCRYPTION_KEY_ALL_USERS ioctl rather than FS_IOC_REMOVE_ENCRYPTION_KEY. In theory this option could be implemented for the user keyrings case too, but it would be difficult and the user keyrings are being deprecated for fscrypt, so don't bother. --- actions/policy.go | 6 +++--- cmd/fscrypt/commands.go | 8 ++++---- cmd/fscrypt/errors.go | 3 ++- cmd/fscrypt/flags.go | 10 +++++++++- keyring/fs_keyring.go | 29 +++++++++++++++++---------- keyring/keyring.go | 8 ++++++-- keyring/keyring_test.go | 50 +++++++++++++++++++++++++++++++++++----------- pam_fscrypt/pam_fscrypt.go | 2 +- 8 files changed, 82 insertions(+), 34 deletions(-) (limited to 'keyring/keyring_test.go') diff --git a/actions/policy.go b/actions/policy.go index f448620..41e108e 100644 --- a/actions/policy.go +++ b/actions/policy.go @@ -56,7 +56,7 @@ func PurgeAllPolicies(ctx *Context) error { } for _, policyDescriptor := range policies { - err = keyring.RemoveEncryptionKey(policyDescriptor, ctx.getKeyringOptions()) + err = keyring.RemoveEncryptionKey(policyDescriptor, ctx.getKeyringOptions(), false) switch errors.Cause(err) { case nil, keyring.ErrKeyNotPresent: // We don't care if the key has already been removed @@ -416,9 +416,9 @@ func (policy *Policy) Provision() error { // Deprovision removes the Policy key from the kernel keyring. This prevents // reading and writing to the directory --- unless the target keyring is a user // keyring, in which case caches must be dropped too. -func (policy *Policy) Deprovision() error { +func (policy *Policy) Deprovision(allUsers bool) error { return keyring.RemoveEncryptionKey(policy.Descriptor(), - policy.Context.getKeyringOptions()) + policy.Context.getKeyringOptions(), allUsers) } // NeedsUserKeyring returns true if Provision and Deprovision for this policy diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go index 0bf0a4c..41009b0 100644 --- a/cmd/fscrypt/commands.go +++ b/cmd/fscrypt/commands.go @@ -232,7 +232,7 @@ func encryptPath(path string) (err error) { defer func() { policy.Lock() if err != nil { - policy.Deprovision() + policy.Deprovision(false) policy.Revert() } }() @@ -248,7 +248,7 @@ func encryptPath(path string) (err error) { return } if skipUnlockFlag.Value { - defer policy.Deprovision() + defer policy.Deprovision(false) } } if err = policy.Apply(path); os.IsPermission(errors.Cause(err)) { @@ -426,7 +426,7 @@ var Lock = cli.Command{ recoverable by an attacker who compromises system memory. To be fully safe, you must reboot with a power cycle.`, directoryArg, shortDisplay(dropCachesFlag)), - Flags: []cli.Flag{dropCachesFlag, userFlag}, + Flags: []cli.Flag{dropCachesFlag, userFlag, allUsersFlag}, Action: lockAction, } @@ -465,7 +465,7 @@ func lockAction(c *cli.Context) error { return newExitError(c, ErrDropCachesPerm) } - if err = policy.Deprovision(); err != nil { + if err = policy.Deprovision(allUsersFlag.Value); err != nil { return newExitError(c, err) } diff --git a/cmd/fscrypt/errors.go b/cmd/fscrypt/errors.go index ba9ec7a..5239155 100644 --- a/cmd/fscrypt/errors.go +++ b/cmd/fscrypt/errors.go @@ -103,7 +103,8 @@ func getErrorSuggestions(err error) string { re-running 'fscrypt lock'.` case keyring.ErrKeyAddedByOtherUsers: return `Directory couldn't be fully locked because other user(s) - have unlocked it.` + have unlocked it. If you want to force the directory to + be locked, use 'sudo fscrypt lock --all-users DIR'.` case keyring.ErrSessionUserKeying: return `This is usually the result of a bad PAM configuration. Either correct the problem in your PAM stack, enable diff --git a/cmd/fscrypt/flags.go b/cmd/fscrypt/flags.go index a22ec05..b7933c9 100644 --- a/cmd/fscrypt/flags.go +++ b/cmd/fscrypt/flags.go @@ -116,7 +116,7 @@ var ( allFlags = []prettyFlag{helpFlag, versionFlag, verboseFlag, quietFlag, forceFlag, legacyFlag, skipUnlockFlag, timeTargetFlag, sourceFlag, nameFlag, keyFileFlag, protectorFlag, - unlockWithFlag, policyFlag} + unlockWithFlag, policyFlag, allUsersFlag} // universalFlags contains flags that should be on every command universalFlags = []cli.Flag{verboseFlag, quietFlag, helpFlag} ) @@ -170,6 +170,14 @@ var ( privileges.`, Default: true, } + allUsersFlag = &boolFlag{ + Name: "all-users", + Usage: `Lock the directory no matter which user(s) have unlocked + it. Requires root privileges. This flag is only + necessary if the directory was unlocked by a user + different from the one you're locking it as. This flag + is only implemented for v2 encryption policies.`, + } ) // Option flags: used to specify options instead of being prompted for them diff --git a/keyring/fs_keyring.go b/keyring/fs_keyring.go index 970105e..42c1648 100644 --- a/keyring/fs_keyring.go +++ b/keyring/fs_keyring.go @@ -228,16 +228,23 @@ func fsRemoveEncryptionKey(descriptor string, mount *filesystem.Mount, return err } - savedPrivs, err := dropPrivsIfNeeded(user, &arg.Key_spec) - if err != nil { - return err + ioc := unix.FS_IOC_REMOVE_ENCRYPTION_KEY + iocName := "FS_IOC_REMOVE_ENCRYPTION_KEY" + var savedPrivs *savedPrivs + if user == nil { + ioc = unix.FS_IOC_REMOVE_ENCRYPTION_KEY_ALL_USERS + iocName = "FS_IOC_REMOVE_ENCRYPTION_KEY_ALL_USERS" + } else { + 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))) + _, _, errno := unix.Syscall(unix.SYS_IOCTL, dir.Fd(), uintptr(ioc), 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) + log.Printf("%s(%q, %s) = %v, removal_status_flags=0x%x", + iocName, mount.Path, descriptor, errno, arg.Removal_status_flags) switch errno { case 0: switch { @@ -251,9 +258,11 @@ func fsRemoveEncryptionKey(descriptor string, mount *filesystem.Mount, // 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 + if user != nil { + status, _ := fsGetEncryptionKeyStatus(descriptor, mount, user) + if status == KeyPresentButOnlyOtherUsers { + return ErrKeyAddedByOtherUsers + } } return ErrKeyNotPresent default: diff --git a/keyring/keyring.go b/keyring/keyring.go index 925d059..5a75153 100644 --- a/keyring/keyring.go +++ b/keyring/keyring.go @@ -100,9 +100,13 @@ func AddEncryptionKey(key *crypto.Key, descriptor string, options *Options) erro // RemoveEncryptionKey removes an encryption policy key from a kernel keyring. // It uses either the filesystem keyring for the target Mount or the user // keyring for the target User. -func RemoveEncryptionKey(descriptor string, options *Options) error { +func RemoveEncryptionKey(descriptor string, options *Options, allUsers bool) error { if shouldUseFsKeyring(descriptor, options) { - return fsRemoveEncryptionKey(descriptor, options.Mount, options.User) + user := options.User + if allUsers { + user = nil + } + return fsRemoveEncryptionKey(descriptor, options.Mount, user) } return userRemoveKey(options.Service+descriptor, options.User) } diff --git a/keyring/keyring_test.go b/keyring/keyring_test.go index a675a70..8912556 100644 --- a/keyring/keyring_test.go +++ b/keyring/keyring_test.go @@ -139,11 +139,11 @@ func testAddAndRemoveKey(t *testing.T, descriptor string, options *Options) { t.Error(err) } assertKeyStatus(t, descriptor, options, KeyPresent) - if err := RemoveEncryptionKey(descriptor, options); err != nil { + if err := RemoveEncryptionKey(descriptor, options, false); err != nil { t.Error(err) } assertKeyStatus(t, descriptor, options, KeyAbsent) - err := RemoveEncryptionKey(descriptor, options) + err := RemoveEncryptionKey(descriptor, options, false) if err != ErrKeyNotPresent { t.Error(err) } @@ -155,12 +155,12 @@ func testAddAndRemoveKey(t *testing.T, descriptor string, options *Options) { if err := AddEncryptionKey(fakeValidPolicyKey, descriptor, options); err != nil { t.Error("AddEncryptionKey should not fail if key already exists") } - RemoveEncryptionKey(descriptor, options) + RemoveEncryptionKey(descriptor, options, false) assertKeyStatus(t, descriptor, options, KeyAbsent) // Adding a key with wrong length should fail if err := AddEncryptionKey(fakeInvalidPolicyKey, descriptor, options); err == nil { - RemoveEncryptionKey(descriptor, options) + RemoveEncryptionKey(descriptor, options, false) t.Error("AddEncryptionKey should fail with wrong-length key") } assertKeyStatus(t, descriptor, options, KeyAbsent) @@ -227,14 +227,14 @@ func TestV2PolicyKeyCannotBeRemovedByAnotherUser(t *testing.T) { assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers) // Key shouldn't be removable by another user, even root. - err := RemoveEncryptionKey(fakeV2Descriptor, user2Options) + err := RemoveEncryptionKey(fakeV2Descriptor, user2Options, false) 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) + err = RemoveEncryptionKey(fakeV2Descriptor, rootOptions, false) if err != ErrKeyAddedByOtherUsers { t.Error(err) } @@ -242,7 +242,7 @@ func TestV2PolicyKeyCannotBeRemovedByAnotherUser(t *testing.T) { assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyPresentButOnlyOtherUsers) assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers) - if err := RemoveEncryptionKey(fakeV2Descriptor, user1Options); err != nil { + if err := RemoveEncryptionKey(fakeV2Descriptor, user1Options, false); err != nil { t.Error(err) } assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyAbsent) @@ -267,7 +267,7 @@ func TestV2PolicyKeyMultipleUsers(t *testing.T) { assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers) // Remove key as one user. - err := RemoveEncryptionKey(fakeV2Descriptor, user1Options) + err := RemoveEncryptionKey(fakeV2Descriptor, user1Options, false) if err != ErrKeyAddedByOtherUsers { t.Error(err) } @@ -276,7 +276,7 @@ func TestV2PolicyKeyMultipleUsers(t *testing.T) { assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers) // Remove key as the other user. - err = RemoveEncryptionKey(fakeV2Descriptor, user2Options) + err = RemoveEncryptionKey(fakeV2Descriptor, user2Options, false) if err != nil { t.Error(err) } @@ -296,7 +296,7 @@ func TestV2PolicyKeyWrongDescriptor(t *testing.T) { for _, desc := range wrongV2Descriptors { if err := AddEncryptionKey(fakeValidPolicyKey, desc, options); err == nil { - RemoveEncryptionKey(desc, options) + RemoveEncryptionKey(desc, options, false) t.Error("For v2 policy keys, AddEncryptionKey should fail if the descriptor is wrong") } } @@ -308,10 +308,10 @@ func TestV2PolicyKeyBadMount(t *testing.T) { User: testUser, } if err := AddEncryptionKey(fakeValidPolicyKey, fakeV2Descriptor, options); err == nil { - RemoveEncryptionKey(fakeV2Descriptor, options) + RemoveEncryptionKey(fakeV2Descriptor, options, false) t.Error("AddEncryptionKey should have failed with bad mount!") } - if err := RemoveEncryptionKey(fakeV2Descriptor, options); err == nil { + if err := RemoveEncryptionKey(fakeV2Descriptor, options, false); err == nil { t.Error("RemoveEncryptionKey should have failed with bad mount!") } status, err := GetEncryptionKeyStatus(fakeV2Descriptor, options) @@ -322,3 +322,29 @@ func TestV2PolicyKeyBadMount(t *testing.T) { t.Error("GetEncryptionKeyStatus should have returned unknown status!") } } + +func TestV2PolicyKeyRemoveForAllUsers(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 for all users as root. + err := RemoveEncryptionKey(fakeV2Descriptor, rootOptions, true) + if err != nil { + t.Error(err) + } + assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyAbsent) + assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyAbsent) + assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyAbsent) +} diff --git a/pam_fscrypt/pam_fscrypt.go b/pam_fscrypt/pam_fscrypt.go index 5f573e3..a7582cc 100644 --- a/pam_fscrypt/pam_fscrypt.go +++ b/pam_fscrypt/pam_fscrypt.go @@ -266,7 +266,7 @@ func lockLoginPolicies(handle *pam.Handle) error { if err := beginProvisioningOp(handle, policy); err != nil { return err } - deprovisionErr := policy.Deprovision() + deprovisionErr := policy.Deprovision(false) if err := endProvisioningOp(handle, policy); err != nil { return err } -- cgit v1.3