80 lines
2.1 KiB
Go
80 lines
2.1 KiB
Go
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.
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|