diff options
| -rw-r--r-- | actions/callback.go | 105 | ||||
| -rw-r--r-- | actions/config.go | 27 | ||||
| -rw-r--r-- | actions/config_test.go | 4 | ||||
| -rw-r--r-- | actions/context.go | 51 | ||||
| -rw-r--r-- | actions/context_test.go | 2 | ||||
| -rw-r--r-- | actions/policy.go | 291 | ||||
| -rw-r--r-- | actions/policy_test.go | 18 | ||||
| -rw-r--r-- | actions/protector.go | 116 | ||||
| -rw-r--r-- | actions/protector_test.go | 10 | ||||
| -rw-r--r-- | crypto/crypto_test.go | 10 | ||||
| -rw-r--r-- | crypto/key.go | 23 |
11 files changed, 389 insertions, 268 deletions
diff --git a/actions/callback.go b/actions/callback.go index c1d2c8a..18be670 100644 --- a/actions/callback.go +++ b/actions/callback.go @@ -22,67 +22,83 @@ package actions import ( "fscrypt/crypto" + "fscrypt/filesystem" "fscrypt/metadata" "log" ) -// ProtectorData is the information a caller will receive about a Protector +// ProtectorInfo is the information a caller will receive about a Protector // before they have to return the corresponding key. This is currently a // read-only view of metadata.ProtectorData. -type ProtectorData interface { - GetProtectorDescriptor() string - GetSource() metadata.SourceType - GetName() string - GetUid() int64 +type ProtectorInfo struct { + data *metadata.ProtectorData } -// KeyCallback is passed to a function that will require a key from the caller. -// For passphrase sources, the returned key should be a password. For raw -// sources, the returned key should be a standard cryptographic key. Consumers -// of the callback will wipe the provided key. If the callback returns an error, -// the function to which the callback is passed returns that error. Note that -// when using the key to unwrap a known key, the callback will be executed until -// the correct key is returned or an error is returned. -type KeyCallback func(data ProtectorData) (*crypto.Key, error) +// Descriptor is the Protector's descriptor used to uniquely identify it. +func (pi *ProtectorInfo) Descriptor() string { return pi.data.GetProtectorDescriptor() } + +// Source indicates the type of the descriptor (how it should be unlocked). +func (pi *ProtectorInfo) Source() metadata.SourceType { return pi.data.GetSource() } + +// Name is used to describe custom passphrase and raw key descriptors. +func (pi *ProtectorInfo) Name() string { return pi.data.GetName() } + +// UID is used to identify the user for login passphrases. +func (pi *ProtectorInfo) UID() int64 { return pi.data.GetUid() } + +// KeyFunc is passed to a function that will require some type of key. +// The info parameter is provided so the callback knows which key to provide. +// The retry parameter indicates that a previous key provided by this callback +// was incorrect (this allows for user feedback like "incorrect passphrase"). +// +// For passphrase sources, the returned key should be a passphrase. For raw +// sources, the returned key should be a 256-bit cryptographic key. Consumers +// of the callback will wipe the returned key. An error returned by the callback +// will be propagated back to the caller. +type KeyFunc func(info ProtectorInfo, retry bool) (*crypto.Key, error) // getWrappingKey uses the provided callback to get the wrapping key -// corresponding to the ProtectorData. This runs the passphrase hash for +// corresponding to the ProtectorInfo. This runs the passphrase hash for // passphrase sources or just relays the callback for raw sources. -func getWrappingKey(data *metadata.ProtectorData, callback KeyCallback) (*crypto.Key, error) { - // We don't need to go anything for raw keys - if data.Source == metadata.SourceType_raw_key { - return callback(data) +func getWrappingKey(info ProtectorInfo, keyFn KeyFunc, retry bool) (*crypto.Key, error) { + // For raw key sources, we can just use the key directly. + if info.Source() == metadata.SourceType_raw_key { + return keyFn(info, retry) } // Run the passphrase hash for other sources. - passphrase, err := callback(data) + passphrase, err := keyFn(info, retry) if err != nil { return nil, err } defer passphrase.Wipe() - log.Printf("running passphrase hash for protector %s", data.ProtectorDescriptor) - return crypto.PassphraseHash(passphrase, data.Salt, data.Costs) + log.Printf("running passphrase hash for protector %s", info.Descriptor()) + return crypto.PassphraseHash(passphrase, info.data.Salt, info.data.Costs) } -// unwrapProtectorKey uses the provided callback and protector data to return -// the unwrapped protector key. This will repeatedly use the callback to get the -// wrapping key until the correct key is returned or an error is returned. -func unwrapProtectorKey(data *metadata.ProtectorData, callback KeyCallback) (*crypto.Key, error) { +// unwrapProtectorKey uses the provided callback and ProtectorInfo to return +// the unwrapped protector key. This will repeatedly call keyFn to get the +// wrapping key until the correct key is returned by the callback or the +// callback returns an error. +func unwrapProtectorKey(info ProtectorInfo, keyFn KeyFunc) (*crypto.Key, error) { + retry := false for { - wrappingKey, err := getWrappingKey(data, callback) + wrappingKey, err := getWrappingKey(info, keyFn, retry) if err != nil { return nil, err } - protectorKey, err := crypto.Unwrap(wrappingKey, data.WrappedKey) + protectorKey, err := crypto.Unwrap(wrappingKey, info.data.WrappedKey) wrappingKey.Wipe() switch err { case nil: - log.Printf("valid wrapping key for protector %s", data.ProtectorDescriptor) + log.Printf("valid wrapping key for protector %s", info.Descriptor()) return protectorKey, nil case crypto.ErrBadAuth: - log.Printf("invalid wrapping key for protector %s", data.ProtectorDescriptor) + // After the first failure, we let the callback know we are retrying. + log.Printf("invalid wrapping key for protector %s", info.Descriptor()) + retry = true continue default: return nil, err @@ -90,12 +106,23 @@ func unwrapProtectorKey(data *metadata.ProtectorData, callback KeyCallback) (*cr } } -// PolicyCallback is passed to a function that needs to unlock a policy. The -// callback is used so that the caller can specify which protector they wish to -// use to unlock a policy. The descriptor is the KeyDescriptor for the Policy, -// while for each Protector protecting the policy there is either an entry in -// protectors (if we were able to read the Protector's data). The PolicyCallback -// should either return a valid index into protectors corresponding to the -// desired protector, or an error. If the callback returns an error, the -// function to which the callback is passed returns that error. -type PolicyCallback func(descriptor string, protectors []ProtectorData) (int, error) +// ProtectorOption is information about a protector relative to a Policy. +type ProtectorOption struct { + ProtectorInfo + // LinkedMount is the mountpoint for a linked protector. It is nil if + // the protector is not a linked protector (or there is a LoadError). + LinkedMount *filesystem.Mount + // LoadError is non-nil if there was an error in getting the data for + // the protector. + LoadError error +} + +// OptionFunc is passed to a function that needs to unlock a Policy. +// The callback is used to specify which protector should be used to unlock a +// Policy. The descriptor indicates which Policy we are using, while the options +// correspond to the valid Protectors protecting the Policy. +// +// The OptionFunc should either return a valid index into options, which +// corresponds to the desired protector, or an error (which will be propagated +// back to the caller). +type OptionFunc func(policyDescriptor string, options []*ProtectorOption) (int, error) diff --git a/actions/config.go b/actions/config.go index 4319814..2010ef1 100644 --- a/actions/config.go +++ b/actions/config.go @@ -34,10 +34,15 @@ import ( "fscrypt/util" ) +// LegacyConfig indicates that keys should be inserted into the keyring with the +// legacy service prefixes. Needed for kernels before v4.8. +const LegacyConfig = "legacy" + +// ConfigFileLocation is the location of fscrypt's global settings. This can be +// overridden by the user of this package. +var ConfigFileLocation = "/etc/fscrypt.conf" + const ( - // LegacyConfig indicates that keys should be inserted into the keyring - // with the legacy service prefixes. Needed for kernels before v4.8. - LegacyConfig = "legacy" // Permissions of the config file (global readable) configPermissions = 0644 // Config file should be created for writing and not already exist @@ -45,19 +50,17 @@ const ( ) var ( - // ConfigFileLocation is the location of fscrypt's global settings. - ConfigFileLocation = "/etc/fscrypt.conf" - timingPassphrase = []byte("I am a fake passphrase") - timingSalt = bytes.Repeat([]byte{42}, metadata.SaltLen) + timingPassphrase = []byte("I am a fake passphrase") + timingSalt = bytes.Repeat([]byte{42}, metadata.SaltLen) ) -// NewConfigFile creates a new config file at the appropriate location with the -// appropriate hashing costs and encryption parameters. This creation is +// CreateConfigFile creates a new config file at the appropriate location with +// the appropriate hashing costs and encryption parameters. This creation is // configurable in two ways. First, a time target must be specified. This target // will determine the hashing costs, by picking parameters that make the hashing // take as long as the specified target. Second, the config can include the // legacy option, which is needed for systems with kernels older than v4.8. -func NewConfigFile(target time.Duration, useLegacy bool) error { +func CreateConfigFile(target time.Duration, useLegacy bool) error { // Create the config file before computing the hashing costs, so we fail // immediately if the program has insufficient permissions. configFile, err := os.OpenFile(ConfigFileLocation, createFlags, configPermissions) @@ -88,8 +91,8 @@ func NewConfigFile(target time.Duration, useLegacy bool) error { // getConfig returns the current configuration struct. Any fields not specified // in the config file use the system defaults. An error is returned if the -// config file hasn't been setup with NewConfigFile yet or the config contains -// invalid data. +// config file hasn't been setup with CreateConfigFile yet or the config +// contains invalid data. func getConfig() (*metadata.Config, error) { configFile, err := os.Open(ConfigFileLocation) switch { diff --git a/actions/config_test.go b/actions/config_test.go index 2b10c10..c0b2089 100644 --- a/actions/config_test.go +++ b/actions/config_test.go @@ -39,13 +39,13 @@ func init() { func TestMakeConfig(t *testing.T) { defer os.RemoveAll(ConfigFileLocation) - err := NewConfigFile(testTime, true) + err := CreateConfigFile(testTime, true) if err != nil { t.Error(err) } os.RemoveAll(ConfigFileLocation) - err = NewConfigFile(testTime, false) + err = CreateConfigFile(testTime, false) if err != nil { t.Error(err) } diff --git a/actions/context.go b/actions/context.go index f4a3985..4d7d30d 100644 --- a/actions/context.go +++ b/actions/context.go @@ -95,3 +95,54 @@ func NewContextFromMountpoint(mountpoint string) (ctx *Context, err error) { ctx.Mount.Path, ctx.Mount.Device) return } + +// 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 + } + return ctx.Mount.CheckSetup() +} + +// GetProtectorOption returns the ProtectorOption for the protector on the +// context's mountpoint with the specified descriptor. +func (ctx *Context) GetProtectorOption(protectorDescriptor string) *ProtectorOption { + mnt, data, err := ctx.Mount.GetProtector(protectorDescriptor) + if err != nil { + return &ProtectorOption{ProtectorInfo{}, nil, err} + } + + info := ProtectorInfo{data} + // No linked path if on the same mountpoint + if mnt == ctx.Mount { + return &ProtectorOption{info, nil, nil} + } + 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) { + descriptors, err := ctx.Mount.ListProtectors() + if err != nil { + return nil, err + } + + options := make([]*ProtectorOption, len(descriptors)) + for i, descriptor := range descriptors { + 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 671b065..74629a3 100644 --- a/actions/context_test.go +++ b/actions/context_test.go @@ -31,7 +31,7 @@ var mountpoint = os.Getenv("TEST_FILESYSTEM_ROOT") // Makes a context using the testing locations for the filesystem and // configuration file. func makeContext() (*Context, error) { - if err := NewConfigFile(testTime, true); err != nil { + if err := CreateConfigFile(testTime, true); err != nil { return nil, err } diff --git a/actions/policy.go b/actions/policy.go index cc1a92c..678bcdc 100644 --- a/actions/policy.go +++ b/actions/policy.go @@ -32,34 +32,144 @@ import ( // 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") + 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") + 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") ) +// 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. +func PurgeAllPolicies(ctx *Context) error { + if err := ctx.checkContext(); err != nil { + return err + } + policies, err := ctx.Mount.ListPolicies() + if err != nil { + 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. + 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 // 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 + Context *Context + data *metadata.PolicyData + key *crypto.Key } -// NewPolicy creates a Policy protected by given Protector and stores the +// CreatePolicy 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 +func CreatePolicy(ctx *Context, protector *Protector) (*Policy, error) { + if err := ctx.checkContext(); err != nil { + return nil, err } - // Randomly create the underlying policy key (and wipe if we fail) key, err := crypto.NewRandomKey(metadata.PolicyKeyLen) if err != nil { @@ -83,107 +193,52 @@ func (ctx *Context) NewPolicy(protector *Protector) (*Policy, error) { 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) +// 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) if err != nil { return nil, err } - log.Printf("got data for %s from filesystem", descriptor) - - return &Policy{Context: ctx, data: data}, nil + return policy, policy.unwrapPolicy(optionFn, keyFn) } -// 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 +// listOptions creates a slice of ProtectorOptions for the protectors protecting +// this policy. +func (policy *Policy) listOptions() []*ProtectorOption { + options := make([]*ProtectorOption, len(policy.data.WrappedPolicyKeys)) + for i, wrappedPolicyKey := range policy.data.WrappedPolicyKeys { + options[i] = policy.Context.GetProtectorOption(wrappedPolicyKey.ProtectorDescriptor) } - - 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) + return options } // 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) +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)) + + for i, wrappedPolicyKey := range policy.data.WrappedPolicyKeys { + wrappedKeys[i] = wrappedPolicyKey.WrappedKey } - log.Printf("%d of our %d protectors are available", len(protectors), totalKeys) - idx, err := policyCallback(policy.data.KeyDescriptor, protectors) + // The OptionFunc indicates which option and wrapped key we should use. + idx, err := optionFn(policy.data.KeyDescriptor, options) if err != nil { return err } - if idx < 0 || idx >= len(protectors) { - return ErrInvalidIndex + option := options[idx] + if option.LoadError != nil { + return option.LoadError } - protectorData := protectors[idx].(*metadata.ProtectorData) wrappedPolicyKey := wrappedKeys[idx] - log.Printf("protector %s selected in callback", protectorData.ProtectorDescriptor) + log.Printf("protector %s selected in callback", option.Descriptor()) - protectorKey, err := unwrapProtectorKey(protectorData, keyCallback) + protectorKey, err := unwrapProtectorKey(option.ProtectorInfo, keyFn) if err != nil { return err } @@ -199,16 +254,16 @@ func (policy *Policy) unwrapPolicy(policyCallback PolicyCallback, keyCallback Ke // 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) + _, err := policy.findWrappedKeyIndex(protector.data.ProtectorDescriptor) 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 policy.Context.Mount != protector.Context.Mount { + err = policy.Context.Mount.AddLinkedProtector( + protector.data.ProtectorDescriptor, protector.Context.Mount) if err != nil { return err } @@ -218,7 +273,6 @@ func (policy *Policy) AddProtector(protector *Protector) error { wrappedPolicyKey := &metadata.WrappedPolicyKey{ ProtectorDescriptor: protector.data.ProtectorDescriptor, } - if wrappedPolicyKey.WrappedKey, err = crypto.Wrap(protector.key, policy.key); err != nil { return err } @@ -239,8 +293,8 @@ func (policy *Policy) AddProtector(protector *Protector) 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) +func (policy *Policy) RemoveProtector(protectorDescriptor string) error { + idx, err := policy.findWrappedKeyIndex(protectorDescriptor) if err != nil { return err } @@ -266,7 +320,7 @@ 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.Mount { + } else if pathMount != policy.Context.Mount { return ErrDifferentFilesystem } @@ -276,22 +330,11 @@ func (policy *Policy) Apply(path string) error { // 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) + return crypto.InsertPolicyKey(policy.key, policy.data.KeyDescriptor, getService(policy.Context)) } -// Wipe wipes a Policy's internal Key. +// 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() } @@ -299,20 +342,20 @@ func (policy *Policy) Wipe() error { // 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) + return policy.Context.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) + 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(protector *Protector) (int, error) { +func (policy *Policy) findWrappedKeyIndex(protectorDescriptor string) (int, error) { for idx, wrappedPolicyKey := range policy.data.WrappedPolicyKeys { - if wrappedPolicyKey.ProtectorDescriptor == protector.data.ProtectorDescriptor { + if wrappedPolicyKey.ProtectorDescriptor == protectorDescriptor { return idx, nil } } diff --git a/actions/policy_test.go b/actions/policy_test.go index 3a64e01..07a7f87 100644 --- a/actions/policy_test.go +++ b/actions/policy_test.go @@ -27,11 +27,11 @@ func makeAll() (ctx *Context, protector *Protector, policy *Policy, err error) { if err != nil { return } - protector, err = ctx.NewProtector(testProtectorName, goodCallback) + protector, err = CreateProtector(ctx, testProtectorName, goodCallback) if err != nil { return } - policy, err = ctx.NewPolicy(protector) + policy, err = CreatePolicy(ctx, protector) return } @@ -47,7 +47,7 @@ func cleanupAll(protector *Protector, policy *Policy) { } // Tests that we can make a policy/protector pair -func TestNewPolicy(t *testing.T) { +func TestCreatePolicy(t *testing.T) { _, pro, pol, err := makeAll() defer cleanupAll(pro, pol) if err != nil { @@ -63,7 +63,7 @@ func TestPolicyGoodAddProtector(t *testing.T) { t.Fatal(err) } - pro2, err := ctx.NewProtector(testProtectorName2, goodCallback) + pro2, err := CreateProtector(ctx, testProtectorName2, goodCallback) if err != nil { t.Fatal(err) } @@ -96,7 +96,7 @@ func TestPolicyGoodRemoveProtector(t *testing.T) { t.Fatal(err) } - pro2, err := ctx.NewProtector(testProtectorName2, goodCallback) + pro2, err := CreateProtector(ctx, testProtectorName2, goodCallback) if err != nil { t.Fatal(err) } @@ -107,7 +107,7 @@ func TestPolicyGoodRemoveProtector(t *testing.T) { t.Fatal(err) } - err = pol.RemoveProtector(pro1) + err = pol.RemoveProtector(pro1.data.ProtectorDescriptor) if err != nil { t.Error(err) } @@ -121,17 +121,17 @@ func TestPolicyBadRemoveProtector(t *testing.T) { t.Fatal(err) } - pro2, err := ctx.NewProtector(testProtectorName2, goodCallback) + pro2, err := CreateProtector(ctx, testProtectorName2, goodCallback) if err != nil { t.Fatal(err) } defer pro2.Wipe() - if pol.RemoveProtector(pro2) == nil { + if pol.RemoveProtector(pro2.data.ProtectorDescriptor) == nil { t.Error("we should not be able to remove a protector we did not add") } - if pol.RemoveProtector(pro1) == nil { + if pol.RemoveProtector(pro1.data.ProtectorDescriptor) == 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 c57d016..4680cba 100644 --- a/actions/protector.go +++ b/actions/protector.go @@ -35,34 +35,16 @@ var ( ErrDuplicateUID = errors.New("there is already a login protector for this user") ) -// ListProtectorData creates a slice of all the data for Protectors on the -// Context's mountpoint. -func (ctx *Context) ListProtectorData() ([]ProtectorData, error) { - descriptors, err := ctx.Mount.ListProtectors() - if err != nil { - return nil, err - } - - data := make([]ProtectorData, len(descriptors)) - for i, descriptor := range descriptors { - data[i], err = ctx.Mount.GetRegularProtector(descriptor) - if err != nil { - return nil, err - } - } - return data, err -} - // 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 (ctx *Context) checkForProtectorWithName(name string) error { - protectors, err := ctx.ListProtectorData() +func checkForProtectorWithName(ctx *Context, name string) error { + options, err := ctx.ListProtectorOptions() if err != nil { return err } - for _, protector := range protectors { - if protector.GetName() == name { + for _, option := range options { + if option.Name() == name { return ErrDuplicateName } } @@ -72,14 +54,13 @@ func (ctx *Context) checkForProtectorWithName(name string) error { // checkForProtectorWithUid returns an error if there is already a login // protector on the filesystem with a specific UID (or if we cannot read the // necessary data). -func (ctx *Context) checkForProtectorWithUID(uid int64) error { - protectors, err := ctx.ListProtectorData() +func checkForProtectorWithUID(ctx *Context, uid int64) error { + options, err := ctx.ListProtectorOptions() if err != nil { return err } - for _, protector := range protectors { - if protector.GetSource() == metadata.SourceType_pam_passphrase && - protector.GetUid() == uid { + for _, option := range options { + if option.Source() == metadata.SourceType_pam_passphrase && option.UID() == uid { return ErrDuplicateUID } } @@ -91,20 +72,19 @@ func (ctx *Context) checkForProtectorWithUID(uid int64) error { // to unlock policies and create new polices. As with the key struct, a // Protector should be wiped after use. type Protector struct { - *Context - data *metadata.ProtectorData - key *crypto.Key + Context *Context + data *metadata.ProtectorData + key *crypto.Key } -// NewProtector creates a protector with a given name (only for custom and raw -// protector types) and uses the provided KeyCallback to get the Key. The -// appropriate data is then stored on the filesystem. On error, nothing is -// changed on the filesystem. -func (ctx *Context) NewProtector(name string, callback KeyCallback) (*Protector, error) { - if !ctx.Config.IsValid() { - return nil, ErrBadConfig +// 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. +func CreateProtector(ctx *Context, name string, keyFn KeyFunc) (*Protector, error) { + if err := ctx.checkContext(); err != nil { + return nil, err } - // Sanity checks for names if ctx.Config.Source == metadata.SourceType_pam_passphrase { // login protectors don't need a name (we use the username instead) @@ -117,7 +97,7 @@ func (ctx *Context) NewProtector(name string, callback KeyCallback) (*Protector, return nil, ErrMissingProtectorName } // we don't want to duplicate naming - if err := ctx.checkForProtectorWithName(name); err != nil { + if err := checkForProtectorWithName(ctx, name); err != nil { return nil, err } } @@ -138,7 +118,7 @@ func (ctx *Context) NewProtector(name string, callback KeyCallback) (*Protector, // UID for this kind of source. protector.data.Uid = int64(os.Getuid()) // Make sure we aren't duplicating protectors - if err := ctx.checkForProtectorWithUID(protector.data.Uid); err != nil { + if err := checkForProtectorWithUID(ctx, protector.data.Uid); err != nil { return nil, err } fallthrough @@ -157,7 +137,7 @@ func (ctx *Context) NewProtector(name string, callback KeyCallback) (*Protector, } protector.data.ProtectorDescriptor = crypto.ComputeDescriptor(protector.key) - if err := protector.Rewrap(callback); err != nil { + if err := protector.Rewrap(keyFn); err != nil { protector.Wipe() return nil, err } @@ -165,13 +145,13 @@ func (ctx *Context) NewProtector(name string, callback KeyCallback) (*Protector, return protector, nil } -// GetProtector retrieves a protector with a specific descriptor. As a key is -// necessary to unlock this Protector, a KeyCallback must also be provided. -func (ctx *Context) GetProtector(descriptor string, callback KeyCallback) (*Protector, error) { - if !ctx.Config.IsValid() { - return nil, ErrBadConfig +// 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} @@ -179,16 +159,39 @@ func (ctx *Context) GetProtector(descriptor string, callback KeyCallback) (*Prot return nil, err } - protector.key, err = unwrapProtectorKey(protector.data, callback) + protector.key, err = unwrapProtectorKey(ProtectorInfo{protector.data}, keyFn) + 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) { + if err := ctx.checkContext(); err != nil { + return nil, err + } + if option.LoadError != nil { + return nil, option.LoadError + } + + // Replace the context if this is a linked protector + if option.LinkedMount != nil { + ctx = &Context{ctx.Config, option.LinkedMount} + } + var err error + protector := &Protector{Context: ctx, data: option.data} + + protector.key, err = unwrapProtectorKey(option.ProtectorInfo, keyFn) return protector, err } // Rewrap updates the data that is wrapping the Protector Key. This is useful if -// a user's password has changed, for example. As a key is necessary to rewrap -// this Protector, a KeyCallback must be provided. If an error is returned, no -// data has been changed. -func (protector *Protector) Rewrap(callback KeyCallback) error { - wrappingKey, err := getWrappingKey(protector.data, callback) +// 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. +func (protector *Protector) Rewrap(keyFn KeyFunc) error { + wrappingKey, err := getWrappingKey(ProtectorInfo{protector.data}, keyFn, false) if err != nil { return err } @@ -206,10 +209,11 @@ func (protector *Protector) Rewrap(callback KeyCallback) error { return err } - return protector.Mount.AddProtector(protector.data) + return protector.Context.Mount.AddProtector(protector.data) } -// Wipe wipes a Protector's internal Key +// 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() } @@ -217,5 +221,5 @@ func (protector *Protector) Wipe() error { // Destroy removes a protector from the filesystem. The internal key should // still be wiped with Wipe(). func (protector *Protector) Destroy() error { - return protector.Mount.RemoveProtector(protector.data.ProtectorDescriptor) + return protector.Context.Mount.RemoveProtector(protector.data.ProtectorDescriptor) } diff --git a/actions/protector_test.go b/actions/protector_test.go index bb59dba..08d9aed 100644 --- a/actions/protector_test.go +++ b/actions/protector_test.go @@ -32,23 +32,23 @@ const testProtectorName2 = testProtectorName + "2" var errCallback = errors.New("bad callback") -func goodCallback(data ProtectorData) (*Key, error) { +func goodCallback(info ProtectorInfo, retry bool) (*Key, error) { return NewFixedLengthKeyFromReader(bytes.NewReader(timingPassphrase), len(timingPassphrase)) } -func badCallback(data ProtectorData) (*Key, error) { +func badCallback(info ProtectorInfo, retry bool) (*Key, error) { return nil, errCallback } // Tests that we can create a valid protector. -func TestNewProtector(t *testing.T) { +func TestCreateProtector(t *testing.T) { ctx, err := makeContext() defer cleaupContext() if err != nil { t.Fatal(err) } - p, err := ctx.NewProtector(testProtectorName, goodCallback) + p, err := CreateProtector(ctx, testProtectorName, goodCallback) if err != nil { t.Error(err) } else { @@ -64,7 +64,7 @@ func TestBadCallback(t *testing.T) { t.Fatal(err) } - p, err := ctx.NewProtector(testProtectorName, badCallback) + p, err := CreateProtector(ctx, testProtectorName, badCallback) if err == nil { p.Wipe() } diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 2141fb8..a3a2880 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -236,7 +236,7 @@ func TestKeyLargeResize(t *testing.T) { // Adds and removes a key with various services. func TestAddRemoveKeys(t *testing.T) { - for _, service := range []string{ServiceDefault, ServiceExt4, ServiceF2FS} { + for _, service := range []string{DefaultService, "ext4:", "f2fs:"} { if err := InsertPolicyKey(fakeValidPolicyKey, fakeValidDescriptor, service); err != nil { t.Error(err) } @@ -248,12 +248,12 @@ func TestAddRemoveKeys(t *testing.T) { // Makes sure a key fails with bad descriptor, policy, or service func TestBadAddKeys(t *testing.T) { - if InsertPolicyKey(fakeInvalidPolicyKey, fakeValidDescriptor, ServiceDefault) == nil { - RemovePolicyKey(fakeValidDescriptor, ServiceDefault) + if InsertPolicyKey(fakeInvalidPolicyKey, fakeValidDescriptor, DefaultService) == nil { + RemovePolicyKey(fakeValidDescriptor, DefaultService) t.Error("InsertPolicyKey should fail with bad policy key") } - if InsertPolicyKey(fakeValidPolicyKey, fakeInvalidDescriptor, ServiceDefault) == nil { - RemovePolicyKey(fakeInvalidDescriptor, ServiceDefault) + if InsertPolicyKey(fakeValidPolicyKey, fakeInvalidDescriptor, DefaultService) == nil { + RemovePolicyKey(fakeInvalidDescriptor, DefaultService) t.Error("InsertPolicyKey should fail with bad descriptor") } if InsertPolicyKey(fakeValidPolicyKey, fakeValidDescriptor, "ext4") == nil { diff --git a/crypto/key.go b/crypto/key.go index bd69b2d..852b213 100644 --- a/crypto/key.go +++ b/crypto/key.go @@ -36,18 +36,17 @@ import ( "fscrypt/util" ) -// Service Prefixes for keyring keys. As of kernel v4.8, all filesystems -// supporting encryption will use FS_KEY_DESC_PREFIX to indicate that a key in -// the keyring should be used with filesystem encryption. However, we also -// include the older service prefixes for legacy compatibility. const ( - ServiceDefault = unix.FS_KEY_DESC_PREFIX - // ServiceExt4 was used before v4.8 for ext4 filesystem encryption. - ServiceExt4 = "ext4:" - // ServiceExt4 was used before v4.6 for F2FS filesystem encryption. - ServiceF2FS = "f2fs:" + // DefaultService is the service which should be used for all encryption + // keys unless not possible for legacy reasons. For ext4 systems before + // v4.8 and f2fs systems before v4.6, filesystem specific services must + // be used (these legacy services will still work with later kernels). + DefaultService = unix.FS_KEY_DESC_PREFIX // keyType is always logon as required by filesystem encryption keyType = "logon" + // Keys need to readable and writable, but hidden from other processes. + keyProtection = unix.PROT_READ | unix.PROT_WRITE + keyMmapFlags = unix.MAP_PRIVATE | unix.MAP_ANONYMOUS ) /* @@ -93,12 +92,6 @@ type Key struct { data []byte } -const ( - // Keys need to readable and writable, but hidden from other processes. - keyProtection = unix.PROT_READ | unix.PROT_WRITE - keyMmapFlags = unix.MAP_PRIVATE | unix.MAP_ANONYMOUS -) - // newBlankKey constructs a blank key of a specified length and returns an error // if we are unable to allocate or lock the necessary memory. func newBlankKey(length int) (*Key, error) { |