diff options
| author | Joe Richey joerichey@google.com <joerichey@google.com> | 2017-06-21 10:03:44 -0700 |
|---|---|---|
| committer | Joe Richey joerichey@google.com <joerichey@google.com> | 2017-06-28 15:15:15 -0700 |
| commit | 93415b198a3ef427c02893b8fdf036aa75ffe50f (patch) | |
| tree | 419be5fa11e9102597d3409800a3d7df4138b05e /actions/policy.go | |
| parent | 77b226a90ef70b77ca556830528c013a23b01e57 (diff) | |
actions: error handling and API changed
This commit changes the error handling for the actions package to use
the error handling library github.com/pkg/errors. This means replacing
"errors" with "github.com/pkg/errors", reworking some of the error
values, and wrapping some errors with additional context.
This commit also changes the Protector/Policy API, moving most of the
package functionality into Protector or Policy methods. These types are
now "locked" when they are queried from the filesystem, and Unlock()
must be used to get their corresponding keys. Note that only certain
operations will require unlocking the keys. Certain unnecessary
functions and methods are also removed.
This CL also fixes two bugs reported by Tyler Hicks in CreateConfigFile.
CPU time is used instead of wall time, and kiB is used instead of kB.
Change-Id: I88f45659e9fe4938d148843e3289e7b6d5b698d8
Diffstat (limited to 'actions/policy.go')
| -rw-r--r-- | actions/policy.go | 334 |
1 files changed, 173 insertions, 161 deletions
diff --git a/actions/policy.go b/actions/policy.go index 678bcdc..ff61e8b 100644 --- a/actions/policy.go +++ b/actions/policy.go @@ -20,10 +20,12 @@ package actions import ( - "errors" + "fmt" "log" "reflect" + "github.com/pkg/errors" + "fscrypt/crypto" "fscrypt/filesystem" "fscrypt/metadata" @@ -32,74 +34,14 @@ import ( // Errors relating to Policies var ( - ErrMissingPolicyMetadata = util.SystemError("policy for directory has no filesystem metadata; metadata may be corrupted") - ErrPolicyMetadataMismatch = util.SystemError("policy metadata is inconsistent; metadata may be corrupted") - ErrPathWrongFilesystem = errors.New("provided path for policy is on the wrong filesystem") + 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("this policy is already protected by this protector") - ErrNotProtected = errors.New("this policy is not protected by this protector") + ErrAlreadyProtected = errors.New("policy already protected by protector") + ErrNotProtected = errors.New("policy not protected by protector") ) -// PolicyDescriptorForPath returns the policy descriptor for a file on the -// filesystem. An error is returned if the metadata is inconsistent, the path is -// for the wrong filesystem, or the path is not encrypted. -func PolicyDescriptorForPath(ctx *Context, path string) (string, error) { - if err := ctx.checkContext(); err != nil { - return "", err - } - // Policies and their paths will always be on the same filesystem - if pathMount, err := filesystem.FindMount(path); err != nil { - return "", err - } else if pathMount != ctx.Mount { - return "", ErrPathWrongFilesystem - } - log.Printf("%q is on mountpoint %q", path, ctx.Mount.Path) - - // 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) - if err != nil { - return "", err - } - descriptor := pathData.KeyDescriptor - log.Printf("found policy %s for %q", descriptor, path) - - mountData, err := ctx.Mount.GetPolicy(descriptor) - if err != nil { - log.Printf("getting metadata for policy %s: %v", descriptor, err) - return "", ErrMissingPolicyMetadata - } - 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 "", ErrPolicyMetadataMismatch - } - log.Print("data from filesystem and path agree") - - return descriptor, nil -} - -// IsPolicyUnlocked returns a boolean indicating if the corresponding policy for -// this filesystem has its key in the keyring, meaning files and directories -// using this policy can be read and written. -func IsPolicyUnlocked(ctx *Context, policyDescriptor string) bool { - _, err := crypto.FindPolicyKey(policyDescriptor, getService(ctx)) - return err == nil -} - -// LockPolicy removes a policy key from the keyring. This means after unmounting -// and remounting the directory, files and directories using this policy will be -// inaccessible. -func LockPolicy(ctx *Context, policyDescriptor string) error { - if err := ctx.checkContext(); err != nil { - return err - } - return crypto.RemovePolicyKey(policyDescriptor, getService(ctx)) -} - // 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. @@ -112,46 +54,21 @@ func PurgeAllPolicies(ctx *Context) error { return err } - for _, policy := range policies { - if err := LockPolicy(ctx, policy); err == crypto.ErrKeyringDelete { - // This means a policy key was present but we could not - // delete it. The other errors just indicate that the - // policy key was not present. + for _, policyDescriptor := range policies { + service := ctx.getService() + err = crypto.RemovePolicyKey(policyDescriptor, service) + + switch errors.Cause(err) { + case nil, crypto.ErrKeyringSearch: + // We don't care if the key has already been removed + break + default: return err } } return nil } -// getService returns the keyring service for this context. We use the presence -// of the LegacyConfig flag to determine if we should use the legacy services -// (which are necessary for kernels before v4.8). -func getService(ctx *Context) string { - if ctx.Config.HasCompatibilityOption(LegacyConfig) { - switch ctx.Mount.Filesystem { - case "ext4", "f2fs": - return ctx.Mount.Filesystem + ":" - } - } - return crypto.DefaultService -} - -// getPolicyData creates a partially constructed policy by looking up -// the descriptor on the appropriate filesystem. The policy returned will not -// have its key initialized. -func getPolicyData(ctx *Context, descriptor string) (*Policy, error) { - if err := ctx.checkContext(); err != nil { - return nil, err - } - data, err := ctx.Mount.GetPolicy(descriptor) - if err != nil { - return nil, err - } - log.Printf("got data for %s from %q", descriptor, ctx.Mount.Path) - - return &Policy{Context: ctx, data: data}, nil -} - // Policy represents an unlocked policy, so it contains the PolicyData as well // as the actual protector key. These unlocked Polices can then be applied to a // directory, or have their key material inserted into the keyring (which will @@ -161,6 +78,7 @@ type Policy struct { Context *Context data *metadata.PolicyData key *crypto.Key + created bool } // CreatePolicy creates a Policy protected by given Protector and stores the @@ -182,51 +100,127 @@ func CreatePolicy(ctx *Context, protector *Protector) (*Policy, error) { Options: ctx.Config.Options, KeyDescriptor: crypto.ComputeDescriptor(key), }, - key: key, + key: key, + created: true, } if err = policy.AddProtector(protector); err != nil { - policy.Wipe() + policy.Lock() return nil, err } return policy, nil } -// GetPolicy retrieves a policy with a specific descriptor. As a Protector is -// needed to unlock the policy, callbacks to select the policy and get the key -// are needed. This method will retry the keyFn as necessary to get the correct -// key for the selected protector. -func GetPolicy(ctx *Context, descriptor string, optionFn OptionFunc, keyFn KeyFunc) (*Policy, error) { - policy, err := getPolicyData(ctx, descriptor) +// GetPolicy retrieves a locked policy with a specific descriptor. The Policy is +// still locked in this case, so it must be unlocked before using certain +// methods. +func GetPolicy(ctx *Context, descriptor string) (*Policy, error) { + if err := ctx.checkContext(); err != nil { + return nil, err + } + data, err := ctx.Mount.GetPolicy(descriptor) + if err != nil { + return nil, err + } + log.Printf("got data for %s from %q", descriptor, ctx.Mount.Path) + + return &Policy{Context: ctx, data: data}, nil +} + +// GetPolicyFromPath returns the locked policy descriptor for a file on the +// filesystem. The Policy is still locked in this case, so it must be unlocked +// before using certain methods. An error is returned if the metadata is +// inconsistent or the path is not encrypted. +func GetPolicyFromPath(ctx *Context, path string) (*Policy, error) { + if err := ctx.checkContext(); err != nil { + return nil, err + } + + // 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) if err != nil { return nil, err } - return policy, policy.unwrapPolicy(optionFn, keyFn) + descriptor := pathData.KeyDescriptor + log.Printf("found policy %s for %q", descriptor, path) + + mountData, err := ctx.Mount.GetPolicy(descriptor) + if err != nil { + log.Printf("getting policy metadata: %v", err) + return nil, errors.Wrap(ErrMissingPolicyMetadata, path) + } + 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) + } + log.Print("data from filesystem and path agree") + + return &Policy{Context: ctx, data: mountData}, nil } -// listOptions creates a slice of ProtectorOptions for the protectors protecting -// this policy. -func (policy *Policy) listOptions() []*ProtectorOption { +// ProtectorOptions creates a slice of ProtectorOptions for the protectors +// protecting this policy. +func (policy *Policy) ProtectorOptions() []*ProtectorOption { options := make([]*ProtectorOption, len(policy.data.WrappedPolicyKeys)) for i, wrappedPolicyKey := range policy.data.WrappedPolicyKeys { - options[i] = policy.Context.GetProtectorOption(wrappedPolicyKey.ProtectorDescriptor) + options[i] = policy.Context.getProtectorOption(wrappedPolicyKey.ProtectorDescriptor) } return options } -// unwrapPolicy initializes the policy key using the provided callbacks. -func (policy *Policy) unwrapPolicy(optionFn OptionFunc, keyFn KeyFunc) error { - // Create a list of the ProtectorOptions and a list of the wrapped keys. - options := policy.listOptions() - wrappedKeys := make([]*metadata.WrappedKeyData, len(policy.data.WrappedPolicyKeys)) - +// ProtectorDescriptors creates a slice of the Protector descriptors for the +// protectors protecting this policy. +func (policy *Policy) ProtectorDescriptors() []string { + descriptors := make([]string, len(policy.data.WrappedPolicyKeys)) for i, wrappedPolicyKey := range policy.data.WrappedPolicyKeys { - wrappedKeys[i] = wrappedPolicyKey.WrappedKey + descriptors[i] = wrappedPolicyKey.ProtectorDescriptor } + return descriptors +} + +// Descriptor returns the key descriptor for this policy. +func (policy *Policy) Descriptor() string { + return policy.data.KeyDescriptor +} + +// Destroy removes a policy from the filesystem. The internal key should still +// be wiped with Lock(). +func (policy *Policy) Destroy() error { + return policy.Context.Mount.RemovePolicy(policy.Descriptor()) +} + +// Revert destroys a policy if it was created, but does nothing if it was just +// queried from the filesystem. +func (policy *Policy) Revert() error { + if !policy.created { + return nil + } + return policy.Destroy() +} + +func (policy *Policy) String() string { + return fmt.Sprintf("Policy: %s\nMountpoint: %s\nOptions: %v\nProtectors:%+v", + policy.Descriptor(), policy.Context.Mount, policy.data.Options, + policy.ProtectorDescriptors()) +} + +// Unlock unwraps the Policy's internal key. As a Protector is needed to unlock +// the Policy, callbacks to select the Policy and get the key are needed. This +// method will retry the keyFn as necessary to get the correct key for the +// selected protector. Does nothing if policy is already unlocked. +func (policy *Policy) Unlock(optionFn OptionFunc, keyFn KeyFunc) error { + if policy.key != nil { + return nil + } + options := policy.ProtectorOptions() // The OptionFunc indicates which option and wrapped key we should use. - idx, err := optionFn(policy.data.KeyDescriptor, options) + idx, err := optionFn(policy.Descriptor(), options) if err != nil { return err } @@ -235,52 +229,67 @@ func (policy *Policy) unwrapPolicy(optionFn OptionFunc, keyFn KeyFunc) error { return option.LoadError } - wrappedPolicyKey := wrappedKeys[idx] log.Printf("protector %s selected in callback", option.Descriptor()) - protectorKey, err := unwrapProtectorKey(option.ProtectorInfo, keyFn) if err != nil { return err } defer protectorKey.Wipe() - log.Printf("unwrapping policy %s with protector", policy.data.KeyDescriptor) + log.Printf("unwrapping policy %s with protector", policy.Descriptor()) + wrappedPolicyKey := policy.data.WrappedPolicyKeys[idx].WrappedKey policy.key, err = crypto.Unwrap(protectorKey, wrappedPolicyKey) return err } +// Lock wipes a Policy's internal Key. It should always be called after using a +// Policy. This is often done with a defer statement. There is no effect if +// called multiple times. +func (policy *Policy) Lock() error { + err := policy.key.Wipe() + policy.key = nil + return err +} + // 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 -// different filesystems, a link will be created between them. +// different filesystems, a link will be created between them. The policy and +// protector must both be unlocked. func (policy *Policy) AddProtector(protector *Protector) error { - _, err := policy.findWrappedKeyIndex(protector.data.ProtectorDescriptor) - if err == nil { + if _, ok := policy.findWrappedKeyIndex(protector.Descriptor()); ok { return ErrAlreadyProtected } + if policy.key == nil || protector.key == nil { + return ErrLocked + } // 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 { - err = policy.Context.Mount.AddLinkedProtector( - protector.data.ProtectorDescriptor, 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) if err != nil { return err } + } else { + log.Printf("policy and protector both on %q", policy.Context.Mount) } // Create the wrapped policy key - wrappedPolicyKey := &metadata.WrappedPolicyKey{ - ProtectorDescriptor: protector.data.ProtectorDescriptor, - } - if wrappedPolicyKey.WrappedKey, err = crypto.Wrap(protector.key, policy.key); err != nil { + wrappedKey, err := crypto.Wrap(protector.key, policy.key) + if err != nil { return err } // Append the wrapped key to the data - policy.addKey(wrappedPolicyKey) + policy.addKey(&metadata.WrappedPolicyKey{ + ProtectorDescriptor: protector.Descriptor(), + WrappedKey: wrappedKey, + }) - if err = policy.commitData(); err != nil { + if err := policy.commitData(); err != nil { // revert the addition on failure policy.removeKey(len(policy.data.WrappedPolicyKeys) - 1) return err @@ -290,13 +299,13 @@ 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 w do not attempt to remove -// any links (for the case where the protector and policy are on different -// filesystems). This is because one protector may protect many polices. -func (policy *Policy) RemoveProtector(protectorDescriptor string) error { - idx, err := policy.findWrappedKeyIndex(protectorDescriptor) - if err != nil { - return err +// is returned, no data has been changed. Note that no protector links are +// 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()) + if !ok { + return ErrNotProtected } if len(policy.data.WrappedPolicyKeys) == 1 { @@ -306,7 +315,7 @@ func (policy *Policy) RemoveProtector(protectorDescriptor string) error { // Remove the wrapped key from the data toRemove := policy.removeKey(idx) - if err = policy.commitData(); err != nil { + if err := policy.commitData(); err != nil { // revert the removal on failure (order is irrelevant) policy.addKey(toRemove) return err @@ -327,40 +336,43 @@ func (policy *Policy) Apply(path string) error { return metadata.SetPolicy(path, policy.data) } -// Unlock provisions the Policy key into the kernel keyring. This allows reading -// and writing of files encrypted with this directory. -func (policy *Policy) Unlock() error { - return crypto.InsertPolicyKey(policy.key, policy.data.KeyDescriptor, getService(policy.Context)) +// 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 := crypto.FindPolicyKey(policy.Descriptor(), policy.Context.getService()) + return err == nil } -// Wipe wipes a Policy's internal Key. It should always be called after using a -// Policy. This is often done with a defer statement. -func (policy *Policy) Wipe() error { - return policy.key.Wipe() +// Provision inserts the Policy key into the kernel keyring. This allows reading +// and writing of files encrypted with this directory. Requires unlocked Policy. +func (policy *Policy) Provision() error { + if policy.key == nil { + return ErrLocked + } + return crypto.InsertPolicyKey(policy.key, policy.Descriptor(), policy.Context.getService()) } -// Destroy removes a policy from the filesystem. The internal key should still -// be wiped with Wipe(). -func (policy *Policy) Destroy() error { - return policy.Context.Mount.RemovePolicy(policy.data.KeyDescriptor) +// 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 crypto.RemovePolicyKey(policy.Descriptor(), policy.Context.getService()) } -// commitData writes the Policy's current data to the filesystem +// commitData writes the Policy's current data to the filesystem. func (policy *Policy) commitData() error { return policy.Context.Mount.AddPolicy(policy.data) } // findWrappedPolicyKey returns the index of the wrapped policy key -// corresponding to this policy and protector. An error is returned if no -// wrapped policy key corresponds to the specified protector. -func (policy *Policy) findWrappedKeyIndex(protectorDescriptor string) (int, error) { +// corresponding to this policy and protector. The returned bool is false if no +// wrapped policy key corresponds to the specified protector, true otherwise. +func (policy *Policy) findWrappedKeyIndex(protectorDescriptor string) (int, bool) { for idx, wrappedPolicyKey := range policy.data.WrappedPolicyKeys { if wrappedPolicyKey.ProtectorDescriptor == protectorDescriptor { - return idx, nil + return idx, true } } - - return 0, ErrNotProtected + return 0, false } // addKey adds the wrapped policy key to end of the wrapped key data. |