Strip Fury back to its vault functionality in one `vault` command: genkey, init, add (file or directory, optional folder inside the vault), list, get, mv and rm. The hybrid FrodoKEM-640 + ML-KEM-768 encryption core is kept under internal/pqcrypt. Signing, policies, profiles, backups, SSH remotes, FIDO2, agents, provenance and compression are removed.
90 lines
2.8 KiB
Go
90 lines
2.8 KiB
Go
package term
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"runtime"
|
|
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
// enableVirtualTerminalProcessing tries to enable virtual terminal processing
|
|
// on Windows. If it fails, avoid using escape sequences to prevent weird
|
|
// characters being printed to the console.
|
|
var enableVirtualTerminalProcessing func(out *os.File) error
|
|
|
|
// clearLine clears the current line on the terminal, or opens a new line if
|
|
// terminal escape codes don't work.
|
|
func clearLine(out *os.File) {
|
|
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, if possible.
|
|
//
|
|
// (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")
|
|
if enableVirtualTerminalProcessing == nil || enableVirtualTerminalProcessing(out) == nil {
|
|
fmt.Fprintf(out, 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 if stdin is in use.
|
|
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)
|
|
}
|
|
}
|
|
|
|
// ReadSecret reads a value from the terminal with no echo. The prompt is ephemeral.
|
|
func ReadSecret(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
|
|
}
|
|
|
|
// ReadPassphrase returns the passphrase from the environment variable env if
|
|
// it is set, otherwise prompts on the terminal.
|
|
func ReadPassphrase(prompt, env string) (string, error) {
|
|
if p, ok := os.LookupEnv(env); ok {
|
|
if p == "" {
|
|
return "", fmt.Errorf("%s is set but empty", env)
|
|
}
|
|
return p, nil
|
|
}
|
|
p, err := ReadSecret(prompt)
|
|
if err != nil {
|
|
return "", fmt.Errorf("could not read passphrase (set %s for non-interactive use): %v", env, err)
|
|
}
|
|
return string(p), nil
|
|
}
|