github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/teambot/bot_keyer.go (about)

     1  package teambot
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"log"
     8  	"time"
     9  
    10  	lru "github.com/hashicorp/golang-lru"
    11  	"github.com/keybase/client/go/encrypteddb"
    12  	"github.com/keybase/client/go/libkb"
    13  	"github.com/keybase/client/go/protocol/keybase1"
    14  	"github.com/keybase/client/go/teams"
    15  	"github.com/keybase/clockwork"
    16  )
    17  
    18  const botKeyStorageVersion = 1
    19  
    20  type BotKeyer struct {
    21  	locktab *libkb.LockTable
    22  	lru     *lru.Cache
    23  	edb     *encrypteddb.EncryptedDB
    24  	clock   clockwork.Clock
    25  
    26  	maxRemoteTries int
    27  }
    28  
    29  var _ libkb.TeambotBotKeyer = (*BotKeyer)(nil)
    30  
    31  func NewBotKeyer(mctx libkb.MetaContext) *BotKeyer {
    32  	keyFn := func(ctx context.Context) ([32]byte, error) {
    33  		return encrypteddb.GetSecretBoxKey(ctx, mctx.G(),
    34  			libkb.EncryptionReasonTeambotKeyLocalStorage, "encrypting teambot keys cache")
    35  	}
    36  	dbFn := func(g *libkb.GlobalContext) *libkb.JSONLocalDb {
    37  		return g.LocalDb
    38  	}
    39  	nlru, err := lru.New(lruSize)
    40  	if err != nil {
    41  		// lru.New only panics if size <= 0
    42  		log.Panicf("Could not create lru cache: %v", err)
    43  	}
    44  	return &BotKeyer{
    45  		edb:            encrypteddb.New(mctx.G(), dbFn, keyFn),
    46  		lru:            nlru,
    47  		locktab:        libkb.NewLockTable(),
    48  		clock:          clockwork.NewRealClock(),
    49  		maxRemoteTries: 10,
    50  	}
    51  }
    52  
    53  func (k *BotKeyer) SetClock(clock clockwork.Clock) {
    54  	k.clock = clock
    55  }
    56  
    57  func (k *BotKeyer) lockKey(teamID keybase1.TeamID) string {
    58  	return teamID.String()
    59  }
    60  
    61  func (k *BotKeyer) cacheKey(mctx libkb.MetaContext, teamID keybase1.TeamID,
    62  	app keybase1.TeamApplication, generation keybase1.TeambotKeyGeneration) (string, error) {
    63  	uv, err := mctx.G().GetMeUV(mctx.Ctx())
    64  	if err != nil {
    65  		return "", err
    66  	}
    67  	key := fmt.Sprintf("teambotKey-%d-%s-%s-%d-%d-%d", botKeyStorageVersion, teamID, uv.Uid,
    68  		uv.EldestSeqno, app, generation)
    69  	return key, nil
    70  }
    71  
    72  func (k *BotKeyer) dbKey(cacheKey string) libkb.DbKey {
    73  	return libkb.DbKey{
    74  		Typ: libkb.DBTeambotKey,
    75  		Key: cacheKey,
    76  	}
    77  }
    78  
    79  func (k *BotKeyer) DeleteTeambotKeyForTest(mctx libkb.MetaContext, teamID keybase1.TeamID,
    80  	app keybase1.TeamApplication, generation keybase1.TeambotKeyGeneration) (err error) {
    81  	defer mctx.Trace(fmt.Sprintf("botKeyer#DeleteTeambotKeyForTest: teamID:%v, app:%v, generation:%v",
    82  		teamID, app, generation), &err)()
    83  
    84  	lock := k.locktab.AcquireOnName(mctx.Ctx(), mctx.G(), k.lockKey(teamID))
    85  	defer lock.Release(mctx.Ctx())
    86  
    87  	boxKey, err := k.cacheKey(mctx, teamID, app, generation)
    88  	if err != nil {
    89  		return err
    90  	}
    91  	k.lru.Remove(boxKey)
    92  
    93  	dbKey := k.dbKey(boxKey)
    94  	err = k.edb.Delete(mctx.Ctx(), dbKey)
    95  	return err
    96  }
    97  
    98  func (k *BotKeyer) get(mctx libkb.MetaContext, teamID keybase1.TeamID, app keybase1.TeamApplication,
    99  	generation keybase1.TeambotKeyGeneration) (key keybase1.TeambotKey, wrongKID bool, err error) {
   100  	defer mctx.Trace(fmt.Sprintf("botKeyer#get: teamID:%v, app:%v, generation:%v, ", teamID, app, generation),
   101  		&err)()
   102  
   103  	key, found, err := k.getFromStorage(mctx, teamID, app, generation)
   104  	if err != nil {
   105  		return key, false, err
   106  	} else if found {
   107  		return key, false, nil
   108  	}
   109  
   110  	return k.fetchAndStore(mctx, teamID, app, generation)
   111  }
   112  
   113  func (k *BotKeyer) getFromStorage(mctx libkb.MetaContext, teamID keybase1.TeamID,
   114  	app keybase1.TeamApplication, generation keybase1.TeambotKeyGeneration) (key keybase1.TeambotKey, found bool, err error) {
   115  	boxKey, err := k.cacheKey(mctx, teamID, app, generation)
   116  	if err != nil {
   117  		return key, false, err
   118  	}
   119  
   120  	res, found := k.lru.Get(boxKey)
   121  	if found {
   122  		key, ok := res.(keybase1.TeambotKey)
   123  		if !ok {
   124  			return key, false, fmt.Errorf("unable to load teambotkey from cache found %T, expected %T", res, keybase1.TeambotKey{})
   125  		}
   126  		return key, true, nil
   127  	}
   128  
   129  	dbKey := k.dbKey(boxKey)
   130  	found, err = k.edb.Get(mctx.Ctx(), dbKey, &key)
   131  	if err != nil {
   132  		mctx.Debug("Unable to fetch from disk err: %v", err)
   133  		return keybase1.TeambotKey{}, false, nil
   134  	}
   135  	if !found {
   136  		return keybase1.TeambotKey{}, false, nil
   137  	}
   138  
   139  	// add to in-mem cache
   140  	k.lru.Add(boxKey, key)
   141  	return key, true, nil
   142  }
   143  
   144  func (k *BotKeyer) put(mctx libkb.MetaContext, teamID keybase1.TeamID,
   145  	app keybase1.TeamApplication, generation keybase1.TeambotKeyGeneration, key keybase1.TeambotKey) error {
   146  
   147  	boxKey, err := k.cacheKey(mctx, teamID, app, generation)
   148  	if err != nil {
   149  		return err
   150  	}
   151  	dbKey := k.dbKey(boxKey)
   152  	if err = k.edb.Put(mctx.Ctx(), dbKey, key); err != nil {
   153  		return err
   154  	}
   155  	k.lru.Add(boxKey, key)
   156  	return nil
   157  }
   158  
   159  func (k *BotKeyer) fetchAndStore(mctx libkb.MetaContext, teamID keybase1.TeamID,
   160  	app keybase1.TeamApplication, generation keybase1.TeambotKeyGeneration) (key keybase1.TeambotKey, wrongKID bool, err error) {
   161  	defer mctx.Trace(fmt.Sprintf("BotKeyer#fetchAndStore: teamID:%v, app: %v, generation:%v", teamID, app, generation), &err)()
   162  
   163  	boxed, wrongKID, err := k.fetch(mctx, teamID, app, generation)
   164  	if err != nil {
   165  		return key, false, err
   166  	}
   167  	key, err = k.unbox(mctx, boxed)
   168  	if err != nil {
   169  		return key, false, err
   170  	}
   171  
   172  	err = k.put(mctx, teamID, app, generation, key)
   173  	return key, wrongKID, err
   174  }
   175  
   176  // unbox decrypts the TeambotKey for the given PUK rengeration
   177  func (k *BotKeyer) unbox(mctx libkb.MetaContext, boxed keybase1.TeambotKeyBoxed) (
   178  	key keybase1.TeambotKey, err error) {
   179  	defer mctx.Trace(fmt.Sprintf("BotKeyer#unbox: generation: %v",
   180  		boxed.Metadata.Generation), &err)()
   181  
   182  	pukring, err := mctx.G().GetPerUserKeyring(mctx.Ctx())
   183  	if err != nil {
   184  		return key, err
   185  	}
   186  	encKey, err := pukring.GetEncryptionKeyByGenerationOrSync(mctx, boxed.Metadata.PukGeneration)
   187  	if err != nil {
   188  		return key, err
   189  	}
   190  
   191  	msg, _, err := encKey.DecryptFromString(boxed.Box)
   192  	if err != nil {
   193  		return key, err
   194  	}
   195  
   196  	seed, err := newTeambotSeedFromBytes(msg)
   197  	if err != nil {
   198  		return key, err
   199  	}
   200  
   201  	keypair := deriveTeambotDHKey(seed)
   202  	if !keypair.GetKID().Equal(boxed.Metadata.Kid) {
   203  		return key, fmt.Errorf("Failed to verify server given seed against signed KID %s",
   204  			boxed.Metadata.Kid)
   205  	}
   206  
   207  	return keybase1.TeambotKey{
   208  		Seed:     seed,
   209  		Metadata: boxed.Metadata,
   210  	}, nil
   211  }
   212  
   213  type TeambotKeyBoxedResponse struct {
   214  	Result *struct {
   215  		Box string `json:"box"`
   216  		Sig string `json:"sig"`
   217  	} `json:"result"`
   218  }
   219  
   220  func (k *BotKeyer) remoteFetch(mctx libkb.MetaContext, teamID keybase1.TeamID,
   221  	app keybase1.TeamApplication, generation keybase1.TeambotKeyGeneration) (res TeambotKeyBoxedResponse, err error) {
   222  	for i := 0; i < k.maxRemoteTries; i++ {
   223  		apiArg := libkb.APIArg{
   224  			Endpoint:    "teambot/box",
   225  			SessionType: libkb.APISessionTypeREQUIRED,
   226  			Args: libkb.HTTPArgs{
   227  				"team_id":      libkb.S{Val: string(teamID)},
   228  				"application":  libkb.I{Val: int(app)},
   229  				"generation":   libkb.U{Val: uint64(generation)},
   230  				"is_ephemeral": libkb.B{Val: false},
   231  			},
   232  		}
   233  		resp, err := mctx.G().GetAPI().Get(mctx, apiArg)
   234  		if err != nil {
   235  			return res, err
   236  		}
   237  		if err = resp.Body.UnmarshalAgain(&res); err != nil {
   238  			return res, err
   239  		}
   240  		if res.Result == nil {
   241  			// we retry on these blank responses to avoid race conditions where the bot
   242  			// wants to send a message before the bot key has been created
   243  			mctx.Debug("not bot key found, trying again, attempt: %d", i)
   244  			time.Sleep(time.Second)
   245  			continue
   246  		}
   247  		return res, nil
   248  	}
   249  	return res, newTeambotTransientKeyError(errors.New("missing box"), generation)
   250  }
   251  
   252  func (k *BotKeyer) fetch(mctx libkb.MetaContext, teamID keybase1.TeamID,
   253  	app keybase1.TeamApplication, generation keybase1.TeambotKeyGeneration) (boxed keybase1.TeambotKeyBoxed, wrongKID bool, err error) {
   254  	// fetch boxed key from server
   255  	resp, err := k.remoteFetch(mctx, teamID, app, generation)
   256  	if err != nil {
   257  		return boxed, false, err
   258  	}
   259  	// It's possible that this key was signed with a PTK that is not our latest
   260  	// and greatest. We allow this when we are using this key for *decryption*.
   261  	// When getting a key for *encryption* callers are responsible for
   262  	// verifying the signature is signed by the latest PTK or requesting a new
   263  	// key. This logic currently lives in
   264  	// teambot/bot_keyer.go#getTeambotKeyLocked
   265  	metadata, wrongKID, err := verifyTeambotKeySigWithLatestPTK(mctx, teamID, resp.Result.Sig)
   266  	switch {
   267  	case wrongKID:
   268  		// charge forward, caller handles wrongKID
   269  		mctx.Debug("signed with wrongKID, bubbling up to caller")
   270  	case err != nil:
   271  		return boxed, false, err
   272  	case metadata == nil: // shouldn't happen
   273  		return boxed, false, fmt.Errorf("unable to fetch valid teambotKeyMetadata")
   274  	}
   275  
   276  	if generation != metadata.Generation {
   277  		// sanity check that we got the right generation
   278  		return boxed, false, fmt.Errorf("generation mismatch, expected:%d vs actual:%d",
   279  			generation, metadata.Generation)
   280  	}
   281  	return keybase1.TeambotKeyBoxed{
   282  		Box:      resp.Result.Box,
   283  		Metadata: *metadata,
   284  	}, wrongKID, nil
   285  }
   286  
   287  type TeambotKeyResponse struct {
   288  	Result *struct {
   289  		Sig string `json:"sig"`
   290  	} `json:"result"`
   291  }
   292  
   293  // GetLatestTeambotKey finds the latest TeambotKey for *encryption*. Since bots
   294  // depend on team members to derive a key for them, if the key is signed by an
   295  // old PTK we allow it to be used for a short window before permanently
   296  // failing, while we ask politely for a new key. If we don't have access to the
   297  // latest generation we fall back to the first key we do as long as it's within
   298  // the signing window.
   299  func (k *BotKeyer) GetLatestTeambotKey(mctx libkb.MetaContext, teamID keybase1.TeamID,
   300  	app keybase1.TeamApplication) (key keybase1.TeambotKey, err error) {
   301  	mctx = mctx.WithLogTag("GLTBK")
   302  	defer mctx.Trace(fmt.Sprintf("BotKeyer#GetLatestTeambotKey teamID: %v, app %v",
   303  		teamID, app), &err)()
   304  
   305  	lock := k.locktab.AcquireOnName(mctx.Ctx(), mctx.G(), k.lockKey(teamID))
   306  	defer lock.Release(mctx.Ctx())
   307  
   308  	team, err := teams.Load(mctx.Ctx(), mctx.G(), keybase1.LoadTeamArg{
   309  		ID: teamID,
   310  	})
   311  	if err != nil {
   312  		return key, err
   313  	}
   314  	gen := keybase1.TeambotKeyGeneration(team.Generation())
   315  	// If we need to use an older generation, force the wrongKID checks to
   316  	// happen so we don't use it for too long
   317  	forceWrongKID := false
   318  	for i := gen; i > 0; i-- {
   319  		if i < gen {
   320  			forceWrongKID = true
   321  		}
   322  		key, err = k.getTeambotKeyLocked(mctx, teamID, i, app, forceWrongKID)
   323  		switch err.(type) {
   324  		case nil:
   325  			return key, nil
   326  		case TeambotTransientKeyError:
   327  			// Ping team members to generate the key for us
   328  			if err2 := NotifyTeambotKeyNeeded(mctx, teamID, app, i); err2 != nil {
   329  				mctx.Debug("BotKeyer#GetLatestTeambotKey: Unable to NotifyTeambotKeyNeeded %v", err2)
   330  			}
   331  			mctx.Debug("BotKeyer#GetLatestTeambotKey Unable get team key at generation %d, retrying with previous generation. %v",
   332  				i, err)
   333  		default:
   334  			return key, err
   335  		}
   336  	}
   337  	return key, err
   338  }
   339  
   340  func (k *BotKeyer) getTeambotKeyLocked(mctx libkb.MetaContext, teamID keybase1.TeamID,
   341  	generation keybase1.TeambotKeyGeneration, app keybase1.TeamApplication, forceWrongKID bool) (key keybase1.TeambotKey, err error) {
   342  	defer mctx.Trace(fmt.Sprintf("BotKeyer#getTeambotKeyLocked teamID: %v, app %v, generation %d",
   343  		teamID, app, generation), &err)()
   344  
   345  	key, wrongKID, err := k.get(mctx, teamID, app, generation)
   346  	if wrongKID || forceWrongKID {
   347  		now := keybase1.ToTime(k.clock.Now())
   348  		permitted, ctime, err := TeambotKeyWrongKIDPermitted(mctx, teamID,
   349  			mctx.G().Env.GetUID(), key.Metadata.Application, key.Metadata.Generation, now)
   350  		if err != nil {
   351  			return key, err
   352  		}
   353  		mctx.Debug("getTeambotKey: wrongKID set, permitted: %v, ctime: %v",
   354  			permitted, ctime)
   355  		if !permitted {
   356  			err = fmt.Errorf("Wrong KID, first seen at %v, now %v", ctime.Time(), now.Time())
   357  			return key, newTeambotPermanentKeyError(err, key.Metadata.Generation)
   358  		}
   359  	}
   360  	return key, err
   361  }
   362  
   363  // GetTeambotKeyAtGeneration finds the TeambotKey at the specified generation.
   364  // This is used for *decryption* since we allow a key to be signed by an old
   365  // PTK. For *encryption* keys, see GetLatestTeambotKey.
   366  func (k *BotKeyer) GetTeambotKeyAtGeneration(mctx libkb.MetaContext, teamID keybase1.TeamID,
   367  	app keybase1.TeamApplication, generation keybase1.TeambotKeyGeneration) (key keybase1.TeambotKey, err error) {
   368  	mctx = mctx.WithLogTag("GTBK")
   369  	defer mctx.Trace(fmt.Sprintf("BotKeyer#GetTeambotKeyAtGeneration teamID: %v, app: %v, generation: %d",
   370  		teamID, app, generation), &err)()
   371  
   372  	lock := k.locktab.AcquireOnName(mctx.Ctx(), mctx.G(), k.lockKey(teamID))
   373  	defer lock.Release(mctx.Ctx())
   374  
   375  	key, _, err = k.get(mctx, teamID, app, generation)
   376  	if err != nil {
   377  		if _, ok := err.(TeambotTransientKeyError); ok {
   378  			// Ping team members to generate the key for us
   379  			if err2 := NotifyTeambotKeyNeeded(mctx, teamID, app, generation); err2 != nil {
   380  				mctx.Debug("Unable to NotifyTeambotKeyNeeded %v", err2)
   381  			}
   382  		}
   383  		return key, err
   384  	}
   385  	return key, nil
   386  }
   387  
   388  func (k *BotKeyer) OnLogout(mctx libkb.MetaContext) error {
   389  	k.lru.Purge()
   390  	return nil
   391  }
   392  
   393  func (k *BotKeyer) OnDbNuke(mctx libkb.MetaContext) error {
   394  	k.lru.Purge()
   395  	return nil
   396  }