github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/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  }