1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package systests
5
6import (
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
24func 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
85type serviceHandle struct {
86	// Emits nil/err when stopped
87	stopCh <-chan error
88	svc    *service.Service
89}
90
91func 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.
113func TestPassphraseRecover(t *testing.T) {
114	testPassphraseRecover(t, false /* createDeviceClone */)
115}
116
117func TestPassphraseRecoverWithDeviceClone(t *testing.T) {
118	testPassphraseRecover(t, true /* createDeviceClone */)
119}
120
121func 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
242type testRecoverUIProvision struct {
243	baseNullUI
244	username   string
245	deviceName string
246	paperkey   string
247}
248
249var _ libkb.LoginUI = (*testRecoverUIProvision)(nil)
250
251func (r *testRecoverUIProvision) GetEmailOrUsername(context.Context, int) (string, error) {
252	return r.username, nil
253}
254func (r *testRecoverUIProvision) PromptRevokePaperKeys(context.Context, keybase1.PromptRevokePaperKeysArg) (ret bool, err error) {
255	return false, nil
256}
257func (r *testRecoverUIProvision) DisplayPaperKeyPhrase(context.Context, keybase1.DisplayPaperKeyPhraseArg) error {
258	return nil
259}
260func (r *testRecoverUIProvision) DisplayPrimaryPaperKey(context.Context, keybase1.DisplayPrimaryPaperKeyArg) error {
261	return nil
262}
263func (r *testRecoverUIProvision) ChooseProvisioningMethod(context.Context, keybase1.ChooseProvisioningMethodArg) (ret keybase1.ProvisionMethod, err error) {
264	return keybase1.ProvisionMethod_PASSPHRASE, nil
265}
266func (r *testRecoverUIProvision) ChooseGPGMethod(context.Context, keybase1.ChooseGPGMethodArg) (ret keybase1.GPGMethod, err error) {
267	return ret, nil
268}
269func (r *testRecoverUIProvision) SwitchToGPGSignOK(context.Context, keybase1.SwitchToGPGSignOKArg) (ret bool, err error) {
270	return ret, nil
271}
272func (r *testRecoverUIProvision) ChooseDeviceType(context.Context, keybase1.ChooseDeviceTypeArg) (ret keybase1.DeviceType, err error) {
273	return ret, nil
274}
275func (r *testRecoverUIProvision) DisplayAndPromptSecret(context.Context, keybase1.DisplayAndPromptSecretArg) (ret keybase1.SecretResponse, err error) {
276	return ret, nil
277}
278func (r *testRecoverUIProvision) DisplaySecretExchanged(context.Context, int) error {
279	return nil
280}
281func (r *testRecoverUIProvision) PromptNewDeviceName(context.Context, keybase1.PromptNewDeviceNameArg) (ret string, err error) {
282	return r.deviceName, nil
283}
284func (r *testRecoverUIProvision) ProvisioneeSuccess(context.Context, keybase1.ProvisioneeSuccessArg) error {
285	return nil
286}
287func (r *testRecoverUIProvision) ProvisionerSuccess(context.Context, keybase1.ProvisionerSuccessArg) error {
288	return nil
289}
290func (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}
298func (r *testRecoverUIProvision) GetPassphrase(p keybase1.GUIEntryArg, terminal *keybase1.SecretEntryArg) (res keybase1.GetPassphraseRes, err error) {
299	res.Passphrase = r.paperkey
300	return res, nil
301}
302func (r *testRecoverUIProvision) PromptResetAccount(_ context.Context, arg keybase1.PromptResetAccountArg) (keybase1.ResetPromptResponse, error) {
303	return keybase1.ResetPromptResponse_NOTHING, nil
304}
305func (r *testRecoverUIProvision) DisplayResetProgress(_ context.Context, arg keybase1.DisplayResetProgressArg) error {
306	return nil
307}
308func (r *testRecoverUIProvision) PromptPassphraseRecovery(_ context.Context, arg keybase1.PromptPassphraseRecoveryArg) (bool, error) {
309	return false, nil
310}
311func (r *testRecoverUIProvision) ExplainDeviceRecovery(_ context.Context, arg keybase1.ExplainDeviceRecoveryArg) error {
312	return nil
313}
314func (r *testRecoverUIProvision) ChooseDeviceToRecoverWith(_ context.Context, arg keybase1.ChooseDeviceToRecoverWithArg) (keybase1.DeviceID, error) {
315	return "", nil
316}
317func (r *testRecoverUIProvision) DisplayResetMessage(_ context.Context, arg keybase1.DisplayResetMessageArg) error {
318	return nil
319}
320
321type testRecoverUIRecover struct {
322	libkb.Contextified
323	kbtest.TestProvisionUI
324	libkb.TestLoginUI
325	passphrase string
326}
327
328func (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}
332func (n *testRecoverUIRecover) PromptPassword(pd libkb.PromptDescriptor, _ string) (string, error) {
333	return "", fmt.Errorf("unexpected prompt password")
334}
335func (n *testRecoverUIRecover) PromptPasswordMaybeScripted(pd libkb.PromptDescriptor, _ string) (string, error) {
336	return "", fmt.Errorf("unexpected prompt password")
337}
338func (n *testRecoverUIRecover) Output(s string) error {
339	n.G().Log.Debug("Terminal Output: %s", s)
340	return nil
341}
342func (n *testRecoverUIRecover) OutputDesc(od libkb.OutputDescriptor, s string) error {
343	n.G().Log.Debug("Terminal Output %d: %s", od, s)
344	return nil
345}
346func (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}
351func (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}
356func (n *testRecoverUIRecover) Write(b []byte) (int, error) {
357	n.G().Log.Debug("Terminal write: %s", string(b))
358	return len(b), nil
359}
360func (n *testRecoverUIRecover) OutputWriter() io.Writer {
361	return n
362}
363func (n *testRecoverUIRecover) UnescapedOutputWriter() io.Writer {
364	return n
365}
366func (n *testRecoverUIRecover) ErrorWriter() io.Writer {
367	return n
368}
369func (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}
373func (n *testRecoverUIRecover) PromptForConfirmation(prompt string) error {
374	return nil
375}
376func (n *testRecoverUIRecover) Tablify(headings []string, rowfunc func() []string) {
377	libkb.Tablify(n.OutputWriter(), headings, rowfunc)
378}
379func (n *testRecoverUIRecover) TerminalSize() (width int, height int) {
380	return 80, 24
381}
382func (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
387type errorAPIMock struct {
388	*libkb.APIArgRecorder
389	realAPI     libkb.API
390	shouldError bool
391}
392
393func (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
402func (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
411func 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}
479