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	"errors"
8	"fmt"
9	"sync"
10	"time"
11
12	gregor "github.com/keybase/client/go/gregor"
13	libkb "github.com/keybase/client/go/libkb"
14	keybase1 "github.com/keybase/client/go/protocol/keybase1"
15	jsonw "github.com/keybase/go-jsonw"
16)
17
18type Identify2TestStats struct {
19	untrackedFastPaths int
20}
21
22type Identify2WithUIDTestArgs struct {
23	noMe                   bool                  // don't load ME
24	tcl                    *libkb.TrackChainLink // the track chainlink to use
25	selfLoad               bool                  // on if this is a self load
26	noCache                bool                  // on if we shouldn't use the cache
27	cache                  libkb.Identify2Cacher
28	clock                  func() time.Time
29	forceRemoteCheck       bool // on if we should force remote checks (like busting caches)
30	allowUntrackedFastPath bool // on if we can allow untracked fast path in test
31	stats                  Identify2TestStats
32}
33
34type identify2TrackType int
35
36const (
37	identify2NoTrack identify2TrackType = iota
38	identify2TrackOK
39	identify2TrackBroke
40)
41
42type identifyUser struct {
43	arg       libkb.LoadUserArg
44	full      *libkb.User
45	thin      *keybase1.UserPlusKeysV2AllIncarnations
46	isDeleted bool
47}
48
49func (i *identifyUser) GetUID() keybase1.UID {
50	if i.thin != nil {
51		return i.thin.GetUID()
52	}
53	if i.full != nil {
54		return i.full.GetUID()
55	}
56	panic("null user")
57}
58
59func (i *identifyUser) GetName() string {
60	if i.thin != nil {
61		return i.thin.GetName()
62	}
63	if i.full != nil {
64		return i.full.GetName()
65	}
66	panic("null user")
67}
68
69func (i *identifyUser) GetStatus() keybase1.StatusCode {
70	if i.thin != nil {
71		return i.thin.GetStatus()
72	}
73	if i.full != nil {
74		return i.full.GetStatus()
75	}
76	panic("null user")
77}
78
79func (i *identifyUser) GetNormalizedName() libkb.NormalizedUsername {
80	return libkb.NewNormalizedUsername(i.GetName())
81}
82
83func (i *identifyUser) BaseProofSet() *libkb.ProofSet {
84	if i.thin != nil {
85		return libkb.BaseProofSet(i.thin)
86	}
87	if i.full != nil {
88		return i.full.BaseProofSet()
89	}
90	panic("null user")
91}
92
93func (i *identifyUser) User(cache libkb.Identify2Cacher) (*libkb.User, error) {
94	if i.full != nil {
95		return i.full, nil
96	}
97	if cache != nil {
98		cache.DidFullUserLoad(i.GetUID())
99	}
100	var err error
101	i.full, err = libkb.LoadUser(i.arg)
102	return i.full, err
103}
104
105func (i *identifyUser) Export() *keybase1.User {
106	if i.thin != nil {
107		tmp := i.thin.ExportToSimpleUser()
108		return &tmp
109	}
110	if i.full != nil {
111		return i.full.Export()
112	}
113	panic("null user")
114}
115
116func (i *identifyUser) ExportToUserPlusKeysV2AllIncarnations() (*keybase1.UserPlusKeysV2AllIncarnations, error) {
117	if i.thin != nil {
118		return i.thin, nil
119	}
120	if i.full != nil {
121		return i.full.ExportToUPKV2AllIncarnations()
122	}
123	return nil, errors.New("null user in identify2: ExportToUserPlusKeysV2AllIncarnations")
124}
125
126func (i *identifyUser) IsCachedIdentifyFresh(upk *keybase1.UserPlusKeysV2AllIncarnations) bool {
127	if i.thin != nil {
128		ret := i.thin.Uvv.Equal(upk.Uvv)
129		return ret
130	}
131	if i.full != nil {
132		return i.full.IsCachedIdentifyFresh(upk)
133	}
134	panic("null user")
135}
136
137func (i *identifyUser) Equal(i2 *identifyUser) bool {
138	return i.GetUID().Equal(i2.GetUID())
139}
140
141func (i *identifyUser) load(g *libkb.GlobalContext) (err error) {
142	i.thin, i.full, err = g.GetUPAKLoader().LoadV2(i.arg)
143	return err
144}
145
146func (i *identifyUser) forceFullLoad(m libkb.MetaContext) (err error) {
147	arg := i.arg.WithForceReload()
148	i.thin, i.full, err = m.G().GetUPAKLoader().LoadV2(arg)
149	return err
150}
151
152func (i *identifyUser) isNil() bool {
153	return i.thin == nil && i.full == nil
154}
155
156func (i *identifyUser) Full() *libkb.User {
157	return i.full
158}
159
160func loadIdentifyUser(m libkb.MetaContext, arg libkb.LoadUserArg, cache libkb.Identify2Cacher) (*identifyUser, error) {
161	ret := &identifyUser{arg: arg}
162	err := ret.load(m.G())
163	if ret.isNil() {
164		ret = nil
165	} else if ret.full != nil && cache != nil {
166		cache.DidFullUserLoad(ret.GetUID())
167	}
168	return ret, err
169}
170
171func (i *identifyUser) trackChainLinkFor(m libkb.MetaContext, name libkb.NormalizedUsername, uid keybase1.UID) (ret *libkb.TrackChainLink, err error) {
172	defer m.Trace(fmt.Sprintf("identifyUser#trackChainLinkFor(%s)", name), &err)()
173
174	if i.full != nil {
175		m.Debug("| Using full user object")
176		return i.full.TrackChainLinkFor(m, name, uid)
177	}
178
179	if i.thin != nil {
180
181		m.Debug("| Using thin user object")
182
183		// In the common case, we look at the thin UPAK and get the chain link
184		// ID of the track chain link for tracking the given user. We'll then
185		// go ahead and load that chain link from local level DB, and it's almost
186		// always going to be there, since it was written as a side effect of
187		// fetching the full user. There's a corner case, see just below...
188		ret, err = libkb.TrackChainLinkFromUPK2AI(m, i.thin, name, uid)
189		if _, inconsistent := err.(libkb.InconsistentCacheStateError); !inconsistent {
190			m.Debug("| returning in common case -> (found=%v, err=%v)", (ret != nil), err)
191			return ret, err
192		}
193
194		m.Debug("| fell through to forceFullLoad corner case")
195
196		//
197		// NOTE(max) 2016-12-31
198		//
199		// There's a corner case here -- the track chain link does exist, but
200		// it wasn't found on disk. This is probably because the db cache was nuked.
201		// Thus, in this case we force a full user reload, and we're sure to get
202		// the tracking information then.
203		//
204		// See Jira ticket CORE-4310
205		//
206		err = i.forceFullLoad(m)
207		if err != nil {
208			return nil, err
209		}
210		return i.full.TrackChainLinkFor(m, name, uid)
211	}
212
213	// No user loaded, so no track chain link.
214	m.Debug("| fell through the empty default case")
215	return nil, nil
216}
217
218//
219// TODOs:
220//   - think harder about what we're caching in failure cases; right now we're only
221//     caching full successes.
222//   - Better error typing for various failures.
223//   - Work back in the identify card
224//
225
226// Identify2WithUID is the Identify engine used in KBFS, chat, and as a
227// subroutine of command-line crypto.
228type Identify2WithUID struct {
229	libkb.Contextified
230
231	arg           *keybase1.Identify2Arg
232	testArgs      *Identify2WithUIDTestArgs
233	trackToken    keybase1.TrackToken
234	confirmResult keybase1.ConfirmResult
235	cachedRes     *keybase1.Identify2ResUPK2
236
237	metaContext libkb.MetaContext
238
239	// If we just resolved a user, then we can plumb this through to loadUser()
240	ResolveBody *jsonw.Wrapper
241
242	me   *identifyUser
243	them *identifyUser
244
245	themAssertion   libkb.AssertionExpression
246	remoteAssertion libkb.AssertionAnd
247	localAssertion  libkb.AssertionAnd
248
249	state        libkb.IdentifyState
250	useTracking  bool
251	identifyKeys []keybase1.IdentifyKey
252
253	resultCh chan<- error
254
255	// For eagerly checking remote Assertions as they come in, these
256	// member variables maintain state, protected by the remotesMutex.
257	remotesMutex     sync.Mutex
258	remotesReceived  *libkb.ProofSet
259	remotesError     error
260	remotesCompleted bool
261
262	responsibleGregorItem gregor.Item
263
264	// When tracking is being performed, the identify engine is used with a tracking ui.
265	// These options are sent to the ui based on command line options.
266	// For normal identify, safe to leave these in their default zero state.
267	trackOptions keybase1.TrackOptions
268
269	// When called from chat, we should just collect breaking tracking failures, but
270	// not fail track. This is where we collect them
271	trackBreaks *keybase1.IdentifyTrackBreaks
272}
273
274var _ (Engine2) = (*Identify2WithUID)(nil)
275var _ (libkb.CheckCompletedListener) = (*Identify2WithUID)(nil)
276
277// Name is the unique engine name.
278func (e *Identify2WithUID) Name() string {
279	return "Identify2WithUID"
280}
281
282func NewIdentify2WithUID(g *libkb.GlobalContext, arg *keybase1.Identify2Arg) *Identify2WithUID {
283	return &Identify2WithUID{
284		Contextified: libkb.NewContextified(g),
285		arg:          arg,
286	}
287}
288
289// GetPrereqs returns the engine prereqs.
290func (e *Identify2WithUID) Prereqs() Prereqs {
291	return Prereqs{}
292}
293
294func (e *Identify2WithUID) WantDelegate(k libkb.UIKind) bool {
295	return k == libkb.IdentifyUIKind && e.arg.UseDelegateUI
296}
297
298func (e *Identify2WithUID) resetError(m libkb.MetaContext, inErr error) (outErr error) {
299
300	defer m.Trace(fmt.Sprintf("Identify2WithUID#resetError(%s)", libkb.ErrToOk(inErr)), &outErr)()
301
302	if inErr == nil {
303		return nil
304	}
305
306	// Check to see if this is an identify failure, and if not just return. If it is, we want
307	// to check what identify mode we are in here before returning an error.
308	if !libkb.IsIdentifyProofError(inErr) {
309		return inErr
310	}
311
312	if e.arg.IdentifyBehavior.WarningInsteadOfErrorOnBrokenTracks() {
313		m.Debug("| Reset err from %v -> nil since caller is '%s' %d", inErr, e.arg.IdentifyBehavior, e.arg.IdentifyBehavior)
314		return nil
315	}
316
317	return inErr
318}
319
320// Run then engine
321func (e *Identify2WithUID) Run(m libkb.MetaContext) (err error) {
322
323	m = m.WithLogTag("ID2")
324
325	n := fmt.Sprintf("Identify2WithUID#Run(UID=%v, Assertion=%s)", e.arg.Uid, e.arg.UserAssertion)
326	defer m.Trace(n, &err)()
327	m.Debug("| Full Arg: %+v", e.arg)
328
329	if e.arg.Uid.IsNil() {
330		return libkb.NoUIDError{}
331	}
332
333	// Only the first send matters, but we don't want to block the subsequent no-op
334	// sends. This code will break when we have more than 100 unblocking opportunities.
335	ch := make(chan error, 100)
336
337	e.resultCh = ch
338	go e.run(m)
339	err = <-ch
340
341	// Potentially reset the error based on the error and the calling context.
342	err = e.resetError(m, err)
343	return err
344}
345
346func (e *Identify2WithUID) notifyChat(m libkb.MetaContext, inErr error) error {
347	m.Debug("Identify2WithUID.run: notifyChat")
348	if !e.arg.IdentifyBehavior.ShouldRefreshChatView() {
349		return nil
350	}
351	if e.them == nil {
352		return fmt.Errorf("nil user")
353	}
354	nun := e.them.GetNormalizedName()
355	if nun.IsNil() {
356		return fmt.Errorf("no username for user")
357	}
358	// Don't notify if there's a non-identity proof error.
359	if inErr != nil && !libkb.IsIdentifyProofError(inErr) {
360		return nil
361	}
362
363	res, err := e.Result(m)
364	if err != nil {
365		return err
366	}
367
368	update := keybase1.CanonicalTLFNameAndIDWithBreaks{
369		CanonicalName: keybase1.CanonicalTlfName(nun),
370	}
371	if libkb.IsIdentifyProofError(inErr) {
372		update.Breaks = keybase1.TLFBreak{Breaks: []keybase1.TLFIdentifyFailure{
373			{
374				User:   *e.them.Export(),
375				Breaks: res.TrackBreaks,
376			},
377		}}
378	}
379	m.Debug("Identify2WithUID.run: running HandleChatIdentifyUpdate: %#v", update)
380	m.G().NotifyRouter.HandleChatIdentifyUpdate(m.Ctx(), update)
381
382	return nil
383}
384
385func (e *Identify2WithUID) run(m libkb.MetaContext) {
386	err := e.runReturnError(m)
387
388	if notifyChatErr := e.notifyChat(m, err); notifyChatErr != nil {
389		m.Warning("failed to notify chat of identify result: %s", notifyChatErr)
390	}
391
392	// always cancel IdentifyUI to allow clients to clean up.
393	// If no identifyUI was specified (because running the background)
394	// then don't do anything.
395	if m.UIs().IdentifyUI != nil {
396		err := m.UIs().IdentifyUI.Cancel(m)
397		if err != nil {
398			m.Debug("| error during cancel: %+v", err)
399		}
400	}
401	e.unblock(m, true /* isFinal */, err)
402}
403
404func (e *Identify2WithUID) hitFastCache(m libkb.MetaContext) bool {
405
406	if !e.allowCaching() {
407		m.Debug("| missed fast cache: no caching allowed")
408		return false
409	}
410	if e.useAnyAssertions() {
411		m.Debug("| missed fast cache: has assertions")
412		return false
413	}
414	if !e.allowEarlyOuts() {
415		m.Debug("| missed fast cache: we don't allow early outs")
416		return false
417	}
418	if !e.checkFastCacheHit(m) {
419		m.Debug("| missed fast cache: didn't hit")
420		return false
421	}
422	return true
423}
424
425func (e *Identify2WithUID) untrackedFastPath(m libkb.MetaContext) (ret bool) {
426
427	defer m.Trace("Identify2WithUID#untrackedFastPath", nil)()
428
429	if !e.arg.IdentifyBehavior.CanUseUntrackedFastPath() {
430		m.Debug("| Can't use untracked fast path due to identify behavior %v", e.arg.IdentifyBehavior)
431		return false
432	}
433
434	statInc := func() {
435		if e.testArgs != nil {
436			e.testArgs.stats.untrackedFastPaths++
437		}
438	}
439
440	if e.testArgs != nil && !e.testArgs.allowUntrackedFastPath {
441		m.Debug("| Can't use untracked fast path since disallowed in test")
442		return false
443	}
444
445	if e.them == nil {
446		m.Debug("| Can't use untracked fast path since failed to load them users")
447		return false
448	}
449
450	nun := e.them.GetNormalizedName()
451
452	// check if there's a tcl in the testArgs
453	if e.testArgs != nil && e.testArgs.tcl != nil {
454		trackedUsername, err := e.testArgs.tcl.GetTrackedUsername()
455		if err == nil && trackedUsername == nun {
456			m.Debug("| Test track link found for %s", nun.String())
457			return false
458		}
459	}
460
461	if e.me == nil {
462		m.Debug("| Can use untracked fastpath since there is no logged in user")
463		statInc()
464		return true
465	}
466
467	tcl, err := e.me.trackChainLinkFor(m, nun, e.them.GetUID())
468	if err != nil {
469		m.Debug("| Error getting track chain link: %s", err)
470		return false
471	}
472
473	if tcl != nil {
474		m.Debug("| Track found for %s", nun.String())
475		return false
476	}
477
478	statInc()
479	return true
480}
481
482func (e *Identify2WithUID) runReturnError(m libkb.MetaContext) (err error) {
483
484	m.Debug("+ acquire singleflight lock for %s", e.arg.Uid)
485	lock, err := m.G().IDLocktab.AcquireOnNameWithContext(m.Ctx(), m.G(), e.arg.Uid.String())
486	if err != nil {
487		m.Debug("| error acquiring singleflight lock for %s: %v", e.arg.Uid, err)
488		return err
489	}
490	m.Debug("- acquired singleflight lock")
491
492	defer func() {
493		m.Debug("+ Releasing singleflight lock for %s", e.arg.Uid)
494		lock.Release(m.Ctx())
495		m.Debug("- Released singleflight lock")
496	}()
497
498	if err = e.loadAssertion(m); err != nil {
499		return err
500	}
501
502	if e.hitFastCache(m) {
503		m.Debug("| hit fast cache")
504		e.maybeNotify(m, "hit fast cache")
505		return nil
506	}
507
508	m.Debug("| Identify2WithUID.loadUsers")
509	if err = e.loadUsers(m); err != nil {
510		return err
511	}
512
513	if err = e.checkLocalAssertions(); err != nil {
514		return err
515	}
516
517	if e.isSelfLoad() && !e.arg.NoSkipSelf && !e.useRemoteAssertions() {
518		m.Debug("| was a self load, short-circuiting")
519		e.maybeCacheSelf(m)
520		return nil
521	}
522
523	// If we are rekeying or reclaiming quota from KBFS, then let's
524	// skip the external checks.
525	if e.arg.IdentifyBehavior.SkipExternalChecks() {
526		m.Debug("| skip external checks specified, short-circuiting")
527		return nil
528	}
529
530	if !e.useRemoteAssertions() && e.allowEarlyOuts() {
531
532		if e.untrackedFastPath(m) {
533			m.Debug("| used untracked fast path")
534			e.maybeNotify(m, "untracked fast path")
535			return nil
536		}
537
538		if e.checkSlowCacheHit(m) {
539			m.Debug("| hit slow cache, first check")
540			e.maybeNotify(m, "slow cache, first check")
541			return nil
542		}
543	}
544
545	m.Debug("| Identify2WithUID.createIdentifyState")
546	if err = e.createIdentifyState(m); err != nil {
547		return err
548	}
549
550	if err = e.runIdentifyPrecomputation(); err != nil {
551		return err
552	}
553
554	// First we check that all remote assertions as present for the user,
555	// whether or not the remote check actually succeeds (hence the
556	// ProofState_NONE check).
557	okStates := []keybase1.ProofState{keybase1.ProofState_NONE, keybase1.ProofState_OK}
558	if err = e.checkRemoteAssertions(okStates); err != nil {
559		m.Debug("| Early fail due to missing remote assertions")
560		return err
561	}
562
563	if e.useRemoteAssertions() && e.allowEarlyOuts() && e.checkSlowCacheHit(m) {
564		m.Debug("| hit slow cache, second check")
565		e.maybeNotify(m, "slow cache, second check")
566		return nil
567	}
568
569	// If we're not using tracking and we're not using remote assertions,
570	// we can unblock the RPC caller here, and perform the identifyUI operations
571	// in the background. NOTE: we need to copy out our background context,
572	// since it will the foreground context will disappear after we unblock.
573	m = m.BackgroundWithLogTags()
574
575	if (!e.useTracking && !e.useRemoteAssertions() && e.allowEarlyOuts()) || e.arg.IdentifyBehavior.UnblockThenForceIDTable() {
576		e.unblock(m, false /* isFinal */, nil /* err */)
577	}
578
579	return e.runIdentifyUI(m)
580}
581
582func (e *Identify2WithUID) allowEarlyOuts() bool {
583	return !e.arg.NeedProofSet && !e.arg.IdentifyBehavior.UnblockThenForceIDTable()
584}
585
586func (e *Identify2WithUID) getNow(m libkb.MetaContext) time.Time {
587	if e.testArgs != nil && e.testArgs.clock != nil {
588		return e.testArgs.clock()
589	}
590	return m.G().Clock().Now()
591}
592
593func (e *Identify2WithUID) unblock(m libkb.MetaContext, isFinal bool, err error) {
594	m.Debug("| unblocking...")
595	if e.arg.AlwaysBlock && !isFinal {
596		m.Debug("| skipping unblock; isFinal=%v; AlwaysBlock=%v...", isFinal, e.arg.AlwaysBlock)
597	} else {
598		e.resultCh <- err
599		m.Debug("| unblock sent...")
600	}
601}
602
603func (e *Identify2WithUID) maybeCacheSelf(m libkb.MetaContext) {
604	if e.getCache() != nil {
605		v, err := e.exportToResult(m)
606		if v != nil && err == nil {
607			err := e.getCache().Insert(v)
608			if err != nil {
609				m.Debug("| error inserting: %+v", err)
610			}
611		}
612	}
613}
614
615// exportToResult either returns (non-nil, nil) on success, or (nil, non-nil) on error.
616func (e *Identify2WithUID) exportToResult(m libkb.MetaContext) (*keybase1.Identify2ResUPK2, error) {
617	if e.them == nil {
618		// this should never happen
619		return nil, libkb.UserNotFoundError{Msg: "failed to get a them user in Identify2WithUID#exportToResult"}
620	}
621	upk, err := e.toUserPlusKeysv2AllIncarnations()
622	if err != nil {
623		return nil, err
624	}
625	if upk == nil {
626		// this should never happen
627		return nil, libkb.UserNotFoundError{Msg: "failed export a them user in Identify2WithUID#exportToResult"}
628	}
629	return &keybase1.Identify2ResUPK2{
630		Upk:          *upk,
631		TrackBreaks:  e.trackBreaks,
632		IdentifiedAt: keybase1.ToTime(e.getNow(m)),
633	}, nil
634}
635
636func (e *Identify2WithUID) maybeCacheResult(m libkb.MetaContext) {
637
638	isOK := e.state.Result().IsOK()
639	canCacheFailures := e.arg.IdentifyBehavior.WarningInsteadOfErrorOnBrokenTracks()
640
641	m.Debug("+ maybeCacheResult (ok=%v; canCacheFailures=%v)", isOK, canCacheFailures)
642	defer m.Debug("- maybeCacheResult")
643
644	if e.getCache() == nil {
645		m.Debug("| cache is disabled, so nothing to do")
646		return
647	}
648
649	// If we hit an identify failure, and we're not allowed to cache failures,
650	// then at least bust out the cache.
651	if !isOK && !canCacheFailures {
652		m.Debug("| clearing cache due to failure")
653		uid := e.them.GetUID()
654		err := e.getCache().Delete(uid)
655		if err != nil {
656			m.Debug("| Error deleting uid: %+v", err)
657		}
658		if err := e.removeSlowCacheFromDB(m); err != nil {
659			m.Debug("| Error in removing slow cache from db: %s", err)
660		}
661		return
662	}
663
664	// Common case --- (isOK || canCacheFailures)
665	v, err := e.exportToResult(m)
666	if err != nil {
667		m.Debug("| not caching: error exporting: %s", err)
668		return
669	}
670	if v == nil {
671		m.Debug("| not caching; nil result")
672		return
673	}
674	err = e.getCache().Insert(v)
675	if err != nil {
676		m.Debug("| error inserting: %+v", err)
677	}
678
679	m.VLogf(libkb.VLog1, "| insert %+v", v)
680
681	// Don't write failures to the disk cache
682	if isOK {
683		if err := e.storeSlowCacheToDB(m); err != nil {
684			m.Debug("| Error in storing slow cache to db: %s", err)
685		}
686	}
687}
688
689func (e *Identify2WithUID) insertTrackToken(m libkb.MetaContext, outcome *libkb.IdentifyOutcome) (err error) {
690	defer m.Trace("Identify2WithUID#insertTrackToken", &err)()
691	e.trackToken, err = m.G().TrackCache().Insert(outcome)
692	if err != nil {
693		return err
694	}
695	return m.UIs().IdentifyUI.ReportTrackToken(m, e.trackToken)
696}
697
698// CCLCheckCompleted is triggered whenever a remote proof check completes.
699// We get these calls as a result of being a "CheckCompletedListener".
700// When each result comes in, we check against our pool of needed remote
701// assertions. If the set is complete, or if one that we need errors,
702// we can unblock the caller.
703func (e *Identify2WithUID) CCLCheckCompleted(lcr *libkb.LinkCheckResult) {
704	e.remotesMutex.Lock()
705	defer e.remotesMutex.Unlock()
706	m := e.metaContext
707
708	m.Debug("+ CheckCompleted for %s", lcr.GetLink().ToIDString())
709	defer m.Debug("- CheckCompleted")
710
711	// Always add to remotesReceived list, so that we have a full ProofSet.
712	pf := libkb.RemoteProofChainLinkToProof(lcr.GetLink())
713	e.remotesReceived.Add(pf)
714
715	if !e.useRemoteAssertions() || e.useTracking {
716		m.Debug("| Not using remote assertions or is tracking")
717		return
718	}
719
720	if !e.remoteAssertion.HasFactor(pf) {
721		m.Debug("| Proof isn't needed in our remote-assertion early-out check: %v", pf)
722		return
723	}
724
725	if err := lcr.GetError(); err != nil {
726		m.Debug("| got error -> %v", err)
727		e.remotesError = err
728	}
729
730	// note(maxtaco): this is a little ugly in that it's O(n^2) where n is the number
731	// of identities in the assertion. But I can't imagine n > 3, so this is fine
732	// for now.
733	matched := e.remoteAssertion.MatchSet(*e.remotesReceived)
734	m.Debug("| matched -> %v", matched)
735	if matched {
736		e.remotesCompleted = true
737	}
738
739	if e.remotesError != nil || e.remotesCompleted {
740		m.Debug("| unblocking, with err = %v", e.remotesError)
741		e.unblock(m, false, e.remotesError)
742	}
743}
744
745func (e *Identify2WithUID) checkLocalAssertions() error {
746	if !e.localAssertion.MatchSet(*e.them.BaseProofSet()) {
747		return libkb.UnmetAssertionError{User: e.them.GetName(), Remote: false}
748	}
749	return nil
750}
751
752func (e *Identify2WithUID) checkRemoteAssertions(okStates []keybase1.ProofState) error {
753	if e.them.isDeleted {
754		if e.G().Env.GetReadDeletedSigChain() {
755			return nil
756		}
757		return libkb.UnmetAssertionError{User: e.them.GetName(), Remote: true}
758	}
759	ps := libkb.NewProofSet(nil)
760	e.state.Result().AddProofsToSet(ps, okStates)
761	if !e.remoteAssertion.MatchSet(*ps) {
762		return libkb.UnmetAssertionError{User: e.them.GetName(), Remote: true}
763	}
764	return nil
765}
766
767func (e *Identify2WithUID) loadAssertion(mctx libkb.MetaContext) (err error) {
768	if len(e.arg.UserAssertion) == 0 {
769		return nil
770	}
771	e.themAssertion, err = libkb.AssertionParseAndOnly(e.G().MakeAssertionContext(mctx), e.arg.UserAssertion)
772	if err == nil {
773		e.remoteAssertion, e.localAssertion = libkb.CollectAssertions(e.themAssertion)
774	}
775	return err
776}
777
778func (e *Identify2WithUID) useAnyAssertions() bool {
779	return e.useLocalAssertions() || e.useRemoteAssertions()
780}
781
782func (e *Identify2WithUID) allowCaching() bool {
783	return e.arg.IdentifyBehavior.AllowCaching()
784}
785
786func (e *Identify2WithUID) useLocalAssertions() bool {
787	return e.localAssertion.Len() > 0
788}
789
790// If we need a ProofSet, it's as if we need remote assertions.
791func (e *Identify2WithUID) useRemoteAssertions() bool {
792	return (e.remoteAssertion.Len() > 0)
793}
794
795func (e *Identify2WithUID) runIdentifyPrecomputation() (err error) {
796
797	keyDiffDisplayHook := func(k keybase1.IdentifyKey) error {
798		e.identifyKeys = append(e.identifyKeys, k)
799		return nil
800	}
801	revokedKeyHook := func(id libkb.TrackIDComponent, diff libkb.TrackDiff) {
802		if diff == nil {
803			return
804		}
805		ipb := keybase1.IdentifyProofBreak{
806			RemoteProof: libkb.ExportTrackIDComponentToRevokedProof(id).Proof,
807			Lcr: keybase1.LinkCheckResult{
808				Diff:           libkb.ExportTrackDiff(diff),
809				BreaksTracking: true,
810			},
811		}
812		if e.trackBreaks == nil {
813			e.trackBreaks = &keybase1.IdentifyTrackBreaks{}
814		}
815		e.trackBreaks.Proofs = append(e.trackBreaks.Proofs, ipb)
816	}
817	e.state.Precompute(keyDiffDisplayHook, revokedKeyHook)
818	return nil
819}
820
821func (e *Identify2WithUID) displayUserCardAsync(m libkb.MetaContext) <-chan error {
822	// Skip showing the userCard if we are allowing deleted users since this
823	// will error out.
824	if e.arg.IdentifyBehavior.SkipUserCard() || e.G().Env.GetReadDeletedSigChain() {
825		return nil
826	}
827	return libkb.DisplayUserCardAsync(m, e.them.GetUID(), (e.me != nil))
828}
829
830func (e *Identify2WithUID) setupIdentifyUI(m libkb.MetaContext) libkb.MetaContext {
831	if e.arg.IdentifyBehavior.ShouldSuppressTrackerPopups() {
832		m.Debug("| using the loopback identify UI")
833		iui := NewLoopbackIdentifyUI(m.G(), &e.trackBreaks)
834		m = m.WithIdentifyUI(iui)
835	} else if e.useTracking && e.arg.CanSuppressUI && !e.arg.ForceDisplay {
836		iui := newBufferedIdentifyUI(m.G(), m.UIs().IdentifyUI, keybase1.ConfirmResult{
837			IdentityConfirmed: true,
838		})
839		m = m.WithIdentifyUI(iui)
840	}
841	return m
842}
843
844func (e *Identify2WithUID) runIdentifyUI(m libkb.MetaContext) (err error) {
845	n := fmt.Sprintf("+ runIdentifyUI(%s)", e.them.GetName())
846	defer m.Trace(n, &err)()
847
848	// RemoteReceived, start with the baseProofSet that has PGP
849	// fingerprints and the user's UID and username.
850	e.remotesReceived = e.them.BaseProofSet()
851
852	m = e.setupIdentifyUI(m)
853	iui := m.UIs().IdentifyUI
854
855	m.Debug("| IdentifyUI.Start(%s)", e.them.GetName())
856	if err = iui.Start(m, e.them.GetName(), e.arg.Reason, e.arg.ForceDisplay); err != nil {
857		return err
858	}
859	for _, k := range e.identifyKeys {
860		if err = iui.DisplayKey(m, k); err != nil {
861			return err
862		}
863	}
864	m.Debug("| IdentifyUI.ReportLastTrack(%s)", e.them.GetName())
865	if err = iui.ReportLastTrack(m, libkb.ExportTrackSummary(e.state.TrackLookup(), e.them.GetName())); err != nil {
866		return err
867	}
868	m.Debug("| IdentifyUI.LaunchNetworkChecks(%s)", e.them.GetName())
869	if err = iui.LaunchNetworkChecks(m, e.state.ExportToUncheckedIdentity(m), e.them.Export()); err != nil {
870		return err
871	}
872
873	waiter := e.displayUserCardAsync(m)
874
875	m.Debug("| IdentifyUI.Identify(%s)", e.them.GetName())
876	var them *libkb.User
877	them, err = e.them.User(e.getCache())
878	if err != nil {
879		return err
880	}
881
882	identifyTableMode := libkb.IdentifyTableModeActive
883	if e.arg.IdentifyBehavior.ShouldSuppressTrackerPopups() {
884		identifyTableMode = libkb.IdentifyTableModePassive
885	}
886
887	// When we get a callback from IDTable().Identify, we don't get to thread our metacontext
888	// through (for now), so stash it in the this.
889	e.metaContext = m
890	if them.IDTable() == nil {
891		m.Debug("| No IDTable for user")
892	} else if err = them.IDTable().Identify(m, e.state, e.forceRemoteCheck(), iui, e, identifyTableMode); err != nil {
893		m.Debug("| Failure in running IDTable")
894		return err
895	}
896
897	if waiter != nil {
898		m.Debug("+ Waiting for UserCard")
899		if err = <-waiter; err != nil {
900			m.Debug("| Failure in showing UserCard")
901			return err
902		}
903		m.Debug("- Waited for UserCard")
904	}
905
906	// use Confirm to display the IdentifyOutcome
907	outcome := e.state.Result()
908	outcome.TrackOptions = e.trackOptions
909	e.confirmResult, err = iui.Confirm(m, outcome.Export(e.G()))
910	if err != nil {
911		m.Debug("| Failure in iui.Confirm")
912		return err
913	}
914
915	err = e.insertTrackToken(m, outcome)
916	if err != nil {
917		m.Debug("| Error inserting track token: %+v", err)
918	}
919
920	if err = iui.Finish(m); err != nil {
921		m.Debug("| Failure in iui.Finish")
922		return err
923	}
924	m.Debug("| IdentifyUI.Finished(%s)", e.them.GetName())
925
926	err = e.checkRemoteAssertions([]keybase1.ProofState{keybase1.ProofState_OK})
927	e.maybeCacheResult(m)
928
929	m.Debug("| IdentifyUI: checked remote assertions (%s)", e.them.GetName())
930
931	if err == nil && !e.arg.NoErrorOnTrackFailure {
932		// We only care about tracking errors in this case; hence GetErrorLax
933		_, err = e.state.Result().GetErrorLax()
934	}
935
936	if outcome.IsOK() {
937		e.maybeNotify(m, "runIdentifyUI complete IsOk")
938	}
939
940	return err
941}
942
943func (e *Identify2WithUID) forceRemoteCheck() bool {
944	return e.arg.ForceRemoteCheck || (e.testArgs != nil && e.testArgs.forceRemoteCheck)
945}
946
947func (e *Identify2WithUID) createIdentifyState(m libkb.MetaContext) (err error) {
948	defer m.Trace("createIdentifyState", &err)()
949	var them *libkb.User
950	them, err = e.them.User(e.getCache())
951	if err != nil {
952		return err
953	}
954
955	e.state = libkb.NewIdentifyStateWithGregorItem(m.G(), e.responsibleGregorItem, them)
956
957	if e.testArgs != nil && e.testArgs.tcl != nil {
958		m.Debug("| using test track")
959		e.useTracking = true
960		e.state.SetTrackLookup(e.testArgs.tcl)
961		return nil
962	}
963
964	if e.me == nil {
965		m.Debug("| null me")
966		return nil
967	}
968
969	tcl, err := e.me.trackChainLinkFor(m, them.GetNormalizedName(), them.GetUID())
970	if tcl != nil {
971		m.Debug("| using track token %s", tcl.LinkID())
972		e.useTracking = true
973		e.state.SetTrackLookup(tcl)
974		if ttcl, _ := libkb.TmpTrackChainLinkFor(m, e.me.GetUID(), them.GetUID()); ttcl != nil {
975			m.Debug("| also have temporary track")
976			e.state.SetTmpTrackLookup(ttcl)
977		}
978	}
979
980	return nil
981}
982
983// RequiredUIs returns the required UIs.
984func (e *Identify2WithUID) RequiredUIs() []libkb.UIKind {
985	ret := []libkb.UIKind{}
986	if e.arg == nil || !e.arg.IdentifyBehavior.ShouldSuppressTrackerPopups() {
987		ret = append(ret, libkb.IdentifyUIKind)
988	}
989	return ret
990}
991
992// SubConsumers returns the other UI consumers for this engine.
993func (e *Identify2WithUID) SubConsumers() []libkb.UIConsumer {
994	return nil
995}
996
997func (e *Identify2WithUID) isSelfLoad() bool {
998	if e.testArgs != nil && e.testArgs.selfLoad {
999		return true
1000	}
1001	return e.me != nil && e.them != nil && e.me.Equal(e.them)
1002}
1003
1004func (e *Identify2WithUID) loadUserOpts(arg libkb.LoadUserArg) libkb.LoadUserArg {
1005	if !e.allowCaching() {
1006		arg = arg.WithForcePoll(true)
1007	}
1008	return arg
1009}
1010
1011func (e *Identify2WithUID) loadMe(m libkb.MetaContext, uid keybase1.UID) (err error) {
1012
1013	// Short circuit loadMe for testing
1014	if e.testArgs != nil && e.testArgs.noMe {
1015		return nil
1016	}
1017	arg := libkb.NewLoadUserArgWithMetaContext(m).WithUID(uid).WithSelf(true).WithStubMode(libkb.StubModeUnstubbed)
1018	e.me, err = loadIdentifyUser(m, e.loadUserOpts(arg), e.getCache())
1019	return err
1020}
1021
1022func (e *Identify2WithUID) loadThem(m libkb.MetaContext) (err error) {
1023	arg := e.loadUserOpts(libkb.NewLoadUserArgWithMetaContext(m).WithUID(e.arg.Uid).WithResolveBody(e.ResolveBody).WithPublicKeyOptional())
1024	e.them, err = loadIdentifyUser(m, arg, e.getCache())
1025	if err != nil {
1026		switch err.(type) {
1027		case libkb.NoKeyError:
1028			// convert this error to NoSigChainError
1029			return libkb.NoSigChainError{}
1030		case libkb.NotFoundError:
1031			return libkb.UserNotFoundError{UID: e.arg.Uid, Msg: "in Identify2WithUID"}
1032		default: // including libkb.UserDeletedError
1033			return err
1034		}
1035	}
1036	if e.them == nil {
1037		return libkb.UserNotFoundError{UID: e.arg.Uid, Msg: "in Identify2WithUID"}
1038	}
1039	err = libkb.UserErrorFromStatus(e.them.GetStatus())
1040	if _, ok := err.(libkb.UserDeletedError); ok && e.arg.IdentifyBehavior.AllowDeletedUsers() || e.G().Env.GetReadDeletedSigChain() {
1041		e.them.isDeleted = true
1042		return nil
1043	}
1044	return err
1045}
1046
1047func (e *Identify2WithUID) loadUsers(m libkb.MetaContext) (err error) {
1048	var loadMeErr, loadThemErr error
1049
1050	var selfLoad bool
1051	var wg sync.WaitGroup
1052
1053	if !e.arg.ActLoggedOut {
1054		loggedIn, myUID := isLoggedIn(m)
1055		if loggedIn {
1056			selfLoad = myUID.Equal(e.arg.Uid)
1057			wg.Add(1)
1058			go func() {
1059				loadMeErr = e.loadMe(m, myUID)
1060				wg.Done()
1061			}()
1062		}
1063	}
1064
1065	if !selfLoad {
1066		wg.Add(1)
1067		go func() {
1068			loadThemErr = e.loadThem(m)
1069			wg.Done()
1070		}()
1071	}
1072	wg.Wait()
1073
1074	if loadMeErr != nil {
1075		return loadMeErr
1076	}
1077	if loadThemErr != nil {
1078		return loadThemErr
1079	}
1080
1081	if selfLoad {
1082		e.them = e.me
1083	}
1084
1085	return nil
1086}
1087
1088func (e *Identify2WithUID) checkFastCacheHit(m libkb.MetaContext) (hit bool) {
1089	prfx := fmt.Sprintf("Identify2WithUID#checkFastCacheHit(%s)", e.arg.Uid)
1090	defer m.Trace(prfx, nil)()
1091	if e.getCache() == nil {
1092		return false
1093	}
1094
1095	fn := func(u keybase1.Identify2ResUPK2) keybase1.Time { return u.Upk.Uvv.CachedAt }
1096	dfn := func(u keybase1.Identify2ResUPK2) time.Duration {
1097		return libkb.Identify2CacheShortTimeout
1098	}
1099	u, err := e.getCache().Get(e.arg.Uid, fn, dfn, e.arg.IdentifyBehavior.WarningInsteadOfErrorOnBrokenTracks())
1100
1101	if err != nil {
1102		m.Debug("| fast cache error for %s: %s", e.arg.Uid, err)
1103	}
1104	if u == nil {
1105		m.Debug("| fast cache returning false on nil output")
1106		return false
1107	}
1108	e.cachedRes = u
1109	return true
1110}
1111
1112func (e *Identify2WithUID) dbKey(them keybase1.UID) libkb.DbKey {
1113	return libkb.DbKey{
1114		Typ: libkb.DBIdentify,
1115		Key: fmt.Sprintf("%s-%s", e.me.GetUID(), them),
1116	}
1117}
1118
1119func (e *Identify2WithUID) loadSlowCacheFromDB(m libkb.MetaContext) (ret *keybase1.Identify2ResUPK2) {
1120	defer m.Trace("Identify2WithUID#loadSlowCacheFromDB", nil)()
1121
1122	if e.getCache() != nil && !e.getCache().UseDiskCache() {
1123		m.Debug("| Disk cached disabled")
1124		return nil
1125	}
1126
1127	var ktm keybase1.Time
1128	key := e.dbKey(e.them.GetUID())
1129	found, err := e.G().LocalDb.GetInto(&ktm, key)
1130	if err != nil {
1131		m.Debug("| Error loading key %+v from cache: %s", key, err)
1132		return nil
1133	}
1134	if !found {
1135		m.Debug("| Key wasn't found: %+v", key)
1136		return nil
1137	}
1138	tm := ktm.Time()
1139	now := e.getNow(m)
1140	diff := now.Sub(tm)
1141	if diff > libkb.Identify2CacheLongTimeout {
1142		m.Debug("| Object timed out %s ago", diff)
1143		return nil
1144	}
1145	var tmp keybase1.Identify2ResUPK2
1146	upk2ai, err := e.them.ExportToUserPlusKeysV2AllIncarnations()
1147	if err != nil {
1148		m.Warning("| Failed to export: %s", err)
1149		return nil
1150	}
1151	tmp.Upk = *upk2ai
1152	tmp.IdentifiedAt = ktm
1153	ret = &tmp
1154	return ret
1155}
1156
1157// Store (meUID, themUID) -> SuccessfulIDTime as we cache users to the slow cache.
1158// Thus, after a cold boot, we don't start up with a cold identify cache.
1159func (e *Identify2WithUID) storeSlowCacheToDB(m libkb.MetaContext) (err error) {
1160	prfx := fmt.Sprintf("Identify2WithUID#storeSlowCacheToDB(%s)", e.them.GetUID())
1161	defer e.G().Trace(prfx, &err)()
1162	if e.me == nil {
1163		m.Debug("not storing to persistent slow cache since no me user")
1164		return nil
1165	}
1166
1167	key := e.dbKey(e.them.GetUID())
1168	now := keybase1.ToTime(e.getNow(m))
1169	err = e.G().LocalDb.PutObj(key, nil, now)
1170	return err
1171}
1172
1173// Remove (themUID) from the identify cache, if they're there.
1174func (e *Identify2WithUID) removeSlowCacheFromDB(m libkb.MetaContext) (err error) {
1175	prfx := fmt.Sprintf("Identify2WithUID#removeSlowCacheFromDB(%s)", e.them.GetUID())
1176	defer e.G().Trace(prfx, &err)()
1177	if e.me == nil {
1178		m.Debug("not removing from persistent slow cache since no me user")
1179		return nil
1180	}
1181	key := e.dbKey(e.them.GetUID())
1182	err = e.G().LocalDb.Delete(key)
1183	return err
1184}
1185
1186func (e *Identify2WithUID) checkSlowCacheHit(m libkb.MetaContext) (ret bool) {
1187	prfx := fmt.Sprintf("Identify2WithUID#checkSlowCacheHit(%s)", e.them.GetUID())
1188	defer m.Trace(prfx, nil)()
1189
1190	if e.getCache() == nil {
1191		return false
1192	}
1193
1194	if !e.allowCaching() {
1195		m.Debug("| missed fast cache: no caching allowed")
1196		return false
1197	}
1198
1199	timeFn := func(u keybase1.Identify2ResUPK2) keybase1.Time { return u.IdentifiedAt }
1200	durationFn := func(u keybase1.Identify2ResUPK2) time.Duration {
1201		if u.TrackBreaks != nil {
1202			return libkb.Identify2CacheBrokenTimeout
1203		}
1204		return libkb.Identify2CacheLongTimeout
1205	}
1206	u, err := e.getCache().Get(e.them.GetUID(), timeFn, durationFn, e.arg.IdentifyBehavior.WarningInsteadOfErrorOnBrokenTracks())
1207
1208	trackBrokenError := false
1209	if err != nil {
1210		m.Debug("| slow cache error for %s: %s", e.them.GetUID(), err)
1211		if _, ok := err.(libkb.TrackBrokenError); ok {
1212			trackBrokenError = true
1213		}
1214	}
1215
1216	if u == nil && e.me != nil && !trackBrokenError {
1217		u = e.loadSlowCacheFromDB(m)
1218	}
1219
1220	if u == nil {
1221		m.Debug("| %s: identify missed cache", prfx)
1222		return false
1223	}
1224
1225	if !e.them.IsCachedIdentifyFresh(&u.Upk) {
1226		m.Debug("| %s: cached identify was stale", prfx)
1227		return false
1228	}
1229
1230	e.cachedRes = u
1231
1232	// Update so that it hits the fast cache the next time
1233	u.Upk.Uvv.CachedAt = keybase1.ToTime(e.getNow(m))
1234	err = e.getCache().Insert(u)
1235	if err != nil {
1236		m.Debug("| error on insert: %+v", err)
1237	}
1238	return true
1239}
1240
1241// Result will return (non-nil,nil) on success, and (nil,non-nil) on failure.
1242func (e *Identify2WithUID) Result(m libkb.MetaContext) (*keybase1.Identify2ResUPK2, error) {
1243	if e.cachedRes != nil {
1244		return e.cachedRes, nil
1245	}
1246	res, err := e.exportToResult(m)
1247	if err != nil {
1248		return nil, err
1249	}
1250	if res == nil {
1251		return nil, libkb.UserNotFoundError{Msg: "identify2 unexpectly returned an empty user"}
1252	}
1253	return res, nil
1254}
1255
1256func (e *Identify2WithUID) GetProofSet() *libkb.ProofSet {
1257	return e.remotesReceived
1258}
1259
1260func (e *Identify2WithUID) GetIdentifyOutcome() *libkb.IdentifyOutcome {
1261	return e.state.Result()
1262}
1263
1264func (e *Identify2WithUID) toUserPlusKeysv2AllIncarnations() (*keybase1.UserPlusKeysV2AllIncarnations, error) {
1265	return e.them.ExportToUserPlusKeysV2AllIncarnations()
1266}
1267
1268func (e *Identify2WithUID) getCache() libkb.Identify2Cacher {
1269	if e.testArgs != nil && e.testArgs.cache != nil {
1270		return e.testArgs.cache
1271	}
1272	if e.testArgs != nil && e.testArgs.noCache {
1273		return nil
1274	}
1275	return e.G().Identify2Cache()
1276}
1277
1278func (e *Identify2WithUID) getTrackType() identify2TrackType {
1279	switch {
1280	case !e.useTracking || e.state.Result() == nil:
1281		return identify2NoTrack
1282	case e.state.Result().IsOK():
1283		return identify2TrackOK
1284	default:
1285		return identify2TrackBroke
1286	}
1287}
1288
1289func (e *Identify2WithUID) SetResponsibleGregorItem(item gregor.Item) {
1290	e.responsibleGregorItem = item
1291}
1292
1293func (e *Identify2WithUID) TrackToken() keybase1.TrackToken {
1294	return e.trackToken
1295}
1296
1297func (e *Identify2WithUID) ConfirmResult() keybase1.ConfirmResult {
1298	return e.confirmResult
1299}
1300
1301func (e *Identify2WithUID) FullMeUser() *libkb.User {
1302	if e.me == nil {
1303		return nil
1304	}
1305	return e.me.Full()
1306}
1307
1308func (e *Identify2WithUID) FullThemUser() *libkb.User {
1309	if e.them == nil {
1310		return nil
1311	}
1312	return e.them.Full()
1313}
1314
1315func (e *Identify2WithUID) maybeNotify(mctx libkb.MetaContext, explanation string) {
1316	target := e.arg.Uid
1317	if e.them != nil {
1318		target = e.them.GetUID()
1319	}
1320	if e.me == nil {
1321		// This check is needed because ActLoggedOut causes the untracked fast path
1322		// to succeed even when the true active user is tracking the identifyee.
1323		mctx.Debug("Identify2WithUID.maybeNotify(%v, %v) nope missing ME", target, explanation)
1324		return
1325	}
1326	if target.IsNil() {
1327		mctx.Debug("Identify2WithUID.maybeNotify(%v, %v) nope missing UID", target, explanation)
1328		return
1329	}
1330	if e.arg.IdentifyBehavior.WarningInsteadOfErrorOnBrokenTracks() {
1331		mctx.Debug("Identify2WithUID.maybeNotify(%v, %v) nope WarningInsteadOfErrorOnBrokenTracks", target, explanation)
1332		return
1333	}
1334	mctx.Debug("Identify2WithUID.maybeNotify(%v, %v) -> sending", target, explanation)
1335	go mctx.G().IdentifyDispatch.NotifyTrackingSuccess(mctx, target)
1336}
1337