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 }