github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/kbpki_client.go (about) 1 // Copyright 2016 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package libkbfs 6 7 import ( 8 "context" 9 "fmt" 10 "time" 11 12 lru "github.com/hashicorp/golang-lru" 13 "github.com/keybase/client/go/kbfs/idutil" 14 "github.com/keybase/client/go/kbfs/kbfscrypto" 15 "github.com/keybase/client/go/kbfs/kbfsmd" 16 "github.com/keybase/client/go/kbfs/tlf" 17 kbname "github.com/keybase/client/go/kbun" 18 "github.com/keybase/client/go/libkb" 19 "github.com/keybase/client/go/logger" 20 "github.com/keybase/client/go/protocol/keybase1" 21 ) 22 23 const ( 24 idToUserCacheSize = 50 25 ) 26 27 // keybaseServiceOwner is a wrapper around a KeybaseService, to allow 28 // switching the underlying service at runtime. It is usually 29 // implemented by Config. 30 type keybaseServiceOwner interface { 31 KeybaseService() KeybaseService 32 } 33 34 // KBPKIClient uses a KeybaseService. 35 type KBPKIClient struct { 36 serviceOwner keybaseServiceOwner 37 log logger.Logger 38 idToUserCache *lru.Cache 39 } 40 41 var _ KBPKI = (*KBPKIClient)(nil) 42 43 // NewKBPKIClient returns a new KBPKIClient with the given service. 44 func NewKBPKIClient( 45 serviceOwner keybaseServiceOwner, log logger.Logger) *KBPKIClient { 46 cache, err := lru.New(idToUserCacheSize) 47 if err != nil { 48 cache = nil 49 log.CDebugf(context.TODO(), "Error creating LRU cache: %+v", err) 50 } 51 52 return &KBPKIClient{serviceOwner, log, cache} 53 } 54 55 // GetCurrentSession implements the KBPKI interface for KBPKIClient. 56 func (k *KBPKIClient) GetCurrentSession(ctx context.Context) ( 57 idutil.SessionInfo, error) { 58 const sessionID = 0 59 return k.serviceOwner.KeybaseService().CurrentSession(ctx, sessionID) 60 } 61 62 // Resolve implements the KBPKI interface for KBPKIClient. 63 func (k *KBPKIClient) Resolve( 64 ctx context.Context, assertion string, 65 offline keybase1.OfflineAvailability) ( 66 kbname.NormalizedUsername, keybase1.UserOrTeamID, error) { 67 return k.serviceOwner.KeybaseService().Resolve(ctx, assertion, offline) 68 } 69 70 // Identify implements the KBPKI interface for KBPKIClient. 71 func (k *KBPKIClient) Identify( 72 ctx context.Context, assertion, reason string, 73 offline keybase1.OfflineAvailability) ( 74 kbname.NormalizedUsername, keybase1.UserOrTeamID, error) { 75 return k.serviceOwner.KeybaseService().Identify( 76 ctx, assertion, reason, offline) 77 } 78 79 // NormalizeSocialAssertion implements the KBPKI interface for KBPKIClient. 80 func (k *KBPKIClient) NormalizeSocialAssertion( 81 ctx context.Context, assertion string) (keybase1.SocialAssertion, error) { 82 return k.serviceOwner.KeybaseService().NormalizeSocialAssertion(ctx, assertion) 83 } 84 85 // ResolveImplicitTeam implements the KBPKI interface for KBPKIClient. 86 func (k *KBPKIClient) ResolveImplicitTeam( 87 ctx context.Context, assertions, suffix string, tlfType tlf.Type, 88 offline keybase1.OfflineAvailability) ( 89 idutil.ImplicitTeamInfo, error) { 90 return k.serviceOwner.KeybaseService().ResolveIdentifyImplicitTeam( 91 ctx, assertions, suffix, tlfType, false, "", offline) 92 } 93 94 // ResolveImplicitTeamByID implements the KBPKI interface for KBPKIClient. 95 func (k *KBPKIClient) ResolveImplicitTeamByID( 96 ctx context.Context, teamID keybase1.TeamID, tlfType tlf.Type, 97 offline keybase1.OfflineAvailability) ( 98 idutil.ImplicitTeamInfo, error) { 99 name, err := k.serviceOwner.KeybaseService().ResolveImplicitTeamByID( 100 ctx, teamID) 101 if err != nil { 102 return idutil.ImplicitTeamInfo{}, err 103 } 104 105 assertions, suffix, err := tlf.SplitExtension(name) 106 if err != nil { 107 return idutil.ImplicitTeamInfo{}, err 108 } 109 110 return k.serviceOwner.KeybaseService().ResolveIdentifyImplicitTeam( 111 ctx, assertions, suffix, tlfType, false, "", offline) 112 } 113 114 // ResolveTeamTLFID implements the KBPKI interface for KBPKIClient. 115 func (k *KBPKIClient) ResolveTeamTLFID( 116 ctx context.Context, teamID keybase1.TeamID, 117 offline keybase1.OfflineAvailability) (tlf.ID, error) { 118 settings, err := k.serviceOwner.KeybaseService().GetTeamSettings( 119 ctx, teamID, offline) 120 if err != nil { 121 return tlf.NullID, err 122 } 123 if settings.TlfID.IsNil() { 124 return tlf.NullID, err 125 } 126 tlfID, err := tlf.ParseID(settings.TlfID.String()) 127 if err != nil { 128 return tlf.NullID, err 129 } 130 return tlfID, nil 131 } 132 133 // IdentifyImplicitTeam identifies (and creates if necessary) the 134 // given implicit team. 135 func (k *KBPKIClient) IdentifyImplicitTeam( 136 ctx context.Context, assertions, suffix string, tlfType tlf.Type, 137 reason string, offline keybase1.OfflineAvailability) ( 138 idutil.ImplicitTeamInfo, error) { 139 return k.serviceOwner.KeybaseService().ResolveIdentifyImplicitTeam( 140 ctx, assertions, suffix, tlfType, true, reason, offline) 141 } 142 143 // GetNormalizedUsername implements the KBPKI interface for 144 // KBPKIClient. 145 func (k *KBPKIClient) GetNormalizedUsername( 146 ctx context.Context, id keybase1.UserOrTeamID, 147 offline keybase1.OfflineAvailability) ( 148 username kbname.NormalizedUsername, err error) { 149 if k.idToUserCache != nil { 150 tmp, ok := k.idToUserCache.Get(id) 151 if ok { 152 username, ok = tmp.(kbname.NormalizedUsername) 153 if ok { 154 return username, nil 155 } 156 } 157 } 158 159 var assertion string 160 if id.IsUser() { 161 assertion = fmt.Sprintf("uid:%s", id) 162 } else { 163 assertion = fmt.Sprintf("tid:%s", id) 164 } 165 username, _, err = k.Resolve(ctx, assertion, offline) 166 if err != nil { 167 return kbname.NormalizedUsername(""), err 168 } 169 k.idToUserCache.Add(id, username) 170 return username, nil 171 } 172 173 func (k *KBPKIClient) hasVerifyingKey( 174 ctx context.Context, uid keybase1.UID, verifyingKey kbfscrypto.VerifyingKey, 175 atServerTime time.Time, offline keybase1.OfflineAvailability) ( 176 bool, error) { 177 userInfo, err := k.loadUserPlusKeys(ctx, uid, verifyingKey.KID(), offline) 178 if err != nil { 179 return false, err 180 } 181 182 for _, key := range userInfo.VerifyingKeys { 183 if verifyingKey.KID().Equal(key.KID()) { 184 return true, nil 185 } 186 } 187 188 info, ok := userInfo.RevokedVerifyingKeys[verifyingKey] 189 if !ok { 190 return false, nil 191 } 192 193 // We add some slack to the revoke time, because the MD server 194 // won't instanteneously find out about the revoke -- it might 195 // keep accepting writes from the revoked device for a short 196 // period of time until it learns about the revoke. 197 const revokeSlack = 1 * time.Minute 198 revokedTime := keybase1.FromTime(info.Time) 199 // Check the server times -- if the key was valid at the given 200 // time, the caller can proceed with their merkle checking if 201 // desired. 202 if atServerTime.Before(revokedTime.Add(revokeSlack)) { 203 k.log.CDebugf(ctx, "Revoked verifying key %s for user %s passes time "+ 204 "check (revoked time: %v vs. server time %v, slack=%s)", 205 verifyingKey.KID(), uid, revokedTime, atServerTime, revokeSlack) 206 return false, RevokedDeviceVerificationError{info} 207 } 208 k.log.CDebugf(ctx, "Not trusting revoked verifying key %s for "+ 209 "user %s (revoked time: %v vs. server time %v, slack=%s)", 210 verifyingKey.KID(), uid, revokedTime, atServerTime, revokeSlack) 211 return false, nil 212 } 213 214 // HasVerifyingKey implements the KBPKI interface for KBPKIClient. 215 func (k *KBPKIClient) HasVerifyingKey( 216 ctx context.Context, uid keybase1.UID, verifyingKey kbfscrypto.VerifyingKey, 217 atServerTime time.Time, offline keybase1.OfflineAvailability) error { 218 ok, err := k.hasVerifyingKey(ctx, uid, verifyingKey, atServerTime, offline) 219 if err != nil { 220 return err 221 } 222 if ok { 223 return nil 224 } 225 226 // If the first attempt couldn't find the key, try again after 227 // clearing our local cache. We might have stale info if the 228 // service hasn't learned of the users' new key yet. 229 k.serviceOwner.KeybaseService().FlushUserFromLocalCache(ctx, uid) 230 231 ok, err = k.hasVerifyingKey(ctx, uid, verifyingKey, atServerTime, offline) 232 if err != nil { 233 return err 234 } 235 if !ok { 236 return VerifyingKeyNotFoundError{verifyingKey} 237 } 238 return nil 239 } 240 241 func (k *KBPKIClient) loadUserPlusKeys(ctx context.Context, 242 uid keybase1.UID, pollForKID keybase1.KID, 243 offline keybase1.OfflineAvailability) (idutil.UserInfo, error) { 244 return k.serviceOwner.KeybaseService().LoadUserPlusKeys( 245 ctx, uid, pollForKID, offline) 246 } 247 248 // GetCryptPublicKeys implements the KBPKI interface for KBPKIClient. 249 func (k *KBPKIClient) GetCryptPublicKeys( 250 ctx context.Context, uid keybase1.UID, 251 offline keybase1.OfflineAvailability) ( 252 keys []kbfscrypto.CryptPublicKey, err error) { 253 userInfo, err := k.loadUserPlusKeys(ctx, uid, "", offline) 254 if err != nil { 255 return nil, err 256 } 257 return userInfo.CryptPublicKeys, nil 258 } 259 260 // GetTeamTLFCryptKeys implements the KBPKI interface for KBPKIClient. 261 func (k *KBPKIClient) GetTeamTLFCryptKeys( 262 ctx context.Context, tid keybase1.TeamID, desiredKeyGen kbfsmd.KeyGen, 263 offline keybase1.OfflineAvailability) ( 264 map[kbfsmd.KeyGen]kbfscrypto.TLFCryptKey, kbfsmd.KeyGen, error) { 265 teamInfo, err := k.serviceOwner.KeybaseService().LoadTeamPlusKeys( 266 ctx, tid, tlf.Unknown, desiredKeyGen, keybase1.UserVersion{}, 267 kbfscrypto.VerifyingKey{}, keybase1.TeamRole_NONE, offline) 268 if err != nil { 269 return nil, 0, err 270 } 271 return teamInfo.CryptKeys, teamInfo.LatestKeyGen, nil 272 } 273 274 // GetCurrentMerkleRoot implements the KBPKI interface for KBPKIClient. 275 func (k *KBPKIClient) GetCurrentMerkleRoot(ctx context.Context) ( 276 keybase1.MerkleRootV2, time.Time, error) { 277 return k.serviceOwner.KeybaseService().GetCurrentMerkleRoot(ctx) 278 } 279 280 // VerifyMerkleRoot implements the KBPKI interface for KBPKIClient. 281 func (k *KBPKIClient) VerifyMerkleRoot( 282 ctx context.Context, root keybase1.MerkleRootV2, 283 kbfsRoot keybase1.KBFSRoot) error { 284 return k.serviceOwner.KeybaseService().VerifyMerkleRoot( 285 ctx, root, kbfsRoot) 286 } 287 288 // IsTeamWriter implements the KBPKI interface for KBPKIClient. 289 func (k *KBPKIClient) IsTeamWriter( 290 ctx context.Context, tid keybase1.TeamID, uid keybase1.UID, 291 verifyingKey kbfscrypto.VerifyingKey, 292 offline keybase1.OfflineAvailability) (bool, error) { 293 if uid.IsNil() || verifyingKey.IsNil() { 294 // A sessionless user can never be a writer. 295 return false, nil 296 } 297 298 // Use the verifying key to find out the eldest seqno of the user. 299 userInfo, err := k.loadUserPlusKeys(ctx, uid, verifyingKey.KID(), offline) 300 if err != nil { 301 return false, err 302 } 303 304 found := false 305 for _, key := range userInfo.VerifyingKeys { 306 if verifyingKey.KID().Equal(key.KID()) { 307 found = true 308 break 309 } 310 } 311 if !found { 312 // For the purposes of finding the eldest seqno, we need to 313 // check the verified key against the list of revoked keys as 314 // well. (The caller should use `HasVerifyingKey` later to 315 // check whether the revoked key was valid at the time of the 316 // update or not.) 317 _, found = userInfo.RevokedVerifyingKeys[verifyingKey] 318 } 319 if !found { 320 // The user doesn't currently have this KID, therefore they 321 // shouldn't be treated as a writer. The caller should check 322 // historical device records and team membership. 323 k.log.CDebugf(ctx, "User %s doesn't currently have verifying key %s", 324 uid, verifyingKey.KID()) 325 return false, nil 326 } 327 328 desiredUser := keybase1.UserVersion{ 329 Uid: uid, 330 EldestSeqno: userInfo.EldestSeqno, 331 } 332 teamInfo, err := k.serviceOwner.KeybaseService().LoadTeamPlusKeys( 333 ctx, tid, tlf.Unknown, kbfsmd.UnspecifiedKeyGen, desiredUser, 334 kbfscrypto.VerifyingKey{}, keybase1.TeamRole_WRITER, offline) 335 if err != nil { 336 if tid.IsPublic() { 337 if _, notFound := err.(libkb.NotFoundError); notFound { 338 // We are probably just not a writer of this public team. 339 k.log.CDebugf(ctx, 340 "Ignoring not found error for public team: %+v", err) 341 return false, nil 342 } 343 } 344 return false, err 345 } 346 return teamInfo.Writers[uid], nil 347 } 348 349 // NoLongerTeamWriter implements the KBPKI interface for KBPKIClient. 350 func (k *KBPKIClient) NoLongerTeamWriter( 351 ctx context.Context, tid keybase1.TeamID, tlfType tlf.Type, 352 uid keybase1.UID, verifyingKey kbfscrypto.VerifyingKey, 353 offline keybase1.OfflineAvailability) (keybase1.MerkleRootV2, error) { 354 if uid.IsNil() || verifyingKey.IsNil() { 355 // A sessionless user can never be a writer. 356 return keybase1.MerkleRootV2{}, nil 357 } 358 359 // We don't need the eldest seqno when we look up an older writer, 360 // the service takes care of that for us. 361 desiredUser := keybase1.UserVersion{ 362 Uid: uid, 363 } 364 365 teamInfo, err := k.serviceOwner.KeybaseService().LoadTeamPlusKeys( 366 ctx, tid, tlfType, kbfsmd.UnspecifiedKeyGen, desiredUser, 367 verifyingKey, keybase1.TeamRole_WRITER, offline) 368 if err != nil { 369 return keybase1.MerkleRootV2{}, err 370 } 371 return teamInfo.LastWriters[verifyingKey], nil 372 } 373 374 // IsTeamReader implements the KBPKI interface for KBPKIClient. 375 func (k *KBPKIClient) IsTeamReader( 376 ctx context.Context, tid keybase1.TeamID, uid keybase1.UID, 377 offline keybase1.OfflineAvailability) (bool, error) { 378 desiredUser := keybase1.UserVersion{Uid: uid} 379 teamInfo, err := k.serviceOwner.KeybaseService().LoadTeamPlusKeys( 380 ctx, tid, tlf.Unknown, kbfsmd.UnspecifiedKeyGen, desiredUser, 381 kbfscrypto.VerifyingKey{}, keybase1.TeamRole_READER, offline) 382 if err != nil { 383 return false, err 384 } 385 return tid.IsPublic() || teamInfo.Writers[uid] || teamInfo.Readers[uid], nil 386 } 387 388 // GetTeamRootID implements the KBPKI interface for KBPKIClient. 389 func (k *KBPKIClient) GetTeamRootID( 390 ctx context.Context, tid keybase1.TeamID, 391 offline keybase1.OfflineAvailability) (keybase1.TeamID, error) { 392 if !tid.IsSubTeam() { 393 return tid, nil 394 } 395 396 teamInfo, err := k.serviceOwner.KeybaseService().LoadTeamPlusKeys( 397 ctx, tid, tlf.Unknown, kbfsmd.UnspecifiedKeyGen, keybase1.UserVersion{}, 398 kbfscrypto.VerifyingKey{}, keybase1.TeamRole_NONE, offline) 399 if err != nil { 400 return keybase1.TeamID(""), err 401 } 402 return teamInfo.RootID, nil 403 } 404 405 // CreateTeamTLF implements the KBPKI interface for KBPKIClient. 406 func (k *KBPKIClient) CreateTeamTLF( 407 ctx context.Context, teamID keybase1.TeamID, tlfID tlf.ID) error { 408 return k.serviceOwner.KeybaseService().CreateTeamTLF(ctx, teamID, tlfID) 409 } 410 411 // FavoriteAdd implements the KBPKI interface for KBPKIClient. 412 func (k *KBPKIClient) FavoriteAdd(ctx context.Context, folder keybase1.FolderHandle) error { 413 return k.serviceOwner.KeybaseService().FavoriteAdd(ctx, folder) 414 } 415 416 // FavoriteDelete implements the KBPKI interface for KBPKIClient. 417 func (k *KBPKIClient) FavoriteDelete(ctx context.Context, folder keybase1.FolderHandle) error { 418 return k.serviceOwner.KeybaseService().FavoriteDelete(ctx, folder) 419 } 420 421 // FavoriteList implements the KBPKI interface for KBPKIClient. 422 func (k *KBPKIClient) FavoriteList(ctx context.Context) ( 423 keybase1.FavoritesResult, error) { 424 const sessionID = 0 425 return k.serviceOwner.KeybaseService().FavoriteList(ctx, sessionID) 426 } 427 428 // Notify implements the KBPKI interface for KBPKIClient. 429 func (k *KBPKIClient) Notify(ctx context.Context, notification *keybase1.FSNotification) error { 430 return k.serviceOwner.KeybaseService().Notify(ctx, notification) 431 } 432 433 // NotifyPathUpdated implements the KBPKI interface for KBPKIClient. 434 func (k *KBPKIClient) NotifyPathUpdated( 435 ctx context.Context, path string) error { 436 return k.serviceOwner.KeybaseService().NotifyPathUpdated(ctx, path) 437 } 438 439 // PutGitMetadata implements the KBPKI interface for KBPKIClient. 440 func (k *KBPKIClient) PutGitMetadata( 441 ctx context.Context, folder keybase1.FolderHandle, repoID keybase1.RepoID, 442 metadata keybase1.GitLocalMetadata) error { 443 return k.serviceOwner.KeybaseService().PutGitMetadata( 444 ctx, folder, repoID, metadata) 445 } 446 447 // InvalidateTeamCacheForID implements the KBPKI interface for KBPKIClient. 448 func (k *KBPKIClient) InvalidateTeamCacheForID(tid keybase1.TeamID) { 449 k.idToUserCache.Remove(tid.AsUserOrTeam()) 450 }