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 }