github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/teams/rotate_hidden_test.go (about) 1 package teams 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 8 "github.com/keybase/client/go/engine" 9 "github.com/keybase/client/go/libkb" 10 "github.com/keybase/client/go/protocol/keybase1" 11 "github.com/keybase/clockwork" 12 "github.com/stretchr/testify/require" 13 ) 14 15 func TestRotateHiddenSelf(t *testing.T) { 16 tc, owner, other, _, name := memberSetupMultiple(t) 17 defer tc.Cleanup() 18 19 err := SetRoleWriter(context.TODO(), tc.G, name, other.Username) 20 require.NoError(t, err) 21 team, err := GetForTestByStringName(context.TODO(), tc.G, name) 22 require.NoError(t, err) 23 require.Equal(t, keybase1.PerTeamKeyGeneration(1), team.Generation()) 24 25 secretBefore := team.Data.PerTeamKeySeedsUnverified[team.Generation()].Seed.ToBytes() 26 keys1, err := team.AllApplicationKeys(context.TODO(), keybase1.TeamApplication_CHAT) 27 require.NoError(t, err) 28 require.Equal(t, len(keys1), 1) 29 require.Equal(t, keys1[0].KeyGeneration, keybase1.PerTeamKeyGeneration(1)) 30 31 err = team.Rotate(context.TODO(), keybase1.RotationType_VISIBLE) 32 require.NoError(t, err) 33 after, err := GetForTestByStringName(context.TODO(), tc.G, name) 34 require.NoError(t, err) 35 require.Equal(t, keybase1.PerTeamKeyGeneration(2), after.Generation()) 36 secretAfter := after.Data.PerTeamKeySeedsUnverified[after.Generation()].Seed.ToBytes() 37 require.False(t, libkb.SecureByteArrayEq(secretAfter, secretBefore)) 38 assertRole(tc, name, owner.Username, keybase1.TeamRole_OWNER) 39 assertRole(tc, name, other.Username, keybase1.TeamRole_WRITER) 40 41 keys2, err := after.AllApplicationKeys(context.TODO(), keybase1.TeamApplication_CHAT) 42 require.NoError(t, err) 43 require.Equal(t, len(keys2), 2) 44 require.Equal(t, keys2[0].KeyGeneration, keybase1.PerTeamKeyGeneration(1)) 45 require.Equal(t, keys1[0].Key, keys2[0].Key) 46 47 for i := 0; i < 3; i++ { 48 team, err = GetForTestByStringName(context.TODO(), tc.G, name) 49 require.NoError(t, err) 50 err = team.Rotate(context.TODO(), keybase1.RotationType_HIDDEN) 51 require.NoError(t, err) 52 team, err = GetForTestByStringName(context.TODO(), tc.G, name) 53 require.NoError(t, err) 54 err = team.Rotate(context.TODO(), keybase1.RotationType_VISIBLE) 55 require.NoError(t, err) 56 } 57 58 team, err = GetForTestByStringName(context.TODO(), tc.G, name) 59 require.NoError(t, err) 60 keys3, err := team.AllApplicationKeys(context.TODO(), keybase1.TeamApplication_CHAT) 61 require.NoError(t, err) 62 require.Equal(t, len(keys3), 8) 63 require.Equal(t, keys3[0].KeyGeneration, keybase1.PerTeamKeyGeneration(1)) 64 require.Equal(t, keys1[0].Key, keys3[0].Key) 65 require.Equal(t, keys2[1].Key, keys3[1].Key) 66 } 67 68 func TestRotateHiddenOther(t *testing.T) { 69 fus, tcs, cleanup := setupNTests(t, 2) 70 defer cleanup() 71 72 t.Logf("u0 creates a team (seqno:1)") 73 teamName, teamID := createTeam2(*tcs[0]) 74 75 t.Logf("U0 adds U1 to the team (2)") 76 _, err := AddMember(context.TODO(), tcs[0].G, teamName.String(), fus[1].Username, keybase1.TeamRole_ADMIN, nil) 77 require.NoError(t, err) 78 79 ctx := context.TODO() 80 numKeys := 1 81 82 rotate := func(h bool) { 83 g := tcs[0].G 84 team, err := GetForTestByID(ctx, g, teamID) 85 require.NoError(t, err) 86 typ := keybase1.RotationType_VISIBLE 87 if h { 88 typ = keybase1.RotationType_HIDDEN 89 } 90 err = team.rotate(ctx, typ) 91 require.NoError(t, err) 92 numKeys++ 93 } 94 95 checkForUser := func(i int) { 96 g := tcs[i].G 97 team, err := GetForTestByID(ctx, g, teamID) 98 require.NoError(t, err) 99 keys, err := team.AllApplicationKeys(context.TODO(), keybase1.TeamApplication_CHAT) 100 require.NoError(t, err) 101 require.Equal(t, len(keys), numKeys) 102 } 103 104 check := func() { 105 checkForUser(0) 106 checkForUser(1) 107 } 108 109 for i := 0; i < 5; i++ { 110 rotate(i%2 == 0) 111 check() 112 } 113 114 mctx1 := libkb.NewMetaContext(ctx, tcs[1].G) 115 ch, err := tcs[1].G.GetHiddenTeamChainManager().Load(mctx1, teamID) 116 require.NoError(t, err) 117 require.Equal(t, keybase1.Seqno(2), ch.RatchetSet.Ratchets[keybase1.RatchetType_MAIN].Triple.Seqno) 118 } 119 120 func TestRotateHiddenOtherFTL(t *testing.T) { 121 fus, tcs, cleanup := setupNTests(t, 2) 122 defer cleanup() 123 124 t.Logf("u0 creates a team (seqno:1)") 125 teamName, teamID := createTeam2(*tcs[0]) 126 127 t.Logf("U0 adds U1 to the team (2)") 128 _, err := AddMember(context.TODO(), tcs[0].G, teamName.String(), fus[1].Username, keybase1.TeamRole_ADMIN, nil) 129 require.NoError(t, err) 130 131 ctx := context.TODO() 132 keyGen := keybase1.PerTeamKeyGeneration(1) 133 134 rotate := func(h bool) { 135 g := tcs[0].G 136 team, err := GetForTestByID(ctx, g, teamID) 137 require.NoError(t, err) 138 typ := keybase1.RotationType_VISIBLE 139 if h { 140 typ = keybase1.RotationType_HIDDEN 141 } 142 err = team.rotate(ctx, typ) 143 require.NoError(t, err) 144 keyGen++ 145 } 146 147 checkForUser := func(i int, forceRefresh bool) { 148 mctx := libkb.NewMetaContext(ctx, tcs[i].G) 149 arg := keybase1.FastTeamLoadArg{ 150 ID: teamID, 151 Applications: []keybase1.TeamApplication{keybase1.TeamApplication_CHAT}, 152 NeedLatestKey: true, 153 ForceRefresh: forceRefresh, 154 } 155 team, err := mctx.G().GetFastTeamLoader().Load(mctx, arg) 156 require.NoError(t, err) 157 require.Equal(t, 1, len(team.ApplicationKeys)) 158 require.Equal(t, keyGen, team.ApplicationKeys[0].KeyGeneration) 159 } 160 161 check := func() { 162 checkForUser(0, true) 163 checkForUser(1, true) 164 } 165 166 for i := 0; i < 5; i++ { 167 rotate(i%2 == 0) 168 check() 169 } 170 171 // Also test the gregor-powered refresh mechanism. We're going to mock out the gregor message for now. 172 rotate(true) 173 mctx1 := libkb.NewMetaContext(ctx, tcs[1].G) 174 err = tcs[1].G.GetHiddenTeamChainManager().HintLatestSeqno(mctx1, teamID, keybase1.Seqno(4)) 175 require.NoError(t, err) 176 checkForUser(1, false) 177 178 ch, err := tcs[1].G.GetHiddenTeamChainManager().Load(mctx1, teamID) 179 require.NoError(t, err) 180 require.Equal(t, keybase1.Seqno(2), ch.RatchetSet.Ratchets[keybase1.RatchetType_MAIN].Triple.Seqno) 181 } 182 183 func TestRotateHiddenImplicitAdmin(t *testing.T) { 184 tc, _, _, _, _, sub := memberSetupSubteam(t) 185 defer tc.Cleanup() 186 team, err := GetForTestByStringName(context.TODO(), tc.G, sub) 187 require.NoError(t, err) 188 require.EqualValues(t, 1, team.Generation()) 189 err = team.Rotate(context.TODO(), keybase1.RotationType_HIDDEN) 190 require.NoError(t, err) 191 } 192 193 // Wait for the BG auditor to finish up, and then we'll make sure that the 194 func pollForTrue(t *testing.T, g *libkb.GlobalContext, poller func(i int) bool) { 195 // Hopefully this is enough for slow CI but you never know. 196 wait := 10 * time.Millisecond * libkb.CITimeMultiplier(g) 197 found := false 198 for i := 0; i < 10; i++ { 199 if poller(i) { 200 found = true 201 break 202 } 203 g.Log.Debug("Didn't get an update; waiting %s more", wait) 204 time.Sleep(wait) 205 wait *= 2 206 } 207 require.True(t, found, "whether condition was satisfied after polling ended") 208 } 209 210 func TestHiddenNeedRotate(t *testing.T) { 211 fus, tcs, cleanup := setupNTests(t, 2) 212 defer cleanup() 213 _, bU := fus[0], fus[1] 214 aTc, bTc := tcs[0], tcs[1] 215 aM, bM := libkb.NewMetaContextForTest(*aTc), libkb.NewMetaContextForTest(*bTc) 216 217 clock := clockwork.NewFakeClockAt(aM.G().Clock().Now()) 218 aM.G().SetClock(clock) 219 220 t.Logf("A creates team") 221 teamName, teamID := createTeam2(*aTc) 222 223 t.Logf("adding B as admin") 224 _, err := AddMember(aM.Ctx(), aTc.G, teamName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil) 225 require.NoError(t, err) 226 227 t.Logf("B rotates the team once (via hidden)") 228 team, err := GetForTestByID(bM.Ctx(), bM.G(), teamID) 229 require.NoError(t, err) 230 typ := keybase1.RotationType_HIDDEN 231 err = team.rotate(bM.Ctx(), typ) 232 require.NoError(t, err) 233 234 t.Logf("B adds a paper key so he doesn't go down to 0 keys after revoke") 235 uis := libkb.UIs{ 236 LogUI: bTc.G.UI.GetLogUI(), 237 LoginUI: &libkb.TestLoginUI{}, 238 SecretUI: &libkb.TestSecretUI{}, 239 } 240 eng := engine.NewPaperKey(bTc.G) 241 err = engine.RunEngine2(bM.WithUIs(uis), eng) 242 require.NoError(t, err) 243 244 loadPTKGen := func(m libkb.MetaContext) keybase1.PerTeamKeyGeneration { 245 // A now loads the team and it should trigger a BG audit 246 team, err := Load(m.Ctx(), m.G(), keybase1.LoadTeamArg{ID: teamID, Public: false, ForceRepoll: true}) 247 require.NoError(t, err) 248 return team.Generation() 249 } 250 251 prevGen := loadPTKGen(aM) 252 253 t.Logf("B self-revoke the device that just rotated") 254 rEng := engine.NewRevokeDeviceEngine(bM.G(), engine.RevokeDeviceEngineArgs{ 255 ID: bM.G().ActiveDevice.DeviceID(), 256 ForceSelf: true, 257 }) 258 err = engine.RunEngine2(bM.WithUIs(uis), rEng) 259 require.NoError(t, err) 260 261 // Time out the UPAK cache so that now, when we check this user against this team, 262 // we'll see that his key is revoked. 263 t.Logf("A is checking that the team didn't rotate") 264 clock.Advance(libkb.CachedUserTimeout + time.Second) 265 genAfterRevoke := loadPTKGen(aM) 266 require.Equal(t, prevGen, genAfterRevoke) 267 268 // There's a random backoff before strating the background audit, so advance past that. 269 t.Logf("A is checking that eventually he rotates") 270 271 pollForTrue(t, aM.G(), func(i int) bool { 272 gen := loadPTKGen(aM) 273 return gen > prevGen 274 }) 275 276 // Allow writes from box auditor to finish up; not really necessary, but makes the logs 277 // look nicer. 278 time.Sleep(10 * time.Millisecond) 279 } 280 281 // See Y2K-611, the scenario we are testing is: 282 // 283 // visible[1] - root - PTK[1] 284 // visible[2] - add member 285 // visible[3] - rotate - PTK[2] 286 // hidden[1] - rotate - PTK[3] 287 // 288 // Now let's say we FTL load this team first, and then full load it. We'll be slotting in 289 // the PTK at generation 2 after we've loaded PTK at generation 3 via the hidden chain. 290 func TestHiddenRotateOtherFTLThenSlowLoad(t *testing.T) { 291 292 fus, tcs, cleanup := setupNTests(t, 2) 293 defer cleanup() 294 295 t.Logf("u0 creates a team (seqno:1)") 296 teamName, teamID := createTeam2(*tcs[0]) 297 298 ctx := context.TODO() 299 300 t.Logf("U0 adds U1 to the team (2)") 301 _, err := AddMember(ctx, tcs[0].G, teamName.String(), fus[1].Username, keybase1.TeamRole_ADMIN, nil) 302 require.NoError(t, err) 303 304 rot := func(typ keybase1.RotationType) { 305 team, err := GetForTestByID(ctx, tcs[0].G, teamID) 306 require.NoError(t, err) 307 err = team.rotate(ctx, typ) 308 require.NoError(t, err) 309 } 310 311 t.Logf("U0 rotates the team once (via visible)") 312 rot(keybase1.RotationType_VISIBLE) 313 t.Logf("U0 rotates the team once (via hidden)") 314 rot(keybase1.RotationType_HIDDEN) 315 316 mctx := libkb.NewMetaContextForTest(*tcs[1]) 317 farg := keybase1.FastTeamLoadArg{ 318 ID: teamID, 319 NeedLatestKey: true, 320 Applications: []keybase1.TeamApplication{keybase1.TeamApplication_CHAT}, 321 } 322 _, err = tcs[1].G.GetFastTeamLoader().Load(mctx, farg) 323 require.NoError(t, err) 324 team, err := Load(ctx, tcs[1].G, keybase1.LoadTeamArg{ID: teamID, Public: false, ForceRepoll: true}) 325 require.NoError(t, err) 326 require.Equal(t, team.Generation(), keybase1.PerTeamKeyGeneration(3)) 327 } 328 329 // See Y2K-679, the scenario we are testing is: 330 // 331 // visible[1] - root - PTK[1] 332 // visible[2] - add member 333 // hidden[1] - rotate - PTK[2] 334 // hidden[2] - rotate - PTK[3] 335 // hidden[3] - rotate - PTK[4] 336 // hidden[4] - rotate - PTK[5] 337 // 338 // Then we: 339 // - FTL generations 1,2,4 (leaving a hole at 3) 340 // - Full load the team with hidden_low=1 and low=0 341 func TestHiddenFTLHole(t *testing.T) { 342 343 fus, tcs, cleanup := setupNTests(t, 2) 344 defer cleanup() 345 346 t.Logf("u0 creates a team (seqno:1)") 347 teamName, teamID := createTeam2(*tcs[0]) 348 349 ctx := context.TODO() 350 351 t.Logf("U0 adds U1 to the team (2)") 352 _, err := AddMember(ctx, tcs[0].G, teamName.String(), fus[1].Username, keybase1.TeamRole_ADMIN, nil) 353 require.NoError(t, err) 354 355 rot := func(typ keybase1.RotationType) { 356 team, err := GetForTestByID(ctx, tcs[0].G, teamID) 357 require.NoError(t, err) 358 err = team.rotate(ctx, typ) 359 require.NoError(t, err) 360 } 361 362 t.Logf("U0 rotates the team 4x (via hidden)") 363 for i := 0; i < 4; i++ { 364 rot(keybase1.RotationType_HIDDEN) 365 } 366 367 mctx := libkb.NewMetaContextForTest(*tcs[1]) 368 farg := keybase1.FastTeamLoadArg{ 369 ID: teamID, 370 Applications: []keybase1.TeamApplication{keybase1.TeamApplication_CHAT}, 371 KeyGenerationsNeeded: []keybase1.PerTeamKeyGeneration{ 372 keybase1.PerTeamKeyGeneration(1), 373 keybase1.PerTeamKeyGeneration(2), 374 keybase1.PerTeamKeyGeneration(4), 375 }, 376 } 377 _, err = tcs[1].G.GetFastTeamLoader().Load(mctx, farg) 378 require.NoError(t, err) 379 team, err := Load(ctx, tcs[1].G, keybase1.LoadTeamArg{ID: teamID, Public: false, ForceRepoll: true}) 380 require.NoError(t, err) 381 require.Equal(t, team.Generation(), keybase1.PerTeamKeyGeneration(5)) 382 }