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  }