mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-10-31 08:03:43 +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
 | |
| }
 | 
