From bab7dfdf68075b345e4de3ae79ea685ca884668f Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Tue, 17 Oct 2017 02:39:07 -0700 Subject: Move around and fscrypt refactor --- cmd/fscrypt/commands.go | 378 ++++++++++++++++-------------------------------- cmd/fscrypt/errors.go | 60 +------- cmd/fscrypt/flags.go | 23 +-- cmd/fscrypt/fscrypt.go | 188 +++++++++++++++++++++++- cmd/fscrypt/prompt.go | 29 ---- cmd/fscrypt/strings.go | 52 ------- 6 files changed, 316 insertions(+), 414 deletions(-) delete mode 100644 cmd/fscrypt/strings.go (limited to 'cmd/fscrypt') 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) -} -- cgit v1.2.3