aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/fscrypt/commands.go114
-rw-r--r--cmd/fscrypt/fscrypt.go2
-rw-r--r--cmd/fscrypt/status.go169
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
+}