aboutsummaryrefslogtreecommitdiff
path: root/actions/policy.go
diff options
context:
space:
mode:
Diffstat (limited to 'actions/policy.go')
-rw-r--r--actions/policy.go318
1 files changed, 256 insertions, 62 deletions
diff --git a/actions/policy.go b/actions/policy.go
index cbdcb3a..d745f8b 100644
--- a/actions/policy.go
+++ b/actions/policy.go
@@ -1,5 +1,5 @@
/*
- * protector.go - functions for dealing with policies
+ * policy.go - functions for dealing with policies
*
* Copyright 2017 Google Inc.
* Author: Joe Richey (joerichey@google.com)
@@ -22,46 +22,147 @@ package actions
import (
"fmt"
"log"
+ "os"
+ "os/user"
"reflect"
"github.com/pkg/errors"
+ "google.golang.org/protobuf/proto"
"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"
)
-// Errors relating to Policies
-var (
- ErrMissingPolicyMetadata = util.SystemError("missing policy metadata for encrypted directory")
- ErrPolicyMetadataMismatch = util.SystemError("inconsistent metadata between filesystem and directory")
- ErrDifferentFilesystem = errors.New("policies may only protect files on the same filesystem")
- ErrOnlyProtector = errors.New("cannot remove the only protector for a policy")
- ErrAlreadyProtected = errors.New("policy already protected by protector")
- ErrNotProtected = errors.New("policy not protected by protector")
-)
+// ErrAccessDeniedPossiblyV2 indicates that a directory's encryption policy
+// couldn't be retrieved due to "permission denied", but it looks like it's due
+// to the directory using a v2 policy but the kernel not supporting it.
+type ErrAccessDeniedPossiblyV2 struct {
+ DirPath string
+}
+
+func (err *ErrAccessDeniedPossiblyV2) Error() string {
+ return fmt.Sprintf(`
+ failed to get encryption policy of %s: permission denied
+
+ This may be caused by the directory using a v2 encryption policy and the
+ current kernel not supporting it. If indeed the case, then this
+ directory can only be used on kernel v5.4 and later. You can create
+ directories accessible on older kernels by changing policy_version to 1
+ in %s.`,
+ err.DirPath, ConfigFileLocation)
+}
+
+// ErrAlreadyProtected indicates that a policy is already protected by the given
+// protector.
+type ErrAlreadyProtected struct {
+ Policy *Policy
+ Protector *Protector
+}
+
+func (err *ErrAlreadyProtected) Error() string {
+ return fmt.Sprintf("policy %s is already protected by protector %s",
+ err.Policy.Descriptor(), err.Protector.Descriptor())
+}
+
+// ErrDifferentFilesystem indicates that a policy can't be applied to a
+// directory on a different filesystem.
+type ErrDifferentFilesystem struct {
+ PolicyMount *filesystem.Mount
+ PathMount *filesystem.Mount
+}
+
+func (err *ErrDifferentFilesystem) Error() string {
+ return fmt.Sprintf(`cannot apply policy from filesystem %q to a
+ directory on filesystem %q. Policies may only protect files on the same
+ filesystem.`, err.PolicyMount.Path, err.PathMount.Path)
+}
+
+// ErrMissingPolicyMetadata indicates that a directory is encrypted but its
+// policy metadata cannot be found.
+type ErrMissingPolicyMetadata struct {
+ Mount *filesystem.Mount
+ DirPath string
+ Descriptor string
+}
+
+func (err *ErrMissingPolicyMetadata) Error() string {
+ return fmt.Sprintf(`filesystem %q does not contain the policy metadata
+ for %q. This directory has either been encrypted with another tool (such
+ as e4crypt), or the file %q has been deleted.`,
+ err.Mount.Path, err.DirPath,
+ err.Mount.PolicyPath(err.Descriptor))
+}
+
+// ErrNotProtected indicates that the given policy is not protected by the given
+// protector.
+type ErrNotProtected struct {
+ PolicyDescriptor string
+ ProtectorDescriptor string
+}
+
+func (err *ErrNotProtected) Error() string {
+ return fmt.Sprintf(`policy %s is not protected by protector %s`,
+ err.PolicyDescriptor, err.ProtectorDescriptor)
+}
+
+// ErrOnlyProtector indicates that the last protector can't be removed from a
+// policy.
+type ErrOnlyProtector struct {
+ Policy *Policy
+}
+
+func (err *ErrOnlyProtector) Error() string {
+ return fmt.Sprintf(`cannot remove the only protector from policy %s. A
+ policy must have at least one protector.`, err.Policy.Descriptor())
+}
+
+// ErrPolicyMetadataMismatch indicates that the policy metadata for an encrypted
+// directory is inconsistent with that directory.
+type ErrPolicyMetadataMismatch struct {
+ DirPath string
+ Mount *filesystem.Mount
+ PathData *metadata.PolicyData
+ MountData *metadata.PolicyData
+}
+
+func (err *ErrPolicyMetadataMismatch) Error() string {
+ return fmt.Sprintf(`inconsistent metadata between encrypted directory %q
+ and its corresponding metadata file %q.
+
+ Directory has descriptor:%s %s
+
+ Metadata file has descriptor:%s %s`,
+ err.DirPath, err.Mount.PolicyPath(err.PathData.KeyDescriptor),
+ err.PathData.KeyDescriptor, err.PathData.Options,
+ err.MountData.KeyDescriptor, err.MountData.Options)
+}
// 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
}
- policies, err := ctx.Mount.ListPolicies()
+ policies, err := ctx.Mount.ListPolicies(nil)
if err != nil {
return err
}
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
}
@@ -75,10 +176,12 @@ func PurgeAllPolicies(ctx *Context) error {
// allow encrypted files to be accessed). As with the key struct, a Policy
// should be wiped after use.
type Policy struct {
- Context *Context
- data *metadata.PolicyData
- key *crypto.Key
- created bool
+ Context *Context
+ data *metadata.PolicyData
+ key *crypto.Key
+ created bool
+ ownerIfCreating *user.User
+ newLinkedProtectors []string
}
// CreatePolicy creates a Policy protected by given Protector and stores the
@@ -94,16 +197,28 @@ 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,
}
+ policy.ownerIfCreating, err = getOwnerOfMetadataForProtector(protector)
+ if err != nil {
+ policy.Lock()
+ return nil, err
+ }
+
if err = policy.AddProtector(protector); err != nil {
policy.Lock()
return nil, err
@@ -119,7 +234,7 @@ func GetPolicy(ctx *Context, descriptor string) (*Policy, error) {
if err := ctx.checkContext(); err != nil {
return nil, err
}
- data, err := ctx.Mount.GetPolicy(descriptor)
+ data, err := ctx.Mount.GetPolicy(descriptor, ctx.TrustedUser)
if err != nil {
return nil, err
}
@@ -140,23 +255,35 @@ func GetPolicyFromPath(ctx *Context, path string) (*Policy, error) {
// We double check that the options agree for both the data we get from
// the path, and the data we get from the mountpoint.
pathData, err := metadata.GetPolicy(path)
+ err = ctx.Mount.EncryptionSupportError(err)
if err != nil {
+ // On kernels that don't support v2 encryption policies, trying
+ // to open a directory with a v2 policy simply gave EACCES. This
+ // is ambiguous with other errors, but try to detect this case
+ // and show a better error message.
+ if os.IsPermission(err) &&
+ filesystem.HaveReadAccessTo(path) &&
+ !keyring.IsFsKeyringSupported(ctx.Mount) {
+ return nil, &ErrAccessDeniedPossiblyV2{path}
+ }
return nil, err
}
descriptor := pathData.KeyDescriptor
log.Printf("found policy %s for %q", descriptor, path)
- mountData, err := ctx.Mount.GetPolicy(descriptor)
+ mountData, err := ctx.Mount.GetPolicy(descriptor, ctx.TrustedUser)
if err != nil {
log.Printf("getting policy metadata: %v", err)
- return nil, errors.Wrap(ErrMissingPolicyMetadata, path)
+ if _, ok := err.(*filesystem.ErrPolicyNotFound); ok {
+ return nil, &ErrMissingPolicyMetadata{ctx.Mount, path, descriptor}
+ }
+ return nil, err
}
log.Printf("found data for policy %s on %q", descriptor, ctx.Mount.Path)
- if !reflect.DeepEqual(pathData.Options, mountData.Options) {
- log.Printf("options from path: %+v", pathData.Options)
- log.Printf("options from mount: %+v", mountData.Options)
- return nil, errors.Wrapf(ErrPolicyMetadataMismatch, "policy %s", descriptor)
+ if !proto.Equal(pathData.Options, mountData.Options) ||
+ pathData.KeyDescriptor != mountData.KeyDescriptor {
+ return nil, &ErrPolicyMetadataMismatch{path, ctx.Mount, pathData, mountData}
}
log.Print("data from filesystem and path agree")
@@ -188,15 +315,23 @@ 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().
+// Destroy removes a policy from the filesystem. It also removes any new
+// protector links that were created for the policy. This does *not* wipe the
+// policy's internal key from memory; use Lock() to do that.
func (policy *Policy) Destroy() error {
+ for _, protectorDescriptor := range policy.newLinkedProtectors {
+ policy.Context.Mount.RemoveProtector(protectorDescriptor)
+ }
return policy.Context.Mount.RemovePolicy(policy.Descriptor())
}
@@ -260,7 +395,7 @@ func (policy *Policy) UnlockWithProtector(protector *Protector) error {
}
idx, ok := policy.findWrappedKeyIndex(protector.Descriptor())
if !ok {
- return ErrNotProtected
+ return &ErrNotProtected{policy.Descriptor(), protector.Descriptor()}
}
var err error
@@ -284,6 +419,25 @@ func (policy *Policy) UsesProtector(protector *Protector) bool {
return ok
}
+// getOwnerOfMetadataForProtector returns the User to whom the owner of any new
+// policies or protector links for the given protector should be set.
+//
+// This will return a non-nil value only when the protector is a login protector
+// and the process is running as root. In this scenario, root is setting up
+// encryption on the user's behalf, so we need to make new policies and
+// protector links owned by the user (rather than root) to allow them to be read
+// by the user, just like the login protector itself which is handled elsewhere.
+func getOwnerOfMetadataForProtector(protector *Protector) (*user.User, error) {
+ if protector.data.Source == metadata.SourceType_pam_passphrase && util.IsUserRoot() {
+ owner, err := util.UserFromUID(protector.data.Uid)
+ if err != nil {
+ return nil, err
+ }
+ return owner, nil
+ }
+ return nil, nil
+}
+
// AddProtector updates the data that is wrapping the Policy Key so that the
// provided Protector is now protecting the specified Policy. If an error is
// returned, no data has been changed. If the policy and protector are on
@@ -291,7 +445,7 @@ func (policy *Policy) UsesProtector(protector *Protector) bool {
// protector must both be unlocked.
func (policy *Policy) AddProtector(protector *Protector) error {
if policy.UsesProtector(protector) {
- return ErrAlreadyProtected
+ return &ErrAlreadyProtected{policy, protector}
}
if policy.key == nil || protector.key == nil {
return ErrLocked
@@ -299,13 +453,22 @@ func (policy *Policy) AddProtector(protector *Protector) error {
// If the protector is on a different filesystem, we need to add a link
// to it on the policy's filesystem.
- if policy.Context.Mount != protector.Context.Mount {
+ if !reflect.DeepEqual(policy.Context.Mount, protector.Context.Mount) {
log.Printf("policy on %s\n protector on %s\n", policy.Context.Mount, protector.Context.Mount)
- err := policy.Context.Mount.AddLinkedProtector(
- protector.Descriptor(), protector.Context.Mount)
+ ownerIfCreating, err := getOwnerOfMetadataForProtector(protector)
+ if err != nil {
+ return err
+ }
+ isNewLink, err := policy.Context.Mount.AddLinkedProtector(
+ protector.Descriptor(), protector.Context.Mount,
+ protector.Context.TrustedUser, ownerIfCreating)
if err != nil {
return err
}
+ if isNewLink {
+ policy.newLinkedProtectors = append(policy.newLinkedProtectors,
+ protector.Descriptor())
+ }
} else {
log.Printf("policy and protector both on %q", policy.Context.Mount)
}
@@ -331,18 +494,19 @@ func (policy *Policy) AddProtector(protector *Protector) error {
}
// RemoveProtector updates the data that is wrapping the Policy Key so that the
-// provided Protector is no longer protecting the specified Policy. If an error
-// is returned, no data has been changed. Note that no protector links are
+// protector with the given descriptor is no longer protecting the specified
+// Policy. If an error is returned, no data has been changed. Note that the
+// protector itself won't be removed, nor will a link to the protector be
// removed (in the case where the protector and policy are on different
-// filesystems). The policy and protector can be locked or unlocked.
-func (policy *Policy) RemoveProtector(protector *Protector) error {
- idx, ok := policy.findWrappedKeyIndex(protector.Descriptor())
+// filesystems). The policy can be locked or unlocked.
+func (policy *Policy) RemoveProtector(protectorDescriptor string) error {
+ idx, ok := policy.findWrappedKeyIndex(protectorDescriptor)
if !ok {
- return ErrNotProtected
+ return &ErrNotProtected{policy.Descriptor(), protectorDescriptor}
}
if len(policy.data.WrappedPolicyKeys) == 1 {
- return ErrOnlyProtector
+ return &ErrOnlyProtector{policy}
}
// Remove the wrapped key from the data
@@ -362,18 +526,26 @@ func (policy *Policy) RemoveProtector(protector *Protector) error {
func (policy *Policy) Apply(path string) error {
if pathMount, err := filesystem.FindMount(path); err != nil {
return err
- } else if pathMount != policy.Context.Mount {
- return ErrDifferentFilesystem
+ } else if !reflect.DeepEqual(pathMount, policy.Context.Mount) {
+ return &ErrDifferentFilesystem{policy.Context.Mount, pathMount}
}
- return metadata.SetPolicy(path, policy.data)
+ err := metadata.SetPolicy(path, policy.data)
+ return policy.Context.Mount.EncryptionSupportError(err)
}
-// 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
}
// Provision inserts the Policy key into the kernel keyring. This allows reading
@@ -382,18 +554,40 @@ 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. If the Policy key was
+// already removed, returns keyring.ErrKeyNotPresent.
+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 (deprecated), 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.
func (policy *Policy) commitData() error {
- return policy.Context.Mount.AddPolicy(policy.data)
+ return policy.Context.Mount.AddPolicy(policy.data, policy.ownerIfCreating)
}
// findWrappedPolicyKey returns the index of the wrapped policy key
@@ -413,7 +607,7 @@ func (policy *Policy) addKey(toAdd *metadata.WrappedPolicyKey) {
policy.data.WrappedPolicyKeys = append(policy.data.WrappedPolicyKeys, toAdd)
}
-// remove removes the wrapped policy key at the specified index. This
+// removeKey removes the wrapped policy key at the specified index. This
// does not preserve the order of the wrapped policy key array. If no index is
// specified the last key is removed.
func (policy *Policy) removeKey(index int) *metadata.WrappedPolicyKey {