github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/ephemeral/lib_test.go (about)

     1  package ephemeral
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"os"
     7  	"path/filepath"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/keybase/client/go/engine"
    12  	"github.com/keybase/client/go/libkb"
    13  	"github.com/keybase/client/go/protocol/keybase1"
    14  	"github.com/keybase/client/go/teams"
    15  	"github.com/keybase/clockwork"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  func getNoiseFilePath(tc libkb.TestContext, key string) string {
    20  	noiseName := fmt.Sprintf("%s.ns", url.QueryEscape(key))
    21  	return filepath.Join(tc.G.Env.GetDataDir(), "eraseablekvstore", "device-eks", noiseName)
    22  }
    23  
    24  func TestKeygenIfNeeded(t *testing.T) {
    25  	tc, mctx, _ := ephemeralKeyTestSetup(t)
    26  	defer tc.Cleanup()
    27  
    28  	ekLib, ok := tc.G.GetEKLib().(*EKLib)
    29  	require.True(t, ok)
    30  	deviceEKStorage := tc.G.GetDeviceEKStorage()
    31  	userEKBoxStorage := tc.G.GetUserEKBoxStorage()
    32  	err := ekLib.KeygenIfNeeded(mctx)
    33  	require.NoError(t, err)
    34  
    35  	expectedDeviceEKGen, err := deviceEKStorage.MaxGeneration(mctx, false)
    36  	require.NoError(t, err)
    37  	if expectedDeviceEKGen < 0 {
    38  		expectedDeviceEKGen = 1
    39  		deviceEKNeeded, err := ekLib.NewDeviceEKNeeded(mctx)
    40  		require.NoError(t, err)
    41  		require.True(t, deviceEKNeeded)
    42  	}
    43  
    44  	expectedUserEKGen, err := userEKBoxStorage.MaxGeneration(mctx, false)
    45  	require.NoError(t, err)
    46  	if expectedUserEKGen < 0 {
    47  		expectedUserEKGen = 1
    48  		userEKNeeded, err := ekLib.NewUserEKNeeded(mctx)
    49  		require.NoError(t, err)
    50  		require.True(t, userEKNeeded)
    51  	}
    52  
    53  	keygen := func(expectedDeviceEKGen, expectedUserEKGen keybase1.EkGeneration) {
    54  		err := ekLib.KeygenIfNeeded(mctx)
    55  		require.NoError(t, err)
    56  
    57  		// verify deviceEK
    58  		deviceEKNeeded, err := ekLib.NewDeviceEKNeeded(mctx)
    59  		require.NoError(t, err)
    60  		require.False(t, deviceEKNeeded)
    61  
    62  		deviceEKMaxGen, err := deviceEKStorage.MaxGeneration(mctx, false)
    63  		require.NoError(t, err)
    64  		require.Equal(t, expectedDeviceEKGen, deviceEKMaxGen)
    65  
    66  		// verify userEK
    67  		userEKNeeded, err := ekLib.NewUserEKNeeded(mctx)
    68  		require.NoError(t, err)
    69  		require.False(t, userEKNeeded)
    70  
    71  		userEKMaxGen, err := userEKBoxStorage.MaxGeneration(mctx, false)
    72  		require.NoError(t, err)
    73  		require.Equal(t, expectedUserEKGen, userEKMaxGen)
    74  	}
    75  
    76  	// If we retry keygen, we don't regenerate keys
    77  	t.Logf("Initial keygen")
    78  	keygen(expectedDeviceEKGen, expectedUserEKGen)
    79  	t.Logf("Keygen again does not create new keys")
    80  	keygen(expectedDeviceEKGen, expectedUserEKGen)
    81  
    82  	rawDeviceEKStorage := NewDeviceEKStorage(mctx)
    83  	rawUserEKBoxStorage := NewUserEKBoxStorage()
    84  
    85  	// Let's purge our local userEK store and make sure we don't regenerate
    86  	// (respecting the server max)
    87  	err = rawUserEKBoxStorage.Delete(mctx, expectedUserEKGen)
    88  	require.NoError(t, err)
    89  	userEKBoxStorage.ClearCache()
    90  	keygen(expectedDeviceEKGen, expectedUserEKGen)
    91  
    92  	// Now let's kill our deviceEK as well by deleting the noise file, we
    93  	// should regenerate a new userEK since we can't access the old one
    94  	key, err := rawDeviceEKStorage.key(mctx, expectedDeviceEKGen)
    95  	require.NoError(t, err)
    96  	noiseFilePath := getNoiseFilePath(tc, key)
    97  	err = os.Remove(noiseFilePath)
    98  	require.NoError(t, err)
    99  
   100  	deviceEKStorage.ClearCache()
   101  	expectedDeviceEKGen++
   102  	expectedUserEKGen++
   103  	t.Logf("Keygen with corrupted deviceEK works")
   104  	keygen(expectedDeviceEKGen, expectedUserEKGen)
   105  
   106  	// Test ForceDeleteAll
   107  	err = deviceEKStorage.ForceDeleteAll(mctx, tc.G.Env.GetUsername())
   108  	require.NoError(t, err)
   109  	deviceEKs, err := rawDeviceEKStorage.GetAll(mctx)
   110  	require.NoError(t, err)
   111  	require.Len(t, deviceEKs, 0)
   112  }
   113  
   114  func TestNewTeamEKNeeded(t *testing.T) {
   115  	tc, mctx, _ := ephemeralKeyTestSetup(t)
   116  	defer tc.Cleanup()
   117  
   118  	teamID := createTeam(tc)
   119  	ekLib, ok := tc.G.GetEKLib().(*EKLib)
   120  	require.True(t, ok)
   121  	fc := clockwork.NewFakeClockAt(time.Now())
   122  	ekLib.SetClock(fc)
   123  	deviceEKStorage := tc.G.GetDeviceEKStorage()
   124  	userEKBoxStorage := tc.G.GetUserEKBoxStorage()
   125  	teamEKBoxStorage := tc.G.GetTeamEKBoxStorage()
   126  
   127  	// We don't have any keys, so we should need a new teamEK
   128  	needed, err := ekLib.NewTeamEKNeeded(mctx, teamID)
   129  	require.NoError(t, err)
   130  	require.True(t, needed)
   131  
   132  	expectedTeamEKGen, err := teamEKBoxStorage.MaxGeneration(mctx, teamID, false)
   133  	require.NoError(t, err)
   134  	if expectedTeamEKGen < 0 {
   135  		expectedTeamEKGen = 1
   136  	}
   137  
   138  	expectedDeviceEKGen, err := deviceEKStorage.MaxGeneration(mctx, false)
   139  	require.NoError(t, err)
   140  	if expectedDeviceEKGen < 0 {
   141  		expectedDeviceEKGen = 1
   142  	}
   143  
   144  	expectedUserEKGen, err := userEKBoxStorage.MaxGeneration(mctx, false)
   145  	require.NoError(t, err)
   146  	if expectedUserEKGen < 0 {
   147  		expectedUserEKGen = 1
   148  	}
   149  
   150  	assertKeyGenerations := func(expectedDeviceEKGen, expectedUserEKGen, expectedTeamEKGen keybase1.EkGeneration, shouldCreate, teamEKCreationInProgress bool) {
   151  		teamEK, created, err := ekLib.GetOrCreateLatestTeamEK(mctx, teamID)
   152  		require.NoError(t, err)
   153  		require.Equal(t, shouldCreate, created)
   154  
   155  		// verify the ekLib teamEKGenCache is working
   156  		cacheKey := ekLib.cacheKey(teamID, keybase1.TeamEphemeralKeyType_TEAM)
   157  		val, ok := ekLib.teamEKGenCache.Get(cacheKey)
   158  		require.True(t, ok)
   159  		cacheEntry, expired := ekLib.isEntryExpired(val)
   160  		require.False(t, expired)
   161  		require.NotNil(t, cacheEntry)
   162  		require.Equal(t, teamEKCreationInProgress, cacheEntry.CreationInProgress)
   163  		require.Equal(t, teamEK.Generation(), cacheEntry.Generation)
   164  
   165  		// verify deviceEK
   166  		deviceEKNeeded, err := ekLib.NewDeviceEKNeeded(mctx)
   167  		require.NoError(t, err)
   168  		require.False(t, deviceEKNeeded)
   169  
   170  		deviceEKMaxGen, err := deviceEKStorage.MaxGeneration(mctx, false)
   171  		require.NoError(t, err)
   172  		require.Equal(t, expectedDeviceEKGen, deviceEKMaxGen)
   173  
   174  		// verify userEK
   175  		userEKNeeded, err := ekLib.NewUserEKNeeded(mctx)
   176  		require.NoError(t, err)
   177  		require.False(t, userEKNeeded)
   178  
   179  		userEKMaxGen, err := userEKBoxStorage.MaxGeneration(mctx, false)
   180  		require.NoError(t, err)
   181  		require.Equal(t, expectedUserEKGen, userEKMaxGen)
   182  
   183  		// verify teamEK
   184  		teamEKGen, err := teamEKBoxStorage.MaxGeneration(mctx, teamID, false)
   185  		require.NoError(t, err)
   186  		require.Equal(t, expectedTeamEKGen, teamEKGen)
   187  		require.Equal(t, expectedTeamEKGen, teamEK.Generation())
   188  
   189  		teamEKNeeded, err := ekLib.NewTeamEKNeeded(mctx, teamID)
   190  		require.NoError(t, err)
   191  		require.False(t, teamEKNeeded)
   192  	}
   193  
   194  	assertKeyGenerations(expectedDeviceEKGen, expectedUserEKGen, expectedTeamEKGen, true /*created*/, false /* teamEKCreationInProgress */)
   195  	// If we retry keygen, we don't regenerate keys
   196  	assertKeyGenerations(expectedDeviceEKGen, expectedUserEKGen, expectedTeamEKGen, false /*created*/, false /* teamEKCreationInProgress */)
   197  
   198  	rawDeviceEKStorage := NewDeviceEKStorage(mctx)
   199  	rawUserEKBoxStorage := NewUserEKBoxStorage()
   200  	rawTeamEKBoxStorage := NewTeamEKBoxStorage(NewTeamEphemeralKeyer())
   201  
   202  	// Let's purge our local teamEK store and make sure we don't regenerate
   203  	// (respecting the server max)
   204  	err = rawTeamEKBoxStorage.Delete(mctx, teamID, expectedTeamEKGen)
   205  	require.NoError(t, err)
   206  	teamEKBoxStorage.ClearCache()
   207  	assertKeyGenerations(expectedDeviceEKGen, expectedUserEKGen, expectedTeamEKGen, false /*created */, false /* teamEKCreationInProgress */)
   208  
   209  	// Now let's kill our userEK, we should gracefully not regenerate
   210  	// since we can still fetch the userEK from the server.
   211  	err = rawUserEKBoxStorage.Delete(mctx, expectedUserEKGen)
   212  	require.NoError(t, err)
   213  	tc.G.GetDeviceEKStorage().ClearCache()
   214  	assertKeyGenerations(expectedDeviceEKGen, expectedUserEKGen, expectedTeamEKGen, false /*created*/, false /* teamEKCreationInProgress */)
   215  
   216  	// Now let's kill our deviceEK as well, and we should generate all new keys
   217  	err = rawDeviceEKStorage.Delete(mctx, expectedDeviceEKGen, "")
   218  	require.NoError(t, err)
   219  	tc.G.GetDeviceEKStorage().ClearCache()
   220  	expectedDeviceEKGen++
   221  	expectedUserEKGen++
   222  	expectedTeamEKGen++
   223  	assertKeyGenerations(expectedDeviceEKGen, expectedUserEKGen, expectedTeamEKGen, true /*created*/, false /* teamEKCreationInProgress */)
   224  
   225  	// If we try to access an older teamEK that we cannot access, we don't
   226  	// create a new teamEK
   227  	teamEK, err := ekLib.GetTeamEK(mctx, teamID, expectedTeamEKGen-1, nil)
   228  	require.Error(t, err)
   229  	require.IsType(t, EphemeralKeyError{}, err)
   230  	ekErr := err.(EphemeralKeyError)
   231  	require.Equal(t, DefaultHumanErrMsg, ekErr.HumanError())
   232  	require.Equal(t, teamEK, keybase1.TeamEphemeralKey{})
   233  	assertKeyGenerations(expectedDeviceEKGen, expectedUserEKGen, expectedTeamEKGen, false /*created*/, false /* teamEKCreationInProgress */)
   234  
   235  	// Now let's kill our deviceEK by corrupting a single bit in the noiseFile,
   236  	// so we can no longer access the latest teamEK and will generate a new one
   237  	// and verify it is the new valid max.
   238  	key, err := rawDeviceEKStorage.key(mctx, expectedDeviceEKGen)
   239  	require.NoError(t, err)
   240  	noiseFilePath := getNoiseFilePath(tc, key)
   241  	noise, err := os.ReadFile(noiseFilePath)
   242  	require.NoError(t, err)
   243  
   244  	// flip one bit
   245  	corruptedNoise := make([]byte, len(noise))
   246  	copy(corruptedNoise, noise)
   247  	corruptedNoise[0] ^= 0x01
   248  
   249  	err = os.WriteFile(noiseFilePath, corruptedNoise, libkb.PermFile)
   250  	require.NoError(t, err)
   251  	tc.G.GetDeviceEKStorage().ClearCache()
   252  
   253  	ch := make(chan bool, 1)
   254  	ekLib.setBackgroundCreationTestCh(ch)
   255  	teamEK, err = ekLib.GetTeamEK(mctx, teamID, expectedTeamEKGen, nil)
   256  	require.Error(t, err)
   257  	require.IsType(t, EphemeralKeyError{}, err)
   258  	ekErr = err.(EphemeralKeyError)
   259  	require.Equal(t, DefaultHumanErrMsg, ekErr.HumanError())
   260  	require.Equal(t, teamEK, keybase1.TeamEphemeralKey{})
   261  	t.Logf("before expectedTeamEkGen: %v", expectedTeamEKGen)
   262  	select {
   263  	case created := <-ch:
   264  		require.True(t, created)
   265  	case <-time.After(time.Second * 20):
   266  		t.Fatalf("teamEK background creation failed")
   267  	}
   268  
   269  	expectedDeviceEKGen++
   270  	expectedUserEKGen++
   271  	expectedTeamEKGen++
   272  	t.Logf("after expectedTeamEkGen: %v", expectedTeamEKGen)
   273  	assertKeyGenerations(expectedDeviceEKGen, expectedUserEKGen, expectedTeamEKGen, false /*created*/, false /* teamEKCreationInProgress */)
   274  
   275  	// Fake the teamEK creation time so we are forced to generate a new one.
   276  	forceEKCtime := func(generation keybase1.EkGeneration, d time.Duration) {
   277  		_, err = rawTeamEKBoxStorage.Get(mctx, teamID, generation, nil)
   278  		require.NoError(t, err)
   279  		cache, found, err := rawTeamEKBoxStorage.getCacheForTeamID(mctx, teamID)
   280  		require.NoError(t, err)
   281  		require.True(t, found)
   282  		cacheItem, ok := cache[generation]
   283  		require.True(t, ok)
   284  		require.False(t, cacheItem.HasError())
   285  		boxed := cacheItem.TeamEKBoxed
   286  		typ, err := boxed.KeyType()
   287  		require.NoError(t, err)
   288  		require.True(t, typ.IsTeam())
   289  		teamEKBoxed := boxed.Team()
   290  		teamEKBoxed.Metadata.Ctime = keybase1.ToTime(teamEKBoxed.Metadata.Ctime.Time().Add(d))
   291  		err = teamEKBoxStorage.Put(mctx, teamID, generation,
   292  			keybase1.NewTeamEphemeralKeyBoxedWithTeam(teamEKBoxed))
   293  		require.NoError(t, err)
   294  	}
   295  
   296  	// First we ensure that we don't do background generation for expired teamEKs.
   297  	fc.Advance(LibCacheEntryLifetime) // expire our cache
   298  	forceEKCtime(expectedTeamEKGen, -libkb.EphemeralKeyGenInterval)
   299  	expectedTeamEKGen++
   300  	assertKeyGenerations(expectedDeviceEKGen, expectedUserEKGen, expectedTeamEKGen, true /*created*/, false /* teamEKCreationInProgress */)
   301  
   302  	// If we are *almost* expired, background generation is possible.
   303  	fc.Advance(LibCacheEntryLifetime) // expire our cache
   304  	forceEKCtime(expectedTeamEKGen, -libkb.EphemeralKeyGenInterval+30*time.Minute)
   305  	assertKeyGenerations(expectedDeviceEKGen, expectedUserEKGen, expectedTeamEKGen, false /*created*/, true /* teamEKCreationInProgress */)
   306  	assertKeyGenerations(expectedDeviceEKGen, expectedUserEKGen, expectedTeamEKGen, false /*created*/, true /* teamEKCreationInProgress */)
   307  	// Signal background generation should start
   308  	ch <- true
   309  
   310  	// Wait until background generation completes
   311  	select {
   312  	case created := <-ch:
   313  		require.True(t, created)
   314  	case <-time.After(time.Second * 20):
   315  		t.Fatalf("teamEK background creation failed")
   316  	}
   317  	close(ch)
   318  	expectedTeamEKGen++
   319  	assertKeyGenerations(expectedDeviceEKGen, expectedUserEKGen, expectedTeamEKGen, false /*created*/, false /* teamEKCreationInProgress */)
   320  }
   321  
   322  func TestCleanupStaleUserAndDeviceEKs(t *testing.T) {
   323  	tc, mctx, _ := ephemeralKeyTestSetup(t)
   324  	defer tc.Cleanup()
   325  
   326  	seed, err := newDeviceEphemeralSeed()
   327  	require.NoError(t, err)
   328  	s := tc.G.GetDeviceEKStorage()
   329  	ctimeExpired := time.Now().Add(-libkb.MaxEphemeralKeyStaleness * 3)
   330  	err = s.Put(mctx, 0, keybase1.DeviceEk{
   331  		Seed: keybase1.Bytes32(seed),
   332  		Metadata: keybase1.DeviceEkMetadata{
   333  			Ctime: keybase1.ToTime(ctimeExpired),
   334  		},
   335  	})
   336  	require.NoError(t, err)
   337  
   338  	ekLib, ok := tc.G.GetEKLib().(*EKLib)
   339  	require.True(t, ok)
   340  	err = ekLib.CleanupStaleUserAndDeviceEKs(mctx)
   341  	require.NoError(t, err)
   342  
   343  	deviceEK, err := s.Get(mctx, 0)
   344  	require.Error(t, err)
   345  	_, ok = err.(libkb.UnboxError)
   346  	require.True(t, ok)
   347  	require.Equal(t, keybase1.DeviceEk{}, deviceEK)
   348  
   349  	err = ekLib.CleanupStaleUserAndDeviceEKs(mctx)
   350  	require.NoError(t, err)
   351  }
   352  
   353  func TestCleanupStaleUserAndDeviceEKsOffline(t *testing.T) {
   354  	tc, mctx, _ := ephemeralKeyTestSetup(t)
   355  	defer tc.Cleanup()
   356  
   357  	seed, err := newDeviceEphemeralSeed()
   358  	require.NoError(t, err)
   359  	s := tc.G.GetDeviceEKStorage()
   360  	ctimeExpired := time.Now().Add(-libkb.MaxEphemeralKeyStaleness * 3)
   361  	err = s.Put(mctx, 0, keybase1.DeviceEk{
   362  		Seed: keybase1.Bytes32(seed),
   363  		Metadata: keybase1.DeviceEkMetadata{
   364  			Ctime:       keybase1.ToTime(ctimeExpired),
   365  			DeviceCtime: keybase1.ToTime(ctimeExpired),
   366  		},
   367  	})
   368  	require.NoError(t, err)
   369  
   370  	ekLib, ok := tc.G.GetEKLib().(*EKLib)
   371  	require.True(t, ok)
   372  	ch := make(chan bool, 1)
   373  	ekLib.setBackgroundDeleteTestCh(ch)
   374  	err = ekLib.keygenIfNeeded(mctx, libkb.MerkleRoot{}, true /* shouldCleanup */)
   375  	require.Error(t, err)
   376  	_, ok = err.(EphemeralKeyError)
   377  	require.False(t, ok)
   378  	require.Equal(t, SkipKeygenNilMerkleRoot, err.Error())
   379  
   380  	// Even though we return an error, we charge through on the deletion
   381  	// successfully.
   382  	<-ch
   383  	deviceEK, err := s.Get(mctx, 0)
   384  	require.Error(t, err)
   385  	_, ok = err.(libkb.UnboxError)
   386  	require.True(t, ok)
   387  	require.Equal(t, keybase1.DeviceEk{}, deviceEK)
   388  	err = ekLib.keygenIfNeeded(mctx, libkb.MerkleRoot{}, true /* shouldCleanup */)
   389  	require.Error(t, err)
   390  	_, ok = err.(libkb.UnboxError)
   391  	require.False(t, ok)
   392  	require.Equal(t, SkipKeygenNilMerkleRoot, err.Error())
   393  }
   394  
   395  func TestLoginOneshotWithEphemeral(t *testing.T) {
   396  	tc, mctx, user := ephemeralKeyTestSetup(t)
   397  	defer tc.Cleanup()
   398  	uis := libkb.UIs{
   399  		LogUI:    tc.G.UI.GetLogUI(),
   400  		LoginUI:  &libkb.TestLoginUI{RevokeBackup: false},
   401  		SecretUI: &libkb.TestSecretUI{},
   402  	}
   403  	mctx = mctx.WithUIs(uis)
   404  	teamID := createTeam(tc)
   405  
   406  	eng := engine.NewPaperKey(tc.G)
   407  	err := engine.RunEngine2(mctx, eng)
   408  	require.NoError(t, err)
   409  	require.NotZero(t, len(eng.Passphrase()))
   410  	require.NoError(t, tc.Logout())
   411  
   412  	keygenWithOneshot := func() (keybase1.DeviceEk, keybase1.UserEk, keybase1.TeamEphemeralKey) {
   413  		tc := libkb.SetupTest(t, "ephemeral", 2)
   414  		defer tc.Cleanup()
   415  		mctx := libkb.NewMetaContextForTest(tc)
   416  		NewEphemeralStorageAndInstall(mctx)
   417  		teams.ServiceInit(tc.G)
   418  
   419  		eng := engine.NewLoginOneshot(tc.G, keybase1.LoginOneshotArg{
   420  			Username: user.NormalizedUsername().String(),
   421  			PaperKey: eng.Passphrase(),
   422  		})
   423  		err = engine.RunEngine2(mctx, eng)
   424  		require.NoError(t, err)
   425  
   426  		err = tc.G.GetEKLib().KeygenIfNeeded(mctx)
   427  		require.NoError(t, err)
   428  
   429  		deks := tc.G.GetDeviceEKStorage()
   430  		dekGen, err := deks.MaxGeneration(mctx, false)
   431  		require.NoError(t, err)
   432  		dek, err := deks.Get(mctx, dekGen)
   433  		require.NoError(t, err)
   434  
   435  		ueks := tc.G.GetUserEKBoxStorage()
   436  		uekGen, err := ueks.MaxGeneration(mctx, false)
   437  		require.NoError(t, err)
   438  		uek, err := ueks.Get(mctx, uekGen, nil)
   439  		require.NoError(t, err)
   440  
   441  		tek, created, err := tc.G.GetEKLib().GetOrCreateLatestTeamEK(mctx, teamID)
   442  		require.NoError(t, err)
   443  		require.True(t, created)
   444  
   445  		return dek, uek, tek
   446  	}
   447  
   448  	dek, uek, tek := keygenWithOneshot()
   449  	dek2, uek2, tek2 := keygenWithOneshot()
   450  	require.NotEqual(t, dek, dek2)
   451  	require.NotEqual(t, uek, uek2)
   452  	require.NotEqual(t, tek, tek2)
   453  }