diff options
| author | Joe Richey joerichey@google.com <joerichey@google.com> | 2017-05-23 18:59:39 -0700 |
|---|---|---|
| committer | Joe Richey joerichey@google.com <joerichey@google.com> | 2017-05-31 13:54:07 -0700 |
| commit | 9c6a5203c72c811c4354a7cdc8d3f705953db095 (patch) | |
| tree | 5d833d367ab12dc9303050515cb2a246168077ab /actions | |
| parent | 70ccdd078e71b36178acf87a88b6ebadf4011266 (diff) | |
actions: creating and unlocking policies
This commit adds in the Policy structure. This structure represents an
unlocked policy key and its associated data. Policies can add or remove
Protectors, apply encryption policies to filesystem directories, and
provision a key into the kernel keyring.
Change-Id: I089710223221e0ea60188d523703469e5d67ad0e
Diffstat (limited to 'actions')
| -rw-r--r-- | actions/policy.go | 341 | ||||
| -rw-r--r-- | actions/policy_test.go | 137 |
2 files changed, 478 insertions, 0 deletions
diff --git a/actions/policy.go b/actions/policy.go new file mode 100644 index 0000000..cc1a92c --- /dev/null +++ b/actions/policy.go @@ -0,0 +1,341 @@ +/* + * protector.go - functions for dealing with policies + * + * Copyright 2017 Google Inc. + * Author: Joe Richey (joerichey@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 actions + +import ( + "errors" + "log" + "reflect" + + "fscrypt/crypto" + "fscrypt/filesystem" + "fscrypt/metadata" + "fscrypt/util" +) + +// Errors relating to Policies +var ( + ErrBadPolicyMetadata = util.SystemError("policy metadata is inconsistent") + ErrPathWrongFilesystem = errors.New("provided path for policy is on the wrong filesystem") + 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") + ErrInvalidIndex = errors.New("policy callback returned an invalid index") +) + +// 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 +// allow encrypted files to be accessed). As with the key struct, a Policy +// should be wiped after use. +type Policy struct { + *Context + data *metadata.PolicyData + key *crypto.Key +} + +// NewPolicy creates a Policy protected by given Protector and stores the +// appropriate data on the filesystem. On error, no data is changed on the +// filesystem. +func (ctx *Context) NewPolicy(protector *Protector) (*Policy, error) { + if !ctx.Config.IsValid() { + return nil, ErrBadConfig + } + + // Randomly create the underlying policy key (and wipe if we fail) + key, err := crypto.NewRandomKey(metadata.PolicyKeyLen) + if err != nil { + return nil, err + } + + policy := &Policy{ + Context: ctx, + data: &metadata.PolicyData{ + Options: ctx.Config.Options, + KeyDescriptor: crypto.ComputeDescriptor(key), + }, + key: key, + } + + if err = policy.AddProtector(protector); err != nil { + policy.Wipe() + return nil, err + } + + return policy, nil +} + +// 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 (ctx *Context) getPolicyData(descriptor string) (*Policy, error) { + data, err := ctx.Mount.GetPolicy(descriptor) + if err != nil { + return nil, err + } + log.Printf("got data for %s from filesystem", descriptor) + + return &Policy{Context: ctx, data: data}, nil +} + +// GetPolicyFromDescriptor retrieves a policy with a specific descriptor. As a +// Protector is needed to unlock the policy, callbacks are necessary as well. +func (ctx *Context) GetPolicyFromDescriptor(descriptor string, c1 PolicyCallback, c2 KeyCallback) (*Policy, error) { + if !ctx.Config.IsValid() { + return nil, ErrBadConfig + } + + policy, err := ctx.getPolicyData(descriptor) + if err != nil { + return nil, err + } + + return policy, policy.unwrapPolicy(c1, c2) +} + +// GetPolicyFromPath returns the policy for a specific path on the same +// filesystem as the Context. As a Protector is needed to unlock the policy, +// callbacks are necessary as well. +func (ctx *Context) GetPolicyFromPath(path string, c1 PolicyCallback, c2 KeyCallback) (*Policy, error) { + if !ctx.Config.IsValid() { + return nil, ErrBadConfig + } + + // Policies and their paths will always be on the same filesystem + if pathMount, err := filesystem.FindMount(path); err != nil { + return nil, err + } else if pathMount != ctx.Mount { + return nil, ErrPathWrongFilesystem + } + log.Printf("using mountpoint %q for %q", ctx.Mount.Path, 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 nil, err + } + log.Printf("found policy %s for %s", pathData.KeyDescriptor, path) + + policy, err := ctx.getPolicyData(pathData.KeyDescriptor) + if err != nil { + return nil, err + } + + if !reflect.DeepEqual(pathData.Options, policy.data.Options) { + log.Printf("options from path: %+v", pathData.Options) + log.Printf("options from mountpoint: %+v", policy.data.Options) + return nil, ErrBadPolicyMetadata + } + log.Print("data from filesystem and directory agree") + + return policy, policy.unwrapPolicy(c1, c2) +} + +// unwrapPolicy initializes the policy key using the provided callbacks. +// The policyCallback +func (policy *Policy) unwrapPolicy(policyCallback PolicyCallback, keyCallback KeyCallback) error { + // Create a list of the ProtectorData structures and a corresponding + // list of the wrapped keys. + totalKeys := len(policy.data.WrappedPolicyKeys) + protectors := make([]ProtectorData, 0, totalKeys) + wrappedKeys := make([]*metadata.WrappedKeyData, 0, totalKeys) + + // This loop excludes protectors that we cannot get from the mount. + for _, wrappedPolicyKey := range policy.data.WrappedPolicyKeys { + protector, err := policy.Mount.GetEitherProtector(wrappedPolicyKey.ProtectorDescriptor) + if err != nil { + log.Print(err) + continue + } + protectors = append(protectors, protector) + wrappedKeys = append(wrappedKeys, wrappedPolicyKey.WrappedKey) + } + log.Printf("%d of our %d protectors are available", len(protectors), totalKeys) + + idx, err := policyCallback(policy.data.KeyDescriptor, protectors) + if err != nil { + return err + } + if idx < 0 || idx >= len(protectors) { + return ErrInvalidIndex + } + + protectorData := protectors[idx].(*metadata.ProtectorData) + wrappedPolicyKey := wrappedKeys[idx] + log.Printf("protector %s selected in callback", protectorData.ProtectorDescriptor) + + protectorKey, err := unwrapProtectorKey(protectorData, keyCallback) + if err != nil { + return err + } + defer protectorKey.Wipe() + + log.Printf("unwrapping policy %s with protector", policy.data.KeyDescriptor) + policy.key, err = crypto.Unwrap(protectorKey, wrappedPolicyKey) + 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. +func (policy *Policy) AddProtector(protector *Protector) error { + _, err := policy.findWrappedKeyIndex(protector) + if err == nil { + return ErrAlreadyProtected + } + + // If the protector is on a different filesystem, we need to add a link + // to it on the policy's filesystem. + if policy.Mount != protector.Mount { + err = policy.Mount.AddLinkedProtector( + protector.data.ProtectorDescriptor, protector.Mount) + if err != nil { + return err + } + } + + // Create the wrapped policy key + wrappedPolicyKey := &metadata.WrappedPolicyKey{ + ProtectorDescriptor: protector.data.ProtectorDescriptor, + } + + if wrappedPolicyKey.WrappedKey, err = crypto.Wrap(protector.key, policy.key); err != nil { + return err + } + + // Append the wrapped key to the data + policy.addKey(wrappedPolicyKey) + + if err = policy.commitData(); err != nil { + // revert the addition on failure + policy.removeKey(len(policy.data.WrappedPolicyKeys) - 1) + return err + } + return nil +} + +// 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(protector *Protector) error { + idx, err := policy.findWrappedKeyIndex(protector) + if err != nil { + return err + } + + if len(policy.data.WrappedPolicyKeys) == 1 { + return ErrOnlyProtector + } + + // Remove the wrapped key from the data + toRemove := policy.removeKey(idx) + + if err = policy.commitData(); err != nil { + // revert the removal on failure (order is irrelevant) + policy.addKey(toRemove) + return err + } + return nil +} + +// Apply sets the Policy on a specified directory. Currently we impose the +// additional constraint that policies and the directories they are applied to +// must reside on the same filesystem. +func (policy *Policy) Apply(path string) error { + if pathMount, err := filesystem.FindMount(path); err != nil { + return err + } else if pathMount != policy.Mount { + return ErrDifferentFilesystem + } + + 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 { + service := crypto.ServiceDefault + + // For legacy configurations, we may need non-standard services + if policy.Config.HasCompatibilityOption(LegacyConfig) { + switch policy.Mount.Filesystem { + case "ext4": + service = crypto.ServiceExt4 + case "f2fs": + service = crypto.ServiceF2FS + } + } + + return crypto.InsertPolicyKey(policy.key, policy.data.KeyDescriptor, service) +} + +// Wipe wipes a Policy's internal Key. +func (policy *Policy) Wipe() error { + return policy.key.Wipe() +} + +// Destroy removes a policy from the filesystem. The internal key should still +// be wiped with Wipe(). +func (policy *Policy) Destroy() error { + return policy.Mount.RemovePolicy(policy.data.KeyDescriptor) +} + +// commitData writes the Policy's current data to the filesystem +func (policy *Policy) commitData() error { + return policy.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(protector *Protector) (int, error) { + for idx, wrappedPolicyKey := range policy.data.WrappedPolicyKeys { + if wrappedPolicyKey.ProtectorDescriptor == protector.data.ProtectorDescriptor { + return idx, nil + } + } + + return 0, ErrNotProtected +} + +// addKey adds the wrapped policy key to end of the wrapped key data. +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 +// 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 { + lastIdx := len(policy.data.WrappedPolicyKeys) - 1 + toRemove := policy.data.WrappedPolicyKeys[index] + + // See https://github.com/golang/go/wiki/SliceTricks + policy.data.WrappedPolicyKeys[index] = policy.data.WrappedPolicyKeys[lastIdx] + policy.data.WrappedPolicyKeys[lastIdx] = nil + policy.data.WrappedPolicyKeys = policy.data.WrappedPolicyKeys[:lastIdx] + + return toRemove +} diff --git a/actions/policy_test.go b/actions/policy_test.go new file mode 100644 index 0000000..3a64e01 --- /dev/null +++ b/actions/policy_test.go @@ -0,0 +1,137 @@ +/* + * policy_test.go - tests for creating and modifying policies + * + * Copyright 2017 Google Inc. + * Author: Joe Richey (joerichey@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 actions + +import "testing" + +// Makes a context, protector, and policy +func makeAll() (ctx *Context, protector *Protector, policy *Policy, err error) { + ctx, err = makeContext() + if err != nil { + return + } + protector, err = ctx.NewProtector(testProtectorName, goodCallback) + if err != nil { + return + } + policy, err = ctx.NewPolicy(protector) + return +} + +// Cleans up a context, protector, and policy +func cleanupAll(protector *Protector, policy *Policy) { + if policy != nil { + policy.Wipe() + } + if protector != nil { + protector.Wipe() + } + cleaupContext() +} + +// Tests that we can make a policy/protector pair +func TestNewPolicy(t *testing.T) { + _, pro, pol, err := makeAll() + defer cleanupAll(pro, pol) + if err != nil { + t.Error(err) + } +} + +// Tests that we can add another protector to the policy +func TestPolicyGoodAddProtector(t *testing.T) { + ctx, pro1, pol, err := makeAll() + defer cleanupAll(pro1, pol) + if err != nil { + t.Fatal(err) + } + + pro2, err := ctx.NewProtector(testProtectorName2, goodCallback) + if err != nil { + t.Fatal(err) + } + defer pro2.Wipe() + + err = pol.AddProtector(pro2) + if err != nil { + t.Error(err) + } +} + +// Tests that we cannot add a protector to a policy twice +func TestPolicyBadAddProtector(t *testing.T) { + _, pro, pol, err := makeAll() + defer cleanupAll(pro, pol) + if err != nil { + t.Fatal(err) + } + + if pol.AddProtector(pro) == nil { + t.Error("we should not be able to add the same protector twice") + } +} + +// Tests that we can remove a protector we added +func TestPolicyGoodRemoveProtector(t *testing.T) { + ctx, pro1, pol, err := makeAll() + defer cleanupAll(pro1, pol) + if err != nil { + t.Fatal(err) + } + + pro2, err := ctx.NewProtector(testProtectorName2, goodCallback) + if err != nil { + t.Fatal(err) + } + defer pro2.Wipe() + + err = pol.AddProtector(pro2) + if err != nil { + t.Fatal(err) + } + + err = pol.RemoveProtector(pro1) + if err != nil { + t.Error(err) + } +} + +// Tests various bad ways to remove protectors +func TestPolicyBadRemoveProtector(t *testing.T) { + ctx, pro1, pol, err := makeAll() + defer cleanupAll(pro1, pol) + if err != nil { + t.Fatal(err) + } + + pro2, err := ctx.NewProtector(testProtectorName2, goodCallback) + if err != nil { + t.Fatal(err) + } + defer pro2.Wipe() + + if pol.RemoveProtector(pro2) == nil { + t.Error("we should not be able to remove a protector we did not add") + } + + if pol.RemoveProtector(pro1) == nil { + t.Error("we should not be able to remove all the protectors from a policy") + } +} |