github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/systests/passphrase_test.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 systests
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"testing"
    11  	"time"
    12  
    13  	"golang.org/x/net/context"
    14  
    15  	"github.com/keybase/client/go/client"
    16  	"github.com/keybase/client/go/engine"
    17  	"github.com/keybase/client/go/kbtest"
    18  	"github.com/keybase/client/go/libkb"
    19  	"github.com/keybase/client/go/protocol/keybase1"
    20  	"github.com/keybase/client/go/service"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  func TestPassphraseChange(t *testing.T) {
    25  	tc := setupTest(t, "pp")
    26  	defer tc.Cleanup()
    27  
    28  	tc2 := cloneContext(tc)
    29  	defer tc2.Cleanup()
    30  
    31  	stopCh := make(chan error)
    32  	svc := service.NewService(tc.G, false)
    33  	startCh := svc.GetStartChannel()
    34  	go func() {
    35  		err := svc.Run()
    36  		if err != nil {
    37  			t.Logf("Running the service produced an error: %v", err)
    38  		}
    39  		stopCh <- err
    40  	}()
    41  	<-startCh
    42  
    43  	userInfo := randomUser("pp")
    44  
    45  	sui := signupUI{
    46  		info:         userInfo,
    47  		Contextified: libkb.NewContextified(tc2.G),
    48  	}
    49  	tc2.G.SetUI(&sui)
    50  	signup := client.NewCmdSignupRunner(tc2.G)
    51  	signup.SetTest()
    52  
    53  	if err := signup.Run(); err != nil {
    54  		t.Fatal(err)
    55  	}
    56  
    57  	m := libkb.NewMetaContextForTest(*tc)
    58  	_, err := libkb.VerifyPassphraseForLoggedInUser(m, userInfo.passphrase)
    59  	require.NoError(t, err, "verified passphrase")
    60  
    61  	oldPassphrase := userInfo.passphrase
    62  	newPassphrase := userInfo.passphrase + userInfo.passphrase
    63  	sui.info.passphrase = newPassphrase
    64  	change := client.NewCmdPassphraseChangeRunner(tc2.G)
    65  
    66  	if err := change.Run(); err != nil {
    67  		t.Fatal(err)
    68  	}
    69  
    70  	_, err = libkb.VerifyPassphraseForLoggedInUser(m, newPassphrase)
    71  	require.NoError(t, err, "verified passphrase")
    72  	_, err = libkb.VerifyPassphraseForLoggedInUser(m, oldPassphrase)
    73  	require.Error(t, err, "old passphrase failed to verify")
    74  
    75  	if err := CtlStop(tc2.G); err != nil {
    76  		t.Fatal(err)
    77  	}
    78  
    79  	// If the server failed, it's also an error
    80  	if err := <-stopCh; err != nil {
    81  		t.Fatal(err)
    82  	}
    83  }
    84  
    85  type serviceHandle struct {
    86  	// Emits nil/err when stopped
    87  	stopCh <-chan error
    88  	svc    *service.Service
    89  }
    90  
    91  func startNewService(tc *libkb.TestContext) (*serviceHandle, error) {
    92  	stopCh := make(chan error)
    93  	svc := service.NewService(tc.G, false)
    94  	startCh := svc.GetStartChannel()
    95  	go func() {
    96  		err := svc.Run()
    97  		if err != nil {
    98  			tc.T.Logf("Running the service produced an error: %v", err)
    99  		}
   100  		stopCh <- err
   101  	}()
   102  
   103  	// Wait for the service to start
   104  	<-startCh
   105  
   106  	return &serviceHandle{
   107  		stopCh: stopCh,
   108  		svc:    svc,
   109  	}, nil
   110  }
   111  
   112  // Tests recovering a passphrase on a second machine by logging in with paperkey.
   113  func TestPassphraseRecover(t *testing.T) {
   114  	testPassphraseRecover(t, false /* createDeviceClone */)
   115  }
   116  
   117  func TestPassphraseRecoverWithDeviceClone(t *testing.T) {
   118  	testPassphraseRecover(t, true /* createDeviceClone */)
   119  }
   120  
   121  func testPassphraseRecover(t *testing.T, createDeviceClone bool) {
   122  	t.Logf("Start")
   123  
   124  	// Service contexts.
   125  	// Make a new context with cloneContext for each client session.
   126  	tc1 := setupTest(t, "ppa")
   127  	defer tc1.Cleanup()
   128  	tc2 := setupTest(t, "ppb")
   129  	defer tc2.Cleanup()
   130  	var tcClient *libkb.TestContext
   131  
   132  	t.Logf("Starting services")
   133  	s1, err := startNewService(tc1)
   134  	require.NoError(t, err)
   135  	s2, err := startNewService(tc2)
   136  	require.NoError(t, err)
   137  
   138  	userInfo := randomUser("pp")
   139  
   140  	t.Logf("Signup on tc1")
   141  	tcClient = cloneContext(tc1)
   142  	defer tcClient.Cleanup()
   143  
   144  	aSignupUI := signupUI{
   145  		info:         userInfo,
   146  		Contextified: libkb.NewContextified(tc1.G),
   147  	}
   148  	tcClient.G.SetUI(&aSignupUI)
   149  	signup := client.NewCmdSignupRunner(tcClient.G)
   150  	signup.SetTest()
   151  	err = signup.Run()
   152  	require.NoError(t, err)
   153  	tcClient = nil
   154  
   155  	// the paper key displayed during signup is in userInfo now
   156  	tc2.G.Log.Debug("signup paper key: %s", userInfo.displayedPaperKey)
   157  
   158  	// clone the device on tc1
   159  	m1 := libkb.NewMetaContextForTest(*tc1)
   160  	if createDeviceClone {
   161  		libkb.CreateClonedDevice(*tc1, m1)
   162  	}
   163  
   164  	t.Logf("Login on tc2")
   165  	tcClient = cloneContext(tc2)
   166  	defer tcClient.Cleanup()
   167  
   168  	aProvisionUI := &testRecoverUIProvision{
   169  		username:   userInfo.username,
   170  		paperkey:   userInfo.displayedPaperKey,
   171  		deviceName: "away thing",
   172  	}
   173  	aUI := genericUI{
   174  		g:           tcClient.G,
   175  		LoginUI:     aProvisionUI,
   176  		ProvisionUI: aProvisionUI,
   177  		SecretUI:    aProvisionUI,
   178  	}
   179  	tcClient.G.SetUI(&aUI)
   180  	login := client.NewCmdLoginRunner(tcClient.G)
   181  	err = login.Run()
   182  	require.NoError(t, err)
   183  	tcClient = nil
   184  
   185  	t.Logf("Verify on tc1")
   186  	_, err = libkb.VerifyPassphraseForLoggedInUser(m1, userInfo.passphrase)
   187  	require.NoError(t, err)
   188  
   189  	oldPassphrase := userInfo.passphrase
   190  	newPassphrase := userInfo.passphrase + userInfo.passphrase
   191  	t.Logf("Passphrase %q -> %q", oldPassphrase, newPassphrase)
   192  
   193  	t.Logf("Recover on tc2")
   194  	tcClient = cloneContext(tc2)
   195  	defer tcClient.Cleanup()
   196  
   197  	aRecoverUI := &testRecoverUIRecover{
   198  		Contextified: libkb.NewContextified(tc2.G),
   199  		passphrase:   newPassphrase,
   200  	}
   201  	aUI = genericUI{
   202  		g:           tc2.G,
   203  		TerminalUI:  aRecoverUI,
   204  		SecretUI:    aRecoverUI,
   205  		ProvisionUI: aRecoverUI,
   206  		LoginUI:     aRecoverUI,
   207  	}
   208  	tcClient.G.SetUI(&aUI)
   209  	changeCmd := client.NewCmdPassphraseChangeRunner(tcClient.G)
   210  	changeCmd.ForceArg = true
   211  	err = changeCmd.Run()
   212  	require.NoError(t, err)
   213  	tcClient = nil
   214  
   215  	t.Logf("Verify new passphrase on tc2")
   216  	m2 := libkb.NewMetaContextForTest(*tc2)
   217  	_, err = libkb.VerifyPassphraseForLoggedInUser(m2, newPassphrase)
   218  	require.NoError(t, err)
   219  
   220  	t.Logf("Verify new passphrase on tc1")
   221  	_, err = libkb.VerifyPassphraseForLoggedInUser(m1, newPassphrase)
   222  	require.NoError(t, err)
   223  
   224  	t.Logf("Verify old passphrase on tc1")
   225  	_, err = libkb.VerifyPassphraseForLoggedInUser(m1, oldPassphrase)
   226  	require.Error(t, err, "old passphrase passed verification after passphrase change")
   227  
   228  	t.Logf("Stop tc1")
   229  	err = CtlStop(tc1.G)
   230  	require.NoError(t, err)
   231  
   232  	t.Logf("Stop tc2")
   233  	err = CtlStop(tc2.G)
   234  	require.NoError(t, err)
   235  
   236  	t.Logf("Waiting for services to stop")
   237  	// If a service failed, that's an error
   238  	require.NoError(t, <-s1.stopCh)
   239  	require.NoError(t, <-s2.stopCh)
   240  }
   241  
   242  type testRecoverUIProvision struct {
   243  	baseNullUI
   244  	username   string
   245  	deviceName string
   246  	paperkey   string
   247  }
   248  
   249  var _ libkb.LoginUI = (*testRecoverUIProvision)(nil)
   250  
   251  func (r *testRecoverUIProvision) GetEmailOrUsername(context.Context, int) (string, error) {
   252  	return r.username, nil
   253  }
   254  func (r *testRecoverUIProvision) PromptRevokePaperKeys(context.Context, keybase1.PromptRevokePaperKeysArg) (ret bool, err error) {
   255  	return false, nil
   256  }
   257  func (r *testRecoverUIProvision) DisplayPaperKeyPhrase(context.Context, keybase1.DisplayPaperKeyPhraseArg) error {
   258  	return nil
   259  }
   260  func (r *testRecoverUIProvision) DisplayPrimaryPaperKey(context.Context, keybase1.DisplayPrimaryPaperKeyArg) error {
   261  	return nil
   262  }
   263  func (r *testRecoverUIProvision) ChooseProvisioningMethod(context.Context, keybase1.ChooseProvisioningMethodArg) (ret keybase1.ProvisionMethod, err error) {
   264  	return keybase1.ProvisionMethod_PASSPHRASE, nil
   265  }
   266  func (r *testRecoverUIProvision) ChooseGPGMethod(context.Context, keybase1.ChooseGPGMethodArg) (ret keybase1.GPGMethod, err error) {
   267  	return ret, nil
   268  }
   269  func (r *testRecoverUIProvision) SwitchToGPGSignOK(context.Context, keybase1.SwitchToGPGSignOKArg) (ret bool, err error) {
   270  	return ret, nil
   271  }
   272  func (r *testRecoverUIProvision) ChooseDeviceType(context.Context, keybase1.ChooseDeviceTypeArg) (ret keybase1.DeviceType, err error) {
   273  	return ret, nil
   274  }
   275  func (r *testRecoverUIProvision) DisplayAndPromptSecret(context.Context, keybase1.DisplayAndPromptSecretArg) (ret keybase1.SecretResponse, err error) {
   276  	return ret, nil
   277  }
   278  func (r *testRecoverUIProvision) DisplaySecretExchanged(context.Context, int) error {
   279  	return nil
   280  }
   281  func (r *testRecoverUIProvision) PromptNewDeviceName(context.Context, keybase1.PromptNewDeviceNameArg) (ret string, err error) {
   282  	return r.deviceName, nil
   283  }
   284  func (r *testRecoverUIProvision) ProvisioneeSuccess(context.Context, keybase1.ProvisioneeSuccessArg) error {
   285  	return nil
   286  }
   287  func (r *testRecoverUIProvision) ProvisionerSuccess(context.Context, keybase1.ProvisionerSuccessArg) error {
   288  	return nil
   289  }
   290  func (r *testRecoverUIProvision) ChooseDevice(ctx context.Context, arg keybase1.ChooseDeviceArg) (ret keybase1.DeviceID, err error) {
   291  	for _, d := range arg.Devices {
   292  		if d.Type == keybase1.DeviceTypeV2_PAPER {
   293  			return d.DeviceID, nil
   294  		}
   295  	}
   296  	return "", nil
   297  }
   298  func (r *testRecoverUIProvision) GetPassphrase(p keybase1.GUIEntryArg, terminal *keybase1.SecretEntryArg) (res keybase1.GetPassphraseRes, err error) {
   299  	res.Passphrase = r.paperkey
   300  	return res, nil
   301  }
   302  func (r *testRecoverUIProvision) PromptResetAccount(_ context.Context, arg keybase1.PromptResetAccountArg) (keybase1.ResetPromptResponse, error) {
   303  	return keybase1.ResetPromptResponse_NOTHING, nil
   304  }
   305  func (r *testRecoverUIProvision) DisplayResetProgress(_ context.Context, arg keybase1.DisplayResetProgressArg) error {
   306  	return nil
   307  }
   308  func (r *testRecoverUIProvision) PromptPassphraseRecovery(_ context.Context, arg keybase1.PromptPassphraseRecoveryArg) (bool, error) {
   309  	return false, nil
   310  }
   311  func (r *testRecoverUIProvision) ExplainDeviceRecovery(_ context.Context, arg keybase1.ExplainDeviceRecoveryArg) error {
   312  	return nil
   313  }
   314  func (r *testRecoverUIProvision) ChooseDeviceToRecoverWith(_ context.Context, arg keybase1.ChooseDeviceToRecoverWithArg) (keybase1.DeviceID, error) {
   315  	return "", nil
   316  }
   317  func (r *testRecoverUIProvision) DisplayResetMessage(_ context.Context, arg keybase1.DisplayResetMessageArg) error {
   318  	return nil
   319  }
   320  
   321  type testRecoverUIRecover struct {
   322  	libkb.Contextified
   323  	kbtest.TestProvisionUI
   324  	libkb.TestLoginUI
   325  	passphrase string
   326  }
   327  
   328  func (n *testRecoverUIRecover) Prompt(pd libkb.PromptDescriptor, s string) (ret string, err error) {
   329  	n.G().Log.Debug("Terminal Prompt %d: %s -> %s (%v)\n", pd, s, ret, libkb.ErrToOk(err))
   330  	return ret, fmt.Errorf("unexpected prompt")
   331  }
   332  func (n *testRecoverUIRecover) PromptPassword(pd libkb.PromptDescriptor, _ string) (string, error) {
   333  	return "", fmt.Errorf("unexpected prompt password")
   334  }
   335  func (n *testRecoverUIRecover) PromptPasswordMaybeScripted(pd libkb.PromptDescriptor, _ string) (string, error) {
   336  	return "", fmt.Errorf("unexpected prompt password")
   337  }
   338  func (n *testRecoverUIRecover) Output(s string) error {
   339  	n.G().Log.Debug("Terminal Output: %s", s)
   340  	return nil
   341  }
   342  func (n *testRecoverUIRecover) OutputDesc(od libkb.OutputDescriptor, s string) error {
   343  	n.G().Log.Debug("Terminal Output %d: %s", od, s)
   344  	return nil
   345  }
   346  func (n *testRecoverUIRecover) Printf(f string, args ...interface{}) (int, error) {
   347  	s := fmt.Sprintf(f, args...)
   348  	n.G().Log.Debug("Terminal Printf: %s", s)
   349  	return len(s), nil
   350  }
   351  func (n *testRecoverUIRecover) PrintfUnescaped(f string, args ...interface{}) (int, error) {
   352  	s := fmt.Sprintf(f, args...)
   353  	n.G().Log.Debug("Terminal PrintfUnescaped: %s", s)
   354  	return len(s), nil
   355  }
   356  func (n *testRecoverUIRecover) Write(b []byte) (int, error) {
   357  	n.G().Log.Debug("Terminal write: %s", string(b))
   358  	return len(b), nil
   359  }
   360  func (n *testRecoverUIRecover) OutputWriter() io.Writer {
   361  	return n
   362  }
   363  func (n *testRecoverUIRecover) UnescapedOutputWriter() io.Writer {
   364  	return n
   365  }
   366  func (n *testRecoverUIRecover) ErrorWriter() io.Writer {
   367  	return n
   368  }
   369  func (n *testRecoverUIRecover) PromptYesNo(pd libkb.PromptDescriptor, s string, def libkb.PromptDefault) (ret bool, err error) {
   370  	n.G().Log.Debug("Terminal PromptYesNo %d: %s -> %s (%v)\n", pd, s, ret, libkb.ErrToOk(err))
   371  	return ret, fmt.Errorf("unexpected prompt yes/no")
   372  }
   373  func (n *testRecoverUIRecover) PromptForConfirmation(prompt string) error {
   374  	return nil
   375  }
   376  func (n *testRecoverUIRecover) Tablify(headings []string, rowfunc func() []string) {
   377  	libkb.Tablify(n.OutputWriter(), headings, rowfunc)
   378  }
   379  func (n *testRecoverUIRecover) TerminalSize() (width int, height int) {
   380  	return 80, 24
   381  }
   382  func (n *testRecoverUIRecover) GetPassphrase(p keybase1.GUIEntryArg, terminal *keybase1.SecretEntryArg) (res keybase1.GetPassphraseRes, err error) {
   383  	res.Passphrase = n.passphrase
   384  	return res, nil
   385  }
   386  
   387  type errorAPIMock struct {
   388  	*libkb.APIArgRecorder
   389  	realAPI     libkb.API
   390  	shouldError bool
   391  }
   392  
   393  func (r *errorAPIMock) GetDecode(mctx libkb.MetaContext, arg libkb.APIArg, w libkb.APIResponseWrapper) error {
   394  	if arg.Endpoint == "user/has_random_pw" {
   395  		if r.shouldError {
   396  			return errors.New("some api error")
   397  		}
   398  	}
   399  	return r.realAPI.GetDecode(mctx, arg, w)
   400  }
   401  
   402  func (r errorAPIMock) Get(mctx libkb.MetaContext, arg libkb.APIArg) (*libkb.APIRes, error) {
   403  	if arg.Endpoint == "user/has_random_pw" {
   404  		if r.shouldError {
   405  			return nil, errors.New("some api error")
   406  		}
   407  	}
   408  	return r.realAPI.Get(mctx, arg)
   409  }
   410  
   411  func TestPassphraseStateGregor(t *testing.T) {
   412  	set := newTestDeviceSet(t, nil)
   413  	defer set.cleanup()
   414  	dev1 := set.newDevice("primary").start(4)
   415  	set.signupUserWithRandomPassphrase(dev1, true)
   416  	dev2 := set.provisionNewDevice("secondary", 4)
   417  	dev3 := set.provisionNewStandaloneDevice("ternary", 4)
   418  	dev4 := set.provisionNewStandaloneDevice("quaternary", 4)
   419  
   420  	ucli1 := keybase1.UserClient{Cli: dev1.cli}
   421  	res, err := ucli1.LoadPassphraseState(context.Background(), 0)
   422  	require.NoError(t, err)
   423  	require.Equal(t, keybase1.PassphraseState_RANDOM, res)
   424  
   425  	ucli2 := keybase1.UserClient{Cli: dev2.cli}
   426  	res, err = ucli2.LoadPassphraseState(context.Background(), 0)
   427  	require.NoError(t, err)
   428  	require.Equal(t, keybase1.PassphraseState_RANDOM, res)
   429  
   430  	ucli3 := keybase1.UserClient{Cli: dev3.cli}
   431  	res, err = ucli3.LoadPassphraseState(context.Background(), 0)
   432  	require.NoError(t, err)
   433  	require.Equal(t, keybase1.PassphraseState_RANDOM, res)
   434  
   435  	mctx1 := libkb.NewMetaContextForTest(*dev1.tctx)
   436  	eng := engine.NewPassphraseChange(dev1.tctx.G, &keybase1.PassphraseChangeArg{
   437  		Passphrase: "password2",
   438  		Force:      true,
   439  	})
   440  	err = eng.Run(mctx1)
   441  	require.NoError(t, err)
   442  
   443  	// The device that made the change learns about the state
   444  	pollForTrue(t, dev1.tctx.G, func(int) bool {
   445  		res, err = ucli1.LoadPassphraseState(context.Background(), 0)
   446  		if err != nil {
   447  			return false
   448  		}
   449  		return keybase1.PassphraseState_KNOWN == res
   450  	})
   451  
   452  	// Devices that did not execute the passphrase change learns about the state
   453  	pollForTrue(t, dev2.tctx.G, func(int) bool {
   454  		res, err = ucli2.LoadPassphraseState(context.Background(), 0)
   455  		if err != nil {
   456  			return false
   457  		}
   458  		return keybase1.PassphraseState_KNOWN == res
   459  	})
   460  
   461  	time.Sleep(1 * time.Second) // wait for any potential gregor messages to be received
   462  
   463  	res, err = ucli3.LoadPassphraseState(context.Background(), 0)
   464  	require.NoError(t, err)
   465  	// device not getting gregor messages will force repoll
   466  	require.Equal(t, res, keybase1.PassphraseState_KNOWN)
   467  
   468  	ucli4 := keybase1.UserClient{Cli: dev4.cli}
   469  	fakeAPI := &errorAPIMock{
   470  		realAPI:     dev4.tctx.G.API,
   471  		shouldError: true,
   472  	}
   473  	dev4.tctx.G.API = fakeAPI
   474  	res, err = ucli4.LoadPassphraseState(context.Background(), 0)
   475  	// device has no gregor state *and* api call failed, so this will error
   476  	require.Error(t, err)
   477  	require.Contains(t, err.Error(), "some api error")
   478  }