aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md91
-rw-r--r--actions/config.go4
-rw-r--r--actions/context.go19
-rw-r--r--actions/policy.go89
-rw-r--r--actions/protector.go6
-rw-r--r--cmd/fscrypt/commands.go195
-rw-r--r--cmd/fscrypt/errors.go24
-rw-r--r--cmd/fscrypt/flags.go43
-rw-r--r--cmd/fscrypt/fscrypt.go2
-rw-r--r--cmd/fscrypt/protector.go6
-rw-r--r--cmd/fscrypt/status.go18
-rw-r--r--crypto/crypto.go43
-rw-r--r--crypto/crypto_test.go81
-rw-r--r--crypto/key.go56
-rw-r--r--keyring/fs_keyring.go318
-rw-r--r--keyring/keyring.go155
-rw-r--r--keyring/keyring_test.go350
-rw-r--r--keyring/user_keyring.go (renamed from security/keyring.go)90
-rw-r--r--metadata/checks.go35
-rw-r--r--metadata/config_test.go44
-rw-r--r--metadata/constants.go18
-rw-r--r--metadata/metadata.pb.go142
-rw-r--r--metadata/metadata.proto3
-rw-r--r--metadata/policy.go170
-rw-r--r--metadata/policy_test.go63
-rw-r--r--pam/pam.go19
-rw-r--r--pam_fscrypt/pam_fscrypt.go86
-rw-r--r--security/privileges.go8
28 files changed, 1795 insertions, 383 deletions
diff --git a/README.md b/README.md
index 3a86723..b815807 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
@@ -193,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
}
```
@@ -236,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
@@ -278,8 +300,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
@@ -360,24 +383,23 @@ 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:
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' 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.
>>>>> fscrypt status /mnt/disk/dir1
"/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:
@@ -398,7 +420,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:
@@ -410,7 +432,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
```
@@ -434,7 +456,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:
@@ -470,7 +492,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:
@@ -566,7 +588,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:
@@ -582,7 +604,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:
@@ -715,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
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/context.go b/actions/context.go
index 5a56789..f07f225 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"
)
@@ -57,10 +58,13 @@ 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. It's also
+ // the user for whom the keys are claimed in the filesystem keyring when
+ // v2 policies are provisioned.
TargetUser *user.User
}
@@ -145,6 +149,15 @@ func (ctx *Context) getService() string {
return unix.FSCRYPT_KEY_DESC_PREFIX
}
+func (ctx *Context) getKeyringOptions() *keyring.Options {
+ return &keyring.Options{
+ Mount: ctx.Mount,
+ User: ctx.TargetUser,
+ Service: ctx.getService(),
+ UseFsKeyringForV1Policies: ctx.Config.GetUseFsKeyringForV1Policies(),
+ }
+}
+
// 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..41e108e 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"
)
@@ -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
@@ -56,12 +56,16 @@ 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(), false)
switch errors.Cause(err) {
- case nil, security.ErrKeySearch:
+ 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)
+ 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
}
@@ -94,11 +98,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,
@@ -188,17 +198,16 @@ 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
}
+// 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 {
@@ -374,11 +383,24 @@ func (policy *Policy) Apply(path string) error {
return metadata.SetPolicy(path, policy.data)
}
-// 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
+// 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
+}
+
+// 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 by all users and with 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
@@ -387,13 +409,34 @@ 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)
+// 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(allUsers bool) error {
+ return keyring.RemoveEncryptionKey(policy.Descriptor(),
+ policy.Context.getKeyringOptions(), allUsers)
+}
+
+// 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.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.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/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/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go
index a3bfef2..41009b0 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,47 @@ 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 {
+ 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.
+ 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 +191,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,28 +224,32 @@ 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 {
- policy.Deprovision()
+ policy.Deprovision(false)
policy.Revert()
}
}()
- // 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(false)
+ }
}
if err = policy.Apply(path); os.IsPermission(errors.Cause(err)) {
// EACCES at this point indicates ownership issues.
@@ -281,8 +336,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,
@@ -293,7 +348,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,9 +364,14 @@ 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())
+ if policy.IsProvisionedByTargetUser() {
+ log.Printf("policy %s is already provisioned by %v",
+ policy.Descriptor(), ctx.TargetUser.Username)
return newExitError(c, errors.Wrapf(ErrPolicyUnlocked, path))
}
@@ -328,6 +388,97 @@ 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.
+
+ 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
+ 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, allUsersFlag},
+ Action: lockAction,
+}
+
+func lockAction(c *cli.Context) error {
+ if c.NArg() != 1 {
+ return expectedArgsErr(c, 1, false)
+ }
+
+ targetUser, err := parseUserFlag()
+ 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)
+ }
+ // 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())
+ 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(allUsersFlag.Value); 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",
@@ -377,7 +528,7 @@ func purgeAction(c *cli.Context) error {
}
}
- targetUser, err := parseUserFlag(true)
+ targetUser, err := parseUserFlag()
if err != nil {
return newExitError(c, err)
}
@@ -386,6 +537,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 {
@@ -401,13 +555,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
}
@@ -527,7 +676,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 288e697..5239155 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"
)
@@ -56,12 +56,14 @@ 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")
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))
@@ -94,11 +96,20 @@ 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.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.ErrKeyAddedByOtherUsers:
+ return `Directory couldn't be fully locked because other user(s)
+ 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
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))
@@ -135,6 +146,13 @@ 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, 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 16a75dc..b7933c9 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/security"
"github.com/google/fscrypt/util"
)
@@ -117,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}
)
@@ -163,12 +162,22 @@ 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,
}
+ 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
@@ -283,24 +292,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()
- }
- if err != nil {
- return nil, err
- }
-
- if checkKeyring {
- _, err = security.UserKeyringID(targetUser, true)
+ return user.Lookup(userFlag.Value)
}
- return targetUser, err
+ return util.EffectiveUser()
}
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])
}
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
diff --git a/cmd/fscrypt/status.go b/cmd/fscrypt/status.go
index 375899b..bf11495 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, keyring.KeyPresentButOnlyOtherUsers:
+ 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/crypto/crypto.go b/crypto/crypto.go
index 8de8134..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"
@@ -167,7 +168,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
}
@@ -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
@@ -196,7 +223,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..6eb0b02 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
@@ -510,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/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/fs_keyring.go b/keyring/fs_keyring.go
new file mode 100644
index 0000000..42c1648
--- /dev/null
+++ b/keyring/fs_keyring.go
@@ -0,0 +1,318 @@
+/*
+ * 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 <string.h>
+*/
+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"
+ "github.com/google/fscrypt/security"
+ "github.com/google/fscrypt/util"
+)
+
+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
+ 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)
+ }
+ copy(spec.U[:], descriptorBytes)
+ 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 {
+
+ 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()))
+
+ savedPrivs, err := dropPrivsIfNeeded(user, &arg.Key_spec)
+ if err != nil {
+ return err
+ }
+ _, _, errno := unix.Syscall(unix.SYS_IOCTL, dir.Fd(),
+ unix.FS_IOC_ADD_ENCRYPTION_KEY, uintptr(argKey.UnsafePtr()))
+ restorePrivs(savedPrivs)
+
+ log.Printf("FS_IOC_ADD_ENCRYPTION_KEY(%q, %s, <raw>) = %v", mount.Path, descriptor, errno)
+ if errno != 0 {
+ return errors.Wrap(ErrKeyAdd, errno.Error())
+ }
+ if descriptor, err = validateKeyDescriptor(&arg.Key_spec, descriptor); err != nil {
+ fsRemoveEncryptionKey(descriptor, mount, user)
+ return err
+ }
+ return nil
+}
+
+// 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
+ }
+
+ 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(), uintptr(ioc), uintptr(unsafe.Pointer(&arg)))
+ restorePrivs(savedPrivs)
+
+ 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 {
+ 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.
+ if user != nil {
+ status, _ := fsGetEncryptionKeyStatus(descriptor, mount, user)
+ if status == KeyPresentButOnlyOtherUsers {
+ return ErrKeyAddedByOtherUsers
+ }
+ }
+ 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
+ }
+
+ 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 {
+ 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:
+ 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
+ default:
+ return KeyStatusUnknown,
+ errors.Wrapf(ErrKeySearch, "unknown key status (%d)", arg.Status)
+ }
+}
diff --git a/keyring/keyring.go b/keyring/keyring.go
new file mode 100644
index 0000000..5a75153
--- /dev/null
+++ b/keyring/keyring.go
@@ -0,0 +1,155 @@
+/*
+ * 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 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"
+ "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")
+ 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
+// 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 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
+}
+
+func shouldUseFsKeyring(descriptor string, options *Options) bool {
+ // 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
+// 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, allUsers bool) error {
+ if shouldUseFsKeyring(descriptor, options) {
+ user := options.User
+ if allUsers {
+ user = nil
+ }
+ return fsRemoveEncryptionKey(descriptor, options.Mount, user)
+ }
+ 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
+ KeyAbsentButFilesBusy
+ KeyPresent
+ KeyPresentButOnlyOtherUsers
+)
+
+func (status KeyStatus) String() string {
+ switch status {
+ case KeyStatusUnknown:
+ return "Unknown"
+ case KeyAbsent:
+ return "Absent"
+ case KeyAbsentButFilesBusy:
+ return "AbsentButFilesBusy"
+ case KeyPresent:
+ return "Present"
+ case KeyPresentButOnlyOtherUsers:
+ return "PresentButOnlyOtherUsers"
+ default:
+ return strconv.Itoa(int(status))
+ }
+}
+
+// GetEncryptionKeyStatus gets the status of an encryption policy key in a
+// 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
+ }
+ return KeyPresent, nil
+}
diff --git a/keyring/keyring_test.go b/keyring/keyring_test.go
new file mode 100644
index 0000000..8912556
--- /dev/null
+++ b/keyring/keyring_test.go
@@ -0,0 +1,350 @@
+/*
+ * 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 (
+ "os/user"
+ "strconv"
+ "testing"
+
+ "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"
+)
+
+// 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 (
+ defaultService = unix.FSCRYPT_KEY_DESC_PREFIX
+ testUser, _ = util.EffectiveUser()
+ 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,
+ 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)
+ }
+}
+
+// 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 and v2 encryption policies are 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.")
+ }
+}
+
+// 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, 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
+ if err := AddEncryptionKey(fakeValidPolicyKey, descriptor, options); err != nil {
+ t.Error(err)
+ }
+ assertKeyStatus(t, descriptor, options, KeyPresent)
+ if err := RemoveEncryptionKey(descriptor, options, false); err != nil {
+ t.Error(err)
+ }
+ assertKeyStatus(t, descriptor, options, KeyAbsent)
+ err := RemoveEncryptionKey(descriptor, options, false)
+ 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(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, false)
+ t.Error("AddEncryptionKey should fail with wrong-length key")
+ }
+ assertKeyStatus(t, descriptor, options, KeyAbsent)
+}
+
+func TestUserKeyringDefaultService(t *testing.T) {
+ options := &Options{
+ User: testUser,
+ Service: defaultService,
+ UseFsKeyringForV1Policies: false,
+ }
+ 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)
+}
+
+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, 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, false)
+ 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, false); 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, false)
+ 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, false)
+ 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, false)
+ 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, false)
+ t.Error("AddEncryptionKey should have failed with bad mount!")
+ }
+ if err := RemoveEncryptionKey(fakeV2Descriptor, options, false); 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!")
+ }
+}
+
+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/security/keyring.go b/keyring/user_keyring.go
index 3236775..71f519d 100644
--- a/security/keyring.go
+++ b/keyring/user_keyring.go
@@ -1,5 +1,6 @@
/*
- * keyring.go - Handles inserting/removing into 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)
@@ -17,62 +18,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 +93,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 +163,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/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 d184a87..83c1eb0 100644
--- a/metadata/config_test.go
+++ b/metadata/config_test.go
@@ -48,8 +48,10 @@ 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
}
`
@@ -77,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 bd3ee2a..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_fa046c95c3cd6aa1, []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_fa046c95c3cd6aa1, []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_fa046c95c3cd6aa1, []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_fa046c95c3cd6aa1, []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_fa046c95c3cd6aa1, []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_fa046c95c3cd6aa1, []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_fa046c95c3cd6aa1, []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_fa046c95c3cd6aa1, []int{5}
+ return fileDescriptor_metadata_0a34c99c54153da9, []int{5}
}
func (m *PolicyData) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PolicyData.Unmarshal(m, b)
@@ -446,20 +454,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_0a34c99c54153da9, []int{6}
}
func (m *Config) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Config.Unmarshal(m, b)
@@ -507,6 +516,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 +535,53 @@ 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_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 735181e..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 {
@@ -97,4 +99,5 @@ message Config {
HashingCosts hash_costs = 2;
string compatibility = 3;
EncryptionOptions options = 4;
+ bool use_fs_keyring_for_v1_policies = 5;
}
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")
+ }
+}
diff --git a/pam/pam.go b/pam/pam.go
index c48dd13..54a60e2 100644
--- a/pam/pam.go
+++ b/pam/pam.go
@@ -127,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 := security.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..a7582cc 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 {
@@ -134,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 {
@@ -144,11 +187,19 @@ 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())
+ log.Printf("policy %s provisioned by %v", policy.Descriptor(),
+ handle.PamUser.Username)
}
return nil
}
@@ -163,7 +214,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,17 +252,29 @@ 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())
+ if !policy.IsProvisionedByTargetUser() {
+ log.Printf("policy %s not provisioned by %v",
+ policy.Descriptor(), handle.PamUser.Username)
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(false)
+ 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())
+ log.Printf("policy %s deprovisioned by %v", policy.Descriptor(), handle.PamUser.Username)
}
return nil
}
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)