diff options
| -rw-r--r-- | actions/callback.go | 8 | ||||
| -rw-r--r-- | actions/config.go | 33 | ||||
| -rw-r--r-- | actions/context.go | 63 | ||||
| -rw-r--r-- | actions/context_test.go | 82 | ||||
| -rw-r--r-- | actions/hashing_test.go (renamed from actions/config_test.go) | 45 | ||||
| -rw-r--r-- | actions/policy.go | 334 | ||||
| -rw-r--r-- | actions/policy_test.go | 76 | ||||
| -rw-r--r-- | actions/protector.go | 125 | ||||
| -rw-r--r-- | actions/protector_test.go | 25 | ||||
| -rw-r--r-- | filesystem/mountpoint.go | 6 |
10 files changed, 424 insertions, 373 deletions
diff --git a/actions/callback.go b/actions/callback.go index 18be670..2415d8c 100644 --- a/actions/callback.go +++ b/actions/callback.go @@ -21,10 +21,13 @@ package actions import ( + "log" + + "github.com/pkg/errors" + "fscrypt/crypto" "fscrypt/filesystem" "fscrypt/metadata" - "log" ) // ProtectorInfo is the information a caller will receive about a Protector @@ -91,7 +94,8 @@ func unwrapProtectorKey(info ProtectorInfo, keyFn KeyFunc) (*crypto.Key, error) protectorKey, err := crypto.Unwrap(wrappingKey, info.data.WrappedKey) wrappingKey.Wipe() - switch err { + + switch errors.Cause(err) { case nil: log.Printf("valid wrapping key for protector %s", info.Descriptor()) return protectorKey, nil diff --git a/actions/config.go b/actions/config.go index 2010ef1..1d81ff9 100644 --- a/actions/config.go +++ b/actions/config.go @@ -27,6 +27,8 @@ import ( "runtime" "time" + "github.com/pkg/errors" + "golang.org/x/sys/unix" "fscrypt/crypto" @@ -68,7 +70,7 @@ func CreateConfigFile(target time.Duration, useLegacy bool) error { case os.IsExist(err): return ErrConfigFileExists case err != nil: - return util.UnderlyingError(err) + return err } defer configFile.Close() @@ -99,15 +101,14 @@ func getConfig() (*metadata.Config, error) { case os.IsNotExist(err): return nil, ErrNoConfigFile case err != nil: - return nil, util.UnderlyingError(err) + return nil, err } defer configFile.Close() log.Printf("Reading config from %q\n", ConfigFileLocation) config, err := metadata.ReadConfig(configFile) if err != nil { - log.Printf("ReadConfig() = %v", err) - return nil, ErrBadConfigFile + return nil, errors.Wrap(ErrBadConfigFile, err.Error()) } // Use system defaults if not specified @@ -128,8 +129,8 @@ func getConfig() (*metadata.Config, error) { log.Printf("Falling back to filenames mode of %q", config.Options.Filenames) } - if !config.IsValid() { - return nil, ErrBadConfigFile + if err := config.CheckValidity(); err != nil { + return nil, errors.Wrap(ErrBadConfigFile, err.Error()) } return config, nil @@ -203,8 +204,8 @@ func ramLimit() int64 { err := unix.Sysinfo(&info) // The sysinfo syscall only fails if given a bad address util.NeverError(err) - // Use half the RAM and convert to kB. - return int64(info.Totalram / 1000 / 2) + // Use half the RAM and convert to kiB. + return int64(info.Totalram / 1024 / 2) } // betweenCosts returns a cost between a and b. Specifically, it returns the @@ -222,11 +223,23 @@ func timeHashingCosts(costs *metadata.HashingCosts) (time.Duration, error) { } defer passphrase.Wipe() - start := time.Now() + // Be sure to measure CPU time, not wall time (time.Now) + begin := cpuTimeInNanoseconds() hash, err := crypto.PassphraseHash(passphrase, timingSalt, costs) if err == nil { hash.Wipe() } + end := cpuTimeInNanoseconds() - return time.Since(start), err + return time.Duration(end - begin), nil +} + +// cpuTimeInNanoseconds returns the nanosecond count based on the process's CPU usage. +// This number has no absolute meaning, only relative meaning to other calls. +func cpuTimeInNanoseconds() int64 { + var ts unix.Timespec + err := unix.ClockGettime(unix.CLOCK_PROCESS_CPUTIME_ID, &ts) + // ClockGettime fails if given a bad address or on a VERY old system. + util.NeverError(err) + return unix.TimespecToNsec(ts) } diff --git a/actions/context.go b/actions/context.go index 4d7d30d..f8d0a3d 100644 --- a/actions/context.go +++ b/actions/context.go @@ -29,21 +29,22 @@ package actions import ( - "errors" - "fmt" "log" + "github.com/pkg/errors" + + "fscrypt/crypto" "fscrypt/filesystem" "fscrypt/metadata" - "fscrypt/util" ) // Errors relating to Config files or Config structures. var ( - ErrNoConfigFile = fmt.Errorf("config file %q does not exist", ConfigFileLocation) - ErrBadConfigFile = fmt.Errorf("config file %q has invalid data", ConfigFileLocation) - ErrConfigFileExists = fmt.Errorf("config file %q already exists", ConfigFileLocation) + ErrNoConfigFile = errors.New("global config file does not exist") + ErrBadConfigFile = errors.New("global config file has invalid data") + ErrConfigFileExists = errors.New("global config file already exists") ErrBadConfig = errors.New("invalid Config structure provided") + ErrLocked = errors.New("method needs a call to Unlock() first") ) // Context contains the necessary global state to perform most of fscrypt's @@ -61,12 +62,9 @@ type Context struct { // success, the Context contains a valid Config and Mount. func NewContextFromPath(path string) (ctx *Context, err error) { ctx = new(Context) - if ctx.Mount, err = filesystem.FindMount(path); err != nil { - err = util.UnderlyingError(err) return } - if ctx.Config, err = getConfig(); err != nil { return } @@ -81,12 +79,9 @@ func NewContextFromPath(path string) (ctx *Context, err error) { // success, the Context contains a valid Config and Mount. func NewContextFromMountpoint(mountpoint string) (ctx *Context, err error) { ctx = new(Context) - if ctx.Mount, err = filesystem.GetMount(mountpoint); err != nil { - err = util.UnderlyingError(err) return } - if ctx.Config, err = getConfig(); err != nil { return } @@ -99,15 +94,29 @@ func NewContextFromMountpoint(mountpoint string) (ctx *Context, err error) { // checkContext verifies that the context contains an valid config and a mount // which is being used with fscrypt. func (ctx *Context) checkContext() error { - if !ctx.Config.IsValid() { - return ErrBadConfig + if err := ctx.Config.CheckValidity(); err != nil { + return errors.Wrap(ErrBadConfig, err.Error()) } return ctx.Mount.CheckSetup() } -// GetProtectorOption returns the ProtectorOption for the protector on the +// 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 (ctx *Context) getService() string { + // For legacy configurations, we may need non-standard services + if ctx.Config.HasCompatibilityOption(LegacyConfig) { + switch ctx.Mount.Filesystem { + case "ext4", "f2fs": + return ctx.Mount.Filesystem + ":" + } + } + return crypto.DefaultService +} + +// getProtectorOption returns the ProtectorOption for the protector on the // context's mountpoint with the specified descriptor. -func (ctx *Context) GetProtectorOption(protectorDescriptor string) *ProtectorOption { +func (ctx *Context) getProtectorOption(protectorDescriptor string) *ProtectorOption { mnt, data, err := ctx.Mount.GetProtector(protectorDescriptor) if err != nil { return &ProtectorOption{ProtectorInfo{}, nil, err} @@ -121,9 +130,12 @@ func (ctx *Context) GetProtectorOption(protectorDescriptor string) *ProtectorOpt return &ProtectorOption{info, mnt, nil} } -// ListProtectorOptions creates a slice of all the options for all of the -// Protectors on the Context's mountpoint. -func (ctx *Context) ListProtectorOptions() ([]*ProtectorOption, error) { +// ProtectorOptions creates a slice of all the options for all of the Protectors +// on the Context's mountpoint. +func (ctx *Context) ProtectorOptions() ([]*ProtectorOption, error) { + if err := ctx.checkContext(); err != nil { + return nil, err + } descriptors, err := ctx.Mount.ListProtectors() if err != nil { return nil, err @@ -131,18 +143,7 @@ func (ctx *Context) ListProtectorOptions() ([]*ProtectorOption, error) { options := make([]*ProtectorOption, len(descriptors)) for i, descriptor := range descriptors { - options[i] = ctx.GetProtectorOption(descriptor) + options[i] = ctx.getProtectorOption(descriptor) } return options, nil } - -// ListOptionsForPolicy creates a slice of the ProtectorOptions which protect -// the policy specified by policyDescriptor. -func (ctx *Context) ListOptionsForPolicy(policyDescriptor string) ([]*ProtectorOption, error) { - policy, err := getPolicyData(ctx, policyDescriptor) - if err != nil { - return nil, err - } - - return policy.listOptions(), nil -} diff --git a/actions/context_test.go b/actions/context_test.go index 74629a3..79adedf 100644 --- a/actions/context_test.go +++ b/actions/context_test.go @@ -20,57 +20,77 @@ package actions import ( + "fmt" + "fscrypt/util" + "log" "os" + "path/filepath" "testing" - - "fscrypt/filesystem" + "time" ) -var mountpoint = os.Getenv("TEST_FILESYSTEM_ROOT") +const testTime = 10 * time.Millisecond + +// holds the context we will use throughout the actions tests +var testContext *Context // Makes a context using the testing locations for the filesystem and // configuration file. -func makeContext() (*Context, error) { - if err := CreateConfigFile(testTime, true); err != nil { +func setupContext() (ctx *Context, err error) { + mountpoint, err := util.TestPath() + if err != nil { return nil, err } - mnt := filesystem.Mount{Path: mountpoint} - if err := mnt.Setup(); err != nil { - return nil, err - } + ConfigFileLocation = filepath.Join(mountpoint, "test.conf") - return NewContextFromMountpoint(mountpoint) -} + // Should not be able to setup without a config file + if badCtx, badCtxErr := NewContextFromMountpoint(mountpoint); badCtxErr == nil { + badCtx.Mount.RemoveAllMetadata() + return nil, fmt.Errorf("created context at %q without config file", badCtx.Mount.Path) + } -// Cleans up the testing config file and testing filesystem data. -func cleaupContext() { - os.RemoveAll(ConfigFileLocation) - mnt := filesystem.Mount{Path: mountpoint} - mnt.RemoveAllMetadata() -} + if err = CreateConfigFile(testTime, true); err != nil { + return nil, err + } + defer func() { + if err != nil { + os.RemoveAll(ConfigFileLocation) + } + }() -// Tests that we can create a context -func TestSetupContext(t *testing.T) { - _, err := makeContext() - defer cleaupContext() + ctx, err = NewContextFromMountpoint(mountpoint) if err != nil { - t.Fatal(err) + return nil, err } + return ctx, ctx.Mount.Setup() } -// Tests that we cannot create a context without a config file. -func TestNoConfigFile(t *testing.T) { - mnt := filesystem.Mount{Path: mountpoint} - if err := mnt.Setup(); err != nil { - t.Fatal(err) +// Cleans up the testing config file and testing filesystem data. +func cleaupContext(ctx *Context) error { + err1 := os.RemoveAll(ConfigFileLocation) + err2 := ctx.Mount.RemoveAllMetadata() + if err1 != nil { + return err1 } + return err2 +} - _, err := NewContextFromMountpoint(mountpoint) - defer cleaupContext() +func TestMain(m *testing.M) { + log.SetFlags(log.LstdFlags | log.Lmicroseconds) + var err error + testContext, err = setupContext() + if err != nil { + fmt.Printf("setupContext() = %v\n", err) + os.Exit(1) + } - if err == nil { - t.Error("should not be able to create context without config file") + returnCode := m.Run() + err = cleaupContext(testContext) + if err != nil { + fmt.Printf("cleanupContext() = %v\n", err) + os.Exit(1) } + os.Exit(returnCode) } diff --git a/actions/config_test.go b/actions/hashing_test.go index c0b2089..e3cffb6 100644 --- a/actions/config_test.go +++ b/actions/hashing_test.go @@ -1,5 +1,5 @@ /* - * config_test.go - tests for setting up the config file + * hashing_test.go - tests for computing and benchmarking hashing costs * * Copyright 2017 Google Inc. * Author: Joe Richey (joerichey@google.com) @@ -22,43 +22,17 @@ package actions import ( "io/ioutil" "log" - "os" "testing" "time" ) -const testTime = 10 * time.Millisecond - -func init() { - // All our testing uses an alternative config file location, so we don't - // need root to run the tests - ConfigFileLocation = "fscrypt_test.conf" -} - -// Tests that we can make the config files with and without legacy settings -func TestMakeConfig(t *testing.T) { - defer os.RemoveAll(ConfigFileLocation) - - err := CreateConfigFile(testTime, true) - if err != nil { - t.Error(err) - } - os.RemoveAll(ConfigFileLocation) - - err = CreateConfigFile(testTime, false) - if err != nil { - t.Error(err) - } -} - // Tests that we can find valid hashing costs for various time targets and the // estimations are somewhat close to the targets. func TestCostsSearch(t *testing.T) { for _, target := range []time.Duration{ - 100 * time.Microsecond, - 1 * time.Millisecond, - 10 * time.Millisecond, + 20 * time.Millisecond, 100 * time.Millisecond, + 500 * time.Millisecond, } { costs, err := getHashingCosts(target) if err != nil { @@ -69,14 +43,11 @@ func TestCostsSearch(t *testing.T) { t.Error(err) } - // Timing tests are only reliable for sufficiently long targets. - if target > time.Millisecond { - if actual*2 < target { - t.Errorf("actual=%v is too small (target=%v)", actual, target) - } - if target*2 < actual { - t.Errorf("actual=%v is too big (target=%v)", actual, target) - } + if actual*2 < target { + t.Errorf("actual=%v is too small (target=%v)", actual, target) + } + if target*2 < actual { + t.Errorf("actual=%v is too big (target=%v)", actual, target) } } } 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. diff --git a/actions/policy_test.go b/actions/policy_test.go index 07a7f87..96b9bb0 100644 --- a/actions/policy_test.go +++ b/actions/policy_test.go @@ -21,53 +21,54 @@ package actions import "testing" -// Makes a context, protector, and policy -func makeAll() (ctx *Context, protector *Protector, policy *Policy, err error) { - ctx, err = makeContext() +// Makes a protector and policy +func makeBoth() (*Protector, *Policy, error) { + protector, err := CreateProtector(testContext, testProtectorName, goodCallback) if err != nil { - return + return nil, nil, err } - protector, err = CreateProtector(ctx, testProtectorName, goodCallback) + policy, err := CreatePolicy(testContext, protector) if err != nil { - return + cleanupProtector(protector) + return nil, nil, err } - policy, err = CreatePolicy(ctx, protector) - return + return protector, policy, nil } -// Cleans up a context, protector, and policy -func cleanupAll(protector *Protector, policy *Policy) { - if policy != nil { - policy.Wipe() - } - if protector != nil { - protector.Wipe() - } - cleaupContext() +func cleanupProtector(protector *Protector) { + protector.Lock() + protector.Destroy() +} + +func cleanupPolicy(policy *Policy) { + policy.Lock() + policy.Destroy() } // Tests that we can make a policy/protector pair func TestCreatePolicy(t *testing.T) { - _, pro, pol, err := makeAll() - defer cleanupAll(pro, pol) + pro, pol, err := makeBoth() if err != nil { t.Error(err) } + cleanupPolicy(pol) + cleanupProtector(pro) } // Tests that we can add another protector to the policy func TestPolicyGoodAddProtector(t *testing.T) { - ctx, pro1, pol, err := makeAll() - defer cleanupAll(pro1, pol) + pro1, pol, err := makeBoth() + defer cleanupProtector(pro1) + defer cleanupPolicy(pol) if err != nil { t.Fatal(err) } - pro2, err := CreateProtector(ctx, testProtectorName2, goodCallback) + pro2, err := CreateProtector(testContext, testProtectorName2, goodCallback) if err != nil { t.Fatal(err) } - defer pro2.Wipe() + defer cleanupProtector(pro2) err = pol.AddProtector(pro2) if err != nil { @@ -77,8 +78,9 @@ func TestPolicyGoodAddProtector(t *testing.T) { // Tests that we cannot add a protector to a policy twice func TestPolicyBadAddProtector(t *testing.T) { - _, pro, pol, err := makeAll() - defer cleanupAll(pro, pol) + pro, pol, err := makeBoth() + defer cleanupProtector(pro) + defer cleanupPolicy(pol) if err != nil { t.Fatal(err) } @@ -90,24 +92,25 @@ func TestPolicyBadAddProtector(t *testing.T) { // Tests that we can remove a protector we added func TestPolicyGoodRemoveProtector(t *testing.T) { - ctx, pro1, pol, err := makeAll() - defer cleanupAll(pro1, pol) + pro1, pol, err := makeBoth() + defer cleanupProtector(pro1) + defer cleanupPolicy(pol) if err != nil { t.Fatal(err) } - pro2, err := CreateProtector(ctx, testProtectorName2, goodCallback) + pro2, err := CreateProtector(testContext, testProtectorName2, goodCallback) if err != nil { t.Fatal(err) } - defer pro2.Wipe() + defer cleanupProtector(pro2) err = pol.AddProtector(pro2) if err != nil { t.Fatal(err) } - err = pol.RemoveProtector(pro1.data.ProtectorDescriptor) + err = pol.RemoveProtector(pro1) if err != nil { t.Error(err) } @@ -115,23 +118,24 @@ func TestPolicyGoodRemoveProtector(t *testing.T) { // Tests various bad ways to remove protectors func TestPolicyBadRemoveProtector(t *testing.T) { - ctx, pro1, pol, err := makeAll() - defer cleanupAll(pro1, pol) + pro1, pol, err := makeBoth() + defer cleanupProtector(pro1) + defer cleanupPolicy(pol) if err != nil { t.Fatal(err) } - pro2, err := CreateProtector(ctx, testProtectorName2, goodCallback) + pro2, err := CreateProtector(testContext, testProtectorName2, goodCallback) if err != nil { t.Fatal(err) } - defer pro2.Wipe() + defer cleanupProtector(pro2) - if pol.RemoveProtector(pro2.data.ProtectorDescriptor) == nil { + if pol.RemoveProtector(pro2) == nil { t.Error("we should not be able to remove a protector we did not add") } - if pol.RemoveProtector(pro1.data.ProtectorDescriptor) == nil { + if pol.RemoveProtector(pro1) == nil { t.Error("we should not be able to remove all the protectors from a policy") } } diff --git a/actions/protector.go b/actions/protector.go index 4680cba..0409b56 100644 --- a/actions/protector.go +++ b/actions/protector.go @@ -20,9 +20,12 @@ package actions import ( - "errors" + "fmt" + "log" "os" + "github.com/pkg/errors" + "fscrypt/crypto" "fscrypt/metadata" ) @@ -31,21 +34,21 @@ import ( var ( ErrProtectorName = errors.New("login protectors do not need a name") ErrMissingProtectorName = errors.New("custom protectors must have a name") - ErrDuplicateName = errors.New("a protector with this name already exists") - ErrDuplicateUID = errors.New("there is already a login protector for this user") + ErrDuplicateName = errors.New("protector with this name already exists") + ErrDuplicateUID = errors.New("login protector for this user already exists") ) // checkForProtectorWithName returns an error if there is already a protector // on the filesystem with a specific name (or if we cannot read the necessary // data). func checkForProtectorWithName(ctx *Context, name string) error { - options, err := ctx.ListProtectorOptions() + options, err := ctx.ProtectorOptions() if err != nil { return err } for _, option := range options { if option.Name() == name { - return ErrDuplicateName + return errors.Wrapf(ErrDuplicateName, "name %q", name) } } return nil @@ -55,13 +58,13 @@ func checkForProtectorWithName(ctx *Context, name string) error { // protector on the filesystem with a specific UID (or if we cannot read the // necessary data). func checkForProtectorWithUID(ctx *Context, uid int64) error { - options, err := ctx.ListProtectorOptions() + options, err := ctx.ProtectorOptions() if err != nil { return err } for _, option := range options { if option.Source() == metadata.SourceType_pam_passphrase && option.UID() == uid { - return ErrDuplicateUID + return errors.Wrapf(ErrDuplicateUID, "uid %d", uid) } } return nil @@ -75,12 +78,13 @@ type Protector struct { Context *Context data *metadata.ProtectorData key *crypto.Key + created bool } -// CreateProtector creates a protector with a given name (only for custom and -// raw protector types). The keyFn provided to create the Protector key will -// only be called once. If an error is returned, no data has been changed on the -// filesystem. +// CreateProtector creates an unlocked protector with a given name (name only +// needed for custom and raw protector types). The keyFn provided to create the +// Protector key will only be called once. If an error is returned, no data has +// been changed on the filesystem. func CreateProtector(ctx *Context, name string, keyFn KeyFunc) (*Protector, error) { if err := ctx.checkContext(); err != nil { return nil, err @@ -109,6 +113,7 @@ func CreateProtector(ctx *Context, name string, keyFn KeyFunc) (*Protector, erro Name: name, Source: ctx.Config.Source, }, + created: true, } // Extra data is needed for some SourceTypes @@ -138,36 +143,34 @@ func CreateProtector(ctx *Context, name string, keyFn KeyFunc) (*Protector, erro protector.data.ProtectorDescriptor = crypto.ComputeDescriptor(protector.key) if err := protector.Rewrap(keyFn); err != nil { - protector.Wipe() + protector.Lock() return nil, err } return protector, nil } -// GetProtector retrieves a Protector with a specific descriptor. The keyFn -// provided to unwrap the Protector key will be retied as necessary to get the -// correct key. -func GetProtector(ctx *Context, descriptor string, keyFn KeyFunc) (*Protector, error) { - if err := ctx.checkContext(); err != nil { - return nil, err - } - var err error - protector := &Protector{Context: ctx} - - if protector.data, err = ctx.Mount.GetRegularProtector(descriptor); err != nil { +// GetProtector retrieves a Protector with a specific descriptor. The Protector +// is still locked in this case, so it must be unlocked before using certain +// methods. +func GetProtector(ctx *Context, descriptor string) (*Protector, error) { + log.Printf("Getting protector %s", descriptor) + err := ctx.checkContext() + if err != nil { return nil, err } - protector.key, err = unwrapProtectorKey(ProtectorInfo{protector.data}, keyFn) + protector := &Protector{Context: ctx} + protector.data, err = ctx.Mount.GetRegularProtector(descriptor) return protector, err } // GetProtectorFromOption retrieves a protector based on a protector option. // If the option had a load error, this function returns that error. The -// keyFn provided to unwrap the Protector key will be retied as necessary to -// get the correct key. -func GetProtectorFromOption(ctx *Context, option *ProtectorOption, keyFn KeyFunc) (*Protector, error) { +// Protector is still locked in this case, so it must be unlocked before using +// certain methods. +func GetProtectorFromOption(ctx *Context, option *ProtectorOption) (*Protector, error) { + log.Printf("Getting protector %s from option", option.Descriptor()) if err := ctx.checkContext(); err != nil { return nil, err } @@ -179,18 +182,62 @@ func GetProtectorFromOption(ctx *Context, option *ProtectorOption, keyFn KeyFunc if option.LinkedMount != nil { ctx = &Context{ctx.Config, option.LinkedMount} } - var err error - protector := &Protector{Context: ctx, data: option.data} + return &Protector{Context: ctx, data: option.data}, nil +} - protector.key, err = unwrapProtectorKey(option.ProtectorInfo, keyFn) - return protector, err +// Descriptor returns the protector descriptor. +func (protector *Protector) Descriptor() string { + return protector.data.ProtectorDescriptor +} + +// Destroy removes a protector from the filesystem. The internal key should +// still be wiped with Lock(). +func (protector *Protector) Destroy() error { + return protector.Context.Mount.RemoveProtector(protector.Descriptor()) +} + +// Revert destroys a protector if it was created, but does nothing if it was +// just queried from the filesystem. +func (protector *Protector) Revert() error { + if !protector.created { + return nil + } + return protector.Destroy() +} + +func (protector *Protector) String() string { + return fmt.Sprintf("Protector: %s\nMountpoint: %s\nSource: %s\nName: %s\nCosts: %v\nUID: %d", + protector.Descriptor(), protector.Context.Mount, protector.data.Source, + protector.data.Name, protector.data.Costs, protector.data.Uid) +} + +// Unlock unwraps the Protector's internal key. The keyFn provided to unwrap the +// Protector key will be retried as necessary to get the correct key. Lock() +// should be called after use. Does nothing if protector is already unlocked. +func (protector *Protector) Unlock(keyFn KeyFunc) (err error) { + if protector.key != nil { + return + } + protector.key, err = unwrapProtectorKey(ProtectorInfo{protector.data}, keyFn) + return +} + +// Lock wipes a Protector's internal Key. It should always be called after using +// an unlocked Protector. This is often done with a defer statement. There is +// no effect if called multiple times. +func (protector *Protector) Lock() error { + err := protector.key.Wipe() + protector.key = nil + return err } // Rewrap updates the data that is wrapping the Protector Key. This is useful if // a user's password has changed, for example. The keyFn provided to rewrap -// the Protector key will only be called once. If an error is returned, no data -// has been changed on the filesystem. +// the Protector key will only be called once. Requires unlocked Protector. func (protector *Protector) Rewrap(keyFn KeyFunc) error { + if protector.key == nil { + return ErrLocked + } wrappingKey, err := getWrappingKey(ProtectorInfo{protector.data}, keyFn, false) if err != nil { return err @@ -211,15 +258,3 @@ func (protector *Protector) Rewrap(keyFn KeyFunc) error { return protector.Context.Mount.AddProtector(protector.data) } - -// Wipe wipes a Protector's internal Key. It should always be called after using -// a Protector. This is often done with a defer statement. -func (protector *Protector) Wipe() error { - return protector.key.Wipe() -} - -// Destroy removes a protector from the filesystem. The internal key should -// still be wiped with Wipe(). -func (protector *Protector) Destroy() error { - return protector.Context.Mount.RemoveProtector(protector.data.ProtectorDescriptor) -} diff --git a/actions/protector_test.go b/actions/protector_test.go index 08d9aed..eacba83 100644 --- a/actions/protector_test.go +++ b/actions/protector_test.go @@ -21,9 +21,10 @@ package actions import ( "bytes" - "errors" "testing" + "github.com/pkg/errors" + . "fscrypt/crypto" ) @@ -42,31 +43,21 @@ func badCallback(info ProtectorInfo, retry bool) (*Key, error) { // Tests that we can create a valid protector. func TestCreateProtector(t *testing.T) { - ctx, err := makeContext() - defer cleaupContext() - if err != nil { - t.Fatal(err) - } - - p, err := CreateProtector(ctx, testProtectorName, goodCallback) + p, err := CreateProtector(testContext, testProtectorName, goodCallback) if err != nil { t.Error(err) } else { - p.Wipe() + p.Lock() + p.Destroy() } } // Tests that a failure in the callback is relayed back to the caller. func TestBadCallback(t *testing.T) { - ctx, err := makeContext() - defer cleaupContext() - if err != nil { - t.Fatal(err) - } - - p, err := CreateProtector(ctx, testProtectorName, badCallback) + p, err := CreateProtector(testContext, testProtectorName, badCallback) if err == nil { - p.Wipe() + p.Lock() + p.Destroy() } if err != errCallback { t.Error("callback error was not relayed back to caller") diff --git a/filesystem/mountpoint.go b/filesystem/mountpoint.go index 1fc41be..eab7592 100644 --- a/filesystem/mountpoint.go +++ b/filesystem/mountpoint.go @@ -139,9 +139,9 @@ func AllFilesystems() ([]*Mount, error) { return nil, err } - mounts := make([]*Mount, len(mountsByPath)) - for i, mount := range mountsByPath { - mounts[i] = mount + mounts := make([]*Mount, 0, len(mountsByPath)) + for _, mount := range mountsByPath { + mounts = append(mounts, mount) } sort.Sort(PathSorter(mounts)) |