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 }