1// Copyright 2015 Keybase, Inc. All rights reserved. Use of 2// this source code is governed by the included BSD license. 3 4package engine 5 6import ( 7 "github.com/keybase/client/go/libkb" 8 "github.com/keybase/client/go/protocol/keybase1" 9) 10 11// LoginProvisionedDevice is an engine that tries to login using the 12// current device, if there is an existing provisioned device. 13type LoginProvisionedDevice struct { 14 libkb.Contextified 15 username libkb.NormalizedUsername 16 uid keybase1.UID 17 deviceID keybase1.DeviceID 18 SecretStoreOnly bool // this should only be set by the service on its startup login attempt 19} 20 21// newLoginCurrentDevice creates a loginProvisionedDevice engine. 22func NewLoginProvisionedDevice(g *libkb.GlobalContext, username string) *LoginProvisionedDevice { 23 return &LoginProvisionedDevice{ 24 username: libkb.NewNormalizedUsername(username), 25 Contextified: libkb.NewContextified(g), 26 } 27} 28 29// Name is the unique engine name. 30func (e *LoginProvisionedDevice) Name() string { 31 return "LoginProvisionedDevice" 32} 33 34// GetPrereqs returns the engine prereqs. 35func (e *LoginProvisionedDevice) Prereqs() Prereqs { 36 return Prereqs{} 37} 38 39// RequiredUIs returns the required UIs. 40func (e *LoginProvisionedDevice) RequiredUIs() []libkb.UIKind { 41 if e.SecretStoreOnly { 42 return []libkb.UIKind{} 43 } 44 45 return []libkb.UIKind{ 46 libkb.LoginUIKind, 47 libkb.SecretUIKind, 48 } 49} 50 51// SubConsumers returns the other UI consumers for this engine. 52func (e *LoginProvisionedDevice) SubConsumers() []libkb.UIConsumer { 53 return []libkb.UIConsumer{} 54} 55 56func (e *LoginProvisionedDevice) Run(m libkb.MetaContext) error { 57 if err := e.run(m); err != nil { 58 return err 59 } 60 61 m.Debug("LoginProvisionedDevice success, sending login notification") 62 m.G().NotifyRouter.HandleLogin(m.Ctx(), e.username.String()) 63 m.Debug("LoginProvisionedDevice success, calling login hooks") 64 m.G().CallLoginHooks(m) 65 66 return nil 67} 68 69func (e *LoginProvisionedDevice) loadMe(m libkb.MetaContext) (err error) { 70 defer m.Trace("LoginProvisionedDevice#loadMe", &err)() 71 72 var config *libkb.UserConfig 73 var nu libkb.NormalizedUsername 74 loadUserArg := libkb.NewLoadUserArgWithMetaContext(m). 75 WithPublicKeyOptional().WithForcePoll(true).WithStaleOK(true) 76 if len(e.username) == 0 { 77 m.Debug("| using current username") 78 config, err = m.G().Env.GetConfig().GetUserConfig() 79 if config == nil { 80 m.Debug("user config is nil") 81 return errNoConfig 82 } 83 loadUserArg = loadUserArg.WithSelf(true).WithUID(config.GetUID()) 84 } else { 85 m.Debug("| using new username %s", e.username) 86 nu = e.username 87 config, err = m.G().Env.GetConfig().GetUserConfigForUsername(nu) 88 loadUserArg = loadUserArg.WithName(e.username.String()) 89 if config == nil { 90 m.Debug("user config is nil for %s", e.username) 91 return errNoConfig 92 } 93 } 94 if err != nil { 95 m.Debug("error getting user config: %s (%T)", err, err) 96 return errNoConfig 97 } 98 deviceID := config.GetDeviceID() 99 if deviceID.IsNil() { 100 m.Debug("no device in user config") 101 return errNoDevice 102 } 103 104 // Make sure the device ID is still valid. 105 upak, _, err := m.G().GetUPAKLoader().LoadV2(loadUserArg) 106 if err != nil { 107 m.Debug("error loading user profile: %#v", err) 108 return err 109 } 110 if upak.Current.Status == keybase1.StatusCode_SCDeleted { 111 m.Debug("User %s was deleted", upak.Current.Uid) 112 return libkb.UserDeletedError{} 113 } 114 115 nu = libkb.NewNormalizedUsername(upak.Current.Username) 116 device := upak.Current.FindSigningDeviceKey(deviceID) 117 118 nukeDevice := false 119 if device == nil { 120 m.Debug("Current device %s not found", deviceID) 121 nukeDevice = true 122 } else if device.Base.Revocation != nil { 123 m.Debug("Current device %s has been revoked", deviceID) 124 nukeDevice = true 125 } 126 127 if nukeDevice { 128 // If our config file is showing that we have a bogus 129 // deviceID (maybe from our account before an account reset), 130 // then we'll delete it from the config file here, so later parts 131 // of provisioning aren't confused by this device ID. 132 tmp := m.SwitchUserNukeConfig(nu) 133 if tmp != nil { 134 m.Warning("Error clearing user config: %s", tmp) 135 } 136 return errNoDevice 137 } 138 139 e.username = nu 140 e.deviceID = deviceID 141 e.uid = upak.Current.Uid 142 return nil 143} 144 145func (e *LoginProvisionedDevice) reattemptUnlockIfDifferentUID(m libkb.MetaContext, loggedInUID keybase1.UID) (success bool, err error) { 146 defer m.Trace("LoginProvisionedDevice#reattemptUnlockIfDifferentUID", &err)() 147 if loggedInUID.Equal(e.uid) { 148 m.Debug("no reattempting unlock; already tried for same UID") 149 return false, nil 150 } 151 return e.reattemptUnlock(m) 152} 153 154// reattemptUnlock reattempts to unlock the device's device keys. We already tried implicitly 155// early on in the run() function via `isLoggedIn`, which calls `Bootstrap...`. We try the whole 156// shebang again twice more: once after switching users (if there is indeeed a switch). And again 157// after asking the user for a passphrase login. 158func (e *LoginProvisionedDevice) reattemptUnlock(m libkb.MetaContext) (success bool, err error) { 159 defer m.Trace("LoginProvisionedDevice#reattemptUnlock", &err)() 160 ad, err := libkb.LoadProvisionalActiveDevice(m, e.uid, e.deviceID, true) 161 if err != nil { 162 m.Debug("Failed to load provisional device for user, but swallowing error: %s", err.Error()) 163 return false, nil 164 } 165 if ad == nil { 166 m.Debug("Unexpected nil active device from LoadProvisionalActiveDevice without error") 167 return false, nil 168 } 169 err = m.SwitchUserToActiveDevice(e.username, ad) 170 if err != nil { 171 m.Debug("Error switching to new active device: %s", err.Error()) 172 return false, err 173 } 174 return true, nil 175} 176 177// tryPassphraseLogin tries a username/passphrase login to the server, and makes a global 178// side effect: to store the user's full LKSec secret into the secret store. After which point, 179// usual attempts to run LoadProvisionalActiveDevice or BootstrapActiveDevice will succeed 180// without a prompt. 181func (e *LoginProvisionedDevice) tryPassphraseLogin(m libkb.MetaContext) (err error) { 182 defer m.Trace("LoginProvisionedDevice#tryPassphraseLogin", &err)() 183 err = libkb.PassphraseLoginPrompt(m, e.username.String(), 3) 184 if err != nil { 185 return err 186 } 187 188 options := libkb.LoadAdvisorySecretStoreOptionsFromRemote(m) 189 // A failure here is just a warning, since we still can use the app for this 190 // session. But it will undoubtedly cause pain. 191 w := libkb.StoreSecretAfterLoginWithOptions(m, e.username, e.uid, e.deviceID, &options) 192 if w != nil { 193 m.Warning("Secret store failed: %s", w.Error()) 194 } 195 196 return nil 197} 198 199func (e *LoginProvisionedDevice) runBug3964Repairman(m libkb.MetaContext) (err error) { 200 defer m.Trace("LoginProvisionedDevice#runBug3964Repairman", &err)() 201 return libkb.RunBug3964Repairman(m) 202} 203 204func (e *LoginProvisionedDevice) passiveLoginWithUsername(m libkb.MetaContext) (ok bool, uid keybase1.UID) { 205 206 m.Debug("LoginProvisionedDevice#passiveLoginWithUsername %s", e.username) 207 208 cr := m.G().Env.GetConfig() 209 if cr == nil { 210 m.Debug("no config file reader") 211 return false, uid 212 } 213 uid = cr.GetUIDForUsername(e.username) 214 if uid.IsNil() { 215 m.Debug("No UID found locally for username %s", e.username) 216 return false, uid 217 } 218 if isLoggedInAs(m, uid) { 219 return true, uid 220 } 221 return false, keybase1.UID("") 222} 223 224func (e *LoginProvisionedDevice) passiveLogin(m libkb.MetaContext) (ok bool, uid keybase1.UID) { 225 defer m.Trace("LoginProvisionedDevice#passiveLogin", nil)() 226 if len(e.username) > 0 { 227 return e.passiveLoginWithUsername(m) 228 } 229 return isLoggedIn(m) 230} 231 232func (e *LoginProvisionedDevice) run(m libkb.MetaContext) (err error) { 233 defer m.Trace("LoginProvisionedDevice#run", &err)() 234 235 in, loggedInUID := e.passiveLogin(m) 236 237 if in { 238 m.Debug("user %s already logged in; short-circuting", loggedInUID) 239 return nil 240 } 241 242 err = e.loadMe(m) 243 if err != nil { 244 return err 245 } 246 247 var success bool 248 success, err = e.reattemptUnlockIfDifferentUID(m, loggedInUID) 249 if err != nil { 250 return err 251 } 252 if success { 253 return nil 254 } 255 256 if e.SecretStoreOnly { 257 return libkb.NewLoginRequiredError("explicit login is required") 258 } 259 260 e.connectivityWarning(m) 261 262 m = m.WithNewProvisionalLoginContext() 263 err = e.tryPassphraseLogin(m) 264 if err != nil { 265 return err 266 } 267 268 err = e.runBug3964Repairman(m) 269 if err != nil { 270 m.Debug("couldn't run bug 3964 repairman: %+v", err) 271 } 272 273 success, err = e.reattemptUnlock(m) 274 if err != nil { 275 return err 276 } 277 if !success { 278 return libkb.NewLoginRequiredError("login failed after passphrase verified") 279 } 280 281 return nil 282} 283 284func (e *LoginProvisionedDevice) connectivityWarning(m libkb.MetaContext) { 285 // CORE-5876 idea that lksec will be unusable if reachability state is NO 286 // and the user changed passphrase with a different device since it won't 287 // be able to sync the new server half. 288 if m.G().ConnectivityMonitor.IsConnected(m.Ctx()) != libkb.ConnectivityMonitorYes { 289 m.Debug("LoginProvisionedDevice: in unlockDeviceKeys, ConnectivityMonitor says not reachable, check to make sure") 290 if err := m.G().ConnectivityMonitor.CheckReachability(m.Ctx()); err != nil { 291 m.Debug("error checking reachability: %s", err) 292 } else { 293 connected := m.G().ConnectivityMonitor.IsConnected(m.Ctx()) 294 m.Debug("after CheckReachability(), IsConnected() => %v (connected? %v)", connected, connected == libkb.ConnectivityMonitorYes) 295 } 296 } 297} 298 299// Returns the username that the user typed during the engine's execution 300func (e *LoginProvisionedDevice) GetUsername() libkb.NormalizedUsername { 301 return e.username 302} 303