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 }