mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 18:13:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			211 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			211 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Package passphrase is a utility function for managing passphrase
 | 
						|
// for TUF and Notary keys.
 | 
						|
package passphrase
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/theupdateframework/notary"
 | 
						|
	"golang.org/x/crypto/ssh/terminal"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	idBytesToDisplay            = 7
 | 
						|
	tufRootAlias                = "root"
 | 
						|
	tufRootKeyGenerationWarning = `You are about to create a new root signing key passphrase. This passphrase
 | 
						|
will be used to protect the most sensitive key in your signing system. Please
 | 
						|
choose a long, complex passphrase and be careful to keep the password and the
 | 
						|
key file itself secure and backed up. It is highly recommended that you use a
 | 
						|
password manager to generate the passphrase and keep it safe. There will be no
 | 
						|
way to recover this key. You can find the key in your config directory.`
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	// ErrTooShort is returned if the passphrase entered for a new key is
 | 
						|
	// below the minimum length
 | 
						|
	ErrTooShort = errors.New("Passphrase too short")
 | 
						|
 | 
						|
	// ErrDontMatch is returned if the two entered passphrases don't match.
 | 
						|
	// new key is below the minimum length
 | 
						|
	ErrDontMatch = errors.New("The entered passphrases do not match")
 | 
						|
 | 
						|
	// ErrTooManyAttempts is returned if the maximum number of passphrase
 | 
						|
	// entry attempts is reached.
 | 
						|
	ErrTooManyAttempts = errors.New("Too many attempts")
 | 
						|
 | 
						|
	// ErrNoInput is returned if we do not have a valid input method for passphrases
 | 
						|
	ErrNoInput = errors.New("Please either use environment variables or STDIN with a terminal to provide key passphrases")
 | 
						|
)
 | 
						|
 | 
						|
// PromptRetriever returns a new Retriever which will provide a prompt on stdin
 | 
						|
// and stdout to retrieve a passphrase. stdin will be checked if it is a terminal,
 | 
						|
// else the PromptRetriever will error when attempting to retrieve a passphrase.
 | 
						|
// Upon successful passphrase retrievals, the passphrase will be cached such that
 | 
						|
// subsequent prompts will produce the same passphrase.
 | 
						|
func PromptRetriever() notary.PassRetriever {
 | 
						|
	if !terminal.IsTerminal(int(os.Stdin.Fd())) {
 | 
						|
		return func(string, string, bool, int) (string, bool, error) {
 | 
						|
			return "", false, ErrNoInput
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return PromptRetrieverWithInOut(os.Stdin, os.Stdout, nil)
 | 
						|
}
 | 
						|
 | 
						|
type boundRetriever struct {
 | 
						|
	in              io.Reader
 | 
						|
	out             io.Writer
 | 
						|
	aliasMap        map[string]string
 | 
						|
	passphraseCache map[string]string
 | 
						|
}
 | 
						|
 | 
						|
func (br *boundRetriever) getPassphrase(keyName, alias string, createNew bool, numAttempts int) (string, bool, error) {
 | 
						|
	if numAttempts == 0 {
 | 
						|
		if alias == tufRootAlias && createNew {
 | 
						|
			fmt.Fprintln(br.out, tufRootKeyGenerationWarning)
 | 
						|
		}
 | 
						|
 | 
						|
		if pass, ok := br.passphraseCache[alias]; ok {
 | 
						|
			return pass, false, nil
 | 
						|
		}
 | 
						|
	} else if !createNew { // per `if`, numAttempts > 0 if we're at this `else`
 | 
						|
		if numAttempts > 3 {
 | 
						|
			return "", true, ErrTooManyAttempts
 | 
						|
		}
 | 
						|
		fmt.Fprintln(br.out, "Passphrase incorrect. Please retry.")
 | 
						|
	}
 | 
						|
 | 
						|
	// passphrase not cached and we're not aborting, get passphrase from user!
 | 
						|
	return br.requestPassphrase(keyName, alias, createNew, numAttempts)
 | 
						|
}
 | 
						|
 | 
						|
func (br *boundRetriever) requestPassphrase(keyName, alias string, createNew bool, numAttempts int) (string, bool, error) {
 | 
						|
	// Figure out if we should display a different string for this alias
 | 
						|
	displayAlias := alias
 | 
						|
	if val, ok := br.aliasMap[alias]; ok {
 | 
						|
		displayAlias = val
 | 
						|
	}
 | 
						|
 | 
						|
	indexOfLastSeparator := strings.LastIndex(keyName, string(filepath.Separator))
 | 
						|
	if indexOfLastSeparator == -1 {
 | 
						|
		indexOfLastSeparator = 0
 | 
						|
	}
 | 
						|
 | 
						|
	var shortName string
 | 
						|
	if len(keyName) > indexOfLastSeparator+idBytesToDisplay {
 | 
						|
		if indexOfLastSeparator > 0 {
 | 
						|
			keyNamePrefix := keyName[:indexOfLastSeparator]
 | 
						|
			keyNameID := keyName[indexOfLastSeparator+1 : indexOfLastSeparator+idBytesToDisplay+1]
 | 
						|
			shortName = keyNameID + " (" + keyNamePrefix + ")"
 | 
						|
		} else {
 | 
						|
			shortName = keyName[indexOfLastSeparator : indexOfLastSeparator+idBytesToDisplay]
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	withID := fmt.Sprintf(" with ID %s", shortName)
 | 
						|
	if shortName == "" {
 | 
						|
		withID = ""
 | 
						|
	}
 | 
						|
 | 
						|
	switch {
 | 
						|
	case createNew:
 | 
						|
		fmt.Fprintf(br.out, "Enter passphrase for new %s key%s: ", displayAlias, withID)
 | 
						|
	case displayAlias == "yubikey":
 | 
						|
		fmt.Fprintf(br.out, "Enter the %s for the attached Yubikey: ", keyName)
 | 
						|
	default:
 | 
						|
		fmt.Fprintf(br.out, "Enter passphrase for %s key%s: ", displayAlias, withID)
 | 
						|
	}
 | 
						|
 | 
						|
	stdin := bufio.NewReader(br.in)
 | 
						|
	passphrase, err := GetPassphrase(stdin)
 | 
						|
	fmt.Fprintln(br.out)
 | 
						|
	if err != nil {
 | 
						|
		return "", false, err
 | 
						|
	}
 | 
						|
 | 
						|
	retPass := strings.TrimSpace(string(passphrase))
 | 
						|
 | 
						|
	if createNew {
 | 
						|
		err = br.verifyAndConfirmPassword(stdin, retPass, displayAlias, withID)
 | 
						|
		if err != nil {
 | 
						|
			return "", false, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	br.cachePassword(alias, retPass)
 | 
						|
 | 
						|
	return retPass, false, nil
 | 
						|
}
 | 
						|
 | 
						|
func (br *boundRetriever) verifyAndConfirmPassword(stdin *bufio.Reader, retPass, displayAlias, withID string) error {
 | 
						|
	if len(retPass) < 8 {
 | 
						|
		fmt.Fprintln(br.out, "Passphrase is too short. Please use a password manager to generate and store a good random passphrase.")
 | 
						|
		return ErrTooShort
 | 
						|
	}
 | 
						|
 | 
						|
	fmt.Fprintf(br.out, "Repeat passphrase for new %s key%s: ", displayAlias, withID)
 | 
						|
 | 
						|
	confirmation, err := GetPassphrase(stdin)
 | 
						|
	fmt.Fprintln(br.out)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	confirmationStr := strings.TrimSpace(string(confirmation))
 | 
						|
 | 
						|
	if retPass != confirmationStr {
 | 
						|
		fmt.Fprintln(br.out, "Passphrases do not match. Please retry.")
 | 
						|
		return ErrDontMatch
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (br *boundRetriever) cachePassword(alias, retPass string) {
 | 
						|
	br.passphraseCache[alias] = retPass
 | 
						|
}
 | 
						|
 | 
						|
// PromptRetrieverWithInOut returns a new Retriever which will provide a
 | 
						|
// prompt using the given in and out readers. The passphrase will be cached
 | 
						|
// such that subsequent prompts will produce the same passphrase.
 | 
						|
// aliasMap can be used to specify display names for TUF key aliases. If aliasMap
 | 
						|
// is nil, a sensible default will be used.
 | 
						|
func PromptRetrieverWithInOut(in io.Reader, out io.Writer, aliasMap map[string]string) notary.PassRetriever {
 | 
						|
	bound := &boundRetriever{
 | 
						|
		in:              in,
 | 
						|
		out:             out,
 | 
						|
		aliasMap:        aliasMap,
 | 
						|
		passphraseCache: make(map[string]string),
 | 
						|
	}
 | 
						|
 | 
						|
	return bound.getPassphrase
 | 
						|
}
 | 
						|
 | 
						|
// ConstantRetriever returns a new Retriever which will return a constant string
 | 
						|
// as a passphrase.
 | 
						|
func ConstantRetriever(constantPassphrase string) notary.PassRetriever {
 | 
						|
	return func(k, a string, c bool, n int) (string, bool, error) {
 | 
						|
		return constantPassphrase, false, nil
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// GetPassphrase get the passphrase from bufio.Reader or from terminal.
 | 
						|
// If typing on the terminal, we disable terminal to echo the passphrase.
 | 
						|
func GetPassphrase(in *bufio.Reader) ([]byte, error) {
 | 
						|
	var (
 | 
						|
		passphrase []byte
 | 
						|
		err        error
 | 
						|
	)
 | 
						|
 | 
						|
	if terminal.IsTerminal(int(os.Stdin.Fd())) {
 | 
						|
		passphrase, err = terminal.ReadPassword(int(os.Stdin.Fd()))
 | 
						|
	} else {
 | 
						|
		passphrase, err = in.ReadBytes('\n')
 | 
						|
	}
 | 
						|
 | 
						|
	return passphrase, err
 | 
						|
}
 |