github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/teams/ftl.go (about) 1 package teams 2 3 import ( 4 "fmt" 5 "strings" 6 "sync" 7 "time" 8 9 "github.com/keybase/client/go/gregor" 10 "github.com/keybase/client/go/libkb" 11 "github.com/keybase/client/go/protocol/keybase1" 12 "github.com/keybase/client/go/sig3" 13 "github.com/keybase/client/go/teams/hidden" 14 storage "github.com/keybase/client/go/teams/storage" 15 ) 16 17 // 18 // ftl.go 19 // 20 // Fast Team chain Loader 21 // 22 // Routines for fast-team loading. In fast team loading, we ignore most signatures 23 // and use the Merkle Tree as a source of truth. This is good enough for getting 24 // directly into a chat, where it's not necessary to see a list of team members. 25 // 26 27 // FastTeamChainLoader loads teams using the "fast approach." It doesn't compute 28 // membership or check any signatures. It just checks for consistency against the merkle 29 // tree, and audits that the merkle tree is being faithfully constructed. 30 type FastTeamChainLoader struct { 31 32 // context for loading things from the outside world. 33 world LoaderContext 34 35 // single-flight lock on TeamID 36 locktab *libkb.LockTable 37 38 // Hold onto FastTeamLoad by-products as long as we have room, and store 39 // them persistently to disk. 40 storage *storage.FTLStorage 41 42 // We can get pushed by the server into "force repoll" mode, in which we're 43 // not getting cache invalidations. An example: when Coyne or Nojima revokes 44 // a device. We want to cut down on notification spam. So instead, all attempts 45 // to load a team result in a preliminary poll for freshness, which this state is enabled. 46 forceRepollMutex sync.RWMutex 47 forceRepollUntil gregor.TimeOrOffset 48 } 49 50 const FTLVersion = 1 51 52 // NewFastLoader makes a new fast loader and initializes it. 53 func NewFastTeamLoader(g *libkb.GlobalContext) *FastTeamChainLoader { 54 ret := &FastTeamChainLoader{ 55 world: NewLoaderContextFromG(g), 56 locktab: libkb.NewLockTable(), 57 } 58 ret.storage = storage.NewFTLStorage(g, ret.upgradeStoredState) 59 return ret 60 } 61 62 // NewFastTeamLoaderAndInstall creates a new loader and installs it into G. 63 func NewFastTeamLoaderAndInstall(g *libkb.GlobalContext) *FastTeamChainLoader { 64 l := NewFastTeamLoader(g) 65 g.SetFastTeamLoader(l) 66 g.AddLogoutHook(l, "fastTeamLoader") 67 g.AddDbNukeHook(l, "fastTeamLoader") 68 return l 69 } 70 71 var _ libkb.FastTeamLoader = (*FastTeamChainLoader)(nil) 72 73 func ftlLogTag(m libkb.MetaContext) libkb.MetaContext { 74 return m.WithLogTag("FTL") 75 } 76 77 func FTL(m libkb.MetaContext, arg keybase1.FastTeamLoadArg) (res keybase1.FastTeamLoadRes, err error) { 78 return m.G().GetFastTeamLoader().Load(m, arg) 79 } 80 81 type ftlCombinedData struct { 82 visible *keybase1.FastTeamData 83 hidden *keybase1.HiddenTeamChain 84 } 85 86 func newFTLCombinedData(v *keybase1.FastTeamData, h *keybase1.HiddenTeamChain) ftlCombinedData { 87 return ftlCombinedData{visible: v, hidden: h} 88 } 89 90 func (f ftlCombinedData) latestKeyGeneration() keybase1.PerTeamKeyGeneration { 91 ret := f.visible.LatestKeyGeneration 92 g := f.hidden.MaxReaderPerTeamKeyGeneration() 93 if ret < g { 94 ret = g 95 } 96 return ret 97 } 98 99 func (f ftlCombinedData) perTeamKey(g keybase1.PerTeamKeyGeneration) *keybase1.PerTeamKey { 100 ret, ok := f.visible.Chain.PerTeamKeys[g] 101 if ok { 102 return &ret 103 } 104 ret, ok = f.hidden.GetReaderPerTeamKeyAtGeneration(g) 105 if ok { 106 return &ret 107 } 108 return nil 109 } 110 111 // Load fast-loads the given team. Provide some hints as to how to load it. You can specify an application 112 // and key generations needed, if you are entering chat. Those links will be returned unstubbed 113 // from the server, and then the keys can be output in the result. 114 func (f *FastTeamChainLoader) Load(m libkb.MetaContext, arg keybase1.FastTeamLoadArg) (res keybase1.FastTeamLoadRes, err error) { 115 m = ftlLogTag(m) 116 defer m.Trace(fmt.Sprintf("FastTeamChainLoader#Load(%+v)", arg), &err)() 117 originalArg := arg.DeepCopy() 118 119 res, err = f.loadOneAttempt(m, arg) 120 if err != nil { 121 return res, err 122 } 123 124 if arg.AssertTeamName != nil && !arg.AssertTeamName.Eq(res.Name) { 125 m.Debug("Did not get expected subteam name; will reattempt with forceRefresh (%s != %s)", arg.AssertTeamName.String(), res.Name.String()) 126 arg.ForceRefresh = true 127 res, err = f.loadOneAttempt(m, arg) 128 if err != nil { 129 return res, err 130 } 131 if !arg.AssertTeamName.Eq(res.Name) { 132 return res, NewBadNameError(fmt.Sprintf("After force-refresh, still bad team name: wanted %s, but got %s", arg.AssertTeamName.String(), res.Name.String())) 133 } 134 } 135 136 if ShouldRunBoxAudit(m) { 137 newM, shouldReload := VerifyBoxAudit(m, res.Name.ToTeamID(arg.Public)) 138 if shouldReload { 139 return f.Load(newM, originalArg) 140 } 141 } else { 142 m.Debug("Box auditor feature flagged off; not checking jail during ftl team load...") 143 } 144 145 return res, nil 146 } 147 148 // VerifyTeamName verifies that the given ID aligns with the given name, using the Merkle tree only 149 // (and not verifying sigs along the way). 150 func (f *FastTeamChainLoader) VerifyTeamName(m libkb.MetaContext, id keybase1.TeamID, name keybase1.TeamName, forceRefresh bool) (err error) { 151 m = m.WithLogTag("FTL") 152 defer m.Trace(fmt.Sprintf("FastTeamChainLoader#VerifyTeamName(%v,%s)", id, name.String()), &err)() 153 _, err = f.Load(m, keybase1.FastTeamLoadArg{ 154 ID: id, 155 Public: id.IsPublic(), 156 AssertTeamName: &name, 157 ForceRefresh: forceRefresh, 158 HiddenChainIsOptional: true, 159 }) 160 return err 161 } 162 163 func (f *FastTeamChainLoader) loadOneAttempt(m libkb.MetaContext, arg keybase1.FastTeamLoadArg) (res keybase1.FastTeamLoadRes, err error) { 164 165 if arg.ID.IsPublic() != arg.Public { 166 return res, NewBadPublicError(arg.ID, arg.Public) 167 } 168 169 flr, err := f.load(m, fastLoadArg{FastTeamLoadArg: arg}) 170 if err != nil { 171 return res, err 172 } 173 174 res.ApplicationKeys = flr.applicationKeys 175 res.Name, err = f.verifyTeamNameViaParentLoad(m, arg.ID, arg.Public, flr.unverifiedName, flr.upPointer, arg.ID, arg.ForceRefresh) 176 if err != nil { 177 return res, err 178 } 179 180 return res, nil 181 } 182 183 // verifyTeamNameViaParentLoad takes a team ID, and a pointer to a parent team's sigchain, and computes 184 // the full resolved team name. If the pointer is null, we'll assume this is a root team and do the 185 // verification via hash-comparison. 186 func (f *FastTeamChainLoader) verifyTeamNameViaParentLoad(m libkb.MetaContext, id keybase1.TeamID, isPublic bool, unverifiedName keybase1.TeamName, parent *keybase1.UpPointer, bottomSubteam keybase1.TeamID, forceRefresh bool) (res keybase1.TeamName, err error) { 187 188 if parent == nil { 189 if !unverifiedName.IsRootTeam() { 190 return res, NewBadNameError("expected a root team") 191 } 192 if !unverifiedName.ToTeamID(isPublic).Eq(id) { 193 return res, NewBadNameError("root team v. team ID mismatch") 194 } 195 return unverifiedName, nil 196 } 197 198 if parent.ParentID.IsPublic() != isPublic { 199 return res, NewBadPublicError(parent.ParentID, isPublic) 200 } 201 202 parentRes, err := f.load(m, fastLoadArg{ 203 FastTeamLoadArg: keybase1.FastTeamLoadArg{ 204 ID: parent.ParentID, 205 Public: isPublic, 206 ForceRefresh: forceRefresh, 207 HiddenChainIsOptional: true, // we do not need to see the hidden chain for the parent 208 }, 209 downPointersNeeded: []keybase1.Seqno{parent.ParentSeqno}, 210 needLatestName: true, 211 readSubteamID: bottomSubteam, 212 }) 213 if err != nil { 214 return res, err 215 } 216 downPointer, ok := parentRes.downPointers[parent.ParentSeqno] 217 if !ok { 218 return res, NewBadNameError("down pointer not found in parent") 219 } 220 suffix := downPointer.NameComponent 221 222 parentName, err := f.verifyTeamNameViaParentLoad(m, parent.ParentID, isPublic, parentRes.unverifiedName, parentRes.upPointer, bottomSubteam, forceRefresh) 223 if err != nil { 224 return res, err 225 } 226 227 return parentName.Append(suffix) 228 } 229 230 // fastLoadRes is used internally to convey the results of the #load() call. 231 type fastLoadRes struct { 232 applicationKeys []keybase1.TeamApplicationKey 233 unverifiedName keybase1.TeamName 234 downPointers map[keybase1.Seqno]keybase1.DownPointer 235 upPointer *keybase1.UpPointer 236 } 237 238 // fastLoadArg is used internally to pass arguments to the #load() call. It is a small wrapper 239 // around the keybase1.FastTeamLoadArg that's passed through to the public #Load() call. 240 type fastLoadArg struct { 241 keybase1.FastTeamLoadArg 242 downPointersNeeded []keybase1.Seqno 243 readSubteamID keybase1.TeamID 244 needLatestName bool 245 forceReset bool 246 } 247 248 // needChainTail returns true if the argument mandates that we need a reasonably up-to-date chain tail, 249 // let's say to figure out what this team is currently named, or to figure out the most recent 250 // encryption key to encrypt new messages for. 251 func (a fastLoadArg) needChainTail() bool { 252 return a.needLatestName || a.NeedLatestKey 253 } 254 255 // load acquires a lock by team ID, and the runs loadLockedWithRetries. 256 func (f *FastTeamChainLoader) load(m libkb.MetaContext, arg fastLoadArg) (res *fastLoadRes, err error) { 257 defer m.Trace(fmt.Sprintf("FastTeamChainLoader#load(%+v)", arg), &err)() 258 259 // Single-flight lock by team ID. 260 lock := f.locktab.AcquireOnName(m.Ctx(), m.G(), arg.ID.String()) 261 defer lock.Release(m.Ctx()) 262 263 res, err = f.loadLockedWithRetries(m, arg) 264 if hidden.ShouldClearSupportFlagOnError(err) { 265 m.Debug("Clearing support hidden chain flag for team %s because of error %v in FTL", arg.ID, err) 266 m.G().GetHiddenTeamChainManager().ClearSupportFlagIfFalse(m, arg.ID) 267 } 268 return res, err 269 } 270 271 // loadLockedWithRetries attempts two loads of the team. If the first iteration returns an FTLMissingSeedError, 272 // we'll blast through the cache and attempt a full reload a second time. Then that's for all the marbles. 273 func (f *FastTeamChainLoader) loadLockedWithRetries(m libkb.MetaContext, arg fastLoadArg) (res *fastLoadRes, err error) { 274 275 for i := 0; i < 2; i++ { 276 res, err = f.loadLocked(m, arg) 277 if err == nil { 278 return res, err 279 } 280 if _, ok := err.(FTLMissingSeedError); !ok { 281 return nil, err 282 } 283 m.Debug("Got retriable error %s; will force reset", err) 284 arg.forceReset = true 285 } 286 return res, err 287 } 288 289 // dervieSeedAtGeneration either goes to cache or rederives the PTK private seed 290 // for the given generation gen. 291 func (f *FastTeamChainLoader) deriveSeedAtGeneration(m libkb.MetaContext, gen keybase1.PerTeamKeyGeneration, dat ftlCombinedData) (seed keybase1.PerTeamKeySeed, err error) { 292 293 state := dat.visible 294 295 seed, ok := state.Chain.PerTeamKeySeedsVerified[gen] 296 if ok { 297 return seed, nil 298 } 299 300 var tmp keybase1.PerTeamKeySeed 301 tmp, ok = state.PerTeamKeySeedsUnverified[gen] 302 if !ok { 303 // See CORE-9207. We can hit this case if we previously loaded a parent team for verifying a subteam 304 // name before we were members of the team, and then later get added to the team, and then try to 305 // reload the team. We didn't have boxes from the first time around, so just force a full reload. 306 // It's inefficient but it's a very rare case. 307 return seed, NewFTLMissingSeedError(gen) 308 } 309 310 ptkChain := dat.perTeamKey(gen) 311 if ptkChain == nil { 312 return seed, NewFastLoadError(fmt.Sprintf("no per team key public halves at generation %d", gen)) 313 } 314 315 check, ok := state.SeedChecks[gen] 316 if !ok { 317 return seed, NewFastLoadError(fmt.Sprintf("no per team key seed check at %d", gen)) 318 } 319 320 km, err := NewTeamKeyManagerWithSecret(state.ID(), tmp, gen, &check) 321 if err != nil { 322 return seed, err 323 } 324 325 sigKey, err := km.SigningKey() 326 if err != nil { 327 return seed, err 328 } 329 330 if !ptkChain.SigKID.SecureEqual(sigKey.GetKID()) { 331 m.Debug("sig KID gen:%v (local) %v != %v (chain)", gen, sigKey.GetKID(), ptkChain.SigKID) 332 return seed, NewFastLoadError(fmt.Sprintf("wrong team key (sig) found at generation %v", gen)) 333 } 334 335 encKey, err := km.EncryptionKey() 336 if err != nil { 337 return seed, err 338 } 339 340 if !ptkChain.EncKID.SecureEqual(encKey.GetKID()) { 341 m.Debug("enc KID gen:%v (local) %v != %v (chain)", gen, encKey.GetKID(), ptkChain.EncKID) 342 return seed, NewFastLoadError(fmt.Sprintf("wrong team key (enc) found at generation %v", gen)) 343 } 344 345 // write back to cache 346 seed = tmp 347 state.Chain.PerTeamKeySeedsVerified[gen] = seed 348 return seed, err 349 } 350 351 // deriveKeyForApplicationAtGeneration pulls from cache or generates the PTK for the 352 // given application at the given generation. 353 func (f *FastTeamChainLoader) deriveKeyForApplicationAtGeneration(m libkb.MetaContext, app keybase1.TeamApplication, gen keybase1.PerTeamKeyGeneration, dat ftlCombinedData) (key keybase1.TeamApplicationKey, err error) { 354 355 seed, err := f.deriveSeedAtGeneration(m, gen, dat) 356 if err != nil { 357 return key, err 358 } 359 360 var mask *keybase1.MaskB64 361 if m := dat.visible.ReaderKeyMasks[app]; m != nil { 362 tmp, ok := m[gen] 363 if ok { 364 mask = &tmp 365 } 366 } 367 if mask == nil { 368 m.Debug("Could not get reader key mask for <%s,%d>", app, gen) 369 if dat.visible.ID().IsSubTeam() { 370 m.Debug("guessing lack of RKM is due to not being an explicit member of the subteam") 371 return key, NewNotExplicitMemberOfSubteamError() 372 } 373 return key, NewFastLoadError("Could not load application keys") 374 } 375 376 rkm := keybase1.ReaderKeyMask{ 377 Application: app, 378 Generation: gen, 379 Mask: *mask, 380 } 381 return applicationKeyForMask(rkm, seed) 382 } 383 384 // deriveKeysForApplication pulls from cache or generates several geneartions of PTKs 385 // for the given application. 386 func (f *FastTeamChainLoader) deriveKeysForApplication(m libkb.MetaContext, app keybase1.TeamApplication, arg fastLoadArg, dat ftlCombinedData) (keys []keybase1.TeamApplicationKey, err error) { 387 388 latestGen := dat.latestKeyGeneration() 389 390 var didLatest bool 391 doKey := func(gen keybase1.PerTeamKeyGeneration) error { 392 var key keybase1.TeamApplicationKey 393 key, err = f.deriveKeyForApplicationAtGeneration(m, app, gen, dat) 394 if err != nil { 395 return err 396 } 397 keys = append(keys, key) 398 if gen == latestGen { 399 didLatest = true 400 } 401 return nil 402 } 403 404 if arg.NeedLatestKey { 405 // This debug is useful to have since it will spell out which version is the latest in the log 406 // if the caller asked for latest. 407 m.Debug("FastTeamChainLoader#deriveKeysForApplication: sending back latest at key generation %d", latestGen) 408 } 409 410 for _, gen := range arg.KeyGenerationsNeeded { 411 if err = doKey(gen); err != nil { 412 return nil, err 413 } 414 } 415 if !didLatest && arg.NeedLatestKey { 416 if err = doKey(latestGen); err != nil { 417 return nil, err 418 } 419 } 420 return keys, nil 421 } 422 423 // deriveKeys pulls from cache or generates PTKs for an set of (application X generations) 424 // pairs, for all in the cartesian product. 425 func (f *FastTeamChainLoader) deriveKeys(m libkb.MetaContext, arg fastLoadArg, dat ftlCombinedData) (keys []keybase1.TeamApplicationKey, err error) { 426 for _, app := range arg.Applications { 427 var tmp []keybase1.TeamApplicationKey 428 tmp, err = f.deriveKeysForApplication(m, app, arg, dat) 429 if err != nil { 430 return nil, err 431 } 432 keys = append(keys, tmp...) 433 } 434 return keys, nil 435 } 436 437 // toResult turns the current fast state into a fastLoadRes. 438 func (f *FastTeamChainLoader) toResult(m libkb.MetaContext, arg fastLoadArg, dat ftlCombinedData) (res *fastLoadRes, err error) { 439 res = &fastLoadRes{ 440 unverifiedName: dat.visible.Name, 441 downPointers: dat.visible.Chain.DownPointers, 442 upPointer: dat.visible.Chain.LastUpPointer, 443 } 444 res.applicationKeys, err = f.deriveKeys(m, arg, dat) 445 if err != nil { 446 return nil, err 447 } 448 return res, nil 449 } 450 451 // findState in cache finds the team ID's state in an in-memory cache. 452 func (f *FastTeamChainLoader) findStateInCache(m libkb.MetaContext, id keybase1.TeamID) (data *keybase1.FastTeamData, frozen bool, tombstoned bool) { 453 return f.storage.Get(m, id, id.IsPublic()) 454 } 455 456 // stateHasKeySeed returns true/false if the state has the seed material for the given 457 // generation. Either the fully verified PTK seed, or the public portion and 458 // unverified PTK seed. 459 func stateHasKeySeed(m libkb.MetaContext, gen keybase1.PerTeamKeyGeneration, state *keybase1.FastTeamData) bool { 460 _, foundVerified := state.Chain.PerTeamKeySeedsVerified[gen] 461 if foundVerified { 462 return true 463 } 464 _, foundUnverifiedSeed := state.PerTeamKeySeedsUnverified[gen] 465 if !foundUnverifiedSeed { 466 return false 467 } 468 _, foundPerTeamKey := state.Chain.PerTeamKeys[gen] 469 return foundPerTeamKey 470 } 471 472 // stateHasKeys checks to see if the given state has the keys specified in the shopping list. If not, it will 473 // modify the shopping list and return false. If yes, it will leave the shopping list unchanged and return 474 // true. 475 func stateHasKeys(m libkb.MetaContext, shoppingList *shoppingList, arg fastLoadArg, data ftlCombinedData) (fresh bool) { 476 gens := make(map[keybase1.PerTeamKeyGeneration]struct{}) 477 state := data.visible 478 479 fresh = true 480 481 if arg.NeedLatestKey && !state.LoadedLatest { 482 m.Debug("latest was never loaded, we need to load it") 483 shoppingList.needMerkleRefresh = true 484 shoppingList.needLatestKey = true 485 fresh = false 486 } 487 488 // The key generations needed are the ones passed in, and also, potentially, our cached 489 // LatestKeyGeneration from the state. It could be that when we go to the server, this is no 490 // longer the LatestKeyGeneration, but it might be. It depends. But in either case, we should 491 // pull down the mask, since it's a bug to not have it if it turns out the server refresh 492 // didn't budge the latest key generation. 493 kgn := append([]keybase1.PerTeamKeyGeneration{}, arg.KeyGenerationsNeeded...) 494 latestKeyGeneration := data.latestKeyGeneration() 495 if arg.NeedLatestKey && state.LoadedLatest && latestKeyGeneration > 0 { 496 kgn = append(kgn, latestKeyGeneration) 497 } 498 499 for _, app := range arg.Applications { 500 for _, gen := range kgn { 501 add := false 502 if state.ReaderKeyMasks[app] == nil || state.ReaderKeyMasks[app][gen] == nil { 503 m.Debug("state doesn't have mask for <%d,%d>", app, gen) 504 add = true 505 } 506 if !stateHasKeySeed(m, gen, state) { 507 m.Debug("state doesn't have key seed for gen=%d", gen) 508 add = true 509 } 510 if add { 511 gens[gen] = struct{}{} 512 fresh = false 513 } 514 } 515 } 516 517 shoppingList.applications = append([]keybase1.TeamApplication{}, arg.Applications...) 518 519 if !fresh { 520 for gen := range gens { 521 shoppingList.generations = append(shoppingList.generations, gen) 522 } 523 } 524 525 // Let's just get all keys from the past, so figure out the minimal seed value that we have. 526 shoppingList.seedLow = computeSeedLow(state) 527 528 return fresh 529 } 530 531 // stateHasDownPointers checks to see if the given state has the down pointers specified in the shopping list. 532 // If not, it will change the shopping list to have the down pointers and return false. If yes, it will 533 // leave the shopping list unchanged and return true. 534 func stateHasDownPointers(m libkb.MetaContext, shoppingList *shoppingList, arg fastLoadArg, state *keybase1.FastTeamData) (ret bool) { 535 ret = true 536 537 for _, seqno := range arg.downPointersNeeded { 538 if _, ok := state.Chain.DownPointers[seqno]; !ok { 539 m.Debug("Down pointer at seqno=%d wasn't found", seqno) 540 shoppingList.addDownPointer(seqno) 541 ret = false 542 } 543 } 544 return ret 545 } 546 547 // computeSeedLow computes the value for ftl_seed_low that we're going to send up to the server for fetches. 548 func computeSeedLow(state *keybase1.FastTeamData) keybase1.PerTeamKeyGeneration { 549 if state.MaxContinuousPTKGeneration > 0 { 550 return state.MaxContinuousPTKGeneration 551 } 552 var ret keybase1.PerTeamKeyGeneration 553 for i := keybase1.PerTeamKeyGeneration(1); i <= state.LatestKeyGeneration; i++ { 554 _, found := state.PerTeamKeySeedsUnverified[i] 555 if !found { 556 break 557 } 558 ret = i 559 } 560 state.MaxContinuousPTKGeneration = ret 561 return ret 562 } 563 564 // shoppingList is a list of what we need from the server. 565 type shoppingList struct { 566 needMerkleRefresh bool // if we need to refresh the Merkle path for this team 567 needLatestKey bool // true if we never loaded the latest mask, and need to do it 568 569 // links *and* PTKs newer than the given seqno. And RKMs for 570 // the given apps. 571 linksSince keybase1.Seqno 572 downPointers []keybase1.Seqno 573 574 // The applications we care about. 575 applications []keybase1.TeamApplication 576 577 // The generations we care about. We'll always get back the most recent RKMs 578 // if we send a needMerkleRefresh. 579 seedLow keybase1.PerTeamKeyGeneration 580 generations []keybase1.PerTeamKeyGeneration 581 582 // the last hidden link we got, in case we need to download more 583 hiddenLinksSince keybase1.Seqno 584 } 585 586 // groceries are what we get back from the server. 587 type groceries struct { 588 newLinks []*ChainLinkUnpacked 589 rkms []keybase1.ReaderKeyMask 590 latestKeyGen keybase1.PerTeamKeyGeneration 591 seeds []keybase1.PerTeamKeySeed 592 newHiddenLinks []sig3.ExportJSON 593 expMaxHiddenSeqno keybase1.Seqno 594 lastMerkleRoot *libkb.MerkleRoot 595 } 596 597 // isEmpty returns true if our shopping list is empty. In this case, we have no need to go to the 598 // server (store), and can just return with what's in our cache. 599 func (s shoppingList) isEmpty() bool { 600 return !s.needMerkleRefresh && len(s.generations) == 0 && len(s.downPointers) == 0 601 } 602 603 // onlyNeedsRefresh will be true if we only are going to the server for a refresh, 604 // say when encrypting for the latest key version. If the merkle tree says we're up to date, 605 // we can skip the team/get call. 606 func (s shoppingList) onlyNeedsRefresh() bool { 607 return s.needMerkleRefresh && !s.needLatestKey && len(s.generations) == 0 && len(s.downPointers) == 0 608 } 609 610 // addDownPointer adds a down pointer to our shopping list. If we need to read naming information 611 // out of a parent team, we'll add the corresponding sequence number here. The we expect the 612 // payload JSON for the corrsponding seqno -- that we already have the wrapper chainlink v2 613 // that contains the hash of this payload JSON. 614 func (s *shoppingList) addDownPointer(seqno keybase1.Seqno) { 615 s.downPointers = append(s.downPointers, seqno) 616 } 617 618 // computeWithPreviousState looks into the given load arg, and also our current cached state, to figure 619 // what to get from the server. The results are compiled into a "shopping list" that we'll later 620 // use when we concoct our server request. 621 func (f *FastTeamChainLoader) computeWithPreviousState(m libkb.MetaContext, s *shoppingList, arg fastLoadArg, data ftlCombinedData) { 622 state := data.visible 623 cachedAt := state.CachedAt.Time() 624 s.linksSince = state.Chain.Last.Seqno 625 626 if arg.forceReset { 627 s.linksSince = keybase1.Seqno(0) 628 m.Debug("forceReset specified, so reloading from low=0") 629 } 630 631 if arg.needChainTail() && m.G().Clock().Now().Sub(cachedAt) > time.Hour { 632 m.Debug("cached value is more than an hour old (cached at %s)", cachedAt) 633 s.needMerkleRefresh = true 634 } 635 if arg.needChainTail() && state.LatestSeqnoHint > state.Chain.Last.Seqno { 636 m.Debug("cached value is stale: seqno %d > %d", state.LatestSeqnoHint, state.Chain.Last.Seqno) 637 s.needMerkleRefresh = true 638 } 639 if arg.needChainTail() && data.hidden.IsStale() { 640 m.Debug("HiddenTeamChain was stale, forcing refresh") 641 s.needMerkleRefresh = true 642 } 643 if arg.ForceRefresh { 644 m.Debug("refresh forced via flag") 645 s.needMerkleRefresh = true 646 } 647 if !s.needMerkleRefresh && f.InForceRepollMode(m) { 648 m.Debug("must repoll since in force mode") 649 s.needMerkleRefresh = true 650 } 651 if !stateHasKeys(m, s, arg, data) { 652 m.Debug("state was missing needed encryption keys, or we need the freshest") 653 } 654 if !stateHasDownPointers(m, s, arg, state) { 655 m.Debug("state was missing unstubbed links") 656 } 657 } 658 659 // computeFreshLoad computes a shopping list from a fresh load of the state. 660 func (s *shoppingList) computeFreshLoad(m libkb.MetaContext, arg fastLoadArg) { 661 s.needMerkleRefresh = true 662 s.applications = append([]keybase1.TeamApplication{}, arg.Applications...) 663 s.downPointers = append([]keybase1.Seqno{}, arg.downPointersNeeded...) 664 s.generations = append([]keybase1.PerTeamKeyGeneration{}, arg.KeyGenerationsNeeded...) 665 } 666 667 func (s *shoppingList) addHiddenLow(hp *hidden.LoaderPackage) { 668 s.hiddenLinksSince = hp.LastSeqno() 669 } 670 671 // applicationsToString converts the list of applications to a comma-separated string. 672 func applicationsToString(applications []keybase1.TeamApplication) string { 673 var tmp []string 674 for _, k := range applications { 675 tmp = append(tmp, fmt.Sprintf("%d", int(k))) 676 } 677 return strings.Join(tmp, ",") 678 } 679 680 // generationsToString converts the list of generations to a comma-separated string. 681 func generationsToString(generations []keybase1.PerTeamKeyGeneration) string { 682 var tmp []string 683 for _, k := range generations { 684 tmp = append(tmp, fmt.Sprintf("%d", int(k))) 685 } 686 return strings.Join(tmp, ",") 687 } 688 689 // toHTTPArgs turns our shopping list into what we need from the server. Here is what we need: 690 // all stubs since `low`, which might be 0, in which case all `stubs`. The first link we 691 // get back must be unstubbed. The last "up pointer" must be unstubbed. Any link in `seqnos` 692 // must be returned unstubbed, and might be in the sequence *before* `low`. We specify 693 // key generations and applications, and need reader key masks for all applications 694 // in the (apps X gens) cartesian product. 695 func (a fastLoadArg) toHTTPArgs(m libkb.MetaContext, s shoppingList, hp *hidden.LoaderPackage) libkb.HTTPArgs { 696 ret := libkb.HTTPArgs{ 697 "id": libkb.S{Val: a.ID.String()}, 698 "public": libkb.B{Val: a.Public}, 699 "ftl": libkb.B{Val: true}, 700 "ftl_low": libkb.I{Val: int(s.linksSince)}, 701 "ftl_seqnos": libkb.S{Val: seqnosToString(s.downPointers)}, 702 "ftl_key_generations": libkb.S{Val: generationsToString(s.generations)}, 703 "ftl_version": libkb.I{Val: FTLVersion}, 704 "ftl_seed_low": libkb.I{Val: int(s.seedLow)}, 705 } 706 if len(s.applications) > 0 { 707 ret["ftl_include_applications"] = libkb.S{Val: applicationsToString(s.applications)} 708 } 709 if a.NeedLatestKey { 710 ret["ftl_n_newest_key_generations"] = libkb.I{Val: int(3)} 711 } 712 if !a.readSubteamID.IsNil() { 713 ret["read_subteam_id"] = libkb.S{Val: a.readSubteamID.String()} 714 } 715 716 if hp.HiddenChainDataEnabled() { 717 ret["ftl_hidden_low"] = libkb.I{Val: int(s.hiddenLinksSince)} 718 } 719 return ret 720 } 721 722 // loadFromServerWithRetries loads the leaf in the merkle tree and then fetches from team/get.json the links 723 // needed for the team chain. There is a race possible, when a link is added between the two. In that 724 // case, refetch in a loop until we match up. It will retry in the case of GreenLinkErrors. If 725 // the given state was fresh already, then we'll return a nil groceries. 726 func (f *FastTeamChainLoader) loadFromServerWithRetries(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, shoppingList shoppingList, hp *hidden.LoaderPackage) (groceries *groceries, err error) { 727 728 defer m.Trace(fmt.Sprintf("FastTeamChainLoader#loadFromServerWithRetries(%s,%v)", arg.ID, arg.Public), &err)() 729 730 const nRetries = 3 731 for i := 0; i < nRetries; i++ { 732 groceries, err = f.loadFromServerOnce(m, arg, state, shoppingList, hp) 733 switch err.(type) { 734 case nil: 735 return groceries, nil 736 case GreenLinkError: 737 m.Debug("FastTeamChainLoader retrying after green link") 738 continue 739 default: 740 return nil, err 741 } 742 } 743 return nil, err 744 } 745 746 // makeHTTPRequest hits the HTTP GET endpoint for the team data. 747 func (f *FastTeamChainLoader) makeHTTPRequest(m libkb.MetaContext, args libkb.HTTPArgs, isPublic bool) (t rawTeam, err error) { 748 apiArg := libkb.NewAPIArg("team/get") 749 apiArg.Args = args 750 if isPublic { 751 apiArg.SessionType = libkb.APISessionTypeOPTIONAL 752 } else { 753 apiArg.SessionType = libkb.APISessionTypeREQUIRED 754 } 755 err = m.G().API.GetDecode(m, apiArg, &t) 756 if err != nil { 757 return t, err 758 } 759 return t, nil 760 } 761 762 func (f *FastTeamChainLoader) checkHiddenResp(m libkb.MetaContext, arg fastLoadArg, hiddenResp *libkb.MerkleHiddenResponse, hp *hidden.LoaderPackage) (hiddenIsFresh bool, err error) { 763 if !arg.HiddenChainIsOptional && hiddenResp.RespType == libkb.MerkleHiddenResponseTypeNONE { 764 return hiddenIsFresh, libkb.NewHiddenChainDataMissingError("the server did not return the necessary hidden chain data") 765 } 766 767 if hiddenResp.RespType != libkb.MerkleHiddenResponseTypeFLAGOFF && hiddenResp.RespType != libkb.MerkleHiddenResponseTypeNONE { 768 hiddenIsFresh, err = hp.CheckHiddenMerklePathResponseAndAddRatchets(m, hiddenResp) 769 if err != nil { 770 return hiddenIsFresh, err 771 } 772 } else { 773 hiddenIsFresh = true 774 } 775 776 return hiddenIsFresh, nil 777 } 778 779 // loadFromServerOnce turns the giving "shoppingList" into requests for the server, and then makes 780 // an HTTP GET to fetch the corresponding "groceries." Once retrieved, we unpack links, and 781 // check for "green" links --- those that might have been added to the team after the merkle update 782 // we previously read. If we find a green link, we retry in our caller. Otherwise, we also do the 783 // key decryption here, decrypting the most recent generation, and all prevs we haven't previously 784 // decrypted. 785 func (f *FastTeamChainLoader) loadFromServerOnce(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, shoppingList shoppingList, hp *hidden.LoaderPackage) (ret *groceries, err error) { 786 787 defer m.Trace("FastTeamChainLoader#loadFromServerOnce", &err)() 788 789 var teamUpdate rawTeam 790 var links []*ChainLinkUnpacked 791 var lastSecretGen keybase1.PerTeamKeyGeneration 792 var seeds []keybase1.PerTeamKeySeed 793 794 lastSeqno, lastLinkID, hiddenResp, lastMerkleRoot, err := f.world.merkleLookupWithHidden(m.Ctx(), arg.ID, arg.Public) 795 if err != nil { 796 return nil, err 797 } 798 799 hiddenIsFresh, err := f.checkHiddenResp(m, arg, hiddenResp, hp) 800 if err != nil { 801 return nil, err 802 } 803 804 if shoppingList.onlyNeedsRefresh() && state != nil && state.Chain.Last != nil && state.Chain.Last.Seqno == lastSeqno && hiddenIsFresh { 805 if !lastLinkID.Eq(state.Chain.Last.LinkID) { 806 m.Debug("link ID mismatch at tail seqno %d: wanted %s but got %s", state.Chain.Last.LinkID, lastLinkID) 807 return nil, NewFastLoadError("cached last link at seqno=%d did not match current merke tree", lastSeqno) 808 } 809 m.Debug("according to merkle tree, previously loaded chain at %d is current, and shopping list was empty", lastSeqno) 810 return nil, nil 811 } 812 813 teamUpdate, err = f.makeHTTPRequest(m, arg.toHTTPArgs(m, shoppingList, hp), arg.Public) 814 if err != nil { 815 return nil, err 816 } 817 818 if !teamUpdate.ID.Eq(arg.ID) { 819 return nil, NewFastLoadError("server returned wrong id: %v != %v", teamUpdate.ID, arg.ID) 820 } 821 links, err = teamUpdate.unpackLinks(m) 822 if err != nil { 823 return nil, err 824 } 825 826 numStubbed := 0 827 828 for _, link := range links { 829 if link.Seqno() > lastSeqno { 830 m.Debug("TeamLoader found green link seqno:%v", link.Seqno()) 831 return nil, NewGreenLinkError(link.Seqno()) 832 } 833 if link.Seqno() == lastSeqno && !lastLinkID.Eq(link.LinkID().Export()) { 834 m.Debug("Merkle tail mismatch at link %d: %v != %v", lastSeqno, lastLinkID, link.LinkID().Export()) 835 return nil, NewInvalidLink(link, "last link did not match merkle tree") 836 } 837 if link.isStubbed() { 838 numStubbed++ 839 } 840 } 841 842 if teamUpdate.Box != nil { 843 lastSecretGen, seeds, err = unboxPerTeamSecrets(m, f.world, teamUpdate.Box, teamUpdate.Prevs) 844 if err != nil { 845 return nil, err 846 } 847 } 848 849 hp.SetRatchetBlindingKeySet(teamUpdate.RatchetBlindingKeySet) 850 851 m.Debug("loadFromServerOnce: got back %d new links; %d stubbed; %d RKMs; %d prevs; box=%v; lastSecretGen=%d; %d hidden chainlinks", len(links), numStubbed, len(teamUpdate.ReaderKeyMasks), len(teamUpdate.Prevs), teamUpdate.Box != nil, lastSecretGen, len(teamUpdate.HiddenChain)) 852 853 return &groceries{ 854 newLinks: links, 855 latestKeyGen: lastSecretGen, 856 rkms: teamUpdate.ReaderKeyMasks, 857 seeds: seeds, 858 newHiddenLinks: teamUpdate.HiddenChain, 859 expMaxHiddenSeqno: hiddenResp.UncommittedSeqno, 860 lastMerkleRoot: lastMerkleRoot, 861 }, nil 862 } 863 864 // checkStubs makes sure that new links sent down from the server have the right stubbing/unstubbing 865 // pattern. The rules are: the most recent "up pointer" should be unstubbed. The first link should be 866 // unstubbed. The last key rotation should be unstubbed (though we can't really check this now). 867 // And any links we ask for should be unstubbed too. 868 func (f *FastTeamChainLoader) checkStubs(m libkb.MetaContext, shoppingList shoppingList, newLinks []*ChainLinkUnpacked, canReadTeam bool) (err error) { 869 870 if len(newLinks) == 0 { 871 return nil 872 } 873 874 isUpPointer := func(t libkb.SigchainV2Type) bool { 875 return (t == libkb.SigchainV2TypeTeamRenameUpPointer) || (t == libkb.SigchainV2TypeTeamDeleteUpPointer) 876 } 877 878 isKeyRotation := func(link *ChainLinkUnpacked) bool { 879 return (link.LinkType() == libkb.SigchainV2TypeTeamRotateKey) || (!link.isStubbed() && link.inner != nil && link.inner.Body.Key != nil) 880 } 881 882 // these are the links that we explicitly asked for from the server. 883 neededSeqnos := make(map[keybase1.Seqno]bool) 884 for _, s := range shoppingList.downPointers { 885 neededSeqnos[s] = true 886 } 887 888 foundUpPointer := false 889 foundKeyRotation := false 890 for i := len(newLinks) - 1; i >= 0; i-- { 891 link := newLinks[i] 892 893 // Check that the most recent up pointer is unstubbed 894 if !foundUpPointer && isUpPointer(link.LinkType()) { 895 if link.isStubbed() { 896 return NewInvalidLink(link, "expected last 'UP' pointer to be unstubbed") 897 } 898 foundUpPointer = true 899 } 900 901 // This check is approximate, since the server can hide key rotations, since they can be 902 // included in membership changes. 903 if !foundKeyRotation && isKeyRotation(link) { 904 if link.isStubbed() { 905 return NewInvalidLink(link, "we expected the last key rotation to be unstubbed") 906 } 907 foundKeyRotation = true 908 } 909 910 if neededSeqnos[link.Seqno()] && link.isStubbed() { 911 return NewInvalidLink(link, "server sent back stubbed link, but we asked for unstubbed") 912 } 913 } 914 915 if newLinks[0].isStubbed() && newLinks[0].Seqno() == keybase1.Seqno(1) { 916 return NewInvalidLink(newLinks[0], "expected head link to be unstubbed") 917 } 918 919 return nil 920 } 921 922 func checkSeqType(m libkb.MetaContext, arg fastLoadArg, link *ChainLinkUnpacked) error { 923 if link.SeqType() != keybase1.SeqType_NONE && ((arg.Public && link.SeqType() != keybase1.SeqType_PUBLIC) || (!arg.Public && link.SeqType() != keybase1.SeqType_SEMIPRIVATE)) { 924 m.Debug("Bad seqtype at %v/%d: %d", arg.ID, link.Seqno(), link.SeqType()) 925 return NewInvalidLink(link, "bad seqtype") 926 } 927 return nil 928 } 929 930 // checkPrevs checks the previous pointers on the new links that came down from the server. It 931 // only checks prevs for links that are newer than the last link gotten in this chain. 932 // We assume the rest are expanding hashes for links we've previously downloaded. 933 func (f *FastTeamChainLoader) checkPrevs(m libkb.MetaContext, arg fastLoadArg, last *keybase1.LinkTriple, newLinks []*ChainLinkUnpacked) (err error) { 934 if len(newLinks) == 0 { 935 return nil 936 } 937 938 var prev keybase1.LinkTriple 939 if last != nil { 940 prev = *last 941 } 942 943 cmpHash := func(prev keybase1.LinkTriple, link *ChainLinkUnpacked) (err error) { 944 945 // not ideal to have to export here, but it simplifies the code. 946 prevex := link.Prev().Export() 947 948 if prev.LinkID.IsNil() && prevex.IsNil() { 949 return nil 950 } 951 if prev.LinkID.IsNil() || prevex.IsNil() { 952 m.Debug("Bad prev nil/non-nil pointer check at seqno %d: (prev=%v vs curr=%v)", link.Seqno(), prev.LinkID.IsNil(), prevex.IsNil()) 953 return NewInvalidLink(link, "bad nil/non-nil prev pointer comparison") 954 } 955 if !prev.LinkID.Eq(prevex) { 956 m.Debug("Bad prev comparison at seqno %d: %s != %s", prev.LinkID, prevex) 957 return NewInvalidLink(link, "bad prev pointer") 958 } 959 return nil 960 } 961 962 cmpSeqnos := func(prev keybase1.LinkTriple, link *ChainLinkUnpacked) (err error) { 963 if prev.Seqno+1 != link.Seqno() { 964 m.Debug("Bad sequence violation: %d+1 != %d", prev.Seqno, link.Seqno()) 965 return NewInvalidLink(link, "seqno violation") 966 } 967 return checkSeqType(m, arg, link) 968 } 969 970 cmp := func(prev keybase1.LinkTriple, link *ChainLinkUnpacked) (err error) { 971 err = cmpHash(prev, link) 972 if err != nil { 973 return err 974 } 975 return cmpSeqnos(prev, link) 976 } 977 978 for _, link := range newLinks { 979 // We might have gotten some links from the past just for the purposes of expanding 980 // previous links that were stubbed. We don't need to check prevs on them, since 981 // we previously did. 982 if last != nil && last.Seqno >= link.Seqno() { 983 continue 984 } 985 err := cmp(prev, link) 986 if err != nil { 987 return err 988 } 989 prev = link.LinkTriple() 990 } 991 return nil 992 } 993 994 // audit runs probabilistic merkle tree audit on the new links, to make sure that the server isn't 995 // running odd-even-style attacks against members in a group. 996 func (f *FastTeamChainLoader) audit(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, hiddenChain *keybase1.HiddenTeamChain, lastMerkleRoot *libkb.MerkleRoot) (err error) { 997 head, ok := state.Chain.MerkleInfo[1] 998 if !ok { 999 return NewAuditError("cannot run audit without merkle info for head") 1000 } 1001 last := state.Chain.Last 1002 if last == nil { 1003 return NewAuditError("cannot run audit, no last chain data") 1004 } 1005 auditMode := keybase1.AuditMode_STANDARD 1006 if arg.HiddenChainIsOptional { 1007 auditMode = keybase1.AuditMode_STANDARD_NO_HIDDEN 1008 } 1009 return m.G().GetTeamAuditor().AuditTeam(m, arg.ID, arg.Public, head.Seqno, state.Chain.LinkIDs, hiddenChain.GetOuter(), last.Seqno, hiddenChain.GetLastCommittedSeqno(), lastMerkleRoot, auditMode) 1010 } 1011 1012 // readDownPointer reads a down pointer out of a given link, if it's unstubbed. Down pointers 1013 // are (1) new_subteams; (2) subteam rename down pointers; and (3) subteam delete down pointers. 1014 // Will return (nil, non-nil) if there is an error. 1015 func readDownPointer(m libkb.MetaContext, link *ChainLinkUnpacked) (*keybase1.DownPointer, error) { 1016 if link.inner == nil || link.inner.Body.Team == nil || link.inner.Body.Team.Subteam == nil { 1017 return nil, nil 1018 } 1019 subteam := link.inner.Body.Team.Subteam 1020 typ := link.LinkType() 1021 if typ != libkb.SigchainV2TypeTeamNewSubteam && typ != libkb.SigchainV2TypeTeamRenameSubteam && typ != libkb.SigchainV2TypeTeamDeleteSubteam { 1022 return nil, nil 1023 } 1024 del := (typ == libkb.SigchainV2TypeTeamDeleteSubteam) 1025 if len(subteam.Name) == 0 && len(subteam.ID) == 0 { 1026 return nil, nil 1027 } 1028 lastPart, err := subteam.Name.LastPart() 1029 if err != nil { 1030 return nil, err 1031 } 1032 xid, err := subteam.ID.ToTeamID() 1033 if err != nil { 1034 return nil, err 1035 } 1036 return &keybase1.DownPointer{ 1037 Id: xid, 1038 NameComponent: lastPart, 1039 IsDeleted: del, 1040 }, nil 1041 } 1042 1043 // readMerkleRoot reads the merkle root out of the link if this link is unstubbed. 1044 func readMerkleRoot(m libkb.MetaContext, link *ChainLinkUnpacked) (*keybase1.MerkleRootV2, error) { 1045 if link.inner == nil { 1046 return nil, nil 1047 } 1048 ret := link.inner.Body.MerkleRoot.ToMerkleRootV2() 1049 return &ret, nil 1050 } 1051 1052 // readUpPointer reads an up pointer out the given link, if it's unstubbed. Up pointers are 1053 // (1) subteam heads; (2) subteam rename up pointers; and (3) subteam delete up pointers. 1054 // Will return (nil, non-nil) if we hit any error condition. 1055 func readUpPointer(m libkb.MetaContext, arg fastLoadArg, link *ChainLinkUnpacked) (*keybase1.UpPointer, error) { 1056 if link.inner == nil || link.inner.Body.Team == nil || link.inner.Body.Team.Parent == nil { 1057 return nil, nil 1058 } 1059 parent := link.inner.Body.Team.Parent 1060 typ := link.LinkType() 1061 if typ != libkb.SigchainV2TypeTeamSubteamHead && typ != libkb.SigchainV2TypeTeamRenameUpPointer && typ != libkb.SigchainV2TypeTeamDeleteUpPointer { 1062 return nil, nil 1063 } 1064 xid, err := parent.ID.ToTeamID() 1065 if err != nil { 1066 return nil, err 1067 } 1068 1069 err = checkSeqType(m, arg, link) 1070 if err != nil { 1071 return nil, err 1072 } 1073 return &keybase1.UpPointer{ 1074 OurSeqno: link.Seqno(), 1075 ParentID: xid, 1076 ParentSeqno: parent.Seqno, 1077 Deletion: (typ == libkb.SigchainV2TypeTeamDeleteUpPointer), 1078 }, nil 1079 } 1080 1081 // putName takes the name out of the team (or subteam) head and stores it to state. 1082 // In the case of a subteam, this name has not been verified, and we should 1083 // verify it ourselves against the merkle tree. 1084 func (f *FastTeamChainLoader) putName(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, newLinks []*ChainLinkUnpacked) (err error) { 1085 if len(newLinks) == 0 || newLinks[0].Seqno() != keybase1.Seqno(1) { 1086 return nil 1087 } 1088 head := newLinks[0] 1089 if head.isStubbed() { 1090 return NewInvalidLink(head, "head should never be stubbed") 1091 } 1092 if head.inner.Body.Team == nil || head.inner.Body.Team.Name == nil { 1093 return NewInvalidLink(head, "head name should never be nil") 1094 } 1095 nm := *head.inner.Body.Team.Name 1096 xname, err := keybase1.TeamNameFromString(string(nm)) 1097 if err != nil { 1098 return err 1099 } 1100 if !state.Name.IsNil() && !state.Name.Eq(xname) { 1101 return NewInvalidLink(head, "wrong name for team") 1102 } 1103 state.Name = xname 1104 return nil 1105 } 1106 1107 // readPerTeamKey reads a PerTeamKey section, if it exists, out of the given unpacked chainlink. 1108 func readPerTeamKey(m libkb.MetaContext, link *ChainLinkUnpacked) (ret *keybase1.PerTeamKey, err error) { 1109 1110 if link.inner == nil || link.inner.Body.Team == nil || link.inner.Body.Team.PerTeamKey == nil { 1111 return nil, nil 1112 } 1113 ptk := link.inner.Body.Team.PerTeamKey 1114 return &keybase1.PerTeamKey{ 1115 Gen: ptk.Generation, 1116 Seqno: link.Seqno(), 1117 SigKID: ptk.SigKID, 1118 EncKID: ptk.EncKID, 1119 }, nil 1120 } 1121 1122 // putLinks takes the links we just downloaded from the server, and stores them to the state. 1123 // It also fills in unstubbed fields for those links that have come back with payloads that 1124 // were previously stubbed. There are several error cases that can come up, when reading down 1125 // or up pointers from the reply. 1126 func (f *FastTeamChainLoader) putLinks(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, newLinks []*ChainLinkUnpacked) (err error) { 1127 if len(newLinks) == 0 { 1128 return nil 1129 } 1130 1131 for _, link := range newLinks { 1132 existing, ok := state.Chain.LinkIDs[link.Seqno()] 1133 linkID := link.LinkID().Export() 1134 if ok { 1135 // NOTE! This is a crucial check, since we might have checked prev's on this link 1136 // in a previous run on the chain. We have to make sure an unstubbed link is 1137 // consistent with that previous check. See checkPrevs for when we skip 1138 // checking prevs in such a case, and need to check here for linkID equality. 1139 if !linkID.Eq(existing) { 1140 return NewInvalidLink(link, "list doesn't match previously cached link") 1141 } 1142 } else { 1143 state.Chain.LinkIDs[link.Seqno()] = linkID 1144 } 1145 dp, err := readDownPointer(m, link) 1146 if err != nil { 1147 return err 1148 } 1149 if dp != nil { 1150 state.Chain.DownPointers[link.Seqno()] = *dp 1151 } 1152 up, err := readUpPointer(m, arg, link) 1153 if err != nil { 1154 return err 1155 } 1156 if up != nil && (state.Chain.LastUpPointer == nil || state.Chain.LastUpPointer.OurSeqno < up.OurSeqno) { 1157 state.Chain.LastUpPointer = up 1158 } 1159 ptk, err := readPerTeamKey(m, link) 1160 if err != nil { 1161 return err 1162 } 1163 if ptk != nil { 1164 state.Chain.PerTeamKeys[ptk.Gen] = *ptk 1165 } 1166 merkleRoot, err := readMerkleRoot(m, link) 1167 if err != nil { 1168 return err 1169 } 1170 if merkleRoot != nil { 1171 state.Chain.MerkleInfo[link.Seqno()] = *merkleRoot 1172 } 1173 } 1174 newLast := newLinks[len(newLinks)-1] 1175 if state.Chain.Last == nil || state.Chain.Last.Seqno < newLast.Seqno() { 1176 tmp := newLast.LinkTriple() 1177 state.Chain.Last = &tmp 1178 } 1179 return nil 1180 } 1181 1182 // putRKMs stores the new reader key masks loaded from the server to the state structure. 1183 func (f *FastTeamChainLoader) putRKMs(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, rkms []keybase1.ReaderKeyMask) (err error) { 1184 for _, rkm := range rkms { 1185 if _, ok := state.ReaderKeyMasks[rkm.Application]; !ok { 1186 state.ReaderKeyMasks[rkm.Application] = make(map[keybase1.PerTeamKeyGeneration]keybase1.MaskB64) 1187 } 1188 state.ReaderKeyMasks[rkm.Application][rkm.Generation] = rkm.Mask 1189 } 1190 return nil 1191 } 1192 1193 // putSeeds stores the crypto seeds to the PeterTeamKeySeedsUnverified slot of the state. It returns 1194 // the last n seeds, counting backwards. We exploit this fact to infer the seed generations from their 1195 // order. 1196 func (f *FastTeamChainLoader) putSeeds(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, latestKeyGen keybase1.PerTeamKeyGeneration, seeds []keybase1.PerTeamKeySeed) (err error) { 1197 for i, seed := range seeds { 1198 state.PerTeamKeySeedsUnverified[latestKeyGen-keybase1.PerTeamKeyGeneration(len(seeds)-i-1)] = seed 1199 } 1200 1201 // We might have gotten back 0 seeds from the server, so don't overwrite a valid LatestKeyGeneration 1202 // with 0 in that case. 1203 if latestKeyGen > state.LatestKeyGeneration { 1204 state.LatestKeyGeneration = latestKeyGen 1205 } 1206 return nil 1207 } 1208 1209 func (f *FastTeamChainLoader) putSeedChecks(m libkb.MetaContext, state *keybase1.FastTeamData) (err error) { 1210 latestChainGen := keybase1.PerTeamKeyGeneration(len(state.PerTeamKeySeedsUnverified)) 1211 if state.SeedChecks == nil { 1212 state.SeedChecks = make(map[keybase1.PerTeamKeyGeneration]keybase1.PerTeamSeedCheck) 1213 } 1214 return computeSeedChecks( 1215 m.Ctx(), 1216 state.ID(), 1217 latestChainGen, 1218 func(g keybase1.PerTeamKeyGeneration) (check *keybase1.PerTeamSeedCheck, seed keybase1.PerTeamKeySeed, err error) { 1219 seed, ok := state.PerTeamKeySeedsUnverified[g] 1220 if !ok { 1221 return nil, keybase1.PerTeamKeySeed{}, fmt.Errorf("unexpected nil PerTeamKeySeedsUnverified at %d", g) 1222 } 1223 tmp, ok := state.SeedChecks[g] 1224 if ok { 1225 check = &tmp 1226 } 1227 return check, seed, nil 1228 }, 1229 func(g keybase1.PerTeamKeyGeneration, check keybase1.PerTeamSeedCheck) { 1230 state.SeedChecks[g] = check 1231 }, 1232 ) 1233 } 1234 1235 func setCachedAtToNow(m libkb.MetaContext, state *keybase1.FastTeamData) { 1236 state.CachedAt = keybase1.ToTime(m.G().Clock().Now()) 1237 } 1238 1239 func (f *FastTeamChainLoader) putMetadata(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData) error { 1240 setCachedAtToNow(m, state) 1241 if arg.NeedLatestKey { 1242 state.LoadedLatest = true 1243 } 1244 return nil 1245 } 1246 1247 // mutateState takes the groceries fetched from the server and applies them to our current state. 1248 func (f *FastTeamChainLoader) mutateState(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, groceries *groceries) (err error) { 1249 1250 err = f.putName(m, arg, state, groceries.newLinks) 1251 if err != nil { 1252 return err 1253 } 1254 err = f.putLinks(m, arg, state, groceries.newLinks) 1255 if err != nil { 1256 return err 1257 } 1258 err = f.putRKMs(m, arg, state, groceries.rkms) 1259 if err != nil { 1260 return err 1261 } 1262 err = f.putSeeds(m, arg, state, groceries.latestKeyGen, groceries.seeds) 1263 if err != nil { 1264 return err 1265 } 1266 err = f.putSeedChecks(m, state) 1267 if err != nil { 1268 return err 1269 } 1270 err = f.putMetadata(m, arg, state) 1271 if err != nil { 1272 return err 1273 } 1274 return nil 1275 } 1276 1277 // makeState does a clone on a non-nil state, or makes a new state if nil. 1278 func makeState(arg fastLoadArg, s *keybase1.FastTeamData) *keybase1.FastTeamData { 1279 if s != nil { 1280 tmp := s.DeepCopy() 1281 return &tmp 1282 } 1283 return &keybase1.FastTeamData{ 1284 Subversion: 1, 1285 PerTeamKeySeedsUnverified: make(map[keybase1.PerTeamKeyGeneration]keybase1.PerTeamKeySeed), 1286 SeedChecks: make(map[keybase1.PerTeamKeyGeneration]keybase1.PerTeamSeedCheck), 1287 ReaderKeyMasks: make(map[keybase1.TeamApplication](map[keybase1.PerTeamKeyGeneration]keybase1.MaskB64)), 1288 Chain: keybase1.FastTeamSigChainState{ 1289 ID: arg.ID, 1290 Public: arg.Public, 1291 PerTeamKeys: make(map[keybase1.PerTeamKeyGeneration]keybase1.PerTeamKey), 1292 PerTeamKeySeedsVerified: make(map[keybase1.PerTeamKeyGeneration]keybase1.PerTeamKeySeed), 1293 DownPointers: make(map[keybase1.Seqno]keybase1.DownPointer), 1294 LinkIDs: make(map[keybase1.Seqno]keybase1.LinkID), 1295 MerkleInfo: make(map[keybase1.Seqno]keybase1.MerkleRootV2), 1296 }, 1297 } 1298 } 1299 1300 func (f *FastTeamChainLoader) hiddenPackage(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData) (hp *hidden.LoaderPackage, err error) { 1301 hp, err = hidden.NewLoaderPackage(m, arg.ID, 1302 func() (encKID keybase1.KID, gen keybase1.PerTeamKeyGeneration, role keybase1.TeamRole, err error) { 1303 // Always return TeamRole_NONE since ftl does not have access to 1304 // member roles. The hidden chain uses the role to skip checks bot 1305 // members are not able to perform. Bot members should never FTL, 1306 // however since they don't have key access. 1307 if state == nil || len(state.Chain.PerTeamKeys) == 0 { 1308 return encKID, gen, keybase1.TeamRole_NONE, nil 1309 } 1310 var ptk keybase1.PerTeamKey 1311 for _, tmp := range state.Chain.PerTeamKeys { 1312 ptk = tmp 1313 break 1314 } 1315 return ptk.EncKID, ptk.Gen, keybase1.TeamRole_NONE, nil 1316 }) 1317 if err != nil { 1318 return nil, err 1319 } 1320 if !arg.readSubteamID.IsNil() { 1321 m.Debug("hiddenPackage: disabling checks since we a subteam reader looking for parent chain") 1322 hp.DisableHiddenChainData() 1323 } 1324 if tmp := hidden.CheckFeatureGateForSupport(m, arg.ID); tmp != nil { 1325 m.Debug("hiddenPackage: disabling checks since we are feature-flagged off") 1326 hp.DisableHiddenChainData() 1327 } 1328 return hp, nil 1329 } 1330 1331 func (f *FastTeamChainLoader) consumeRatchets(m libkb.MetaContext, newLinks []*ChainLinkUnpacked, hp *hidden.LoaderPackage) (err error) { 1332 for _, link := range newLinks { 1333 if err := consumeRatchets(m, hp, link); err != nil { 1334 return err 1335 } 1336 } 1337 return nil 1338 } 1339 1340 func (f *FastTeamChainLoader) processHidden(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, groceries *groceries, hp *hidden.LoaderPackage) (err error) { 1341 1342 err = f.consumeRatchets(m, groceries.newLinks, hp) 1343 if err != nil { 1344 return err 1345 } 1346 1347 err = hp.Update(m, groceries.newHiddenLinks, groceries.expMaxHiddenSeqno) 1348 if err != nil { 1349 return err 1350 } 1351 err = hp.CheckUpdatesAgainstSeeds(m, func(g keybase1.PerTeamKeyGeneration) *keybase1.PerTeamSeedCheck { 1352 chk, ok := state.SeedChecks[g] 1353 if !ok { 1354 return nil 1355 } 1356 return &chk 1357 }) 1358 if err != nil { 1359 return err 1360 } 1361 err = hp.CheckParentPointersOnFastLoad(m, state) 1362 if err != nil { 1363 return err 1364 } 1365 1366 err = hp.Commit(m) 1367 if err != nil { 1368 return err 1369 } 1370 return nil 1371 } 1372 1373 // refresh the team's state, but loading with the server. It will download new stubbed chainlinks, 1374 // fill in unstubbed chainlinks, make sure that prev pointers match, make sure that the merkle 1375 // tree agrees with the chain tail, and then run the audit mechanism. If the state is already 1376 // fresh, we will return (nil, nil) and short-circuit. 1377 func (f *FastTeamChainLoader) refresh(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, shoppingList shoppingList, hp *hidden.LoaderPackage) (res *keybase1.FastTeamData, err error) { 1378 1379 defer m.Trace(fmt.Sprintf("FastTeamChainLoader#refresh(%+v)", arg), &err)() 1380 1381 groceries, err := f.loadFromServerWithRetries(m, arg, state, shoppingList, hp) 1382 if err != nil { 1383 return nil, err 1384 } 1385 1386 if groceries == nil { 1387 m.Debug("FastTeamChainLoader#refresh: our state was fresh according to the Merkle tree") 1388 1389 // Even if the state is fresh (no new chain links are necessary), we 1390 // might still have learned about old hidden links being committed to 1391 // the blind tree. This information needs to be persisted. 1392 err = hp.Update(m, []sig3.ExportJSON{}, 0) 1393 if err != nil { 1394 return nil, err 1395 } 1396 err = hp.Commit(m) 1397 if err != nil { 1398 return nil, err 1399 } 1400 1401 return nil, nil 1402 } 1403 1404 // Either makes a new state, or deepcopies the existing state, so that in the case 1405 // of an error, we haven't corrupted what's in cache. Thus, from here on out, 1406 // we are playing with our own (unshared) copy of the state. 1407 state = makeState(arg, state) 1408 1409 // check that all chain links sent down form a valid hash chain, and point 1410 // to what we already in had in cache. 1411 err = f.checkPrevs(m, arg, state.Chain.Last, groceries.newLinks) 1412 if err != nil { 1413 return nil, err 1414 } 1415 1416 // check that the server stubbed properly. 1417 err = f.checkStubs(m, shoppingList, groceries.newLinks, arg.readSubteamID.IsNil() /* canReadTeam */) 1418 if err != nil { 1419 return nil, err 1420 } 1421 1422 err = f.mutateState(m, arg, state, groceries) 1423 if err != nil { 1424 return nil, err 1425 } 1426 1427 err = f.processHidden(m, arg, state, groceries, hp) 1428 if err != nil { 1429 return nil, err 1430 } 1431 1432 // peform a probabilistic audit on the new links 1433 err = f.audit(m, arg, state, hp.ChainData(), groceries.lastMerkleRoot) 1434 if err != nil { 1435 return nil, err 1436 } 1437 1438 return state, nil 1439 } 1440 1441 // updateCache puts the new version of the state into the cache on the team's ID. 1442 func (f *FastTeamChainLoader) updateCache(m libkb.MetaContext, state *keybase1.FastTeamData) { 1443 f.storage.Put(m, state) 1444 } 1445 1446 func (f *FastTeamChainLoader) upgradeStoredState(mctx libkb.MetaContext, state *keybase1.FastTeamData) (changed bool, err error) { 1447 if state == nil { 1448 return false, nil 1449 } 1450 1451 changed = false 1452 if state.Subversion == 0 { 1453 err = f.putSeedChecks(mctx, state) 1454 if err != nil { 1455 mctx.Debug("failed in upgrade of subversion 0->1: %s", err) 1456 return false, err 1457 } 1458 mctx.Debug("Upgrade to subversion 1") 1459 state.Subversion = 1 1460 changed = true 1461 } 1462 1463 return changed, nil 1464 } 1465 1466 // loadLocked is the inner loop for loading team. Should be called when holding the lock 1467 // this teamID. 1468 func (f *FastTeamChainLoader) loadLocked(m libkb.MetaContext, arg fastLoadArg) (res *fastLoadRes, err error) { 1469 1470 frozenState, frozen, tombstoned := f.findStateInCache(m, arg.ID) 1471 var state *keybase1.FastTeamData 1472 var hp *hidden.LoaderPackage 1473 if tombstoned { 1474 return nil, NewTeamTombstonedError() 1475 } 1476 if !frozen { 1477 state = frozenState 1478 } 1479 1480 hp, err = f.hiddenPackage(m, arg, state) 1481 if err != nil { 1482 return nil, err 1483 } 1484 1485 var shoppingList shoppingList 1486 if state != nil { 1487 combinedData := newFTLCombinedData(state, hp.ChainData()) 1488 f.computeWithPreviousState(m, &shoppingList, arg, combinedData) 1489 if shoppingList.isEmpty() { 1490 return f.toResult(m, arg, combinedData) 1491 } 1492 } else { 1493 shoppingList.computeFreshLoad(m, arg) 1494 } 1495 shoppingList.addHiddenLow(hp) 1496 1497 m.Debug("FastTeamChainLoader#loadLocked: computed shopping list: %+v", shoppingList) 1498 1499 var newState *keybase1.FastTeamData 1500 newState, err = f.refresh(m, arg, state, shoppingList, hp) 1501 if err != nil { 1502 return nil, err 1503 } 1504 1505 // If newState == nil, that means that no updates were required, and the old state 1506 // is fine, we just need to update the cachedAt time. If newState is non-nil, 1507 // then we use if for our state going forward. 1508 if newState == nil { 1509 setCachedAtToNow(m, state) 1510 } else { 1511 state = newState 1512 } 1513 // Always update the cache, even if we're just bumping the cachedAt time. 1514 f.updateCache(m, state) 1515 1516 if frozen && frozenState != nil { 1517 frozenLast := frozenState.Chain.Last 1518 linkID := state.Chain.LinkIDs[frozenLast.Seqno] 1519 if !linkID.Eq(frozenLast.LinkID) { 1520 return nil, fmt.Errorf("FastTeamChainLoader#loadLocked: got wrong sigchain link ID for seqno %d: expected %v from previous cache entry (frozen=%t); got %v in new chain", frozenLast.Seqno, frozenLast.LinkID, frozen, linkID) 1521 } 1522 } 1523 1524 return f.toResult(m, arg, newFTLCombinedData(state, hp.ChainData())) 1525 } 1526 1527 // OnLogout is called when the user logs out, which purges the LRU. 1528 func (f *FastTeamChainLoader) OnLogout(mctx libkb.MetaContext) error { 1529 f.storage.ClearMem() 1530 return nil 1531 } 1532 1533 // OnDbNuke is called when the disk cache is cleared, which purges the LRU. 1534 func (f *FastTeamChainLoader) OnDbNuke(mctx libkb.MetaContext) error { 1535 f.storage.ClearMem() 1536 return nil 1537 } 1538 1539 func (f *FastTeamChainLoader) HintLatestSeqno(m libkb.MetaContext, id keybase1.TeamID, seqno keybase1.Seqno) (err error) { 1540 m = ftlLogTag(m) 1541 1542 defer m.Trace(fmt.Sprintf("FastTeamChainLoader#HintLatestSeqno(%v->%d)", id, seqno), &err)() 1543 1544 // Single-flight lock by team ID. 1545 lock := f.locktab.AcquireOnName(m.Ctx(), m.G(), id.String()) 1546 defer lock.Release(m.Ctx()) 1547 1548 if state, frozen, tombstoned := f.findStateInCache(m, id); state != nil && !frozen && !tombstoned { 1549 m.Debug("Found state in cache; updating") 1550 state.LatestSeqnoHint = seqno 1551 f.updateCache(m, state) 1552 } 1553 1554 return nil 1555 } 1556 1557 func (f *FastTeamChainLoader) ForceRepollUntil(m libkb.MetaContext, dtime gregor.TimeOrOffset) error { 1558 m.Debug("FastTeamChainLoader#ForceRepollUntil(%+v)", dtime) 1559 f.forceRepollMutex.Lock() 1560 defer f.forceRepollMutex.Unlock() 1561 f.forceRepollUntil = dtime 1562 return nil 1563 } 1564 1565 func (f *FastTeamChainLoader) InForceRepollMode(m libkb.MetaContext) bool { 1566 f.forceRepollMutex.Lock() 1567 defer f.forceRepollMutex.Unlock() 1568 if f.forceRepollUntil == nil { 1569 return false 1570 } 1571 if !f.forceRepollUntil.Before(m.G().Clock().Now()) { 1572 m.Debug("FastTeamChainLoader#InForceRepollMode: returning true") 1573 return true 1574 } 1575 f.forceRepollUntil = nil 1576 return false 1577 } 1578 1579 func newFrozenFastChain(chain *keybase1.FastTeamSigChainState) keybase1.FastTeamSigChainState { 1580 return keybase1.FastTeamSigChainState{ 1581 ID: chain.ID, 1582 Public: chain.Public, 1583 Last: chain.Last, 1584 } 1585 } 1586 1587 func (f *FastTeamChainLoader) Freeze(mctx libkb.MetaContext, teamID keybase1.TeamID) (err error) { 1588 defer mctx.Trace(fmt.Sprintf("FastTeamChainLoader#Freeze(%s)", teamID), &err)() 1589 1590 // Single-flight lock by team ID. 1591 lock := f.locktab.AcquireOnName(mctx.Ctx(), mctx.G(), teamID.String()) 1592 defer lock.Release(mctx.Ctx()) 1593 1594 td, frozen, tombstoned := f.storage.Get(mctx, teamID, teamID.IsPublic()) 1595 if frozen || td == nil { 1596 return nil 1597 } 1598 newTD := &keybase1.FastTeamData{ 1599 Frozen: true, 1600 Tombstoned: tombstoned, 1601 Chain: newFrozenFastChain(&td.Chain), 1602 } 1603 f.storage.Put(mctx, newTD) 1604 return nil 1605 } 1606 1607 func (f *FastTeamChainLoader) Tombstone(mctx libkb.MetaContext, teamID keybase1.TeamID) (err error) { 1608 defer mctx.Trace(fmt.Sprintf("FastTeamChainLoader#Tombstone(%s)", teamID), &err)() 1609 1610 // Single-flight lock by team ID. 1611 lock := f.locktab.AcquireOnName(mctx.Ctx(), mctx.G(), teamID.String()) 1612 defer lock.Release(mctx.Ctx()) 1613 1614 td, frozen, tombstoned := f.storage.Get(mctx, teamID, teamID.IsPublic()) 1615 if tombstoned || td == nil { 1616 return nil 1617 } 1618 newTD := &keybase1.FastTeamData{ 1619 Frozen: frozen, 1620 Tombstoned: true, 1621 Chain: newFrozenFastChain(&td.Chain), 1622 } 1623 f.storage.Put(mctx, newTD) 1624 return nil 1625 }