aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Biggers <ebiggers@google.com>2019-12-15 19:31:39 -0800
committerEric Biggers <ebiggers@google.com>2020-01-05 10:02:13 -0800
commit462d166d5355d33a05271d24de4d52f30dd62f67 (patch)
tree9bf53558105694002d442e0d997a9bb2b95140e2
parent80654f23ebfd552277ed217a2c5e1d0bb1374189 (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.go8
-rw-r--r--actions/policy.go30
-rw-r--r--cmd/fscrypt/errors.go6
-rw-r--r--cmd/fscrypt/flags.go4
-rw-r--r--crypto/crypto.go4
-rw-r--r--crypto/crypto_test.go54
-rw-r--r--crypto/key.go56
-rw-r--r--keyring/keyring.go100
-rw-r--r--keyring/keyring_test.go95
-rw-r--r--keyring/user_keyring.go (renamed from security/keyring.go)89
-rw-r--r--pam/pam.go3
-rw-r--r--security/privileges.go8
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
}
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/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)