aboutsummaryrefslogtreecommitdiff
path: root/cmd/fscrypt/commands.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/fscrypt/commands.go')
-rw-r--r--cmd/fscrypt/commands.go482
1 files changed, 380 insertions, 102 deletions
diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go
index 2f23a0f..3fc68a9 100644
--- a/cmd/fscrypt/commands.go
+++ b/cmd/fscrypt/commands.go
@@ -24,55 +24,72 @@ import (
"fmt"
"log"
"os"
+ "path/filepath"
+ "strings"
"github.com/pkg/errors"
"github.com/urfave/cli"
"github.com/google/fscrypt/actions"
+ "github.com/google/fscrypt/crypto"
"github.com/google/fscrypt/filesystem"
+ "github.com/google/fscrypt/keyring"
"github.com/google/fscrypt/metadata"
"github.com/google/fscrypt/security"
"github.com/google/fscrypt/util"
)
-// Setup is a command which can to global or per-filesystem initialization.
+// Setup is a command which can do global or per-filesystem initialization.
var Setup = cli.Command{
Name: "setup",
ArgsUsage: fmt.Sprintf("[%s]", mountpointArg),
Usage: "perform global setup or filesystem setup",
Description: fmt.Sprintf(`This command creates fscrypt's global config
- file or enables fscrypt on a filesystem.
-
- (1) When used without %[1]s, create the parameters in %[2]s.
- This is primarily used to configure the passphrase hashing
- parameters to the appropriate hardness (as determined by %[3]s).
- Being root is required to write the config file.
-
- (2) When used with %[1]s, enable fscrypt on %[1]s. This involves
- creating the necessary folders on the filesystem which will hold
- the metadata structures. Begin root may be required to create
- these folders.`, mountpointArg, actions.ConfigFileLocation,
+ file and/or prepares a filesystem for use with fscrypt.
+
+ (1) When used without %[1]s, this command creates the global
+ config file %[2]s and the fscrypt metadata directory for the
+ root filesystem (i.e. /.fscrypt). This requires root privileges.
+ The passphrase hashing parameters in %[2]s are automatically set
+ to an appropriate hardness, as determined by %[3]s. The root
+ filesystem's metadata directory is created even if the root
+ filesystem doesn't support encryption itself, since it's where
+ login passphrase protectors are stored.
+
+ (2) When used with %[1]s, this command creates the fscrypt
+ metadata directory for the filesystem mounted at %[1]s. This
+ allows fscrypt to be used on that filesystem, provided that any
+ kernel and filesystem-specific prerequisites are also met (see
+ the README). This may require root privileges.`,
+ mountpointArg, actions.ConfigFileLocation,
shortDisplay(timeTargetFlag)),
- Flags: []cli.Flag{timeTargetFlag, legacyFlag, forceFlag},
+ Flags: []cli.Flag{timeTargetFlag, forceFlag, allUsersSetupFlag},
Action: setupAction,
}
func setupAction(c *cli.Context) error {
- var err error
switch c.NArg() {
case 0:
// Case (1) - global setup
- err = createGlobalConfig(c.App.Writer, actions.ConfigFileLocation)
+ if err := createGlobalConfig(c.App.Writer, actions.ConfigFileLocation); err != nil {
+ return newExitError(c, err)
+ }
+ if err := setupFilesystem(c.App.Writer, actions.LoginProtectorMountpoint); err != nil {
+ if _, ok := err.(*filesystem.ErrAlreadySetup); !ok {
+ return newExitError(c, err)
+ }
+ fmt.Fprintf(c.App.Writer,
+ "Skipping creating %s because it already exists.\n",
+ filepath.Join(actions.LoginProtectorMountpoint, ".fscrypt"))
+ }
case 1:
// Case (2) - filesystem setup
- err = setupFilesystem(c.App.Writer, c.Args().Get(0))
+ if err := setupFilesystem(c.App.Writer, c.Args().Get(0)); err != nil {
+ return newExitError(c, err)
+ }
default:
return expectedArgsErr(c, 1, true)
}
-
- if err != nil {
- return newExitError(c, err)
- }
return nil
}
@@ -90,7 +107,7 @@ var Encrypt = cli.Command{
immediately be used.`, directoryArg, shortDisplay(policyFlag),
shortDisplay(protectorFlag), mountpointArg),
Flags: []cli.Flag{policyFlag, unlockWithFlag, protectorFlag, sourceFlag,
- userFlag, nameFlag, keyFileFlag, skipUnlockFlag},
+ userFlag, nameFlag, keyFileFlag, skipUnlockFlag, noRecoveryFlag},
Action: encryptAction,
}
@@ -104,6 +121,13 @@ func encryptAction(c *cli.Context) error {
return newExitError(c, err)
}
+ // Most people expect that other users can't see their encrypted files
+ // while they're unlocked, so change the directory's mode to 0700.
+ if err := os.Chmod(path, 0700); err != nil {
+ fmt.Fprintf(c.App.Writer, "Warning: unable to chmod %q to 0700 [%v]\n", path, err)
+ // Continue on; don't consider this a fatal error.
+ }
+
if !skipUnlockFlag.Value {
fmt.Fprintf(c.App.Writer,
"%q is now encrypted, unlocked, and ready for use.\n", path)
@@ -115,15 +139,71 @@ func encryptAction(c *cli.Context) error {
return nil
}
+// validateKeyringPrereqs ensures we're ready to add, remove, or get the status
+// of the key for the given encryption policy (if policy != nil) or for the
+// current default encryption policy (if policy == nil).
+func validateKeyringPrereqs(ctx *actions.Context, policy *actions.Policy) error {
+ var policyVersion int64
+ if policy == nil {
+ policyVersion = ctx.Config.Options.PolicyVersion
+ } else {
+ policyVersion = policy.Version()
+ }
+ // If it's a v2 policy, we're good to go, since non-root users can
+ // add/remove v2 policy keys directly to/from the filesystem, where they
+ // are usable by the filesystem on behalf of any process.
+ if policyVersion != 1 {
+ return nil
+ }
+ if ctx.Config.GetUseFsKeyringForV1Policies() {
+ // We'll be using the filesystem keyring, but it's a v1
+ // encryption policy so root is required.
+ if !util.IsUserRoot() {
+ return ErrFsKeyringPerm
+ }
+ return nil
+ }
+ // We'll be using the target user's user keyring, so make sure a user
+ // was explicitly specified if the command is being run as root, and
+ // make sure that user's keyring is accessible.
+ if userFlag.Value == "" && util.IsUserRoot() {
+ return ErrSpecifyUser
+ }
+ if _, err := keyring.UserKeyringID(ctx.TargetUser, true); err != nil {
+ return err
+ }
+ return nil
+}
+
+func writeRecoveryInstructions(recoveryPassphrase *crypto.Key, recoveryProtector *actions.Protector,
+ policy *actions.Policy, dirPath string) error {
+ if recoveryPassphrase == nil {
+ return nil
+ }
+ recoveryFile := filepath.Join(dirPath, "fscrypt_recovery_readme.txt")
+ if err := actions.WriteRecoveryInstructions(recoveryPassphrase, recoveryProtector,
+ policy, recoveryFile); err != nil {
+ return err
+ }
+ msg := fmt.Sprintf(`See %q for important recovery instructions.
+ It is *strongly recommended* to record the recovery passphrase in a
+ secure location; otherwise you will lose access to this directory if you
+ reinstall the operating system or move this filesystem to another
+ system.`, recoveryFile)
+ hdr := "IMPORTANT: "
+ fmt.Print("\n" + hdr + wrapText(msg, len(hdr)) + "\n\n")
+ return nil
+}
+
// encryptPath sets up encryption on path and provisions the policy to the
// keyring unless --skip-unlock is used. On failure, an error is returned, any
// metadata creation is reverted, and the directory is unmodified.
func encryptPath(path string) (err error) {
- target, err := parseUserFlag(!skipUnlockFlag.Value)
+ targetUser, err := parseUserFlag()
if err != nil {
return
}
- ctx, err := actions.NewContextFromPath(path, target)
+ ctx, err := actions.NewContextFromPath(path, targetUser)
if err != nil {
return
}
@@ -132,20 +212,37 @@ func encryptPath(path string) (err error) {
}
var policy *actions.Policy
+ var recoveryPassphrase *crypto.Key
+ var recoveryProtector *actions.Protector
if policyFlag.Value != "" {
log.Printf("getting policy for %q", path)
- policy, err = getPolicyFromFlag(policyFlag.Value, ctx.TargetUser)
+ if policy, err = getPolicyFromFlag(policyFlag.Value, ctx.TargetUser); err != nil {
+ return
+ }
+ defer policy.Lock()
+
+ if !skipUnlockFlag.Value {
+ if err = validateKeyringPrereqs(ctx, policy); err != nil {
+ return
+ }
+ }
} else {
log.Printf("creating policy for %q", path)
+ if !skipUnlockFlag.Value {
+ if err = validateKeyringPrereqs(ctx, nil); err != nil {
+ return
+ }
+ }
+
protector, created, protErr := selectOrCreateProtector(ctx)
- // Successfully created protector should be reverted on failure.
if protErr != nil {
return protErr
}
defer func() {
protector.Lock()
+ // Successfully created protector should be reverted on failure.
if err != nil && created {
protector.Revert()
}
@@ -154,39 +251,70 @@ func encryptPath(path string) (err error) {
if err = protector.Unlock(existingKeyFn); err != nil {
return
}
- policy, err = actions.CreatePolicy(ctx, protector)
- }
- // Successfully created policy should be reverted on failure.
- if err != nil {
- return
- }
- defer func() {
- policy.Lock()
- if err != nil {
- policy.Deprovision()
- policy.Revert()
+ if policy, err = actions.CreatePolicy(ctx, protector); err != nil {
+ return
}
- }()
+ defer func() {
+ policy.Lock()
+ // Successfully created policy should be reverted on failure.
+ if err != nil {
+ policy.Revert()
+ }
+ }()
- // Unlock() first, so if the Unlock() fails the directory isn't changed.
- if !skipUnlockFlag.Value {
+ // Generate a recovery passphrase if needed.
+ if ctx.Mount != protector.Context.Mount && !noRecoveryFlag.Value {
+ if recoveryPassphrase, recoveryProtector, err = actions.AddRecoveryPassphrase(
+ policy, filepath.Base(path)); err != nil {
+ return
+ }
+ defer func() {
+ recoveryPassphrase.Wipe()
+ recoveryProtector.Lock()
+ // Successfully created protector should be reverted on failure.
+ if err != nil {
+ recoveryProtector.Revert()
+ }
+ }()
+ }
+ }
+
+ // Unlock() and Provision() first, so if that if these fail the
+ // directory isn't changed, and also because v2 policies can't be
+ // applied while deprovisioned unless the process is running as root.
+ if !skipUnlockFlag.Value || !policy.CanBeAppliedWithoutProvisioning() {
if err = policy.Unlock(optionFn, existingKeyFn); err != nil {
return
}
if err = policy.Provision(); err != nil {
return
}
+ defer func() {
+ if err != nil || skipUnlockFlag.Value {
+ policy.Deprovision(false)
+ }
+ }()
}
- if err = policy.Apply(path); os.IsPermission(errors.Cause(err)) {
- // EACCES at this point indicates ownership issues.
- err = errors.Wrap(ErrBadOwners, path)
+ if err = policy.Apply(path); err != nil {
+ return
}
- return
+ return writeRecoveryInstructions(recoveryPassphrase, recoveryProtector, policy, path)
}
// checkEncryptable returns an error if the path cannot be encrypted.
func checkEncryptable(ctx *actions.Context, path string) error {
- log.Printf("ensuring %s is an empty and readable directory", path)
+
+ log.Printf("checking whether %q is already encrypted", path)
+ if _, err := metadata.GetPolicy(path); err == nil {
+ return &metadata.ErrAlreadyEncrypted{Path: path}
+ }
+
+ log.Printf("checking whether filesystem %s supports encryption", ctx.Mount.Path)
+ if err := ctx.Mount.CheckSupport(); err != nil {
+ return err
+ }
+
+ log.Printf("checking whether %q is an empty and readable directory", path)
f, err := os.Open(path)
if err != nil {
return err
@@ -196,29 +324,17 @@ func checkEncryptable(ctx *actions.Context, path string) error {
switch names, err := f.Readdirnames(-1); {
case err != nil:
// Could not read directory (might not be a directory)
- log.Print(errors.Wrap(err, path))
- return errors.Wrap(ErrNotEmptyDir, path)
- case len(names) > 0:
- log.Printf("directory %s is not empty", path)
- return errors.Wrap(ErrNotEmptyDir, path)
- }
-
- log.Printf("ensuring %s supports encryption and filesystem is using fscrypt", path)
- switch _, err := actions.GetPolicyFromPath(ctx, path); errors.Cause(err) {
- case metadata.ErrNotEncrypted:
- // We are not encrypted. Finally, we check that the filesystem
- // supports encryption
- return ctx.Mount.CheckSupport()
- case nil:
- // We are encrypted
- return errors.Wrap(metadata.ErrEncrypted, path)
- default:
+ err = errors.Wrap(err, path)
+ log.Print(err)
return err
+ case len(names) > 0:
+ return &ErrDirNotEmpty{path}
}
+ return err
}
// selectOrCreateProtector uses user input (or flags) to either create a new
-// protector or select and existing one. The boolean return value is true if we
+// protector or select an existing one. The boolean return value is true if we
// created a new protector.
func selectOrCreateProtector(ctx *actions.Context) (*actions.Protector, bool, error) {
if protectorFlag.Value != "" {
@@ -262,8 +378,8 @@ var Unlock = cli.Command{
appropriate key into the keyring. This requires unlocking one of
the protectors protecting this directory (either by selecting a
protector or specifying one with %s). This directory will be
- locked again upon reboot, or after running "fscrypt purge" and
- unmounting the corresponding filesystem.`, directoryArg,
+ locked again upon reboot, or after running "fscrypt lock" or
+ "fscrypt purge".`, directoryArg,
shortDisplay(unlockWithFlag)),
Flags: []cli.Flag{unlockWithFlag, keyFileFlag, userFlag},
Action: unlockAction,
@@ -274,12 +390,12 @@ func unlockAction(c *cli.Context) error {
return expectedArgsErr(c, 1, false)
}
- target, err := parseUserFlag(true)
+ targetUser, err := parseUserFlag()
if err != nil {
return newExitError(c, err)
}
path := c.Args().Get(0)
- ctx, err := actions.NewContextFromPath(path, target)
+ ctx, err := actions.NewContextFromPath(path, targetUser)
if err != nil {
return newExitError(c, err)
}
@@ -290,10 +406,15 @@ func unlockAction(c *cli.Context) error {
if err != nil {
return newExitError(c, err)
}
+ // Ensure the keyring is ready.
+ if err = validateKeyringPrereqs(ctx, policy); err != nil {
+ return newExitError(c, err)
+ }
// Check if directory is already unlocked
- if policy.IsProvisioned() {
- log.Printf("policy %s is already provisioned", policy.Descriptor())
- return newExitError(c, errors.Wrapf(ErrPolicyUnlocked, path))
+ if policy.IsProvisionedByTargetUser() {
+ log.Printf("policy %s is already provisioned by %v",
+ policy.Descriptor(), ctx.TargetUser.Username)
+ return newExitError(c, errors.Wrap(ErrDirAlreadyUnlocked, path))
}
if err := policy.Unlock(optionFn, existingKeyFn); err != nil {
@@ -309,6 +430,163 @@ func unlockAction(c *cli.Context) error {
return nil
}
+func dropCachesIfRequested(c *cli.Context, ctx *actions.Context) error {
+ if dropCachesFlag.Value {
+ if err := security.DropFilesystemCache(); err != nil {
+ return err
+ }
+ fmt.Fprintf(c.App.Writer, "Encrypted data removed from filesystem cache.\n")
+ } else {
+ fmt.Fprintf(c.App.Writer, "Filesystem %q should now be unmounted.\n", ctx.Mount.Path)
+ }
+ return nil
+}
+
+// Lock takes an encrypted directory and locks it, undoing Unlock.
+var Lock = cli.Command{
+ Name: "lock",
+ ArgsUsage: directoryArg,
+ Usage: "lock an encrypted directory",
+ Description: fmt.Sprintf(`This command takes %s, an encrypted directory
+ which has been unlocked by fscrypt, and locks the directory by
+ removing the encryption key from the kernel. I.e., it undoes the
+ effect of 'fscrypt unlock'.
+
+ For this to be effective, all files in the directory must first
+ be closed.
+
+ If the directory uses a v1 encryption policy, then the %s=true
+ option may be needed to properly lock it. Root is required for
+ this.
+
+ If the directory uses a v2 encryption policy, then a non-root
+ user can lock it, but only if it's the same user who unlocked it
+ originally and if no other users have unlocked it too.
+
+ WARNING: even after the key has been removed, decrypted data may
+ still be present in freed memory, where it may still be
+ recoverable by an attacker who compromises system memory. To be
+ fully safe, you must reboot with a power cycle.`,
+ directoryArg, shortDisplay(dropCachesFlag)),
+ Flags: []cli.Flag{dropCachesFlag, userFlag, allUsersLockFlag},
+ Action: lockAction,
+}
+
+func lockAction(c *cli.Context) error {
+ if c.NArg() != 1 {
+ return expectedArgsErr(c, 1, false)
+ }
+
+ targetUser, err := parseUserFlag()
+ if err != nil {
+ return newExitError(c, err)
+ }
+ path := c.Args().Get(0)
+ ctx, err := actions.NewContextFromPath(path, targetUser)
+ if err != nil {
+ return newExitError(c, err)
+ }
+
+ log.Printf("performing sanity checks")
+ // Ensure path is encrypted and filesystem is using fscrypt.
+ policy, err := actions.GetPolicyFromPath(ctx, path)
+ if err != nil {
+ return newExitError(c, err)
+ }
+ // Ensure the keyring is ready.
+ if err = validateKeyringPrereqs(ctx, policy); err != nil {
+ return newExitError(c, err)
+ }
+ // Check for permission to drop caches, if it may be needed.
+ if policy.NeedsUserKeyring() && dropCachesFlag.Value && !util.IsUserRoot() {
+ return newExitError(c, ErrDropCachesPerm)
+ }
+
+ if err = policy.Deprovision(allUsersLockFlag.Value); err != nil {
+ switch err {
+ case keyring.ErrKeyNotPresent:
+ break
+ case keyring.ErrKeyAddedByOtherUsers:
+ return newExitError(c, &ErrDirUnlockedByOtherUsers{path})
+ case keyring.ErrKeyFilesOpen:
+ return newExitError(c, &ErrDirFilesOpen{path})
+ default:
+ return newExitError(c, err)
+ }
+ // Key is no longer present. Normally that means the directory
+ // is already locked; in that case we exit with an error. But
+ // if the policy uses the user keyring (v1 policies only), then
+ // the directory might have been incompletely locked earlier,
+ // due to open files. Try to detect that case and finish
+ // locking the directory by dropping caches again.
+ if !policy.NeedsUserKeyring() || !isDirUnlockedHeuristic(path) {
+ log.Printf("policy %s is already fully deprovisioned", policy.Descriptor())
+ return newExitError(c, errors.Wrap(ErrDirAlreadyLocked, path))
+ }
+ }
+
+ if policy.NeedsUserKeyring() {
+ if err = dropCachesIfRequested(c, ctx); err != nil {
+ return newExitError(c, err)
+ }
+ if isDirUnlockedHeuristic(path) {
+ return newExitError(c, &ErrDirFilesOpen{path})
+ }
+ }
+
+ fmt.Fprintf(c.App.Writer, "%q is now locked.\n", path)
+ return nil
+}
+
+func isPossibleNoKeyName(filename string) bool {
+ // No-key names are at least 22 bytes long, since they are
+ // base64-encoded and ciphertext filenames are at least 16 bytes.
+ if len(filename) < 22 {
+ return false
+ }
+ // On the latest kernels, no-key names contain only base64url characters
+ // (A-Z, a-z, 0-9, -, and _). On older kernels, the + and , characters
+ // were used too. Allow all of these characters.
+ validChars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_+,"
+ for _, char := range filename {
+ if !strings.ContainsRune(validChars, char) {
+ return false
+ }
+ }
+ return true
+}
+
+// isDirUnlockedHeuristic returns true if the directory is definitely still
+// unlocked. This is the case if we can create a subdirectory or if the
+// directory contains filenames that aren't valid no-key names. It returns
+// false if the directory is probably locked (though it could also be unlocked).
+//
+// This is only useful if the directory's policy uses the user keyring, since
+// otherwise the status can be easily found via the filesystem keyring.
+func isDirUnlockedHeuristic(dirPath string) bool {
+ subdirPath := filepath.Join(dirPath, "fscrypt-is-dir-unlocked")
+ if err := os.Mkdir(subdirPath, 0700); err == nil {
+ os.Remove(subdirPath)
+ return true
+ }
+ dir, err := os.Open(dirPath)
+ if err != nil {
+ return false
+ }
+ defer dir.Close()
+
+ names, err := dir.Readdirnames(-1)
+ if err != nil {
+ return false
+ }
+ for _, name := range names {
+ if !isPossibleNoKeyName(name) {
+ return true
+ }
+ }
+ return false
+}
+
// Purge removes all the policy keys from the keyring (also need unmount).
var Purge = cli.Command{
Name: "purge",
@@ -358,15 +636,18 @@ func purgeAction(c *cli.Context) error {
}
}
- target, err := parseUserFlag(true)
+ targetUser, err := parseUserFlag()
if err != nil {
return newExitError(c, err)
}
mountpoint := c.Args().Get(0)
- ctx, err := actions.NewContextFromMountpoint(mountpoint, target)
+ ctx, err := actions.NewContextFromMountpoint(mountpoint, targetUser)
if err != nil {
return newExitError(c, err)
}
+ if err = validateKeyringPrereqs(ctx, nil); err != nil {
+ return newExitError(c, err)
+ }
question := fmt.Sprintf("Purge all policy keys from %q", ctx.Mount.Path)
if dropCachesFlag.Value {
@@ -382,13 +663,8 @@ func purgeAction(c *cli.Context) error {
}
fmt.Fprintf(c.App.Writer, "Policies purged for %q.\n", ctx.Mount.Path)
- if dropCachesFlag.Value {
- if err = security.DropFilesystemCache(); err != nil {
- return newExitError(c, err)
- }
- fmt.Fprintf(c.App.Writer, "Encrypted data removed filesystem cache.\n")
- } else {
- fmt.Fprintf(c.App.Writer, "Filesystem %q should now be unmounted.\n", ctx.Mount.Path)
+ if err = dropCachesIfRequested(c, ctx); err != nil {
+ return newExitError(c, err)
}
return nil
}
@@ -429,17 +705,15 @@ func statusAction(c *cli.Context) error {
err = writeGlobalStatus(c.App.Writer)
case 1:
path := c.Args().Get(0)
- ctx, mntErr := actions.NewContextFromMountpoint(path, nil)
- switch errors.Cause(mntErr) {
- case nil:
+ var ctx *actions.Context
+ ctx, err = actions.NewContextFromMountpoint(path, nil)
+ if err == nil {
// Case (2) - mountpoint status
err = writeFilesystemStatus(c.App.Writer, ctx)
- case filesystem.ErrNotAMountpoint:
+ } else if _, ok := err.(*filesystem.ErrNotAMountpoint); ok {
// Case (3) - file or directory status
err = writePathStatus(c.App.Writer, path)
- default:
- err = mntErr
}
default:
return expectedArgsErr(c, 1, true)
@@ -474,7 +748,7 @@ var Metadata = cli.Command{
(4) Changing the protector protecting a policy using the
"add-protector-to-policy" and "remove-protector-from-policy"
subcommands.`,
- Subcommands: []cli.Command{createMetadata, destoryMetadata, changePassphrase,
+ Subcommands: []cli.Command{createMetadata, destroyMetadata, changePassphrase,
addProtectorToPolicy, removeProtectorFromPolicy, dumpMetadata},
}
@@ -508,18 +782,18 @@ func createProtectorAction(c *cli.Context) error {
return expectedArgsErr(c, 1, false)
}
- target, err := parseUserFlag(false)
+ targetUser, err := parseUserFlag()
if err != nil {
return newExitError(c, err)
}
mountpoint := c.Args().Get(0)
- ctx, err := actions.NewContextFromMountpoint(mountpoint, target)
+ ctx, err := actions.NewContextFromMountpoint(mountpoint, targetUser)
if err != nil {
return newExitError(c, err)
}
prompt := fmt.Sprintf("Create new protector on %q", ctx.Mount.Path)
- if err := askConfirmation(prompt, true, ""); err != nil {
+ if err = askConfirmation(prompt, true, ""); err != nil {
return newExitError(c, err)
}
@@ -537,8 +811,8 @@ func createProtectorAction(c *cli.Context) error {
var createPolicy = cli.Command{
Name: "policy",
ArgsUsage: fmt.Sprintf("%s %s", mountpointArg, shortDisplay(protectorFlag)),
- Usage: "create a new protector on a filesystem",
- Description: fmt.Sprintf(`This command creates a new protector on %s
+ Usage: "create a new policy on a filesystem",
+ Description: fmt.Sprintf(`This command creates a new policy on %s
that has not (yet) been applied to any directory. After
creation, the user can use %s with "fscrypt encrypt" to encrypt
a directory with this new policy. As all policies must be
@@ -561,20 +835,20 @@ func createPolicyAction(c *cli.Context) error {
return newExitError(c, err)
}
- if err := checkRequiredFlags(c, []*stringFlag{protectorFlag}); err != nil {
+ if err = checkRequiredFlags(c, []*stringFlag{protectorFlag}); err != nil {
return err
}
protector, err := getProtectorFromFlag(protectorFlag.Value, ctx.TargetUser)
if err != nil {
return newExitError(c, err)
}
- if err := protector.Unlock(existingKeyFn); err != nil {
+ if err = protector.Unlock(existingKeyFn); err != nil {
return newExitError(c, err)
}
defer protector.Lock()
prompt := fmt.Sprintf("Create new policy on %q", ctx.Mount.Path)
- if err := askConfirmation(prompt, true, ""); err != nil {
+ if err = askConfirmation(prompt, true, ""); err != nil {
return newExitError(c, err)
}
@@ -589,7 +863,7 @@ func createPolicyAction(c *cli.Context) error {
return nil
}
-var destoryMetadata = cli.Command{
+var destroyMetadata = cli.Command{
Name: "destroy",
ArgsUsage: fmt.Sprintf("[%s | %s | %s]", shortDisplay(protectorFlag),
shortDisplay(policyFlag), mountpointArg),
@@ -616,10 +890,10 @@ var destoryMetadata = cli.Command{
shortDisplay(protectorFlag), shortDisplay(policyFlag),
mountpointArg),
Flags: []cli.Flag{protectorFlag, policyFlag, forceFlag},
- Action: destoryMetadataAction,
+ Action: destroyMetadataAction,
}
-func destoryMetadataAction(c *cli.Context) error {
+func destroyMetadataAction(c *cli.Context) error {
switch c.NArg() {
case 0:
switch {
@@ -759,6 +1033,9 @@ func addProtectorAction(c *cli.Context) error {
}
// Sanity check before unlocking everything
if err := policy.AddProtector(protector); errors.Cause(err) != actions.ErrLocked {
+ if err == nil {
+ err = errors.New("policy and protector are not locked")
+ }
return newExitError(c, err)
}
@@ -806,29 +1083,30 @@ func removeProtectorAction(c *cli.Context) error {
return err
}
- // We do not need to unlock anything for this operation
- protector, err := getProtectorFromFlag(protectorFlag.Value, nil)
+ // We only need the protector descriptor, not the protector itself.
+ ctx, protectorDescriptor, err := parseMetadataFlag(protectorFlag.Value, nil)
if err != nil {
return newExitError(c, err)
}
- policy, err := getPolicyFromFlag(policyFlag.Value, protector.Context.TargetUser)
+ // We don't need to unlock the policy for this operation.
+ policy, err := getPolicyFromFlag(policyFlag.Value, ctx.TargetUser)
if err != nil {
return newExitError(c, err)
}
prompt := fmt.Sprintf("Stop protecting policy %s with protector %s?",
- policy.Descriptor(), protector.Descriptor())
+ policy.Descriptor(), protectorDescriptor)
warning := "All files using this policy will NO LONGER be accessible with this protector!!"
if err := askConfirmation(prompt, false, warning); err != nil {
return newExitError(c, err)
}
- if err := policy.RemoveProtector(protector); err != nil {
+ if err := policy.RemoveProtector(protectorDescriptor); err != nil {
return newExitError(c, err)
}
fmt.Fprintf(c.App.Writer, "Protector %s no longer protecting policy %s.\n",
- protector.Descriptor(), policy.Descriptor())
+ protectorDescriptor, policy.Descriptor())
return nil
}