github.com/keybase/client/go@v0.0.0-20240520164431-4f512a4c85a3/teams/loader_ctx.go (about) 1 package teams 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/keybase/client/go/libkb" 12 "github.com/keybase/client/go/protocol/keybase1" 13 "github.com/keybase/client/go/sig3" 14 "github.com/keybase/client/go/teams/hidden" 15 ) 16 17 // Things TeamLoader uses that are mocked out for tests. 18 type LoaderContext interface { 19 // Get new links from the server. 20 getNewLinksFromServer(ctx context.Context, 21 teamID keybase1.TeamID, lows getLinksLows, 22 readSubteamID *keybase1.TeamID) (*rawTeam, error) 23 // Get full links from the server. 24 // Does not guarantee that the server returned the correct links, nor that they are unstubbed. 25 getLinksFromServer(ctx context.Context, 26 teamID keybase1.TeamID, requestSeqnos []keybase1.Seqno, 27 readSubteamID *keybase1.TeamID) (*rawTeam, error) 28 getMe(context.Context) (keybase1.UserVersion, error) 29 // Lookup the eldest seqno of a user. Can use the cache. 30 lookupEldestSeqno(context.Context, keybase1.UID) (keybase1.Seqno, error) 31 // Get the current user's per-user-key's derived encryption key (full). 32 perUserEncryptionKey(ctx context.Context, userSeqno keybase1.Seqno) (*libkb.NaclDHKeyPair, error) 33 merkleLookup(ctx context.Context, teamID keybase1.TeamID, public bool) (r1 keybase1.Seqno, r2 keybase1.LinkID, err error) 34 merkleLookupWithHidden(ctx context.Context, teamID keybase1.TeamID, public bool) (r1 keybase1.Seqno, r2 keybase1.LinkID, hiddenResp *libkb.MerkleHiddenResponse, lastMerkleRoot *libkb.MerkleRoot, err error) 35 merkleLookupTripleInPast(ctx context.Context, isPublic bool, leafID keybase1.UserOrTeamID, root keybase1.MerkleRootV2) (triple *libkb.MerkleTriple, err error) 36 forceLinkMapRefreshForUser(ctx context.Context, uid keybase1.UID) (linkMap linkMapT, err error) 37 loadKeyV2(ctx context.Context, uid keybase1.UID, kid keybase1.KID, lkc *loadKeyCache) (keybase1.UserVersion, *keybase1.PublicKeyV2NaCl, linkMapT, error) 38 } 39 40 // The main LoaderContext is G. 41 type LoaderContextG struct { 42 libkb.Contextified 43 44 // Cache of size=1 for caching merkle leaf lookups at the checkpoint, since in practice 45 // we hit this is rapid succession. 46 cacheMu sync.RWMutex 47 cachedSeqno keybase1.Seqno 48 cachedLeaf *libkb.MerkleGenericLeaf 49 } 50 51 var _ LoaderContext = (*LoaderContextG)(nil) 52 53 func NewLoaderContextFromG(g *libkb.GlobalContext) LoaderContext { 54 return &LoaderContextG{ 55 Contextified: libkb.NewContextified(g), 56 } 57 } 58 59 type rawTeam struct { 60 ID keybase1.TeamID `json:"id"` 61 Name keybase1.TeamName `json:"name"` 62 Status libkb.AppStatus `json:"status"` 63 Chain []json.RawMessage `json:"chain"` 64 Box *TeamBox `json:"box"` 65 Prevs map[keybase1.PerTeamKeyGeneration]prevKeySealedEncoded `json:"prevs"` 66 ReaderKeyMasks []keybase1.ReaderKeyMask `json:"reader_key_masks"` 67 // Whether the user is only being allowed to view the chain 68 // because they are a member of a descendent team. 69 SubteamReader bool `json:"subteam_reader"` 70 Showcase keybase1.TeamShowcase `json:"showcase"` 71 LegacyTLFUpgrade []keybase1.TeamGetLegacyTLFUpgrade `json:"legacy_tlf_upgrade"` 72 HiddenChain []sig3.ExportJSON `json:"hidden"` 73 RatchetBlindingKeySet *hidden.RatchetBlindingKeySet `json:"ratchet_blinding_keys"` 74 } 75 76 func (r *rawTeam) GetAppStatus() *libkb.AppStatus { 77 return &r.Status 78 } 79 80 func (r *rawTeam) GetHiddenChain() []sig3.ExportJSON { 81 if r == nil { 82 return nil 83 } 84 return r.HiddenChain 85 } 86 87 func (r *rawTeam) unpackLinks(mctx libkb.MetaContext) (links []*ChainLinkUnpacked, err error) { 88 if r == nil { 89 return nil, nil 90 } 91 defer mctx.PerfTrace(fmt.Sprintf("TeamLoad: unpackLinks(%v, %d)", r.ID, len(r.Chain)), &err)() 92 start := time.Now() 93 defer func() { 94 if len(links) == 0 { 95 return 96 } 97 var message string 98 if err == nil { 99 message = fmt.Sprintf("Unpacking links %d for %s", len(r.Chain), r.ID) 100 } else { 101 message = fmt.Sprintf("Failed to unpack links for %s", r.ID) 102 } 103 mctx.G().RuntimeStats.PushPerfEvent(keybase1.PerfEvent{ 104 EventType: keybase1.PerfEventType_TEAMCHAIN, 105 Message: message, 106 Ctime: keybase1.ToTime(start), 107 }) 108 }() 109 parsedLinks, err := r.parseLinks(mctx.Ctx()) 110 if err != nil { 111 return nil, err 112 } 113 for _, pLink := range parsedLinks { 114 pLink2 := pLink 115 link, err := unpackChainLink(&pLink2) 116 if err != nil { 117 return nil, err 118 } 119 if !link.isStubbed() { 120 if !link.innerTeamID.Eq(r.ID) { 121 return nil, fmt.Errorf("link has wrong team ID in response: %v != %v", link.innerTeamID, r.ID) 122 } 123 } 124 links = append(links, link) 125 } 126 return links, nil 127 } 128 129 func (r *rawTeam) parseLinks(ctx context.Context) ([]SCChainLink, error) { 130 var links []SCChainLink 131 for _, raw := range r.Chain { 132 link, err := ParseTeamChainLink(string(raw)) 133 if err != nil { 134 return nil, err 135 } 136 links = append(links, link) 137 } 138 return links, nil 139 } 140 141 // Get new links from the server. 142 func (l *LoaderContextG) getNewLinksFromServer(ctx context.Context, 143 teamID keybase1.TeamID, lows getLinksLows, 144 readSubteamID *keybase1.TeamID) (*rawTeam, error) { 145 return l.getLinksFromServerCommon(ctx, teamID, &lows, nil, readSubteamID) 146 } 147 148 // Get full links from the server. 149 // Does not guarantee that the server returned the correct links, nor that they are unstubbed. 150 func (l *LoaderContextG) getLinksFromServer(ctx context.Context, 151 teamID keybase1.TeamID, requestSeqnos []keybase1.Seqno, readSubteamID *keybase1.TeamID) (*rawTeam, error) { 152 return l.getLinksFromServerCommon(ctx, teamID, nil, requestSeqnos, readSubteamID) 153 } 154 155 func (l *LoaderContextG) getLinksFromServerCommon(ctx context.Context, 156 teamID keybase1.TeamID, lows *getLinksLows, requestSeqnos []keybase1.Seqno, readSubteamID *keybase1.TeamID) (*rawTeam, error) { 157 158 mctx := libkb.NewMetaContext(ctx, l.G()) 159 arg := libkb.NewAPIArg("team/get") 160 arg.SessionType = libkb.APISessionTypeREQUIRED 161 if teamID.IsPublic() { 162 arg.SessionType = libkb.APISessionTypeOPTIONAL 163 } 164 165 arg.Args = libkb.HTTPArgs{ 166 "id": libkb.S{Val: teamID.String()}, 167 "public": libkb.B{Val: teamID.IsPublic()}, 168 } 169 if lows != nil { 170 arg.Args["low"] = libkb.I{Val: int(lows.Seqno)} 171 arg.Args["per_team_key_low"] = libkb.I{Val: int(lows.PerTeamKey)} 172 arg.Args["hidden_low"] = libkb.I{Val: int(lows.HiddenChainSeqno)} 173 } 174 if len(requestSeqnos) > 0 { 175 arg.Args["seqnos"] = libkb.S{Val: seqnosToString(requestSeqnos)} 176 } 177 if readSubteamID != nil { 178 arg.Args["read_subteam_id"] = libkb.S{Val: readSubteamID.String()} 179 } 180 181 var rt rawTeam 182 if err := mctx.G().API.GetDecode(mctx, arg, &rt); err != nil { 183 return nil, err 184 } 185 if !rt.ID.Eq(teamID) { 186 return nil, fmt.Errorf("server returned wrong team ID: %v != %v", rt.ID, teamID) 187 } 188 return &rt, nil 189 } 190 191 func seqnosToString(v []keybase1.Seqno) string { 192 var s []string 193 for _, e := range v { 194 s = append(s, fmt.Sprintf("%d", int(e))) 195 } 196 return strings.Join(s, ",") 197 } 198 199 func (l *LoaderContextG) getMe(ctx context.Context) (res keybase1.UserVersion, err error) { 200 uid := l.G().ActiveDevice.UID() 201 // If we're logged out, we still should be able to access the team loader 202 // for public teams. So we'll just return a nil UID here, and it should just work. 203 if uid.IsNil() { 204 return res, nil 205 } 206 return l.G().GetMeUV(ctx) 207 } 208 209 func (l *LoaderContextG) lookupEldestSeqno(ctx context.Context, uid keybase1.UID) (keybase1.Seqno, error) { 210 // Lookup the latest eldest seqno for that uid. 211 // This value may come from a cache. 212 upak, err := loadUPAK2(ctx, l.G(), uid, false /*forcePoll */) 213 if err != nil { 214 return keybase1.Seqno(1), err 215 } 216 return upak.Current.EldestSeqno, nil 217 } 218 219 func (l *LoaderContextG) perUserEncryptionKey(ctx context.Context, userSeqno keybase1.Seqno) (*libkb.NaclDHKeyPair, error) { 220 return perUserEncryptionKey(l.MetaContext(ctx), userSeqno) 221 } 222 223 func perUserEncryptionKey(m libkb.MetaContext, userSeqno keybase1.Seqno) (*libkb.NaclDHKeyPair, error) { 224 kr, err := m.G().GetPerUserKeyring(m.Ctx()) 225 if err != nil { 226 return nil, err 227 } 228 return kr.GetEncryptionKeyBySeqnoOrSync(m, userSeqno) 229 } 230 231 func (l *LoaderContextG) merkleLookupWithHidden(ctx context.Context, teamID keybase1.TeamID, public bool) (r1 keybase1.Seqno, r2 keybase1.LinkID, hiddenResp *libkb.MerkleHiddenResponse, lastMerkleRoot *libkb.MerkleRoot, err error) { 232 leaf, hiddenResp, lastMerkleRoot, err := l.G().GetMerkleClient().LookupTeamWithHidden(l.MetaContext(ctx), teamID, hidden.ProcessHiddenResponseFunc) 233 if err != nil { 234 return r1, r2, nil, nil, err 235 } 236 r1, r2, err = l.processMerkleReply(ctx, teamID, public, leaf) 237 if err != nil { 238 return r1, r2, nil, nil, err 239 } 240 241 return r1, r2, hiddenResp, lastMerkleRoot, err 242 } 243 244 func (l *LoaderContextG) merkleLookup(ctx context.Context, teamID keybase1.TeamID, public bool) (r1 keybase1.Seqno, r2 keybase1.LinkID, err error) { 245 leaf, err := l.G().GetMerkleClient().LookupTeam(l.MetaContext(ctx), teamID) 246 if err != nil { 247 return r1, r2, err 248 } 249 r1, r2, err = l.processMerkleReply(ctx, teamID, public, leaf) 250 return r1, r2, err 251 } 252 253 func (l *LoaderContextG) processMerkleReply(ctx context.Context, teamID keybase1.TeamID, public bool, leaf *libkb.MerkleTeamLeaf) (r1 keybase1.Seqno, r2 keybase1.LinkID, err error) { 254 255 if !leaf.TeamID.Eq(teamID) { 256 return r1, r2, fmt.Errorf("merkle returned wrong leaf: %v != %v", leaf.TeamID.String(), teamID.String()) 257 } 258 259 if public { 260 if leaf.Public == nil { 261 l.G().Log.CDebugf(ctx, "TeamLoader hidden error: merkle returned nil leaf") 262 return r1, r2, NewTeamDoesNotExistError(public, teamID.String()) 263 } 264 return leaf.Public.Seqno, leaf.Public.LinkID.Export(), nil 265 } 266 if leaf.Private == nil { 267 l.G().Log.CDebugf(ctx, "TeamLoader hidden error: merkle returned nil leaf") 268 return r1, r2, NewTeamDoesNotExistError(public, teamID.String()) 269 } 270 return leaf.Private.Seqno, leaf.Private.LinkID.Export(), nil 271 } 272 273 func (l *LoaderContextG) getCachedCheckpointLookup(leafID keybase1.UserOrTeamID, seqno keybase1.Seqno) *libkb.MerkleGenericLeaf { 274 l.cacheMu.RLock() 275 defer l.cacheMu.RUnlock() 276 if l.cachedLeaf == nil || !l.cachedLeaf.LeafID.Equal(leafID) || !l.cachedSeqno.Eq(seqno) { 277 return nil 278 } 279 ret := l.cachedLeaf.PartialClone() 280 return &ret 281 } 282 283 func (l *LoaderContextG) putCachedCheckpoint(seqno keybase1.Seqno, leaf *libkb.MerkleGenericLeaf) { 284 l.cacheMu.Lock() 285 defer l.cacheMu.Unlock() 286 tmp := leaf.PartialClone() 287 l.cachedLeaf = &tmp 288 l.cachedSeqno = seqno 289 } 290 291 func (l *LoaderContextG) merkleLookupTripleAtCheckpoint(mctx libkb.MetaContext, leafID keybase1.UserOrTeamID, seqno keybase1.Seqno) (leaf *libkb.MerkleGenericLeaf, err error) { 292 293 ret := l.getCachedCheckpointLookup(leafID, seqno) 294 if ret != nil { 295 mctx.VLogf(libkb.VLog0, "hit checkpoint cache") 296 return ret, nil 297 } 298 299 mc := l.G().MerkleClient 300 leaf, _, err = mc.LookupLeafAtSeqno(mctx, leafID, seqno) 301 if leaf != nil && err == nil { 302 l.putCachedCheckpoint(seqno, leaf) 303 } 304 return leaf, err 305 } 306 307 func (l *LoaderContextG) merkleLookupTripleInPast(ctx context.Context, isPublic bool, leafID keybase1.UserOrTeamID, root keybase1.MerkleRootV2) (triple *libkb.MerkleTriple, err error) { 308 mctx := l.MetaContext(ctx) 309 310 mc := l.G().MerkleClient 311 checkpoint := mc.FirstExaminableHistoricalRoot(mctx) 312 var leaf *libkb.MerkleGenericLeaf 313 314 // If we're trying to lookup a leaf from before the checkpoint, just bump forward to the checkpoint. 315 // The checkpoint is consindered to be a legitimate version of Tree. 316 if checkpoint != nil && root.Seqno < *checkpoint { 317 mctx.Debug("Bumping up pre-checkpoint merkle fetch to checkpoint at %d for %s", *checkpoint, leafID) 318 leaf, err = l.merkleLookupTripleAtCheckpoint(mctx, leafID, *checkpoint) 319 } else { 320 leaf, err = mc.LookupLeafAtHashMeta(mctx, leafID, root.HashMeta) 321 } 322 323 if err != nil { 324 return nil, err 325 } 326 if isPublic { 327 triple = leaf.Public 328 } else { 329 triple = leaf.Private 330 } 331 if triple == nil { 332 return nil, fmt.Errorf("unexpected nil leaf for %v", leafID) 333 } 334 return triple, nil 335 } 336 337 func (l *LoaderContextG) forceLinkMapRefreshForUser(ctx context.Context, uid keybase1.UID) (linkMap linkMapT, err error) { 338 arg := libkb.NewLoadUserArg(l.G()).WithNetContext(ctx).WithUID(uid).WithForcePoll(true) 339 upak, _, err := l.G().GetUPAKLoader().LoadV2(arg) 340 if err != nil { 341 return nil, err 342 } 343 return upak.SeqnoLinkIDs, nil 344 } 345 346 func (l *LoaderContextG) loadKeyV2(ctx context.Context, uid keybase1.UID, kid keybase1.KID, lkc *loadKeyCache) ( 347 uv keybase1.UserVersion, pubKey *keybase1.PublicKeyV2NaCl, linkMap linkMapT, err error) { 348 ctx, tbs := l.G().CTimeBuckets(ctx) 349 defer tbs.Record("LoaderContextG.loadKeyV2")() 350 351 return lkc.loadKeyV2(l.MetaContext(ctx), uid, kid) 352 }