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

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package engine
     5  
     6  import (
     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.
    13  type 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.
    22  func 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.
    30  func (e *LoginProvisionedDevice) Name() string {
    31  	return "LoginProvisionedDevice"
    32  }
    33  
    34  // GetPrereqs returns the engine prereqs.
    35  func (e *LoginProvisionedDevice) Prereqs() Prereqs {
    36  	return Prereqs{}
    37  }
    38  
    39  // RequiredUIs returns the required UIs.
    40  func (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.
    52  func (e *LoginProvisionedDevice) SubConsumers() []libkb.UIConsumer {
    53  	return []libkb.UIConsumer{}
    54  }
    55  
    56  func (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  
    69  func (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  
   145  func (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.
   158  func (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.
   181  func (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  
   199  func (e *LoginProvisionedDevice) runBug3964Repairman(m libkb.MetaContext) (err error) {
   200  	defer m.Trace("LoginProvisionedDevice#runBug3964Repairman", &err)()
   201  	return libkb.RunBug3964Repairman(m)
   202  }
   203  
   204  func (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  
   224  func (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  
   232  func (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  
   284  func (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
   300  func (e *LoginProvisionedDevice) GetUsername() libkb.NormalizedUsername {
   301  	return e.username
   302  }