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  }