github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/passphrase_login.go (about) 1 package libkb 2 3 import ( 4 "fmt" 5 6 keybase1 "github.com/keybase/client/go/protocol/keybase1" 7 ) 8 9 func loginWithPassphraseStream(mctx MetaContext, usernameOrEmail string, tsec Triplesec, 10 pps *PassphraseStream, ls *LoginSession) (err error) { 11 12 defer mctx.Trace("pplGotPassphrase", &err)() 13 14 loginSessionBytes, err := ls.Session() 15 if err != nil { 16 return err 17 } 18 pdpka, err := computeLoginPackageFromEmailOrUsername(usernameOrEmail, pps, loginSessionBytes) 19 if err != nil { 20 return err 21 } 22 res, err := pplPost(mctx, usernameOrEmail, pdpka) 23 if err != nil { 24 return err 25 } 26 27 var nilDeviceID keybase1.DeviceID 28 err = mctx.LoginContext().SaveState( 29 res.sessionID, 30 res.csrfToken, 31 NewNormalizedUsername(res.username), 32 res.uv, 33 nilDeviceID, 34 ) 35 if err != nil { 36 return err 37 } 38 pps.SetGeneration(res.ppGen) 39 mctx.LoginContext().CreateStreamCache(tsec, pps) 40 41 return nil 42 } 43 44 func pplPromptCheckPreconditions(m MetaContext, usernameOrEmail string) (err error) { 45 if m.LoginContext() == nil { 46 return InternalError{"PassphraseLoginPrompt: need a non-nil LoginContext"} 47 } 48 if m.UIs().SecretUI == nil { 49 return NoUIError{"secret"} 50 } 51 if m.UIs().LoginUI == nil && len(usernameOrEmail) == 0 { 52 return NoUIError{"login"} 53 } 54 return nil 55 } 56 57 func pplGetEmailOrUsername(m MetaContext, usernameOrEmail string) (string, error) { 58 var err error 59 60 if len(usernameOrEmail) > 0 { 61 return usernameOrEmail, nil 62 } 63 usernameOrEmail, err = m.UIs().LoginUI.GetEmailOrUsername(m.Ctx(), 0) 64 if err != nil { 65 return "", err 66 } 67 if len(usernameOrEmail) == 0 { 68 return "", NewNoUsernameError() 69 } 70 return usernameOrEmail, nil 71 } 72 73 func pplGetLoginSession(m MetaContext, usernameOrEmail string) (*LoginSession, error) { 74 ret := NewLoginSession(m.G(), usernameOrEmail) 75 err := ret.Load(m) 76 if err != nil { 77 ret = nil 78 } 79 // Update the LoginContext() so that other downstream calls can use this LoginContext. 80 // In particular, DeleteAccountWithContext needs this login context. We might choose 81 // to plumb it all the way back, this system is way more convenient (though harder to 82 // follow). 83 if ret != nil { 84 m.LoginContext().SetLoginSession(ret) 85 } 86 return ret, err 87 } 88 89 func pplPromptOnce(m MetaContext, ls *LoginSession, arg keybase1.GUIEntryArg) (err error) { 90 defer m.Trace("pplPromptOnce", &err)() 91 ppres, err := GetKeybasePassphrase(m, m.UIs().SecretUI, arg) 92 if err != nil { 93 return err 94 } 95 96 return pplGotPassphrase(m, arg.Username, ppres.Passphrase, ls) 97 } 98 99 func pplGotPassphrase(m MetaContext, usernameOrEmail string, passphrase string, ls *LoginSession) (err error) { 100 defer m.Trace("pplGotPassphrase", &err)() 101 102 tsec, pps, err := StretchPassphrase(m.G(), passphrase, ls.salt) 103 if err != nil { 104 return err 105 } 106 return loginWithPassphraseStream(m, usernameOrEmail, tsec, pps, ls) 107 } 108 109 func pplPromptLoop(m MetaContext, maxAttempts int, ls *LoginSession, arg keybase1.GUIEntryArg) (err error) { 110 defer m.Trace("pplPromptLoop", &err)() 111 for i := 0; i < maxAttempts; i++ { 112 if err = pplPromptOnce(m, ls, arg); err == nil { 113 return nil 114 } 115 if _, badpw := err.(PassphraseError); !badpw { 116 return err 117 } 118 arg.RetryLabel = err.Error() 119 } 120 return err 121 } 122 123 type loginReply struct { 124 Status AppStatus `json:"status"` 125 Session string `json:"session"` 126 CsrfToken string `json:"csrf_token"` 127 UID keybase1.UID `json:"uid"` 128 Me struct { 129 Basics struct { 130 Username string `json:"username"` 131 PassphraseGeneration PassphraseGeneration `json:"passphrase_generation"` 132 EldestSeqno keybase1.Seqno `json:"eldest_seqno"` 133 } `json:"basics"` 134 } `json:"me"` 135 } 136 137 func (l *loginReply) GetAppStatus() *AppStatus { 138 return &l.Status 139 } 140 141 func pplPost(m MetaContext, eOu string, lp PDPKALoginPackage) (*loginAPIResult, error) { 142 143 arg := APIArg{ 144 Endpoint: "login", 145 SessionType: APISessionTypeNONE, 146 Args: HTTPArgs{ 147 "email_or_username": S{eOu}, 148 }, 149 AppStatusCodes: []int{SCOk, SCBadLoginPassword, SCBadLoginUserNotFound}, 150 } 151 lp.PopulateArgs(&arg.Args) 152 var res loginReply 153 err := m.G().API.PostDecode(m, arg, &res) 154 if err != nil { 155 return nil, err 156 } 157 if res.Status.Code == SCBadLoginPassword { 158 // NOTE: This error message is also hardcoded in the frontend to detect 159 // this class of errors. 160 return nil, PassphraseError{"Invalid password. Server rejected login attempt."} 161 } 162 if res.Status.Code == SCBadLoginUserNotFound { 163 return nil, NotFoundError{} 164 } 165 return &loginAPIResult{ 166 sessionID: res.Session, 167 csrfToken: res.CsrfToken, 168 uv: keybase1.UserVersion{Uid: res.UID, EldestSeqno: res.Me.Basics.EldestSeqno}, 169 username: res.Me.Basics.Username, 170 ppGen: res.Me.Basics.PassphraseGeneration, 171 }, nil 172 } 173 174 func PassphraseLoginNoPrompt(m MetaContext, usernameOrEmail string, passphrase string) (err error) { 175 defer m.Trace("PassphraseLoginNoPrompt", &err)() 176 177 var loginSession *LoginSession 178 if loginSession, err = pplGetLoginSession(m, usernameOrEmail); err != nil { 179 return err 180 } 181 return pplGotPassphrase(m, usernameOrEmail, passphrase, loginSession) 182 } 183 184 func PassphraseLoginNoPromptThenSecretStore(m MetaContext, usernameOrEmail string, passphrase string, failOnStoreError bool) (err error) { 185 defer m.Trace("PassphraseLoginNoPromptThenSecretStore", &err)() 186 187 err = PassphraseLoginNoPrompt(m, usernameOrEmail, passphrase) 188 if err != nil { 189 return err 190 } 191 storeErr := pplSecretStore(m) 192 if storeErr == nil { 193 return nil 194 } 195 if failOnStoreError { 196 return storeErr 197 } 198 m.Warning("Secret store failure: %s", storeErr) 199 return nil 200 } 201 202 func PassphraseLoginPromptWithArg(m MetaContext, maxAttempts int, arg keybase1.GUIEntryArg) (err error) { 203 defer m.Trace("PassphraseLoginPrompt", &err)() 204 205 if err = pplPromptCheckPreconditions(m, arg.Username); err != nil { 206 return err 207 } 208 if arg.Username, err = pplGetEmailOrUsername(m, arg.Username); err != nil { 209 return err 210 } 211 loginSession, err := pplGetLoginSession(m, arg.Username) 212 if err != nil { 213 return err 214 } 215 return pplPromptLoop(m, maxAttempts, loginSession, arg) 216 } 217 218 func PassphraseLoginPrompt(m MetaContext, usernameOrEmail string, maxAttempts int) (err error) { 219 arg := DefaultPassphrasePromptArg(m, usernameOrEmail) 220 return PassphraseLoginPromptWithArg(m, maxAttempts, arg) 221 } 222 223 func pplSecretStore(m MetaContext) (err error) { 224 lctx := m.LoginContext() 225 uid := lctx.GetUID() 226 if uid.IsNil() { 227 return NoUIDError{} 228 } 229 deviceID := m.G().Env.GetDeviceIDForUID(uid) 230 if deviceID.IsNil() { 231 return NewNoDeviceError(fmt.Sprintf("UID=%s", uid)) 232 } 233 return StoreSecretAfterLoginWithOptions(m, lctx.GetUsername(), uid, deviceID, nil) 234 } 235 236 func PassphraseLoginPromptThenSecretStore(m MetaContext, usernameOrEmail string, maxAttempts int, failOnStoreError bool) (err error) { 237 defer m.Trace("PassphraseLoginPromptThenSecretStore", &err)() 238 239 err = PassphraseLoginPrompt(m, usernameOrEmail, maxAttempts) 240 if err != nil { 241 return err 242 } 243 244 storeErr := pplSecretStore(m) 245 if storeErr == nil { 246 return nil 247 } 248 if failOnStoreError { 249 return storeErr 250 } 251 m.Debug("Secret store failure: %s", storeErr) 252 return nil 253 } 254 255 func LoadAdvisorySecretStoreOptionsFromRemote(mctx MetaContext) (options SecretStoreOptions) { 256 options = DefaultSecretStoreOptions() 257 258 var ret struct { 259 AppStatusEmbed 260 RandomPW bool `json:"random_pw"` 261 } 262 err := mctx.G().API.GetDecode(mctx, APIArg{ 263 Endpoint: "user/has_random_pw", 264 SessionType: APISessionTypeREQUIRED, 265 }, &ret) 266 if err != nil { 267 mctx.Warning("Failed to load advisory secret store options from remote: %s", err) 268 // If there was an API error, just return the default options. 269 options.RandomPw = true 270 return options 271 } 272 options.RandomPw = ret.RandomPW 273 274 return options 275 } 276 277 func StoreSecretAfterLoginWithOptions(m MetaContext, n NormalizedUsername, uid keybase1.UID, deviceID keybase1.DeviceID, options *SecretStoreOptions) (err error) { 278 defer m.Trace("StoreSecretAfterLogin", &err)() 279 lksec := NewLKSecWithDeviceID(m.LoginContext().PassphraseStreamCache().PassphraseStream(), uid, deviceID) 280 return StoreSecretAfterLoginWithLKSWithOptions(m, n, lksec, options) 281 } 282 283 func StoreSecretAfterLoginWithLKSWithOptions(m MetaContext, n NormalizedUsername, lks *LKSec, options *SecretStoreOptions) (err error) { 284 defer m.Trace("StoreSecretAfterLoginWithLKSWithOptions", &err)() 285 286 secretStore := NewSecretStore(m, n) 287 if secretStore == nil { 288 m.Debug("not storing secret; no secret store available") 289 return nil 290 } 291 292 secret, err := lks.GetSecret(m) 293 if err != nil { 294 return err 295 } 296 297 previousOptions := secretStore.GetOptions(m) 298 secretStore.SetOptions(m, options) 299 ret := secretStore.StoreSecret(m, secret) 300 secretStore.SetOptions(m, previousOptions) 301 return ret 302 } 303 304 func getStoredPassphraseStream(m MetaContext) (*PassphraseStream, error) { 305 fullSecret, err := m.G().SecretStore().RetrieveSecret(m, m.CurrentUsername()) 306 if err != nil { 307 return nil, err 308 } 309 lks := NewLKSecWithFullSecret(fullSecret, m.CurrentUID()) 310 if err = lks.LoadServerHalf(m); err != nil { 311 return nil, err 312 } 313 stream, err := NewPassphraseStreamLKSecOnly(lks) 314 if err != nil { 315 return nil, err 316 } 317 return stream, nil 318 } 319 320 // GetPassphraseStreamStored either returns a cached, verified passphrase 321 // stream from a previous login, the secret store, or generates a new one via 322 // login. NOTE: this function can return a partial passphrase stream if it 323 // reads from the secret store. It won't have the material used to decrypt 324 // server-synced keys or to generate PDPKA material in that case. 325 func GetPassphraseStreamStored(m MetaContext) (pps *PassphraseStream, err error) { 326 defer m.Trace("GetPassphraseStreamStored", &err)() 327 328 // 1. try cached 329 m.Debug("| trying cached passphrase stream") 330 if pps = m.PassphraseStream(); pps != nil { 331 m.Debug("| cached passphrase stream ok, using it") 332 return pps, nil 333 } 334 335 // 2. try from secret store 336 if m.G().SecretStore() != nil { 337 m.Debug("| trying to get passphrase stream from secret store") 338 pps, err = getStoredPassphraseStream(m) 339 if err == nil { 340 m.Debug("| got passphrase stream from secret store") 341 return pps, nil 342 } 343 m.Info("| failed to get passphrase stream from secret store: %s", err) 344 } 345 346 // 3. login and get it 347 m.Debug("| using full GetPassphraseStream") 348 pps, _, err = GetPassphraseStreamViaPrompt(m) 349 if pps != nil { 350 m.Debug("| success using full GetPassphraseStream") 351 } 352 return pps, err 353 } 354 355 // GetTriplesecMaybePrompt will try to get the user's current triplesec. 356 // It will either pluck it out of the environment or prompt the user for 357 // a passphrase if it can't be found. The secret store is of no use here, 358 // so skip it. Recall that the full passphrase stream isn't stored to 359 // the secret store, only the bits that encrypt local keys. 360 func GetTriplesecMaybePrompt(m MetaContext) (tsec Triplesec, ppgen PassphraseGeneration, err error) { 361 defer m.Trace("GetTriplesecMaybePrompt", &err)() 362 363 // 1. try cached 364 m.Debug("| trying cached triplesec") 365 if tsec, ppgen = m.TriplesecAndGeneration(); tsec != nil && !ppgen.IsNil() { 366 m.Debug("| cached trieplsec stream ok, using it") 367 return tsec, ppgen, nil 368 } 369 370 // 2. login and get it 371 m.Debug("| using full GetPassphraseStreamViaPrompt") 372 var pps *PassphraseStream 373 pps, tsec, err = GetPassphraseStreamViaPrompt(m) 374 if err != nil { 375 return nil, ppgen, err 376 } 377 if pps == nil { 378 m.Debug("| Got back empty passphrase stream; returning nil") 379 return nil, ppgen, NewNoTriplesecError() 380 } 381 if tsec == nil { 382 m.Debug("| Got back empty triplesec") 383 return nil, ppgen, NewNoTriplesecError() 384 } 385 ppgen = pps.Generation() 386 if ppgen.IsNil() { 387 m.Debug("| Got back a non-nill Triplesec but an invalid ppgen; returning nil") 388 return nil, ppgen, NewNoTriplesecError() 389 } 390 m.Debug("| got non-nil Triplesec back from prompt") 391 return tsec, ppgen, err 392 } 393 394 // GetPassphraseStreamViaPrompt prompts the user for a passphrase and on 395 // success returns a PassphraseStream and Triplesec derived from the user's 396 // passphrase. As a side effect, it stores the full LKSec in the secret store. 397 func GetPassphraseStreamViaPrompt(m MetaContext) (pps *PassphraseStream, tsec Triplesec, err error) { 398 399 // We have to get the current username before we install the new provisional login context, 400 // which will shadow the logged in username. 401 nun := m.CurrentUsername() 402 defer m.Trace(fmt.Sprintf("GetPassphraseStreamViaPrompt(%s)", nun), &err)() 403 404 m = m.WithNewProvisionalLoginContext() 405 err = PassphraseLoginPromptThenSecretStore(m, nun.String(), 5, false /* failOnStoreError */) 406 if err != nil { 407 return nil, nil, err 408 } 409 pps, tsec = m.PassphraseStreamAndTriplesec() 410 m.CommitProvisionalLogin() 411 412 return pps, tsec, nil 413 } 414 415 // GetFullPassphraseStreamViaPrompt gets the user's passphrase stream either cached from the 416 // LoginContext or from the prompt. It doesn't involve the secret store at all, since 417 // the full passphrase stream isn't stored in the secret store. And also it doesn't 418 // write the secret store because this function is called right before the user 419 // changes to a new passphrase, so what's the point. It's assumed that the login context is 420 // set to non-nil by the caller. 421 func GetPassphraseStreamViaPromptInLoginContext(m MetaContext) (pps *PassphraseStream, err error) { 422 defer m.Trace("GetPassphraseStreamViaPromptInLoginContext", &err)() 423 if pps = m.PassphraseStream(); pps != nil { 424 return pps, nil 425 } 426 nun := m.CurrentUsername() 427 if nun.IsNil() { 428 return nil, NewNoUsernameError() 429 } 430 if err = PassphraseLoginPrompt(m, nun.String(), 5); err != nil { 431 return nil, err 432 } 433 return m.PassphraseStream(), nil 434 } 435 436 // VerifyPassphraseGetFullStream verifies the current passphrase is a correct login 437 // and if so, will return a full passphrase stream derived from it. Assumes the caller 438 // made a non-nil LoginContext for us to operate in. 439 func VerifyPassphraseGetStreamInLoginContext(m MetaContext, passphrase string) (pps *PassphraseStream, err error) { 440 defer m.Trace("VerifyPassphraseGetStreamInLoginContext", &err)() 441 nun := m.CurrentUsername() 442 if nun.IsNil() { 443 return nil, NewNoUsernameError() 444 } 445 if err = PassphraseLoginNoPrompt(m, nun.String(), passphrase); err != nil { 446 return nil, err 447 } 448 return m.PassphraseStream(), nil 449 } 450 451 // VerifyPassphraseForLoggedInUser verifies that the current passphrase is correct for the logged 452 // in user, returning nil if correct, and an error if not. Only used in tests right now, but 453 // it's fine to use in production code if it seems appropriate. 454 func VerifyPassphraseForLoggedInUser(m MetaContext, pp string) (pps *PassphraseStream, err error) { 455 defer m.Trace("VerifyPassphraseForLoggedInUser", &err)() 456 uv, un := m.ActiveDevice().GetUsernameAndUserVersionIfValid(m) 457 if uv.IsNil() { 458 return nil, NewLoginRequiredError("for VerifyPassphraseForLoggedInUser") 459 } 460 m = m.WithNewProvisionalLoginContextForUserVersionAndUsername(uv, un) 461 pps, err = VerifyPassphraseGetStreamInLoginContext(m, pp) 462 return pps, err 463 } 464 465 // ComputeLoginPackage2 computes the login package for the given UID as dictated by 466 // the context. It assumes that a passphrase stream has already been loaded. A LoginSession 467 // is optional. If not available, a new one is requested. Eventually we will kill ComputeLoginPackage 468 // and rename this to that. 469 func ComputeLoginPackage2(m MetaContext, pps *PassphraseStream) (ret PDPKALoginPackage, err error) { 470 471 defer m.Trace("ComputeLoginPackage2", &err)() 472 var ls *LoginSession 473 if m.LoginContext() != nil { 474 ls = m.LoginContext().LoginSession() 475 } 476 if ls == nil { 477 ls, err = pplGetLoginSession(m, m.CurrentUsername().String()) 478 if err != nil { 479 return ret, err 480 } 481 } 482 var loginSessionRaw []byte 483 loginSessionRaw, err = ls.Session() 484 if err != nil { 485 return ret, err 486 } 487 return computeLoginPackageFromUID(m.CurrentUID(), pps, loginSessionRaw) 488 } 489 490 // UnverifiedPassphraseStream takes a passphrase as a parameter and 491 // also the salt from the Account and computes a Triplesec and 492 // a passphrase stream. It's not verified through a Login. 493 func UnverifiedPassphraseStream(m MetaContext, uid keybase1.UID, passphrase string) (tsec Triplesec, ret *PassphraseStream, err error) { 494 var salt []byte 495 if lctx := m.LoginContext(); lctx != nil && lctx.GetUID().Equal(uid) { 496 salt = lctx.Salt() 497 } 498 if salt == nil { 499 salt, err = LookupSaltForUID(m, uid) 500 if err != nil { 501 return nil, nil, err 502 } 503 } 504 return StretchPassphrase(m.G(), passphrase, salt) 505 } 506 507 func LoginFromPassphraseStream(mctx MetaContext, username string, pps *PassphraseStream) (err error) { 508 defer mctx.Trace("LoginFromPassphraseStream", &err)() 509 ls, err := pplGetLoginSession(mctx, username) 510 if err != nil { 511 return err 512 } 513 return loginWithPassphraseStream(mctx, username, nil, pps, ls) 514 }