aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--actions/callback.go105
-rw-r--r--actions/config.go27
-rw-r--r--actions/config_test.go4
-rw-r--r--actions/context.go51
-rw-r--r--actions/context_test.go2
-rw-r--r--actions/policy.go291
-rw-r--r--actions/policy_test.go18
-rw-r--r--actions/protector.go116
-rw-r--r--actions/protector_test.go10
-rw-r--r--crypto/crypto_test.go10
-rw-r--r--crypto/key.go23
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) {