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  }