github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/passphrase_helper.go (about)

     1  package libkb
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    10  )
    11  
    12  func GetKeybasePassphrase(m MetaContext, ui SecretUI, arg keybase1.GUIEntryArg) (keybase1.GetPassphraseRes, error) {
    13  	resCh := make(chan keybase1.GetPassphraseRes)
    14  	errCh := make(chan error)
    15  	go func() {
    16  		res, err := GetPassphraseUntilCheckWithChecker(m, arg,
    17  			newUIPrompter(ui), &CheckPassphraseSimple)
    18  		if err != nil {
    19  			errCh <- err
    20  			return
    21  		}
    22  		res.StoreSecret = true
    23  		resCh <- res
    24  	}()
    25  
    26  	select {
    27  	case res := <-resCh:
    28  		return res, nil
    29  	case err := <-errCh:
    30  		return keybase1.GetPassphraseRes{}, err
    31  	case <-time.After(3 * time.Minute):
    32  		return keybase1.GetPassphraseRes{}, TimeoutError{}
    33  	}
    34  }
    35  
    36  func GetSecret(m MetaContext, ui SecretUI, title, prompt, retryMsg string, allowSecretStore bool) (keybase1.GetPassphraseRes, error) {
    37  	arg := DefaultPassphraseArg(m)
    38  	arg.WindowTitle = title
    39  	arg.Type = keybase1.PassphraseType_PASS_PHRASE
    40  	arg.Prompt = prompt
    41  	arg.RetryLabel = retryMsg
    42  	res, err := GetPassphraseUntilCheckWithChecker(m, arg, newUIPrompter(ui), &CheckPassphraseSimple)
    43  	if err != nil {
    44  		return res, err
    45  	}
    46  	res.StoreSecret = allowSecretStore
    47  	return res, nil
    48  }
    49  
    50  func GetPaperKeyPassphrase(m MetaContext, ui SecretUI, username string, lastErr error, expectedPrefix *string) (string, error) {
    51  	arg := DefaultPassphraseArg(m)
    52  	arg.WindowTitle = "Paper Key"
    53  	arg.Type = keybase1.PassphraseType_PAPER_KEY
    54  	if len(username) == 0 {
    55  		username = "your account"
    56  	}
    57  	arg.Prompt = fmt.Sprintf("Please enter a paper key for %s", username)
    58  	arg.Username = username
    59  	arg.Features.ShowTyping.Allow = true
    60  	arg.Features.ShowTyping.DefaultValue = true
    61  	if lastErr != nil {
    62  		arg.RetryLabel = lastErr.Error()
    63  	}
    64  	res, err := GetPassphraseUntilCheck(m, arg, newUIPrompter(ui), &PaperChecker{expectedPrefix})
    65  	if err != nil {
    66  		return "", err
    67  	}
    68  	return res.Passphrase, nil
    69  }
    70  
    71  func GetPaperKeyForCryptoPassphrase(m MetaContext, ui SecretUI, reason string, devices []*Device) (string, error) {
    72  	if len(devices) == 0 {
    73  		return "", errors.New("empty device list")
    74  	}
    75  	arg := DefaultPassphraseArg(m)
    76  	arg.WindowTitle = "Paper Key"
    77  	arg.Type = keybase1.PassphraseType_PAPER_KEY
    78  	arg.Features.ShowTyping.Allow = true
    79  	arg.Features.ShowTyping.DefaultValue = true
    80  	if len(devices) == 1 {
    81  		arg.Prompt = fmt.Sprintf("%s: please enter the paper key '%s...'", reason, *devices[0].Description)
    82  	} else {
    83  		descs := make([]string, len(devices))
    84  		for i, dev := range devices {
    85  			descs[i] = fmt.Sprintf("'%s...'", *dev.Description)
    86  		}
    87  		paperOpts := strings.Join(descs, " or ")
    88  		arg.Prompt = fmt.Sprintf("%s: please enter one of the following paper keys %s", reason, paperOpts)
    89  	}
    90  
    91  	res, err := GetPassphraseUntilCheck(m, arg, newUIPrompter(ui), &PaperChecker{})
    92  	if err != nil {
    93  		return "", err
    94  	}
    95  	return res.Passphrase, nil
    96  }
    97  
    98  func GetNewKeybasePassphrase(mctx MetaContext, ui SecretUI, arg keybase1.GUIEntryArg, confirm string) (keybase1.GetPassphraseRes, error) {
    99  	initialPrompt := arg.Prompt
   100  
   101  	for i := 0; i < 10; i++ {
   102  		res, err := GetPassphraseUntilCheckWithChecker(mctx, arg,
   103  			newUIPrompter(ui), &CheckPassphraseNew)
   104  		if err != nil {
   105  			return keybase1.GetPassphraseRes{}, nil
   106  		}
   107  
   108  		// confirm the password
   109  		arg.RetryLabel = ""
   110  		arg.Prompt = confirm
   111  		confirm, err := GetPassphraseUntilCheckWithChecker(mctx, arg,
   112  			newUIPrompter(ui), &CheckPassphraseNew)
   113  		if err != nil {
   114  			return keybase1.GetPassphraseRes{}, nil
   115  		}
   116  
   117  		if res.Passphrase == confirm.Passphrase {
   118  			return res, nil
   119  		}
   120  
   121  		// setup the prompt, label for new first attempt
   122  		arg.Prompt = initialPrompt
   123  		arg.RetryLabel = "Passphrase mismatch"
   124  	}
   125  
   126  	return keybase1.GetPassphraseRes{}, RetryExhaustedError{}
   127  }
   128  
   129  type PassphrasePrompter interface {
   130  	Prompt(keybase1.GUIEntryArg) (keybase1.GetPassphraseRes, error)
   131  }
   132  
   133  type uiPrompter struct {
   134  	ui SecretUI
   135  }
   136  
   137  var _ PassphrasePrompter = &uiPrompter{}
   138  
   139  func newUIPrompter(ui SecretUI) *uiPrompter {
   140  	return &uiPrompter{ui: ui}
   141  }
   142  
   143  func (u *uiPrompter) Prompt(arg keybase1.GUIEntryArg) (keybase1.GetPassphraseRes, error) {
   144  	return u.ui.GetPassphrase(arg, nil)
   145  }
   146  
   147  func GetPassphraseUntilCheckWithChecker(m MetaContext, arg keybase1.GUIEntryArg, prompter PassphrasePrompter, checker *Checker) (keybase1.GetPassphraseRes, error) {
   148  	if checker == nil {
   149  		return keybase1.GetPassphraseRes{}, errors.New("nil passphrase checker")
   150  	}
   151  	w := &CheckerWrapper{checker: *checker}
   152  	return GetPassphraseUntilCheck(m, arg, prompter, w)
   153  }
   154  
   155  func GetPassphraseUntilCheck(m MetaContext, arg keybase1.GUIEntryArg, prompter PassphrasePrompter, checker PassphraseChecker) (keybase1.GetPassphraseRes, error) {
   156  	for i := 0; i < 10; i++ {
   157  		res, err := prompter.Prompt(arg)
   158  		if err != nil {
   159  			return keybase1.GetPassphraseRes{}, err
   160  		}
   161  		if checker == nil {
   162  			return res, nil
   163  		}
   164  
   165  		s := res.Passphrase
   166  		t, err := checker.Automutate(m, s)
   167  		if err != nil {
   168  			return keybase1.GetPassphraseRes{}, err
   169  		}
   170  		res = keybase1.GetPassphraseRes{Passphrase: t, StoreSecret: res.StoreSecret}
   171  
   172  		err = checker.Check(m, res.Passphrase)
   173  		if err == nil {
   174  			return res, nil
   175  		}
   176  		arg.RetryLabel = err.Error()
   177  	}
   178  	return keybase1.GetPassphraseRes{}, RetryExhaustedError{}
   179  }
   180  
   181  func DefaultPassphraseArg(m MetaContext) keybase1.GUIEntryArg {
   182  	arg := keybase1.GUIEntryArg{
   183  		SubmitLabel: "Submit",
   184  		CancelLabel: "Cancel",
   185  		Features: keybase1.GUIEntryFeatures{
   186  			ShowTyping: keybase1.Feature{
   187  				Allow:        true,
   188  				DefaultValue: false,
   189  				Readonly:     true,
   190  				Label:        "Show typing",
   191  			},
   192  		},
   193  	}
   194  	return arg
   195  }
   196  
   197  func DefaultPassphrasePromptArg(mctx MetaContext, username string) keybase1.GUIEntryArg {
   198  	arg := DefaultPassphraseArg(mctx)
   199  	arg.WindowTitle = "Keybase password"
   200  	arg.Type = keybase1.PassphraseType_PASS_PHRASE
   201  	arg.Username = username
   202  	arg.Prompt = fmt.Sprintf("Please enter the Keybase password for %s (%d+ characters)", username, MinPassphraseLength)
   203  	return arg
   204  }
   205  
   206  // PassphraseChecker is an interface for checking the format of a
   207  // passphrase. Returns nil if the format is ok, or a descriptive
   208  // hint otherwise.
   209  type PassphraseChecker interface {
   210  	Check(MetaContext, string) error
   211  	Automutate(MetaContext, string) (string, error)
   212  }
   213  
   214  // CheckerWrapper wraps a Checker type to make it conform to the
   215  // PassphraseChecker interface.
   216  type CheckerWrapper struct {
   217  	checker Checker
   218  }
   219  
   220  func (w *CheckerWrapper) Automutate(m MetaContext, s string) (string, error) {
   221  	return s, nil
   222  }
   223  
   224  // Check s using checker, respond with checker.Hint if check
   225  // fails.
   226  func (w *CheckerWrapper) Check(m MetaContext, s string) error {
   227  	if w.checker.F(s) {
   228  		return nil
   229  	}
   230  	return errors.New(w.checker.Hint)
   231  }
   232  
   233  // PaperChecker implements PassphraseChecker for paper keys.
   234  type PaperChecker struct {
   235  	expectedPrefix *string
   236  }
   237  
   238  func (p *PaperChecker) Automutate(m MetaContext, s string) (string, error) {
   239  	phrase := NewPaperKeyPhrase(s)
   240  	if phrase.NumWords() == PaperKeyNoPrefixLen {
   241  		if p.expectedPrefix == nil {
   242  			return "", errors.New("No prefix given but expectedPrefix is nil; must give the entire paper key.")
   243  		}
   244  		return fmt.Sprintf("%s %s", *p.expectedPrefix, s), nil
   245  	}
   246  	return s, nil
   247  }
   248  
   249  // Check a paper key format.  Will return a detailed error message
   250  // specific to the problems found in s.
   251  func (p *PaperChecker) Check(m MetaContext, s string) error {
   252  	phrase := NewPaperKeyPhrase(s)
   253  
   254  	// check for empty
   255  	if len(phrase.String()) == 0 {
   256  		m.Debug("paper phrase is empty")
   257  		return NewPaperKeyError("paper key was empty", true)
   258  	}
   259  
   260  	// check for at least PaperKeyWordCountMin words
   261  	if phrase.NumWords() < PaperKeyWordCountMin {
   262  		return NewPaperKeyError(fmt.Sprintf("your paper key should have at least %d words", PaperKeyWordCountMin), true)
   263  	}
   264  
   265  	// check for invalid words
   266  	invalids := phrase.InvalidWords()
   267  	if len(invalids) > 0 {
   268  		m.Debug("paper phrase has invalid word(s) in it")
   269  		var err error
   270  		var w []string
   271  		for _, i := range invalids {
   272  			w = append(w, fmt.Sprintf("%q", i))
   273  		}
   274  		if len(invalids) > 1 {
   275  			err = NewPaperKeyError(fmt.Sprintf("the words %s are invalid", strings.Join(w, ", ")), true)
   276  		} else {
   277  			err = NewPaperKeyError(fmt.Sprintf("the word %s is invalid", w[0]), true)
   278  		}
   279  		return err
   280  	}
   281  
   282  	// check version
   283  	version, err := phrase.Version()
   284  	if err != nil {
   285  		m.Debug("error getting paper key version: %s", err)
   286  		// despite the error, just tell the user the paper key is wrong:
   287  		return NewPaperKeyError("key didn't match any known keys for this account", true)
   288  	}
   289  	if version != PaperKeyVersion {
   290  		m.Debug("paper key version mismatch: generated version = %d, libkb version = %d", version, PaperKeyVersion)
   291  		return NewPaperKeyError("key didn't match any known keys for this account", true)
   292  	}
   293  
   294  	return nil
   295  }