package cli import ( "errors" "fmt" "os" "github.com/spf13/afero" "golang.org/x/term" "sneak.berlin/go/mfer/internal/log" "sneak.berlin/go/mfer/mfer" ) // envSignKeyPassphrase names the environment variable holding the // passphrase of a protected signing key. // //nolint:gosec // G101: the name of a variable, not a credential const envSignKeyPassphrase = "MFER_SIGN_KEY_PASSPHRASE" // errNoPassphrase indicates a protected signing key whose passphrase is // neither in the environment nor can be asked for on a terminal. var errNoPassphrase = errors.New( "signing key is protected: set " + envSignKeyPassphrase + " to its passphrase") // signingOptions returns the signing options for the OpenPGP secret key in // the file path. The passphrase of a protected key comes from // MFER_SIGN_KEY_PASSPHRASE, or else from the terminal on stdin, and must // unlock the key. func (mfa *CLIApp) signingOptions(path string) (*mfer.SigningOptions, error) { secretKey, err := afero.ReadFile(mfa.Fs, path) if err != nil { return nil, fmt.Errorf("read signing key: %w", err) } protected, err := mfer.SecretKeyIsProtected(secretKey) if err != nil { return nil, fmt.Errorf("%s: %w", path, err) } log.Infof("signing manifest with the OpenPGP key in %s", path) opts := &mfer.SigningOptions{SecretKey: secretKey} if !protected { return opts, nil } opts.Passphrase, err = mfa.readPassphrase(path) if err != nil { return nil, err } // gen and freshen read the signing options before any file, so a // wrong passphrase stops them before they hash anything. err = mfer.CheckSigningKey(opts) if err != nil { return nil, fmt.Errorf("%s: %w", path, err) } return opts, nil } // readPassphrase returns MFER_SIGN_KEY_PASSPHRASE when it is set, or else // asks for the passphrase of the key in the file path on the terminal on // stdin. func (mfa *CLIApp) readPassphrase(path string) ([]byte, error) { passphrase := os.Getenv(envSignKeyPassphrase) if passphrase != "" { return []byte(passphrase), nil } stdin, ok := mfa.Stdin.(*os.File) if !ok || !term.IsTerminal(int(stdin.Fd())) { return nil, errNoPassphrase } _, _ = fmt.Fprintf(mfa.Stderr, "Passphrase for %s: ", path) typed, err := term.ReadPassword(int(stdin.Fd())) _, _ = fmt.Fprintln(mfa.Stderr) if err != nil { return nil, fmt.Errorf("read passphrase: %w", err) } return typed, nil }