diff options
Diffstat (limited to 'cmd/fscrypt')
| -rw-r--r-- | cmd/fscrypt/commands.go | 114 | ||||
| -rw-r--r-- | cmd/fscrypt/fscrypt.go | 2 | ||||
| -rw-r--r-- | cmd/fscrypt/status.go | 169 |
3 files changed, 284 insertions, 1 deletions
diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go index 2407f32..a56b8bb 100644 --- a/cmd/fscrypt/commands.go +++ b/cmd/fscrypt/commands.go @@ -29,6 +29,7 @@ import ( "github.com/urfave/cli" "fscrypt/actions" + "fscrypt/filesystem" "fscrypt/metadata" ) @@ -294,3 +295,116 @@ func unlockAction(c *cli.Context) error { 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: "[EXPERIMENTAL] remove a filesystem's keys", + Description: fmt.Sprintf(`EXPERIMENTAL: This command removes all the + policy keys for directories on %[1]s. This is intended to lock + all encrypted files and directories on %[1]s, in that unlocking + them for reading will require providing a key again. However, + this action is currently subject to two significant limitations: + + (1) If "fscrypt purge" is run, but the filesystem has not yet + been unmounted, recently accessed encrypted directories and + files will remain accessible for some time. Because of this, + after purging a filesystem's keys, it is recommended to unmount + the filesystem. This limitation might be eliminated in a future + kernel version. + + (2) Even after unmounting the filesystem, the kernel may keep + contents of encrypted files cached 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), + Flags: []cli.Flag{forceFlag}, + Action: purgeAction, +} + +func purgeAction(c *cli.Context) error { + if c.NArg() != 1 { + return expectedArgsErr(c, 1, false) + } + + ctx, err := actions.NewContextFromMountpoint(c.Args().Get(0)) + if err != nil { + return newExitError(c, err) + } + + err = askConfirmation(fmt.Sprintf( + "Purge all policy keys from %q?", + ctx.Mount.Path), false, + "Encrypted data on this filesystem will be inaccessible until unlocked again!!") + if err != nil { + return newExitError(c, err) + } + + if err = actions.PurgeAllPolicies(ctx); err != nil { + return newExitError(c, err) + } + + fmt.Fprintf(c.App.Writer, "All keys purged for %q.\n", ctx.Mount.Path) + 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) + + 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 +} diff --git a/cmd/fscrypt/fscrypt.go b/cmd/fscrypt/fscrypt.go index d3185fa..7f0c000 100644 --- a/cmd/fscrypt/fscrypt.go +++ b/cmd/fscrypt/fscrypt.go @@ -75,7 +75,7 @@ func main() { // Initialize command list and setup all of the commands. app.Action = defaultAction - app.Commands = []cli.Command{Setup, Encrypt, Unlock} + app.Commands = []cli.Command{Setup, Encrypt, Unlock, Purge, Status} for i := range app.Commands { setupCommand(&app.Commands[i]) } diff --git a/cmd/fscrypt/status.go b/cmd/fscrypt/status.go new file mode 100644 index 0000000..049c370 --- /dev/null +++ b/cmd/fscrypt/status.go @@ -0,0 +1,169 @@ +/* + * status.go - File which contains the functions for outputting the status of + * fscrypt, a filesystem, or a directory. + * + * 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" + "io" + "log" + "strings" + "text/tabwriter" + + "github.com/pkg/errors" + + "fscrypt/actions" + "fscrypt/filesystem" + "fscrypt/metadata" +) + +// Creates a writer which correctly aligns tabs with the specified header. +// Must call Flush() when done. +func makeTableWriter(w io.Writer, header string) *tabwriter.Writer { + tableWriter := tabwriter.NewWriter(w, 0, indentLength, indentLength, ' ', 0) + fmt.Fprintln(tableWriter, header) + return tableWriter +} + +// statusString is what will be printed in the STATUS column. An empty string +// means a status should not be printed. +func statusString(mount *filesystem.Mount) string { + switch err := mount.CheckSetup(); errors.Cause(err) { + case nil: + return "setup with fscrypt" + case filesystem.ErrNotSetup: + return "not setup with fscrypt" + case metadata.ErrEncryptionNotEnabled: + return "encryption not enabled" + case metadata.ErrEncryptionNotSupported: + return "" + default: + log.Printf("Unexpected Error: %v", err) + return "" + } +} + +func yesNoString(b bool) string { + if b { + return "Yes" + } + return "No" +} + +// writeGlobalStatus prints all the filesystem that use (or could use) fscrypt. +func writeGlobalStatus(w io.Writer) error { + mounts, err := filesystem.AllFilesystems() + if err != nil { + return err + } + + t := makeTableWriter(w, "MOUNTPOINT\tDEVICE\tFILESYSTEM\tSTATUS") + supportCount := 0 + for _, mount := range mounts { + if status := statusString(mount); status != "" { + fmt.Fprintf(t, "%s\t%s\t%s\t%s\n", + mount.Path, mount.Device, mount.Filesystem, status) + supportCount++ + } + } + + fmt.Fprintf(w, "%s on this system support encryption\n\n", pluralize(supportCount, "filesystem")) + return t.Flush() +} + +// writeOptions writes a table of the status for a slice of protector options. +func writeOptions(w io.Writer, options []*actions.ProtectorOption) { + t := makeTableWriter(w, "PROTECTOR\tLINKED\tDESCRIPTION") + for _, option := range options { + if option.LoadError != nil { + fmt.Fprintf(t, "%s\t\tERROR: %v\n", option.Descriptor(), option.LoadError) + continue + } + + // For linked protectors, indicate which filesystem. + isLinked := option.LinkedMount != nil + linkedText := yesNoString(isLinked) + if isLinked { + linkedText += fmt.Sprintf(" (%s)", option.LinkedMount.Path) + } + fmt.Fprintf(t, "%s\t%s\t%s\n", option.Descriptor(), linkedText, + formatInfo(option.ProtectorInfo)) + } + t.Flush() +} + +func writeFilesystemStatus(w io.Writer, ctx *actions.Context) error { + options, err := ctx.ProtectorOptions() + if err != nil { + return err + } + + policyDescriptors, err := ctx.Mount.ListPolicies() + if err != nil { + return err + } + + fmt.Fprintf(w, "%s filesystem %q has %s and %s\n\n", ctx.Mount.Filesystem, ctx.Mount.Path, + pluralize(len(options), "protector"), pluralize(len(policyDescriptors), "policy")) + + if len(options) > 0 { + writeOptions(w, options) + } + + if len(policyDescriptors) == 0 { + return nil + } + + fmt.Fprintln(w) + t := makeTableWriter(w, "POLICY\tUNLOCKED\tPROTECTORS") + for _, descriptor := range policyDescriptors { + policy, err := actions.GetPolicy(ctx, descriptor) + if err != nil { + fmt.Fprintf(t, "%s\t\tERROR: %v\n", descriptor, err) + continue + } + + fmt.Fprintf(t, "%s\t%s\t%s\n", descriptor, yesNoString(policy.IsProvisioned()), + strings.Join(policy.ProtectorDescriptors(), ", ")) + } + return t.Flush() +} + +func writePathStatus(w io.Writer, path string) error { + ctx, err := actions.NewContextFromPath(path) + if err != nil { + return err + } + policy, err := actions.GetPolicyFromPath(ctx, path) + if err != nil { + return err + } + + fmt.Fprintf(w, "%q is encrypted with fscrypt.\n", path) + fmt.Fprintln(w) + fmt.Fprintf(w, "Policy: %s\n", policy.Descriptor()) + fmt.Fprintf(w, "Unlocked: %s\n", yesNoString(policy.IsProvisioned())) + fmt.Fprintln(w) + + options := policy.ProtectorOptions() + fmt.Fprintf(w, "Protected with %s:\n", pluralize(len(options), "protector")) + writeOptions(w, options) + return nil +} |