Skip to content

Commit

Permalink
Fix unknown username bug in login via passphrase
Browse files Browse the repository at this point in the history
In particular, for web users without keys logging in.

keybase/keybase-issues#1855

This also fixes another bug where logging in on a device
with another account would try to get the passphrase for
the original account instead of for the username
specified during login (either via UI or command line).

Added/clarified tests for both situations.  Verified
that they fail without fix, pass with fix.
  • Loading branch information
patrickxb committed Nov 17, 2015
1 parent ccac250 commit 980a5a7
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 7 deletions.
12 changes: 11 additions & 1 deletion go/engine/login_provision.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,15 @@ func (e *LoginProvision) makeDeviceKeysWithSigner(ctx *Context, signer libkb.Gen
// addEldestDeviceKey makes the device keys the eldest keys for
// e.user.
func (e *LoginProvision) addEldestDeviceKey(ctx *Context) error {
e.G().Log.Debug("addEldestDeviceKey: setting username to %q", e.arg.Username)
nu := libkb.NewNormalizedUsername(e.arg.Username)
if err := e.G().Env.GetConfigWriter().SwitchUser(nu); err != nil {
e.G().Log.Debug("SwitchUser error: %s", err)
}
e.G().LoginState().Account(func(a *libkb.Account) {
a.EnsureUsername(nu)
}, "LoginProvision.addEldestDeviceKey")

args, err := e.makeDeviceWrapArgs(ctx)
if err != nil {
return err
Expand Down Expand Up @@ -416,7 +425,7 @@ func (e *LoginProvision) ppStream(ctx *Context) (*libkb.PassphraseStream, error)
}
return cached.PassphraseStream(), nil
}
return e.G().LoginState().GetPassphraseStream(ctx.SecretUI)
return e.G().LoginState().GetPassphraseStreamForUser(ctx.SecretUI, e.arg.Username)
}

// deviceName gets a new device name from the user.
Expand Down Expand Up @@ -452,6 +461,7 @@ func (e *LoginProvision) loadUser(ctx *Context) (*libkb.User, error) {
}
e.arg.Username = username
}
e.G().Log.Debug("LoginProvision: loading user %s", e.arg.Username)
arg := libkb.NewLoadUserByNameArg(e.G(), e.arg.Username)
arg.PublicKeyOptional = true
return libkb.LoadUser(arg)
Expand Down
55 changes: 50 additions & 5 deletions go/engine/login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestLoginAndSwitch(t *testing.T) {
return
}

func TestLoginFakeUserNoKeys(t *testing.T) {
func TestCreateFakeUserNoKeys(t *testing.T) {
tc := SetupEngineTest(t, "login")
defer tc.Cleanup()

Expand Down Expand Up @@ -231,12 +231,15 @@ func TestProvisionPassphraseFail(t *testing.T) {

// If a user has no keys, provision via passphrase should work.
func TestProvisionPassphraseNoKeys(t *testing.T) {
tc := SetupEngineTest(t, "login")
defer tc.Cleanup()
tcWeb := SetupEngineTest(t, "web")
defer tcWeb.Cleanup()

username, passphrase := createFakeUserWithNoKeys(tc)
username, passphrase := createFakeUserWithNoKeys(tcWeb)

Logout(tc)
Logout(tcWeb)

tc := SetupEngineTest(t, "login")
defer tc.Cleanup()

ctx := &Context{
ProvisionUI: newTestProvisionUIPassphrase(),
Expand Down Expand Up @@ -473,6 +476,48 @@ func TestProvisionDupDevice(t *testing.T) {
}
}

// If a user has no keys, provision via passphrase should work.
// This tests when they have another account on the same machine.
func TestProvisionPassphraseNoKeysMultipleAccounts(t *testing.T) {
tcWeb := SetupEngineTest(t, "login")

// create a "web" user with no keys
username, passphrase := createFakeUserWithNoKeys(tcWeb)
Logout(tcWeb)
tcWeb.Cleanup()

// create a new test context
tc := SetupEngineTest(t, "fake")
defer tc.Cleanup()

// create a user to fill up config with something
CreateAndSignupFakeUser(tc, "fake")
Logout(tc)

// now try to log in as the web user
ctx := &Context{
ProvisionUI: newTestProvisionUIPassphrase(),
LoginUI: &libkb.TestLoginUI{Username: username},
LogUI: tc.G.UI.GetLogUI(),
SecretUI: &libkb.TestSecretUI{Passphrase: passphrase},
GPGUI: &gpgtestui{},
}
eng := NewLogin(tc.G, libkb.DeviceTypeDesktop, username)
if err := RunEngine(eng, ctx); err != nil {
t.Fatal(err)
}

// since this user didn't have any keys, login should have fixed that:
testUserHasDeviceKey(tc)

// and they should have a paper backup key
hasOnePaperDev(tc, &FakeUser{Username: username, Passphrase: passphrase})

if err := AssertProvisioned(tc); err != nil {
t.Fatal(err)
}
}

type testProvisionUI struct {
secretCh chan kex2.Secret
method keybase1.ProvisionMethod
Expand Down
16 changes: 15 additions & 1 deletion go/libkb/login_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,14 +193,28 @@ func (s *LoginState) GetPassphraseStream(ui SecretUI) (pps *PassphraseStream, er
s.G().Log.Debug("+ GetPassphraseStream() called")
defer func() { s.G().Log.Debug("- GetPassphraseStream() -> %s", ErrToOk(err)) }()

pps, err = s.GetPassphraseStreamForUser(ui, string(s.G().Env.GetUsername()))
return
}

// GetPassphraseStreamForUser either returns a cached, verified passphrase stream
// (maybe from a previous login) or generates a new one via Login. It will
// return the current Passphrase stream on success or an error on failure.
func (s *LoginState) GetPassphraseStreamForUser(ui SecretUI, username string) (pps *PassphraseStream, err error) {
s.G().Log.Debug("+ GetPassphraseStreamForUser() called")
defer func() { s.G().Log.Debug("- GetPassphraseStreamForUser() -> %s", ErrToOk(err)) }()

pps, err = s.PassphraseStream()
if err != nil {
return nil, err
}
if pps != nil {
return pps, nil
}
if err = s.verifyPassphraseWithServer(ui); err != nil {
err = s.loginHandle(func(lctx LoginContext) error {
return s.loginWithPromptHelper(lctx, username, nil, ui, true)
}, nil, "LoginState - GetPassphraseStreamForUser")
if err != nil {
return nil, err
}
pps, err = s.PassphraseStream()
Expand Down

0 comments on commit 980a5a7

Please sign in to comment.