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. --- actions/context.go | 8 ++ actions/policy.go | 30 +++---- cmd/fscrypt/errors.go | 6 +- cmd/fscrypt/flags.go | 4 +- crypto/crypto.go | 4 +- crypto/crypto_test.go | 54 +----------- crypto/key.go | 56 +++++------- keyring/keyring.go | 100 ++++++++++++++++++++++ keyring/keyring_test.go | 95 +++++++++++++++++++++ keyring/user_keyring.go | 221 ++++++++++++++++++++++++++++++++++++++++++++++++ pam/pam.go | 3 +- security/keyring.go | 214 ---------------------------------------------- security/privileges.go | 8 +- 13 files changed, 477 insertions(+), 326 deletions(-) create mode 100644 keyring/keyring.go create mode 100644 keyring/keyring_test.go create mode 100644 keyring/user_keyring.go delete mode 100644 security/keyring.go diff --git a/actions/context.go b/actions/context.go index 5a56789..7703db5 100644 --- a/actions/context.go +++ b/actions/context.go @@ -37,6 +37,7 @@ import ( "github.com/pkg/errors" "github.com/google/fscrypt/filesystem" + "github.com/google/fscrypt/keyring" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/util" ) @@ -145,6 +146,13 @@ func (ctx *Context) getService() string { return unix.FSCRYPT_KEY_DESC_PREFIX } +func (ctx *Context) getKeyringOptions() *keyring.Options { + return &keyring.Options{ + User: ctx.TargetUser, + Service: ctx.getService(), + } +} + // getProtectorOption returns the ProtectorOption for the protector on the // context's mountpoint with the specified descriptor. func (ctx *Context) getProtectorOption(protectorDescriptor string) *ProtectorOption { diff --git a/actions/policy.go b/actions/policy.go index 875a01f..5bc2c5c 100644 --- a/actions/policy.go +++ b/actions/policy.go @@ -28,8 +28,8 @@ import ( "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/filesystem" + "github.com/google/fscrypt/keyring" "github.com/google/fscrypt/metadata" - "github.com/google/fscrypt/security" "github.com/google/fscrypt/util" ) @@ -56,11 +56,9 @@ func PurgeAllPolicies(ctx *Context) error { } for _, policyDescriptor := range policies { - service := ctx.getService() - err = security.RemoveKey(service+policyDescriptor, ctx.TargetUser) - + err = keyring.RemoveEncryptionKey(policyDescriptor, ctx.getKeyringOptions()) switch errors.Cause(err) { - case nil, security.ErrKeySearch: + case nil, keyring.ErrKeyNotPresent: // We don't care if the key has already been removed default: return err @@ -188,12 +186,6 @@ func (policy *Policy) Descriptor() string { return policy.data.KeyDescriptor } -// Description returns the description that will be used when the key for this -// Policy is inserted into the keyring -func (policy *Policy) Description() string { - return policy.Context.getService() + policy.Descriptor() -} - // Options returns the encryption options of this policy. func (policy *Policy) Options() *metadata.EncryptionOptions { return policy.data.Options @@ -374,11 +366,17 @@ func (policy *Policy) Apply(path string) error { return metadata.SetPolicy(path, policy.data) } +// GetProvisioningStatus returns the status of this policy's key in the keyring. +func (policy *Policy) GetProvisioningStatus() keyring.KeyStatus { + status, _ := keyring.GetEncryptionKeyStatus(policy.Descriptor(), + policy.Context.getKeyringOptions()) + 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 { - _, err := security.FindKey(policy.Description(), policy.Context.TargetUser) - return err == nil + return policy.GetProvisioningStatus() == keyring.KeyPresent } // Provision inserts the Policy key into the kernel keyring. This allows reading @@ -387,13 +385,15 @@ func (policy *Policy) Provision() error { if policy.key == nil { return ErrLocked } - return crypto.InsertPolicyKey(policy.key, policy.Description(), policy.Context.TargetUser) + return keyring.AddEncryptionKey(policy.key, policy.Descriptor(), + policy.Context.getKeyringOptions()) } // Deprovision removes the Policy key from the kernel keyring. This prevents // reading and writing to the directory once the caches are cleared. func (policy *Policy) Deprovision() error { - return security.RemoveKey(policy.Description(), policy.Context.TargetUser) + return keyring.RemoveEncryptionKey(policy.Descriptor(), + policy.Context.getKeyringOptions()) } // commitData writes the Policy's current data to the filesystem. diff --git a/cmd/fscrypt/errors.go b/cmd/fscrypt/errors.go index 288e697..ed57dbe 100644 --- a/cmd/fscrypt/errors.go +++ b/cmd/fscrypt/errors.go @@ -34,8 +34,8 @@ import ( "github.com/google/fscrypt/actions" "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/filesystem" + "github.com/google/fscrypt/keyring" "github.com/google/fscrypt/metadata" - "github.com/google/fscrypt/security" "github.com/google/fscrypt/util" ) @@ -94,11 +94,11 @@ func getErrorSuggestions(err error) string { needs to be enabled for this filesystem. See the documentation on how to enable encryption on ext4 systems (and the risks of doing so).` - case security.ErrSessionUserKeying: + case keyring.ErrSessionUserKeying: return `This is usually the result of a bad PAM configuration. Either correct the problem in your PAM stack, enable pam_keyinit.so, or run "keyctl link @u @s".` - case security.ErrAccessUserKeyring: + case keyring.ErrAccessUserKeyring: return fmt.Sprintf(`You can only use %s to access the user keyring of another user if you are running as root.`, shortDisplay(userFlag)) diff --git a/cmd/fscrypt/flags.go b/cmd/fscrypt/flags.go index 16a75dc..2eea8de 100644 --- a/cmd/fscrypt/flags.go +++ b/cmd/fscrypt/flags.go @@ -33,7 +33,7 @@ import ( "github.com/urfave/cli" "github.com/google/fscrypt/actions" - "github.com/google/fscrypt/security" + "github.com/google/fscrypt/keyring" "github.com/google/fscrypt/util" ) @@ -300,7 +300,7 @@ func parseUserFlag(checkKeyring bool) (targetUser *user.User, err error) { } if checkKeyring { - _, err = security.UserKeyringID(targetUser, true) + _, err = keyring.UserKeyringID(targetUser, true) } return targetUser, err } diff --git a/crypto/crypto.go b/crypto/crypto.go index 8de8134..ec961b6 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -167,7 +167,7 @@ func Unwrap(wrappingKey *Key, data *metadata.WrappedKeyData) (*Key, error) { return nil, ErrBadAuth } - secretKey, err := newBlankKey(len(data.EncryptedKey)) + secretKey, err := NewBlankKey(len(data.EncryptedKey)) if err != nil { return nil, err } @@ -196,7 +196,7 @@ func PassphraseHash(passphrase *Key, salt []byte, costs *metadata.HashingCosts) p := uint8(costs.Parallelism) key := argon2.IDKey(passphrase.data, salt, t, m, p, metadata.InternalKeyLen) - hash, err := newBlankKey(metadata.InternalKeyLen) + hash, err := NewBlankKey(metadata.InternalKeyLen) if err != nil { return nil, err } diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 6f973ef..d0cef82 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -30,11 +30,7 @@ import ( "os" "testing" - "golang.org/x/sys/unix" - "github.com/google/fscrypt/metadata" - "github.com/google/fscrypt/security" - "github.com/google/fscrypt/util" ) // Reader that always returns the same byte @@ -53,16 +49,11 @@ func makeKey(b byte, n int) (*Key, error) { } var ( - fakeValidDescriptor = "0123456789abcdef" - fakeSalt = bytes.Repeat([]byte{'a'}, metadata.SaltLen) - fakePassword = []byte("password") - defaultService = unix.FSCRYPT_KEY_DESC_PREFIX - - fakeValidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen) - fakeInvalidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen-1) - fakeWrappingKey, _ = makeKey(17, metadata.InternalKeyLen) + fakeSalt = bytes.Repeat([]byte{'a'}, metadata.SaltLen) + fakePassword = []byte("password") - testUser, _ = util.EffectiveUser() + fakeValidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen) + fakeWrappingKey, _ = makeKey(17, metadata.InternalKeyLen) ) // As the passphrase hashing function clears the passphrase, we need to make @@ -242,43 +233,6 @@ func TestKeyLargeResize(t *testing.T) { } } -// Adds and removes a key with various services. -func TestAddRemoveKeys(t *testing.T) { - for _, service := range []string{defaultService, "ext4:", "f2fs:"} { - validDescription := service + fakeValidDescriptor - if err := InsertPolicyKey(fakeValidPolicyKey, validDescription, testUser); err != nil { - t.Error(err) - } - if err := security.RemoveKey(validDescription, testUser); err != nil { - t.Error(err) - } - } -} - -// Adds a key twice (both should succeed) -func TestAddTwice(t *testing.T) { - validDescription := defaultService + fakeValidDescriptor - InsertPolicyKey(fakeValidPolicyKey, validDescription, testUser) - if InsertPolicyKey(fakeValidPolicyKey, validDescription, testUser) != nil { - t.Error("InsertPolicyKey should not fail if key already exists") - } - security.RemoveKey(validDescription, testUser) -} - -// Makes sure a key fails with bad policy or service -func TestBadAddKeys(t *testing.T) { - validDescription := defaultService + fakeValidDescriptor - if InsertPolicyKey(fakeInvalidPolicyKey, validDescription, testUser) == nil { - security.RemoveKey(validDescription, testUser) - t.Error("InsertPolicyKey should fail with bad policy key") - } - invalidDescription := "ext4" + fakeValidDescriptor - if InsertPolicyKey(fakeValidPolicyKey, invalidDescription, testUser) == nil { - security.RemoveKey(invalidDescription, testUser) - t.Error("InsertPolicyKey should fail with bad service") - } -} - // Check that we can create random keys. All this test does to test the // "randomness" is generate a page of random bytes and attempts compression. // If the data can be compressed it is probably not very random. This isn't diff --git a/crypto/key.go b/crypto/key.go index 52efb54..2220652 100644 --- a/crypto/key.go +++ b/crypto/key.go @@ -33,7 +33,6 @@ import ( "io" "log" "os" - "os/user" "runtime" "unsafe" @@ -41,7 +40,6 @@ import ( "golang.org/x/sys/unix" "github.com/google/fscrypt/metadata" - "github.com/google/fscrypt/security" "github.com/google/fscrypt/util" ) @@ -94,9 +92,9 @@ type Key struct { data []byte } -// newBlankKey constructs a blank key of a specified length and returns an error +// NewBlankKey constructs a blank key of a specified length and returns an error // if we are unable to allocate or lock the necessary memory. -func newBlankKey(length int) (*Key, error) { +func NewBlankKey(length int) (*Key, error) { if length == 0 { return &Key{data: nil}, nil } else if length < 0 { @@ -167,7 +165,7 @@ func (key *Key) resize(requestedSize int) (*Key, error) { } defer key.Wipe() - resizedKey, err := newBlankKey(requestedSize) + resizedKey, err := NewBlankKey(requestedSize) if err != nil { return nil, err } @@ -175,6 +173,18 @@ func (key *Key) resize(requestedSize int) (*Key, error) { return resizedKey, nil } +// Data returns a slice of the key's underlying data. Note that this may become +// outdated if the key is resized. +func (key *Key) Data() []byte { + return key.data +} + +// UnsafePtr returns an unsafe pointer to the key's underlying data. Note that +// this will only be valid as long as the key is not resized. +func (key *Key) UnsafePtr() unsafe.Pointer { + return util.Ptr(key.data) +} + // UnsafeToCString makes a copy of the string's data into a null-terminated C // string allocated by C. Note that this method is unsafe as this C copy has no // locking or wiping functionality. The key shouldn't contain any `\0` bytes. @@ -190,7 +200,7 @@ func (key *Key) UnsafeToCString() unsafe.Pointer { // ensure that this original copy is secured. func NewKeyFromCString(str unsafe.Pointer) (*Key, error) { size := C.strlen((*C.char)(str)) - key, err := newBlankKey(int(size)) + key, err := NewBlankKey(int(size)) if err != nil { return nil, err } @@ -203,7 +213,7 @@ func NewKeyFromCString(str unsafe.Pointer) (*Key, error) { func NewKeyFromReader(reader io.Reader) (*Key, error) { // Use an initial key size of a page. As Mmap allocates a page anyway, // there isn't much additional overhead from starting with a whole page. - key, err := newBlankKey(os.Getpagesize()) + key, err := NewBlankKey(os.Getpagesize()) if err != nil { return nil, err } @@ -235,7 +245,7 @@ func NewKeyFromReader(reader io.Reader) (*Key, error) { // NewFixedLengthKeyFromReader constructs a key with a specified length by // reading exactly length bytes from reader. func NewFixedLengthKeyFromReader(reader io.Reader, length int) (*Key, error) { - key, err := newBlankKey(length) + key, err := NewBlankKey(length) if err != nil { return nil, err } @@ -246,30 +256,6 @@ func NewFixedLengthKeyFromReader(reader io.Reader, length int) (*Key, error) { return key, nil } -// InsertPolicyKey puts the provided policy key into the kernel keyring with the -// provided description, and type logon. The key must be a policy key. -func InsertPolicyKey(key *Key, description string, targetUser *user.User) error { - if err := util.CheckValidLength(metadata.PolicyKeyLen, key.Len()); err != nil { - return errors.Wrap(err, "policy key") - } - - // Create our payload (containing an FscryptKey) - payload, err := newBlankKey(int(unsafe.Sizeof(unix.FscryptKey{}))) - if err != nil { - return err - } - defer payload.Wipe() - - // Cast the payload to an FscryptKey so we can initialize the fields. - fscryptKey := (*unix.FscryptKey)(util.Ptr(payload.data)) - // Mode is ignored by the kernel - fscryptKey.Mode = 0 - fscryptKey.Size = metadata.PolicyKeyLen - copy(fscryptKey.Raw[:], key.data) - - return security.InsertKey(payload.data, description, targetUser) -} - var ( // The recovery code is base32 with a dash between each block of 8 characters. encoding = base32.StdEncoding @@ -290,7 +276,7 @@ func WriteRecoveryCode(key *Key, writer io.Writer) error { } // We store the base32 encoded data (without separators) in a temp key - encodedKey, err := newBlankKey(encodedLength) + encodedKey, err := NewBlankKey(encodedLength) if err != nil { return err } @@ -318,7 +304,7 @@ func WriteRecoveryCode(key *Key, writer io.Writer) error { // be given the same level of protection as a raw cryptographic key. func ReadRecoveryCode(reader io.Reader) (*Key, error) { // We store the base32 encoded data (without separators) in a temp key - encodedKey, err := newBlankKey(encodedLength) + encodedKey, err := NewBlankKey(encodedLength) if err != nil { return nil, err } @@ -347,7 +333,7 @@ func ReadRecoveryCode(reader io.Reader) (*Key, error) { } // Now we decode the key, resizing if necessary - decodedKey, err := newBlankKey(decodedLength) + decodedKey, err := NewBlankKey(decodedLength) if err != nil { return nil, err } diff --git a/keyring/keyring.go b/keyring/keyring.go new file mode 100644 index 0000000..7a5fd64 --- /dev/null +++ b/keyring/keyring.go @@ -0,0 +1,100 @@ +/* + * keyring.go - Add/remove encryption policy keys to/from kernel + * + * 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 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. +package keyring + +import ( + "os/user" + "strconv" + + "github.com/pkg/errors" + + "github.com/google/fscrypt/crypto" + "github.com/google/fscrypt/metadata" + "github.com/google/fscrypt/util" +) + +// 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") + 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 +// added/removed/gotten to, and how. +type Options struct { + // 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 string +} + +// AddEncryptionKey adds an encryption policy key to a kernel keyring. +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") + } + return userAddKey(key, options.Service+descriptor, options.User) +} + +// RemoveEncryptionKey removes an encryption policy key from a kernel keyring. +func RemoveEncryptionKey(descriptor string, options *Options) error { + return userRemoveKey(options.Service+descriptor, options.User) +} + +// KeyStatus is an enum that represents the status of a key in a kernel keyring. +type KeyStatus int + +// The possible values of KeyStatus. +const ( + KeyStatusUnknown = 0 + iota + KeyAbsent + KeyPresent +) + +func (status KeyStatus) String() string { + switch status { + case KeyStatusUnknown: + return "Unknown" + case KeyAbsent: + return "Absent" + case KeyPresent: + return "Present" + default: + return strconv.Itoa(int(status)) + } +} + +// GetEncryptionKeyStatus gets the status of an encryption policy key in a +// kernel keyring. +func GetEncryptionKeyStatus(descriptor string, options *Options) (KeyStatus, error) { + _, err := userFindKey(options.Service+descriptor, options.User) + if err != nil { + return KeyAbsent, nil + } + return KeyPresent, nil +} 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") + } +} diff --git a/keyring/user_keyring.go b/keyring/user_keyring.go new file mode 100644 index 0000000..adac71a --- /dev/null +++ b/keyring/user_keyring.go @@ -0,0 +1,221 @@ +/* + * user_keyring.go - Add/remove encryption policy keys to/from user keyrings + * + * Copyright 2017 Google Inc. + * Author: Joe Richey (joerichey@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 + +import ( + "os/user" + "runtime" + "unsafe" + + "github.com/pkg/errors" + "golang.org/x/sys/unix" + + "fmt" + "log" + + "github.com/google/fscrypt/crypto" + "github.com/google/fscrypt/security" + "github.com/google/fscrypt/util" +) + +// KeyType is always logon as required by filesystem encryption. +const KeyType = "logon" + +// userAddKey puts the provided policy key into the user keyring for the +// specified user with the provided description, and type logon. +func userAddKey(key *crypto.Key, description string, targetUser *user.User) error { + runtime.LockOSThread() // ensure target user keyring remains possessed in thread keyring + defer runtime.UnlockOSThread() + + // Create our payload (containing an FscryptKey) + payload, err := crypto.NewBlankKey(int(unsafe.Sizeof(unix.FscryptKey{}))) + if err != nil { + return err + } + defer payload.Wipe() + + // Cast the payload to an FscryptKey so we can initialize the fields. + fscryptKey := (*unix.FscryptKey)(payload.UnsafePtr()) + // Mode is ignored by the kernel + fscryptKey.Mode = 0 + fscryptKey.Size = uint32(key.Len()) + copy(fscryptKey.Raw[:], key.Data()) + + keyringID, err := UserKeyringID(targetUser, true) + if err != nil { + return err + } + keyID, err := unix.AddKey(KeyType, description, payload.Data(), keyringID) + log.Printf("KeyctlAddKey(%s, %s, , %d) = %d, %v", + KeyType, description, keyringID, keyID, err) + if err != nil { + return errors.Wrap(ErrKeyAdd, err.Error()) + } + return nil +} + +// userRemoveKey tries to remove a policy key from the user keyring with the +// provided description. An error is returned if the key does not exist. +func userRemoveKey(description string, targetUser *user.User) error { + runtime.LockOSThread() // ensure target user keyring remains possessed in thread keyring + defer runtime.UnlockOSThread() + + keyID, err := userFindKey(description, targetUser) + if err != nil { + return ErrKeyNotPresent + } + + // We use KEYCTL_INVALIDATE instead of KEYCTL_REVOKE because + // invalidating a key immediately removes it. + _, err = unix.KeyctlInt(unix.KEYCTL_INVALIDATE, keyID, 0, 0, 0) + log.Printf("KeyctlInvalidate(%d) = %v", keyID, err) + if err != nil { + return errors.Wrap(ErrKeyRemove, err.Error()) + } + return nil +} + +// userFindKey tries to locate a key in the user keyring with the provided +// description. The key ID is returned if we can find the key. An error is +// returned if the key does not exist. +func userFindKey(description string, targetUser *user.User) (int, error) { + runtime.LockOSThread() // ensure target user keyring remains possessed in thread keyring + defer runtime.UnlockOSThread() + + keyringID, err := UserKeyringID(targetUser, false) + if err != nil { + return 0, err + } + + keyID, err := unix.KeyctlSearch(keyringID, KeyType, description, 0) + log.Printf("KeyctlSearch(%d, %s, %s) = %d, %v", keyringID, KeyType, description, keyID, err) + if err != nil { + return 0, errors.Wrap(ErrKeySearch, err.Error()) + } + return keyID, err +} + +// UserKeyringID returns the key id of the target user's user keyring. We also +// ensure that the keyring will be accessible by linking it into the thread +// keyring and linking it into the root user keyring (permissions allowing). If +// checkSession is true, an error is returned if a normal user requests their +// user keyring, but it is not in the current session keyring. +func UserKeyringID(targetUser *user.User, checkSession bool) (int, error) { + runtime.LockOSThread() // ensure target user keyring remains possessed in thread keyring + defer runtime.UnlockOSThread() + + uid := util.AtoiOrPanic(targetUser.Uid) + targetKeyring, err := userKeyringIDLookup(uid) + if err != nil { + return 0, errors.Wrap(ErrAccessUserKeyring, err.Error()) + } + + if !util.IsUserRoot() { + // Make sure the returned keyring will be accessible by checking + // that it is in the session keyring. + if checkSession && !isUserKeyringInSession(uid) { + return 0, ErrSessionUserKeying + } + return targetKeyring, nil + } + + // Make sure the returned keyring will be accessible by linking it into + // the root user's user keyring (which will not be garbage collected). + rootKeyring, err := userKeyringIDLookup(0) + if err != nil { + return 0, errors.Wrap(ErrLinkUserKeyring, err.Error()) + } + + if rootKeyring != targetKeyring { + if err = keyringLink(targetKeyring, rootKeyring); err != nil { + return 0, errors.Wrap(ErrLinkUserKeyring, err.Error()) + } + } + return targetKeyring, nil +} + +func userKeyringIDLookup(uid int) (keyringID int, err error) { + + // Our goals here are to: + // - Find the user keyring (for the provided uid) + // - Link it into the current thread keyring (so we can use it) + // - Make no permanent changes to the process privileges + // Complicating this are the facts that: + // - The value of KEY_SPEC_USER_KEYRING is determined by the ruid + // - Keyring linking permissions use the euid + // So we have to change both the ruid and euid to make this work, + // setting the suid to 0 so that we can later switch back. + ruid, euid, suid := security.GetUids() + if ruid != uid || euid != uid { + if err = security.SetUids(uid, uid, 0); err != nil { + return + } + defer func() { + resetErr := security.SetUids(ruid, euid, suid) + if resetErr != nil { + err = resetErr + } + }() + } + + // We get the value of KEY_SPEC_USER_KEYRING. Note that this will also + // trigger the creation of the uid keyring if it does not yet exist. + keyringID, err = unix.KeyctlGetKeyringID(unix.KEY_SPEC_USER_KEYRING, true) + log.Printf("keyringID(_uid.%d) = %d, %v", uid, keyringID, err) + if err != nil { + return 0, err + } + + // We still want to use this keyring after our privileges are reset. So + // we link it into the thread keyring, preventing a loss of access. + // + // We must be under LockOSThread() for this to work reliably. Note that + // we can't just use the process keyring, since it doesn't work reliably + // in Go programs, due to the Go runtime creating threads before the + // program starts and has a chance to create the process keyring. + if err = keyringLink(keyringID, unix.KEY_SPEC_THREAD_KEYRING); err != nil { + return 0, err + } + + return keyringID, nil +} + +// isUserKeyringInSession tells us if the user's uid keyring is in the current +// session keyring. +func isUserKeyringInSession(uid int) bool { + // We cannot use unix.KEY_SPEC_SESSION_KEYRING directly as that might + // create a session keyring if one does not exist. + sessionKeyring, err := unix.KeyctlGetKeyringID(unix.KEY_SPEC_SESSION_KEYRING, false) + log.Printf("keyringID(session) = %d, %v", sessionKeyring, err) + if err != nil { + return false + } + + description := fmt.Sprintf("_uid.%d", uid) + id, err := unix.KeyctlSearch(sessionKeyring, "keyring", description, 0) + log.Printf("KeyctlSearch(%d, keyring, %s) = %d, %v", sessionKeyring, description, id, err) + return err == nil +} + +func keyringLink(keyID int, keyringID int) error { + _, err := unix.KeyctlInt(unix.KEYCTL_LINK, keyID, keyringID, 0, 0) + log.Printf("KeyctlLink(%d, %d) = %v", keyID, keyringID, err) + return err +} diff --git a/pam/pam.go b/pam/pam.go index c48dd13..ece6bda 100644 --- a/pam/pam.go +++ b/pam/pam.go @@ -34,6 +34,7 @@ import ( "os/user" "unsafe" + "github.com/google/fscrypt/keyring" "github.com/google/fscrypt/security" ) @@ -130,7 +131,7 @@ func (h *Handle) GetItem(i Item) (unsafe.Pointer, error) { // StartAsPamUser sets the effective privileges to that of the PAM user, and // configures the PAM user's keyrings to be properly linked. func (h *Handle) StartAsPamUser() error { - if _, err := security.UserKeyringID(h.PamUser, true); err != nil { + if _, err := keyring.UserKeyringID(h.PamUser, true); err != nil { log.Printf("Setting up keyrings in PAM: %v", err) } userPrivs, err := security.UserPrivileges(h.PamUser) diff --git a/security/keyring.go b/security/keyring.go deleted file mode 100644 index 3236775..0000000 --- a/security/keyring.go +++ /dev/null @@ -1,214 +0,0 @@ -/* - * keyring.go - Handles inserting/removing into user keyrings. - * - * Copyright 2017 Google Inc. - * Author: Joe Richey (joerichey@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 security - -import ( - "fmt" - "log" - "os/user" - "runtime" - - "github.com/pkg/errors" - "golang.org/x/sys/unix" - - "github.com/google/fscrypt/util" -) - -// KeyType is always logon as required by filesystem encryption. -const KeyType = "logon" - -// Keyring related error values -var ( - ErrKeySearch = errors.New("could not find key with descriptor") - ErrKeyRemove = util.SystemError("could not remove key from the keyring") - ErrKeyInsert = util.SystemError("could not insert key into the keyring") - 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") -) - -// FindKey tries to locate a key in the kernel keyring with the provided -// description. The key ID is returned if we can find the key. An error is -// returned if the key does not exist. -func FindKey(description string, targetUser *user.User) (int, error) { - runtime.LockOSThread() // ensure target user keyring remains possessed in thread keyring - defer runtime.UnlockOSThread() - - keyringID, err := UserKeyringID(targetUser, false) - if err != nil { - return 0, err - } - - keyID, err := unix.KeyctlSearch(keyringID, KeyType, description, 0) - log.Printf("KeyctlSearch(%d, %s, %s) = %d, %v", keyringID, KeyType, description, keyID, err) - if err != nil { - return 0, errors.Wrap(ErrKeySearch, err.Error()) - } - return keyID, err -} - -// RemoveKey tries to remove a policy key from the kernel keyring with the -// provided description. An error is returned if the key does not exist. -func RemoveKey(description string, targetUser *user.User) error { - runtime.LockOSThread() // ensure target user keyring remains possessed in thread keyring - defer runtime.UnlockOSThread() - - keyID, err := FindKey(description, targetUser) - if err != nil { - return err - } - - // We use KEYCTL_INVALIDATE instead of KEYCTL_REVOKE because - // invalidating a key immediately removes it. - _, err = unix.KeyctlInt(unix.KEYCTL_INVALIDATE, keyID, 0, 0, 0) - log.Printf("KeyctlInvalidate(%d) = %v", keyID, err) - if err != nil { - return errors.Wrap(ErrKeyRemove, err.Error()) - } - return nil -} - -// InsertKey puts the provided data into the kernel keyring with the provided -// description. -func InsertKey(data []byte, description string, targetUser *user.User) error { - runtime.LockOSThread() // ensure target user keyring remains possessed in thread keyring - defer runtime.UnlockOSThread() - - keyringID, err := UserKeyringID(targetUser, true) - if err != nil { - return err - } - - keyID, err := unix.AddKey(KeyType, description, data, keyringID) - log.Printf("KeyctlAddKey(%s, %s, , %d) = %d, %v", - KeyType, description, keyringID, keyID, err) - if err != nil { - return errors.Wrap(ErrKeyInsert, err.Error()) - } - return nil -} - -// UserKeyringID returns the key id of the target user's user keyring. We also -// ensure that the keyring will be accessible by linking it into the thread -// keyring and linking it into the root user keyring (permissions allowing). If -// checkSession is true, an error is returned if a normal user requests their -// user keyring, but it is not in the current session keyring. -func UserKeyringID(targetUser *user.User, checkSession bool) (int, error) { - runtime.LockOSThread() // ensure target user keyring remains possessed in thread keyring - defer runtime.UnlockOSThread() - - uid := util.AtoiOrPanic(targetUser.Uid) - targetKeyring, err := userKeyringIDLookup(uid) - if err != nil { - return 0, errors.Wrap(ErrAccessUserKeyring, err.Error()) - } - - if !util.IsUserRoot() { - // Make sure the returned keyring will be accessible by checking - // that it is in the session keyring. - if checkSession && !isUserKeyringInSession(uid) { - return 0, ErrSessionUserKeying - } - return targetKeyring, nil - } - - // Make sure the returned keyring will be accessible by linking it into - // the root user's user keyring (which will not be garbage collected). - rootKeyring, err := userKeyringIDLookup(0) - if err != nil { - return 0, errors.Wrap(ErrLinkUserKeyring, err.Error()) - } - - if rootKeyring != targetKeyring { - if err = keyringLink(targetKeyring, rootKeyring); err != nil { - return 0, errors.Wrap(ErrLinkUserKeyring, err.Error()) - } - } - return targetKeyring, nil -} - -func userKeyringIDLookup(uid int) (keyringID int, err error) { - - // Our goals here are to: - // - Find the user keyring (for the provided uid) - // - Link it into the current thread keyring (so we can use it) - // - Make no permanent changes to the process privileges - // Complicating this are the facts that: - // - The value of KEY_SPEC_USER_KEYRING is determined by the ruid - // - Keyring linking permissions use the euid - // So we have to change both the ruid and euid to make this work, - // setting the suid to 0 so that we can later switch back. - ruid, euid, suid := getUids() - if ruid != uid || euid != uid { - if err = setUids(uid, uid, 0); err != nil { - return - } - defer func() { - resetErr := setUids(ruid, euid, suid) - if resetErr != nil { - err = resetErr - } - }() - } - - // We get the value of KEY_SPEC_USER_KEYRING. Note that this will also - // trigger the creation of the uid keyring if it does not yet exist. - keyringID, err = unix.KeyctlGetKeyringID(unix.KEY_SPEC_USER_KEYRING, true) - log.Printf("keyringID(_uid.%d) = %d, %v", uid, keyringID, err) - if err != nil { - return 0, err - } - - // We still want to use this keyring after our privileges are reset. So - // we link it into the thread keyring, preventing a loss of access. - // - // We must be under LockOSThread() for this to work reliably. Note that - // we can't just use the process keyring, since it doesn't work reliably - // in Go programs, due to the Go runtime creating threads before the - // program starts and has a chance to create the process keyring. - if err = keyringLink(keyringID, unix.KEY_SPEC_THREAD_KEYRING); err != nil { - return 0, err - } - - return keyringID, nil -} - -// isUserKeyringInSession tells us if the user's uid keyring is in the current -// session keyring. -func isUserKeyringInSession(uid int) bool { - // We cannot use unix.KEY_SPEC_SESSION_KEYRING directly as that might - // create a session keyring if one does not exist. - sessionKeyring, err := unix.KeyctlGetKeyringID(unix.KEY_SPEC_SESSION_KEYRING, false) - log.Printf("keyringID(session) = %d, %v", sessionKeyring, err) - if err != nil { - return false - } - - description := fmt.Sprintf("_uid.%d", uid) - id, err := unix.KeyctlSearch(sessionKeyring, "keyring", description, 0) - log.Printf("KeyctlSearch(%d, keyring, %s) = %d, %v", sessionKeyring, description, id, err) - return err == nil -} - -func keyringLink(keyID int, keyringID int) error { - _, err := unix.KeyctlInt(unix.KEYCTL_LINK, keyID, keyringID, 0, 0) - log.Printf("KeyctlLink(%d, %d) = %v", keyID, keyringID, err) - return err -} diff --git a/security/privileges.go b/security/privileges.go index 3a1ca81..e5751b5 100644 --- a/security/privileges.go +++ b/security/privileges.go @@ -19,9 +19,7 @@ // Package security manages: // - Cache clearing (cache.go) -// - Keyring Operations (keyring.go) // - Privilege manipulation (privileges.go) -// - Maintaining the link between the root and user keyrings. package security // Use the libc versions of setreuid, setregid, and setgroups instead of the @@ -142,7 +140,8 @@ func SetProcessPrivileges(privs *Privileges) error { return nil } -func setUids(ruid, euid, suid int) error { +// SetUids sets the process's real, effective, and saved UIDs. +func SetUids(ruid, euid, suid int) error { log.Printf("Setting ruid=%d euid=%d suid=%d", ruid, euid, suid) // We elevate all the privs before setting them. This prevents issues // with (ruid=1000,euid=1000,suid=0), where just a single call to @@ -156,7 +155,8 @@ func setUids(ruid, euid, suid int) error { return nil } -func getUids() (int, int, int) { +// GetUids gets the process's real, effective, and saved UIDs. +func GetUids() (int, int, int) { var ruid, euid, suid C.uid_t C.getresuid(&ruid, &euid, &suid) return int(ruid), int(euid), int(suid) -- cgit v1.2.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 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.2.3 From 9003a0331a112e8901fae8279f4897a825ee8069 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 15 Dec 2019 19:31:39 -0800 Subject: cmd/fscrypt: add 'fscrypt lock' command Add support for 'fscrypt lock'. This command "locks" a directory, undoing 'fscrypt unlock'. When the filesystem keyring is used, 'fscrypt lock' also detects when a directory wasn't fully locked due to some files still being in-use. It can then be run again later to try to finish locking the files. --- README.md | 15 ++++---- actions/policy.go | 6 ++++ cmd/fscrypt/commands.go | 95 ++++++++++++++++++++++++++++++++++++++++++++----- cmd/fscrypt/errors.go | 6 ++++ cmd/fscrypt/fscrypt.go | 2 +- 5 files changed, 106 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 3a86723..f183f2e 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,8 @@ Concretely, fscrypt contains the following functionality: * `fscrypt setup MOUNTPOINT` - Gets a filesystem ready for use with fscrypt * `fscrypt encrypt DIRECTORY` - Encrypts an empty directory * `fscrypt unlock DIRECTORY` - Unlocks an encrypted directory -* `fscrypt purge MOUNTPOINT` - Removes keys for a filesystem before unmounting +* `fscrypt lock DIRECTORY` - Locks an encrypted directory +* `fscrypt purge MOUNTPOINT` - Locks all encrypted directories on a filesystem * `fscrypt status [PATH]` - Gets detailed info about filesystems or paths * `fscrypt metadata` - Manages policies or protectors directly @@ -367,12 +368,10 @@ Protected with 1 protector: PROTECTOR LINKED DESCRIPTION 7626382168311a9d No custom protector "Super Secret" -# Purging a filesystem locks all the files. ->>>>> sudo fscrypt purge /mnt/disk --user=$USER -WARNING: Encrypted data on this filesystem will be inaccessible until unlocked again!! -Purge all policy keys from "/mnt/disk" and drop global inode cache? [y/N] y -Policies purged for "/mnt/disk". - +# Lock the directory. +>>>>> sudo fscrypt lock /mnt/disk/dir1 --user=$USER +Encrypted data removed from filesystem cache. +"/mnt/disk/dir1" is now locked. >>>>> fscrypt status /mnt/disk/dir1 "/mnt/disk/dir1" is encrypted with fscrypt. @@ -410,7 +409,7 @@ Hello World #### Quiet Version ```bash ->>>>> sudo fscrypt purge /mnt/disk --user=$USER --quiet --force +>>>>> sudo fscrypt lock /mnt/disk/dir1 --quiet --user=$USER >>>>> echo "hunter2" | fscrypt unlock /mnt/disk/dir1 --quiet ``` diff --git a/actions/policy.go b/actions/policy.go index 6ef83ce..2d8c521 100644 --- a/actions/policy.go +++ b/actions/policy.go @@ -406,6 +406,12 @@ func (policy *Policy) Deprovision() error { policy.Context.getKeyringOptions()) } +// 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() +} + // commitData writes the Policy's current data to the filesystem. func (policy *Policy) commitData() error { return policy.Context.Mount.AddPolicy(policy.data) diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go index a3bfef2..8f2d21b 100644 --- a/cmd/fscrypt/commands.go +++ b/cmd/fscrypt/commands.go @@ -281,8 +281,8 @@ var Unlock = cli.Command{ appropriate key into the keyring. This requires unlocking one of the protectors protecting this directory (either by selecting a protector or specifying one with %s). This directory will be - locked again upon reboot, or after running "fscrypt purge" and - unmounting the corresponding filesystem.`, directoryArg, + locked again upon reboot, or after running "fscrypt lock" or + "fscrypt purge".`, directoryArg, shortDisplay(unlockWithFlag)), Flags: []cli.Flag{unlockWithFlag, keyFileFlag, userFlag}, Action: unlockAction, @@ -328,6 +328,88 @@ func unlockAction(c *cli.Context) error { return nil } +func dropCachesIfRequested(c *cli.Context, ctx *actions.Context) error { + if dropCachesFlag.Value { + if err := security.DropFilesystemCache(); err != nil { + return err + } + fmt.Fprintf(c.App.Writer, "Encrypted data removed from filesystem cache.\n") + } else { + fmt.Fprintf(c.App.Writer, "Filesystem %q should now be unmounted.\n", ctx.Mount.Path) + } + return nil +} + +// Lock takes an encrypted directory and locks it, undoing Unlock. +var Lock = cli.Command{ + Name: "lock", + ArgsUsage: directoryArg, + Usage: "lock an encrypted directory", + Description: fmt.Sprintf(`This command takes %s, an encrypted directory + which has been unlocked by fscrypt, and locks the directory by + removing the encryption key from the kernel. I.e., it undoes the + effect of 'fscrypt unlock'. + + 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. + + WARNING: even after the key has been removed, decrypted data may + still be present in freed memory, where it may still be + 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}, + Action: lockAction, +} + +func lockAction(c *cli.Context) error { + if c.NArg() != 1 { + return expectedArgsErr(c, 1, false) + } + + targetUser, err := parseUserFlag(true) + if err != nil { + return newExitError(c, err) + } + path := c.Args().Get(0) + ctx, err := actions.NewContextFromPath(path, targetUser) + if err != nil { + return newExitError(c, err) + } + + log.Printf("performing sanity checks") + // Ensure path is encrypted and filesystem is using fscrypt. + policy, err := actions.GetPolicyFromPath(ctx, path) + if err != nil { + return newExitError(c, err) + } + // Check if directory is already locked + if policy.IsFullyDeprovisioned() { + log.Printf("policy %s is already fully deprovisioned", policy.Descriptor()) + return newExitError(c, errors.Wrapf(ErrPolicyLocked, path)) + } + // Check for permission to drop caches, if it will be needed. + if policy.NeedsUserKeyring() && dropCachesFlag.Value && !util.IsUserRoot() { + return newExitError(c, ErrDropCachesPerm) + } + + if err = policy.Deprovision(); err != nil { + return newExitError(c, err) + } + + if policy.NeedsUserKeyring() { + if err = dropCachesIfRequested(c, ctx); err != nil { + return newExitError(c, err) + } + } + + fmt.Fprintf(c.App.Writer, "%q is now locked.\n", path) + return nil +} + // Purge removes all the policy keys from the keyring (also need unmount). var Purge = cli.Command{ Name: "purge", @@ -401,13 +483,8 @@ func purgeAction(c *cli.Context) error { } fmt.Fprintf(c.App.Writer, "Policies purged for %q.\n", ctx.Mount.Path) - if dropCachesFlag.Value { - if err = security.DropFilesystemCache(); err != nil { - return newExitError(c, err) - } - fmt.Fprintf(c.App.Writer, "Encrypted data removed from filesystem cache.\n") - } else { - fmt.Fprintf(c.App.Writer, "Filesystem %q should now be unmounted.\n", ctx.Mount.Path) + if err = dropCachesIfRequested(c, ctx); err != nil { + return newExitError(c, err) } return nil } diff --git a/cmd/fscrypt/errors.go b/cmd/fscrypt/errors.go index ed57dbe..68135fe 100644 --- a/cmd/fscrypt/errors.go +++ b/cmd/fscrypt/errors.go @@ -56,6 +56,7 @@ var ( ErrAllLoadsFailed = errors.New("could not load any protectors") ErrMustBeRoot = errors.New("this command must be run as root") ErrPolicyUnlocked = errors.New("this file or directory is already unlocked") + ErrPolicyLocked = errors.New("this file or directory is already locked") ErrBadOwners = errors.New("you do not own this directory") ErrNotEmptyDir = errors.New("not an empty directory") ErrNotPassphrase = errors.New("protector does not use a passphrase") @@ -94,6 +95,11 @@ func getErrorSuggestions(err error) string { needs to be enabled for this filesystem. See the documentation on how to enable encryption on ext4 systems (and the risks of doing so).` + case keyring.ErrKeyFilesOpen: + return `Directory was incompletely locked because some files are + still open. These files remain accessible. Try killing + any processes using files in the directory, then + re-running 'fscrypt lock'.` 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/fscrypt.go b/cmd/fscrypt/fscrypt.go index 9ac8e2f..b6549f4 100644 --- a/cmd/fscrypt/fscrypt.go +++ b/cmd/fscrypt/fscrypt.go @@ -76,7 +76,7 @@ func main() { // Initialize command list and setup all of the commands. app.Action = defaultAction - app.Commands = []cli.Command{Setup, Encrypt, Unlock, Purge, Status, Metadata} + app.Commands = []cli.Command{Setup, Encrypt, Unlock, Lock, Purge, Status, Metadata} for i := range app.Commands { setupCommand(&app.Commands[i]) } -- cgit v1.2.3 From 0829eb74863bd279ae012779e52040ecc7f7178e Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 15 Dec 2019 19:31:39 -0800 Subject: cmd/fscrypt: adjust user and keyring validation and preparation Don't force the user to provide a --user argument when running fscrypt as root if they're doing something where the TargetUser isn't actually needed, such as provisioning/deprovisioning a v1 encryption policy to/from the filesystem keyring, or creating a non-login protector. Also don't set up the user keyring (or check for it being set up) if it won't actually be used. Finally, if we'll be provisioning/deprovisioning a v1 encryption policy to/from the filesystem keyring, make sure the command is running as root, since the kernel requires this. --- cmd/fscrypt/commands.go | 69 +++++++++++++++++++++++++++++++++++++++++------- cmd/fscrypt/errors.go | 5 ++++ cmd/fscrypt/flags.go | 23 +++------------- cmd/fscrypt/protector.go | 6 +++++ 4 files changed, 74 insertions(+), 29 deletions(-) diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go index 8f2d21b..621651e 100644 --- a/cmd/fscrypt/commands.go +++ b/cmd/fscrypt/commands.go @@ -30,6 +30,7 @@ import ( "github.com/google/fscrypt/actions" "github.com/google/fscrypt/filesystem" + "github.com/google/fscrypt/keyring" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/security" "github.com/google/fscrypt/util" @@ -134,11 +135,35 @@ func encryptAction(c *cli.Context) error { return nil } +// validateKeyringPrereqs ensures we're ready to add, remove, or get the status +// 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 { + if ctx.Config.GetUseFsKeyringForV1Policies() { + // We'll be using the filesystem keyring, but it's a v1 + // encryption policy so root is required. + if !util.IsUserRoot() { + return ErrFsKeyringPerm + } + return nil + } + // We'll be using the target user's user keyring, so make sure a user + // was explicitly specified if the command is being run as root, and + // make sure that user's keyring is accessible. + if userFlag.Value == "" && util.IsUserRoot() { + return ErrSpecifyUser + } + if _, err := keyring.UserKeyringID(ctx.TargetUser, true); err != nil { + return err + } + return nil +} + // encryptPath sets up encryption on path and provisions the policy to the // keyring unless --skip-unlock is used. On failure, an error is returned, any // metadata creation is reverted, and the directory is unmodified. func encryptPath(path string) (err error) { - targetUser, err := parseUserFlag(!skipUnlockFlag.Value) + targetUser, err := parseUserFlag() if err != nil { return } @@ -154,10 +179,24 @@ func encryptPath(path string) (err error) { if policyFlag.Value != "" { log.Printf("getting policy for %q", path) - policy, err = getPolicyFromFlag(policyFlag.Value, ctx.TargetUser) + if policy, err = getPolicyFromFlag(policyFlag.Value, ctx.TargetUser); err != nil { + return + } + + if !skipUnlockFlag.Value { + if err = validateKeyringPrereqs(ctx, policy); err != nil { + return + } + } } else { log.Printf("creating policy for %q", path) + if !skipUnlockFlag.Value { + if err = validateKeyringPrereqs(ctx, nil); err != nil { + return + } + } + protector, created, protErr := selectOrCreateProtector(ctx) // Successfully created protector should be reverted on failure. if protErr != nil { @@ -173,12 +212,11 @@ func encryptPath(path string) (err error) { if err = protector.Unlock(existingKeyFn); err != nil { return } - policy, err = actions.CreatePolicy(ctx, protector) + if policy, err = actions.CreatePolicy(ctx, protector); err != nil { + return + } } // Successfully created policy should be reverted on failure. - if err != nil { - return - } defer func() { policy.Lock() if err != nil { @@ -293,7 +331,7 @@ func unlockAction(c *cli.Context) error { return expectedArgsErr(c, 1, false) } - targetUser, err := parseUserFlag(true) + targetUser, err := parseUserFlag() if err != nil { return newExitError(c, err) } @@ -309,6 +347,10 @@ func unlockAction(c *cli.Context) error { if err != nil { return newExitError(c, err) } + // Ensure the keyring is ready. + if err = validateKeyringPrereqs(ctx, policy); err != nil { + return newExitError(c, err) + } // Check if directory is already unlocked if policy.IsProvisioned() { log.Printf("policy %s is already provisioned", policy.Descriptor()) @@ -370,7 +412,7 @@ func lockAction(c *cli.Context) error { return expectedArgsErr(c, 1, false) } - targetUser, err := parseUserFlag(true) + targetUser, err := parseUserFlag() if err != nil { return newExitError(c, err) } @@ -386,6 +428,10 @@ func lockAction(c *cli.Context) error { if err != nil { return newExitError(c, err) } + // Ensure the keyring is ready. + if err = validateKeyringPrereqs(ctx, policy); err != nil { + return newExitError(c, err) + } // Check if directory is already locked if policy.IsFullyDeprovisioned() { log.Printf("policy %s is already fully deprovisioned", policy.Descriptor()) @@ -459,7 +505,7 @@ func purgeAction(c *cli.Context) error { } } - targetUser, err := parseUserFlag(true) + targetUser, err := parseUserFlag() if err != nil { return newExitError(c, err) } @@ -468,6 +514,9 @@ func purgeAction(c *cli.Context) error { if err != nil { return newExitError(c, err) } + if err = validateKeyringPrereqs(ctx, nil); err != nil { + return newExitError(c, err) + } question := fmt.Sprintf("Purge all policy keys from %q", ctx.Mount.Path) if dropCachesFlag.Value { @@ -604,7 +653,7 @@ func createProtectorAction(c *cli.Context) error { return expectedArgsErr(c, 1, false) } - targetUser, err := parseUserFlag(false) + targetUser, err := parseUserFlag() if err != nil { return newExitError(c, err) } diff --git a/cmd/fscrypt/errors.go b/cmd/fscrypt/errors.go index 68135fe..ac969f6 100644 --- a/cmd/fscrypt/errors.go +++ b/cmd/fscrypt/errors.go @@ -63,6 +63,7 @@ var ( ErrUnknownUser = errors.New("unknown user") ErrDropCachesPerm = errors.New("inode cache can only be dropped as root") ErrSpecifyUser = errors.New("user must be specified when run as root") + ErrFsKeyringPerm = errors.New("root is required to add/remove v1 encryption policy keys to/from filesystem") ) var loadHelpText = fmt.Sprintf("You may need to mount a linked filesystem. Run with %s for more information.", shortDisplay(verboseFlag)) @@ -141,6 +142,10 @@ func getErrorSuggestions(err error) string { properly clear the inode cache, or it should be run with %s=false (this may leave encrypted files and directories in an accessible state).`, shortDisplay(dropCachesFlag)) + 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.` 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 2eea8de..361732c 100644 --- a/cmd/fscrypt/flags.go +++ b/cmd/fscrypt/flags.go @@ -33,7 +33,6 @@ import ( "github.com/urfave/cli" "github.com/google/fscrypt/actions" - "github.com/google/fscrypt/keyring" "github.com/google/fscrypt/util" ) @@ -283,24 +282,10 @@ func getPolicyFromFlag(flagValue string, targetUser *user.User) (*actions.Policy } // parseUserFlag returns the user specified by userFlag or the current effective -// user if the flag value is missing. If the effective user is root, however, a -// user must specified in the flag. If checkKeyring is true, we also make sure -// there are no problems accessing the user keyring. -func parseUserFlag(checkKeyring bool) (targetUser *user.User, err error) { +// user if the flag value is missing. +func parseUserFlag() (targetUser *user.User, err error) { if userFlag.Value != "" { - targetUser, err = user.Lookup(userFlag.Value) - } else { - if util.IsUserRoot() { - return nil, ErrSpecifyUser - } - targetUser, err = util.EffectiveUser() + return user.Lookup(userFlag.Value) } - if err != nil { - return nil, err - } - - if checkKeyring { - _, err = keyring.UserKeyringID(targetUser, true) - } - return targetUser, err + return util.EffectiveUser() } diff --git a/cmd/fscrypt/protector.go b/cmd/fscrypt/protector.go index 8cbcf03..25f1984 100644 --- a/cmd/fscrypt/protector.go +++ b/cmd/fscrypt/protector.go @@ -26,6 +26,7 @@ import ( "github.com/google/fscrypt/actions" "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/metadata" + "github.com/google/fscrypt/util" ) // createProtector makes a new protector on either ctx.Mount or if the requested @@ -37,6 +38,11 @@ func createProtectorFromContext(ctx *actions.Context) (*actions.Protector, error } log.Printf("using source: %s", ctx.Config.Source.String()) + if ctx.Config.Source == metadata.SourceType_pam_passphrase && + userFlag.Value == "" && util.IsUserRoot() { + return nil, ErrSpecifyUser + } + name, err := promptForName(ctx) if err != nil { return nil, err -- cgit v1.2.3 From d0ac36dcea341ff000aca983dd80e7bef9fc30ec Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 15 Dec 2019 19:31:39 -0800 Subject: pam_fscrypt: update to handle filesystem keyring FS_IOC_ADD_ENCRYPTION_KEY and FS_IOC_REMOVE_ENCRYPTION_KEY require root for v1 policy keys, so update the PAM module to re-acquire root privileges while provisioning/deprovisioning policies that need this. Also, only set up the user keyring if it will actually be used. --- actions/policy.go | 6 ++++ pam/pam.go | 20 +++++++------ pam_fscrypt/pam_fscrypt.go | 71 ++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 84 insertions(+), 13 deletions(-) diff --git a/actions/policy.go b/actions/policy.go index 2d8c521..b9cd88c 100644 --- a/actions/policy.go +++ b/actions/policy.go @@ -412,6 +412,12 @@ func (policy *Policy) NeedsUserKeyring() bool { return !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() +} + // commitData writes the Policy's current data to the filesystem. func (policy *Policy) commitData() error { return policy.Context.Mount.AddPolicy(policy.data) diff --git a/pam/pam.go b/pam/pam.go index ece6bda..54a60e2 100644 --- a/pam/pam.go +++ b/pam/pam.go @@ -34,7 +34,6 @@ import ( "os/user" "unsafe" - "github.com/google/fscrypt/keyring" "github.com/google/fscrypt/security" ) @@ -128,26 +127,31 @@ func (h *Handle) GetItem(i Item) (unsafe.Pointer, error) { return data, nil } -// StartAsPamUser sets the effective privileges to that of the PAM user, and -// configures the PAM user's keyrings to be properly linked. +// StartAsPamUser sets the effective privileges to that of the PAM user. func (h *Handle) StartAsPamUser() error { - if _, err := keyring.UserKeyringID(h.PamUser, true); err != nil { - log.Printf("Setting up keyrings in PAM: %v", err) - } userPrivs, err := security.UserPrivileges(h.PamUser) if err != nil { return err } - if h.origPrivs, err = security.ProcessPrivileges(); err != nil { + origPrivs, err := security.ProcessPrivileges() + if err != nil { + return err + } + if err = security.SetProcessPrivileges(userPrivs); err != nil { return err } - return security.SetProcessPrivileges(userPrivs) + h.origPrivs = origPrivs + return nil } // StopAsPamUser restores the original privileges that were running the // PAM module (this is usually root). func (h *Handle) StopAsPamUser() error { + if h.origPrivs == nil { + return nil + } err := security.SetProcessPrivileges(h.origPrivs) + h.origPrivs = nil if err != nil { log.Print(err) } diff --git a/pam_fscrypt/pam_fscrypt.go b/pam_fscrypt/pam_fscrypt.go index c7f9931..b3c7a0e 100644 --- a/pam_fscrypt/pam_fscrypt.go +++ b/pam_fscrypt/pam_fscrypt.go @@ -36,6 +36,7 @@ import ( "github.com/google/fscrypt/actions" "github.com/google/fscrypt/crypto" + "github.com/google/fscrypt/keyring" "github.com/google/fscrypt/pam" "github.com/google/fscrypt/security" ) @@ -81,6 +82,43 @@ func Authenticate(handle *pam.Handle, _ map[string]bool) error { return errors.Wrap(err, "could not set AUTHTOK data") } +func beginProvisioningOp(handle *pam.Handle, policy *actions.Policy) error { + if policy.NeedsRootToProvision() { + return handle.StopAsPamUser() + } + return nil +} + +func endProvisioningOp(handle *pam.Handle, policy *actions.Policy) error { + if policy.NeedsRootToProvision() { + return handle.StartAsPamUser() + } + return nil +} + +// Set up the PAM user's keyring if needed by any encryption policies. +func setupUserKeyringIfNeeded(handle *pam.Handle, policies []*actions.Policy) error { + needed := false + for _, policy := range policies { + if policy.NeedsUserKeyring() { + needed = true + break + } + } + if !needed { + return nil + } + err := handle.StopAsPamUser() + if err != nil { + return err + } + _, err = keyring.UserKeyringID(handle.PamUser, true) + if err != nil { + log.Printf("Setting up keyrings in PAM: %v", err) + } + return handle.StartAsPamUser() +} + // OpenSession provisions any policies protected with the login protector. func OpenSession(handle *pam.Handle, _ map[string]bool) error { // We will always clear the AUTHTOK data @@ -107,6 +145,10 @@ func OpenSession(handle *pam.Handle, _ map[string]bool) error { return nil } + if err = setupUserKeyringIfNeeded(handle, policies); err != nil { + return errors.Wrapf(err, "setting up user keyring") + } + log.Printf("unlocking %d policies protected with AUTHTOK", len(policies)) keyFn := func(_ actions.ProtectorInfo, retry bool) (*crypto.Key, error) { if retry { @@ -144,8 +186,15 @@ func OpenSession(handle *pam.Handle, _ map[string]bool) error { } defer policy.Lock() - if err := policy.Provision(); err != nil { - log.Printf("provisioning policy %s: %s", policy.Descriptor(), err) + if err := beginProvisioningOp(handle, policy); err != nil { + return err + } + provisionErr := policy.Provision() + if err := endProvisioningOp(handle, policy); err != nil { + return err + } + if provisionErr != nil { + log.Printf("provisioning policy %s: %s", policy.Descriptor(), provisionErr) continue } log.Printf("policy %s provisioned", policy.Descriptor()) @@ -163,7 +212,8 @@ func CloseSession(handle *pam.Handle, args map[string]bool) error { } var errLock, errCache error - // Don't automatically drop privileges, we may need them to drop caches. + // Don't automatically drop privileges, since we may need them to + // deprovision policies or to drop caches. if args[lockFlag] { log.Print("locking polices protected with login protector") errLock = lockLoginPolicies(handle) @@ -200,14 +250,25 @@ func lockLoginPolicies(handle *pam.Handle) error { return nil } + if err = setupUserKeyringIfNeeded(handle, policies); err != nil { + return errors.Wrapf(err, "setting up user keyring") + } + // We will try to deprovision all of the policies. for _, policy := range policies { if !policy.IsProvisioned() { log.Printf("policy %s not provisioned", policy.Descriptor()) continue } - if err := policy.Deprovision(); err != nil { - log.Printf("deprovisioning policy %s: %s", policy.Descriptor(), err) + if err := beginProvisioningOp(handle, policy); err != nil { + return err + } + deprovisionErr := policy.Deprovision() + if err := endProvisioningOp(handle, policy); err != nil { + return err + } + if deprovisionErr != nil { + log.Printf("deprovisioning policy %s: %s", policy.Descriptor(), deprovisionErr) continue } log.Printf("policy %s deprovisioned", policy.Descriptor()) -- cgit v1.2.3 From 2b25de6d445faefc28629603dd754aec9f744e60 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 15 Dec 2019 19:31:39 -0800 Subject: Metadata support for v2 encryption policies Linux v5.4 and later supports v2 encryption policies. These have several advantages over v1 encryption policies: - Their encryption keys can be added/removed to/from the filesystem by non-root users, thus gaining the benefits of the filesystem keyring while also retaining support for non-root use. - They use a more standard, secure, and flexible key derivation function. Because of this, some future kernel-level fscrypt features will be implemented for v2 policies only. - They prevent a denial-of-service attack where a user could associate the wrong key with another user's encrypted files. Prepare the fscrypt tool to support v2 encryption policies by: - Adding a policy_version field to the EncryptionOptions, i.e. to the config file and to the policy metadata files. - Using the kernel-specified algorithm to compute the key descriptor for v2 policies. - Handling setting and getting v2 policies. Actually adding/removing the keys for v2 policies to/from the kernel is left for the next patch. --- README.md | 14 ++-- actions/config.go | 4 ++ actions/policy.go | 8 ++- actions/protector.go | 6 +- crypto/crypto.go | 39 +++++++++-- crypto/crypto_test.go | 27 ++++++++ metadata/checks.go | 35 ++++++++-- metadata/config_test.go | 41 +++++++++++- metadata/constants.go | 18 +++-- metadata/metadata.pb.go | 123 +++++++++++++++++++---------------- metadata/metadata.proto | 2 + metadata/policy.go | 170 +++++++++++++++++++++++++++++++++++------------- metadata/policy_test.go | 63 +++++++++++++++--- 13 files changed, 412 insertions(+), 138 deletions(-) diff --git a/README.md b/README.md index f183f2e..ee8e389 100644 --- a/README.md +++ b/README.md @@ -361,7 +361,7 @@ POLICY UNLOCKED PROTECTORS "/mnt/disk/dir1" is encrypted with fscrypt. Policy: 16382f282d7b29ee -Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:1 Unlocked: Yes Protected with 1 protector: @@ -376,7 +376,7 @@ Encrypted data removed from filesystem cache. "/mnt/disk/dir1" is encrypted with fscrypt. Policy: 16382f282d7b29ee -Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:1 Unlocked: No Protected with 1 protector: @@ -397,7 +397,7 @@ Enter custom passphrase for protector "Super Secret": "/mnt/disk/dir1" is encrypted with fscrypt. Policy: 16382f282d7b29ee -Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:1 Unlocked: Yes Protected with 1 protector: @@ -433,7 +433,7 @@ Enter login passphrase for joerichey: "/mnt/disk/dir2" is encrypted with fscrypt. Policy: fe1c92009abc1cff -Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:1 Unlocked: Yes Protected with 1 protector: @@ -469,7 +469,7 @@ PROTECTOR LINKED DESCRIPTION "/mnt/disk/dir1" is encrypted with fscrypt. Policy: 16382f282d7b29ee -Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:1 Unlocked: Yes Protected with 1 protector: @@ -565,7 +565,7 @@ fe1c92009abc1cff No 6891f0a901f0065e "/mnt/disk/dir1" is encrypted with fscrypt. Policy: 16382f282d7b29ee -Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:1 Unlocked: No Protected with 1 protector: @@ -581,7 +581,7 @@ Protector 2c75f519b9c9959d now protecting policy 16382f282d7b29ee. "/mnt/disk/dir1" is encrypted with fscrypt. Policy: 16382f282d7b29ee -Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:1 Unlocked: No Protected with 2 protectors: diff --git a/actions/config.go b/actions/config.go index 7fdaf5b..6b019df 100644 --- a/actions/config.go +++ b/actions/config.go @@ -133,6 +133,10 @@ func getConfig() (*metadata.Config, error) { config.Options.Filenames = metadata.DefaultOptions.Filenames log.Printf("Falling back to filenames mode of %q", config.Options.Filenames) } + if config.Options.PolicyVersion == 0 { + config.Options.PolicyVersion = metadata.DefaultOptions.PolicyVersion + log.Printf("Falling back to policy version of %d", config.Options.PolicyVersion) + } if err := config.CheckValidity(); err != nil { return nil, errors.Wrap(ErrBadConfigFile, err.Error()) diff --git a/actions/policy.go b/actions/policy.go index b9cd88c..f6d3ea9 100644 --- a/actions/policy.go +++ b/actions/policy.go @@ -95,11 +95,17 @@ func CreatePolicy(ctx *Context, protector *Protector) (*Policy, error) { return nil, err } + keyDescriptor, err := crypto.ComputeKeyDescriptor(key, ctx.Config.Options.PolicyVersion) + if err != nil { + key.Wipe() + return nil, err + } + policy := &Policy{ Context: ctx, data: &metadata.PolicyData{ Options: ctx.Config.Options, - KeyDescriptor: crypto.ComputeDescriptor(key), + KeyDescriptor: keyDescriptor, }, key: key, created: true, diff --git a/actions/protector.go b/actions/protector.go index fe5d694..4bd7c15 100644 --- a/actions/protector.go +++ b/actions/protector.go @@ -140,7 +140,11 @@ func CreateProtector(ctx *Context, name string, keyFn KeyFunc) (*Protector, erro if protector.key, err = crypto.NewRandomKey(metadata.InternalKeyLen); err != nil { return nil, err } - protector.data.ProtectorDescriptor = crypto.ComputeDescriptor(protector.key) + protector.data.ProtectorDescriptor, err = crypto.ComputeKeyDescriptor(protector.key, 1) + if err != nil { + protector.Lock() + return nil, err + } if err = protector.Rewrap(keyFn); err != nil { protector.Lock() diff --git a/crypto/crypto.go b/crypto/crypto.go index ec961b6..9a138d0 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -28,7 +28,7 @@ // - key stretching (SHA256-based HKDF) // - key wrapping/unwrapping (Encrypt then MAC) // - passphrase-based key derivation (Argon2id) -// - descriptor computation (double SHA512) +// - key descriptor computation (double SHA512, or HKDF-SHA512) package crypto import ( @@ -38,6 +38,7 @@ import ( "crypto/sha256" "crypto/sha512" "encoding/hex" + "io" "github.com/pkg/errors" "golang.org/x/crypto/argon2" @@ -176,16 +177,42 @@ func Unwrap(wrappingKey *Key, data *metadata.WrappedKeyData) (*Key, error) { return secretKey, nil } -// ComputeDescriptor computes the descriptor for a given cryptographic key. In -// keeping with the process used in e4crypt, this uses the initial bytes -// (formatted as hexadecimal) of the double application of SHA512 on the key. -func ComputeDescriptor(key *Key) string { +func computeKeyDescriptorV1(key *Key) string { h1 := sha512.Sum512(key.data) h2 := sha512.Sum512(h1[:]) - length := hex.DecodedLen(metadata.DescriptorLen) + length := hex.DecodedLen(metadata.PolicyDescriptorLenV1) return hex.EncodeToString(h2[:length]) } +func computeKeyDescriptorV2(key *Key) (string, error) { + // This algorithm is specified by the kernel. It uses unsalted + // HKDF-SHA512, where the application-information string is the prefix + // "fscrypt\0" followed by the HKDF_CONTEXT_KEY_IDENTIFIER byte. + hkdf := hkdf.New(sha512.New, key.data, nil, []byte("fscrypt\x00\x01")) + h := make([]byte, hex.DecodedLen(metadata.PolicyDescriptorLenV2)) + if _, err := io.ReadFull(hkdf, h); err != nil { + return "", err + } + return hex.EncodeToString(h), nil +} + +// ComputeKeyDescriptor computes the descriptor for a given cryptographic key. +// If policyVersion=1, it uses the first 8 bytes of the double application of +// SHA512 on the key. Use this for protectors and v1 policy keys. +// If policyVersion=2, it uses HKDF-SHA512 to compute a key identifier that's +// compatible with the kernel's key identifiers for v2 policy keys. +// In both cases, the resulting bytes are formatted as hex. +func ComputeKeyDescriptor(key *Key, policyVersion int64) (string, error) { + switch policyVersion { + case 1: + return computeKeyDescriptorV1(key), nil + case 2: + return computeKeyDescriptorV2(key) + default: + return "", errors.Errorf("policy version of %d is invalid", policyVersion) + } +} + // PassphraseHash uses Argon2id to produce a Key given the passphrase, salt, and // hashing costs. This method is designed to take a long time and consume // considerable memory. For more information, see the documentation at diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index d0cef82..6eb0b02 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -464,6 +464,33 @@ func TestUnwrapWrongData(t *testing.T) { } } +func TestComputeKeyDescriptorV1(t *testing.T) { + descriptor, err := ComputeKeyDescriptor(fakeValidPolicyKey, 1) + if err != nil { + t.Fatal(err) + } + if descriptor != "8290608a029c5aae" { + t.Errorf("wrong v1 descriptor: %s", descriptor) + } +} + +func TestComputeKeyDescriptorV2(t *testing.T) { + descriptor, err := ComputeKeyDescriptor(fakeValidPolicyKey, 2) + if err != nil { + t.Fatal(err) + } + if descriptor != "2139f52bf8386ee99845818ac7e91c4a" { + t.Errorf("wrong v2 descriptor: %s", descriptor) + } +} + +func TestComputeKeyDescriptorBadVersion(t *testing.T) { + _, err := ComputeKeyDescriptor(fakeValidPolicyKey, 0) + if err == nil { + t.Error("computing key descriptor with bad version should fail") + } +} + // Run our test cases for passphrase hashing func TestPassphraseHashing(t *testing.T) { for i, testCase := range hashTestCases { diff --git a/metadata/checks.go b/metadata/checks.go index 4fe4531..84fd208 100644 --- a/metadata/checks.go +++ b/metadata/checks.go @@ -119,7 +119,7 @@ func (p *ProtectorData) CheckValidity() error { if err := p.WrappedKey.CheckValidity(); err != nil { return errors.Wrap(err, "wrapped protector key") } - if err := util.CheckValidLength(DescriptorLen, len(p.ProtectorDescriptor)); err != nil { + if err := util.CheckValidLength(ProtectorDescriptorLen, len(p.ProtectorDescriptor)); err != nil { return errors.Wrap(err, "protector descriptor") } @@ -138,7 +138,17 @@ func (e *EncryptionOptions) CheckValidity() error { if err := e.Contents.CheckValidity(); err != nil { return errors.Wrap(err, "contents encryption mode") } - return errors.Wrap(e.Filenames.CheckValidity(), "filenames encryption mode") + if err := e.Filenames.CheckValidity(); err != nil { + return errors.Wrap(err, "filenames encryption mode") + } + // If PolicyVersion is unset, treat it as 1. + if e.PolicyVersion == 0 { + e.PolicyVersion = 1 + } + if e.PolicyVersion != 1 && e.PolicyVersion != 2 { + return errors.Errorf("policy version of %d is invalid", e.PolicyVersion) + } + return nil } // CheckValidity ensures the fields are valid and have the correct lengths. @@ -152,7 +162,7 @@ func (w *WrappedPolicyKey) CheckValidity() error { if err := util.CheckValidLength(PolicyKeyLen, len(w.WrappedKey.EncryptedKey)); err != nil { return errors.Wrap(err, "encrypted key") } - err := util.CheckValidLength(DescriptorLen, len(w.ProtectorDescriptor)) + err := util.CheckValidLength(ProtectorDescriptorLen, len(w.ProtectorDescriptor)) return errors.Wrap(err, "wrapping protector descriptor") } @@ -167,11 +177,26 @@ func (p *PolicyData) CheckValidity() error { return errors.Wrapf(err, "policy key slot %d", i) } } - if err := util.CheckValidLength(DescriptorLen, len(p.KeyDescriptor)); err != nil { + + if err := p.Options.CheckValidity(); err != nil { + return errors.Wrap(err, "policy options") + } + + var expectedLen int + switch p.Options.PolicyVersion { + case 1: + expectedLen = PolicyDescriptorLenV1 + case 2: + expectedLen = PolicyDescriptorLenV2 + default: + return errors.Errorf("policy version of %d is invalid", p.Options.PolicyVersion) + } + + if err := util.CheckValidLength(expectedLen, len(p.KeyDescriptor)); err != nil { return errors.Wrap(err, "policy key descriptor") } - return errors.Wrap(p.Options.CheckValidity(), "policy options") + return nil } // CheckValidity ensures the Config has all the necessary info for its Source. diff --git a/metadata/config_test.go b/metadata/config_test.go index 3c20c51..83c1eb0 100644 --- a/metadata/config_test.go +++ b/metadata/config_test.go @@ -48,7 +48,8 @@ var testConfigString = `{ "options": { "padding": "32", "contents": "AES_256_XTS", - "filenames": "AES_256_CTS" + "filenames": "AES_256_CTS", + "policy_version": "1" }, "use_fs_keyring_for_v1_policies": false } @@ -78,3 +79,41 @@ func TestRead(t *testing.T) { t.Errorf("did not match: %s", testConfig) } } + +// Makes sure we can parse a legacy config file that doesn't have the fields +// that were added later. +func TestOptionalFields(t *testing.T) { + contents := `{ + "source": "custom_passphrase", + "hash_costs": { + "time": "10", + "memory": "4096", + "parallelism": "8" + }, + "compatibility": "", + "options": { + "padding": "32", + "contents": "AES_256_XTS", + "filenames": "AES_256_CTS" + } + } + ` + buf := bytes.NewBufferString(contents) + cfg, err := ReadConfig(buf) + if err != nil { + t.Fatal(err) + } + if cfg.GetUseFsKeyringForV1Policies() { + t.Error("use_fs_keyring_for_v1_policies should be false, but was true") + } + if cfg.Options.PolicyVersion != 0 { + t.Errorf("policy version should be 0, but was %d", cfg.Options.PolicyVersion) + } + if err = cfg.CheckValidity(); err != nil { + t.Error(err) + } + // CheckValidity() should change an unset policy version to 1. + if cfg.Options.PolicyVersion != 1 { + t.Errorf("policy version should be 1 now, but was %d", cfg.Options.PolicyVersion) + } +} diff --git a/metadata/constants.go b/metadata/constants.go index 8855ae3..fa6b8a7 100644 --- a/metadata/constants.go +++ b/metadata/constants.go @@ -27,8 +27,12 @@ import ( // Lengths for our keys, buffers, and strings used in fscrypt. const ( - // DescriptorLen is the length of all Protector and Policy descriptors. - DescriptorLen = 2 * unix.FSCRYPT_KEY_DESCRIPTOR_SIZE + // Length of policy descriptor (in hex chars) for v1 encryption policies + PolicyDescriptorLenV1 = 2 * unix.FSCRYPT_KEY_DESCRIPTOR_SIZE + // Length of protector descriptor (in hex chars) + ProtectorDescriptorLen = PolicyDescriptorLenV1 + // Length of policy descriptor (in hex chars) for v2 encryption policies + PolicyDescriptorLenV2 = 2 * unix.FSCRYPT_KEY_IDENTIFIER_SIZE // We always use 256-bit keys internally (compared to 512-bit policy keys). InternalKeyLen = 32 IVLen = 16 @@ -40,11 +44,13 @@ const ( ) var ( - // DefaultOptions use the supported encryption modes and max padding. + // DefaultOptions use the supported encryption modes, max padding, and + // policy version 1. DefaultOptions = &EncryptionOptions{ - Padding: 32, - Contents: EncryptionOptions_AES_256_XTS, - Filenames: EncryptionOptions_AES_256_CTS, + Padding: 32, + Contents: EncryptionOptions_AES_256_XTS, + Filenames: EncryptionOptions_AES_256_CTS, + PolicyVersion: 1, } // DefaultSource is the source we use if none is specified. DefaultSource = SourceType_custom_passphrase diff --git a/metadata/metadata.pb.go b/metadata/metadata.pb.go index aa4eeff..e6067f9 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_ff272c42a9f0f3d3, []int{0} + return fileDescriptor_metadata_0a34c99c54153da9, []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_ff272c42a9f0f3d3, []int{3, 0} + return fileDescriptor_metadata_0a34c99c54153da9, []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_ff272c42a9f0f3d3, []int{0} + return fileDescriptor_metadata_0a34c99c54153da9, []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_ff272c42a9f0f3d3, []int{1} + return fileDescriptor_metadata_0a34c99c54153da9, []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_ff272c42a9f0f3d3, []int{2} + return fileDescriptor_metadata_0a34c99c54153da9, []int{2} } func (m *ProtectorData) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ProtectorData.Unmarshal(m, b) @@ -293,6 +293,7 @@ type EncryptionOptions struct { Padding int64 `protobuf:"varint,1,opt,name=padding,proto3" json:"padding,omitempty"` Contents EncryptionOptions_Mode `protobuf:"varint,2,opt,name=contents,proto3,enum=metadata.EncryptionOptions_Mode" json:"contents,omitempty"` Filenames EncryptionOptions_Mode `protobuf:"varint,3,opt,name=filenames,proto3,enum=metadata.EncryptionOptions_Mode" json:"filenames,omitempty"` + PolicyVersion int64 `protobuf:"varint,4,opt,name=policy_version,json=policyVersion,proto3" json:"policy_version,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -302,7 +303,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_ff272c42a9f0f3d3, []int{3} + return fileDescriptor_metadata_0a34c99c54153da9, []int{3} } func (m *EncryptionOptions) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_EncryptionOptions.Unmarshal(m, b) @@ -343,6 +344,13 @@ func (m *EncryptionOptions) GetFilenames() EncryptionOptions_Mode { return EncryptionOptions_default } +func (m *EncryptionOptions) GetPolicyVersion() int64 { + if m != nil { + return m.PolicyVersion + } + return 0 +} + type WrappedPolicyKey struct { ProtectorDescriptor string `protobuf:"bytes,1,opt,name=protector_descriptor,json=protectorDescriptor,proto3" json:"protector_descriptor,omitempty"` WrappedKey *WrappedKeyData `protobuf:"bytes,2,opt,name=wrapped_key,json=wrappedKey,proto3" json:"wrapped_key,omitempty"` @@ -355,7 +363,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_ff272c42a9f0f3d3, []int{4} + return fileDescriptor_metadata_0a34c99c54153da9, []int{4} } func (m *WrappedPolicyKey) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_WrappedPolicyKey.Unmarshal(m, b) @@ -403,7 +411,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_ff272c42a9f0f3d3, []int{5} + return fileDescriptor_metadata_0a34c99c54153da9, []int{5} } func (m *PolicyData) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PolicyData.Unmarshal(m, b) @@ -460,7 +468,7 @@ 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_ff272c42a9f0f3d3, []int{6} + return fileDescriptor_metadata_0a34c99c54153da9, []int{6} } func (m *Config) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Config.Unmarshal(m, b) @@ -527,52 +535,53 @@ func init() { proto.RegisterEnum("metadata.EncryptionOptions_Mode", EncryptionOptions_Mode_name, EncryptionOptions_Mode_value) } -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, +func init() { proto.RegisterFile("metadata/metadata.proto", fileDescriptor_metadata_0a34c99c54153da9) } + +var fileDescriptor_metadata_0a34c99c54153da9 = []byte{ + // 717 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0x5d, 0x6b, 0x13, 0x4d, + 0x14, 0xc7, 0x9f, 0xdd, 0xa4, 0x79, 0x39, 0x79, 0x79, 0xb6, 0xd3, 0x3e, 0x7d, 0x56, 0x05, 0x09, + 0xd1, 0x42, 0x91, 0x52, 0x49, 0xa4, 0xa2, 0x20, 0x42, 0x4d, 0x5b, 0xad, 0xa5, 0x58, 0x37, 0x21, + 0x2a, 0x08, 0xcb, 0x74, 0x77, 0x92, 0x0c, 0xd9, 0xdd, 0x59, 0x66, 0x26, 0x0d, 0x7b, 0xe7, 0x9d, + 0x57, 0x5e, 0xf9, 0x5d, 0xfc, 0x34, 0x7e, 0x18, 0x99, 0xd9, 0xcd, 0x5b, 0x0b, 0xa5, 0xf5, 0x66, + 0x39, 0xf3, 0x9f, 0x33, 0xe7, 0x9c, 0xf9, 0x9d, 0x39, 0x0b, 0xff, 0x87, 0x44, 0x62, 0x1f, 0x4b, + 0xfc, 0x74, 0x66, 0xec, 0xc5, 0x9c, 0x49, 0x86, 0x4a, 0xb3, 0x75, 0xf3, 0x2b, 0x54, 0xdf, 0x61, + 0x31, 0xa2, 0xd1, 0xb0, 0xc3, 0x84, 0x14, 0x08, 0x41, 0x5e, 0xd2, 0x90, 0xd8, 0x66, 0xc3, 0xd8, + 0xc9, 0x39, 0xda, 0x46, 0x5b, 0x50, 0x08, 0x49, 0xc8, 0x78, 0x62, 0xe7, 0xb4, 0x9a, 0xad, 0x50, + 0x03, 0x2a, 0x31, 0xe6, 0x38, 0x08, 0x48, 0x40, 0x45, 0x68, 0xe7, 0xf5, 0xe6, 0xb2, 0xd4, 0xfc, + 0x02, 0xf5, 0x4f, 0x1c, 0xc7, 0x31, 0xf1, 0x4f, 0x49, 0x72, 0x88, 0x25, 0x46, 0x75, 0x30, 0x4f, + 0xfa, 0xb6, 0xd1, 0x30, 0x76, 0xaa, 0x8e, 0x79, 0xd2, 0x47, 0x8f, 0xa0, 0x46, 0x22, 0x8f, 0x27, + 0xb1, 0x24, 0xbe, 0x3b, 0x26, 0x89, 0x4e, 0x5c, 0x75, 0xaa, 0x73, 0xf1, 0x94, 0x24, 0xaa, 0xa8, + 0x51, 0x88, 0x3d, 0x9d, 0xbe, 0xea, 0x68, 0xbb, 0xf9, 0xd3, 0x84, 0xda, 0x39, 0x67, 0x92, 0x78, + 0x92, 0x71, 0x1d, 0xba, 0x05, 0x9b, 0xf1, 0x4c, 0x70, 0x7d, 0x22, 0x3c, 0x4e, 0x63, 0xc9, 0xb8, + 0x4e, 0x56, 0x76, 0x36, 0xe6, 0x7b, 0x87, 0xf3, 0x2d, 0xb4, 0x0b, 0x05, 0xc1, 0x26, 0xdc, 0x4b, + 0xef, 0x5b, 0x6f, 0x6f, 0xee, 0xcd, 0x41, 0x75, 0xb5, 0xde, 0x4b, 0x62, 0xe2, 0x64, 0x3e, 0xaa, + 0x8c, 0x08, 0x87, 0x44, 0x97, 0x51, 0x76, 0xb4, 0x8d, 0x76, 0x61, 0xcd, 0x53, 0xe0, 0xf4, 0xed, + 0x2b, 0xed, 0xad, 0x45, 0x80, 0x65, 0xac, 0x4e, 0xea, 0xa4, 0x22, 0x08, 0x1c, 0x48, 0x7b, 0x2d, + 0xbd, 0x88, 0xb2, 0x91, 0x05, 0xb9, 0x09, 0xf5, 0xed, 0x82, 0xa6, 0xa7, 0x4c, 0xf4, 0x12, 0x2a, + 0xd3, 0x94, 0x9a, 0x26, 0x52, 0xd4, 0x91, 0xed, 0x45, 0xe4, 0x55, 0xa4, 0x0e, 0x4c, 0xe7, 0xeb, + 0xe6, 0x6f, 0x13, 0xd6, 0x8f, 0x52, 0x74, 0x94, 0x45, 0x1f, 0xf4, 0x57, 0x20, 0x1b, 0x8a, 0x31, + 0xf6, 0x7d, 0x1a, 0x0d, 0x35, 0x8c, 0x9c, 0x33, 0x5b, 0xa2, 0x57, 0x50, 0xf2, 0x58, 0x24, 0x49, + 0x24, 0x45, 0x86, 0xa0, 0xb1, 0xc8, 0x73, 0x2d, 0xd0, 0xde, 0x19, 0xf3, 0x89, 0x33, 0x3f, 0x81, + 0x5e, 0x43, 0x79, 0x40, 0x03, 0xa2, 0x40, 0x08, 0x4d, 0xe5, 0x36, 0xc7, 0x17, 0x47, 0xd0, 0x36, + 0xd4, 0x63, 0x16, 0x50, 0x2f, 0x71, 0x2f, 0x09, 0x17, 0x94, 0x45, 0xd9, 0x1b, 0xaa, 0xa5, 0x6a, + 0x3f, 0x15, 0x9b, 0xdf, 0x0d, 0xc8, 0xab, 0xa3, 0xa8, 0x02, 0x45, 0x9f, 0x0c, 0xf0, 0x24, 0x90, + 0xd6, 0x3f, 0xe8, 0x5f, 0xa8, 0x1c, 0x1c, 0x75, 0xdd, 0xf6, 0xfe, 0x73, 0xf7, 0x73, 0xaf, 0x6b, + 0x19, 0xcb, 0xc2, 0xdb, 0xce, 0x99, 0x65, 0x2e, 0x0b, 0x9d, 0x37, 0x1d, 0x2b, 0xb7, 0x22, 0xf4, + 0xba, 0x56, 0x7e, 0x26, 0xb4, 0xda, 0x2f, 0xb4, 0xc7, 0xda, 0x8a, 0xd0, 0xeb, 0x5a, 0x05, 0x54, + 0x85, 0xd2, 0x81, 0x4f, 0x71, 0x24, 0x27, 0xa1, 0x55, 0x6e, 0x7e, 0x33, 0xc0, 0xca, 0xe8, 0x9f, + 0xeb, 0x12, 0xd5, 0xeb, 0xfc, 0x8b, 0x77, 0x77, 0xa5, 0xc3, 0xe6, 0x1d, 0x3a, 0xfc, 0xcb, 0x00, + 0x48, 0x73, 0xeb, 0x47, 0xbf, 0x0d, 0xf5, 0x31, 0x49, 0xae, 0xa7, 0xad, 0x8d, 0x49, 0xb2, 0x94, + 0x70, 0x1f, 0x8a, 0x2c, 0x6d, 0x42, 0x96, 0xec, 0xc1, 0x0d, 0x7d, 0x72, 0x66, 0xbe, 0xe8, 0x3d, + 0x6c, 0xcc, 0xea, 0xcc, 0x1a, 0x35, 0x26, 0x89, 0x6a, 0x75, 0x6e, 0xa7, 0xd2, 0xbe, 0x7f, 0xad, + 0xde, 0x39, 0x13, 0x67, 0x7d, 0x7a, 0x45, 0x11, 0xcd, 0x1f, 0x26, 0x14, 0x3a, 0x2c, 0x1a, 0xd0, + 0xe1, 0xd2, 0xd8, 0x19, 0xb7, 0x18, 0xbb, 0x7d, 0x80, 0x11, 0x16, 0x23, 0x37, 0x9d, 0x33, 0xf3, + 0xc6, 0x39, 0x2b, 0x2b, 0xcf, 0xf4, 0x4f, 0xf6, 0x18, 0x6a, 0x1e, 0x0b, 0x63, 0x2c, 0xe9, 0x05, + 0x0d, 0xa8, 0x4c, 0xb2, 0xb1, 0x5d, 0x15, 0x97, 0xc1, 0xe4, 0xef, 0x00, 0xe6, 0x00, 0x1e, 0x4e, + 0x04, 0x71, 0x07, 0x42, 0x01, 0xe1, 0x34, 0x1a, 0xba, 0x03, 0xc6, 0xdd, 0xcb, 0x56, 0x8a, 0x89, + 0x12, 0xa1, 0x47, 0xbc, 0xe4, 0xdc, 0x9b, 0x08, 0x72, 0x2c, 0x4e, 0x53, 0x9f, 0x63, 0xc6, 0xfb, + 0xad, 0xf3, 0xcc, 0xe1, 0xc9, 0x47, 0x80, 0xc5, 0x65, 0x57, 0x9f, 0x36, 0x82, 0x7a, 0x8c, 0x43, + 0x37, 0xc6, 0x42, 0xc4, 0x23, 0x8e, 0x05, 0xb1, 0x0c, 0xf4, 0x1f, 0xac, 0x7b, 0x13, 0x21, 0xd9, + 0x8a, 0x6c, 0xaa, 0x73, 0x1c, 0x4f, 0x55, 0x15, 0x56, 0xee, 0xa2, 0xa0, 0xff, 0xee, 0xcf, 0xfe, + 0x04, 0x00, 0x00, 0xff, 0xff, 0xfc, 0x97, 0x5e, 0xdf, 0xf8, 0x05, 0x00, 0x00, } diff --git a/metadata/metadata.proto b/metadata/metadata.proto index e682212..81b3bf9 100644 --- a/metadata/metadata.proto +++ b/metadata/metadata.proto @@ -77,6 +77,8 @@ message EncryptionOptions { Mode contents = 2; Mode filenames = 3; + + int64 policy_version = 4; } message WrappedPolicyKey { diff --git a/metadata/policy.go b/metadata/policy.go index f9af44a..b95bf42 100644 --- a/metadata/policy.go +++ b/metadata/policy.go @@ -42,14 +42,14 @@ var ( ErrBadEncryptionOptions = util.SystemError("invalid encryption options provided") ) -// policyIoctl is a wrapper for the ioctl syscall. It passes the correct -// pointers and file descriptors to the IOCTL syscall. This function also takes -// some of the unclear errors returned by the syscall and translates then into -// more specific error strings. -func policyIoctl(file *os.File, request uintptr, policy *unix.FscryptPolicyV1) error { +// policyIoctl is a wrapper around the ioctls that get and set encryption +// policies: FS_IOC_GET_ENCRYPTION_POLICY, FS_IOC_GET_ENCRYPTION_POLICY_EX, and +// FS_IOC_SET_ENCRYPTION_POLICY. It translates the raw errno values into more +// descriptive errors. +func policyIoctl(file *os.File, request uintptr, arg unsafe.Pointer) error { // The returned errno value can sometimes give strange errors, so we // return encryption specific errors. - _, _, errno := unix.Syscall(unix.SYS_IOCTL, file.Fd(), request, uintptr(unsafe.Pointer(policy))) + _, _, errno := unix.Syscall(unix.SYS_IOCTL, file.Fd(), request, uintptr(arg)) switch errno { case 0: return nil @@ -61,7 +61,6 @@ func policyIoctl(file *os.File, request uintptr, policy *unix.FscryptPolicyV1) e // ENOENT was returned instead of ENODATA on some filesystems before v4.11. return ErrNotEncrypted case unix.EEXIST: - // EINVAL was returned instead of EEXIST on some filesystems before v4.11. return ErrEncrypted default: return errno @@ -75,6 +74,42 @@ var ( unix.FSCRYPT_POLICY_FLAGS_PAD_16, unix.FSCRYPT_POLICY_FLAGS_PAD_32} ) +// flagsToPadding returns the amount of padding specified in the policy flags. +func flagsToPadding(flags uint8) int64 { + paddingFlag := int64(flags & unix.FS_POLICY_FLAGS_PAD_MASK) + + // This lookup should always succeed + padding, ok := util.Lookup(paddingFlag, flagsArray, paddingArray) + if !ok { + log.Panicf("padding flag of %x not found", paddingFlag) + } + return padding +} + +func buildV1PolicyData(policy *unix.FscryptPolicyV1) *PolicyData { + return &PolicyData{ + KeyDescriptor: hex.EncodeToString(policy.Master_key_descriptor[:]), + Options: &EncryptionOptions{ + Padding: flagsToPadding(policy.Flags), + Contents: EncryptionOptions_Mode(policy.Contents_encryption_mode), + Filenames: EncryptionOptions_Mode(policy.Filenames_encryption_mode), + PolicyVersion: 1, + }, + } +} + +func buildV2PolicyData(policy *unix.FscryptPolicyV2) *PolicyData { + return &PolicyData{ + KeyDescriptor: hex.EncodeToString(policy.Master_key_identifier[:]), + Options: &EncryptionOptions{ + Padding: flagsToPadding(policy.Flags), + Contents: EncryptionOptions_Mode(policy.Contents_encryption_mode), + Filenames: EncryptionOptions_Mode(policy.Filenames_encryption_mode), + PolicyVersion: 2, + }, + } +} + // GetPolicy returns the Policy data for the given directory or file (includes // the KeyDescriptor and the encryption options). Returns an error if the // path is not encrypted or the policy couldn't be retrieved. @@ -85,28 +120,36 @@ func GetPolicy(path string) (*PolicyData, error) { } defer file.Close() - var policy unix.FscryptPolicyV1 - if err = policyIoctl(file, unix.FS_IOC_GET_ENCRYPTION_POLICY, &policy); err != nil { + // First try the new version of the ioctl. This works for both v1 and v2 policies. + var arg unix.FscryptGetPolicyExArg + arg.Size = uint64(unsafe.Sizeof(arg.Policy)) + policyPtr := util.Ptr(arg.Policy[:]) + err = policyIoctl(file, unix.FS_IOC_GET_ENCRYPTION_POLICY_EX, unsafe.Pointer(&arg)) + if err == ErrEncryptionNotSupported { + // Fall back to the old version of the ioctl. This works for v1 policies only. + err = policyIoctl(file, unix.FS_IOC_GET_ENCRYPTION_POLICY, policyPtr) + arg.Size = uint64(unsafe.Sizeof(unix.FscryptPolicyV1{})) + } + if err != nil { return nil, errors.Wrapf(err, "get encryption policy %s", path) } - - // Convert the padding flag into an amount of padding - paddingFlag := int64(policy.Flags & unix.FSCRYPT_POLICY_FLAGS_PAD_MASK) - - // This lookup should always succeed - padding, ok := util.Lookup(paddingFlag, flagsArray, paddingArray) - if !ok { - log.Panicf("padding flag of %x not found", paddingFlag) + switch arg.Policy[0] { // arg.policy.version + case unix.FSCRYPT_POLICY_V1: + if arg.Size != uint64(unsafe.Sizeof(unix.FscryptPolicyV1{})) { + // should never happen + return nil, errors.New("unexpected size for v1 policy") + } + return buildV1PolicyData((*unix.FscryptPolicyV1)(policyPtr)), nil + case unix.FSCRYPT_POLICY_V2: + if arg.Size != uint64(unsafe.Sizeof(unix.FscryptPolicyV2{})) { + // should never happen + return nil, errors.New("unexpected size for v2 policy") + } + return buildV2PolicyData((*unix.FscryptPolicyV2)(policyPtr)), nil + default: + return nil, errors.Errorf("unsupported encryption policy version [%d]", + arg.Policy[0]) } - - return &PolicyData{ - KeyDescriptor: hex.EncodeToString(policy.Master_key_descriptor[:]), - Options: &EncryptionOptions{ - Padding: padding, - Contents: EncryptionOptions_Mode(policy.Contents_encryption_mode), - Filenames: EncryptionOptions_Mode(policy.Filenames_encryption_mode), - }, - }, nil } // For improved performance, use the DIRECT_KEY flag when using ciphers that @@ -121,6 +164,52 @@ func shouldUseDirectKeyFlag(options *EncryptionOptions) bool { return options.Contents == EncryptionOptions_Adiantum } +func buildPolicyFlags(options *EncryptionOptions) uint8 { + // This lookup should always succeed (as policy is valid) + flags, ok := util.Lookup(options.Padding, paddingArray, flagsArray) + if !ok { + log.Panicf("padding of %d was not found", options.Padding) + } + if shouldUseDirectKeyFlag(options) { + flags |= unix.FSCRYPT_POLICY_FLAG_DIRECT_KEY + } + return uint8(flags) +} + +func setV1Policy(file *os.File, options *EncryptionOptions, descriptorBytes []byte) error { + policy := unix.FscryptPolicyV1{ + Version: unix.FSCRYPT_POLICY_V1, + Contents_encryption_mode: uint8(options.Contents), + Filenames_encryption_mode: uint8(options.Filenames), + Flags: uint8(buildPolicyFlags(options)), + } + + // The descriptor should always be the correct length (as policy is valid) + if len(descriptorBytes) != unix.FSCRYPT_KEY_DESCRIPTOR_SIZE { + log.Panic("wrong descriptor size for v1 policy") + } + copy(policy.Master_key_descriptor[:], descriptorBytes) + + return policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, unsafe.Pointer(&policy)) +} + +func setV2Policy(file *os.File, options *EncryptionOptions, descriptorBytes []byte) error { + policy := unix.FscryptPolicyV2{ + Version: unix.FSCRYPT_POLICY_V2, + Contents_encryption_mode: uint8(options.Contents), + Filenames_encryption_mode: uint8(options.Filenames), + Flags: uint8(buildPolicyFlags(options)), + } + + // The descriptor should always be the correct length (as policy is valid) + if len(descriptorBytes) != unix.FSCRYPT_KEY_IDENTIFIER_SIZE { + log.Panic("wrong descriptor size for v2 policy") + } + copy(policy.Master_key_identifier[:], descriptorBytes) + + return policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, unsafe.Pointer(&policy)) +} + // SetPolicy sets up the specified directory to be encrypted with the specified // policy. Returns an error if we cannot set the policy for any reason (not a // directory, invalid options or KeyDescriptor, etc). @@ -135,30 +224,21 @@ func SetPolicy(path string, data *PolicyData) error { return errors.Wrap(err, "invalid policy") } - // This lookup should always succeed (as policy is valid) - flags, ok := util.Lookup(data.Options.Padding, paddingArray, flagsArray) - if !ok { - log.Panicf("padding of %d was not found", data.Options.Padding) - } - descriptorBytes, err := hex.DecodeString(data.KeyDescriptor) if err != nil { - return errors.New("invalid descriptor: " + data.KeyDescriptor) - } - - if shouldUseDirectKeyFlag(data.Options) { - flags |= unix.FSCRYPT_POLICY_FLAG_DIRECT_KEY + return errors.New("invalid key descriptor: " + data.KeyDescriptor) } - policy := unix.FscryptPolicyV1{ - Version: unix.FSCRYPT_POLICY_V1, - Contents_encryption_mode: uint8(data.Options.Contents), - Filenames_encryption_mode: uint8(data.Options.Filenames), - Flags: uint8(flags), + switch data.Options.PolicyVersion { + case 1: + err = setV1Policy(file, data.Options, descriptorBytes) + case 2: + err = setV2Policy(file, data.Options, descriptorBytes) + default: + err = errors.Errorf("policy version of %d is invalid", data.Options.PolicyVersion) } - copy(policy.Master_key_descriptor[:], descriptorBytes) - if err = policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, &policy); err == unix.EINVAL { + if err == unix.EINVAL { // Before kernel v4.11, many different errors all caused unix.EINVAL to be returned. // We try to disambiguate this error here. This disambiguation will not always give // the correct error due to a potential race condition on path. @@ -195,7 +275,7 @@ func CheckSupport(path string) error { Flags: math.MaxUint8, } - err = policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, &badPolicy) + err = policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, unsafe.Pointer(&badPolicy)) switch err { case nil: log.Panicf(`FS_IOC_SET_ENCRYPTION_POLICY succeeded when it should have failed. diff --git a/metadata/policy_test.go b/metadata/policy_test.go index ad9dd80..3c0704a 100644 --- a/metadata/policy_test.go +++ b/metadata/policy_test.go @@ -26,17 +26,30 @@ import ( "testing" "github.com/golang/protobuf/proto" + "golang.org/x/sys/unix" "github.com/google/fscrypt/util" ) -const goodDescriptor = "0123456789abcdef" +const goodV1Descriptor = "0123456789abcdef" -var goodPolicy = &PolicyData{ - KeyDescriptor: goodDescriptor, +var goodV1Policy = &PolicyData{ + KeyDescriptor: goodV1Descriptor, Options: DefaultOptions, } +var goodV2EncryptionOptions = &EncryptionOptions{ + Padding: 32, + Contents: EncryptionOptions_AES_256_XTS, + Filenames: EncryptionOptions_AES_256_CTS, + PolicyVersion: 2, +} + +var goodV2Policy = &PolicyData{ + KeyDescriptor: "0123456789abcdef0123456789abcdef", + Options: goodV2EncryptionOptions, +} + // Creates a temporary directory for testing. func createTestDirectory(t *testing.T) (directory string, err error) { baseDirectory, err := util.TestRoot() @@ -83,7 +96,7 @@ func TestSetPolicyEmptyDirectory(t *testing.T) { } defer os.RemoveAll(directory) - if err = SetPolicy(directory, goodPolicy); err != nil { + if err = SetPolicy(directory, goodV1Policy); err != nil { t.Error(err) } } @@ -96,7 +109,7 @@ func TestSetPolicyNonemptyDirectory(t *testing.T) { } defer os.RemoveAll(directory) - if err = SetPolicy(directory, goodPolicy); err == nil { + if err = SetPolicy(directory, goodV1Policy); err == nil { t.Error("should have failed to set policy on a nonempty directory") } } @@ -109,7 +122,7 @@ func TestSetPolicyFile(t *testing.T) { } defer os.RemoveAll(directory) - if err = SetPolicy(file, goodPolicy); err == nil { + if err = SetPolicy(file, goodV1Policy); err == nil { t.Error("should have failed to set policy on a file") } } @@ -141,15 +154,15 @@ func TestGetPolicyEmptyDirectory(t *testing.T) { defer os.RemoveAll(directory) var actualPolicy *PolicyData - if err = SetPolicy(directory, goodPolicy); err != nil { + if err = SetPolicy(directory, goodV1Policy); err != nil { t.Fatal(err) } if actualPolicy, err = GetPolicy(directory); err != nil { t.Fatal(err) } - if !proto.Equal(actualPolicy, goodPolicy) { - t.Errorf("policy %+v does not equal expected policy %+v", actualPolicy, goodPolicy) + if !proto.Equal(actualPolicy, goodV1Policy) { + t.Errorf("policy %+v does not equal expected policy %+v", actualPolicy, goodV1Policy) } } @@ -165,3 +178,35 @@ func TestGetPolicyUnencrypted(t *testing.T) { t.Error("should have failed to set policy on a file") } } + +func requireV2PolicySupport(t *testing.T, directory string) { + file, err := os.Open(directory) + if err != nil { + t.Fatal(err) + } + defer file.Close() + + err = policyIoctl(file, unix.FS_IOC_GET_ENCRYPTION_POLICY_EX, nil) + if err == ErrEncryptionNotSupported { + t.Skip("No support for v2 encryption policies, skipping test") + } +} + +// Tests that a non-root user cannot set a v2 encryption policy unless the key +// has been added. +func TestSetV2PolicyNoKey(t *testing.T) { + if util.IsUserRoot() { + t.Skip("This test cannot be run as root") + } + directory, err := createTestDirectory(t) + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(directory) + requireV2PolicySupport(t, directory) + + err = SetPolicy(directory, goodV2Policy) + if err == nil { + t.Error("shouldn't have been able to set v2 policy without key added") + } +} -- cgit v1.2.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(-) 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.2.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(-) 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.2.3 From fe2939cc7e50f4c6025253efdf7380c04fac9ae1 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 15 Dec 2019 19:31:39 -0800 Subject: README.md: document new settings and troubleshooting key access Document the new /etc/fscrypt.conf settings for the filesystem keyring and v2 encryption policies, and add a new subsection for troubleshooting key access problems. --- README.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4130962..b815807 100644 --- a/README.md +++ b/README.md @@ -194,8 +194,10 @@ that looks like the following: "options": { "padding": "32", "contents": "AES_256_XTS", - "filenames": "AES_256_CTS" - } + "filenames": "AES_256_CTS", + "policy_version": "1" + }, + "use_fs_keyring_for_v1_policies": false } ``` @@ -237,6 +239,25 @@ The fields are: documentation](https://www.kernel.org/doc/html/latest/filesystems/fscrypt.html#encryption-modes-and-usage) for more details about the supported algorithms. + * "policy\_version" is the version of encryption policy to use. + The choices are "1" and "2". Directories created with policy + version "2" are only usable on kernel v5.4 or later, but are + preferable to version "1" if you don't mind this restriction. + +* "use\_fs\_keyring\_for\_v1\_policies" specifies whether to add keys + for v1 encryption policies to the filesystem keyring, rather than to + user keyrings. This can solve [issues with processes being unable + to access encrypted files](#cant-log-in-with-ssh-even-when-users-encrypted-home-directory-is-unlocked). + However, it requires kernel v5.4 or later, and it makes unlocking + and locking encrypted directories require root. + + The purpose of this setting is to allow people to take advantage of + some of the improvements in Linux v5.4 on encrypted directories that + are also compatible with older kernels. If you don't need + compatibility with older kernels, it's better to not use this + setting and instead (re-)create your encrypted directories with + `"policy_version": "2"`. + ### Setting up the PAM module Note that to make use of the installed PAM module, your @@ -716,6 +737,37 @@ shred -u file However, `shred` isn't guaranteed to be effective on all filesystems and storage devices. +#### Can't log in with ssh even when user's encrypted home directory is unlocked + +This is caused by a limitation in the original design of Linux +filesystem encryption which made it difficult to ensure that all +processes can access unlocked encrypted files. This issue can also +manifest in other ways such as Docker containers being unable to +access encrypted files, or NetworkManager being unable to access +certificates if they are located in an encrypted directory. + +If you are using kernel v5.4 or later, you can fix this by setting the +following in `/etc/fscrypt.conf`: + + "use_fs_keyring_for_v1_policies": true + +However, this makes manually unlocking and locking encrypted +directories start to require root. (The PAM module will still work.) +E.g., you'll need to run `sudo fscrypt unlock`, not `fscrypt unlock`. + +Alternatively, you can upgrade your encrypted directories to use v2 +encryption policies by setting the following in the "options" section +of `/etc/fscrypt.conf`: + + "policy_version": "2" + +... and then for each of your encrypted directories, using `fscrypt +encrypt` to encrypt a new empty directory, copying your files into it, +and replacing the original directory with it. This will fix the key +access problems, while also keeping `fscrypt unlock` and `fscrypt +lock` usable by non-root users. This is the recommended solution if +you don't need to access your files on kernels older than v5.4. + ## Legal Copyright 2017 Google Inc. under the -- cgit v1.2.3