diff options
| author | Eric Biggers <ebiggers@google.com> | 2019-12-15 19:31:39 -0800 |
|---|---|---|
| committer | Eric Biggers <ebiggers@google.com> | 2020-01-05 10:02:13 -0800 |
| commit | 462d166d5355d33a05271d24de4d52f30dd62f67 (patch) | |
| tree | 9bf53558105694002d442e0d997a9bb2b95140e2 | |
| parent | 80654f23ebfd552277ed217a2c5e1d0bb1374189 (diff) | |
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.
| -rw-r--r-- | actions/context.go | 8 | ||||
| -rw-r--r-- | actions/policy.go | 30 | ||||
| -rw-r--r-- | cmd/fscrypt/errors.go | 6 | ||||
| -rw-r--r-- | cmd/fscrypt/flags.go | 4 | ||||
| -rw-r--r-- | crypto/crypto.go | 4 | ||||
| -rw-r--r-- | crypto/crypto_test.go | 54 | ||||
| -rw-r--r-- | crypto/key.go | 56 | ||||
| -rw-r--r-- | keyring/keyring.go | 100 | ||||
| -rw-r--r-- | keyring/keyring_test.go | 95 | ||||
| -rw-r--r-- | keyring/user_keyring.go (renamed from security/keyring.go) | 89 | ||||
| -rw-r--r-- | pam/pam.go | 3 | ||||
| -rw-r--r-- | security/privileges.go | 8 |
12 files changed, 304 insertions, 153 deletions
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/security/keyring.go b/keyring/user_keyring.go index 3236775..adac71a 100644 --- a/security/keyring.go +++ b/keyring/user_keyring.go @@ -1,5 +1,5 @@ /* - * keyring.go - Handles inserting/removing into user keyrings. + * user_keyring.go - Add/remove encryption policy keys to/from user keyrings * * Copyright 2017 Google Inc. * Author: Joe Richey (joerichey@google.com) @@ -17,62 +17,69 @@ * the License. */ -package security +package keyring import ( - "fmt" - "log" "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" -// 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) { +// 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() - keyringID, err := UserKeyringID(targetUser, false) + // Create our payload (containing an FscryptKey) + payload, err := crypto.NewBlankKey(int(unsafe.Sizeof(unix.FscryptKey{}))) if err != nil { - return 0, err + return err } + defer payload.Wipe() - keyID, err := unix.KeyctlSearch(keyringID, KeyType, description, 0) - log.Printf("KeyctlSearch(%d, %s, %s) = %d, %v", keyringID, KeyType, description, keyID, err) + // 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 0, errors.Wrap(ErrKeySearch, err.Error()) + return err } - return keyID, err + keyID, err := unix.AddKey(KeyType, description, payload.Data(), keyringID) + log.Printf("KeyctlAddKey(%s, %s, <data>, %d) = %d, %v", + KeyType, description, keyringID, keyID, err) + if err != nil { + return errors.Wrap(ErrKeyAdd, err.Error()) + } + return nil } -// RemoveKey tries to remove a policy key from the kernel keyring with the +// 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 RemoveKey(description string, targetUser *user.User) error { +func userRemoveKey(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) + keyID, err := userFindKey(description, targetUser) if err != nil { - return err + return ErrKeyNotPresent } // We use KEYCTL_INVALIDATE instead of KEYCTL_REVOKE because @@ -85,24 +92,24 @@ func RemoveKey(description string, targetUser *user.User) 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 { +// 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, true) + keyringID, err := UserKeyringID(targetUser, false) if err != nil { - return err + return 0, err } - keyID, err := unix.AddKey(KeyType, description, data, keyringID) - log.Printf("KeyctlAddKey(%s, %s, <data>, %d) = %d, %v", - KeyType, description, keyringID, keyID, 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 errors.Wrap(ErrKeyInsert, err.Error()) + return 0, errors.Wrap(ErrKeySearch, err.Error()) } - return nil + return keyID, err } // UserKeyringID returns the key id of the target user's user keyring. We also @@ -155,13 +162,13 @@ func userKeyringIDLookup(uid int) (keyringID int, err error) { // - 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() + ruid, euid, suid := security.GetUids() if ruid != uid || euid != uid { - if err = setUids(uid, uid, 0); err != nil { + if err = security.SetUids(uid, uid, 0); err != nil { return } defer func() { - resetErr := setUids(ruid, euid, suid) + resetErr := security.SetUids(ruid, euid, suid) if resetErr != nil { err = resetErr } @@ -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/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) |