github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/tlf.go (about) 1 package chat 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 "time" 8 9 "github.com/keybase/client/go/auth" 10 "github.com/keybase/client/go/chat/globals" 11 "github.com/keybase/client/go/chat/types" 12 "github.com/keybase/client/go/chat/utils" 13 "github.com/keybase/client/go/libkb" 14 "github.com/keybase/client/go/protocol/chat1" 15 "github.com/keybase/client/go/protocol/gregor1" 16 "github.com/keybase/client/go/protocol/keybase1" 17 "github.com/keybase/go-framed-msgpack-rpc/rpc" 18 context "golang.org/x/net/context" 19 "golang.org/x/sync/errgroup" 20 ) 21 22 const kbfsTimeout = 15 * time.Second 23 24 type KBFSNameInfoSource struct { 25 globals.Contextified 26 utils.DebugLabeler 27 *NameIdentifier 28 } 29 30 func NewKBFSNameInfoSource(g *globals.Context) *KBFSNameInfoSource { 31 return &KBFSNameInfoSource{ 32 DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "KBFSNameInfoSource", false), 33 Contextified: globals.NewContextified(g), 34 NameIdentifier: NewNameIdentifier(g), 35 } 36 } 37 38 func (t *KBFSNameInfoSource) tlfKeysClient() (*keybase1.TlfKeysClient, error) { 39 if t.G().ConnectionManager == nil { 40 return nil, errors.New("no connection manager available") 41 } 42 xp := t.G().ConnectionManager.LookupByClientType(keybase1.ClientType_KBFS) 43 if xp == nil { 44 return nil, libkb.KBFSNotRunningError{} 45 } 46 return &keybase1.TlfKeysClient{ 47 Cli: rpc.NewClient( 48 xp, libkb.NewContextifiedErrorUnwrapper(t.G().ExternalG()), libkb.LogTagsFromContext), 49 }, nil 50 } 51 52 func (t *KBFSNameInfoSource) loadAll(ctx context.Context, tlfName string, public bool) (res types.NameInfo, keys types.AllCryptKeys, err error) { 53 var lastErr error 54 keys = types.NewAllCryptKeys() 55 visibility := keybase1.TLFVisibility_PRIVATE 56 if public { 57 visibility = keybase1.TLFVisibility_PUBLIC 58 } 59 for i := 0; i < 5; i++ { 60 if visibility == keybase1.TLFVisibility_PUBLIC { 61 var pres keybase1.CanonicalTLFNameAndIDWithBreaks 62 pres, err = t.PublicCanonicalTLFNameAndID(ctx, tlfName) 63 res.CanonicalName = pres.CanonicalName.String() 64 res.ID = chat1.TLFID(pres.TlfID.ToBytes()) 65 keys[chat1.ConversationMembersType_KBFS] = 66 append(keys[chat1.ConversationMembersType_KBFS], publicCryptKey) 67 } else { 68 var cres keybase1.GetTLFCryptKeysRes 69 cres, err = t.CryptKeys(ctx, tlfName) 70 res.CanonicalName = cres.NameIDBreaks.CanonicalName.String() 71 res.ID = chat1.TLFID(cres.NameIDBreaks.TlfID.ToBytes()) 72 for _, key := range cres.CryptKeys { 73 keys[chat1.ConversationMembersType_KBFS] = 74 append(keys[chat1.ConversationMembersType_KBFS], key) 75 } 76 } 77 if err != nil { 78 if _, ok := err.(auth.BadKeyError); ok { 79 // BadKeyError could be returned if there is a rekey race, so 80 // we are retrying a few times when that happens 81 lastErr = err 82 time.Sleep(500 * time.Millisecond) 83 continue 84 } 85 return res, keys, err 86 } 87 return res, keys, nil 88 } 89 return res, keys, lastErr 90 } 91 92 func (t *KBFSNameInfoSource) LookupID(ctx context.Context, tlfName string, public bool) (res types.NameInfo, err error) { 93 defer t.Trace(ctx, &err, fmt.Sprintf("Lookup(%s)", tlfName))() 94 res, _, err = t.loadAll(ctx, tlfName, public) 95 return res, err 96 } 97 98 func (t *KBFSNameInfoSource) LookupName(ctx context.Context, tlfID chat1.TLFID, public bool, 99 unverifiedTLFName string) (res types.NameInfo, err error) { 100 return res, fmt.Errorf("LookupName not implemented for KBFSNameInfoSource") 101 } 102 103 func (t *KBFSNameInfoSource) TeamBotSettings(ctx context.Context, tlfName string, tlfID chat1.TLFID, 104 membersType chat1.ConversationMembersType, public bool) (map[keybase1.UserVersion]keybase1.TeamBotSettings, error) { 105 return nil, errors.New("TeamBotSettings not implemented for KBFSNameInfoSource") 106 } 107 108 func (t *KBFSNameInfoSource) AllCryptKeys(ctx context.Context, tlfName string, public bool) (res types.AllCryptKeys, err error) { 109 defer t.Trace(ctx, &err, "AllCryptKeys(%s,%v)", tlfName, public)() 110 _, res, err = t.loadAll(ctx, tlfName, public) 111 return res, err 112 } 113 114 func (t *KBFSNameInfoSource) EncryptionKey(ctx context.Context, tlfName string, tlfID chat1.TLFID, 115 membersType chat1.ConversationMembersType, public bool, botUID *gregor1.UID) (res types.CryptKey, ni types.NameInfo, err error) { 116 defer t.Trace(ctx, &err, "EncryptionKey(%s,%v)", tlfName, public)() 117 if botUID != nil { 118 return res, ni, fmt.Errorf("TeambotKeys not supported by KBFS") 119 } 120 ni, allKeys, err := t.loadAll(ctx, tlfName, public) 121 if err != nil { 122 return res, ni, err 123 } 124 keys := allKeys[chat1.ConversationMembersType_KBFS] 125 if len(keys) == 0 { 126 return res, ni, errors.New("no encryption keys for tlf") 127 } 128 return keys[len(keys)-1], ni, nil 129 } 130 131 func (t *KBFSNameInfoSource) DecryptionKey(ctx context.Context, tlfName string, tlfID chat1.TLFID, 132 membersType chat1.ConversationMembersType, public bool, 133 keyGeneration int, kbfsEncrypted bool, botUID *gregor1.UID) (res types.CryptKey, err error) { 134 defer t.Trace(ctx, &err, "DecryptionKey(%s,%v)", tlfName, public)() 135 136 if botUID != nil { 137 return res, fmt.Errorf("TeambotKeys not supported by KBFS") 138 } 139 140 if public { 141 return publicCryptKey, nil 142 } 143 144 ni, err := t.AllCryptKeys(ctx, tlfName, public) 145 if err != nil { 146 // Banned folders are only detectable by the error string currently, 147 // hopefully we can do something better in the future. 148 if err.Error() == "Operations for this folder are temporarily throttled (error 2800)" { 149 return nil, NewDecryptionKeyNotFoundError(keyGeneration, public, kbfsEncrypted) 150 } 151 // This happens to finalized folders that are no longer being rekeyed 152 if strings.HasPrefix(err.Error(), "Can't get TLF private key for key generation") { 153 return nil, NewDecryptionKeyNotFoundError(keyGeneration, public, kbfsEncrypted) 154 } 155 return nil, err 156 } 157 for _, key := range ni[chat1.ConversationMembersType_KBFS] { 158 if key.Generation() == keyGeneration { 159 return key, nil 160 } 161 } 162 return nil, NewDecryptionKeyNotFoundError(keyGeneration, public, kbfsEncrypted) 163 } 164 165 func (t *KBFSNameInfoSource) EphemeralEncryptionKey(mctx libkb.MetaContext, tlfName string, tlfID chat1.TLFID, 166 membersType chat1.ConversationMembersType, public bool, botUID *gregor1.UID) (teamEK types.EphemeralCryptKey, err error) { 167 return teamEK, fmt.Errorf("KBFSNameInfoSource doesn't support ephemeral keys") 168 } 169 170 func (t *KBFSNameInfoSource) EphemeralDecryptionKey(mctx libkb.MetaContext, tlfName string, tlfID chat1.TLFID, 171 membersType chat1.ConversationMembersType, public bool, botUID *gregor1.UID, 172 generation keybase1.EkGeneration, contentCtime *gregor1.Time) (teamEK types.EphemeralCryptKey, err error) { 173 return teamEK, fmt.Errorf("KBFSNameInfoSource doesn't support ephemeral keys") 174 } 175 176 func (t *KBFSNameInfoSource) ShouldPairwiseMAC(ctx context.Context, tlfName string, tlfID chat1.TLFID, 177 membersType chat1.ConversationMembersType, public bool) (bool, []keybase1.KID, error) { 178 return false, nil, nil 179 } 180 181 func (t *KBFSNameInfoSource) CryptKeys(ctx context.Context, tlfName string) (res keybase1.GetTLFCryptKeysRes, err error) { 182 identBehavior, _, ok := globals.CtxIdentifyMode(ctx) 183 if !ok { 184 return res, fmt.Errorf("invalid context with no chat metadata") 185 } 186 defer t.Trace(ctx, &err, 187 fmt.Sprintf("CryptKeys(tlf=%s,mode=%v)", tlfName, identBehavior))() 188 189 username := t.G().Env.GetUsername() 190 if len(username) == 0 { 191 return res, libkb.LoginRequiredError{} 192 } 193 // Prepend username in case it's not present. We don't need to check if it 194 // exists already since CryptKeys calls below transforms the TLF name into a 195 // canonical one. 196 tlfName = string(username) + "," + tlfName 197 198 // call Identify and GetTLFCryptKeys concurrently: 199 group, ectx := errgroup.WithContext(globals.BackgroundChatCtx(ctx, t.G())) 200 201 var ib []keybase1.TLFIdentifyFailure 202 doneCh := make(chan struct{}) 203 group.Go(func() error { 204 t.Debug(ectx, "CryptKeys: running identify") 205 var err error 206 names := utils.SplitTLFName(tlfName) 207 ib, err = t.Identify(ectx, names, true, 208 func() keybase1.TLFID { 209 <-doneCh 210 return res.NameIDBreaks.TlfID 211 }, 212 func() keybase1.CanonicalTlfName { 213 <-doneCh 214 return res.NameIDBreaks.CanonicalName 215 }, 216 ) 217 return err 218 }) 219 group.Go(func() error { 220 defer close(doneCh) 221 t.Debug(ectx, "CryptKeys: running GetTLFCryptKeys on KFBS daemon") 222 tlfClient, err := t.tlfKeysClient() 223 if err != nil { 224 return err 225 } 226 227 // skip identify: 228 query := keybase1.TLFQuery{ 229 TlfName: tlfName, 230 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_SKIP, 231 } 232 233 tctx, cancel := context.WithTimeout(ectx, kbfsTimeout) 234 defer cancel() 235 res, err = tlfClient.GetTLFCryptKeys(tctx, query) 236 if err == context.DeadlineExceeded { 237 return ErrKeyServerTimeout 238 } 239 return err 240 }) 241 242 if err := group.Wait(); err != nil { 243 return keybase1.GetTLFCryptKeysRes{}, err 244 } 245 res.NameIDBreaks.Breaks.Breaks = ib 246 return res, nil 247 } 248 249 func (t *KBFSNameInfoSource) PublicCanonicalTLFNameAndID(ctx context.Context, tlfName string) (res keybase1.CanonicalTLFNameAndIDWithBreaks, err error) { 250 identBehavior, _, ok := globals.CtxIdentifyMode(ctx) 251 if !ok { 252 return res, fmt.Errorf("invalid context with no chat metadata") 253 } 254 defer t.Trace(ctx, &err, 255 fmt.Sprintf("PublicCanonicalTLFNameAndID(tlf=%s,mode=%v)", tlfName, identBehavior))() 256 257 // call Identify and CanonicalTLFNameAndIDWithBreaks concurrently: 258 group, ectx := errgroup.WithContext(globals.BackgroundChatCtx(ctx, t.G())) 259 260 var ib []keybase1.TLFIdentifyFailure 261 doneCh := make(chan struct{}) 262 if identBehavior != keybase1.TLFIdentifyBehavior_CHAT_SKIP { 263 group.Go(func() error { 264 var err error 265 names := utils.SplitTLFName(tlfName) 266 ib, err = t.Identify(ectx, names, false, 267 func() keybase1.TLFID { 268 <-doneCh 269 return res.TlfID 270 }, 271 func() keybase1.CanonicalTlfName { 272 <-doneCh 273 return res.CanonicalName 274 }, 275 ) 276 return err 277 }) 278 } 279 280 group.Go(func() error { 281 defer close(doneCh) 282 tlfClient, err := t.tlfKeysClient() 283 if err != nil { 284 return err 285 } 286 287 // skip identify: 288 query := keybase1.TLFQuery{ 289 TlfName: tlfName, 290 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_SKIP, 291 } 292 293 tctx, cancel := context.WithTimeout(ectx, kbfsTimeout) 294 defer cancel() 295 res, err = tlfClient.GetPublicCanonicalTLFNameAndID(tctx, query) 296 if err == context.DeadlineExceeded { 297 return ErrKeyServerTimeout 298 } 299 return err 300 }) 301 302 if err := group.Wait(); err != nil { 303 return keybase1.CanonicalTLFNameAndIDWithBreaks{}, err 304 } 305 res.Breaks.Breaks = ib 306 return res, nil 307 } 308 309 func (t *KBFSNameInfoSource) CompleteAndCanonicalizePrivateTlfName(ctx context.Context, tlfName string) (res keybase1.CanonicalTLFNameAndIDWithBreaks, err error) { 310 username := t.G().Env.GetUsername() 311 if len(username) == 0 { 312 return keybase1.CanonicalTLFNameAndIDWithBreaks{}, libkb.LoginRequiredError{} 313 } 314 315 // Prepend username in case it's not present. We don't need to check if it 316 // exists already since CryptKeys calls below transforms the TLF name into a 317 // canonical one. 318 // 319 // This makes username a writer on this TLF, which might be unexpected. 320 // TODO: We should think about how to handle read-only TLFs. 321 tlfName = string(username) + "," + tlfName 322 323 // TODO: do some caching so we don't end up calling this RPC 324 // unnecessarily too often 325 resp, err := t.CryptKeys(ctx, tlfName) 326 if err != nil { 327 return keybase1.CanonicalTLFNameAndIDWithBreaks{}, err 328 } 329 330 return resp.NameIDBreaks, nil 331 }