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 }