aboutsummaryrefslogtreecommitdiff
path: root/cmd/fscrypt
diff options
context:
space:
mode:
authorJoe Richey joerichey@google.com <joerichey@google.com>2017-10-17 02:39:07 -0700
committerJoseph Richey <joerichey94@gmail.com>2017-10-19 02:22:26 -0700
commitbab7dfdf68075b345e4de3ae79ea685ca884668f (patch)
treeeced4ecdfd37aae1a92f28ba0864837db1205eb0 /cmd/fscrypt
parentb5cc60b2b974645f0d09721c292cd243d049cbcf (diff)
Move around and fscrypt refactor
Diffstat (limited to 'cmd/fscrypt')
-rw-r--r--cmd/fscrypt/commands.go378
-rw-r--r--cmd/fscrypt/errors.go60
-rw-r--r--cmd/fscrypt/flags.go23
-rw-r--r--cmd/fscrypt/fscrypt.go188
-rw-r--r--cmd/fscrypt/prompt.go29
-rw-r--r--cmd/fscrypt/strings.go52
6 files changed, 316 insertions, 414 deletions
diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go
index e8d32da..4eec1e6 100644
--- a/cmd/fscrypt/commands.go
+++ b/cmd/fscrypt/commands.go
@@ -29,90 +29,46 @@ import (
"github.com/urfave/cli"
"github.com/google/fscrypt/actions"
- "github.com/google/fscrypt/filesystem"
"github.com/google/fscrypt/metadata"
- "github.com/google/fscrypt/security"
- "github.com/google/fscrypt/util"
)
-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,
- shortDisplay(timeTargetFlag)),
- Flags: []cli.Flag{timeTargetFlag, legacyFlag, forceFlag},
- 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)
- case 1:
- // Case (2) - filesystem setup
- err = setupFilesystem(c.App.Writer, c.Args().Get(0))
- default:
- return expectedArgsErr(c, 1, true)
- }
-
- if err != nil {
- return newExitError(c, err)
- }
- return nil
-}
-
-// Encrypt performs the functions of setupDirectory and Unlock in one command.
-var Encrypt = cli.Command{
- Name: "encrypt",
- ArgsUsage: directoryArg,
- Usage: "enable filesystem encryption for a directory",
- Description: fmt.Sprintf(`This command enables filesystem encryption on
- %[1]s. This may involve creating a new policy (if one is not
- specified with %[2]s) or a new protector (if one is not
- specified with %[3]s). This command requires that the
- corresponding filesystem has been setup with "fscrypt setup
- %[4]s". By default, after %[1]s is setup, it is unlocked and can
- immediately be used.`, directoryArg, shortDisplay(policyFlag),
- shortDisplay(protectorFlag), mountpointArg),
- Flags: []cli.Flag{policyFlag, unlockWithFlag, protectorFlag, sourceFlag,
- userFlag, nameFlag, keyFileFlag, skipUnlockFlag},
- Action: encryptAction,
-}
-
-func encryptAction(c *cli.Context) error {
- if c.NArg() != 1 {
- return expectedArgsErr(c, 1, false)
- }
-
- path := c.Args().Get(0)
- if err := encryptPath(path); err != nil {
- return newExitError(c, err)
- }
-
- if !skipUnlockFlag.Value {
- fmt.Fprintf(c.App.Writer,
- "%q is now encrypted, unlocked, and ready for use.\n", path)
- } else {
- fmt.Fprintf(c.App.Writer,
- "%q is now encrypted, but it is still locked.\n", path)
- fmt.Fprintln(c.App.Writer, `It can be unlocked with "fscrypt unlock".`)
- }
- return nil
-}
+// 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,
+// shortDisplay(timeTargetFlag)),
+// Flags: []cli.Flag{timeTargetFlag, legacyFlag, forceFlag},
+// Action: setupAction,
+// }
+
+// var Encrypt = cli.Command{
+// Name: "encrypt",
+// ArgsUsage: directoryArg,
+// Usage: "enable filesystem encryption for a directory",
+// Description: fmt.Sprintf(`This command enables filesystem encryption on
+// %[1]s. This may involve creating a new policy (if one is not
+// specified with %[2]s) or a new protector (if one is not
+// specified with %[3]s). This command requires that the
+// corresponding filesystem has been setup with "fscrypt setup
+// %[4]s". By default, after %[1]s is setup, it is unlocked and can
+// immediately be used.`, directoryArg, shortDisplay(policyFlag),
+// shortDisplay(protectorFlag), mountpointArg),
+// Flags: []cli.Flag{policyFlag, unlockWithFlag, protectorFlag, sourceFlag,
+// userFlag, nameFlag, keyFileFlag, skipUnlockFlag},
+// Action: encryptAction,
+// }
// 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
@@ -251,204 +207,114 @@ func selectOrCreateProtector(ctx *actions.Context) (*actions.Protector, bool, er
return protector, false, err
}
-// Unlock takes an encrypted directory and unlocks it for reading and writing.
-var Unlock = cli.Command{
- Name: "unlock",
- ArgsUsage: directoryArg,
- Usage: "unlock an encrypted directory",
- Description: fmt.Sprintf(`This command takes %s, a directory setup for
- use with fscrypt, and unlocks the directory by passing the
- 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,
- shortDisplay(unlockWithFlag)),
- Flags: []cli.Flag{unlockWithFlag, keyFileFlag, userFlag},
- Action: unlockAction,
-}
-
-func unlockAction(c *cli.Context) error {
- if c.NArg() != 1 {
- return expectedArgsErr(c, 1, false)
- }
-
+// var Unlock = cli.Command{
+// Name: "unlock",
+// ArgsUsage: directoryArg,
+// Usage: "unlock an encrypted directory",
+// Description: fmt.Sprintf(`This command takes %s, a directory setup for
+// use with fscrypt, and unlocks the directory by passing the
+// 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,
+// shortDisplay(unlockWithFlag)),
+// Flags: []cli.Flag{unlockWithFlag, keyFileFlag, userFlag},
+// Action: unlockAction,
+// }
+
+func unlockPath(path string) error {
target, err := parseUserFlag(true)
if err != nil {
- return newExitError(c, err)
+ return err
}
- path := c.Args().Get(0)
ctx, err := actions.NewContextFromPath(path, target)
if err != nil {
- return newExitError(c, err)
+ return 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)
+ return 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))
+ return errors.Wrapf(ErrPolicyUnlocked, path)
}
if err := policy.Unlock(optionFn, existingKeyFn); err != nil {
- return newExitError(c, err)
+ return err
}
defer policy.Lock()
- if err := policy.Provision(); err != nil {
- return newExitError(c, err)
- }
-
- fmt.Fprintf(c.App.Writer, "%q is now unlocked and ready for use.\n", path)
- return nil
-}
-
-// Purge removes all the policy keys from the keyring (also need unmount).
-var Purge = cli.Command{
- Name: "purge",
- ArgsUsage: mountpointArg,
- Usage: "Remove a filesystem's keys",
- Description: fmt.Sprintf(`This command removes a user's policy keys for
- directories on %[1]s. This is intended to lock all files and
- directories encrypted by the user on %[1]s, in that unlocking
- them for reading will require providing a key again. However,
- there are four important things to note about this command:
-
- (1) When run with the default options, this command also clears
- the reclaimable dentries and inodes, so that the encrypted files
- and directories will no longer be visible. However, this
- requires root privileges. Note that any open file descriptors to
- plaintext data will not be affected by this command.
-
- (2) When run with %[2]s=false, the keyring is cleared and root
- permissions are not required, but recently accessed encrypted
- directories and files will remain cached for some time. Because
- of this, after purging a filesystem's keys in this manner, it
- is recommended to unmount the filesystem.
-
- (3) When run as root, this command removes the policy keys for
- all users. However, this will only work if the PAM module has
- been enabled. Otherwise, only root's keys may be removed.
-
- (4) Even after unmounting the filesystem or clearing the
- caches, the kernel may keep contents of files in memory. This
- means direct memory access (either though physical compromise or
- a kernel exploit) could compromise encrypted data. This weakness
- can be eliminated by cycling the power or mitigated by using
- page cache and slab cache poisoning.`, mountpointArg,
- shortDisplay(dropCachesFlag)),
- Flags: []cli.Flag{forceFlag, dropCachesFlag, userFlag},
- Action: purgeAction,
+ return policy.Provision()
}
-func purgeAction(c *cli.Context) error {
- if c.NArg() != 1 {
- return expectedArgsErr(c, 1, false)
- }
-
- if dropCachesFlag.Value {
- if !util.IsUserRoot() {
- return newExitError(c, ErrDropCachesPerm)
- }
- }
-
- target, err := parseUserFlag(true)
- if err != nil {
- return newExitError(c, err)
- }
- mountpoint := c.Args().Get(0)
- ctx, err := actions.NewContextFromMountpoint(mountpoint, target)
- if err != nil {
- return newExitError(c, err)
- }
-
- question := fmt.Sprintf("Purge all policy keys from %q", ctx.Mount.Path)
- if dropCachesFlag.Value {
- question += " and drop global inode cache"
- }
- warning := "Encrypted data on this filesystem will be inaccessible until unlocked again!!"
- if err = askConfirmation(question+"?", false, warning); err != nil {
- return newExitError(c, err)
- }
-
- if err = actions.PurgeAllPolicies(ctx); err != nil {
- return newExitError(c, err)
- }
- 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)
- }
- return nil
-}
-
-// Status is a command with three subcommands relating to printing out status.
-var Status = cli.Command{
- Name: "status",
- ArgsUsage: fmt.Sprintf("[%s]", pathArg),
- Usage: "print the global, filesystem, or file status",
- Description: fmt.Sprintf(`This command prints out the global,
- per-filesystem, or per-file status.
-
- (1) When used without %[1]s, print all of the currently visible
- filesystems which support use with fscrypt. For each of
- the filesystems, this command also notes if they are actually
- being used by fscrypt. This command will fail if no there is no
- support for fscrypt anywhere on the system.
-
- (2) When %[1]s is a filesystem mountpoint, list information
- about all the policies and protectors which exist on %[1]s. This
- command will fail if %[1]s is not being used with fscrypt. For
- each policy, this command also notes if the policy is currently
- unlocked.
-
- (3) When %[1]s is just a normal path, print information about
- the policy being used on %[1]s and the protectors protecting
- this file or directory. This command will fail if %[1]s is not
- setup for encryption with fscrypt.`, pathArg),
- Action: statusAction,
-}
-
-func statusAction(c *cli.Context) error {
- var err error
-
- switch c.NArg() {
- case 0:
- // Case (1) - global status
- err = writeGlobalStatus(c.App.Writer)
- case 1:
- path := c.Args().Get(0)
- ctx, mntErr := actions.NewContextFromMountpoint(path, nil)
-
- switch errors.Cause(mntErr) {
- case nil:
- // Case (2) - mountpoint status
- err = writeFilesystemStatus(c.App.Writer, ctx)
- case filesystem.ErrNotAMountpoint:
- // Case (3) - file or directory status
- err = writePathStatus(c.App.Writer, path)
- default:
- err = mntErr
- }
- default:
- return expectedArgsErr(c, 1, true)
- }
-
- if err != nil {
- return newExitError(c, err)
- }
- return nil
-}
+// var Purge = cli.Command{
+// Name: "purge",
+// ArgsUsage: mountpointArg,
+// Usage: "Remove a filesystem's keys",
+// Description: fmt.Sprintf(`This command removes a user's policy keys for
+// directories on %[1]s. This is intended to lock all files and
+// directories encrypted by the user on %[1]s, in that unlocking
+// them for reading will require providing a key again. However,
+// there are four important things to note about this command:
+
+// (1) When run with the default options, this command also clears
+// the reclaimable dentries and inodes, so that the encrypted files
+// and directories will no longer be visible. However, this
+// requires root privileges. Note that any open file descriptors to
+// plaintext data will not be affected by this command.
+
+// (2) When run with %[2]s=false, the keyring is cleared and root
+// permissions are not required, but recently accessed encrypted
+// directories and files will remain cached for some time. Because
+// of this, after purging a filesystem's keys in this manner, it
+// is recommended to unmount the filesystem.
+
+// (3) When run as root, this command removes the policy keys for
+// all users. However, this will only work if the PAM module has
+// been enabled. Otherwise, only root's keys may be removed.
+
+// (4) Even after unmounting the filesystem or clearing the
+// caches, the kernel may keep contents of files in memory. This
+// means direct memory access (either though physical compromise or
+// a kernel exploit) could compromise encrypted data. This weakness
+// can be eliminated by cycling the power or mitigated by using
+// page cache and slab cache poisoning.`, mountpointArg,
+// shortDisplay(dropCachesFlag)),
+// Flags: []cli.Flag{forceFlag, dropCachesFlag, userFlag},
+// Action: purgeAction,
+// }
+
+// var Status = cli.Command{
+// Name: "status",
+// ArgsUsage: fmt.Sprintf("[%s]", pathArg),
+// Usage: "print the global, filesystem, or file status",
+// Description: fmt.Sprintf(`This command prints out the global,
+// per-filesystem, or per-file status.
+
+// (1) When used without %[1]s, print all of the currently visible
+// filesystems which support use with fscrypt. For each of
+// the filesystems, this command also notes if they are actually
+// being used by fscrypt. This command will fail if no there is no
+// support for fscrypt anywhere on the system.
+
+// (2) When %[1]s is a filesystem mountpoint, list information
+// about all the policies and protectors which exist on %[1]s. This
+// command will fail if %[1]s is not being used with fscrypt. For
+// each policy, this command also notes if the policy is currently
+// unlocked.
+
+// (3) When %[1]s is just a normal path, print information about
+// the policy being used on %[1]s and the protectors protecting
+// this file or directory. This command will fail if %[1]s is not
+// setup for encryption with fscrypt.`, pathArg),
+// Action: statusAction,
+// }
// Metadata is a collection of commands for manipulating the metadata files.
var Metadata = cli.Command{
diff --git a/cmd/fscrypt/errors.go b/cmd/fscrypt/errors.go
index 4ce6133..1c7ee75 100644
--- a/cmd/fscrypt/errors.go
+++ b/cmd/fscrypt/errors.go
@@ -23,10 +23,8 @@ package main
import (
"fmt"
- "unicode/utf8"
"github.com/pkg/errors"
- "github.com/urfave/cli"
"github.com/google/fscrypt/actions"
"github.com/google/fscrypt/crypto"
@@ -41,8 +39,6 @@ const failureExitCode = 1
// Various errors used for the top level user interface
var (
- ErrCanceled = errors.New("operation canceled")
- ErrNoDesctructiveOps = errors.New("operation would be destructive")
ErrMaxPassphrase = util.SystemError("max passphrase length exceeded")
ErrInvalidSource = errors.New("invalid source type")
ErrPassphraseMismatch = errors.New("entered passphrases do not match")
@@ -51,7 +47,6 @@ var (
ErrSpecifyKeyFile = errors.New("no key file specified")
ErrKeyFileLength = errors.Errorf("key file must be %d bytes", metadata.InternalKeyLen)
ErrAllLoadsFailed = errors.New("could not load any protectors")
- ErrMustBeRoot = errors.New("this command must be run as root")
ErrPolicyUnlocked = errors.New("this file or directory is already unlocked")
ErrBadOwners = errors.New("you do not own this directory")
ErrNotEmptyDir = errors.New("not an empty directory")
@@ -63,12 +58,8 @@ var (
var loadHelpText = fmt.Sprintf("You may need to mount a linked filesystem. Run with %s for more information.", shortDisplay(verboseFlag))
-// getFullName returns the full name of the application or command being used.
-func getFullName(c *cli.Context) string {
- if c.Command.HelpName != "" {
- return c.Command.HelpName
- }
- return c.App.HelpName
+var fscryptHelpTextMap = map[error]string{
+ actions.ErrBadConfigFile: `Run "sudo fscrypt setup" to recreate the file.`,
}
// getErrorSuggestions returns a string containing suggestions about how to fix
@@ -99,8 +90,6 @@ func getErrorSuggestions(err error) string {
return fmt.Sprintf(`You can only use %s to access the user
keyring of another user if you are running as root.`,
shortDisplay(userFlag))
- case actions.ErrBadConfigFile:
- return `Run "sudo fscrypt setup" to recreate the file.`
case actions.ErrNoConfigFile:
return `Run "sudo fscrypt setup" to create the file.`
case actions.ErrMissingPolicyMetadata:
@@ -146,48 +135,3 @@ func getErrorSuggestions(err error) string {
return ""
}
}
-
-// newExitError creates a new error for a given context and normal error. The
-// returned error prepends the name of the relevant command and will make
-// fscrypt return a non-zero exit value.
-func newExitError(c *cli.Context, err error) error {
- // Prepend the full name and append suggestions (if any)
- fullNamePrefix := getFullName(c) + ": "
- message := fullNamePrefix + wrapText(err.Error(), utf8.RuneCountInString(fullNamePrefix))
-
- if suggestion := getErrorSuggestions(err); suggestion != "" {
- message += "\n\n" + wrapText(suggestion, 0)
- }
-
- return cli.NewExitError(message, failureExitCode)
-}
-
-// expectedArgsErr creates a usage error for the incorrect number of arguments
-// being specified. atMost should be true only if any number of arguments from 0
-// to expectedArgs would be acceptable.
-func expectedArgsErr(c *cli.Context, expectedArgs int, atMost bool) error {
- message := "expected "
- if atMost {
- message += "at most "
- }
- message += fmt.Sprintf("%s, got %s",
- pluralize(expectedArgs, "argument"), pluralize(c.NArg(), "argument"))
- return &usageError{c, message}
-}
-
-// onUsageError is a function handler for the application and each command.
-func onUsageError(c *cli.Context, err error, _ bool) error {
- return &usageError{c, err.Error()}
-}
-
-// checkRequiredFlags makes sure that all of the specified string flags have
-// been given nonempty values. Returns a usage error on failure.
-func checkRequiredFlags(c *cli.Context, flags []*stringFlag) error {
- for _, flag := range flags {
- if flag.Value == "" {
- message := fmt.Sprintf("required flag %s not provided", shortDisplay(flag))
- return &usageError{c, message}
- }
- }
- return nil
-}
diff --git a/cmd/fscrypt/flags.go b/cmd/fscrypt/flags.go
index 69126bc..2bf7f73 100644
--- a/cmd/fscrypt/flags.go
+++ b/cmd/fscrypt/flags.go
@@ -29,25 +29,26 @@ import (
"time"
"github.com/google/fscrypt/actions"
+ "github.com/google/fscrypt/cmd"
"github.com/google/fscrypt/security"
"github.com/google/fscrypt/util"
)
// Bool flags: used to switch some behavior on or off
var (
- legacyFlag = &boolFlag{
+ legacyFlag = &cmd.BoolFlag{
Name: "legacy",
Usage: `Allow for support of older kernels with ext4 (before
v4.8) and F2FS (before v4.6) filesystems.`,
Default: true,
}
- skipUnlockFlag = &boolFlag{
+ skipUnlockFlag = &cmd.BoolFlag{
Name: "skip-unlock",
Usage: `Leave the directory in a locked state after setup.
"fscrypt unlock" will need to be run in order to use the
directory.`,
}
- dropCachesFlag = &boolFlag{
+ dropCachesFlag = &cmd.BoolFlag{
Name: "drop-caches",
Usage: `After purging the keys from the keyring, drop the
associated caches for the purge to take effect. Without
@@ -59,7 +60,7 @@ var (
// Option flags: used to specify options instead of being prompted for them
var (
- timeTargetFlag = &durationFlag{
+ timeTargetFlag = &cmd.DurationFlag{
Name: "time",
ArgName: "TIME",
Usage: `Set the global options so that passphrase hashing takes
@@ -69,7 +70,7 @@ var (
units are "ms", "s", "m", and "h".`,
Default: 1 * time.Second,
}
- sourceFlag = &stringFlag{
+ sourceFlag = &cmd.StringFlag{
Name: "source",
ArgName: "SOURCE",
Usage: fmt.Sprintf(`New protectors will have type SOURCE. SOURCE
@@ -78,14 +79,14 @@ var (
the source, with a default pulled from %s.`,
actions.ConfigFileLocation),
}
- nameFlag = &stringFlag{
+ nameFlag = &cmd.StringFlag{
Name: "name",
ArgName: "PROTECTOR_NAME",
Usage: `New custom_passphrase and raw_key protectors will be
named PROTECTOR_NAME. If not specified, the user will be
prompted for a name.`,
}
- keyFileFlag = &stringFlag{
+ keyFileFlag = &cmd.StringFlag{
Name: "key",
ArgName: "FILE",
Usage: `Use the contents of FILE as the wrapping key when
@@ -93,20 +94,20 @@ var (
formatted as raw binary and should be exactly 32 bytes
long.`,
}
- userFlag = &stringFlag{
+ userFlag = &cmd.StringFlag{
Name: "user",
ArgName: "USERNAME",
Usage: `Specifiy which user should be used for login passphrases
or to which user's keyring keys should be provisioned.`,
}
- protectorFlag = &stringFlag{
+ protectorFlag = &cmd.StringFlag{
Name: "protector",
ArgName: "MOUNTPOINT:ID",
Usage: `Specify an existing protector on filesystem MOUNTPOINT
with protector descriptor ID which should be used in the
command.`,
}
- unlockWithFlag = &stringFlag{
+ unlockWithFlag = &cmd.StringFlag{
Name: "unlock-with",
ArgName: "MOUNTPOINT:ID",
Usage: `Specify an existing protector on filesystem MOUNTPOINT
@@ -116,7 +117,7 @@ var (
multiple protectors. If not specified, the user will be
prompted for a protector.`,
}
- policyFlag = &stringFlag{
+ policyFlag = &cmd.StringFlag{
Name: "policy",
ArgName: "MOUNTPOINT:ID",
Usage: `Specify an existing policy on filesystem MOUNTPOINT with
diff --git a/cmd/fscrypt/fscrypt.go b/cmd/fscrypt/fscrypt.go
index f1649fc..65da7cb 100644
--- a/cmd/fscrypt/fscrypt.go
+++ b/cmd/fscrypt/fscrypt.go
@@ -27,9 +27,35 @@ package main
import (
"fmt"
+ "github.com/google/fscrypt/filesystem"
+ "github.com/google/fscrypt/security"
+ "github.com/pkg/errors"
+
+ "github.com/google/fscrypt/actions"
+
"github.com/google/fscrypt/cmd"
)
+// Arguments used in fscrypt commands.
+var (
+ unusedMountpointArg = &cmd.Argument{
+ Name: "mountpoint",
+ Usage: "path to a mountpoint on which to setup fscrypt",
+ }
+ usedMountpointArg = &cmd.Argument{
+ Name: "mountpoint",
+ Usage: "path to a mountpoint being used with fscrypt",
+ }
+ directoryToEncryptArg = &cmd.Argument{
+ Name: "directory",
+ Usage: "path to an empty directory to encrypt with fscrypt",
+ }
+ encryptedPathArg = &cmd.Argument{
+ Name: "path",
+ Usage: "file or directory encrypted with fscrypt",
+ }
+)
+
func main() { fscryptCommand.Run() }
var fscryptCommand = cmd.Command{
@@ -40,10 +66,10 @@ var fscryptCommand = cmd.Command{
cmd.VersionUsage,
},
SubCommands: []*Command{
- &setupCommand,
- &encryptCommand,
- // unlockCommand,
- // purgeCommand,
+ setupCommand,
+ encryptCommand,
+ unlockCommand,
+ purgeCommand,
// statusCommand,
// metadataCommand,
cmd.VersionCommand,
@@ -58,9 +84,9 @@ var setupCommand = &cmd.Command{
Title: "setup a system/filesystem to use fscrypt",
UsageLines: []string{
fmt.Sprintf("[options]"),
- fmt.Sprintf("%s [%s]", mountpointArg, cmd.ForceFlag),
+ fmt.Sprintf("%s [%s]", unusedMountpointArg, cmd.ForceFlag),
},
- Arguments: []*cmd.Argument{mountpointArg},
+ Arguments: []*cmd.Argument{unusedMountpointArg},
InheritFlags: true,
Flags: []cmd.Flag{configFileFlag, targetFlag, legacyFlag, cmd.ForceFlag},
ManPage: &cmd.ManPage{Name: "fscrypt-setup", Section: 8},
@@ -80,5 +106,151 @@ func setupAction(c *cmd.Context) error {
}
}
-// encrypt performs the functions of setupDirectory and Unlock in one command.
-var encryptCommand = &cmd.Command{}
+// encrypt takes an empty directory, enables encryption, and unlocks it.
+var encryptCommand = &cmd.Command{
+ Name: "encrypt",
+ Title: "start encrypting an empty directory",
+ UsageLines: nil, // TODO(joerichey)
+ Arguments: []*cmd.Argument{directoryToEncryptArg},
+ InheritFlags: true,
+ Flags: []cmd.Flag{sourceFlag, nameFlag, protectorFlag, policyFlag,
+ keyFileFlag, userFlag, skipUnlockFlag},
+ ManPage: &cmd.ManPage{Name: "fscrypt-encrypt", Section: 8},
+ Action: encryptAction,
+}
+
+func encryptAction(c *cmd.Context) error {
+ if err := cmd.CheckExpectedArgs(c, 1, false); err != nil {
+ return err
+ }
+
+ path := c.Args[0]
+ if err := encryptPath(path); err != nil {
+ return err
+ }
+
+ if !skipUnlockFlag.Value {
+ fmt.Fprintf(cmd.Output, "%q is now encrypted, unlocked, and ready for use.\n", path)
+ return nil
+ }
+
+ fmt.Fprintf(cmd.Output, "%q is now encrypted, but it is still locked.\n", path)
+ fmt.Fprintf(cmd.Output, "It can be unlocked with: fscrypt unlock %q\n", path)
+ return nil
+}
+
+// unlock takes an encrypted path and makes it available for reading/writing.
+var unlockCommand = &cmd.Command{
+ Name: "unlock",
+ Title: "unlock an encrypted file or directory",
+ UsageLines: nil, // TODO(joerichey)
+ Arguments: []*cmd.Argument{encryptedPathArg},
+ InheritFlags: true,
+ Flags: []cmd.Flag{protectorFlag, policyFlag, keyFileFlag, userFlag},
+ ManPage: &cmd.ManPage{Name: "fscrypt-unlock", Section: 8},
+ Action: unlockAction,
+}
+
+func unlockAction(c *cmd.Context) error {
+ if err := cmd.CheckExpectedArgs(c, 1, false); err != nil {
+ return err
+ }
+
+ path := c.Args[0]
+ if err := unlockPath(path); err != nil {
+ return err
+ }
+
+ fmt.Fprintf(cmd.Output, "%q is now unlocked and ready for use.\n", path)
+ return nil
+}
+
+// purge removes all the policy keys from the keyring (my require unmount).
+var purgeCommand = &cmd.Command{
+ Name: "purge",
+ Title: "remove a directory's encryption keys",
+ UsageLines: []string{fmt.Sprintf("%s, [%s=false] [%s] [%s]",
+ usedMountpointArg, dropCachesFlag, userFlag, cmd.ForceFlag)},
+ Arguments: []*cmd.Argument{usedMountpointArg},
+ InheritFlags: true,
+ Flags: []cmd.Flag{dropCachesFlag, userFlag, cmd.ForceFlag},
+ ManPage: &cmd.ManPage{Name: "fscrypt-purge", Section: 8},
+ Action: purgeAction,
+}
+
+func purgeAction(c *cmd.Context) error {
+ if err := cmd.CheckExpectedArgs(c, 1, false); err != nil {
+ return err
+ }
+ if dropCachesFlag.Value {
+ if cmd.CheckIfRoot() != nil {
+ return ErrDropCachesPerm
+ }
+ }
+
+ targetUser, err := parseUserFlag(true)
+ if err != nil {
+ return err
+ }
+ ctx, err := actions.NewContextFromMountpoint(c.Args[0], target)
+ if err != nil {
+ return err
+ }
+
+ question := fmt.Sprintf("Purge all policy keys from %q", ctx.Mount.Path)
+ if dropCachesFlag.Value {
+ question += " and drop global inode cache"
+ }
+ warning := "Encrypted data on this filesystem will be inaccessible until unlocked again!!"
+ if err = cmd.AskConfirmation(question+"?", warning, false); err != nil {
+ return err
+ }
+ if err = actions.PurgeAllPolicies(ctx); err != nil {
+ return err
+ }
+ fmt.Fprintf(cmd.Output, "Policies purged from filesystem %q.\n", ctx.Mount.Path)
+
+ if !dropCachesFlag.Value {
+ fmt.Fprintf(cmd.Output, "Filesystem %q should now be unmounted.\n", cmd.Mount.Path)
+ return nil
+ }
+ if err = security.DropFilesystemCache(); err != nil {
+ return err
+ }
+ fmt.Fprintln(cmd.Output, "Encrypted data removed from filesystem cache.")
+ return nil
+}
+
+// status is a command that gets info about the system, a mountpoint, or a path.
+var statusCommand = &cmd.Command{
+ Name: "status",
+ Title: "get the status of the system or a path",
+ UsageLines: []string{"", usedMountpointArg.String(), encryptedPathArg.String()},
+ Flags: []cmd.Flag{cmd.VerboseFlag, cmd.HelpFlag},
+ ManPage: &cmd.ManPage{Name: "fscrypt-status", Section: 8},
+ Action: statusAction,
+}
+
+func statusAction(c *cmd.Context) error {
+ switch len(c.Args) {
+ case 0:
+ // Case (1) - global status
+ return writeGlobalStatus()
+ case 1:
+ path := c.Args[0]
+ ctx, mntErr := actions.NewContextFromMountpoint(path, nil)
+
+ switch errors.Cause(mntErr) {
+ case nil:
+ // Case (2) - mountpoint status
+ return writeFilesystemStatus(ctx)
+ case filesystem.ErrNotAMountpoint:
+ // Case (3) - file or directory status
+ return writePathStatus(path)
+ default:
+ return mntErr
+ }
+ default:
+ return expectedArgsErr(c, 1, true)
+ }
+}
diff --git a/cmd/fscrypt/prompt.go b/cmd/fscrypt/prompt.go
index bccf534..57d0fc7 100644
--- a/cmd/fscrypt/prompt.go
+++ b/cmd/fscrypt/prompt.go
@@ -23,22 +23,13 @@ import (
"fmt"
"log"
"os"
- "os/user"
"strconv"
- "github.com/pkg/errors"
-
"github.com/google/fscrypt/actions"
"github.com/google/fscrypt/metadata"
"github.com/google/fscrypt/util"
)
-const (
- // Suffixes for questions with a yes or no default
- defaultYesSuffix = " [Y/n] "
- defaultNoSuffix = " [y/N] "
-)
-
// Descriptions for each of the protector sources
var sourceDescriptions = map[metadata.SourceType]string{
metadata.SourceType_pam_passphrase: "Your login passphrase",
@@ -46,26 +37,6 @@ var sourceDescriptions = map[metadata.SourceType]string{
metadata.SourceType_raw_key: "A raw 256-bit key",
}
-// usernameFromID returns the username for the provided UID. If the UID does not
-// correspond to a user or the username is blank, an error is returned.
-func usernameFromID(uid int64) (string, error) {
- u, err := user.LookupId(strconv.Itoa(int(uid)))
- if err != nil || u.Username == "" {
- return "", errors.Wrapf(ErrUnknownUser, "uid %d", uid)
- }
- return u.Username, nil
-}
-
-// formatUsername either returns the username for the provided UID, or a string
-// containing the error for unknown UIDs.
-func formatUsername(uid int64) string {
- username, err := usernameFromID(uid)
- if err != nil {
- return fmt.Sprintf("[%v]", err)
- }
- return username
-}
-
// formatInfo gives a string description of metadata.ProtectorData.
func formatInfo(data actions.ProtectorInfo) string {
switch data.Source() {
diff --git a/cmd/fscrypt/strings.go b/cmd/fscrypt/strings.go
deleted file mode 100644
index 07b6b64..0000000
--- a/cmd/fscrypt/strings.go
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * strings.go - File which contains the specific strings used for output and
- * formatting in fscrypt.
- *
- * Copyright 2017 Google Inc.
- * Author: Joe Richey (joerichey@google.com)
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package main
-
-import (
- "fmt"
-)
-
-// Argument usage strings
-const (
- directoryArg = "DIRECTORY"
- mountpointArg = "MOUNTPOINT"
- pathArg = "PATH"
- mountpointIDArg = mountpointArg + ":ID"
-)
-
-// Add words to this map if pluralization does not just involve adding an s.
-var plurals = map[string]string{
- "policy": "policies",
-}
-
-// pluralize prints our the correct pluralization of a work along with the
-// specified count. This means pluralize(1, "policy") = "1 policy" but
-// pluralize(2, "policy") = "2 policies"
-func pluralize(count int, word string) string {
- if count != 1 {
- if plural, ok := plurals[word]; ok {
- word = plural
- } else {
- word += "s"
- }
- }
- return fmt.Sprintf("%d %s", count, word)
-}