github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/ephemeral/team_ek.go (about) 1 package ephemeral 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 type TeamEKSeed keybase1.Bytes32 15 16 func newTeamEphemeralSeed() (seed TeamEKSeed, err error) { 17 randomSeed, err := makeNewRandomSeed() 18 if err != nil { 19 return seed, err 20 } 21 return TeamEKSeed(randomSeed), nil 22 } 23 24 func newTeamEKSeedFromBytes(b []byte) (s TeamEKSeed, err error) { 25 seed, err := newEKSeedFromBytes(b) 26 if err != nil { 27 return s, err 28 } 29 return TeamEKSeed(seed), nil 30 } 31 32 func (s *TeamEKSeed) DeriveDHKey() *libkb.NaclDHKeyPair { 33 return deriveDHKey(keybase1.Bytes32(*s), libkb.DeriveReasonTeamEKEncryption) 34 } 35 36 type TeamEphemeralKeyer struct{} 37 38 var _ EphemeralKeyer = (*TeamEphemeralKeyer)(nil) 39 40 func NewTeamEphemeralKeyer() *TeamEphemeralKeyer { 41 return &TeamEphemeralKeyer{} 42 } 43 44 func (k *TeamEphemeralKeyer) Type() keybase1.TeamEphemeralKeyType { 45 return keybase1.TeamEphemeralKeyType_TEAM 46 } 47 48 func postNewTeamEK(mctx libkb.MetaContext, teamID keybase1.TeamID, sig string, 49 boxes *[]keybase1.TeamEkBoxMetadata) (err error) { 50 defer mctx.Trace("postNewTeamEK", &err)() 51 52 boxesJSON, err := json.Marshal(*boxes) 53 if err != nil { 54 return err 55 } 56 apiArg := libkb.APIArg{ 57 Endpoint: "team/team_ek", 58 SessionType: libkb.APISessionTypeREQUIRED, 59 Args: libkb.HTTPArgs{ 60 "team_id": libkb.S{Val: string(teamID)}, 61 "sig": libkb.S{Val: sig}, 62 "boxes": libkb.S{Val: string(boxesJSON)}, 63 }, 64 } 65 _, err = mctx.G().GetAPI().Post(mctx, apiArg) 66 return err 67 } 68 69 func prepareNewTeamEK(mctx libkb.MetaContext, teamID keybase1.TeamID, 70 signingKey libkb.NaclSigningKeyPair, membersMetadata map[keybase1.UID]keybase1.UserEkMetadata, 71 merkleRoot libkb.MerkleRoot) (sig string, boxes *[]keybase1.TeamEkBoxMetadata, 72 metadata keybase1.TeamEkMetadata, myBox *keybase1.TeamEkBoxed, err error) { 73 defer mctx.Trace("prepareNewTeamEK", &err)() 74 75 seed, err := newTeamEphemeralSeed() 76 if err != nil { 77 return "", nil, metadata, nil, err 78 } 79 80 prevStatement, latestGeneration, wrongKID, err := fetchTeamEKStatement(mctx, teamID) 81 if !wrongKID && err != nil { 82 return "", nil, metadata, nil, err 83 } 84 var generation keybase1.EkGeneration 85 if prevStatement == nil { 86 // Even if the teamEK statement was signed by the wrong key (this can 87 // happen when legacy clients roll the PTK), fetchTeamEKStatement will 88 // return the generation number from the last (unverifiable) statement. 89 // If there was never any statement, latestGeneration will be 0, so 90 // adding one is correct in all cases. 91 generation = latestGeneration + 1 92 } else { 93 generation = prevStatement.CurrentTeamEkMetadata.Generation + 1 94 } 95 96 dhKeypair := seed.DeriveDHKey() 97 98 metadata = keybase1.TeamEkMetadata{ 99 Kid: dhKeypair.GetKID(), 100 Generation: generation, 101 HashMeta: merkleRoot.HashMeta(), 102 // The ctime is derivable from the hash meta, by fetching the hashed 103 // root from the server, but including it saves readers a potential 104 // extra round trip. 105 Ctime: keybase1.TimeFromSeconds(merkleRoot.Ctime()), 106 } 107 108 statement := keybase1.TeamEkStatement{ 109 CurrentTeamEkMetadata: metadata, 110 } 111 statementJSON, err := json.Marshal(statement) 112 if err != nil { 113 return "", nil, metadata, nil, err 114 } 115 116 sig, _, err = signingKey.SignToString(statementJSON) 117 if err != nil { 118 return "", nil, metadata, nil, err 119 } 120 121 teamEK := keybase1.TeamEk{ 122 Seed: keybase1.Bytes32(seed), 123 Metadata: metadata, 124 } 125 boxes, myTeamEKBoxed, err := boxTeamEKForUsers(mctx, membersMetadata, teamEK) 126 if err != nil { 127 return "", nil, metadata, nil, err 128 } 129 return sig, boxes, metadata, myTeamEKBoxed, nil 130 } 131 132 func publishNewTeamEK(mctx libkb.MetaContext, teamID keybase1.TeamID, 133 merkleRoot libkb.MerkleRoot, forceCreateGeneration *keybase1.EkGeneration) (metadata keybase1.TeamEkMetadata, err error) { 134 defer mctx.Trace("publishNewTeamEK", &err)() 135 136 team, err := teams.Load(mctx.Ctx(), mctx.G(), keybase1.LoadTeamArg{ 137 ID: teamID, 138 }) 139 if err != nil { 140 return metadata, err 141 } 142 signingKey, err := team.SigningKey(mctx.Ctx()) 143 if err != nil { 144 return metadata, err 145 } 146 147 statementMap, err := fetchTeamMemberStatements(mctx, teamID) 148 if err != nil { 149 return metadata, err 150 } 151 membersMetadata, err := activeUserEKMetadata(mctx, statementMap, merkleRoot) 152 if err != nil { 153 return metadata, err 154 } 155 156 sig, boxes, teamEKMetadata, myBox, err := prepareNewTeamEK(mctx, teamID, signingKey, membersMetadata, merkleRoot) 157 if err != nil { 158 return metadata, err 159 } 160 161 if forceCreateGeneration != nil { 162 if *forceCreateGeneration+1 != teamEKMetadata.Generation { 163 return metadata, fmt.Errorf("Not posting new teamEK, expected %d, found %d", *forceCreateGeneration+1, teamEKMetadata.Generation) 164 } 165 mctx.Debug("forceCreateGeneration set to: %d", *forceCreateGeneration) 166 } 167 168 if err = postNewTeamEK(mctx, teamID, sig, boxes); err != nil { 169 return metadata, err 170 } 171 172 if myBox == nil { 173 mctx.Debug("No box made for own teamEK") 174 } else { 175 storage := mctx.G().GetTeamEKBoxStorage() 176 boxed := keybase1.NewTeamEphemeralKeyBoxedWithTeam(*myBox) 177 if err = storage.Put(mctx, teamID, teamEKMetadata.Generation, boxed); err != nil { 178 return metadata, err 179 } 180 } 181 return teamEKMetadata, nil 182 } 183 184 func (k *TeamEphemeralKeyer) Fetch(mctx libkb.MetaContext, teamID keybase1.TeamID, generation keybase1.EkGeneration, contentCtime *gregor1.Time) (teamEK keybase1.TeamEphemeralKeyBoxed, err error) { 185 apiArg := libkb.APIArg{ 186 Endpoint: "team/team_ek_box", 187 SessionType: libkb.APISessionTypeREQUIRED, 188 Args: libkb.HTTPArgs{ 189 "team_id": libkb.S{Val: string(teamID)}, 190 "generation": libkb.U{Val: uint64(generation)}, 191 }, 192 } 193 194 var result TeamEKBoxedResponse 195 res, err := mctx.G().GetAPI().Get(mctx, apiArg) 196 if err != nil { 197 err = errFromAppStatus(err) 198 return teamEK, err 199 } 200 201 err = res.Body.UnmarshalAgain(&result) 202 if err != nil { 203 return teamEK, err 204 } 205 206 if result.Result == nil { 207 err = newEKMissingBoxErr(mctx, TeamEKKind, generation) 208 return teamEK, err 209 } 210 211 // Although we verify the signature is valid, it's possible that this key 212 // was signed with a PTK that is not our latest and greatest. We allow this 213 // when we are using this ek for *decryption*. When getting a key for 214 // *encryption* callers are responsible for verifying the signature is 215 // signed by the latest PTK or generating a new EK. This logic currently 216 // lives in ephemeral/lib.go#GetOrCreateLatestTeamEK (#newTeamEKNeeded) 217 _, teamEKStatement, err := extractTeamEKStatementFromSig(result.Result.Sig) 218 if err != nil { 219 return teamEK, err 220 } else if teamEKStatement == nil { // shouldn't happen 221 return teamEK, fmt.Errorf("unable to fetch valid teamEKStatement") 222 } 223 224 teamEKMetadata := teamEKStatement.CurrentTeamEkMetadata 225 if generation != teamEKMetadata.Generation { 226 // sanity check that we got the right generation 227 return teamEK, newEKCorruptedErr(mctx, TeamEKKind, generation, teamEKMetadata.Generation) 228 } 229 teamEKBoxed := keybase1.TeamEkBoxed{ 230 Box: result.Result.Box, 231 UserEkGeneration: result.Result.UserEKGeneration, 232 Metadata: teamEKMetadata, 233 } 234 235 return keybase1.NewTeamEphemeralKeyBoxedWithTeam(teamEKBoxed), nil 236 } 237 238 func (k *TeamEphemeralKeyer) Unbox(mctx libkb.MetaContext, boxed keybase1.TeamEphemeralKeyBoxed, 239 contentCtime *gregor1.Time) (ek keybase1.TeamEphemeralKey, err error) { 240 defer mctx.Trace(fmt.Sprintf("TeamEKBoxStorage#unbox: teamEKGeneration: %v", boxed.Generation()), 241 &err)() 242 243 typ, err := boxed.KeyType() 244 if err != nil { 245 return ek, err 246 } 247 if !typ.IsTeam() { 248 return ek, NewIncorrectTeamEphemeralKeyTypeError(typ, keybase1.TeamEphemeralKeyType_TEAM) 249 } 250 251 teamEKBoxed := boxed.Team() 252 teamEKGeneration := teamEKBoxed.Metadata.Generation 253 userEKBoxStorage := mctx.G().GetUserEKBoxStorage() 254 userEK, err := userEKBoxStorage.Get(mctx, teamEKBoxed.UserEkGeneration, contentCtime) 255 if err != nil { 256 mctx.Debug("unable to get from userEKStorage %v", err) 257 if _, ok := err.(EphemeralKeyError); ok { 258 return ek, newEKUnboxErr(mctx, TeamEKKind, teamEKGeneration, UserEKKind, 259 teamEKBoxed.UserEkGeneration, contentCtime) 260 } 261 return ek, err 262 } 263 264 userSeed := UserEKSeed(userEK.Seed) 265 userKeypair := userSeed.DeriveDHKey() 266 267 msg, _, err := userKeypair.DecryptFromString(teamEKBoxed.Box) 268 if err != nil { 269 mctx.Debug("unable to decrypt teamEKBoxed %v", err) 270 return ek, newEKUnboxErr(mctx, TeamEKKind, teamEKGeneration, UserEKKind, 271 teamEKBoxed.UserEkGeneration, contentCtime) 272 } 273 274 seed, err := newTeamEKSeedFromBytes(msg) 275 if err != nil { 276 return ek, err 277 } 278 279 keypair := seed.DeriveDHKey() 280 if !keypair.GetKID().Equal(teamEKBoxed.Metadata.Kid) { 281 return ek, fmt.Errorf("Failed to verify server given seed [%s] against signed KID [%s]. Box: %+v", 282 teamEKBoxed.Metadata.Kid, keypair.GetKID(), teamEKBoxed) 283 } 284 285 return keybase1.NewTeamEphemeralKeyWithTeam(keybase1.TeamEk{ 286 Seed: keybase1.Bytes32(seed), 287 Metadata: teamEKBoxed.Metadata, 288 }), nil 289 } 290 291 // There are plenty of race conditions where the PTK or teamEK or 292 // membership list can change out from under us while we're in the middle 293 // of posting a new key, causing the post to fail. Detect these conditions 294 // and retry. 295 func teamEKRetryWrapper(mctx libkb.MetaContext, retryFn func() error) (err error) { 296 for tries := 0; tries < maxRetries; tries++ { 297 if err = retryFn(); err == nil { 298 return nil 299 } 300 if !libkb.IsEphemeralRetryableError(err) { 301 return err 302 } 303 mctx.Debug("teamEKRetryWrapper found a retryable error on try %d: %v", 304 tries, err) 305 select { 306 case <-mctx.Ctx().Done(): 307 return mctx.Ctx().Err() 308 default: 309 // continue retrying 310 } 311 } 312 return err 313 } 314 315 func ForcePublishNewTeamEKForTesting(mctx libkb.MetaContext, teamID keybase1.TeamID, 316 merkleRoot libkb.MerkleRoot) (metadata keybase1.TeamEkMetadata, err error) { 317 defer mctx.Trace("ForcePublishNewTeamEKForTesting", &err)() 318 err = teamEKRetryWrapper(mctx, func() error { 319 metadata, err = publishNewTeamEK(mctx, teamID, merkleRoot, nil) 320 return err 321 }) 322 return metadata, err 323 } 324 325 func boxTeamEKForUsers(mctx libkb.MetaContext, usersMetadata map[keybase1.UID]keybase1.UserEkMetadata, 326 teamEK keybase1.TeamEk) (teamBoxes *[]keybase1.TeamEkBoxMetadata, myTeamEKBoxed *keybase1.TeamEkBoxed, err error) { 327 defer mctx.Trace("boxTeamEKForUsers", &err)() 328 329 myUID := mctx.G().Env.GetUID() 330 boxes := make([]keybase1.TeamEkBoxMetadata, 0, len(usersMetadata)) 331 for uid, metadata := range usersMetadata { 332 recipientKey, err := libkb.ImportKeypairFromKID(metadata.Kid) 333 if err != nil { 334 return nil, nil, err 335 } 336 // Encrypting with a nil sender means we'll generate a random sender private key. 337 box, err := recipientKey.EncryptToString(teamEK.Seed[:], nil) 338 if err != nil { 339 return nil, nil, err 340 } 341 boxMetadata := keybase1.TeamEkBoxMetadata{ 342 RecipientUID: uid, 343 RecipientGeneration: metadata.Generation, 344 Box: box, 345 } 346 boxes = append(boxes, boxMetadata) 347 348 if uid == myUID { 349 myTeamEKBoxed = &keybase1.TeamEkBoxed{ 350 Box: box, 351 UserEkGeneration: metadata.Generation, 352 Metadata: teamEK.Metadata, 353 } 354 } 355 } 356 return &boxes, myTeamEKBoxed, err 357 } 358 359 type teamEKStatementResponse struct { 360 Sig *string `json:"sig"` 361 } 362 363 // Returns nil if the team has never published a teamEK. If the team has 364 // published a teamEK, but has since rolled their PTK without publishing a new 365 // one, this function will also return nil and log a warning. This is a 366 // transitional thing, and eventually when all "reasonably up to date" clients 367 // in the wild have EK support, we will make that case an error. 368 func fetchTeamEKStatement(mctx libkb.MetaContext, teamID keybase1.TeamID) ( 369 statement *keybase1.TeamEkStatement, latestGeneration keybase1.EkGeneration, wrongKID bool, err error) { 370 defer mctx.Trace("fetchTeamEKStatement", &err)() 371 372 apiArg := libkb.APIArg{ 373 Endpoint: "team/team_ek", 374 SessionType: libkb.APISessionTypeREQUIRED, 375 Args: libkb.HTTPArgs{ 376 "team_id": libkb.S{Val: string(teamID)}, 377 }, 378 } 379 res, err := mctx.G().GetAPI().Get(mctx, apiArg) 380 if err != nil { 381 return nil, latestGeneration, false, err 382 } 383 384 parsedResponse := teamEKStatementResponse{} 385 err = res.Body.UnmarshalAgain(&parsedResponse) 386 if err != nil { 387 return nil, latestGeneration, false, err 388 } 389 390 // If the result field in the response is null, the server is saying that 391 // the team has never published a teamEKStatement, stale or otherwise. 392 if parsedResponse.Sig == nil { 393 mctx.Debug("team has no teamEKStatement at all") 394 return nil, latestGeneration, false, nil 395 } 396 397 statement, latestGeneration, wrongKID, err = verifySigWithLatestPTK(mctx, teamID, *parsedResponse.Sig) 398 // Check the wrongKID condition before checking the error, since an error 399 // is still returned in this case. TODO: Turn this warning into an error 400 // after EK support is sufficiently widespread. 401 if wrongKID { 402 mctx.Debug("It looks like someone rolled the PTK without generating new ephemeral keys. They might be on an old version.") 403 return nil, latestGeneration, true, nil 404 } else if err != nil { 405 return nil, latestGeneration, false, err 406 } 407 408 return statement, latestGeneration, false, nil 409 } 410 411 func extractTeamEKStatementFromSig(sig string) (signerKey *kbcrypto.NaclSigningKeyPublic, statement *keybase1.TeamEkStatement, err error) { 412 signerKey, payload, _, err := kbcrypto.NaclVerifyAndExtract(sig) 413 if err != nil { 414 return signerKey, nil, err 415 } 416 417 parsedStatement := keybase1.TeamEkStatement{} 418 if err = json.Unmarshal(payload, &parsedStatement); err != nil { 419 return signerKey, nil, err 420 } 421 return signerKey, &parsedStatement, nil 422 } 423 424 // Verify that the blob is validly signed, and that the signing key is the 425 // given team's latest PTK, then parse its contents. If the blob is signed by 426 // the wrong KID, that's still an error, but we'll also return this special 427 // `wrongKID` flag. As a transitional measure while we wait for all clients in 428 // the wild to have EK support, callers will treat that case as "there is no 429 // key" and convert the error to a warning. 430 func verifySigWithLatestPTK(mctx libkb.MetaContext, teamID keybase1.TeamID, 431 sig string) (statement *keybase1.TeamEkStatement, latestGeneration keybase1.EkGeneration, wrongKID bool, err error) { 432 defer mctx.Trace("verifySigWithLatestPTK", &err)() 433 434 // Parse the statement before we verify the signing key. Even if the 435 // signing key is bad (likely because of a legacy PTK roll that didn't 436 // include a teamEK statement), we'll still return the generation number. 437 signerKey, parsedStatement, err := extractTeamEKStatementFromSig(sig) 438 if err != nil { 439 return nil, latestGeneration, false, err 440 } 441 latestGeneration = parsedStatement.CurrentTeamEkMetadata.Generation 442 443 // Verify the signing key corresponds to the latest PTK. We load the team's 444 // from cache, but if the KID doesn't match, we try a forced reload to see 445 // if the cache might've been stale. Only if the KID still doesn't match 446 // after the reload do we complain. 447 team, err := teams.Load(mctx.Ctx(), mctx.G(), keybase1.LoadTeamArg{ 448 ID: teamID, 449 }) 450 if err != nil { 451 return nil, latestGeneration, false, err 452 } 453 teamSigningKey, err := team.SigningKey(mctx.Ctx()) 454 if err != nil { 455 return nil, latestGeneration, false, err 456 } 457 if !teamSigningKey.GetKID().Equal(signerKey.GetKID()) { 458 // The latest PTK might be stale. Force a reload, then check this over again. 459 team, err := teams.Load(mctx.Ctx(), mctx.G(), keybase1.LoadTeamArg{ 460 ID: teamID, 461 ForceRepoll: true, 462 }) 463 if err != nil { 464 return nil, latestGeneration, false, err 465 } 466 teamSigningKey, err = team.SigningKey(mctx.Ctx()) 467 if err != nil { 468 return nil, latestGeneration, false, err 469 } 470 if !teamSigningKey.GetKID().Equal(signerKey.GetKID()) { 471 return nil, latestGeneration, true, fmt.Errorf("teamEK returned for PTK signing KID %s, but latest is %s", 472 signerKey.GetKID(), teamSigningKey.GetKID()) 473 } 474 } 475 476 // If we didn't short circuit above, then the signing key is correct. 477 // Return the parsed statement. 478 return parsedStatement, latestGeneration, false, nil 479 } 480 481 type teamMemberEKStatementResponse struct { 482 Sigs map[keybase1.UID]string `json:"sigs"` 483 } 484 485 // Returns nil if all team members have never published a teamEK. Verifies that 486 // the map of users the server returns are indeed valid team members of the 487 // team and all signatures verify correctly for the users. 488 func fetchTeamMemberStatements(mctx libkb.MetaContext, 489 teamID keybase1.TeamID) (statements map[keybase1.UID]*keybase1.UserEkStatement, err error) { 490 defer mctx.Trace("fetchTeamMemberStatements", &err)() 491 492 apiArg := libkb.APIArg{ 493 Endpoint: "team/member_eks", 494 SessionType: libkb.APISessionTypeREQUIRED, 495 Args: libkb.HTTPArgs{ 496 "team_id": libkb.S{Val: string(teamID)}, 497 }, 498 } 499 res, err := mctx.G().GetAPI().Get(mctx, apiArg) 500 if err != nil { 501 return nil, err 502 } 503 504 memberEKStatements := teamMemberEKStatementResponse{} 505 if err = res.Body.UnmarshalAgain(&memberEKStatements); err != nil { 506 return nil, err 507 } 508 509 team, err := teams.Load(mctx.Ctx(), mctx.G(), keybase1.LoadTeamArg{ 510 ID: teamID, 511 }) 512 if err != nil { 513 return nil, err 514 } 515 var uids []keybase1.UID 516 for uid := range memberEKStatements.Sigs { 517 uids = append(uids, uid) 518 } 519 520 getArg := func(i int) *libkb.LoadUserArg { 521 if i >= len(uids) { 522 return nil 523 } 524 tmp := libkb.NewLoadUserArgWithMetaContext(mctx).WithUID(uids[i]) 525 return &tmp 526 } 527 528 var upaks []*keybase1.UserPlusKeysV2AllIncarnations 529 statements = make(map[keybase1.UID]*keybase1.UserEkStatement) 530 processResult := func(i int, upak *keybase1.UserPlusKeysV2AllIncarnations) error { 531 mctx.Debug("processing member %d/%d %.2f%% complete", i, len(uids), (float64(i) / float64(len(uids)) * 100)) 532 if upak == nil { 533 mctx.Debug("Unable to load user %v", uids[i]) 534 return nil 535 } 536 uv := upak.Current.ToUserVersion() 537 if !team.IsMember(mctx.Ctx(), uv) { 538 // Team membership may be stale, force a reload and check again 539 team, err = teams.Load(mctx.Ctx(), mctx.G(), keybase1.LoadTeamArg{ 540 ID: teamID, 541 ForceRepoll: true, 542 }) 543 if err != nil { 544 return err 545 } 546 if !team.IsMember(mctx.Ctx(), uv) { 547 mctx.Debug("%v is not a member of team %v", uv, teamID) 548 return nil 549 } 550 } 551 upaks = append(upaks, upak) 552 return nil 553 } 554 if err = mctx.G().GetUPAKLoader().Batcher(mctx.Ctx(), getArg, processResult, 0); err != nil { 555 return nil, err 556 } 557 for _, upak := range upaks { 558 uid := upak.GetUID() 559 sig, ok := memberEKStatements.Sigs[uid] 560 if !ok { 561 mctx.Debug("missing memberEK statement for UID %v in team %v", uid, teamID) 562 continue 563 } 564 statement, _, wrongKID, err := verifySigWithLatestPUK(mctx, uid, 565 upak.Current.GetLatestPerUserKey(), sig) 566 if wrongKID { 567 mctx.Debug("UID %v has a statement signed with the wrongKID, skipping", uid) 568 // Don't box for this member since they have no valid userEK 569 continue 570 } else if err != nil { 571 return nil, err 572 } 573 statements[uid] = statement 574 } 575 576 return statements, nil 577 }