github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/ephemeral/device_ek_storage_test.go (about)

     1  package ephemeral
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/keybase/client/go/kbtest"
    10  	"github.com/keybase/client/go/libkb"
    11  	"github.com/keybase/client/go/protocol/keybase1"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  func TestDeviceEKStorage(t *testing.T) {
    16  	tc := libkb.SetupTest(t, "ephemeral", 2)
    17  	defer tc.Cleanup()
    18  
    19  	_, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
    20  	require.NoError(t, err)
    21  
    22  	mctx := libkb.NewMetaContextForTest(tc)
    23  	s := NewDeviceEKStorage(mctx)
    24  
    25  	now := time.Now()
    26  	testKeys := []keybase1.DeviceEk{
    27  		{
    28  			Seed: keybase1.Bytes32(libkb.MakeByte32([]byte("deviceekseed-deviceekseed-devic0"))),
    29  			Metadata: keybase1.DeviceEkMetadata{
    30  				Generation:  0,
    31  				HashMeta:    keybase1.HashMeta("fakeHashMeta0"),
    32  				Kid:         "",
    33  				Ctime:       keybase1.ToTime(now.Add(-libkb.MaxEphemeralKeyStaleness * 3)),
    34  				DeviceCtime: keybase1.ToTime(now.Add(-libkb.MaxEphemeralKeyStaleness * 3)),
    35  			},
    36  		},
    37  		{
    38  			Seed: keybase1.Bytes32(libkb.MakeByte32([]byte("deviceekseed-deviceekseed-devic1"))),
    39  			Metadata: keybase1.DeviceEkMetadata{
    40  				Generation:  1,
    41  				HashMeta:    keybase1.HashMeta("fakeHashMeta1"),
    42  				Kid:         "",
    43  				Ctime:       keybase1.ToTime(now.Add(-libkb.MaxEphemeralKeyStaleness * 3)),
    44  				DeviceCtime: keybase1.ToTime(now.Add(-libkb.MaxEphemeralKeyStaleness * 3)),
    45  			},
    46  		},
    47  		{
    48  			Seed: keybase1.Bytes32(libkb.MakeByte32([]byte("deviceekseed-deviceekseed-devic2"))),
    49  			Metadata: keybase1.DeviceEkMetadata{
    50  				Generation:  2,
    51  				HashMeta:    keybase1.HashMeta("fakeHashMeta2"),
    52  				Kid:         "",
    53  				Ctime:       keybase1.ToTime(now),
    54  				DeviceCtime: keybase1.ToTime(now),
    55  			},
    56  		},
    57  		{
    58  			Seed: keybase1.Bytes32(libkb.MakeByte32([]byte("deviceekseed-deviceekseed-devic3"))),
    59  			Metadata: keybase1.DeviceEkMetadata{
    60  				Generation:  3,
    61  				HashMeta:    keybase1.HashMeta("fakeHashMeta3"),
    62  				Kid:         "",
    63  				Ctime:       keybase1.ToTime(now),
    64  				DeviceCtime: keybase1.ToTime(now),
    65  			},
    66  		},
    67  	}
    68  
    69  	merkleRootPtr, err := tc.G.GetMerkleClient().FetchRootFromServer(mctx, libkb.EphemeralKeyMerkleFreshness)
    70  	require.NoError(t, err)
    71  	merkleRoot := *merkleRootPtr
    72  
    73  	for _, test := range testKeys {
    74  		err := s.Put(mctx, test.Metadata.Generation, test)
    75  		require.NoError(t, err)
    76  
    77  		deviceEK, err := s.Get(mctx, test.Metadata.Generation)
    78  		require.NoError(t, err)
    79  		require.Equal(t, test, deviceEK)
    80  	}
    81  
    82  	// corrupt a key in storage and ensure we get the right error back
    83  	corruptedGeneration := keybase1.EkGeneration(3)
    84  	ek, err := s.Get(mctx, corruptedGeneration)
    85  	require.NoError(t, err)
    86  
    87  	ek.Metadata.Generation = 100
    88  	err = s.Put(mctx, corruptedGeneration, ek)
    89  	require.Error(t, err)
    90  	require.IsType(t, EphemeralKeyError{}, err)
    91  	ekErr := err.(EphemeralKeyError)
    92  	expectedErr := newEKCorruptedErr(mctx, DeviceEKKind, corruptedGeneration, 100)
    93  	require.Equal(t, expectedErr.Error(), ekErr.Error())
    94  	require.Equal(t, DefaultHumanErrMsg, ekErr.HumanError())
    95  
    96  	// Test GetAll
    97  	deviceEKs, err := s.GetAll(mctx)
    98  	require.NoError(t, err)
    99  
   100  	require.Equal(t, len(deviceEKs), len(testKeys))
   101  	for _, test := range testKeys {
   102  		deviceEK, ok := deviceEKs[test.Metadata.Generation]
   103  		require.True(t, ok)
   104  		require.Equal(t, deviceEK, test)
   105  	}
   106  
   107  	// Test Delete
   108  	require.NoError(t, s.Delete(mctx, 2, ""))
   109  
   110  	deviceEK, err := s.Get(mctx, 2)
   111  	require.Error(t, err)
   112  	require.IsType(t, libkb.UnboxError{}, err)
   113  	require.Equal(t, keybase1.DeviceEk{}, deviceEK)
   114  
   115  	// Test Get nonexistent
   116  	nonexistent, err := s.Get(mctx, keybase1.EkGeneration(len(testKeys)+1))
   117  	require.Error(t, err)
   118  	require.IsType(t, libkb.UnboxError{}, err)
   119  	require.Equal(t, keybase1.DeviceEk{}, nonexistent)
   120  
   121  	// include the cached error in the max
   122  	maxGeneration, err := s.MaxGeneration(mctx, true)
   123  	require.NoError(t, err)
   124  	require.EqualValues(t, keybase1.EkGeneration(len(testKeys)+1), maxGeneration)
   125  
   126  	// Test MaxGeneration
   127  	maxGeneration, err = s.MaxGeneration(mctx, false)
   128  	require.NoError(t, err)
   129  	require.EqualValues(t, 3, maxGeneration)
   130  	s.ClearCache()
   131  
   132  	require.NoError(t, s.Delete(mctx, 3, ""))
   133  
   134  	maxGeneration, err = s.MaxGeneration(mctx, false)
   135  	require.NoError(t, err)
   136  	require.EqualValues(t, 1, maxGeneration)
   137  
   138  	// Test delete expired and check that we handle incorrect eldest seqno
   139  	// deletion correctly.
   140  	uv, err := tc.G.GetMeUV(context.TODO())
   141  	require.NoError(t, err)
   142  	erasableStorage := libkb.NewFileErasableKVStore(mctx, deviceEKSubDir, deviceEKKeygen)
   143  
   144  	// First, let's drop a deviceEK for a different user, this shouldn't be deleted
   145  	badUserKey := fmt.Sprintf("%s-%s-%s-0.ek", deviceEKPrefix, mctx.G().Env.GetUsername()+"x", uv.EldestSeqno)
   146  	err = erasableStorage.Put(mctx, badUserKey, keybase1.DeviceEk{})
   147  	require.NoError(t, err)
   148  
   149  	// Now let's add a file with a wrong eldest seqno
   150  	badEldestSeqnoKey := fmt.Sprintf("%s-%s-%s-0.ek", deviceEKPrefix, mctx.G().Env.GetUsername(), uv.EldestSeqno+1)
   151  	err = erasableStorage.Put(mctx, badEldestSeqnoKey, keybase1.DeviceEk{})
   152  	require.NoError(t, err)
   153  
   154  	expected := []keybase1.EkGeneration{0, 1}
   155  	expired, err := s.DeleteExpired(mctx, merkleRoot)
   156  	require.NoError(t, err)
   157  	require.Equal(t, expected, expired)
   158  
   159  	deviceEKsAfterDeleteExpired, err := s.GetAll(mctx)
   160  	require.NoError(t, err)
   161  
   162  	require.Len(t, deviceEKsAfterDeleteExpired, 0)
   163  
   164  	var badUserDeviceEK keybase1.DeviceEk
   165  	err = erasableStorage.Get(mctx, badUserKey, &badUserDeviceEK)
   166  	require.NoError(t, err)
   167  	require.Equal(t, badUserDeviceEK, keybase1.DeviceEk{})
   168  
   169  	var badEldestSeqnoDeviceEK keybase1.DeviceEk
   170  	err = erasableStorage.Get(mctx, badEldestSeqnoKey, &badEldestSeqnoDeviceEK)
   171  	require.Error(t, err)
   172  	require.IsType(t, libkb.UnboxError{}, err)
   173  	require.Equal(t, badEldestSeqnoDeviceEK, keybase1.DeviceEk{})
   174  
   175  	// Verify we store failures in the cache
   176  	t.Logf("cache failures")
   177  	nonexistent, err = s.Get(mctx, maxGeneration+1)
   178  	require.Error(t, err)
   179  	require.IsType(t, libkb.UnboxError{}, err)
   180  	require.Equal(t, keybase1.DeviceEk{}, nonexistent)
   181  
   182  	cache, err := s.getCache(mctx)
   183  	require.NoError(t, err)
   184  	require.Len(t, cache, 1)
   185  
   186  	cacheItem, ok := cache[maxGeneration+1]
   187  	require.True(t, ok)
   188  	require.Error(t, cacheItem.Err)
   189  	require.IsType(t, libkb.UnboxError{}, cacheItem.Err)
   190  }
   191  
   192  // If we change the key format intentionally, we have to introduce some form of
   193  // migration or versioning between the keys. This test should blow up if we
   194  // break it unintentionally.
   195  func TestDeviceEKStorageKeyFormat(t *testing.T) {
   196  	tc := libkb.SetupTest(t, "ephemeral", 2)
   197  	defer tc.Cleanup()
   198  
   199  	_, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
   200  	require.NoError(t, err)
   201  
   202  	mctx := libkb.NewMetaContextForTest(tc)
   203  	s := NewDeviceEKStorage(mctx)
   204  
   205  	generation := keybase1.EkGeneration(1)
   206  	uv, err := tc.G.GetMeUV(context.TODO())
   207  	require.NoError(t, err)
   208  
   209  	key, err := s.key(mctx, generation)
   210  	require.NoError(t, err)
   211  	expected := fmt.Sprintf("deviceEphemeralKey-%s-%s-%d.ek", mctx.G().Env.GetUsername(), uv.EldestSeqno, generation)
   212  	require.Equal(t, expected, key)
   213  }
   214  
   215  func TestDeleteExpiredOffline(t *testing.T) {
   216  	tc := libkb.SetupTest(t, "ephemeral", 2)
   217  	defer tc.Cleanup()
   218  	_, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
   219  	require.NoError(t, err)
   220  	mctx := libkb.NewMetaContextForTest(tc)
   221  	s := NewDeviceEKStorage(mctx)
   222  
   223  	now := time.Now()
   224  	expiredTestKeys := []keybase1.DeviceEk{
   225  		{
   226  			Seed: keybase1.Bytes32(libkb.MakeByte32([]byte("deviceekseed-deviceekseed-devic0"))),
   227  			Metadata: keybase1.DeviceEkMetadata{
   228  				Generation: 0,
   229  				HashMeta:   keybase1.HashMeta("fakeHashMeta0"),
   230  				Kid:        "",
   231  				Ctime:      keybase1.ToTime(now.Add(-libkb.MaxEphemeralKeyStaleness * 3)),
   232  				// Although we are 'offline' and can't get a merkleRoot, we
   233  				// correctly delete this key since we fall back to the Ctime
   234  				DeviceCtime: -1,
   235  			},
   236  		},
   237  		{
   238  			Seed: keybase1.Bytes32(libkb.MakeByte32([]byte("deviceekseed-deviceekseed-devic1"))),
   239  			Metadata: keybase1.DeviceEkMetadata{
   240  				Generation:  1,
   241  				HashMeta:    keybase1.HashMeta("fakeHashMeta1"),
   242  				Kid:         "",
   243  				Ctime:       keybase1.ToTime(now.Add(-libkb.MaxEphemeralKeyStaleness * 3)),
   244  				DeviceCtime: keybase1.ToTime(now.Add(-libkb.MaxEphemeralKeyStaleness * 3)),
   245  			},
   246  		},
   247  		{
   248  			Seed: keybase1.Bytes32(libkb.MakeByte32([]byte("deviceekseed-deviceekseed-devic2"))),
   249  			Metadata: keybase1.DeviceEkMetadata{
   250  				Generation:  2,
   251  				HashMeta:    keybase1.HashMeta("fakeHashMeta2"),
   252  				Kid:         "",
   253  				Ctime:       keybase1.ToTime(now),
   254  				DeviceCtime: keybase1.ToTime(now),
   255  			},
   256  		},
   257  	}
   258  
   259  	for _, test := range expiredTestKeys {
   260  		err := s.Put(mctx, test.Metadata.Generation, test)
   261  		require.NoError(t, err)
   262  	}
   263  
   264  	expected := []keybase1.EkGeneration{0, 1}
   265  	expired, err := s.DeleteExpired(mctx, libkb.MerkleRoot{})
   266  	require.NoError(t, err)
   267  	require.Equal(t, expected, expired)
   268  
   269  	deviceEKsAfterDeleteExpired, err := s.GetAll(mctx)
   270  	require.NoError(t, err)
   271  
   272  	require.Len(t, deviceEKsAfterDeleteExpired, 1)
   273  }
   274  
   275  func TestDeviceEKStorageDeleteExpiredKeys(t *testing.T) {
   276  	tc := libkb.SetupTest(t, "ephemeral", 2)
   277  	defer tc.Cleanup()
   278  
   279  	mctx := libkb.NewMetaContextForTest(tc)
   280  	s := NewDeviceEKStorage(mctx)
   281  
   282  	now := time.Now()
   283  
   284  	// Test empty
   285  	expired := s.getExpiredGenerations(mctx, make(keyExpiryMap), now)
   286  	expected := []keybase1.EkGeneration(nil)
   287  	require.Equal(t, expected, expired)
   288  
   289  	// Test with a single key that is not expired
   290  	keyMap := keyExpiryMap{
   291  		0: keybase1.ToTime(now),
   292  	}
   293  	expired = s.getExpiredGenerations(mctx, keyMap, now)
   294  	expected = nil
   295  	require.Equal(t, expected, expired)
   296  
   297  	// Test with a single key that is stale but not expired
   298  	keyMap = keyExpiryMap{
   299  		0: keybase1.ToTime(now.Add(-libkb.MaxEphemeralKeyStaleness)),
   300  	}
   301  	expired = s.getExpiredGenerations(mctx, keyMap, now)
   302  	expected = nil
   303  	require.Equal(t, expected, expired)
   304  
   305  	// Test with a single key that is expired
   306  	keyMap = keyExpiryMap{
   307  		0: keybase1.ToTime(now.Add(-(2*libkb.MaxEphemeralKeyStaleness + libkb.MinEphemeralKeyLifetime))),
   308  	}
   309  	expired = s.getExpiredGenerations(mctx, keyMap, now)
   310  	expected = []keybase1.EkGeneration{0}
   311  	require.Equal(t, expected, expired)
   312  
   313  	// Test with one stale and one expired key
   314  	keyMap = keyExpiryMap{
   315  		0: keybase1.ToTime(now.Add(-(2*libkb.MaxEphemeralKeyStaleness + libkb.MinEphemeralKeyLifetime))),
   316  		1: keybase1.ToTime(now.Add(-(libkb.MaxEphemeralKeyStaleness + libkb.MinEphemeralKeyLifetime))),
   317  	}
   318  	expired = s.getExpiredGenerations(mctx, keyMap, now)
   319  	expected = []keybase1.EkGeneration{0}
   320  	require.Equal(t, expected, expired)
   321  
   322  	// Test with one expired key, one stale, and one that has reached libkb.MinEphemeralKeyLifetime
   323  	keyMap = keyExpiryMap{
   324  		0: keybase1.ToTime(now.Add(-(2*libkb.MaxEphemeralKeyStaleness + libkb.MinEphemeralKeyLifetime))),
   325  		1: keybase1.ToTime(now.Add(-(libkb.MaxEphemeralKeyStaleness + libkb.MinEphemeralKeyLifetime))),
   326  		2: keybase1.ToTime(now.Add(-libkb.MinEphemeralKeyLifetime)),
   327  	}
   328  	expired = s.getExpiredGenerations(mctx, keyMap, now)
   329  	expected = []keybase1.EkGeneration{0}
   330  	require.Equal(t, expected, expired)
   331  
   332  	// edge of deletion
   333  	expired = s.getExpiredGenerations(mctx, keyMap, now.Add(-time.Second))
   334  	expected = nil
   335  	require.Equal(t, expected, expired)
   336  
   337  	// Test multiple gaps, only the last key is valid though.
   338  	keyMap = make(keyExpiryMap)
   339  	numKeys := 5
   340  	for i := 0; i < numKeys; i++ {
   341  		keyMap[keybase1.EkGeneration((numKeys - i - 1))] = keybase1.ToTime(now.Add(-(libkb.MaxEphemeralKeyStaleness*time.Duration(i) + libkb.MinEphemeralKeyLifetime)))
   342  	}
   343  	expired = s.getExpiredGenerations(mctx, keyMap, now)
   344  	expected = []keybase1.EkGeneration{0, 1, 2}
   345  	require.Equal(t, expected, expired)
   346  
   347  	// Test case from bug
   348  	now = keybase1.Time(1528818944000).Time()
   349  	keyMap = keyExpiryMap{
   350  		46: 1528207927000,
   351  		47: 1528294344000,
   352  		48: 1528382176000,
   353  		49: 1528472751000,
   354  		50: 1528724605000,
   355  		51: 1528811030000,
   356  	}
   357  	expired = s.getExpiredGenerations(mctx, keyMap, now)
   358  	expected = nil
   359  	require.Equal(t, expected, expired)
   360  }