1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package engine
5
6import (
7	"crypto/rand"
8	"encoding/hex"
9	"errors"
10	"fmt"
11	"testing"
12
13	"github.com/keybase/client/go/externalstest"
14	"github.com/keybase/client/go/libkb"
15	"github.com/keybase/client/go/protocol/keybase1"
16	insecureTriplesec "github.com/keybase/go-triplesec-insecure"
17	"github.com/stretchr/testify/require"
18)
19
20func SetupEngineTest(tb libkb.TestingTB, name string) libkb.TestContext {
21	tc := externalstest.SetupTest(tb, name, 2)
22
23	// use an insecure triplesec in tests
24	tc.G.NewTriplesec = func(passphrase []byte, salt []byte) (libkb.Triplesec, error) {
25		warner := func() { tc.G.Log.Warning("Installing insecure Triplesec with weak stretch parameters") }
26		isProduction := func() bool {
27			return tc.G.Env.GetRunMode() == libkb.ProductionRunMode
28		}
29		return insecureTriplesec.NewCipher(passphrase, salt, libkb.ClientTriplesecVersion, warner, isProduction)
30	}
31
32	return tc
33}
34
35func SetupEngineTestRealTriplesec(tb libkb.TestingTB, name string) libkb.TestContext {
36	tc := externalstest.SetupTest(tb, name, 2)
37	tc.G.NewTriplesec = libkb.NewSecureTriplesec
38	return tc
39}
40
41type FakeUser struct {
42	Username      string
43	Email         string
44	Passphrase    string
45	User          *libkb.User
46	EncryptionKey libkb.GenericKey
47	DeviceName    string
48}
49
50func NewFakeUser(prefix string) (fu *FakeUser, err error) {
51	buf := make([]byte, 5)
52	if _, err = rand.Read(buf); err != nil {
53		return
54	}
55	username := fmt.Sprintf("%s_%s", prefix, hex.EncodeToString(buf))
56	email := fmt.Sprintf("%s@noemail.keybase.io", username)
57	buf = make([]byte, 12)
58	if _, err = rand.Read(buf); err != nil {
59		return
60	}
61	passphrase := hex.EncodeToString(buf)
62	fu = &FakeUser{Username: username, Email: email, Passphrase: passphrase}
63	return
64}
65
66func (fu FakeUser) NormalizedUsername() libkb.NormalizedUsername {
67	return libkb.NewNormalizedUsername(fu.Username)
68}
69
70func (fu *FakeUser) LoadUser(tc libkb.TestContext) error {
71	var err error
72	fu.User, err = libkb.LoadMe(libkb.NewLoadUserArg(tc.G))
73	return err
74}
75
76func (fu FakeUser) UID() keybase1.UID {
77	// All new-style names will have a 1-to-1 mapping
78	return libkb.UsernameToUID(fu.Username)
79}
80
81func (fu FakeUser) UserVersion() keybase1.UserVersion {
82	return keybase1.UserVersion{Uid: fu.UID(), EldestSeqno: 1}
83}
84
85func NewFakeUserOrBust(tb libkb.TestingTB, prefix string) (fu *FakeUser) {
86	var err error
87	if fu, err = NewFakeUser(prefix); err != nil {
88		tb.Fatal(err)
89	}
90	return fu
91}
92
93const defaultDeviceName = "my device"
94
95// MakeTestSignupEngineRunArg fills a SignupEngineRunArg with the most
96// common parameters for testing and returns it.
97func MakeTestSignupEngineRunArg(fu *FakeUser) SignupEngineRunArg {
98	return SignupEngineRunArg{
99		Username:    fu.Username,
100		Email:       fu.Email,
101		InviteCode:  libkb.TestInvitationCode,
102		Passphrase:  fu.Passphrase,
103		StoreSecret: false,
104		DeviceName:  defaultDeviceName,
105		SkipGPG:     true,
106		SkipMail:    true,
107		SkipPaper:   true,
108	}
109}
110
111func SignupFakeUserWithArg(tc libkb.TestContext, fu *FakeUser, arg SignupEngineRunArg) *SignupEngine {
112	uis := libkb.UIs{
113		LogUI:    tc.G.UI.GetLogUI(),
114		GPGUI:    &gpgtestui{},
115		SecretUI: fu.NewSecretUI(),
116		LoginUI:  &libkb.TestLoginUI{Username: fu.Username},
117	}
118	s := NewSignupEngine(tc.G, &arg)
119	m := NewMetaContextForTest(tc).WithUIs(uis)
120	err := RunEngine2(m, s)
121	require.NoError(tc.T, err)
122	fu.EncryptionKey = s.encryptionKey
123	return s
124}
125
126func CreateAndSignupFakeUser(tc libkb.TestContext, prefix string) *FakeUser {
127	fu, _ := CreateAndSignupFakeUser2(tc, prefix)
128	return fu
129}
130
131func CreateAndSignupFakeUser2(tc libkb.TestContext, prefix string) (*FakeUser, *SignupEngine) {
132	fu := NewFakeUserOrBust(tc.T, prefix)
133	tc.G.Log.Debug("New test user: %s / %s", fu.Username, fu.Email)
134	arg := MakeTestSignupEngineRunArg(fu)
135	fu.DeviceName = arg.DeviceName
136	eng := SignupFakeUserWithArg(tc, fu, arg)
137	return fu, eng
138}
139
140func CreateAndSignupFakeUserPaper(tc libkb.TestContext, prefix string) *FakeUser {
141	fu := NewFakeUserOrBust(tc.T, prefix)
142	tc.G.Log.Debug("New test user: %s / %s", fu.Username, fu.Email)
143	arg := MakeTestSignupEngineRunArg(fu)
144	arg.SkipPaper = false
145	_ = SignupFakeUserWithArg(tc, fu, arg)
146	return fu
147}
148
149func CreateAndSignupFakeUserSafeWithArg(g *libkb.GlobalContext, fu *FakeUser, arg SignupEngineRunArg) (*FakeUser, error) {
150	uis := libkb.UIs{
151		LogUI:    g.UI.GetLogUI(),
152		GPGUI:    &gpgtestui{},
153		SecretUI: fu.NewSecretUI(),
154		LoginUI:  &libkb.TestLoginUI{Username: fu.Username},
155	}
156	s := NewSignupEngine(g, &arg)
157	err := RunEngine2(libkb.NewMetaContextTODO(g).WithUIs(uis), s)
158	if err != nil {
159		return nil, err
160	}
161	return fu, nil
162}
163
164func CreateAndSignupFakeUserSafe(g *libkb.GlobalContext, prefix string) (*FakeUser, error) {
165	fu, err := NewFakeUser(prefix)
166	if err != nil {
167		return nil, err
168	}
169	arg := MakeTestSignupEngineRunArg(fu)
170
171	return CreateAndSignupFakeUserSafeWithArg(g, fu, arg)
172}
173
174func CreateAndSignupFakeUserGPG(tc libkb.TestContext, prefix string) *FakeUser {
175	fu := NewFakeUserOrBust(tc.T, prefix)
176	if err := tc.GenerateGPGKeyring(fu.Email); err != nil {
177		tc.T.Fatal(err)
178	}
179	arg := MakeTestSignupEngineRunArg(fu)
180	arg.SkipGPG = false
181	uis := libkb.UIs{
182		LogUI:    tc.G.UI.GetLogUI(),
183		GPGUI:    &gpgtestui{},
184		SecretUI: fu.NewSecretUI(),
185		LoginUI:  &libkb.TestLoginUI{Username: fu.Username},
186	}
187	s := NewSignupEngine(tc.G, &arg)
188	err := RunEngine2(NewMetaContextForTest(tc).WithUIs(uis), s)
189	if err != nil {
190		tc.T.Fatal(err)
191	}
192	return fu
193}
194
195func SignupFakeUserStoreSecret(tc libkb.TestContext, prefix string) *FakeUser {
196	fu := NewFakeUserOrBust(tc.T, prefix)
197	tc.G.Log.Debug("New test user: %s / %s", fu.Username, fu.Email)
198	arg := MakeTestSignupEngineRunArg(fu)
199	arg.SkipPaper = false
200	arg.StoreSecret = true
201	_ = SignupFakeUserWithArg(tc, fu, arg)
202	return fu
203}
204
205func CreateAndSignupFakeUserCustomArg(tc libkb.TestContext, prefix string, fmod func(*SignupEngineRunArg)) (fu *FakeUser, signingKey libkb.GenericKey, encryptionKey libkb.NaclDHKeyPair) {
206	fu = NewFakeUserOrBust(tc.T, prefix)
207	arg := MakeTestSignupEngineRunArg(fu)
208	fmod(&arg)
209	uis := libkb.UIs{
210		LogUI:    tc.G.UI.GetLogUI(),
211		GPGUI:    &gpgtestui{},
212		SecretUI: fu.NewSecretUI(),
213		LoginUI:  &libkb.TestLoginUI{Username: fu.Username},
214	}
215	s := NewSignupEngine(tc.G, &arg)
216	err := RunEngine2(NewMetaContextForTest(tc).WithUIs(uis), s)
217	if err != nil {
218		tc.T.Fatal(err)
219	}
220	return fu, s.signingKey, s.encryptionKey
221}
222
223func CreateAndSignupFakeUserWithPassphrase(tc libkb.TestContext, prefix, passphrase string) *FakeUser {
224	fu := NewFakeUserOrBust(tc.T, prefix)
225	fu.Passphrase = passphrase
226	tc.G.Log.Debug("New test user: %s / %s", fu.Username, fu.Email)
227	arg := MakeTestSignupEngineRunArg(fu)
228	SignupFakeUserWithArg(tc, fu, arg)
229	return fu
230}
231
232func (fu *FakeUser) LoginWithSecretUI(secui libkb.SecretUI, g *libkb.GlobalContext) error {
233	uis := libkb.UIs{
234		ProvisionUI: newTestProvisionUI(),
235		LogUI:       g.UI.GetLogUI(),
236		GPGUI:       &gpgtestui{},
237		SecretUI:    secui,
238		LoginUI:     &libkb.TestLoginUI{Username: fu.Username},
239	}
240	m := libkb.NewMetaContextTODO(g).WithUIs(uis)
241	li := NewLogin(g, keybase1.DeviceTypeV2_DESKTOP, fu.Username, keybase1.ClientType_CLI)
242	return RunEngine2(m, li)
243}
244
245func (fu *FakeUser) Login(g *libkb.GlobalContext) error {
246	s := fu.NewSecretUI()
247	return fu.LoginWithSecretUI(s, g)
248}
249
250type nullSecretUI struct{}
251
252func (n nullSecretUI) GetPassphrase(pinentry keybase1.GUIEntryArg, terminal *keybase1.SecretEntryArg) (keybase1.GetPassphraseRes, error) {
253	return keybase1.GetPassphraseRes{}, errors.New("nullSecretUI should never be called")
254}
255
256func (fu *FakeUser) SwitchTo(g *libkb.GlobalContext, withPassword bool) error {
257	var secui libkb.SecretUI
258	if withPassword {
259		secui = fu.NewSecretUI()
260	} else {
261		secui = nullSecretUI{}
262	}
263	uis := libkb.UIs{
264		ProvisionUI: newTestProvisionUI(),
265		LogUI:       g.UI.GetLogUI(),
266		GPGUI:       &gpgtestui{},
267		SecretUI:    secui,
268		LoginUI:     &libkb.TestLoginUI{Username: fu.Username},
269	}
270	m := libkb.NewMetaContextTODO(g).WithUIs(uis)
271	li := NewLoginWithUserSwitch(g, keybase1.DeviceTypeV2_DESKTOP, fu.Username, keybase1.ClientType_CLI, true)
272	return RunEngine2(m, li)
273}
274
275func (fu *FakeUser) LoginOrBust(tc libkb.TestContext) {
276	if err := fu.Login(tc.G); err != nil {
277		tc.T.Fatal(err)
278	}
279}
280
281func (fu *FakeUser) NewSecretUI() *libkb.TestSecretUI {
282	return &libkb.TestSecretUI{Passphrase: fu.Passphrase}
283}
284
285func (fu *FakeUser) NewCountSecretUI() *libkb.TestCountSecretUI {
286	return &libkb.TestCountSecretUI{Passphrase: fu.Passphrase}
287}
288
289func AssertProvisioned(tc libkb.TestContext) error {
290	m := NewMetaContextForTest(tc)
291	prov, err := isLoggedInWithError(m)
292	if err != nil {
293		return err
294	}
295	if !prov {
296		return libkb.LoginRequiredError{}
297	}
298	return nil
299}
300
301func AssertLoggedIn(tc libkb.TestContext) error {
302	if !LoggedIn(tc) {
303		return libkb.LoginRequiredError{}
304	}
305	return nil
306}
307
308func AssertLoggedOut(tc libkb.TestContext) error {
309	if LoggedIn(tc) {
310		return libkb.LogoutError{}
311	}
312	return nil
313}
314
315func LoggedIn(tc libkb.TestContext) bool {
316	return tc.G.ActiveDevice.Valid()
317}
318
319func Logout(tc libkb.TestContext) {
320	mctx := libkb.NewMetaContextForTest(tc)
321	if err := mctx.LogoutKillSecrets(); err != nil {
322		tc.T.Fatalf("logout error: %s", err)
323	}
324}
325
326// TODO: Add tests that use testEngineWithSecretStore for every engine
327// that should work with the secret store.
328
329// testEngineWithSecretStore takes a given engine-running function and
330// makes sure that it works with the secret store, i.e. that it stores
331// data into it when told to and reads data out from it.
332func testEngineWithSecretStore(
333	t *testing.T,
334	runEngine func(libkb.TestContext, *FakeUser, libkb.SecretUI)) {
335
336	tc := SetupEngineTest(t, "wss")
337	defer tc.Cleanup()
338
339	fu := SignupFakeUserStoreSecret(tc, "wss")
340	simulateServiceRestart(t, tc, fu)
341
342	testSecretUI := libkb.TestSecretUI{
343		Passphrase:  fu.Passphrase,
344		StoreSecret: true,
345	}
346	runEngine(tc, fu, &testSecretUI)
347
348	if testSecretUI.CalledGetPassphrase {
349		t.Fatal("GetPassphrase() unexpectedly called")
350	}
351}
352
353func SetupTwoDevices(t *testing.T, nm string) (user *FakeUser, dev1 libkb.TestContext, dev2 libkb.TestContext, cleanup func()) {
354	return SetupTwoDevicesWithHook(t, nm, nil)
355}
356
357func SetupTwoDevicesWithHook(t *testing.T, nm string, hook func(tc *libkb.TestContext)) (user *FakeUser, dev1 libkb.TestContext, dev2 libkb.TestContext, cleanup func()) {
358	if len(nm) > 5 {
359		t.Fatalf("Sorry, test name must be fewer than 6 chars (got %q)", nm)
360	}
361
362	// device X (provisioner) context:
363	dev1 = SetupEngineTest(t, nm)
364
365	// device Y (provisionee) context:
366	dev2 = SetupEngineTest(t, nm)
367	if hook != nil {
368		hook(&dev2)
369	}
370
371	user = NewFakeUserOrBust(t, nm)
372	arg := MakeTestSignupEngineRunArg(user)
373	arg.SkipPaper = false
374	loginUI := &paperLoginUI{Username: user.Username}
375	uis := libkb.UIs{
376		LogUI:    dev1.G.UI.GetLogUI(),
377		GPGUI:    &gpgtestui{},
378		SecretUI: user.NewSecretUI(),
379		LoginUI:  loginUI,
380	}
381	s := NewSignupEngine(dev1.G, &arg)
382	err := RunEngine2(NewMetaContextForTest(dev1).WithUIs(uis), s)
383	if err != nil {
384		t.Fatal(err)
385	}
386
387	assertNumDevicesAndKeys(dev1, user, 2, 4)
388
389	if len(loginUI.PaperPhrase) == 0 {
390		t.Fatal("login ui has no paper key phrase")
391	}
392
393	secUI := user.NewSecretUI()
394	secUI.Passphrase = loginUI.PaperPhrase
395	provUI := newTestProvisionUIPaper()
396	provLoginUI := &libkb.TestLoginUI{Username: user.Username}
397	uis = libkb.UIs{
398		ProvisionUI: provUI,
399		LogUI:       dev2.G.UI.GetLogUI(),
400		SecretUI:    secUI,
401		LoginUI:     provLoginUI,
402		GPGUI:       &gpgtestui{},
403	}
404	eng := NewLogin(dev2.G, keybase1.DeviceTypeV2_DESKTOP, "", keybase1.ClientType_CLI)
405	m2 := NewMetaContextForTest(dev2).WithUIs(uis)
406	if err := RunEngine2(m2, eng); err != nil {
407		t.Fatal(err)
408	}
409
410	testUserHasDeviceKey(dev2)
411
412	assertNumDevicesAndKeys(dev2, user, 3, 6)
413
414	if err := AssertProvisioned(dev2); err != nil {
415		t.Fatal(err)
416	}
417
418	cleanup = func() {
419		dev1.Cleanup()
420		dev2.Cleanup()
421	}
422
423	return user, dev1, dev2, cleanup
424}
425
426func NewMetaContextForTest(tc libkb.TestContext) libkb.MetaContext {
427	return libkb.NewMetaContextForTest(tc)
428}
429func NewMetaContextForTestWithLogUI(tc libkb.TestContext) libkb.MetaContext {
430	return libkb.NewMetaContextForTestWithLogUI(tc)
431}
432
433func ResetAccount(tc libkb.TestContext, u *FakeUser) {
434	ResetAccountNoLogout(tc, u)
435	Logout(tc)
436}
437
438func ResetAccountNoLogout(tc libkb.TestContext, u *FakeUser) {
439	m := NewMetaContextForTest(tc)
440	err := libkb.ResetAccount(m, u.NormalizedUsername(), u.Passphrase)
441	if err != nil {
442		tc.T.Fatalf("In account reset: %s", err)
443	}
444	tc.T.Logf("Account reset for user %s", u.Username)
445}
446
447func ForcePUK(tc libkb.TestContext) {
448	arg := &PerUserKeyUpgradeArgs{}
449	eng := NewPerUserKeyUpgrade(tc.G, arg)
450	uis := libkb.UIs{
451		LogUI: tc.G.UI.GetLogUI(),
452	}
453	m := NewMetaContextForTest(tc).WithUIs(uis)
454	if err := RunEngine2(m, eng); err != nil {
455		tc.T.Fatal(err)
456	}
457}
458
459func getUserSeqno(tc *libkb.TestContext, uid keybase1.UID) keybase1.Seqno {
460	mctx := NewMetaContextForTest(*tc)
461	res, err := tc.G.API.Get(mctx, libkb.APIArg{
462		Endpoint: "user/lookup",
463		Args: libkb.HTTPArgs{
464			"uid": libkb.UIDArg(uid),
465		},
466	})
467	require.NoError(tc.T, err)
468	seqno, err := res.Body.AtKey("them").AtKey("sigs").AtKey("last").AtKey("seqno").GetInt()
469	require.NoError(tc.T, err)
470	return keybase1.Seqno(seqno)
471}
472
473func checkUserSeqno(tc *libkb.TestContext, uid keybase1.UID, expected keybase1.Seqno) {
474	require.Equal(tc.T, expected, getUserSeqno(tc, uid))
475}
476
477func fakeSalt() []byte {
478	return []byte("fakeSALTfakeSALT")
479}
480
481func clearCaches(g *libkb.GlobalContext) {
482	g.ActiveDevice.ClearCaches()
483}
484