github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/teambot/common.go (about)

     1  package teambot
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  
     7  	"github.com/keybase/client/go/kbcrypto"
     8  	"github.com/keybase/client/go/libkb"
     9  	"github.com/keybase/client/go/protocol/gregor1"
    10  	"github.com/keybase/client/go/protocol/keybase1"
    11  	"github.com/keybase/client/go/teams"
    12  )
    13  
    14  const lruSize = 1000
    15  const maxRetries = 5
    16  
    17  type TeambotTransientKeyError struct {
    18  	inner      error
    19  	generation keybase1.TeambotKeyGeneration
    20  }
    21  
    22  func newTeambotTransientKeyError(inner error, generation keybase1.TeambotKeyGeneration) TeambotTransientKeyError {
    23  	return TeambotTransientKeyError{
    24  		inner:      inner,
    25  		generation: generation,
    26  	}
    27  }
    28  
    29  func (e TeambotTransientKeyError) Error() string {
    30  	return fmt.Sprintf("TeambotTransientKeyError for generation: %d. %v", e.generation, e.inner)
    31  }
    32  
    33  type TeambotPermanentKeyError struct {
    34  	inner      error
    35  	generation keybase1.TeambotKeyGeneration
    36  }
    37  
    38  func newTeambotPermanentKeyError(inner error, generation keybase1.TeambotKeyGeneration) TeambotPermanentKeyError {
    39  	return TeambotPermanentKeyError{
    40  		inner:      inner,
    41  		generation: generation,
    42  	}
    43  }
    44  
    45  func (e TeambotPermanentKeyError) Error() string {
    46  	return fmt.Sprintf("TeambotPermanentKeyError for generation: %d. %v", e.generation, e.inner)
    47  }
    48  
    49  func newTeambotSeedFromBytes(b []byte) (seed keybase1.Bytes32, err error) {
    50  	if len(b) != libkb.NaclDHKeysize {
    51  		err = fmt.Errorf("Wrong EkSeed len: %d != %d", len(b), libkb.NaclDHKeysize)
    52  		return seed, err
    53  	}
    54  	copy(seed[:], b)
    55  	return seed, nil
    56  }
    57  
    58  func deriveTeambotDHKey(seed keybase1.Bytes32) *libkb.NaclDHKeyPair {
    59  	derived, err := libkb.DeriveFromSecret(seed, libkb.DeriveReasonTeambotKeyEncryption)
    60  	if err != nil {
    61  		panic("This should never fail: " + err.Error())
    62  	}
    63  	keypair, err := libkb.MakeNaclDHKeyPairFromSecret(derived)
    64  	if err != nil {
    65  		panic("This should never fail: " + err.Error())
    66  	}
    67  	return &keypair
    68  }
    69  
    70  func extractTeambotKeyMetadataFromSig(sig string) (*kbcrypto.NaclSigningKeyPublic, *keybase1.TeambotKeyMetadata, error) {
    71  	signerKey, payload, _, err := kbcrypto.NaclVerifyAndExtract(sig)
    72  	if err != nil {
    73  		return signerKey, nil, err
    74  	}
    75  
    76  	parsedMetadata := keybase1.TeambotKeyMetadata{}
    77  	if err = json.Unmarshal(payload, &parsedMetadata); err != nil {
    78  		return signerKey, nil, err
    79  	}
    80  	return signerKey, &parsedMetadata, nil
    81  }
    82  
    83  // Verify that the blob is validly signed, and that the signing key is the
    84  // given team's latest PTK, then parse its contents.
    85  func verifyTeambotKeySigWithLatestPTK(mctx libkb.MetaContext, teamID keybase1.TeamID, sig string) (
    86  	metadata *keybase1.TeambotKeyMetadata, wrongKID bool, err error) {
    87  	defer mctx.Trace("verifyTeambotSigWithLatestPTK", &err)()
    88  
    89  	signerKey, metadata, err := extractTeambotKeyMetadataFromSig(sig)
    90  	if err != nil {
    91  		return nil, false, err
    92  	}
    93  
    94  	team, err := teams.Load(mctx.Ctx(), mctx.G(), keybase1.LoadTeamArg{
    95  		ID: teamID,
    96  	})
    97  	if err != nil {
    98  		return nil, false, err
    99  	}
   100  
   101  	// Verify the signing key corresponds to the latest PTK. We load the team's
   102  	// from cache, but if the KID doesn't match, we try a forced reload to see
   103  	// if the cache might've been stale. Only if the KID still doesn't match
   104  	// after the reload do we complain.
   105  	teamSigningKID, err := team.SigningKID(mctx.Ctx())
   106  	if err != nil {
   107  		return nil, false, err
   108  	}
   109  	if !teamSigningKID.Equal(signerKey.GetKID()) {
   110  		// The latest PTK might be stale. Force a reload, then check this over again.
   111  		team, err := teams.Load(mctx.Ctx(), mctx.G(), keybase1.LoadTeamArg{
   112  			ID:          team.ID,
   113  			ForceRepoll: true,
   114  		})
   115  		if err != nil {
   116  			return nil, false, err
   117  		}
   118  		teamSigningKID, err = team.SigningKID(mctx.Ctx())
   119  		if err != nil {
   120  			return nil, false, err
   121  		}
   122  		// return the metdata with wrongKID=true
   123  		if !teamSigningKID.Equal(signerKey.GetKID()) {
   124  			return metadata, true, fmt.Errorf("teambotEK returned for PTK signing KID %s, but latest is %s",
   125  				signerKey.GetKID(), teamSigningKID)
   126  		}
   127  	}
   128  
   129  	// If we didn't short circuit above, then the signing key is correct.
   130  	return metadata, false, nil
   131  }
   132  
   133  func CurrentUserIsBot(mctx libkb.MetaContext, botUID *gregor1.UID) bool {
   134  	return botUID != nil && botUID.Eq(gregor1.UID(mctx.ActiveDevice().UID().ToBytes()))
   135  }
   136  
   137  func DeleteTeambotKeyForTest(mctx libkb.MetaContext, teamID keybase1.TeamID,
   138  	app keybase1.TeamApplication, generation keybase1.TeambotKeyGeneration) error {
   139  	if err := deleteTeambotKeyForTest(mctx, teamID, app, int(generation), false /* isEphemeral */); err != nil {
   140  		return err
   141  	}
   142  	return mctx.G().GetTeambotBotKeyer().DeleteTeambotKeyForTest(mctx, teamID, app, generation)
   143  }
   144  
   145  func DeleteTeambotEKForTest(mctx libkb.MetaContext, teamID keybase1.TeamID,
   146  	generation keybase1.EkGeneration) error {
   147  	if err := deleteTeambotKeyForTest(mctx, teamID, keybase1.TeamApplication_CHAT, int(generation), true /* isEphemeral */); err != nil {
   148  		return err
   149  	}
   150  	return mctx.G().GetTeambotEKBoxStorage().Delete(mctx, teamID, generation)
   151  }
   152  
   153  func deleteTeambotKeyForTest(mctx libkb.MetaContext, teamID keybase1.TeamID,
   154  	app keybase1.TeamApplication, generation int, isEphemeral bool) error {
   155  	apiArg := libkb.APIArg{
   156  		Endpoint:    "teambot/delete_for_test",
   157  		SessionType: libkb.APISessionTypeREQUIRED,
   158  		Args: libkb.HTTPArgs{
   159  			"team_id":      libkb.S{Val: string(teamID)},
   160  			"application":  libkb.I{Val: int(app)},
   161  			"generation":   libkb.U{Val: uint64(generation)},
   162  			"is_ephemeral": libkb.B{Val: isEphemeral},
   163  		},
   164  	}
   165  	_, err := mctx.G().GetAPI().Post(mctx, apiArg)
   166  	return err
   167  }