github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/ephemeral/team_ek_box_storage.go (about) 1 package ephemeral 2 3 import ( 4 "fmt" 5 "log" 6 "sync" 7 8 lru "github.com/hashicorp/golang-lru" 9 "github.com/keybase/client/go/libkb" 10 "github.com/keybase/client/go/protocol/gregor1" 11 "github.com/keybase/client/go/protocol/keybase1" 12 ) 13 14 const teamEKBoxStorageDBVersion = 5 15 16 type teamEKBoxCacheItem struct { 17 TeamEKBoxed keybase1.TeamEphemeralKeyBoxed 18 Err *EphemeralKeyError 19 } 20 21 func newTeamEKBoxCacheItem(teamEKBoxed keybase1.TeamEphemeralKeyBoxed, err error) teamEKBoxCacheItem { 22 var ekErr *EphemeralKeyError 23 e, ok := err.(EphemeralKeyError) 24 if !ok && err != nil { 25 e = newEphemeralKeyError(err.Error(), DefaultHumanErrMsg, 26 EphemeralKeyErrorKindUNKNOWN, TeamEKKind) 27 } 28 if err != nil { 29 ekErr = &e 30 } 31 return teamEKBoxCacheItem{ 32 TeamEKBoxed: teamEKBoxed, 33 Err: ekErr, 34 } 35 } 36 37 func (c teamEKBoxCacheItem) HasError() bool { 38 return c.Err != nil 39 } 40 41 func (c teamEKBoxCacheItem) Error() error { 42 if c.HasError() { 43 return *c.Err 44 } 45 return nil 46 } 47 48 type teamEKBoxCache map[keybase1.EkGeneration]teamEKBoxCacheItem 49 type TeamEKBoxMap map[keybase1.EkGeneration]keybase1.TeamEphemeralKeyBoxed 50 type TeamEKMap map[keybase1.EkGeneration]keybase1.TeamEphemeralKey 51 52 func teamKey(mctx libkb.MetaContext, teamID keybase1.TeamID) string { 53 return fmt.Sprintf("%s-%s", teamID, mctx.G().Env.GetUsername()) 54 } 55 56 // We cache TeamEKBoxes from the server in a LRU and a persist to a local 57 // KVStore. 58 type TeamEKBoxStorage struct { 59 sync.RWMutex 60 locktab *libkb.LockTable 61 cache *teamEKCache 62 keyer EphemeralKeyer 63 } 64 65 func NewTeamEKBoxStorage(keyer EphemeralKeyer) *TeamEKBoxStorage { 66 return &TeamEKBoxStorage{ 67 cache: newTeamEKCache(), 68 keyer: keyer, 69 locktab: libkb.NewLockTable(), 70 } 71 } 72 73 func (s *TeamEKBoxStorage) lockForTeamID(mctx libkb.MetaContext, teamID keybase1.TeamID) func() { 74 s.RLock() 75 lock := s.locktab.AcquireOnName(mctx.Ctx(), mctx.G(), teamID.String()) 76 return func() { 77 s.RUnlock() 78 lock.Release(mctx.Ctx()) 79 } 80 } 81 82 func (s *TeamEKBoxStorage) dbKey(mctx libkb.MetaContext, teamID keybase1.TeamID) (dbKey libkb.DbKey, err error) { 83 uv, err := mctx.G().GetMeUV(mctx.Ctx()) 84 if err != nil { 85 return dbKey, err 86 } 87 key := fmt.Sprintf("teamEphemeralKeyBox-%s-%s-%s-%d", s.keyer.Type(), 88 teamKey(mctx, teamID), uv.EldestSeqno, teamEKBoxStorageDBVersion) 89 return libkb.DbKey{ 90 Typ: libkb.DBTeamEKBox, 91 Key: key, 92 }, nil 93 } 94 95 func (s *TeamEKBoxStorage) Get(mctx libkb.MetaContext, teamID keybase1.TeamID, generation keybase1.EkGeneration, 96 contentCtime *gregor1.Time) (teamEK keybase1.TeamEphemeralKey, err error) { 97 defer mctx.Trace(fmt.Sprintf("TeamEKBoxStorage#Get: teamID:%v, generation:%v", teamID, generation), &err)() 98 99 unlock := s.lockForTeamID(mctx, teamID) 100 cache, found, err := s.getCacheForTeamID(mctx, teamID) 101 if err != nil { 102 unlock() 103 return teamEK, err 104 } else if !found { 105 unlock() // release the lock while we fetch 106 return s.fetchAndStore(mctx, teamID, generation, contentCtime) 107 } 108 109 cacheItem, ok := cache[generation] 110 if !ok { 111 unlock() // release the lock while we fetch 112 return s.fetchAndStore(mctx, teamID, generation, contentCtime) 113 } 114 115 defer unlock() // release the lock after we unbox 116 if cacheItem.HasError() { 117 return teamEK, cacheItem.Error() 118 } 119 120 teamEK, err = s.keyer.Unbox(mctx, cacheItem.TeamEKBoxed, contentCtime) 121 switch err.(type) { 122 case EphemeralKeyError: 123 if perr := s.putLocked(mctx, teamID, generation, keybase1.TeamEphemeralKeyBoxed{}, err); perr != nil { 124 mctx.Debug("unable to store unboxing error %v", perr) 125 } 126 default: 127 // don't store 128 } 129 return teamEK, err 130 } 131 132 func (s *TeamEKBoxStorage) getCacheForTeamID(mctx libkb.MetaContext, teamID keybase1.TeamID) (cache teamEKBoxCache, found bool, err error) { 133 cache, found = s.cache.GetMap(mctx, teamID) 134 if found { 135 return cache, found, nil 136 } 137 138 key, err := s.dbKey(mctx, teamID) 139 if err != nil { 140 return nil, false, err 141 } 142 cache = make(teamEKBoxCache) 143 found, err = mctx.G().GetKVStore().GetInto(&cache, key) 144 if err != nil { 145 return nil, found, err 146 } else if found { 147 s.cache.PutMap(mctx, teamID, cache) 148 } 149 return cache, found, err 150 } 151 152 type TeamEKBoxedResponse struct { 153 Result *struct { 154 Box string `json:"box"` 155 UserEKGeneration keybase1.EkGeneration `json:"user_ek_generation"` 156 Sig string `json:"sig"` 157 } `json:"result"` 158 } 159 160 func (s *TeamEKBoxStorage) fetchAndStore(mctx libkb.MetaContext, teamID keybase1.TeamID, generation keybase1.EkGeneration, 161 contentCtime *gregor1.Time) (teamEK keybase1.TeamEphemeralKey, err error) { 162 defer mctx.Trace(fmt.Sprintf("TeamEKBoxStorage#fetchAndStore: teamID:%v, generation:%v", teamID, generation), &err)() 163 164 // cache unboxing/missing box errors so we don't continually try to fetch 165 // something nonexistent. 166 defer func() { 167 if _, ok := err.(EphemeralKeyError); ok { 168 unlock := s.lockForTeamID(mctx, teamID) 169 defer unlock() 170 if perr := s.putLocked(mctx, teamID, generation, keybase1.TeamEphemeralKeyBoxed{}, err); perr != nil { 171 mctx.Debug("unable to store error %v", perr) 172 } 173 } 174 }() 175 176 teamEKBoxed, err := s.keyer.Fetch(mctx, teamID, generation, contentCtime) 177 if err != nil { 178 return teamEK, err 179 } 180 teamEK, err = s.keyer.Unbox(mctx, teamEKBoxed, contentCtime) 181 if err != nil { 182 return teamEK, err 183 } 184 185 // Store the boxed version, return the unboxed 186 err = s.Put(mctx, teamID, generation, teamEKBoxed) 187 return teamEK, err 188 } 189 190 func (s *TeamEKBoxStorage) Put(mctx libkb.MetaContext, teamID keybase1.TeamID, 191 generation keybase1.EkGeneration, teamEKBoxed keybase1.TeamEphemeralKeyBoxed) (err error) { 192 unlock := s.lockForTeamID(mctx, teamID) 193 defer unlock() 194 return s.putLocked(mctx, teamID, generation, teamEKBoxed, nil /* ekErr */) 195 } 196 197 func (s *TeamEKBoxStorage) putLocked(mctx libkb.MetaContext, teamID keybase1.TeamID, 198 generation keybase1.EkGeneration, teamEKBoxed keybase1.TeamEphemeralKeyBoxed, ekErr error) (err error) { 199 defer mctx.Trace(fmt.Sprintf("TeamEKBoxStorage#putLocked: teamID:%v, generation:%v", teamID, generation), &err)() 200 201 // sanity check that we got the right generation 202 if teamEKBoxed.Generation() != generation && ekErr == nil { 203 return newEKCorruptedErr(mctx, TeamEKKind, generation, teamEKBoxed.Generation()) 204 } 205 206 key, err := s.dbKey(mctx, teamID) 207 if err != nil { 208 return err 209 } 210 cache, _, err := s.getCacheForTeamID(mctx, teamID) 211 if err != nil { 212 return err 213 } 214 cache[generation] = newTeamEKBoxCacheItem(teamEKBoxed, ekErr) 215 if err = mctx.G().GetKVStore().PutObj(key, nil, cache); err != nil { 216 return err 217 } 218 s.cache.PutMap(mctx, teamID, cache) 219 return nil 220 } 221 222 func (s *TeamEKBoxStorage) Delete(mctx libkb.MetaContext, teamID keybase1.TeamID, 223 generation keybase1.EkGeneration) (err error) { 224 unlock := s.lockForTeamID(mctx, teamID) 225 defer unlock() 226 return s.deleteMany(mctx, teamID, []keybase1.EkGeneration{generation}) 227 } 228 229 func (s *TeamEKBoxStorage) deleteMany(mctx libkb.MetaContext, teamID keybase1.TeamID, 230 generations []keybase1.EkGeneration) (err error) { 231 defer mctx.Trace(fmt.Sprintf("TeamEKBoxStorage#delete: teamID:%v, generations:%v", teamID, generations), &err)() 232 233 cache, found, err := s.getCacheForTeamID(mctx, teamID) 234 if err != nil { 235 return err 236 } else if !found { 237 return nil 238 } 239 240 for _, generation := range generations { 241 delete(cache, generation) 242 } 243 244 key, err := s.dbKey(mctx, teamID) 245 if err != nil { 246 return err 247 } 248 if err = mctx.G().GetKVStore().PutObj(key, nil, cache); err != nil { 249 return err 250 } 251 s.cache.PutMap(mctx, teamID, cache) 252 return nil 253 } 254 255 func (s *TeamEKBoxStorage) PurgeCacheForTeamID(mctx libkb.MetaContext, teamID keybase1.TeamID) (err error) { 256 defer mctx.Trace(fmt.Sprintf("TeamEKBoxStorage#PurgeCacheForTeamID: teamID:%v", teamID), &err)() 257 unlock := s.lockForTeamID(mctx, teamID) 258 defer unlock() 259 260 key, err := s.dbKey(mctx, teamID) 261 if err != nil { 262 return err 263 } 264 cache := make(teamEKBoxCache) 265 if err = mctx.G().GetKVStore().PutObj(key, nil, cache); err != nil { 266 return err 267 } 268 s.cache.PutMap(mctx, teamID, cache) 269 return nil 270 } 271 272 func (s *TeamEKBoxStorage) DeleteExpired(mctx libkb.MetaContext, teamID keybase1.TeamID, 273 merkleRoot libkb.MerkleRoot) (expired []keybase1.EkGeneration, err error) { 274 defer mctx.Trace(fmt.Sprintf("TeamEKBoxStorage#DeleteExpired: teamID:%v", teamID), &err)() 275 276 unlock := s.lockForTeamID(mctx, teamID) 277 defer unlock() 278 279 cache, found, err := s.getCacheForTeamID(mctx, teamID) 280 if err != nil { 281 return nil, err 282 } else if !found { 283 return nil, nil 284 } 285 286 merkleCtime := keybase1.TimeFromSeconds(merkleRoot.Ctime()).Time() 287 // We delete expired and invalid cache entries but only return the expired. 288 toDelete := []keybase1.EkGeneration{} 289 for generation, cacheItem := range cache { 290 // purge any cached errors here so they don't stick around forever. 291 if cacheItem.HasError() { 292 toDelete = append(toDelete, generation) 293 } else { 294 keyAge := merkleCtime.Sub(cacheItem.TeamEKBoxed.Ctime().Time()) 295 // TeamEKs will never encrypt new data if the current key is older than 296 // libkb.EphemeralKeyGenInterval, thus the maximum lifetime of 297 // ephemeral content will not exceed libkb.MinEphemeralKeyLifetime = 298 // libkb.MaxEphemeralContentLifetime + libkb.EphemeralKeyGenInterval 299 if keyAge >= libkb.MinEphemeralKeyLifetime { 300 expired = append(expired, generation) 301 } 302 } 303 } 304 toDelete = append(toDelete, expired...) 305 return expired, s.deleteMany(mctx, teamID, toDelete) 306 } 307 308 func (s *TeamEKBoxStorage) GetAll(mctx libkb.MetaContext, teamID keybase1.TeamID) (teamEKs TeamEKMap, err error) { 309 defer mctx.Trace(fmt.Sprintf("TeamEKBoxStorage#GetAll: teamID:%v", teamID), &err)() 310 311 unlock := s.lockForTeamID(mctx, teamID) 312 defer unlock() 313 314 teamEKs = make(TeamEKMap) 315 cache, found, err := s.getCacheForTeamID(mctx, teamID) 316 if err != nil { 317 return nil, err 318 } else if !found { 319 return nil, nil 320 } 321 322 for generation, cacheItem := range cache { 323 if cacheItem.HasError() { 324 continue 325 } 326 teamEK, err := s.keyer.Unbox(mctx, cacheItem.TeamEKBoxed, nil) 327 if err != nil { 328 return nil, err 329 } 330 teamEKs[generation] = teamEK 331 } 332 return teamEKs, err 333 } 334 335 func (s *TeamEKBoxStorage) ClearCache() { 336 s.Lock() 337 defer s.Unlock() 338 s.cache.Clear() 339 } 340 341 func (s *TeamEKBoxStorage) MaxGeneration(mctx libkb.MetaContext, teamID keybase1.TeamID, includeErrs bool) (maxGeneration keybase1.EkGeneration, err error) { 342 defer mctx.Trace(fmt.Sprintf("TeamEKBoxStorage#MaxGeneration: teamID:%v", teamID), nil)() 343 344 unlock := s.lockForTeamID(mctx, teamID) 345 defer unlock() 346 347 maxGeneration = -1 348 cache, _, err := s.getCacheForTeamID(mctx, teamID) 349 if err != nil { 350 return maxGeneration, err 351 } 352 353 for generation, cacheItem := range cache { 354 if cacheItem.HasError() && !includeErrs { 355 continue 356 } 357 if generation > maxGeneration { 358 maxGeneration = generation 359 } 360 } 361 return maxGeneration, nil 362 } 363 364 // -------------------------------------------------- 365 366 const MemCacheLRUSize = 1000 367 368 // Store some TeamEKBoxes's in memory. Threadsafe. 369 type teamEKCache struct { 370 lru *lru.Cache 371 } 372 373 func newTeamEKCache() *teamEKCache { 374 nlru, err := lru.New(MemCacheLRUSize) 375 if err != nil { 376 // lru.New only panics if size <= 0 377 log.Panicf("Could not create lru cache: %v", err) 378 } 379 return &teamEKCache{ 380 lru: nlru, 381 } 382 } 383 384 func (s *teamEKCache) GetMap(mctx libkb.MetaContext, teamID keybase1.TeamID) (cache teamEKBoxCache, found bool) { 385 untyped, found := s.lru.Get(s.key(mctx, teamID)) 386 if !found { 387 return nil, found 388 } 389 cache, ok := untyped.(teamEKBoxCache) 390 if !ok { 391 mctx.Debug("TeamEK teamEKCache got bad type from lru: %T", untyped) 392 return nil, found 393 } 394 return cache, found 395 } 396 397 func (s *teamEKCache) PutMap(mctx libkb.MetaContext, teamID keybase1.TeamID, cache teamEKBoxCache) { 398 s.lru.Add(s.key(mctx, teamID), cache) 399 } 400 401 func (s *teamEKCache) Clear() { 402 s.lru.Purge() 403 } 404 405 func (s *teamEKCache) key(mctx libkb.MetaContext, teamID keybase1.TeamID) (key string) { 406 return teamKey(mctx, teamID) 407 }