github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/ephemeral/teambot_ek.go (about)

     1  package ephemeral
     2  
     3  import (
     4  	"crypto/hmac"
     5  	"crypto/sha256"
     6  	"encoding/json"
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/keybase/client/go/kbcrypto"
    11  	"github.com/keybase/client/go/libkb"
    12  	"github.com/keybase/client/go/protocol/gregor1"
    13  	"github.com/keybase/client/go/protocol/keybase1"
    14  	"github.com/keybase/client/go/teambot"
    15  	"github.com/keybase/client/go/teams"
    16  )
    17  
    18  type TeambotEKSeed keybase1.Bytes32
    19  
    20  func (s *TeambotEKSeed) DeriveDHKey() *libkb.NaclDHKeyPair {
    21  	return deriveDHKey(keybase1.Bytes32(*s), libkb.DeriveReasonTeambotEKEncryption)
    22  }
    23  
    24  func deriveTeambotEKFromTeamEK(mctx libkb.MetaContext, teamEK keybase1.TeamEk, botUID keybase1.UID) TeambotEKSeed {
    25  	hasher := hmac.New(sha256.New, teamEK.Seed[:])
    26  	_, _ = hasher.Write(botUID.ToBytes())
    27  	_, _ = hasher.Write([]byte(libkb.EncryptionReasonTeambotEphemeralKey))
    28  	return TeambotEKSeed(libkb.MakeByte32(hasher.Sum(nil)))
    29  }
    30  
    31  func newTeambotEKSeedFromBytes(b []byte) (s TeambotEKSeed, err error) {
    32  	seed, err := newEKSeedFromBytes(b)
    33  	if err != nil {
    34  		return s, err
    35  	}
    36  	return TeambotEKSeed(seed), nil
    37  }
    38  
    39  type TeambotEphemeralKeyer struct{}
    40  
    41  var _ EphemeralKeyer = (*TeambotEphemeralKeyer)(nil)
    42  
    43  func NewTeambotEphemeralKeyer() *TeambotEphemeralKeyer {
    44  	return &TeambotEphemeralKeyer{}
    45  }
    46  
    47  func (k *TeambotEphemeralKeyer) Type() keybase1.TeamEphemeralKeyType {
    48  	return keybase1.TeamEphemeralKeyType_TEAMBOT
    49  }
    50  
    51  func postNewTeambotEK(mctx libkb.MetaContext, teamID keybase1.TeamID, sig, box string) (err error) {
    52  	defer mctx.Trace("postNewTeambotEK", &err)()
    53  
    54  	apiArg := libkb.APIArg{
    55  		Endpoint:    "teambot/key",
    56  		SessionType: libkb.APISessionTypeREQUIRED,
    57  		Args: libkb.HTTPArgs{
    58  			"team_id":      libkb.S{Val: string(teamID)},
    59  			"sig":          libkb.S{Val: sig},
    60  			"box":          libkb.S{Val: box},
    61  			"is_ephemeral": libkb.B{Val: true},
    62  			"application":  libkb.I{Val: int(keybase1.TeamApplication_CHAT)},
    63  		},
    64  		AppStatusCodes: []int{libkb.SCOk, libkb.SCTeambotKeyGenerationExists},
    65  	}
    66  	_, err = mctx.G().GetAPI().Post(mctx, apiArg)
    67  	return err
    68  }
    69  
    70  func prepareNewTeambotEK(mctx libkb.MetaContext, team *teams.Team, botUID keybase1.UID,
    71  	seed TeambotEKSeed, metadata *keybase1.TeambotEkMetadata,
    72  	merkleRoot libkb.MerkleRoot) (sig string, box *keybase1.TeambotEkBoxed, err error) {
    73  	defer mctx.Trace("prepareNewTeambotEK", &err)()
    74  
    75  	statement, _, _, err := fetchUserEKStatement(mctx, botUID)
    76  	if err != nil {
    77  		return "", nil, err
    78  	}
    79  	activeMetadataMap, err := activeUserEKMetadata(mctx, map[keybase1.UID]*keybase1.UserEkStatement{botUID: statement}, merkleRoot)
    80  	if err != nil {
    81  		return "", nil, err
    82  	} else if len(activeMetadataMap) == 0 {
    83  		mctx.Debug("unable to make teambot key, bot has no active user EKs")
    84  		return "", nil, nil
    85  	}
    86  	activeMetadata := activeMetadataMap[botUID]
    87  	metadata.UserEkGeneration = activeMetadata.Generation
    88  
    89  	// Encrypting with a nil sender means we'll generate a random sender
    90  	// private key.
    91  	recipientKey, err := libkb.ImportKeypairFromKID(activeMetadata.Kid)
    92  	if err != nil {
    93  		return "", nil, err
    94  	}
    95  	boxedSeed, err := recipientKey.EncryptToString(seed[:], nil)
    96  	if err != nil {
    97  		return "", nil, err
    98  	}
    99  
   100  	boxed := keybase1.TeambotEkBoxed{
   101  		Box:      boxedSeed,
   102  		Metadata: *metadata,
   103  	}
   104  
   105  	metadataJSON, err := json.Marshal(metadata)
   106  	if err != nil {
   107  		return "", nil, err
   108  	}
   109  
   110  	signingKey, err := team.SigningKey(mctx.Ctx())
   111  	if err != nil {
   112  		return "", nil, err
   113  	}
   114  	sig, _, err = signingKey.SignToString(metadataJSON)
   115  	if err != nil {
   116  		return "", nil, err
   117  	}
   118  	return sig, &boxed, nil
   119  }
   120  
   121  func fetchLatestTeambotEK(mctx libkb.MetaContext, teamID keybase1.TeamID) (metadata *keybase1.TeambotEkMetadata, wrongKID bool, err error) {
   122  	defer mctx.Trace("fetchLatestTeambotEK", &err)()
   123  
   124  	var parsedResponse teambot.TeambotKeyResponse
   125  	gotResult := false
   126  	for i := 0; i < 10; i++ {
   127  		apiArg := libkb.APIArg{
   128  			Endpoint:    "teambot/key",
   129  			SessionType: libkb.APISessionTypeREQUIRED,
   130  			Args: libkb.HTTPArgs{
   131  				"team_id":      libkb.S{Val: string(teamID)},
   132  				"is_ephemeral": libkb.B{Val: true},
   133  				"application":  libkb.I{Val: int(keybase1.TeamApplication_CHAT)},
   134  			},
   135  		}
   136  		res, err := mctx.G().GetAPI().Get(mctx, apiArg)
   137  		if err != nil {
   138  			return nil, false, err
   139  		}
   140  
   141  		if err = res.Body.UnmarshalAgain(&parsedResponse); err != nil {
   142  			return nil, false, err
   143  		}
   144  		if parsedResponse.Result == nil {
   145  			mctx.Debug("no teambot ek response, trying again, attempt: %d", i)
   146  			time.Sleep(time.Second)
   147  			continue
   148  		}
   149  		gotResult = true
   150  		break
   151  	}
   152  	if !gotResult {
   153  		return nil, false, nil
   154  	}
   155  	return verifyTeambotSigWithLatestPTK(mctx, teamID, parsedResponse.Result.Sig)
   156  }
   157  
   158  func extractTeambotEKMetadataFromSig(sig string) (*kbcrypto.NaclSigningKeyPublic, *keybase1.TeambotEkMetadata, error) {
   159  	signerKey, payload, _, err := kbcrypto.NaclVerifyAndExtract(sig)
   160  	if err != nil {
   161  		return signerKey, nil, err
   162  	}
   163  
   164  	parsedMetadata := keybase1.TeambotEkMetadata{}
   165  	if err = json.Unmarshal(payload, &parsedMetadata); err != nil {
   166  		return signerKey, nil, err
   167  	}
   168  	return signerKey, &parsedMetadata, nil
   169  }
   170  
   171  // Verify that the blob is validly signed, and that the signing key is the
   172  // given team's latest PTK, then parse its contents.
   173  func verifyTeambotSigWithLatestPTK(mctx libkb.MetaContext, teamID keybase1.TeamID, sig string) (
   174  	metadata *keybase1.TeambotEkMetadata, wrongKID bool, err error) {
   175  	defer mctx.Trace("verifyTeambotSigWithLatestPTK", &err)()
   176  
   177  	signerKey, metadata, err := extractTeambotEKMetadataFromSig(sig)
   178  	if err != nil {
   179  		return nil, false, err
   180  	}
   181  
   182  	team, err := teams.Load(mctx.Ctx(), mctx.G(), keybase1.LoadTeamArg{
   183  		ID: teamID,
   184  	})
   185  	if err != nil {
   186  		return nil, false, err
   187  	}
   188  
   189  	// Verify the signing key corresponds to the latest PTK. We load the team's
   190  	// from cache, but if the KID doesn't match, we try a forced reload to see
   191  	// if the cache might've been stale. Only if the KID still doesn't match
   192  	// after the reload do we complain.
   193  	teamSigningKID, err := team.SigningKID(mctx.Ctx())
   194  	if err != nil {
   195  		return nil, false, err
   196  	}
   197  	if !teamSigningKID.Equal(signerKey.GetKID()) {
   198  		// The latest PTK might be stale. Force a reload, then check this over again.
   199  		team, err := teams.Load(mctx.Ctx(), mctx.G(), keybase1.LoadTeamArg{
   200  			ID:          team.ID,
   201  			ForceRepoll: true,
   202  		})
   203  		if err != nil {
   204  			return nil, false, err
   205  		}
   206  		teamSigningKID, err = team.SigningKID(mctx.Ctx())
   207  		if err != nil {
   208  			return nil, false, err
   209  		}
   210  		// return the metdata with wrongKID=true
   211  		if !teamSigningKID.Equal(signerKey.GetKID()) {
   212  			return metadata, true, fmt.Errorf("teambotEK returned for PTK signing KID %s, but latest is %s",
   213  				signerKey.GetKID(), teamSigningKID)
   214  		}
   215  	}
   216  
   217  	// If we didn't short circuit above, then the signing key is correct.
   218  	return metadata, false, nil
   219  }
   220  
   221  func (k *TeambotEphemeralKeyer) Unbox(mctx libkb.MetaContext, boxed keybase1.TeamEphemeralKeyBoxed,
   222  	contentCtime *gregor1.Time) (ek keybase1.TeamEphemeralKey, err error) {
   223  	defer mctx.Trace(fmt.Sprintf("TeambotEphemeralKeyer#Unbox: teambotEKGeneration: %v", boxed.Generation()), &err)()
   224  
   225  	typ, err := boxed.KeyType()
   226  	if err != nil {
   227  		return ek, err
   228  	}
   229  	if !typ.IsTeambot() {
   230  		return ek, NewIncorrectTeamEphemeralKeyTypeError(typ, keybase1.TeamEphemeralKeyType_TEAMBOT)
   231  	}
   232  
   233  	teambotEKBoxed := boxed.Teambot()
   234  	teambotEKGeneration := teambotEKBoxed.Metadata.Generation
   235  	userEKBoxStorage := mctx.G().GetUserEKBoxStorage()
   236  	userEK, err := userEKBoxStorage.Get(mctx, teambotEKBoxed.Metadata.UserEkGeneration, contentCtime)
   237  	if err != nil {
   238  		mctx.Debug("unable to get from userEKStorage %v", err)
   239  		if _, ok := err.(EphemeralKeyError); ok {
   240  			return ek, newEKUnboxErr(mctx, TeambotEKKind, teambotEKGeneration, UserEKKind,
   241  				teambotEKBoxed.Metadata.UserEkGeneration, contentCtime)
   242  		}
   243  		return ek, err
   244  	}
   245  
   246  	userSeed := UserEKSeed(userEK.Seed)
   247  	userKeypair := userSeed.DeriveDHKey()
   248  
   249  	msg, _, err := userKeypair.DecryptFromString(teambotEKBoxed.Box)
   250  	if err != nil {
   251  		mctx.Debug("unable to decrypt teambotEKBoxed %v", err)
   252  		return ek, newEKUnboxErr(mctx, TeambotEKKind, teambotEKGeneration, UserEKKind,
   253  			teambotEKBoxed.Metadata.UserEkGeneration, contentCtime)
   254  	}
   255  
   256  	seed, err := newTeambotEKSeedFromBytes(msg)
   257  	if err != nil {
   258  		return ek, err
   259  	}
   260  
   261  	keypair := seed.DeriveDHKey()
   262  	if !keypair.GetKID().Equal(teambotEKBoxed.Metadata.Kid) {
   263  		return ek, fmt.Errorf("Failed to verify server given seed [%s] against signed KID [%s]. Box: %+v",
   264  			teambotEKBoxed.Metadata.Kid, keypair.GetKID(), teambotEKBoxed)
   265  	}
   266  
   267  	return keybase1.NewTeamEphemeralKeyWithTeambot(keybase1.TeambotEk{
   268  		Seed:     keybase1.Bytes32(seed),
   269  		Metadata: teambotEKBoxed.Metadata,
   270  	}), nil
   271  }
   272  
   273  func (k *TeambotEphemeralKeyer) Fetch(mctx libkb.MetaContext, teamID keybase1.TeamID, generation keybase1.EkGeneration,
   274  	contentCtime *gregor1.Time) (teambotEK keybase1.TeamEphemeralKeyBoxed, err error) {
   275  	apiArg := libkb.APIArg{
   276  		Endpoint:    "teambot/box",
   277  		SessionType: libkb.APISessionTypeREQUIRED,
   278  		Args: libkb.HTTPArgs{
   279  			"team_id":      libkb.S{Val: string(teamID)},
   280  			"generation":   libkb.U{Val: uint64(generation)},
   281  			"is_ephemeral": libkb.B{Val: true},
   282  			"application":  libkb.I{Val: int(keybase1.TeamApplication_CHAT)},
   283  		},
   284  	}
   285  
   286  	var resp teambot.TeambotKeyBoxedResponse
   287  	res, err := mctx.G().GetAPI().Get(mctx, apiArg)
   288  	if err != nil {
   289  		err = errFromAppStatus(err)
   290  		return teambotEK, err
   291  	}
   292  
   293  	if err = res.Body.UnmarshalAgain(&resp); err != nil {
   294  		return teambotEK, err
   295  	}
   296  
   297  	if resp.Result == nil {
   298  		err = newEKMissingBoxErr(mctx, TeambotEKKind, generation)
   299  		return teambotEK, err
   300  	}
   301  
   302  	// Although we verify the signature is valid, it's possible that this key
   303  	// was signed with a PTK that is not our latest and greatest. We allow this
   304  	// when we are using this ek for *decryption*. When getting a key for
   305  	// *encryption* callers are responsible for verifying the signature is
   306  	// signed by the latest PTK or requesting the generation of a new EK. This
   307  	// logic currently lives in ephemeral/lib.go#getLatestTeambotEK
   308  	_, metadata, err := extractTeambotEKMetadataFromSig(resp.Result.Sig)
   309  	if err != nil {
   310  		return teambotEK, err
   311  	} else if metadata == nil { // shouldn't happen
   312  		return teambotEK, fmt.Errorf("unable to fetch valid teambotEKMetadata")
   313  	}
   314  
   315  	if generation != metadata.Generation {
   316  		// sanity check that we got the right generation
   317  		return teambotEK, newEKCorruptedErr(mctx, TeambotEKKind, generation, metadata.Generation)
   318  	}
   319  	teambotEKBoxed := keybase1.TeambotEkBoxed{
   320  		Box:      resp.Result.Box,
   321  		Metadata: *metadata,
   322  	}
   323  	return keybase1.NewTeamEphemeralKeyBoxedWithTeambot(teambotEKBoxed), nil
   324  }