Add an openssl(1)-compatible passphrase reader

pull/1061/head
Eugene Kim 6 years ago
parent 0c15daa0c5
commit 5c35b96b08
  1. 59
      internal/utils/passphrase.go
  2. 44
      internal/utils/passphrase_test.go

@ -2,8 +2,14 @@ package utils
import (
"fmt"
"io"
"io/ioutil"
"os"
"strconv"
"strings"
"syscall"
"github.com/pkg/errors"
"golang.org/x/crypto/ssh/terminal"
)
@ -19,3 +25,56 @@ func AskForPassphrase(prompt string) string {
return password
}
// readAllAsString reads the entire file contents as a string.
func readAllAsString(r io.Reader) (data string, err error) {
bytes, err := ioutil.ReadAll(r)
return string(bytes), err
}
// GetPassphraseFromSource reads a passphrase such as a key-encrypting one
// non-interactively from the given source.
//
// The source can be "pass:password", "env:var", "file:pathname", "fd:number",
// or "stdin". See “PASS PHRASE ARGUMENTS” section of openssl(1) for details.
func GetPassphraseFromSource(src string) (pass string, err error) {
switch src {
case "stdin":
return readAllAsString(os.Stdin)
}
methodArg := strings.SplitN(src, ":", 2)
if len(methodArg) < 2 {
return "", errors.Errorf("invalid passphrase reading method %#v", src)
}
method := methodArg[0]
arg := methodArg[1]
switch method {
case "pass":
return arg, nil
case "env":
pass, ok := os.LookupEnv(arg)
if !ok {
return "", errors.Errorf("environment variable %#v undefined", arg)
}
return pass, nil
case "file":
f, err := os.Open(arg)
if err != nil {
return "", errors.Wrapf(err, "cannot open file %#v", arg)
}
defer func() { _ = f.Close() }()
return readAllAsString(f)
case "fd":
fd, err := strconv.ParseUint(arg, 10, 0)
if err != nil {
return "", errors.Wrapf(err, "invalid fd literal %#v", arg)
}
f := os.NewFile(uintptr(fd), "(passphrase-source)")
if f == nil {
return "", errors.Errorf("cannot open fd %#v", fd)
}
defer func() { _ = f.Close() }()
return readAllAsString(f)
}
return "", errors.Errorf("invalid passphrase reading method %#v", method)
}

@ -0,0 +1,44 @@
package utils
import (
"fmt"
"os"
"testing"
"github.com/pkg/errors"
testutils "github.com/harmony-one/harmony/internal/utils/testing"
)
func exerciseGetPassphraseFromSource(t *testing.T, source, expected string) {
if actual, err := GetPassphraseFromSource(source); err != nil {
t.Fatal(errors.Wrap(err, "cannot read passphrase"))
} else if actual != expected {
t.Errorf("expected passphrase %#v; got %#v", expected, actual)
}
}
func TestGetPassphraseFromSource_Pass(t *testing.T) {
exerciseGetPassphraseFromSource(t, "pass:hello world", "hello world")
}
func TestGetPassphraseFromSource_File(t *testing.T) {
expected := "\nhello world\n"
t.Run("stdin", func(t *testing.T) {
f := testutils.NewTempFileWithContents(t, []byte(expected))
savedStdin := os.Stdin
defer func() { os.Stdin = savedStdin }()
os.Stdin = f
exerciseGetPassphraseFromSource(t, "stdin", expected)
})
t.Run("file", func(t *testing.T) {
f := testutils.NewTempFileWithContents(t, []byte(expected))
defer testutils.CloseAndRemoveTempFile(t, f)
exerciseGetPassphraseFromSource(t, "file:"+f.Name(), expected)
})
t.Run("fd", func(t *testing.T) {
f := testutils.NewTempFileWithContents(t, []byte(expected))
defer testutils.CloseAndRemoveTempFile(t, f)
exerciseGetPassphraseFromSource(t, fmt.Sprintf("fd:%d", f.Fd()), expected)
})
}
Loading…
Cancel
Save