1package engine
2
3import (
4	"fmt"
5	"testing"
6
7	"github.com/keybase/client/go/libkb"
8	"github.com/keybase/client/go/protocol/keybase1"
9	"github.com/stretchr/testify/require"
10)
11
12func TestWebOfTrustVouch(t *testing.T) {
13	tc1 := SetupEngineTest(t, "wot")
14	tc2 := SetupEngineTest(t, "wot")
15	tc3 := SetupEngineTest(t, "wot")
16	defer tc1.Cleanup()
17	defer tc2.Cleanup()
18	defer tc3.Cleanup()
19
20	fu1 := CreateAndSignupFakeUser(tc1, "wot")
21	fu2 := CreateAndSignupFakeUser(tc2, "wot")
22	fu3 := CreateAndSignupFakeUser(tc3, "wot")
23
24	// make mutual track b/w fu1 and fu2, fu1 and fu3
25	sigVersion := libkb.GetDefaultSigVersion(tc1.G)
26	trackUser(tc2, fu2, fu1.NormalizedUsername(), sigVersion)
27	trackUser(tc1, fu1, fu2.NormalizedUsername(), sigVersion)
28	trackUser(tc1, fu1, fu3.NormalizedUsername(), sigVersion)
29	trackUser(tc3, fu3, fu1.NormalizedUsername(), sigVersion)
30
31	err := fu2.LoadUser(tc2)
32	require.NoError(tc2.T, err)
33
34	err = fu1.LoadUser(tc1)
35	require.NoError(tc1.T, err)
36	idt := fu1.User.IDTable()
37	lenBefore := idt.Len()
38	// should be logged in as fu1, double check:
39	require.Equal(t, tc1.G.ActiveDevice.UID(), fu1.UID())
40	mctx := NewMetaContextForTest(tc1)
41
42	// fu1 vouches for fu2
43	arg := &WotVouchArg{
44		Vouchee:    fu2.User.ToUserVersion(),
45		Confidence: keybase1.Confidence{UsernameVerifiedVia: keybase1.UsernameVerificationType_OTHER_CHAT},
46		VouchText:  "alice is awesome",
47	}
48
49	eng := NewWotVouch(tc1.G, arg)
50	err = RunEngine2(mctx, eng)
51	require.NoError(t, err)
52
53	err = fu1.LoadUser(tc1)
54	require.NoError(tc1.T, err)
55	idt = fu1.User.IDTable()
56
57	// for now, let's just check that it got bigger:
58	require.Equal(tc1.T, lenBefore+1, idt.Len())
59
60	err = fu3.LoadUser(tc3)
61	require.NoError(tc3.T, err)
62
63	// make sure that if the user is attesting to something about
64	// a user and eldest seqno changes, that they get an error.
65	uv := fu3.User.ToUserVersion()
66	uv.EldestSeqno++
67	arg = &WotVouchArg{
68		Vouchee:    uv,
69		Confidence: keybase1.Confidence{UsernameVerifiedVia: keybase1.UsernameVerificationType_OTHER_CHAT},
70		VouchText:  "bob is nice",
71	}
72	eng = NewWotVouch(tc1.G, arg)
73	err = RunEngine2(mctx, eng)
74	require.Error(tc1.T, err)
75
76	err = fu1.LoadUser(tc1)
77	require.NoError(tc1.T, err)
78	idt = fu1.User.IDTable()
79	require.Equal(tc1.T, lenBefore+1, idt.Len())
80
81	// make an fu1 -> fu3 attest with confidence stuff
82	arg = &WotVouchArg{
83		Vouchee:    fu3.User.ToUserVersion(),
84		VouchText:  "charlie rocks",
85		Confidence: confidence,
86	}
87	eng = NewWotVouch(tc1.G, arg)
88	err = RunEngine2(mctx, eng)
89	require.NoError(tc1.T, err)
90
91	err = fu1.LoadUser(tc1)
92	require.NoError(tc1.T, err)
93	idt = fu1.User.IDTable()
94	require.Equal(tc1.T, lenBefore+2, idt.Len())
95}
96
97func TestWebOfTrustPending(t *testing.T) {
98	tcAlice := SetupEngineTest(t, "wot")
99	tcBob := SetupEngineTest(t, "wot")
100	defer tcAlice.Cleanup()
101	defer tcBob.Cleanup()
102	alice := CreateAndSignupFakeUser(tcAlice, "wot")
103	bob := CreateAndSignupFakeUser(tcBob, "wot")
104	mctxA := NewMetaContextForTest(tcAlice)
105	mctxB := NewMetaContextForTest(tcBob)
106	t.Log("alice and bob exist")
107
108	sigVersion := libkb.GetDefaultSigVersion(tcAlice.G)
109	trackUser(tcBob, bob, alice.NormalizedUsername(), sigVersion)
110	trackUser(tcAlice, alice, bob.NormalizedUsername(), sigVersion)
111	err := bob.LoadUser(tcBob)
112	require.NoError(tcBob.T, err)
113	err = alice.LoadUser(tcAlice)
114	require.NoError(tcAlice.T, err)
115	t.Log("alice and bob follow each other")
116
117	aliceName := alice.User.GetName()
118	bobName := bob.User.GetName()
119
120	var vouches []keybase1.WotVouch
121	vouches, err = libkb.FetchWotVouches(mctxA, libkb.FetchWotVouchesArg{})
122	require.NoError(t, err)
123	require.Empty(t, vouches)
124	t.Log("alice has no pending vouches")
125	vouches, err = libkb.FetchWotVouches(mctxB, libkb.FetchWotVouchesArg{Vouchee: aliceName})
126	require.NoError(t, err)
127	require.Empty(t, vouches)
128	t.Log("bob sees no vouches for Alice")
129
130	firstVouch := "alice is wondibar but i don't have much confidence"
131	arg := &WotVouchArg{
132		Vouchee:    alice.User.ToUserVersion(),
133		Confidence: keybase1.Confidence{UsernameVerifiedVia: keybase1.UsernameVerificationType_OTHER_CHAT},
134		VouchText:  firstVouch,
135	}
136	eng := NewWotVouch(tcBob.G, arg)
137	err = RunEngine2(mctxB, eng)
138	require.NoError(t, err)
139	t.Log("bob vouches for alice without confidence")
140
141	vouches, err = libkb.FetchWotVouches(mctxA, libkb.FetchWotVouchesArg{})
142	require.NoError(t, err)
143	require.Len(t, vouches, 1)
144	bobVouch := vouches[0]
145	require.Equal(t, bob.User.GetUID(), bobVouch.Voucher.Uid)
146	require.Equal(t, bobName, bobVouch.VoucherUsername)
147	require.Equal(t, aliceName, bobVouch.VoucheeUsername)
148	require.Equal(t, firstVouch, bobVouch.VouchText)
149	require.NotNil(t, bobVouch.Confidence)
150	require.EqualValues(t, keybase1.UsernameVerificationType_OTHER_CHAT, bobVouch.Confidence.UsernameVerifiedVia)
151	require.Equal(t, keybase1.WotStatusType_PROPOSED, bobVouch.Status)
152	t.Log("alice sees one pending vouch")
153	vouches, err = libkb.FetchWotVouches(mctxB, libkb.FetchWotVouchesArg{Vouchee: aliceName})
154	require.NoError(t, err)
155	require.Equal(t, 1, len(vouches))
156	t.Log("bob also sees his pending vouch for Alice")
157
158	tcCharlie := SetupEngineTest(t, "wot")
159	defer tcCharlie.Cleanup()
160	charlie := CreateAndSignupFakeUser(tcCharlie, "wot")
161	mctxC := NewMetaContextForTest(tcCharlie)
162	t.Log("charlie exists")
163
164	trackUser(tcCharlie, charlie, alice.NormalizedUsername(), sigVersion)
165	trackUser(tcAlice, alice, charlie.NormalizedUsername(), sigVersion)
166	err = charlie.LoadUser(tcCharlie)
167	require.NoError(tcCharlie.T, err)
168	t.Log("alice and charlie follow each other")
169
170	charlieName := charlie.User.GetName()
171
172	vouchText := "alice is wondibar and doug agrees"
173	arg = &WotVouchArg{
174		Vouchee:    alice.User.ToUserVersion(),
175		VouchText:  vouchText,
176		Confidence: confidence,
177	}
178	eng = NewWotVouch(tcCharlie.G, arg)
179	err = RunEngine2(mctxC, eng)
180	require.NoError(t, err)
181	t.Log("charlie vouches for alice with confidence")
182
183	// ensure alice does a full load of bob by adding another link
184	// to bob's chain so the wot.vouch isn't the last one (which is always unstubbed)
185	// and nuking alice's local db to wipe any cache
186	trackUser(tcBob, bob, charlie.NormalizedUsername(), sigVersion)
187	_, err = mctxA.G().LocalDb.Nuke()
188	require.NoError(t, err)
189
190	vouches, err = libkb.FetchWotVouches(mctxA, libkb.FetchWotVouchesArg{})
191	require.NoError(t, err)
192	require.Len(t, vouches, 2)
193	require.EqualValues(t, bobVouch, vouches[0])
194	charlieVouch := vouches[1]
195	require.Equal(t, keybase1.WotStatusType_PROPOSED, charlieVouch.Status)
196	require.Equal(t, confidence, charlieVouch.Confidence)
197	t.Log("alice sees two pending vouches")
198
199	// alice gets just charlie's vouch using FetchWotVouches
200	vouches, err = libkb.FetchWotVouches(mctxA, libkb.FetchWotVouchesArg{Vouchee: aliceName, Voucher: charlieName})
201	require.NoError(t, err)
202	require.Len(t, vouches, 1)
203	require.Equal(t, keybase1.WotStatusType_PROPOSED, vouches[0].Status)
204	t.Log("alice sees charlie's pending vouch")
205}
206
207func TestWebOfTrustAccept(t *testing.T) {
208	tcAlice := SetupEngineTest(t, "wot")
209	tcBob := SetupEngineTest(t, "wot")
210	defer tcAlice.Cleanup()
211	defer tcBob.Cleanup()
212	alice := CreateAndSignupFakeUser(tcAlice, "wot")
213	bob := CreateAndSignupFakeUser(tcBob, "wot")
214	mctxA := NewMetaContextForTest(tcAlice)
215	mctxB := NewMetaContextForTest(tcBob)
216	t.Log("alice and bob exist")
217
218	sigVersion := libkb.GetDefaultSigVersion(tcAlice.G)
219	trackUser(tcBob, bob, alice.NormalizedUsername(), sigVersion)
220	trackUser(tcAlice, alice, bob.NormalizedUsername(), sigVersion)
221	err := bob.LoadUser(tcBob)
222	require.NoError(tcBob.T, err)
223	err = alice.LoadUser(tcAlice)
224	require.NoError(tcAlice.T, err)
225	t.Log("alice and bob follow each other")
226
227	aliceName := alice.User.GetName()
228	bobName := bob.User.GetName()
229
230	vouchText := "alice is wondibar and doug agrees"
231	argV := &WotVouchArg{
232		Vouchee:    alice.User.ToUserVersion(),
233		VouchText:  vouchText,
234		Confidence: confidence,
235	}
236	engV := NewWotVouch(tcBob.G, argV)
237	err = RunEngine2(mctxB, engV)
238	require.NoError(t, err)
239	t.Log("bob vouches for alice with confidence")
240
241	vouches, err := libkb.FetchWotVouches(mctxA, libkb.FetchWotVouchesArg{})
242	require.NoError(t, err)
243	require.Len(t, vouches, 1)
244	bobVouch := vouches[0]
245	require.Equal(t, keybase1.WotStatusType_PROPOSED, bobVouch.Status)
246	require.Equal(t, bob.User.GetUID(), bobVouch.Voucher.Uid)
247	require.Equal(t, bobName, bobVouch.VoucherUsername)
248	require.Equal(t, aliceName, bobVouch.VoucheeUsername)
249	require.Equal(t, vouchText, bobVouch.VouchText)
250	t.Log("alice fetches one pending vouch")
251
252	argR := &WotReactArg{
253		Voucher:  bob.User.ToUserVersion(),
254		Proof:    bobVouch.VouchProof,
255		Reaction: keybase1.WotReactionType_ACCEPT,
256	}
257	engR := NewWotReact(tcAlice.G, argR)
258	err = RunEngine2(mctxA, engR)
259	require.NoError(t, err)
260	t.Log("alice accepts")
261
262	vouches, err = libkb.FetchWotVouches(mctxA, libkb.FetchWotVouchesArg{})
263	require.NoError(t, err)
264	require.Equal(t, 1, len(vouches))
265	vouch := vouches[0]
266	require.Equal(t, keybase1.WotStatusType_ACCEPTED, vouch.Status)
267	require.Equal(t, bob.User.GetUID(), vouch.Voucher.Uid)
268	require.Equal(t, vouchText, vouch.VouchText)
269	require.EqualValues(t, confidence, vouch.Confidence)
270
271	vouches, err = libkb.FetchWotVouches(mctxB, libkb.FetchWotVouchesArg{Vouchee: aliceName})
272	require.NoError(t, err)
273	require.Equal(t, 1, len(vouches))
274	vouch = vouches[0]
275	require.Equal(t, keybase1.WotStatusType_ACCEPTED, vouch.Status)
276	require.Equal(t, bob.User.GetUID(), vouch.Voucher.Uid)
277	require.Equal(t, vouchText, vouch.VouchText)
278	require.EqualValues(t, confidence, vouch.Confidence)
279}
280
281func TestWebOfTrustReject(t *testing.T) {
282	tcAlice := SetupEngineTest(t, "wot")
283	tcBob := SetupEngineTest(t, "wot")
284	defer tcAlice.Cleanup()
285	defer tcBob.Cleanup()
286	alice := CreateAndSignupFakeUser(tcAlice, "wot")
287	bob := CreateAndSignupFakeUser(tcBob, "wot")
288	mctxA := NewMetaContextForTest(tcAlice)
289	mctxB := NewMetaContextForTest(tcBob)
290	t.Log("alice and bob exist")
291
292	sigVersion := libkb.GetDefaultSigVersion(tcAlice.G)
293	trackUser(tcBob, bob, alice.NormalizedUsername(), sigVersion)
294	trackUser(tcAlice, alice, bob.NormalizedUsername(), sigVersion)
295	err := bob.LoadUser(tcBob)
296	require.NoError(tcBob.T, err)
297	err = alice.LoadUser(tcAlice)
298	require.NoError(tcAlice.T, err)
299	t.Log("alice and bob follow each other")
300
301	aliceName := alice.User.GetName()
302
303	vouchText := "alice is wondibar"
304	argV := &WotVouchArg{
305		Vouchee:    alice.User.ToUserVersion(),
306		Confidence: keybase1.Confidence{UsernameVerifiedVia: keybase1.UsernameVerificationType_OTHER_CHAT},
307		VouchText:  vouchText,
308	}
309	engV := NewWotVouch(tcBob.G, argV)
310	err = RunEngine2(mctxB, engV)
311	require.NoError(t, err)
312	t.Log("bob vouches for alice")
313
314	vouches, err := libkb.FetchWotVouches(mctxA, libkb.FetchWotVouchesArg{})
315	require.NoError(t, err)
316	require.Len(t, vouches, 1)
317	bobVouch := vouches[0]
318	require.Equal(t, keybase1.WotStatusType_PROPOSED, bobVouch.Status)
319	require.Equal(t, bob.User.GetUID(), bobVouch.Voucher.Uid)
320	require.Equal(t, vouchText, bobVouch.VouchText)
321	t.Log("alice fetches one pending vouch")
322
323	argR := &WotReactArg{
324		Voucher:  bob.User.ToUserVersion(),
325		Proof:    bobVouch.VouchProof,
326		Reaction: keybase1.WotReactionType_REJECT,
327	}
328	engR := NewWotReact(tcAlice.G, argR)
329	err = RunEngine2(mctxA, engR)
330	require.NoError(t, err)
331	t.Log("alice rejects it")
332
333	vouches, err = libkb.FetchWotVouches(mctxA, libkb.FetchWotVouchesArg{})
334	require.NoError(t, err)
335	require.Equal(t, 1, len(vouches))
336	vouch := vouches[0]
337	require.Equal(t, keybase1.WotStatusType_REJECTED, vouch.Status)
338	require.Equal(t, bob.User.GetUID(), vouch.Voucher.Uid)
339	require.Equal(t, vouchText, vouch.VouchText)
340	require.NotNil(t, vouch.Confidence)
341	require.EqualValues(t, keybase1.UsernameVerificationType_OTHER_CHAT, bobVouch.Confidence.UsernameVerifiedVia)
342	t.Log("alice can see it as rejected")
343
344	vouches, err = libkb.FetchWotVouches(mctxB, libkb.FetchWotVouchesArg{Vouchee: aliceName})
345	require.NoError(t, err)
346	require.Equal(t, 1, len(vouches))
347	require.Equal(t, keybase1.WotStatusType_REJECTED, vouches[0].Status)
348	t.Log("bob can also see it as rejected")
349}
350
351func TestWebOfTrustRevoke(t *testing.T) {
352	var err error
353	tcAlice := SetupEngineTest(t, "wot")
354	defer tcAlice.Cleanup()
355	tcBob := SetupEngineTest(t, "wot")
356	defer tcBob.Cleanup()
357	alice := CreateAndSignupFakeUser(tcAlice, "wot")
358	uisA := libkb.UIs{
359		LogUI:    tcAlice.G.UI.GetLogUI(),
360		SecretUI: alice.NewSecretUI(),
361	}
362	mctxA := NewMetaContextForTest(tcAlice).WithUIs(uisA)
363	bob := CreateAndSignupFakeUser(tcBob, "wot")
364	uisB := libkb.UIs{
365		LogUI:    tcBob.G.UI.GetLogUI(),
366		SecretUI: bob.NewSecretUI(),
367	}
368	mctxB := NewMetaContextForTest(tcBob).WithUIs(uisB)
369	aliceName := alice.NormalizedUsername().String()
370	bobName := bob.NormalizedUsername().String()
371	t.Logf("alice: %s and bob: %s exist", aliceName, bobName)
372	sigVersion := libkb.GetDefaultSigVersion(tcAlice.G)
373	trackUser(tcBob, bob, alice.NormalizedUsername(), sigVersion)
374	trackUser(tcAlice, alice, bob.NormalizedUsername(), sigVersion)
375	err = bob.LoadUser(tcBob)
376	require.NoError(tcBob.T, err)
377	err = alice.LoadUser(tcAlice)
378	require.NoError(tcAlice.T, err)
379	t.Log("alice and bob follow each other")
380
381	bobVouchesForAlice := func(version int) {
382		vouchText := fmt.Sprintf("alice is wondibar v%d", version)
383		arg := &WotVouchArg{
384			Vouchee:    alice.User.ToUserVersion(),
385			VouchText:  vouchText,
386			Confidence: confidence,
387		}
388		eng := NewWotVouch(tcBob.G, arg)
389		err = RunEngine2(mctxB, eng)
390		require.NoError(t, err)
391	}
392	aliceAccepts := func(sigID keybase1.SigID) error {
393		arg := &WotReactArg{
394			Voucher:  bob.User.ToUserVersion(),
395			Proof:    sigID,
396			Reaction: keybase1.WotReactionType_ACCEPT,
397		}
398		eng := NewWotReact(tcAlice.G, arg)
399		return RunEngine2(mctxA, eng)
400	}
401	aliceRejects := func(sigID keybase1.SigID) error {
402		arg := &WotReactArg{
403			Voucher:  bob.User.ToUserVersion(),
404			Proof:    sigID,
405			Reaction: keybase1.WotReactionType_REJECT,
406		}
407		eng := NewWotReact(tcAlice.G, arg)
408		return RunEngine2(mctxA, eng)
409	}
410	assertFetch := func(mctx libkb.MetaContext, version int, expectedStatus keybase1.WotStatusType) keybase1.WotVouch {
411		vouches, err := libkb.FetchWotVouches(mctx, libkb.FetchWotVouchesArg{Voucher: bobName, Vouchee: aliceName})
412		require.NoError(t, err)
413		require.Equal(t, 1, len(vouches))
414		vouch := vouches[0]
415		require.Equal(t, expectedStatus, vouch.Status)
416		require.Equal(t, bob.User.GetUID(), vouch.Voucher.Uid)
417		require.Equal(t, alice.User.GetUID(), vouch.Vouchee.Uid)
418		expectedVouchText := fmt.Sprintf("alice is wondibar v%d", version)
419		require.Equal(t, expectedVouchText, vouch.VouchText)
420		require.NotNil(t, vouch.Confidence)
421		return vouch
422	}
423	revokeSig := func(mctx libkb.MetaContext, sigID string) {
424		eng := NewRevokeSigsEngine(mctx.G(), []string{sigID})
425		err := RunEngine2(mctx, eng)
426		require.NoError(t, err)
427	}
428
429	// bob vouches for alice
430	vouchVersion := 1
431	bobVouchesForAlice(vouchVersion)
432	_ = assertFetch(mctxA, vouchVersion, keybase1.WotStatusType_PROPOSED)
433	wotVouchOne := assertFetch(mctxB, vouchVersion, keybase1.WotStatusType_PROPOSED)
434	t.Log("bob vouches for alice and everything looks good")
435
436	// bob revokes the attestation
437	revokeSig(mctxB, wotVouchOne.VouchProof.String())
438	_ = assertFetch(mctxA, vouchVersion, keybase1.WotStatusType_REVOKED)
439	_ = assertFetch(mctxB, vouchVersion, keybase1.WotStatusType_REVOKED)
440	t.Log("bob revokes the chainlink with the attestation, and it comes back `revoked` for both of them")
441
442	// bob vouches again
443	vouchVersion++
444	bobVouchesForAlice(vouchVersion)
445	_ = assertFetch(mctxA, vouchVersion, keybase1.WotStatusType_PROPOSED)
446	wotVouchTwo := assertFetch(mctxB, vouchVersion, keybase1.WotStatusType_PROPOSED)
447
448	// alice cannot accept the revoked proof
449	err = aliceAccepts(wotVouchOne.VouchProof)
450	require.Error(t, err)
451	// alice accepts the new proposed proof
452	err = aliceAccepts(wotVouchTwo.VouchProof)
453	require.NoError(t, err)
454	_ = assertFetch(mctxA, vouchVersion, keybase1.WotStatusType_ACCEPTED)
455	_ = assertFetch(mctxB, vouchVersion, keybase1.WotStatusType_ACCEPTED)
456
457	// alice revokes her acceptance
458	err = alice.LoadUser(tcAlice)
459	require.NoError(t, err)
460	aliceLastLink := alice.User.GetLastLink()
461	tlink, w := libkb.NewTypedChainLink(aliceLastLink)
462	require.Nil(t, w)
463	reactionLink, ok := tlink.(*libkb.WotReactChainLink)
464	require.True(t, ok)
465	revokeSig(mctxA, reactionLink.GetSigID().String())
466	// it goes back to proposed
467	_ = assertFetch(mctxA, vouchVersion, keybase1.WotStatusType_PROPOSED)
468	_ = assertFetch(mctxB, vouchVersion, keybase1.WotStatusType_PROPOSED)
469	// and now she accepts it and it looks accepted to both of them
470	err = aliceAccepts(wotVouchTwo.VouchProof)
471	require.NoError(t, err)
472	_ = assertFetch(mctxA, vouchVersion, keybase1.WotStatusType_ACCEPTED)
473	_ = assertFetch(mctxB, vouchVersion, keybase1.WotStatusType_ACCEPTED)
474
475	// bob revokes and it shows up as revoked
476	revokeSig(mctxB, wotVouchTwo.VouchProof.String())
477	_ = assertFetch(mctxA, vouchVersion, keybase1.WotStatusType_REVOKED)
478	_ = assertFetch(mctxB, vouchVersion, keybase1.WotStatusType_REVOKED)
479
480	///////////
481	// VouchWithRevoke
482	///////////
483	assertUserLastLinkIsVouchWithRevoke := func(user *FakeUser, tc libkb.TestContext) *libkb.WotVouchChainLink {
484		err := user.LoadUser(tc)
485		require.NoError(t, err)
486		lastLink := user.User.GetLastLink()
487		tlink, warning := libkb.NewTypedChainLink(lastLink)
488		require.Nil(t, warning)
489		vouchLink, ok := tlink.(*libkb.WotVouchChainLink)
490		require.True(t, ok)
491		require.Equal(t, 1, len(vouchLink.Revocations))
492		return vouchLink
493	}
494	vouchVersion++
495	bobVouchesForAlice(vouchVersion)
496	_ = assertFetch(mctxA, vouchVersion, keybase1.WotStatusType_PROPOSED)
497	vouchToRevoke := assertFetch(mctxB, vouchVersion, keybase1.WotStatusType_PROPOSED)
498	// another vouch on top of an unrevoked previous one should be of type vouchWithRevoke
499	vouchVersion++
500	bobVouchesForAlice(vouchVersion)
501	_ = assertFetch(mctxA, vouchVersion, keybase1.WotStatusType_PROPOSED)
502	vouchToAccept := assertFetch(mctxB, vouchVersion, keybase1.WotStatusType_PROPOSED)
503	vwrLink := assertUserLastLinkIsVouchWithRevoke(bob, tcBob)
504	require.Contains(t, vwrLink.Revocations, vouchToRevoke.VouchProof)
505	err = aliceAccepts(vouchToRevoke.VouchProof)
506	require.Error(t, err)
507
508	// can vouchWithRevoke an accepted vouch, and then accept it
509	err = aliceAccepts(vouchToAccept.VouchProof)
510	vouchToRevoke = vouchToAccept
511	require.NoError(t, err)
512	vouchVersion++
513	bobVouchesForAlice(vouchVersion)
514	vouchToAccept = assertFetch(mctxA, vouchVersion, keybase1.WotStatusType_PROPOSED)
515	_ = assertFetch(mctxB, vouchVersion, keybase1.WotStatusType_PROPOSED)
516	vwrLink = assertUserLastLinkIsVouchWithRevoke(bob, tcBob)
517	require.Contains(t, vwrLink.Revocations, vouchToRevoke.VouchProof)
518	err = aliceAccepts(vouchToAccept.VouchProof)
519	require.NoError(t, err)
520	// it is significant that bob has not loaded alice between these two steps
521	// see comment in sig_chain.go#GetLinkFromSigID
522	err = aliceRejects(vouchToAccept.VouchProof)
523	require.NoError(t, err)
524
525	// can vouchWithRevoke a rejected vouch, and then accept it
526	vouchToRevoke = assertFetch(mctxB, vouchVersion, keybase1.WotStatusType_REJECTED)
527	bobVouchesForAlice(vouchVersion)
528	_ = assertFetch(mctxA, vouchVersion, keybase1.WotStatusType_PROPOSED)
529	vouchToAccept = assertFetch(mctxB, vouchVersion, keybase1.WotStatusType_PROPOSED)
530	vwrLink = assertUserLastLinkIsVouchWithRevoke(bob, tcBob)
531	require.Contains(t, vwrLink.Revocations, vouchToRevoke.VouchProof)
532	err = aliceAccepts(vouchToAccept.VouchProof)
533	require.NoError(t, err)
534
535	// confirm that vouch and vouchWithRevoke links are not stubbed by the server
536	// by checking that, when they are not the last link in someone's chain (which is always unstubbed),
537	// an unstubbed load is successful, and subsequently, those vouches are reactable.
538	// ------------------------------------------
539	// create a new user
540	tcCharlie := SetupEngineTest(t, "wot")
541	defer tcCharlie.Cleanup()
542	charlie := CreateAndSignupFakeUser(tcCharlie, "wot")
543	// clear out so our next vouch link doesn't have a revoke
544	revokeSig(mctxB, vouchToAccept.VouchProof.String())
545
546	assertVouchLinkLoadsCleanly := func(vouchToAccept keybase1.WotVouch) {
547		// add two stubbable links to the end of bob's chain
548		trackUser(tcBob, bob, charlie.NormalizedUsername(), sigVersion)
549		require.NoError(t, runUntrack(tcBob, bob, charlie.NormalizedUsername().String(), sigVersion))
550		// wipe alice so the load is definitely uncached
551		_, err := mctxA.G().LocalDb.Nuke()
552		require.NoError(t, err)
553		// alice can do a normal stubbed load of bob (there are no problems with vouch links being stubbed)
554		_, err = libkb.LoadUser(libkb.NewLoadUserByNameArg(tcAlice.G, bobName))
555		require.NoError(t, err)
556		// alice can still react to this link after having done a stubbed load
557		err = aliceAccepts(vouchToAccept.VouchProof)
558		require.NoError(t, err)
559	}
560	// a vouch link buried inside a sigchain does not cause loading/stubbing issues
561	vouchVersion++
562	bobVouchesForAlice(vouchVersion)
563	vouchToAccept = assertFetch(mctxB, vouchVersion, keybase1.WotStatusType_PROPOSED)
564	assertVouchLinkLoadsCleanly(vouchToAccept)
565	// a vouch_with_revoke link buried inside a sigchain does not cause loading/stubbing issues
566	vouchVersion++
567	bobVouchesForAlice(vouchVersion)
568	_ = assertUserLastLinkIsVouchWithRevoke(bob, tcBob)
569	vouchToAccept = assertFetch(mctxB, vouchVersion, keybase1.WotStatusType_PROPOSED)
570	assertVouchLinkLoadsCleanly(vouchToAccept)
571}
572
573// perhaps revisit after Y2K-1494
574func TestWebOfTrustSigBug(t *testing.T) {
575	tcAlice := SetupEngineTest(t, "wot")
576	tcBob := SetupEngineTest(t, "wot")
577	defer tcAlice.Cleanup()
578	defer tcBob.Cleanup()
579	alice := CreateAndSignupFakeUser(tcAlice, "wot")
580	bob := CreateAndSignupFakeUser(tcBob, "wot")
581	mctxA := NewMetaContextForTest(tcAlice)
582	mctxB := NewMetaContextForTest(tcBob)
583	t.Log("alice and bob exist")
584
585	sigVersion := libkb.GetDefaultSigVersion(tcAlice.G)
586	trackUser(tcBob, bob, alice.NormalizedUsername(), sigVersion)
587	trackUser(tcAlice, alice, bob.NormalizedUsername(), sigVersion)
588	err := bob.LoadUser(tcBob)
589	require.NoError(tcBob.T, err)
590	err = alice.LoadUser(tcAlice)
591	require.NoError(tcAlice.T, err)
592	t.Log("alice and bob follow each other")
593
594	// bob vouches for alice
595	firstVouch := "alice is wondibar cause we texted"
596	argV := &WotVouchArg{
597		Vouchee:    alice.User.ToUserVersion(),
598		Confidence: keybase1.Confidence{UsernameVerifiedVia: keybase1.UsernameVerificationType_OTHER_CHAT},
599		VouchText:  firstVouch,
600	}
601	engV := NewWotVouch(tcBob.G, argV)
602	err = RunEngine2(mctxB, engV)
603	require.NoError(t, err)
604
605	// alice rejects
606	vouches, err := libkb.FetchWotVouches(mctxA, libkb.FetchWotVouchesArg{})
607	require.NoError(t, err)
608	require.Len(t, vouches, 1)
609	bobVouch := vouches[0]
610	argR := &WotReactArg{
611		Voucher:  bob.User.ToUserVersion(),
612		Proof:    bobVouch.VouchProof,
613		Reaction: keybase1.WotReactionType_REJECT,
614	}
615	engR := NewWotReact(tcAlice.G, argR)
616	err = RunEngine2(mctxA, engR)
617	require.NoError(t, err)
618	t.Log("alice rejects it")
619	_, err = mctxA.G().LocalDb.Nuke()
620	require.NoError(t, err)
621
622	// bob vouches for alice
623	engV2 := NewWotVouch(tcBob.G, argV)
624	err = RunEngine2(mctxB, engV2)
625	require.NoError(t, err)
626
627	// this attestation is correctly recognized as proposed
628	vouches, err = libkb.FetchWotVouches(mctxA, libkb.FetchWotVouchesArg{})
629	require.NoError(t, err)
630	bobVouch = vouches[0]
631	require.Equal(t, bobVouch.Status, keybase1.WotStatusType_PROPOSED)
632}
633
634var confidence = keybase1.Confidence{
635	UsernameVerifiedVia: keybase1.UsernameVerificationType_PROOFS,
636	Proofs: []keybase1.WotProof{
637		{ProofType: keybase1.ProofType_REDDIT, Name: "reddit", Username: "betaveros"},
638		{ProofType: keybase1.ProofType_GENERIC_SOCIAL, Name: "mastodon.social", Username: "gammaveros"},
639		{ProofType: keybase1.ProofType_GENERIC_WEB_SITE, Protocol: "https:", Username: "beta.veros"},
640		{ProofType: keybase1.ProofType_DNS, Protocol: "dns", Username: "beta.veros"},
641	},
642}
643