1package engine
2
3import (
4	"crypto/rand"
5	"errors"
6	"fmt"
7	"strings"
8	"sync"
9	"testing"
10	"time"
11
12	"github.com/keybase/go-crypto/ed25519"
13
14	libkb "github.com/keybase/client/go/libkb"
15	keybase1 "github.com/keybase/client/go/protocol/keybase1"
16	clockwork "github.com/keybase/clockwork"
17	jsonw "github.com/keybase/go-jsonw"
18	require "github.com/stretchr/testify/require"
19	"golang.org/x/net/context"
20)
21
22func importTrackingLink(t *testing.T, g *libkb.GlobalContext) *libkb.TrackChainLink {
23	cl, err := libkb.ImportLinkFromServer(libkb.NewMetaContextBackground(g), nil, []byte(trackingServerReply), trackingUID)
24	if err != nil {
25		t.Fatal(err)
26	}
27	gl := libkb.GenericChainLink{ChainLink: cl}
28	tcl, err := libkb.ParseTrackChainLink(gl)
29	if err != nil {
30		t.Fatal(err)
31	}
32	return tcl
33}
34
35func TestIdentify2WithUIDImportTrackingLink(t *testing.T) {
36	tc := libkb.SetupTest(t, "TestIdentify2WithUIDImportTrackingLink", 0)
37	defer tc.Cleanup()
38	link := importTrackingLink(t, tc.G)
39	if link == nil {
40		t.Fatalf("link import failed")
41	}
42}
43
44type cacheStats struct {
45	hit     int
46	timeout int
47	miss    int
48	notime  int
49	breaks  int
50}
51
52type identify2testCache map[keybase1.UID](*keybase1.Identify2ResUPK2)
53
54func (c cacheStats) eq(h, t, m, n, b int) bool {
55	return h == c.hit && t == c.timeout && m == c.miss && n == c.notime && b == c.breaks
56}
57
58type Identify2WithUIDTester struct {
59	libkb.Contextified
60	libkb.BaseServiceType
61	sync.Mutex
62	finishCh        chan struct{}
63	startCh         chan struct{}
64	checkStatusHook func(libkb.SigHint, libkb.ProofCheckerMode) libkb.ProofError
65	cache           identify2testCache
66	slowStats       cacheStats
67	fastStats       cacheStats
68	now             time.Time
69	card            keybase1.UserCard
70	userLoads       map[keybase1.UID]int
71	noDiskCache     bool
72}
73
74func newIdentify2WithUIDTester(g *libkb.GlobalContext) *Identify2WithUIDTester {
75	return &Identify2WithUIDTester{
76		Contextified: libkb.NewContextified(g),
77		finishCh:     make(chan struct{}),
78		startCh:      make(chan struct{}, 1),
79		cache:        make(identify2testCache),
80		now:          time.Now(),
81		userLoads:    make(map[keybase1.UID]int),
82	}
83}
84
85func (i *Identify2WithUIDTester) ListProofCheckers(libkb.MetaContext) []string { return nil }
86func (i *Identify2WithUIDTester) ListServicesThatAcceptNewProofs(libkb.MetaContext) []string {
87	return nil
88}
89func (i *Identify2WithUIDTester) ListDisplayConfigs(libkb.MetaContext) []keybase1.ServiceDisplayConfig {
90	return nil
91}
92func (i *Identify2WithUIDTester) SuggestionFoldPriority(libkb.MetaContext) int { return 0 }
93func (i *Identify2WithUIDTester) Key() string                                  { return i.GetTypeName() }
94func (i *Identify2WithUIDTester) CheckProofText(text string, id keybase1.SigID, sig string) error {
95	return nil
96}
97func (i *Identify2WithUIDTester) DisplayName() string  { return "Identify2WithUIDTester" }
98func (i *Identify2WithUIDTester) GetPrompt() string    { return "" }
99func (i *Identify2WithUIDTester) GetProofType() string { return "" }
100func (i *Identify2WithUIDTester) GetTypeName() string  { return "" }
101func (i *Identify2WithUIDTester) NormalizeRemoteName(_ libkb.MetaContext, name string) (string, error) {
102	return name, nil
103}
104func (i *Identify2WithUIDTester) NormalizeUsername(name string) (string, error)    { return name, nil }
105func (i *Identify2WithUIDTester) PostInstructions(remotename string) *libkb.Markup { return nil }
106func (i *Identify2WithUIDTester) RecheckProofPosting(tryNumber int, status keybase1.ProofStatus, remotename string) (*libkb.Markup, error) {
107	return nil, nil
108}
109func (i *Identify2WithUIDTester) ToServiceJSON(remotename string) *jsonw.Wrapper { return nil }
110
111func (i *Identify2WithUIDTester) MakeProofChecker(_ libkb.RemoteProofChainLink) libkb.ProofChecker {
112	return i
113}
114func (i *Identify2WithUIDTester) GetServiceType(context.Context, string) libkb.ServiceType { return i }
115func (i *Identify2WithUIDTester) PickerSubtext() string                                    { return "" }
116
117func (i *Identify2WithUIDTester) CheckStatus(m libkb.MetaContext, h libkb.SigHint,
118	pcm libkb.ProofCheckerMode, _ keybase1.MerkleStoreEntry) (*libkb.SigHint, libkb.ProofError) {
119	if i.checkStatusHook != nil {
120		return nil, i.checkStatusHook(h, pcm)
121	}
122	m.Debug("Check status rubber stamp: %+v", h)
123	return nil, nil
124}
125
126func (i *Identify2WithUIDTester) GetTorError() libkb.ProofError {
127	return nil
128}
129
130func (i *Identify2WithUIDTester) FinishSocialProofCheck(libkb.MetaContext, keybase1.RemoteProof, keybase1.LinkCheckResult) error {
131	return nil
132}
133func (i *Identify2WithUIDTester) Confirm(libkb.MetaContext, *keybase1.IdentifyOutcome) (res keybase1.ConfirmResult, err error) {
134	return
135}
136func (i *Identify2WithUIDTester) FinishWebProofCheck(libkb.MetaContext, keybase1.RemoteProof, keybase1.LinkCheckResult) error {
137	return nil
138}
139func (i *Identify2WithUIDTester) DisplayCryptocurrency(libkb.MetaContext, keybase1.Cryptocurrency) error {
140	return nil
141}
142func (i *Identify2WithUIDTester) DisplayStellarAccount(libkb.MetaContext, keybase1.StellarAccount) error {
143	return nil
144}
145func (i *Identify2WithUIDTester) DisplayKey(libkb.MetaContext, keybase1.IdentifyKey) error {
146	return nil
147}
148func (i *Identify2WithUIDTester) ReportLastTrack(libkb.MetaContext, *keybase1.TrackSummary) error {
149	return nil
150}
151func (i *Identify2WithUIDTester) LaunchNetworkChecks(libkb.MetaContext, *keybase1.Identity, *keybase1.User) error {
152	return nil
153}
154func (i *Identify2WithUIDTester) DisplayTrackStatement(libkb.MetaContext, string) error {
155	return nil
156}
157func (i *Identify2WithUIDTester) ReportTrackToken(libkb.MetaContext, keybase1.TrackToken) (err error) {
158	return nil
159}
160func (i *Identify2WithUIDTester) SetStrict(b bool) error {
161	return nil
162}
163func (i *Identify2WithUIDTester) DisplayUserCard(_ libkb.MetaContext, card keybase1.UserCard) error {
164	i.Lock()
165	defer i.Unlock()
166	i.card = card
167	return nil
168}
169
170func (i *Identify2WithUIDTester) DisplayTLFCreateWithInvite(libkb.MetaContext, keybase1.DisplayTLFCreateWithInviteArg) error {
171	return nil
172}
173
174func (i *Identify2WithUIDTester) Cancel(libkb.MetaContext) error {
175	return nil
176}
177
178func (i *Identify2WithUIDTester) Finish(libkb.MetaContext) error {
179	i.finishCh <- struct{}{}
180	return nil
181}
182
183func (i *Identify2WithUIDTester) Dismiss(_ libkb.MetaContext, _ string, _ keybase1.DismissReason) error {
184	return nil
185}
186
187func (i *Identify2WithUIDTester) Start(libkb.MetaContext, string, keybase1.IdentifyReason, bool) error {
188	i.startCh <- struct{}{}
189	return nil
190}
191
192func (i *Identify2WithUIDTester) Get(uid keybase1.UID, gctf libkb.GetCheckTimeFunc, gcdf libkb.GetCacheDurationFunc, breaksOK bool) (*keybase1.Identify2ResUPK2, error) {
193	i.Lock()
194	defer i.Unlock()
195	res := i.cache[uid]
196	stats := &i.slowStats
197
198	// Please excuse this horrible hack, but use the `GetCacheDurationFunc` to see if we're dealing
199	// with a fast cache duration
200	if gcdf(keybase1.Identify2ResUPK2{}) == libkb.Identify2CacheShortTimeout {
201		stats = &i.fastStats
202	}
203
204	if res == nil {
205		stats.miss++
206		return nil, nil
207	}
208	if gctf != nil {
209		then := gctf(*res)
210		if then == 0 {
211			stats.notime++
212			return nil, libkb.TimeoutError{}
213		}
214		if res.TrackBreaks != nil && !breaksOK {
215			stats.breaks++
216			return nil, libkb.TrackBrokenError{}
217		}
218		timeout := gcdf(*res)
219		thenTime := keybase1.FromTime(then)
220		if i.now.Sub(thenTime) > timeout {
221			stats.timeout++
222			return nil, libkb.TimeoutError{}
223		}
224	}
225	stats.hit++
226	return res, nil
227}
228
229func (i *Identify2WithUIDTester) Insert(up *keybase1.Identify2ResUPK2) error {
230	i.Lock()
231	defer i.Unlock()
232	tmp := *up
233	copy := &tmp
234	copy.Upk.Uvv.CachedAt = keybase1.ToTime(i.now)
235	i.cache[up.Upk.GetUID()] = copy
236	return nil
237}
238func (i *Identify2WithUIDTester) DidFullUserLoad(uid keybase1.UID) {
239	i.Lock()
240	defer i.Unlock()
241	i.userLoads[uid]++
242}
243func (i *Identify2WithUIDTester) UseDiskCache() bool {
244	i.Lock()
245	defer i.Unlock()
246	return !i.noDiskCache
247}
248
249func (i *Identify2WithUIDTester) Delete(uid keybase1.UID) error {
250	i.Lock()
251	defer i.Unlock()
252	delete(i.cache, uid)
253	return nil
254}
255
256func (i *Identify2WithUIDTester) Shutdown() {}
257
258var _ libkb.Identify2Cacher = (*Identify2WithUIDTester)(nil)
259
260func identify2MetaContext(tc libkb.TestContext, i libkb.IdentifyUI) libkb.MetaContext {
261	return NewMetaContextForTest(tc).WithUIs(libkb.UIs{IdentifyUI: i})
262}
263
264func TestIdentify2WithUIDWithoutTrack(t *testing.T) {
265	tc := SetupEngineTest(t, "Identify2WithUIDWithoutTrack")
266	defer tc.Cleanup()
267	i := newIdentify2WithUIDTester(tc.G)
268	tc.G.SetProofServices(i)
269	arg := &keybase1.Identify2Arg{
270		Uid:              tracyUID,
271		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
272	}
273	eng := NewIdentify2WithUID(tc.G, arg)
274	err := eng.Run(identify2MetaContext(tc, i))
275	if err != nil {
276		t.Fatal(err)
277	}
278	<-i.finishCh
279}
280
281func launchWaiter(t *testing.T, ch chan struct{}) func() {
282	waitCh := make(chan error)
283	go func() {
284		select {
285		case <-ch:
286			waitCh <- nil
287		case <-time.After(10 * time.Second):
288			waitCh <- errors.New("failed to get a finish after timeout")
289		}
290	}()
291	return func() {
292		err := <-waitCh
293		if err != nil {
294			t.Fatal(err)
295		}
296	}
297}
298
299func TestIdentify2WithUIDWithTrack(t *testing.T) {
300	tc := SetupEngineTest(t, "Identify2WithUIDWithTrack")
301	defer tc.Cleanup()
302	i := newIdentify2WithUIDTester(tc.G)
303	tc.G.SetProofServices(i)
304	arg := &keybase1.Identify2Arg{
305		Uid:              tracyUID,
306		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
307	}
308	eng := NewIdentify2WithUID(tc.G, arg)
309
310	eng.testArgs = &Identify2WithUIDTestArgs{
311		noMe: true,
312		tcl:  importTrackingLink(t, tc.G),
313	}
314
315	waiter := launchWaiter(t, i.finishCh)
316	err := eng.Run(identify2MetaContext(tc, i))
317	if err != nil {
318		t.Fatal(err)
319	}
320
321	waiter()
322}
323
324func TestIdentify2WithUIDWithTrackAndSuppress(t *testing.T) {
325	tc := SetupEngineTest(t, "Identify2WithUIDWithTrackAndSuppress")
326	defer tc.Cleanup()
327	i := newIdentify2WithUIDTester(tc.G)
328	tc.G.SetProofServices(i)
329	arg := &keybase1.Identify2Arg{
330		Uid:              tracyUID,
331		CanSuppressUI:    true,
332		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
333	}
334	eng := NewIdentify2WithUID(tc.G, arg)
335
336	eng.testArgs = &Identify2WithUIDTestArgs{
337		noMe: true,
338		tcl:  importTrackingLink(t, tc.G),
339	}
340
341	err := eng.Run(identify2MetaContext(tc, i))
342	if err != nil {
343		t.Fatal(err)
344	}
345
346	select {
347	case <-i.startCh:
348		t.Fatalf("did not expect the identify to start")
349	default:
350	}
351
352	select {
353	case <-i.finishCh:
354		t.Fatalf("did not expect the identify to end")
355	default:
356	}
357}
358
359func identify2WithUIDWithBrokenTrackMakeEngine(t *testing.T, arg *keybase1.Identify2Arg) (func(), error) {
360	tc := SetupEngineTest(t, "testIdentify2WithUIDWithBrokenTrack")
361	defer tc.Cleanup()
362	i := newIdentify2WithUIDTester(tc.G)
363	tc.G.SetProofServices(i)
364	eng := NewIdentify2WithUID(tc.G, arg)
365
366	eng.testArgs = &Identify2WithUIDTestArgs{
367		noMe:  true,
368		cache: i,
369		tcl:   importTrackingLink(t, tc.G),
370	}
371	i.checkStatusHook = func(l libkb.SigHint, _ libkb.ProofCheckerMode) libkb.ProofError {
372		if strings.Contains(l.GetHumanURL(), "twitter") {
373			tc.G.Log.Debug("failing twitter proof %s", l.GetHumanURL())
374			return libkb.NewProofError(keybase1.ProofStatus_DELETED, "gone!")
375		}
376		return nil
377	}
378	waiter := launchWaiter(t, i.finishCh)
379	err := eng.Run(identify2MetaContext(tc, i))
380	return waiter, err
381}
382
383func testIdentify2WithUIDWithBrokenTrack(t *testing.T, suppress bool) {
384	arg := &keybase1.Identify2Arg{
385		Uid:              tracyUID,
386		CanSuppressUI:    suppress,
387		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
388	}
389	waiter, err := identify2WithUIDWithBrokenTrackMakeEngine(t, arg)
390
391	if err == nil {
392		t.Fatal("expected an ID2 error since twitter proof failed")
393	}
394	waiter()
395}
396
397func TestIdentify2WithUIDWithBrokenTrack(t *testing.T) {
398	testIdentify2WithUIDWithBrokenTrack(t, false)
399}
400
401func TestIdentify2WithUIDWithBrokenTrackWithSuppressUI(t *testing.T) {
402	testIdentify2WithUIDWithBrokenTrack(t, true)
403}
404
405func TestIdentify2WithUIDWithUntrackedFastPath(t *testing.T) {
406	tc := SetupEngineTest(t, "TestIdentify2WithUIDWithUntrackedFastPath")
407	defer tc.Cleanup()
408	sigVersion := libkb.GetDefaultSigVersion(tc.G)
409
410	fu := CreateAndSignupFakeUser(tc, "track")
411
412	runID2 := func(expectFastPath bool) {
413
414		tester := newIdentify2WithUIDTester(tc.G)
415		tester.noDiskCache = true
416
417		eng := NewIdentify2WithUID(tc.G, &keybase1.Identify2Arg{Uid: aliceUID, IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_GUI})
418		eng.testArgs = &Identify2WithUIDTestArgs{
419			cache:                  tester,
420			allowUntrackedFastPath: true,
421		}
422		err := eng.Run(identify2MetaContext(tc, tester))
423		require.NoError(t, err)
424		require.Equal(t, expectFastPath, (eng.testArgs.stats.untrackedFastPaths == 1), "right number of untracked fast paths")
425	}
426
427	runID2(true)
428	trackAlice(tc, fu, sigVersion)
429	defer untrackAlice(tc, fu, sigVersion)
430	runID2(false)
431}
432
433func TestIdentify2WithUIDWithBrokenTrackFromChatGUI(t *testing.T) {
434
435	tc := SetupEngineTest(t, "TestIdentify2WithUIDWithBrokenTrackFromChatGUI")
436	defer tc.Cleanup()
437	tester := newIdentify2WithUIDTester(tc.G)
438	tc.G.SetProofServices(tester)
439	tester.checkStatusHook = func(l libkb.SigHint, _ libkb.ProofCheckerMode) libkb.ProofError {
440		if strings.Contains(l.GetHumanURL(), "twitter") {
441			tc.G.Log.Debug("failing twitter proof %s", l.GetHumanURL())
442			return libkb.NewProofError(keybase1.ProofStatus_DELETED, "gone!")
443		}
444		return nil
445	}
446
447	origUI := tester
448
449	checkBrokenRes := func(res *keybase1.Identify2ResUPK2) {
450		if !res.Upk.GetUID().Equal(tracyUID) {
451			t.Fatal("bad UID for t_tracy")
452		}
453		if res.Upk.GetName() != "t_tracy" {
454			t.Fatal("bad username for t_tracy")
455		}
456		if len(res.Upk.Current.DeviceKeys) != 4 {
457			t.Fatal("wrong # of device keys for tracy")
458		}
459		if res.TrackBreaks == nil || len(res.TrackBreaks.Proofs) != 1 {
460			t.Fatal("Expected to get back 1 broken proof")
461		}
462		if res.TrackBreaks.Proofs[0].RemoteProof.Key != "twitter" {
463			t.Fatal("Expected a twitter proof type")
464		}
465		if res.TrackBreaks.Proofs[0].Lcr.RemoteDiff.Type != keybase1.TrackDiffType_REMOTE_FAIL {
466			t.Fatal("wrong remote failure type")
467		}
468	}
469
470	runChatGUI := func() {
471		// Now run the engine again, but in gui mode, and check that we don't hit
472		// the cached broken guy.
473		eng := NewIdentify2WithUID(tc.G, &keybase1.Identify2Arg{Uid: tracyUID, IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_GUI})
474
475		eng.testArgs = &Identify2WithUIDTestArgs{
476			noMe:                   true,
477			cache:                  tester,
478			tcl:                    importTrackingLink(t, tc.G),
479			allowUntrackedFastPath: true,
480		}
481
482		waiter := launchWaiter(t, tester.finishCh)
483		m := identify2MetaContext(tc, tester)
484		err := eng.Run(m)
485		// Since we threw away the test UI, we have to manually complete the UI here,
486		// otherwise the waiter() will block indefinitely.
487		_ = origUI.Finish(m)
488		waiter()
489		if err != nil {
490			t.Fatalf("expected no ID2 error; got %v", err)
491		}
492		res, err := eng.Result(m)
493		if err != nil {
494			t.Fatalf("unexpected export error: %s", err)
495		}
496		checkBrokenRes(res)
497		if n := eng.testArgs.stats.untrackedFastPaths; n > 0 {
498			t.Fatalf("Didn't expect any untracked fast paths, but got %d", n)
499		}
500	}
501
502	runStandard := func() {
503		// Now run the engine again, but in normal mode, and check that we don't hit
504		// the cached broken guy.
505		eng := NewIdentify2WithUID(tc.G, &keybase1.Identify2Arg{Uid: tracyUID, IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI})
506
507		eng.testArgs = &Identify2WithUIDTestArgs{
508			noMe:  true,
509			cache: tester,
510			tcl:   importTrackingLink(t, tc.G),
511		}
512
513		waiter := launchWaiter(t, tester.finishCh)
514		err := eng.Run(identify2MetaContext(tc, tester))
515		waiter()
516		if err == nil {
517			t.Fatalf("Expected a break with running ID2 in standard mode")
518		}
519	}
520
521	runChatGUI()
522
523	// First time through, we should miss both caches
524	if !tester.fastStats.eq(0, 0, 1, 0, 0) || !tester.slowStats.eq(0, 0, 1, 0, 0) {
525		t.Fatalf("bad cache stats: %+v, %+v", tester.fastStats, tester.slowStats)
526	}
527
528	runStandard()
529
530	// If we run without the chat GUI, we should hit the cache, but have it be
531	// disqualified because the cached copy has broken tracker statements.
532	if !tester.fastStats.eq(0, 0, 1, 0, 1) || !tester.slowStats.eq(0, 0, 1, 0, 1) {
533		t.Fatalf("bad cache stats: %+v, %+v", tester.fastStats, tester.slowStats)
534	}
535
536	runChatGUI()
537
538	// The next time we run with the chat GUI, we won't hit the slow or fast
539	// cache, since the failure in standard mode cleared out the cache for this
540	// user.
541	if !tester.fastStats.eq(0, 0, 2, 0, 1) || !tester.slowStats.eq(0, 0, 2, 0, 1) {
542		t.Fatalf("bad cache stats: %+v, %+v", tester.fastStats, tester.slowStats)
543	}
544
545	tester.incNow(time.Second)
546	runChatGUI()
547
548	// Now we should get a fast cache hit
549	if !tester.fastStats.eq(1, 0, 2, 0, 1) || !tester.slowStats.eq(0, 0, 2, 0, 1) {
550		t.Fatalf("bad cache stats: %+v, %+v", tester.fastStats, tester.slowStats)
551	}
552
553	tester.incNow(time.Second + libkb.Identify2CacheShortTimeout)
554	runChatGUI()
555
556	// A fast cache timeout and a slow cache hit!
557	if !tester.fastStats.eq(1, 1, 2, 0, 1) || !tester.slowStats.eq(1, 0, 2, 0, 1) {
558		t.Fatalf("bad cache stats: %+v, %+v", tester.fastStats, tester.slowStats)
559	}
560
561	// The fast cached should have been primed with the slow cache, so we expected
562	// a fast cache hit
563	runChatGUI()
564	if !tester.fastStats.eq(2, 1, 2, 0, 1) || !tester.slowStats.eq(1, 0, 2, 0, 1) {
565		t.Fatalf("bad cache stats: %+v, %+v", tester.fastStats, tester.slowStats)
566	}
567
568	tester.incNow(time.Second + libkb.Identify2CacheBrokenTimeout)
569	runChatGUI()
570
571	// After the broken timeout passes, we should get timeouts on both caches
572	if !tester.fastStats.eq(2, 2, 2, 0, 1) || !tester.slowStats.eq(1, 1, 2, 0, 1) {
573		t.Fatalf("bad cache stats: %+v, %+v", tester.fastStats, tester.slowStats)
574	}
575}
576
577func TestIdentify2WithUIDWithAssertion(t *testing.T) {
578	tc := SetupEngineTest(t, "Identify2WithUIDWithAssertion")
579	defer tc.Cleanup()
580	i := newIdentify2WithUIDTester(tc.G)
581	tc.G.SetProofServices(i)
582	arg := &keybase1.Identify2Arg{
583		Uid:              tracyUID,
584		UserAssertion:    "tacovontaco@twitter",
585		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
586	}
587	eng := NewIdentify2WithUID(tc.G, arg)
588
589	eng.testArgs = &Identify2WithUIDTestArgs{
590		noMe: true,
591	}
592
593	err := eng.Run(identify2MetaContext(tc, i))
594	if err != nil {
595		t.Fatal(err)
596	}
597
598	<-i.finishCh
599}
600
601func TestIdentify2WithUIDWithAssertions(t *testing.T) {
602	tc := SetupEngineTest(t, "Identify2WithUIDWithAssertion")
603	defer tc.Cleanup()
604	i := newIdentify2WithUIDTester(tc.G)
605	tc.G.SetProofServices(i)
606	arg := &keybase1.Identify2Arg{
607		Uid:              tracyUID,
608		UserAssertion:    "tacovontaco@twitter+t_tracy@rooter",
609		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
610	}
611	eng := NewIdentify2WithUID(tc.G, arg)
612
613	eng.testArgs = &Identify2WithUIDTestArgs{
614		noMe: true,
615	}
616
617	err := eng.Run(identify2MetaContext(tc, i))
618	if err != nil {
619		t.Fatal(err)
620	}
621
622	<-i.finishCh
623}
624
625func TestIdentify2WithUIDWithNonExistentAssertion(t *testing.T) {
626	tc := SetupEngineTest(t, "Identify2WithUIDWithNonExistentAssertion")
627	defer tc.Cleanup()
628	i := newIdentify2WithUIDTester(tc.G)
629	tc.G.SetProofServices(i)
630	arg := &keybase1.Identify2Arg{
631		Uid:              tracyUID,
632		UserAssertion:    "beyonce@twitter",
633		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
634	}
635	eng := NewIdentify2WithUID(tc.G, arg)
636
637	eng.testArgs = &Identify2WithUIDTestArgs{
638		noMe: true,
639	}
640
641	done := make(chan bool)
642	starts := 0
643	go func() {
644		select {
645		case <-i.startCh:
646			starts++
647		case <-done:
648			return
649		}
650	}()
651
652	err := eng.Run(identify2MetaContext(tc, i))
653	if err == nil {
654		t.Fatal(err)
655	}
656	if _, ok := err.(libkb.UnmetAssertionError); !ok {
657		t.Fatalf("Wanted an error of type %T; got %T", libkb.UnmetAssertionError{}, err)
658	}
659	if starts > 0 {
660		t.Fatalf("Didn't expect the identify UI to start in this case")
661	}
662
663	done <- true
664}
665
666func TestIdentify2WithUIDWithFailedAssertion(t *testing.T) {
667	tc := SetupEngineTest(t, "TestIdentify2WithUIDWithFailedAssertion")
668	defer tc.Cleanup()
669	i := newIdentify2WithUIDTester(tc.G)
670	tc.G.SetProofServices(i)
671	arg := &keybase1.Identify2Arg{
672		Uid:              tracyUID,
673		UserAssertion:    "tacovontaco@twitter",
674		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
675	}
676	eng := NewIdentify2WithUID(tc.G, arg)
677
678	eng.testArgs = &Identify2WithUIDTestArgs{
679		noMe: true,
680	}
681
682	starts := 0
683	var wg sync.WaitGroup
684	wg.Add(1)
685	go func() {
686		tc.G.Log.Debug("In BG: waiting for UI notification on startCh")
687		<-i.startCh
688		starts++
689		tc.G.Log.Debug("In BG: waited for UI notification on startCh")
690		wg.Done()
691	}()
692
693	i.checkStatusHook = func(l libkb.SigHint, _ libkb.ProofCheckerMode) libkb.ProofError {
694		if strings.Contains(l.GetHumanURL(), "twitter") {
695			tc.G.Log.Debug("failing twitter proof %s", l.GetHumanURL())
696			return libkb.NewProofError(keybase1.ProofStatus_DELETED, "gone!")
697		}
698		return nil
699	}
700
701	err := eng.Run(identify2MetaContext(tc, i))
702
703	if err == nil {
704		t.Fatal(err)
705	}
706	if _, ok := err.(libkb.ProofError); !ok {
707		t.Fatalf("Wanted an error of type libkb.ProofError; got %T", err)
708	}
709	wg.Wait()
710	if starts != 1 {
711		t.Fatalf("Expected the UI to have started")
712	}
713	<-i.finishCh
714}
715
716func TestIdentify2WithUIDWithFailedAncillaryAssertion(t *testing.T) {
717	tc := SetupEngineTest(t, "TestIdentify2WithUIDWithFailedAncillaryAssertion")
718	defer tc.Cleanup()
719	i := newIdentify2WithUIDTester(tc.G)
720	tc.G.SetProofServices(i)
721	arg := &keybase1.Identify2Arg{
722		Uid:              tracyUID,
723		UserAssertion:    "tacoplusplus@github+t_tracy@rooter",
724		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
725	}
726	eng := NewIdentify2WithUID(tc.G, arg)
727
728	eng.testArgs = &Identify2WithUIDTestArgs{
729		noMe: true,
730	}
731
732	var wg sync.WaitGroup
733	wg.Add(1)
734
735	i.checkStatusHook = func(l libkb.SigHint, _ libkb.ProofCheckerMode) libkb.ProofError {
736		switch {
737		case strings.Contains(l.GetHumanURL(), "twitter"):
738			wg.Done()
739			tc.G.Log.Debug("failing twitter proof %s", l.GetHumanURL())
740			return libkb.NewProofError(keybase1.ProofStatus_DELETED, "gone!")
741		case strings.Contains(l.GetHumanURL(), "github"):
742			wg.Wait()
743			return nil
744		case strings.Contains(l.GetHumanURL(), "rooter"):
745			wg.Wait()
746			return nil
747		default:
748			return nil
749		}
750	}
751
752	err := eng.Run(identify2MetaContext(tc, i))
753
754	if err != nil {
755		t.Fatal(err)
756	}
757	<-i.startCh
758	<-i.finishCh
759}
760
761func (i *Identify2WithUIDTester) incNow(d time.Duration) {
762	i.Lock()
763	defer i.Unlock()
764	i.now = i.now.Add(d)
765}
766
767func TestIdentify2WithUIDCache(t *testing.T) {
768	tc := SetupEngineTest(t, "Identify2WithUIDWithoutTrack")
769	defer tc.Cleanup()
770	i := newIdentify2WithUIDTester(tc.G)
771	tc.G.SetProofServices(i)
772	arg := &keybase1.Identify2Arg{
773		Uid:              tracyUID,
774		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
775	}
776	run := func() {
777		eng := NewIdentify2WithUID(tc.G, arg)
778		eng.testArgs = &Identify2WithUIDTestArgs{
779			cache: i,
780			clock: func() time.Time { return i.now },
781		}
782		err := eng.Run(identify2MetaContext(tc, i))
783		if err != nil {
784			t.Fatal(err)
785		}
786	}
787
788	// First time we'll cause an ID, so we need to finish
789	run()
790	<-i.startCh
791	<-i.finishCh
792
793	if !i.fastStats.eq(0, 0, 1, 0, 0) || !i.slowStats.eq(0, 0, 1, 0, 0) {
794		t.Fatalf("bad cache stats %+v %+v", i.fastStats, i.slowStats)
795	}
796
797	i.incNow(time.Second)
798	run()
799
800	// A new fast-path hit
801	if !i.fastStats.eq(1, 0, 1, 0, 0) || !i.slowStats.eq(0, 0, 1, 0, 0) {
802		t.Fatalf("bad cache stats %+v %+v", i.fastStats, i.slowStats)
803	}
804
805	i.incNow(time.Second + libkb.Identify2CacheShortTimeout)
806	run()
807
808	// A new fast-path timeout and a new slow-path hit
809	if !i.fastStats.eq(1, 1, 1, 0, 0) || !i.slowStats.eq(1, 0, 1, 0, 0) {
810		t.Fatalf("bad cache stats %+v %+v", i.fastStats, i.slowStats)
811	}
812
813	i.incNow(time.Second + libkb.Identify2CacheLongTimeout)
814	run()
815	<-i.startCh
816	<-i.finishCh
817
818	// A new fast-path timeout and a new slow-path timeout
819	if !i.fastStats.eq(1, 2, 1, 0, 0) || !i.slowStats.eq(1, 1, 1, 0, 0) {
820		t.Fatalf("bad cache stats %+v %+v", i.fastStats, i.slowStats)
821	}
822
823	i.incNow(time.Second)
824	run()
825	// A new fast-path hit
826	if !i.fastStats.eq(2, 2, 1, 0, 0) || !i.slowStats.eq(1, 1, 1, 0, 0) {
827		t.Fatalf("bad cache stats %+v %+v", i.fastStats, i.slowStats)
828	}
829
830	arg.UserAssertion = "tacovontaco@twitter"
831	i.incNow(time.Second)
832	run()
833	// A new slow-path hit; we have to use the slow path with assertions
834	if !i.fastStats.eq(2, 2, 1, 0, 0) || !i.slowStats.eq(2, 1, 1, 0, 0) {
835		t.Fatalf("bad cache stats %+v %+v", i.fastStats, i.slowStats)
836	}
837}
838
839func TestIdentify2WithUIDLocalAssertions(t *testing.T) {
840	tc := SetupEngineTest(t, "TestIdentify2WithUIDLocalAssertions")
841	defer tc.Cleanup()
842	i := newIdentify2WithUIDTester(tc.G)
843	tc.G.SetProofServices(i)
844	arg := &keybase1.Identify2Arg{
845		Uid:              tracyUID,
846		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
847	}
848
849	run := func() {
850		testArgs := &Identify2WithUIDTestArgs{
851			cache: i,
852			clock: func() time.Time { return i.now },
853		}
854		eng := NewIdentify2WithUID(tc.G, arg)
855		eng.testArgs = testArgs
856		err := eng.Run(identify2MetaContext(tc, i))
857		if err != nil {
858			t.Fatal(err)
859		}
860	}
861
862	numTracyLoads := func() int {
863		tracyUID := keybase1.UID("eb72f49f2dde6429e5d78003dae0c919")
864		return i.userLoads[tracyUID]
865	}
866
867	// First time we'll cause an ID, so we need to start & finish
868	arg.UserAssertion = "4ff50d580914427227bb14c821029e2c7cf0d488@" + libkb.PGPAssertionKey
869	run()
870	if n := numTracyLoads(); n != 1 {
871		t.Fatalf("expected 1 full user load; got %d", n)
872	}
873	<-i.startCh
874	<-i.finishCh
875
876	// Don't attempt to hit fast cache, since we're using local assertions.
877	if !i.fastStats.eq(0, 0, 0, 0, 0) || !i.slowStats.eq(0, 0, 1, 0, 0) {
878		t.Fatalf("bad cache stats %+v %+v", i.fastStats, i.slowStats)
879	}
880
881	i.incNow(time.Second)
882	run()
883	// A new slow-path hit
884	if !i.fastStats.eq(0, 0, 0, 0, 0) || !i.slowStats.eq(1, 0, 1, 0, 0) {
885		t.Fatalf("bad cache stats %+v %+v", i.fastStats, i.slowStats)
886	}
887	if n := numTracyLoads(); n != 1 {
888		t.Fatalf("expected 1 full user load; got %d", n)
889	}
890	arg.UserAssertion += "+tacovontaco@twitter"
891	i.incNow(time.Second)
892	run()
893	// A new slow-path hit
894	if !i.fastStats.eq(0, 0, 0, 0, 0) || !i.slowStats.eq(2, 0, 1, 0, 0) {
895		t.Fatalf("bad cache stats %+v %+v", i.fastStats, i.slowStats)
896	}
897	if n := numTracyLoads(); n != 2 {
898		t.Fatalf("expected 2 full user load; got %d", n)
899	}
900
901	i.incNow(libkb.Identify2CacheLongTimeout)
902	run()
903	<-i.startCh
904	<-i.finishCh
905	// A new slow-path timeout
906	if !i.fastStats.eq(0, 0, 0, 0, 0) || !i.slowStats.eq(2, 1, 1, 0, 0) {
907		t.Fatalf("bad cache stats %+v %+v", i.fastStats, i.slowStats)
908	}
909
910	i.incNow(time.Second)
911	run()
912	// A new slow-path hit
913	if !i.fastStats.eq(0, 0, 0, 0, 0) || !i.slowStats.eq(3, 1, 1, 0, 0) {
914		t.Fatalf("bad cache stats %+v %+v", i.fastStats, i.slowStats)
915	}
916}
917
918func TestResolveAndIdentify2WithUIDWithAssertions(t *testing.T) {
919	tc := SetupEngineTest(t, "Identify2WithUIDWithAssertion")
920	defer tc.Cleanup()
921	i := newIdentify2WithUIDTester(tc.G)
922	tc.G.SetProofServices(i)
923	arg := &keybase1.Identify2Arg{
924		UserAssertion:    "tacovontaco@twitter+t_tracy@rooter",
925		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
926	}
927	eng := NewResolveThenIdentify2(tc.G, arg)
928	eng.testArgs = &Identify2WithUIDTestArgs{
929		noMe: true,
930	}
931	err := eng.Run(identify2MetaContext(tc, i))
932	if err != nil {
933		t.Fatal(err)
934	}
935	<-i.startCh
936	<-i.finishCh
937}
938
939func TestIdentify2NoSigchain(t *testing.T) {
940	tc := SetupEngineTest(t, "Identify2NoSigchain")
941	defer tc.Cleanup()
942
943	u, _ := createFakeUserWithNoKeys(tc)
944	Logout(tc)
945
946	i := newIdentify2WithUIDTester(tc.G)
947	tc.G.SetProofServices(i)
948	arg := &keybase1.Identify2Arg{
949		UserAssertion:    u,
950		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
951	}
952	eng := NewResolveThenIdentify2(tc.G, arg)
953	m := identify2MetaContext(tc, i)
954	err := eng.Run(m)
955	if err != nil {
956		t.Fatalf("identify2 failed on user with no keys: %s", err)
957	}
958
959	// kbfs would like to have some info about the user
960	result, err := eng.Result(m)
961	if err != nil {
962		t.Fatalf("unexpeted export error: %s", err)
963	}
964	if result == nil {
965		t.Fatal("no result on id2 w/ no sigchain")
966	}
967	if result.Upk.GetName() != u {
968		t.Errorf("result username: %q, expected %q", result.Upk.GetName(), u)
969	}
970}
971
972// See CORE-4310
973func TestIdentifyAfterDbNuke(t *testing.T) {
974	tc := SetupEngineTest(t, "track")
975	defer tc.Cleanup()
976	sigVersion := libkb.GetDefaultSigVersion(tc.G)
977	fu := CreateAndSignupFakeUser(tc, "track")
978
979	trackAlice(tc, fu, sigVersion)
980	defer untrackAlice(tc, fu, sigVersion)
981
982	runIDAlice := func() {
983
984		i := newIdentify2WithUIDTester(tc.G)
985		arg := &keybase1.Identify2Arg{
986			Uid:              aliceUID,
987			IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
988		}
989		eng := NewIdentify2WithUID(tc.G, arg)
990		eng.testArgs = &Identify2WithUIDTestArgs{
991			noCache: true,
992		}
993		waiter := launchWaiter(t, i.finishCh)
994		if err := eng.Run(identify2MetaContext(tc, i)); err != nil {
995			t.Fatal(err)
996		}
997		waiter()
998	}
999
1000	tc.G.Log.Debug("------------ ID Alice Iteration 0 ---------------")
1001	runIDAlice()
1002	if _, err := tc.G.LocalDb.Nuke(); err != nil {
1003		t.Fatal(err)
1004	}
1005	if err := tc.G.ConfigureCaches(); err != nil {
1006		t.Fatal(err)
1007	}
1008	tc.G.Log.Debug("------------ ID Alice Iteration 1 ---------------")
1009	runIDAlice()
1010}
1011
1012func TestNoSelfHostedIdentifyInPassiveMode(t *testing.T) {
1013	tc := SetupEngineTest(t, "id")
1014	defer tc.Cleanup()
1015	sigVersion := libkb.GetDefaultSigVersion(tc.G)
1016
1017	eve := CreateAndSignupFakeUser(tc, "e")
1018	_, _, err := proveRooter(tc.G, eve, sigVersion)
1019	tc.G.ProofCache.DisableDisk()
1020	require.NoError(t, err)
1021	Logout(tc)
1022
1023	alice := CreateAndSignupFakeUser(tc, "a")
1024
1025	runTest := func(identifyBehavior keybase1.TLFIdentifyBehavior, returnUnchecked bool, shouldCheck bool, wantedMode libkb.ProofCheckerMode) {
1026
1027		i := newIdentify2WithUIDTester(tc.G)
1028		checked := false
1029		i.checkStatusHook = func(l libkb.SigHint, pcm libkb.ProofCheckerMode) libkb.ProofError {
1030			checked = true
1031			if strings.Contains(l.GetHumanURL(), "rooter") {
1032				if !shouldCheck {
1033					t.Fatalf("should not have gotten a check; should have hit cache")
1034				}
1035				require.Equal(t, pcm, wantedMode, "we get a passive ID in GUI mode")
1036				if returnUnchecked {
1037					return libkb.ProofErrorUnchecked
1038				}
1039			}
1040			tc.G.Log.Debug("proof rubber-stamped: %s", l.GetHumanURL())
1041			return nil
1042		}
1043
1044		tc.G.SetProofServices(i)
1045		arg := &keybase1.Identify2Arg{
1046			Uid:              eve.UID(),
1047			IdentifyBehavior: identifyBehavior,
1048			NeedProofSet:     true,
1049		}
1050		eng := NewIdentify2WithUID(tc.G, arg)
1051		eng.testArgs = &Identify2WithUIDTestArgs{
1052			noMe: false,
1053		}
1054		var waiter func()
1055		if !identifyBehavior.ShouldSuppressTrackerPopups() {
1056			waiter = launchWaiter(t, i.finishCh)
1057		}
1058		err := eng.Run(identify2MetaContext(tc, i))
1059		require.NoError(t, err)
1060		require.Equal(t, checked, shouldCheck)
1061		if waiter != nil {
1062			waiter()
1063		}
1064	}
1065
1066	// Alice ID's Eve, in chat mode, without a track. Assert that we get a
1067	// PASSIVE proof checker mode for rooter.
1068	runTest(keybase1.TLFIdentifyBehavior_CHAT_GUI, true, true, libkb.ProofCheckerModePassive)
1069
1070	// Alice ID's Eve, in standard ID mode, without a track. Assert that we get a
1071	// ACTIVE proof checker mode for the rooter
1072	runTest(keybase1.TLFIdentifyBehavior_DEFAULT_KBFS, false, true, libkb.ProofCheckerModeActive)
1073
1074	// Alice ID's Eve in chat mode, without a track. But she should hit the proof cache
1075	// from right above.
1076	runTest(keybase1.TLFIdentifyBehavior_CHAT_GUI, false, false, libkb.ProofCheckerModePassive)
1077
1078	trackUser(tc, alice, eve.NormalizedUsername(), sigVersion)
1079
1080	err = tc.G.ProofCache.Reset()
1081	require.NoError(t, err)
1082
1083	// Alice ID's Eve, in chat mode, with a track. Assert that we get an
1084	// Active proof checker mode for rooter.
1085	runTest(keybase1.TLFIdentifyBehavior_CHAT_GUI, true, true, libkb.ProofCheckerModeActive)
1086}
1087
1088func TestSkipExternalChecks(t *testing.T) {
1089	arg := &keybase1.Identify2Arg{
1090		Uid:           tracyUID,
1091		CanSuppressUI: true,
1092	}
1093	arg.IdentifyBehavior = keybase1.TLFIdentifyBehavior_KBFS_REKEY
1094	_, err := identify2WithUIDWithBrokenTrackMakeEngine(t, arg)
1095	require.NoError(t, err)
1096
1097	arg.IdentifyBehavior = keybase1.TLFIdentifyBehavior_KBFS_QR
1098	_, err = identify2WithUIDWithBrokenTrackMakeEngine(t, arg)
1099	require.NoError(t, err)
1100
1101	arg.IdentifyBehavior = keybase1.TLFIdentifyBehavior_CHAT_CLI
1102	_, err = identify2WithUIDWithBrokenTrackMakeEngine(t, arg)
1103	require.Error(t, err)
1104}
1105
1106type evilResolver struct {
1107	*libkb.ResolverImpl
1108	badPrefix string
1109	badUID    keybase1.UID
1110}
1111
1112func (e *evilResolver) ResolveFullExpressionWithBody(m libkb.MetaContext, s string) libkb.ResolveResult {
1113	ret := e.ResolverImpl.ResolveFullExpressionWithBody(m, s)
1114	if strings.HasPrefix(s, e.badPrefix) {
1115		ret.SetUIDForTesting(e.badUID)
1116	}
1117	return ret
1118}
1119
1120var _ libkb.Resolver = (*evilResolver)(nil)
1121
1122func TestResolveAndCheck(t *testing.T) {
1123	tc := SetupEngineTest(t, "id")
1124	defer tc.Cleanup()
1125	m := NewMetaContextForTest(tc)
1126	goodResolver := tc.G.Resolver.(*libkb.ResolverImpl)
1127	evilResolver := evilResolver{goodResolver, "t_alice", tracyUID}
1128
1129	var tests = []struct {
1130		s       string
1131		e       error
1132		useEvil bool
1133	}{
1134		{"tacovontaco@twitter+t_tracy@rooter", nil, false},
1135		{"tacovontaco@twitter+t_tracy@rooter+t_tracy", nil, false},
1136		{"t_tracy", nil, false},
1137		{"t_tracy+" + string(tracyUID) + "@uid", nil, false},
1138		{"tacovontaco@twitter+t_tracy@rooter+foobunny@github", libkb.UnmetAssertionError{}, false},
1139		{"foobunny@github", libkb.ResolutionError{}, false},
1140		{"foobunny", libkb.NotFoundError{}, false},
1141		{"foobunny+foobunny@github", libkb.NotFoundError{}, false},
1142		{"t_alice", libkb.UIDMismatchError{}, true},
1143		{"t_alice+t_tracy@rooter", libkb.UnmetAssertionError{}, true},
1144		{"t_alice+" + string(aliceUID) + "@uid", libkb.UnmetAssertionError{}, true},
1145		{"foobunny@gubble.social", libkb.ResolutionError{}, false},
1146	}
1147	for _, test := range tests {
1148		tc.G.Resolver = goodResolver
1149		if test.useEvil {
1150			tc.G.Resolver = &evilResolver
1151		}
1152		upk, err := ResolveAndCheck(m, test.s, true /*useTracking*/)
1153		require.IsType(t, test.e, err)
1154		if err == nil {
1155			require.True(t, upk.GetUID().Equal(tracyUID))
1156			require.Equal(t, upk.GetName(), "t_tracy")
1157		}
1158	}
1159
1160	// Test happy path for gubble social assertion
1161	fu := CreateAndSignupFakeUser(tc, "track")
1162	proveGubbleSocial(tc, fu, libkb.KeybaseSignatureV2)
1163	assertion := fmt.Sprintf("%s@gubble.social", fu.Username)
1164	upk, err := ResolveAndCheck(m, assertion, true /* useTracking */)
1165	require.NoError(t, err)
1166	require.True(t, upk.GetUID().Equal(fu.UID()))
1167	require.Equal(t, upk.GetName(), fu.Username)
1168}
1169
1170// TestTrackThenRevokeWithDifferentChatModes is described in CORE-9372. The scenario
1171// is that: (1) bob proves rooter; (2) alice follows bob; (3) bob revokes rooter;
1172// (4) alice ID's bob with CHAT_GUI, and that should work; (5)
1173// alice ID's bob with CHAT_GUI_STRICT, and that should fail
1174func TestTrackThenRevokeThenIdentifyWithDifferentChatModes(t *testing.T) {
1175	tc := SetupEngineTest(t, "id")
1176	defer tc.Cleanup()
1177
1178	fakeClock := clockwork.NewFakeClockAt(time.Now())
1179	tc.G.SetClock(fakeClock)
1180
1181	bob := CreateAndSignupFakeUser(tc, "b")
1182	_, sigID, err := proveRooter(tc.G, bob, 2)
1183	require.NoError(t, err)
1184	alice := CreateAndSignupFakeUser(tc, "a")
1185	trackUser(tc, alice, bob.NormalizedUsername(), 2)
1186	Logout(tc)
1187	err = bob.Login(tc.G)
1188	require.NoError(t, err)
1189	err = doRevokeSig(tc, bob, sigID)
1190	require.NoError(t, err)
1191	Logout(tc)
1192	err = alice.Login(tc.G)
1193	require.NoError(t, err)
1194
1195	// Blast through the cache
1196	fakeClock.Advance(libkb.Identify2CacheLongTimeout + time.Minute)
1197
1198	runIdentify := func(idb keybase1.TLFIdentifyBehavior) (err error) {
1199		idUI := &FakeIdentifyUI{}
1200		arg := keybase1.Identify2Arg{
1201			UserAssertion:    bob.Username,
1202			UseDelegateUI:    false,
1203			CanSuppressUI:    true,
1204			IdentifyBehavior: idb,
1205		}
1206
1207		uis := libkb.UIs{
1208			LogUI:      tc.G.UI.GetLogUI(),
1209			IdentifyUI: idUI,
1210		}
1211		eng := NewResolveThenIdentify2(tc.G, &arg)
1212		m := NewMetaContextForTest(tc).WithUIs(uis)
1213		err = RunEngine2(m, eng)
1214		return err
1215	}
1216
1217	err = runIdentify(keybase1.TLFIdentifyBehavior_CHAT_GUI)
1218	require.NoError(t, err)
1219}
1220
1221// Alice signs up using key X, Bob signs up, Bob tracks Alice,
1222// Alice resets and provisions using the same key X, Bob ids Alice
1223func TestTrackResetReuseKey(t *testing.T) {
1224	// Prepare key X
1225	var keyX [ed25519.SeedSize]byte
1226	_, err := rand.Read(keyX[:])
1227	require.NoError(t, err)
1228
1229	// Alice signs up using key X
1230	tcX := SetupEngineTest(t, "ida")
1231	defer tcX.Cleanup()
1232	fuX := NewFakeUserOrBust(t, "ida")
1233	suArg := MakeTestSignupEngineRunArg(fuX)
1234	pairX, err := libkb.GenerateNaclSigningKeyPairFromSeed(keyX)
1235	require.NoError(t, err)
1236	suArg.naclSigningKeyPair = pairX
1237	fuX.DeviceName = suArg.DeviceName
1238	SignupFakeUserWithArg(tcX, fuX, suArg)
1239	require.NoError(t, AssertProvisioned(tcX))
1240
1241	// Bob signs up using whatever key
1242	tcY := SetupEngineTest(t, "idb")
1243	defer tcY.Cleanup()
1244	fuY := CreateAndSignupFakeUser(tcY, "idb")
1245	require.NoError(t, AssertProvisioned(tcY))
1246
1247	// Bob should be able to ID Alice without any issues
1248	idUI := &FakeIdentifyUI{}
1249	require.NoError(t, RunEngine2(
1250		NewMetaContextForTest(tcY).WithUIs(libkb.UIs{
1251			LogUI:      tcY.G.UI.GetLogUI(),
1252			IdentifyUI: &FakeIdentifyUI{},
1253		}),
1254		NewResolveThenIdentify2(tcY.G, &keybase1.Identify2Arg{
1255			UserAssertion:    fuX.Username,
1256			ForceDisplay:     true,
1257			IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
1258		})),
1259	)
1260	require.False(t, idUI.BrokenTracking)
1261	require.Empty(t, idUI.DisplayKeyDiffs)
1262
1263	// Bob tracks Alice
1264	trackUser(tcY, fuY, fuX.NormalizedUsername(), libkb.GetDefaultSigVersion(tcX.G))
1265	assertTracking(tcY, fuX.Username)
1266
1267	// Alice gets reset and logs out
1268	ResetAccount(tcX, fuX)
1269
1270	// Alice logs in (and provisions) again
1271	loginEng := NewLogin(tcX.G, keybase1.DeviceTypeV2_DESKTOP, fuX.Username, keybase1.ClientType_CLI)
1272	loginEng.naclSigningKeyPair = pairX
1273	require.NoError(t,
1274		RunEngine2(
1275			NewMetaContextForTest(tcX).WithUIs(libkb.UIs{
1276				ProvisionUI: newTestProvisionUI(),
1277				LoginUI:     &libkb.TestLoginUI{},
1278				LogUI:       tcX.G.UI.GetLogUI(),
1279				SecretUI:    fuX.NewSecretUI(),
1280				GPGUI:       &gpgtestui{},
1281			}),
1282			loginEng,
1283		),
1284	)
1285	require.NoError(t, AssertProvisioned(tcX))
1286
1287	// Manually get rid of the id2 cache
1288	require.NoError(t, tcY.G.Identify2Cache().Delete(fuX.UID()))
1289
1290	// Bob should see that Alice reset even though the eldest kid is the same
1291	idUI = &FakeIdentifyUI{}
1292	err = RunEngine2(
1293		NewMetaContextForTest(tcY).WithUIs(libkb.UIs{
1294			LogUI:      tcY.G.UI.GetLogUI(),
1295			IdentifyUI: idUI,
1296		}),
1297		NewResolveThenIdentify2(tcY.G, &keybase1.Identify2Arg{
1298			UserAssertion:    fuX.Username,
1299			ForceDisplay:     true,
1300			IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
1301		}),
1302	)
1303	require.Error(t, err)
1304	require.Equal(t, "1 followed proof failed", err.(libkb.IdentifySummaryError).Problems()[0])
1305	require.Len(t, idUI.DisplayKeyDiffs, 1, "key diffs count")
1306	require.Equal(t, keybase1.TrackDiffType_NEW_ELDEST, idUI.DisplayKeyDiffs[0].Type, "key diff new eldest")
1307	require.False(t, idUI.BrokenTracking) // tracking is not "broken" for this user - it's a key change
1308
1309	// He should be able to retrack
1310	trackUser(tcY, fuY, fuX.NormalizedUsername(), libkb.GetDefaultSigVersion(tcX.G))
1311	assertTracking(tcY, fuX.Username)
1312
1313	// Which should fix the identification
1314	idUI = &FakeIdentifyUI{}
1315	require.NoError(t, RunEngine2(
1316		NewMetaContextForTest(tcY).WithUIs(libkb.UIs{
1317			LogUI:      tcY.G.UI.GetLogUI(),
1318			IdentifyUI: idUI,
1319		}),
1320		NewResolveThenIdentify2(tcY.G, &keybase1.Identify2Arg{
1321			UserAssertion:    fuX.Username,
1322			ForceDisplay:     true,
1323			IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
1324		})),
1325	)
1326	require.False(t, idUI.BrokenTracking)
1327	require.Empty(t, idUI.DisplayKeyDiffs)
1328}
1329
1330var aliceUID = keybase1.UID("295a7eea607af32040647123732bc819")
1331var tracyUID = keybase1.UID("eb72f49f2dde6429e5d78003dae0c919")
1332var trackingUID = keybase1.UID("92b3b3dbe457059f28c9f74e8e6b9419")
1333var trackingServerReply = `{"seqno":3,"payload_hash":"c3ffe390e9c9dabdd5f7253b81e0a38fad2c17589a9c7fcd967958418055140a","sig_id":"4ec10665ad163d0aa419ce4eab8ff661429c9a3a32cd4978fdb8c6b5c6d047620f","sig_id_short":"TsEGZa0WPQqkGc5Oq4_2YUKcmjoyzUl4_bjG","kid":"0101f3b2f0e8c9d1f099db64cac366c6a9c1da63da624127b2f66a056acfa36834fe0a","sig":"-----BEGIN PGP MESSAGE-----\nVersion: Keybase OpenPGP v2.0.49\nComment: https://keybase.io/crypto\n\n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\nHzt0Q3P9OxPLmqeOPX3RVSPpG2YdtWQ=\n=h5Bq\n-----END PGP MESSAGE-----","payload_json":"{\"body\":{\"client\":{\"name\":\"keybase.io web\"},\"key\":{\"eldest_kid\":\"0101f3b2f0e8c9d1f099db64cac366c6a9c1da63da624127b2f66a056acfa36834fe0a\",\"fingerprint\":\"a889587e1ce7bd7edbe3eeb8ef8fd8f7b31c4a2f\",\"host\":\"keybase.io\",\"key_id\":\"ef8fd8f7b31c4a2f\",\"kid\":\"0101f3b2f0e8c9d1f099db64cac366c6a9c1da63da624127b2f66a056acfa36834fe0a\",\"uid\":\"92b3b3dbe457059f28c9f74e8e6b9419\",\"username\":\"tracy_friend1\"},\"track\":{\"basics\":{\"id_version\":14,\"last_id_change\":1449514728,\"username\":\"t_tracy\"},\"id\":\"eb72f49f2dde6429e5d78003dae0c919\",\"key\":{\"key_fingerprint\":\"\",\"kid\":\"01209bd2e255235529cf45877767ad8687d85200518adc74595d058750e2f7ab7b000a\"},\"pgp_keys\":[{\"key_fingerprint\":\"4ff50d580914427227bb14c821029e2c7cf0d488\",\"kid\":\"0101ee69b1566428109eb7548d9a9d7267d48933daa4614fa743cedbeac618ab66dd0a\"}],\"remote_proofs\":[{\"ctime\":1449512840,\"curr\":\"f09c84ccadf8817aea944526638e9a4c034c9200dd68b5a3292c7f69d980390d\",\"etime\":1954088840,\"prev\":\"909f6aa65b050ec5582515cad43aeb1f9279ee21db955cff309abe4692b7e11a\",\"remote_key_proof\":{\"check_data_json\":{\"name\":\"twitter\",\"username\":\"tacovontaco\"},\"proof_type\":2,\"state\":1},\"seqno\":5,\"sig_id\":\"67570e971c5b8881cf07179d1872a83042be4285ba897a8f12dc3e419cade80b0f\",\"sig_type\":2},{\"ctime\":1449512883,\"curr\":\"8ad8ce94c9d23d260750294905877ef92adf4e7736198909fcbe7e27d6dfb463\",\"etime\":1954088883,\"prev\":\"f09c84ccadf8817aea944526638e9a4c034c9200dd68b5a3292c7f69d980390d\",\"remote_key_proof\":{\"check_data_json\":{\"name\":\"github\",\"username\":\"tacoplusplus\"},\"proof_type\":3,\"state\":1},\"seqno\":6,\"sig_id\":\"bfe76a25acf046f7477350291cdd178e1f0026a49f85733d97c122ba4e4a000f0f\",\"sig_type\":2},{\"ctime\":1449512914,\"curr\":\"ea5bee1701e7ec7c8dfd71421bd2ab6fb0fa2af473412c664fa49d35c34078ea\",\"etime\":1954088914,\"prev\":\"8ad8ce94c9d23d260750294905877ef92adf4e7736198909fcbe7e27d6dfb463\",\"remote_key_proof\":{\"check_data_json\":{\"name\":\"rooter\",\"username\":\"t_tracy\"},\"proof_type\":100001,\"state\":1},\"seqno\":7,\"sig_id\":\"0c467de321795b777aa10916eb9aa8153bffa5163b5079600db7d50ca00a77410f\",\"sig_type\":2},{\"ctime\":1449514687,\"curr\":\"bfd3462a2193fa7946f7f31e5074cfc4ac95400680273deb520078a6a4f5cbf5\",\"etime\":1954090687,\"prev\":\"9ae84f56c0c62dc91206363b9f5609245f94199d58a4a3c0bee7d4bb91c47de7\",\"remote_key_proof\":{\"check_data_json\":{\"hostname\":\"keybase.io\",\"protocol\":\"https:\"},\"proof_type\":1000,\"state\":1},\"seqno\":9,\"sig_id\":\"92eeea3db99cb519409765c17ea32a82ce8b86bbacd8f366e8e8930f1faea20b0f\",\"sig_type\":2}],\"seq_tail\":{\"payload_hash\":\"bfd3462a2193fa7946f7f31e5074cfc4ac95400680273deb520078a6a4f5cbf5\",\"seqno\":9,\"sig_id\":\"92eeea3db99cb519409765c17ea32a82ce8b86bbacd8f366e8e8930f1faea20b0f\"}},\"type\":\"track\",\"version\":1},\"ctime\":1449514785,\"expire_in\":157680000,\"prev\":\"a4f76660341a087d69238f5a25e98d8b3d038224457107bad91ffdfbd82d84d9\",\"seqno\":3,\"tag\":\"signature\"}","sig_type":3,"ctime":1449514785,"etime":1607194785,"rtime":null,"sig_status":0,"prev":"a4f76660341a087d69238f5a25e98d8b3d038224457107bad91ffdfbd82d84d9","proof_id":null,"proof_type":null,"proof_text_check":null,"proof_text_full":null,"check_data_json":null,"remote_id":null,"api_url":null,"human_url":null,"proof_state":null,"proof_status":null,"retry_count":null,"hard_fail_count":null,"last_check":null,"last_success":null,"version":null,"fingerprint":"a889587e1ce7bd7edbe3eeb8ef8fd8f7b31c4a2f","sig_version":1}`
1334