Merge pull request #1061 from harmony-ek/non_interactive_passphrase

Support non-interactive passphrase
pull/1063/head
Eugene Kim 6 years ago committed by GitHub
commit 066354758b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      cmd/harmony/main.go
  2. 59
      internal/utils/passphrase.go
  3. 44
      internal/utils/passphrase_test.go
  4. 46
      internal/utils/testing/tempfile.go

@ -13,6 +13,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/accounts"
"github.com/harmony-one/harmony/accounts/keystore"
"github.com/harmony-one/harmony/consensus"
@ -23,7 +24,7 @@ import (
"github.com/harmony-one/harmony/internal/ctxerror"
"github.com/harmony-one/harmony/internal/genesis"
hmykey "github.com/harmony-one/harmony/internal/keystore"
memprofiling "github.com/harmony-one/harmony/internal/memprofiling"
"github.com/harmony-one/harmony/internal/memprofiling"
"github.com/harmony-one/harmony/internal/profiler"
"github.com/harmony-one/harmony/internal/shardchain"
"github.com/harmony-one/harmony/internal/utils"
@ -110,6 +111,10 @@ var (
// -nopass is false by default. The keyfile must be encrypted.
hmyNoPass = flag.Bool("nopass", false, "No passphrase for the key (testing only)")
// -pass takes on "pass:password", "env:var", "file:pathname",
// "fd:number", or "stdin" form.
// See “PASS PHRASE ARGUMENTS” section of openssl(1) for details.
hmyPass = flag.String("pass", "", "how to get passphrase for the key")
stakingAccounts = flag.String("accounts", "", "account addresses of the node")
@ -196,7 +201,14 @@ func initSetup() {
var myPass string
if !*hmyNoPass {
myPass = utils.AskForPassphrase("Passphrase: ")
if *hmyPass == "" {
myPass = utils.AskForPassphrase("Passphrase: ")
} else if pass, err := utils.GetPassphraseFromSource(*hmyPass); err != nil {
fmt.Printf("Cannot read passphrase: %s\n", err)
os.Exit(3)
} else {
myPass = pass
}
err := ks.Unlock(myAccount, myPass)
if err != nil {
fmt.Printf("Wrong Passphrase! Unable to unlock account key!\n")

@ -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)
})
}

@ -0,0 +1,46 @@
package testutils
import (
"io"
"io/ioutil"
"os"
"strings"
"testing"
"github.com/pkg/errors"
)
// NewTempFile creates a new, empty temp file for testing. Errors are fatal.
func NewTempFile(t *testing.T) *os.File {
pattern := strings.ReplaceAll(t.Name(), string(os.PathSeparator), "_")
f, err := ioutil.TempFile("", pattern)
if err != nil {
t.Fatal(errors.Wrap(err, "cannot create temp file"))
}
return f
}
// NewTempFileWithContents creates a new, empty temp file for testing,
// with the given contents. The read/write offset is set to the beginning.
// Errors are fatal.
func NewTempFileWithContents(t *testing.T, contents []byte) *os.File {
f := NewTempFile(t)
if _, err := f.Write(contents); err != nil {
t.Fatal(errors.Wrapf(err, "cannot write contents into %s", f.Name()))
}
if _, err := f.Seek(0, io.SeekStart); err != nil {
t.Fatal(errors.Wrapf(err, "cannot rewind test file %s", f.Name()))
}
return f
}
// CloseAndRemoveTempFile closes/removes the temp file. Errors are logged.
func CloseAndRemoveTempFile(t *testing.T, f *os.File) {
fn := f.Name()
if err := f.Close(); err != nil {
t.Log(errors.Wrapf(err, "cannot close test file %s", fn))
}
if err := os.Remove(fn); err != nil {
t.Log(errors.Wrapf(err, "cannot remove test file %s", fn))
}
}
Loading…
Cancel
Save