Skip to content

Commit

Permalink
Add windows support for read a password
Browse files Browse the repository at this point in the history
It adds a new internal library borrowed from age that prompts for
a password.
  • Loading branch information
maraino committed Nov 3, 2022
1 parent c39aaaf commit 4aeb7a5
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 6 deletions.
9 changes: 3 additions & 6 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@ package cmd

import (
"errors"
"fmt"
"os"
"syscall"

"github.com/spf13/cobra"
"go.step.sm/crypto/pemutil"
"golang.org/x/term"

"github.com/smallstep/step-kms-plugin/internal/termutil"
)

// rootCmd represents the base command when called without any subcommands
Expand Down Expand Up @@ -68,8 +67,6 @@ func init() {
if s[len(s)-1] != ':' {
s += ":"
}
fmt.Fprint(os.Stderr, s+" ")
defer fmt.Fprintln(os.Stderr)
return term.ReadPassword(syscall.Stderr)
return termutil.ReadPassword(s)
}
}
71 changes: 71 additions & 0 deletions internal/termutil/tui.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2021 The age Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package termutil

import (
"fmt"
"io"
"os"
"runtime"

"golang.org/x/term"
)

// clearLine clears the current line on the terminal, or opens a new line if
// terminal escape codes don't work.
func clearLine(out io.Writer) {
const (
CUI = "\033[" // Control Sequence Introducer
CPL = CUI + "F" // Cursor Previous Line
EL = CUI + "K" // Erase in Line
)

// First, open a new line, which is guaranteed to work everywhere. Then, try
// to erase the line above with escape codes.
//
// (We use CRLF instead of LF to work around an apparent bug in WSL2's
// handling of CONOUT$. Only when running a Windows binary from WSL2, the
// cursor would not go back to the start of the line with a simple LF.
// Honestly, it's impressive CONIN$ and CONOUT$ work at all inside WSL2.)
fmt.Fprintf(out, "\r\n"+CPL+EL)
}

// withTerminal runs f with the terminal input and output files, if available.
// withTerminal does not open a non-terminal stdin, so the caller does not need
// to check stdinInUse.
func withTerminal(f func(in, out *os.File) error) error {
if runtime.GOOS == "windows" {
in, err := os.OpenFile("CONIN$", os.O_RDWR, 0)
if err != nil {
return err
}
defer in.Close()
out, err := os.OpenFile("CONOUT$", os.O_WRONLY, 0)
if err != nil {
return err
}
defer out.Close()
return f(in, out)
} else if tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0); err == nil {
defer tty.Close()
return f(tty, tty)
} else if term.IsTerminal(int(os.Stdin.Fd())) {
return f(os.Stdin, os.Stdin)
} else {
return fmt.Errorf("standard input is not a terminal, and /dev/tty is not available: %v", err)
}
}

// ReadPassword reads a value from the terminal with no echo. The prompt is
// ephemeral.
func ReadPassword(prompt string) (s []byte, err error) {
err = withTerminal(func(in, out *os.File) error {
fmt.Fprintf(out, "%s ", prompt)
defer clearLine(out)
s, err = term.ReadPassword(int(in.Fd()))
return err
})
return
}

0 comments on commit 4aeb7a5

Please sign in to comment.