github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/engine/track_token.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package engine
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  
    10  	"github.com/keybase/client/go/libkb"
    11  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    12  )
    13  
    14  // TrackToken is an engine.
    15  type TrackToken struct {
    16  	libkb.Contextified
    17  	arg                 *TrackTokenArg
    18  	them                *libkb.User
    19  	trackStatementBytes []byte
    20  	trackStatement      *libkb.ProofMetadataRes
    21  }
    22  
    23  type TrackTokenArg struct {
    24  	Token   keybase1.TrackToken
    25  	Me      *libkb.User
    26  	Options keybase1.TrackOptions
    27  	Outcome *libkb.IdentifyOutcome
    28  }
    29  
    30  // NewTrackToken creates a TrackToken engine.
    31  func NewTrackToken(g *libkb.GlobalContext, arg *TrackTokenArg) *TrackToken {
    32  	if arg.Options.SigVersion == nil || libkb.SigVersion(*arg.Options.SigVersion) == libkb.KeybaseNullSigVersion {
    33  		tmp := keybase1.SigVersion(libkb.GetDefaultSigVersion(g))
    34  		arg.Options.SigVersion = &tmp
    35  	}
    36  
    37  	return &TrackToken{
    38  		arg:          arg,
    39  		Contextified: libkb.NewContextified(g),
    40  	}
    41  }
    42  
    43  // Name is the unique engine name.
    44  func (e *TrackToken) Name() string {
    45  	return "TrackToken"
    46  }
    47  
    48  // GetPrereqs returns the engine prereqs.
    49  func (e *TrackToken) Prereqs() Prereqs {
    50  	return Prereqs{
    51  		Device: true,
    52  	}
    53  }
    54  
    55  // RequiredUIs returns the required UIs.
    56  func (e *TrackToken) RequiredUIs() []libkb.UIKind {
    57  	return []libkb.UIKind{}
    58  }
    59  
    60  // SubConsumers returns the other UI consumers for this engine.
    61  func (e *TrackToken) SubConsumers() []libkb.UIConsumer {
    62  	return nil
    63  }
    64  
    65  // Run starts the engine.
    66  func (e *TrackToken) Run(m libkb.MetaContext) (err error) {
    67  	defer m.Trace("TrackToken#Run", &err)()
    68  
    69  	if len(e.arg.Token) == 0 && e.arg.Outcome == nil {
    70  		err = fmt.Errorf("missing TrackToken argument")
    71  		return err
    72  	}
    73  	if err = e.loadMe(m); err != nil {
    74  		m.Info("loadme err: %s", err)
    75  		return err
    76  	}
    77  
    78  	// We can either pass this in directly, or look it up via TrackToken
    79  	outcome := e.arg.Outcome
    80  	if outcome == nil {
    81  		outcome, err = m.G().TrackCache().Get(e.arg.Token)
    82  		if err != nil {
    83  			return err
    84  		}
    85  	}
    86  
    87  	if outcome.TrackStatus() == keybase1.TrackStatus_UPDATE_OK && !e.arg.Options.ForceRetrack {
    88  		m.Debug("tracking statement up-to-date.")
    89  		return nil
    90  	}
    91  
    92  	if err = e.loadThem(m, outcome.Username); err != nil {
    93  		return err
    94  	}
    95  
    96  	if e.arg.Me.Equal(e.them) {
    97  		err = libkb.SelfTrackError{}
    98  		return err
    99  	}
   100  
   101  	if err = e.isTrackTokenStale(m, outcome); err != nil {
   102  		m.Debug("Track statement is stale")
   103  		return err
   104  	}
   105  
   106  	// need public signing key for track statement
   107  	signingKeyPub, err := e.arg.Me.SigningKeyPub()
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	e.trackStatement, err = e.arg.Me.TrackingProofFor(m, signingKeyPub, libkb.SigVersion(*e.arg.Options.SigVersion), e.them, outcome)
   113  	if err != nil {
   114  		m.Debug("tracking proof err: %s", err)
   115  		return err
   116  	}
   117  	if e.trackStatementBytes, err = e.trackStatement.J.Marshal(); err != nil {
   118  		return err
   119  	}
   120  
   121  	m.Debug("| Tracking statement: %s", string(e.trackStatementBytes))
   122  
   123  	if e.arg.Options.LocalOnly || e.arg.Options.ExpiringLocal {
   124  		m.Debug("| Local")
   125  		err = e.storeLocalTrack(m)
   126  	} else {
   127  		err = e.storeRemoteTrack(m, signingKeyPub.GetKID())
   128  		if err == nil {
   129  			// if the remote track succeeded, remove local tracks
   130  			// (this also removes any snoozes)
   131  			err := e.removeLocalTracks(m)
   132  			if err != nil {
   133  				return err
   134  			}
   135  		}
   136  	}
   137  	if err != nil {
   138  		return err
   139  	}
   140  	themUPAK, err := e.them.ExportToUPKV2AllIncarnations()
   141  	if err != nil {
   142  		return err
   143  	}
   144  	err = m.G().Pegboard.TrackUPAK(m, themUPAK.Current)
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	// Remove this after desktop notification change complete:
   150  	m.G().UserChanged(m.Ctx(), e.them.GetUID())
   151  
   152  	// Remove these after desktop notification change complete, but
   153  	// add in: m.G().BustLocalUserCache(e.arg.Me.GetUID())
   154  	m.G().UserChanged(m.Ctx(), e.arg.Me.GetUID())
   155  
   156  	// Keep these:
   157  	m.G().NotifyRouter.HandleTrackingChanged(e.arg.Me.GetUID(), e.arg.Me.GetNormalizedName(), false)
   158  	m.G().NotifyRouter.HandleTrackingChanged(e.them.GetUID(), e.them.GetNormalizedName(), true)
   159  
   160  	// Dismiss any associated gregor item.
   161  	if outcome.ResponsibleGregorItem != nil {
   162  		err = m.G().GregorState.DismissItem(m.Ctx(), nil,
   163  			outcome.ResponsibleGregorItem.Metadata().MsgID())
   164  	}
   165  
   166  	return err
   167  }
   168  
   169  func (e *TrackToken) isTrackTokenStale(m libkb.MetaContext, o *libkb.IdentifyOutcome) (err error) {
   170  	if idt := e.arg.Me.IDTable(); idt == nil {
   171  		return nil
   172  	} else if tm := idt.GetTrackMap(); tm == nil {
   173  		return nil
   174  	} else if v := tm[o.Username]; len(v) == 0 {
   175  		return nil
   176  	} else if lastTrack := v[len(v)-1]; lastTrack != nil && !lastTrack.IsRevoked() && o.TrackUsed == nil {
   177  		// If we had a valid track that we didn't use in the identification, then
   178  		// someone must have slipped in before us. Distinguish this case from the
   179  		// other case below for the purposes of testing, to make sure we hit
   180  		// both error cases in our tests.
   181  		return libkb.TrackStaleError{FirstTrack: true}
   182  	} else if o.TrackUsed == nil || lastTrack == nil {
   183  		return nil
   184  	} else if o.TrackUsed.GetTrackerSeqno() < lastTrack.GetSeqno() {
   185  		// Similarly, if there was a last track for this user that wasn't the
   186  		// one we were expecting, someone also must have intervened.
   187  		m.Debug("Stale track! We were at seqno %d, but %d is already in chain", o.TrackUsed.GetTrackerSeqno(), lastTrack.GetSeqno())
   188  		return libkb.TrackStaleError{FirstTrack: false}
   189  	}
   190  	return nil
   191  }
   192  
   193  func (e *TrackToken) loadMe(m libkb.MetaContext) error {
   194  	if e.arg.Me != nil {
   195  		return nil
   196  	}
   197  
   198  	me, err := libkb.LoadMe(libkb.NewLoadUserArgWithMetaContext(m))
   199  	if err != nil {
   200  		return err
   201  	}
   202  	e.arg.Me = me
   203  	return nil
   204  }
   205  
   206  func (e *TrackToken) loadThem(m libkb.MetaContext, username libkb.NormalizedUsername) error {
   207  
   208  	arg := libkb.NewLoadUserArgWithMetaContext(m).WithName(username.String()).WithPublicKeyOptional()
   209  	them, err := libkb.LoadUser(arg)
   210  	if err != nil {
   211  		return err
   212  	}
   213  	e.them = them
   214  	return nil
   215  }
   216  
   217  func (e *TrackToken) storeLocalTrack(m libkb.MetaContext) error {
   218  	return libkb.StoreLocalTrack(m, e.arg.Me.GetUID(), e.them.GetUID(), e.arg.Options.ExpiringLocal, e.trackStatement.J)
   219  }
   220  
   221  func (e *TrackToken) storeRemoteTrack(m libkb.MetaContext, pubKID keybase1.KID) (err error) {
   222  	defer m.Trace("TrackToken#StoreRemoteTrack", &err)()
   223  
   224  	// need unlocked signing key
   225  	me := e.arg.Me
   226  	ska := libkb.SecretKeyArg{
   227  		Me:      me,
   228  		KeyType: libkb.DeviceSigningKeyType,
   229  	}
   230  	arg := m.SecretKeyPromptArg(ska, "tracking signature")
   231  	signingKey, err := m.G().Keyrings.GetSecretKeyWithPrompt(m, arg)
   232  	if err != nil {
   233  		return err
   234  	}
   235  	if signingKey == nil {
   236  		return libkb.NoSecretKeyError{}
   237  	}
   238  	// double-check that the KID of the unlocked key matches
   239  	if signingKey.GetKID().NotEqual(pubKID) {
   240  		return errors.New("unexpeceted KID mismatch between locked and unlocked signing key")
   241  	}
   242  
   243  	sigVersion := libkb.SigVersion(*e.arg.Options.SigVersion)
   244  	sig, sigID, linkID, err := libkb.MakeSig(
   245  		m,
   246  		signingKey,
   247  		libkb.LinkTypeTrack,
   248  		e.trackStatementBytes,
   249  		libkb.SigHasRevokes(false),
   250  		keybase1.SeqType_PUBLIC,
   251  		libkb.SigIgnoreIfUnsupported(false),
   252  		me,
   253  		sigVersion,
   254  	)
   255  
   256  	if err != nil {
   257  		return err
   258  	}
   259  
   260  	httpsArgs := libkb.HTTPArgs{
   261  		"sig_id_base":  libkb.S{Val: sigID.StripSuffix().String()},
   262  		"sig_id_short": libkb.S{Val: sigID.ToShortID()},
   263  		"sig":          libkb.S{Val: sig},
   264  		"uid":          libkb.UIDArg(e.them.GetUID()),
   265  		"type":         libkb.S{Val: "track"},
   266  		"signing_kid":  signingKey.GetKID(),
   267  	}
   268  
   269  	if sigVersion == libkb.KeybaseSignatureV2 {
   270  		httpsArgs["sig_inner"] = libkb.S{Val: string(e.trackStatementBytes)}
   271  	}
   272  	_, err = m.G().API.Post(m, libkb.APIArg{
   273  		Endpoint:    "follow",
   274  		SessionType: libkb.APISessionTypeREQUIRED,
   275  		Args:        httpsArgs,
   276  	})
   277  	if err != nil {
   278  		m.Warning("api error: %s", err)
   279  		return err
   280  	}
   281  	if err = libkb.MerkleCheckPostedUserSig(m, me.GetUID(), e.trackStatement.Seqno, linkID); err != nil {
   282  		return err
   283  	}
   284  
   285  	me.SigChainBump(linkID, sigID, false)
   286  	m.G().IdentifyDispatch.NotifyTrackingSuccess(m, e.them.GetUID())
   287  
   288  	return err
   289  }
   290  
   291  func (e *TrackToken) removeLocalTracks(m libkb.MetaContext) (err error) {
   292  	defer m.Trace("removeLocalTracks", &err)()
   293  	err = libkb.RemoveLocalTracks(m, e.arg.Me.GetUID(), e.them.GetUID())
   294  	return err
   295  }