Skip to content

Commit

Permalink
Create secure in-memory directory on macOS (gopasspw#144)
Browse files Browse the repository at this point in the history
* Create secure in-memory directory on macOS

Fixes #29

* Vendor github.com/cenkalti/backoff
  • Loading branch information
dominikschulz authored Jun 18, 2017
1 parent f57d3c5 commit 30c0a17
Show file tree
Hide file tree
Showing 15 changed files with 711 additions and 28 deletions.
14 changes: 7 additions & 7 deletions action/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"

"github.com/fatih/color"
"github.com/justwatchcom/gopass/fsutil"
"github.com/justwatchcom/gopass/pwgen"
"github.com/justwatchcom/gopass/store"
Expand Down Expand Up @@ -65,21 +65,21 @@ func (s *Action) editor(content []byte) ([]byte, error) {
editor = "editor"
}

tmpfile, err := ioutil.TempFile(fsutil.Tempdir(), "gopass-edit")
tmpfile, err := fsutil.TempFile("gopass-edit")
if err != nil {
return []byte{}, fmt.Errorf("failed to create tmpfile to start with %s: %v", editor, tmpfile.Name())
return []byte{}, fmt.Errorf("failed to create tmpfile %s: %s", editor, err)
}
defer func() {
if err := os.Remove(tmpfile.Name()); err != nil {
log.Fatal(err)
if err := tmpfile.Remove(); err != nil {
color.Red("Failed to remove tempfile at %s: %s", tmpfile.Name(), err)
}
}()

if _, err := tmpfile.Write([]byte(content)); err != nil {
return []byte{}, fmt.Errorf("failed to create tmpfile to start with %s: %v", editor, tmpfile.Name())
return []byte{}, fmt.Errorf("failed to write tmpfile to start with %s %v: %s", editor, tmpfile.Name(), err)
}
if err := tmpfile.Close(); err != nil {
return []byte{}, fmt.Errorf("failed to create tmpfile to start with %s: %v", editor, tmpfile.Name())
return []byte{}, fmt.Errorf("failed to close tmpfile to start with %s %v: %s", editor, tmpfile.Name(), err)
}

cmdArgs, err := shellquote.Split(editor)
Expand Down
10 changes: 1 addition & 9 deletions fsutil/fsutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func TestIsDir(t *testing.T) {
t.Errorf("Should be not dir: %s", fn)
}
}

func TestIsFile(t *testing.T) {
tempdir, err := ioutil.TempDir("", "gopass-")
if err != nil {
Expand All @@ -63,12 +64,3 @@ func TestIsFile(t *testing.T) {
t.Errorf("Should be not dir: %s", fn)
}
}
func TestTempdir(t *testing.T) {
tempdir, err := ioutil.TempDir(Tempdir(), "gopass-")
if err != nil {
t.Fatalf("Failed to create tempdir: %s", err)
}
defer func() {
_ = os.RemoveAll(tempdir)
}()
}
9 changes: 0 additions & 9 deletions fsutil/fsutil_windows.go

This file was deleted.

79 changes: 79 additions & 0 deletions fsutil/tempdir.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package fsutil

import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
)

type tempfile struct {
dir string
dev string
fh *os.File
dbg bool
}

// TempFiler is a tempfile interface
type TempFiler interface {
io.WriteCloser
Name() string
Remove() error
}

// TempFile returns a new tempfile wrapper
func TempFile(prefix string) (TempFiler, error) {
td, err := ioutil.TempDir(tempdirBase(), prefix)
if err != nil {
return nil, err
}
tf := &tempfile{
dir: td,
}
if gdb := os.Getenv("GOPASS_DEBUG"); gdb == "true" {
tf.dbg = true
}
if err := tf.mount(); err != nil {
_ = os.RemoveAll(tf.dir)
return nil, err
}

fn := filepath.Join(tf.dir, "tempfile")
fh, err := os.OpenFile(fn, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
if err != nil {
return nil, fmt.Errorf("Failed to open file %s: %s", fn, err)
}
tf.fh = fh

return tf, nil
}

func (t *tempfile) Name() string {
if t.fh == nil {
return ""
}
return t.fh.Name()
}

func (t *tempfile) Write(p []byte) (int, error) {
if t.fh == nil {
return 0, fmt.Errorf("not initialized")
}
return t.fh.Write(p)
}

func (t *tempfile) Close() error {
if t.fh == nil {
return nil
}
return t.fh.Close()
}

func (t *tempfile) Remove() error {
_ = t.Close()
if err := t.unmount(); err != nil {
return fmt.Errorf("Failed to unmount %s from %s: %s", t.dev, t.dir, err)
}
return os.RemoveAll(t.dir)
}
93 changes: 93 additions & 0 deletions fsutil/tempdir_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// +build darwin

package fsutil

import (
"fmt"
"os"
"os/exec"
"strings"
"time"

"github.com/cenkalti/backoff"
)

func tempdirBase() string {
return ""
}

func (t *tempfile) mount() error {
// create 16MB ramdisk
cmd := exec.Command("hdid", "-drivekey", "system-image=yes", "-nomount", "ram://32768")
cmd.Stderr = os.Stderr
if t.dbg {
fmt.Printf("[DEBUG] CMD: %s %+v\n", cmd.Path, cmd.Args)
}
out, err := cmd.Output()
if err != nil {
return fmt.Errorf("Failed to create disk with hdid: %s", err)
}
if t.dbg {
fmt.Printf("[DEBUG] Output: %s\n", out)
}
p := strings.Split(string(out), " ")
if len(p) < 1 {
return fmt.Errorf("Unhandeled hdid output: %s", string(out))
}
t.dev = p[0]

// create filesystem on ramdisk
cmd = exec.Command("newfs_hfs", "-M", "700", t.dev)
cmd.Stderr = os.Stderr
if t.dbg {
cmd.Stdout = os.Stdout
fmt.Printf("[DEBUG] CMD: %s %+v\n", cmd.Path, cmd.Args)
}
if err := cmd.Run(); err != nil {
return fmt.Errorf("Failed to make filesystem on %s: %s", t.dev, err)
}

// mount ramdisk
cmd = exec.Command("mount", "-t", "hfs", "-o", "noatime", "-o", "nobrowse", t.dev, t.dir)
cmd.Stderr = os.Stderr
if t.dbg {
cmd.Stdout = os.Stdout
fmt.Printf("[DEBUG] CMD: %s %+v\n", cmd.Path, cmd.Args)
}
if err := cmd.Run(); err != nil {
return fmt.Errorf("Failed to mount filesystem %s to %s: %s", t.dev, t.dir, err)
}
time.Sleep(100 * time.Millisecond)
return nil
}

func (t *tempfile) unmount() error {
bo := backoff.NewExponentialBackOff()
bo.MaxElapsedTime = 10 * time.Second
return backoff.Retry(t.tryUnmount, bo)
}

func (t *tempfile) tryUnmount() error {
if t.dir == "" || t.dev == "" {
return fmt.Errorf("need dir and dev")
}
// unmount ramdisk
cmd := exec.Command("diskutil", "unmountDisk", t.dev)
cmd.Stderr = os.Stderr
if t.dbg {
cmd.Stdout = os.Stdout
fmt.Printf("[DEBUG] CMD: %s %+v\n", cmd.Path, cmd.Args)
}
if err := cmd.Run(); err != nil {
return err
}

// eject disk
cmd = exec.Command("diskutil", "quiet", "eject", t.dev)
cmd.Stderr = os.Stderr
if t.dbg {
cmd.Stdout = os.Stdout
fmt.Printf("[DEBUG] CMD: %s %+v\n", cmd.Path, cmd.Args)
}
return cmd.Run()
}
15 changes: 12 additions & 3 deletions fsutil/fsutil_others.go → fsutil/tempdir_linux.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// +build !windows
// +build linux

package fsutil

Expand All @@ -8,10 +8,10 @@ import (
"golang.org/x/sys/unix"
)

// Tempdir returns a temporary directory suiteable for sensitive data. It tries
// tempdir returns a temporary directory suiteable for sensitive data. It tries
// /dev/shm but if this isn't working it will return an empty string. Using
// this with ioutil.Tempdir will ensure that we're getting the "best" tempdir.
func Tempdir() string {
func tempdirBase() string {
shmDir := "/dev/shm"
if fi, err := os.Stat(shmDir); err == nil {
if fi.IsDir() {
Expand All @@ -22,3 +22,12 @@ func Tempdir() string {
}
return ""
}

func (t *tempfile) mount() error {
_ = t.dev // to trick megacheck
return nil
}

func (t *tempfile) unmount() error {
return nil
}
18 changes: 18 additions & 0 deletions fsutil/tempdir_others.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// +build !linux,!darwin

package fsutil

// tempdir returns a temporary directory suiteable for sensitive data. On
// Windows, just return empty string for ioutil.TempFile.
func tempdirBase() string {
return ""
}

func (t *tempfile) mount() error {
_ = t.dev // to trick megacheck
return nil
}

func (t *tempfile) unmount() error {
return nil
}
17 changes: 17 additions & 0 deletions fsutil/tempdir_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package fsutil

import (
"io/ioutil"
"os"
"testing"
)

func TestTempdirBase(t *testing.T) {
tempdir, err := ioutil.TempDir(tempdirBase(), "gopass-")
if err != nil {
t.Fatalf("Failed to create tempdir: %s", err)
}
defer func() {
_ = os.RemoveAll(tempdir)
}()
}
20 changes: 20 additions & 0 deletions vendor/github.com/cenkalti/backoff/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 30c0a17

Please sign in to comment.