From 2d144cd25cf5cff7a258ab21d4a6b47ecae88587 Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Sat, 22 Feb 2025 13:55:12 +0100 Subject: [PATCH] use common function to read password Signed-off-by: Tomasz Duda --- age/keysource.go | 24 +++------------------ age/keysource_test.go | 50 +++---------------------------------------- age/tui.go | 9 ++++++++ 3 files changed, 15 insertions(+), 68 deletions(-) diff --git a/age/keysource.go b/age/keysource.go index b46f8fe52..3ad379bf7 100644 --- a/age/keysource.go +++ b/age/keysource.go @@ -15,7 +15,6 @@ import ( "filippo.io/age/agessh" "filippo.io/age/armor" "github.com/sirupsen/logrus" - "golang.org/x/term" gpgagent "github.com/getsops/gopgagent" "github.com/getsops/sops/v3/logging" @@ -370,28 +369,11 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, error) { Passphrase: func() (string, error) { conn, err := gpgagent.NewConn() if err != nil { - fmt.Fprintf(os.Stderr, "Enter passphrase for identity '%s':", n) - - var pass string - if term.IsTerminal(int(os.Stdout.Fd())) { - p, err = term.ReadPassword(int(os.Stdout.Fd())) - if err == nil { - pass = string(p) - } - } else { - reader := bufio.NewReader(os.Stdin) - pass, err = reader.ReadString('\n') - if err == io.EOF { - err = nil - } - } + passphrase, err := readPassphrase("Enter passphrase for identity " + n + ":") if err != nil { - return "", fmt.Errorf("could not read passphrase: %v", err) + return "", err } - - fmt.Fprintln(os.Stderr) - - return pass, nil + return string(passphrase), nil } defer func(conn *gpgagent.Conn) { if err := conn.Close(); err != nil { diff --git a/age/keysource_test.go b/age/keysource_test.go index a625482ce..e02d9792d 100644 --- a/age/keysource_test.go +++ b/age/keysource_test.go @@ -2,7 +2,6 @@ package age import ( "fmt" - "io/ioutil" "os" "path/filepath" "runtime" @@ -520,10 +519,7 @@ func TestMasterKey_Identities_Passphrase(t *testing.T) { t.Setenv(SopsAgeKeyEnv, mockEncryptedIdentity) //blocks calling gpg-agent os.Unsetenv("XDG_RUNTIME_DIR") - - funcDefer, _ := mockStdin(t, mockIdentityPassphrase) - defer funcDefer() - + t.Setenv(SopsAgePasswordEnv, mockIdentityPassphrase) got, err := key.Decrypt() assert.NoError(t, err) @@ -542,9 +538,7 @@ func TestMasterKey_Identities_Passphrase(t *testing.T) { t.Setenv(SopsAgeKeyFileEnv, keyPath) //blocks calling gpg-agent os.Unsetenv("XDG_RUNTIME_DIR") - - funcDefer, _ := mockStdin(t, mockIdentityPassphrase) - defer funcDefer() + t.Setenv(SopsAgePasswordEnv, mockIdentityPassphrase) got, err := key.Decrypt() assert.NoError(t, err) @@ -556,9 +550,7 @@ func TestMasterKey_Identities_Passphrase(t *testing.T) { t.Setenv(SopsAgeKeyEnv, mockEncryptedIdentity) //blocks calling gpg-agent os.Unsetenv("XDG_RUNTIME_DIR") - - funcDefer, _ := mockStdin(t, mockIdentityPassphrase) - defer funcDefer() + t.Setenv(SopsAgePasswordEnv, mockIdentityPassphrase) got, err := key.Decrypt() assert.Error(t, err) @@ -566,39 +558,3 @@ func TestMasterKey_Identities_Passphrase(t *testing.T) { assert.Nil(t, got) }) } - -// mockStdin is a helper function that lets the test pretend dummyInput as os.Stdin. -// It will return a function for `defer` to clean up after the test. -// -// Note: `ioutil.TempFile` should be replaced to `os.CreateTemp` for Go v1.16 or higher. -func mockStdin(t *testing.T, dummyInput string) (funcDefer func(), err error) { - t.Helper() - - oldOsStdin := os.Stdin - - fmt.Println(t.TempDir(), t.Name()) - - tmpfile, err := ioutil.TempFile(t.TempDir(), strings.Replace(t.Name(), "/", "_", -1)) - if err != nil { - return nil, err - } - - content := []byte(dummyInput) - - if _, err := tmpfile.Write(content); err != nil { - return nil, err - } - - if _, err := tmpfile.Seek(0, 0); err != nil { - return nil, err - } - - // Set stdin to the temp file - os.Stdin = tmpfile - - return func() { - // clean up - os.Stdin = oldOsStdin - os.Remove(tmpfile.Name()) - }, nil -} diff --git a/age/tui.go b/age/tui.go index e0c82831c..4733fbd59 100644 --- a/age/tui.go +++ b/age/tui.go @@ -18,9 +18,18 @@ import ( "golang.org/x/term" ) +const ( + SopsAgePasswordEnv = "SOPS_AGE_PASSWORD" +) + // readPassphrase reads a passphrase from the terminal. It does not read from a // non-terminal stdin, so it does not check stdinInUse. func readPassphrase(prompt string) ([]byte, error) { + password := os.Getenv(SopsAgePasswordEnv) + if password != "" { + return []byte(password), nil + } + var in, out *os.File if runtime.GOOS == "windows" { var err error