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  }