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 }