github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/service/kvstore_test.go (about) 1 package service 2 3 import ( 4 "context" 5 "encoding/base64" 6 "encoding/json" 7 "fmt" 8 "regexp" 9 "strings" 10 "sync" 11 "testing" 12 13 "github.com/keybase/client/go/chat/signencrypt" 14 "github.com/keybase/client/go/kbtest" 15 "github.com/keybase/client/go/kvstore" 16 "github.com/keybase/client/go/libkb" 17 "github.com/keybase/client/go/msgpack" 18 keybase1 "github.com/keybase/client/go/protocol/keybase1" 19 "github.com/keybase/client/go/teams" 20 "github.com/stretchr/testify/require" 21 ) 22 23 func kvTestSetup(t *testing.T) libkb.TestContext { 24 tc := libkb.SetupTest(t, "kvstore", 0) 25 teams.ServiceInit(tc.G) 26 newRevisionCache := kvstore.NewKVRevisionCache(tc.G) 27 tc.G.SetKVRevisionCache(newRevisionCache) 28 return tc 29 } 30 31 func TestKvStoreSelfTeamPutGet(t *testing.T) { 32 tc := kvTestSetup(t) 33 defer tc.Cleanup() 34 35 user, err := kbtest.CreateAndSignupFakeUser("kvs", tc.G) 36 require.NoError(t, err) 37 handler := NewKVStoreHandler(nil, tc.G) 38 ctx := context.Background() 39 teamName := fmt.Sprintf("%s,%s", user.Username, user.Username) 40 namespace := "ye-namespace" 41 entryKey := "lookmeup" 42 43 // fetch nonexistent 44 getArg := keybase1.GetKVEntryArg{ 45 TeamName: teamName, 46 Namespace: namespace, 47 EntryKey: entryKey, 48 } 49 getRes, err := handler.GetKVEntry(ctx, getArg) 50 require.NoError(t, err) 51 require.Nil(t, getRes.EntryValue) 52 require.Equal(t, 0, getRes.Revision) 53 // and list 54 listNamespacesArg := keybase1.ListKVNamespacesArg{TeamName: teamName} 55 listNamespacesRes, err := handler.ListKVNamespaces(ctx, listNamespacesArg) 56 require.NoError(t, err) 57 require.EqualValues(t, listNamespacesRes.Namespaces, []string{}) 58 listEntriesArg := keybase1.ListKVEntriesArg{TeamName: teamName, Namespace: namespace} 59 listEntriesRes, err := handler.ListKVEntries(ctx, listEntriesArg) 60 require.NoError(t, err) 61 require.EqualValues(t, []keybase1.KVListEntryKey{}, listEntriesRes.EntryKeys) 62 63 // put a secret 64 cleartextSecret := "lorem ipsum blah blah blah" 65 putArg := keybase1.PutKVEntryArg{ 66 SessionID: 0, 67 TeamName: teamName, 68 Namespace: namespace, 69 EntryKey: entryKey, 70 EntryValue: cleartextSecret, 71 } 72 putRes, err := handler.PutKVEntry(ctx, putArg) 73 require.NoError(t, err) 74 require.Equal(t, 1, putRes.Revision) 75 76 // fetch it and assert that it's now correct 77 getRes, err = handler.GetKVEntry(ctx, getArg) 78 require.NoError(t, err) 79 require.Equal(t, cleartextSecret, *getRes.EntryValue) 80 81 updatedSecret := `Contrary to popular belief, Lorem Ipsum is not simply 82 random text. It has roots in a piece of classical Latin literature 83 from 45 BC, making it over 2000 years old.` 84 putArg = keybase1.PutKVEntryArg{ 85 SessionID: 0, 86 TeamName: teamName, 87 Namespace: namespace, 88 EntryKey: entryKey, 89 EntryValue: updatedSecret, 90 } 91 putRes, err = handler.PutKVEntry(ctx, putArg) 92 require.NoError(t, err) 93 require.Equal(t, 2, putRes.Revision) 94 getRes, err = handler.GetKVEntry(ctx, getArg) 95 require.NoError(t, err) 96 require.Equal(t, updatedSecret, *getRes.EntryValue) 97 98 // another user cannot see or edit this entry 99 tcEve := kvTestSetup(t) 100 defer tcEve.Cleanup() 101 _, err = kbtest.CreateAndSignupFakeUser("kvs", tcEve.G) 102 require.NoError(t, err) 103 eveHandler := NewKVStoreHandler(nil, tcEve.G) 104 getRes, err = eveHandler.GetKVEntry(ctx, getArg) 105 require.Error(t, err) 106 require.Contains(t, err.Error(), "You are not a member of this team") 107 108 // put again 109 putRes, err = handler.PutKVEntry(ctx, putArg) 110 require.NoError(t, err) 111 112 // lists correctly 113 listNamespacesRes, err = handler.ListKVNamespaces(ctx, listNamespacesArg) 114 require.NoError(t, err) 115 require.EqualValues(t, listNamespacesRes.Namespaces, []string{namespace}) 116 117 listEntriesRes, err = handler.ListKVEntries(ctx, listEntriesArg) 118 require.NoError(t, err) 119 expectedKey := keybase1.KVListEntryKey{EntryKey: entryKey, Revision: 3} 120 require.EqualValues(t, []keybase1.KVListEntryKey{expectedKey}, listEntriesRes.EntryKeys) 121 } 122 123 func TestKvStoreMultiUserTeam(t *testing.T) { 124 tcAlice := kvTestSetup(t) 125 defer tcAlice.Cleanup() 126 tcBob := kvTestSetup(t) 127 defer tcBob.Cleanup() 128 tcCharlie := kvTestSetup(t) 129 defer tcCharlie.Cleanup() 130 ctx := context.Background() 131 132 alice, err := kbtest.CreateAndSignupFakeUser("kvsA", tcAlice.G) 133 require.NoError(t, err) 134 aliceHandler := NewKVStoreHandler(nil, tcAlice.G) 135 bob, err := kbtest.CreateAndSignupFakeUser("kvsB", tcBob.G) 136 require.NoError(t, err) 137 bobHandler := NewKVStoreHandler(nil, tcBob.G) 138 charlie, err := kbtest.CreateAndSignupFakeUser("kvsB", tcCharlie.G) 139 require.NoError(t, err) 140 charlieHandler := NewKVStoreHandler(nil, tcCharlie.G) 141 142 teamName := alice.Username + "t" 143 teamID, err := teams.CreateRootTeam(context.Background(), tcAlice.G, teamName, keybase1.TeamSettings{}) 144 require.NoError(t, err) 145 require.NotNil(t, teamID) 146 _, err = teams.AddMember(context.Background(), tcAlice.G, teamName, bob.Username, keybase1.TeamRole_WRITER, nil) 147 require.NoError(t, err) 148 t.Logf("%s created team %s:%s", alice.Username, teamName, teamID) 149 150 // Alice puts a secret 151 namespace := "myapp" 152 entryKey := "asdfasfeasef" 153 secretData := map[string]interface{}{ 154 "username": "hunter2", 155 "email": "thereal@example.com", 156 "password": "super random password", 157 "OTP": "otp secret", 158 "twoFactorAuth": "not-really-anymore", 159 } 160 cleartextSecret, err := json.Marshal(secretData) 161 require.NoError(t, err) 162 putArg := keybase1.PutKVEntryArg{ 163 SessionID: 0, 164 TeamName: teamName, 165 Namespace: namespace, 166 EntryKey: entryKey, 167 EntryValue: string(cleartextSecret), 168 } 169 putRes, err := aliceHandler.PutKVEntry(ctx, putArg) 170 require.NoError(t, err) 171 require.Equal(t, 1, putRes.Revision) 172 t.Logf("alice successfully wrote an entry at revision 1") 173 174 // Bob can read it 175 getArg := keybase1.GetKVEntryArg{ 176 TeamName: teamName, 177 Namespace: namespace, 178 EntryKey: entryKey, 179 } 180 getRes, err := bobHandler.GetKVEntry(ctx, getArg) 181 require.NoError(t, err) 182 require.Equal(t, string(cleartextSecret), *getRes.EntryValue) 183 require.Equal(t, 1, getRes.Revision) 184 listEntriesArg := keybase1.ListKVEntriesArg{TeamName: teamName, Namespace: namespace} 185 expectedKey := keybase1.KVListEntryKey{EntryKey: entryKey, Revision: 1} 186 listEntriesRes, err := bobHandler.ListKVEntries(ctx, listEntriesArg) 187 require.NoError(t, err) 188 require.EqualValues(t, listEntriesRes.EntryKeys, []keybase1.KVListEntryKey{expectedKey}) 189 t.Logf("bob can GET and LIST it") 190 191 // Alice kicks bob out of the team. 192 err = teams.RemoveMember(ctx, tcAlice.G, teamName, bob.Username) 193 require.NoError(t, err) 194 err = teams.RotateKeyVisible(context.TODO(), tcAlice.G, *teamID) 195 require.NoError(t, err) 196 t.Logf("bob is no longer in the team") 197 198 // Bob cannot read the entry anymore. 199 getRes, err = bobHandler.GetKVEntry(ctx, getArg) 200 require.Error(t, err) 201 require.Contains(t, err.Error(), "You are not a member of this team (error 2623)") 202 require.IsType(t, err, libkb.AppStatusError{}) 203 aerr, _ := err.(libkb.AppStatusError) 204 if aerr.Code != libkb.SCTeamReadError { 205 t.Fatalf("expected an SCTeamReadError error but got %v", err) 206 } 207 listNamespacesArg := keybase1.ListKVNamespacesArg{TeamName: teamName} 208 _, err = bobHandler.ListKVNamespaces(ctx, listNamespacesArg) 209 require.Error(t, err) 210 require.IsType(t, err, libkb.AppStatusError{}) 211 aerr, _ = err.(libkb.AppStatusError) 212 if aerr.Code != libkb.SCTeamReadError { 213 t.Fatalf("expected an SCTeamReadError error but got %v", err) 214 } 215 t.Logf("bob can no longer GET or LIST the entry") 216 217 // New user to the team can overwrite the existing entry without specifying a revision 218 _, err = teams.AddMember(ctx, tcAlice.G, teamName, charlie.Username, keybase1.TeamRole_WRITER, nil) 219 require.NoError(t, err) 220 t.Logf("new user, charlie, is added to the team") 221 cleartextSecret = []byte("overwritten") 222 putArg = keybase1.PutKVEntryArg{ 223 SessionID: 0, 224 TeamName: teamName, 225 Namespace: namespace, 226 EntryKey: entryKey, 227 EntryValue: string(cleartextSecret), 228 } 229 putRes, err = charlieHandler.PutKVEntry(ctx, putArg) 230 require.NoError(t, err) 231 require.Equal(t, 2, putRes.Revision) 232 t.Logf("charlie can write to the entry") 233 getRes, err = charlieHandler.GetKVEntry(ctx, getArg) 234 require.NoError(t, err) 235 require.Equal(t, string(cleartextSecret), *getRes.EntryValue) 236 require.Equal(t, 2, getRes.Revision) 237 listNamespacesRes, err := charlieHandler.ListKVNamespaces(ctx, listNamespacesArg) 238 require.NoError(t, err) 239 require.EqualValues(t, listNamespacesRes.Namespaces, []string{namespace}) 240 listEntriesRes, err = charlieHandler.ListKVEntries(ctx, listEntriesArg) 241 require.NoError(t, err) 242 expectedKey = keybase1.KVListEntryKey{EntryKey: entryKey, Revision: 2} 243 require.EqualValues(t, []keybase1.KVListEntryKey{expectedKey}, listEntriesRes.EntryKeys) 244 t.Logf("charlie can fetch and list the entry") 245 } 246 247 func TestKVDelete(t *testing.T) { 248 tc := kvTestSetup(t) 249 defer tc.Cleanup() 250 mctx := libkb.NewMetaContextForTest(tc) 251 ctx := context.Background() 252 user, err := kbtest.CreateAndSignupFakeUser("kv", tc.G) 253 require.NoError(t, err) 254 handler := NewKVStoreHandler(nil, tc.G) 255 teamName := user.Username + "t" 256 teamID, err := teams.CreateRootTeam(context.Background(), tc.G, teamName, keybase1.TeamSettings{}) 257 require.NoError(t, err) 258 require.NotNil(t, teamID) 259 260 namespace := "myapp" 261 entryKey := "entry-key-whatever" 262 // delete a non-existent entry 263 delArg := keybase1.DelKVEntryArg{ 264 SessionID: 0, 265 TeamName: teamName, 266 Namespace: namespace, 267 EntryKey: entryKey, 268 } 269 _, err = handler.DelKVEntry(mctx.Ctx(), delArg) 270 require.Error(t, err) 271 require.IsType(t, err, libkb.AppStatusError{}) 272 aerr, _ := err.(libkb.AppStatusError) 273 if aerr.Code != libkb.SCTeamStorageNotFound { 274 t.Fatalf("expected an SCTeamStorageNotFound error but got %v", err) 275 } 276 t.Logf("attempting to delete a non-existent entry errors") 277 278 // create the new entry 279 putArg := keybase1.PutKVEntryArg{ 280 SessionID: 0, 281 TeamName: teamName, 282 Namespace: namespace, 283 EntryKey: entryKey, 284 EntryValue: "secret value", 285 } 286 putRes, err := handler.PutKVEntry(ctx, putArg) 287 require.NoError(t, err) 288 require.Equal(t, 1, putRes.Revision) 289 290 // delete it 291 delRes, err := handler.DelKVEntry(mctx.Ctx(), delArg) 292 require.NoError(t, err) 293 require.Equal(t, 2, delRes.Revision) 294 295 t.Logf("deleting an entry returns the next revision") 296 getArg := keybase1.GetKVEntryArg{ 297 TeamName: teamName, 298 Namespace: namespace, 299 EntryKey: entryKey, 300 } 301 getRes, err := handler.GetKVEntry(ctx, getArg) 302 require.NoError(t, err) 303 require.Equal(t, 2, getRes.Revision) 304 require.Equal(t, teamName, getRes.TeamName) 305 require.Equal(t, namespace, getRes.Namespace) 306 require.Equal(t, entryKey, getRes.EntryKey) 307 require.Nil(t, getRes.EntryValue) 308 t.Logf("fetching a deleted entry has the correct revision and empty value") 309 310 // delete it again 311 delRes, err = handler.DelKVEntry(mctx.Ctx(), delArg) 312 require.Error(t, err) 313 require.IsType(t, err, libkb.AppStatusError{}) 314 aerr, _ = err.(libkb.AppStatusError) 315 if aerr.Code != libkb.SCTeamStorageNotFound { 316 t.Fatalf("expected an SCTeamStorageNotFound error but got %v", err) 317 } 318 t.Logf("attempting to delete a deleted entry errors") 319 320 // recreate it after deletion 321 putRes, err = handler.PutKVEntry(ctx, putArg) 322 require.NoError(t, err) 323 require.Equal(t, 3, putRes.Revision) 324 getRes, err = handler.GetKVEntry(ctx, getArg) 325 require.NoError(t, err) 326 require.Equal(t, 3, getRes.Revision) 327 require.Equal(t, "secret value", *getRes.EntryValue) 328 329 // delete it again 330 delRes, err = handler.DelKVEntry(mctx.Ctx(), delArg) 331 require.NoError(t, err) 332 require.Equal(t, 4, delRes.Revision) 333 } 334 335 func assertRevisionError(t *testing.T, err error, expectedSource string) { 336 require.Error(t, err) 337 aerr, _ := err.(libkb.AppStatusError) 338 require.IsType(t, libkb.AppStatusError{}, aerr) 339 require.Equal(t, libkb.SCTeamStorageWrongRevision, aerr.Code) 340 require.Contains(t, err.Error(), "(error 2760)") 341 require.Contains(t, err.Error(), "revision") 342 switch expectedSource { 343 case "server": 344 possibleServerMessages := []string{ 345 "expected revision [0-9]+ but got [0-9]+", 346 "revision [0-9]+ already exists for this entry", 347 } 348 regex := strings.Join(possibleServerMessages, "|") 349 require.Regexp(t, regexp.MustCompile(regex), err.Error()) 350 case "cache": 351 require.Regexp(t, regexp.MustCompile("revision out of date"), err.Error()) 352 default: 353 require.Fail(t, "revision error must come from the server or the cache") 354 } 355 } 356 357 func TestRevisionCache(t *testing.T) { 358 tc := kvTestSetup(t) 359 defer tc.Cleanup() 360 mctx := libkb.NewMetaContextForTest(tc) 361 ctx := mctx.Ctx() 362 user, err := kbtest.CreateAndSignupFakeUser("kv", tc.G) 363 require.NoError(t, err) 364 handler := NewKVStoreHandler(nil, tc.G) 365 teamName := user.Username + "t" 366 teamID, err := teams.CreateRootTeam(context.Background(), tc.G, teamName, keybase1.TeamSettings{}) 367 require.NoError(t, err) 368 require.NotNil(t, teamID) 369 370 // create a new entry and other basic setup 371 namespace := "myapp" 372 entryKey := "messin-withtha-cache" 373 secretData := "supersecret" 374 putArg := keybase1.PutKVEntryArg{ 375 SessionID: 0, 376 TeamName: teamName, 377 Namespace: namespace, 378 EntryKey: entryKey, 379 EntryValue: secretData, 380 } 381 putRes, err := handler.PutKVEntry(ctx, putArg) 382 require.NoError(t, err) 383 require.Equal(t, 1, putRes.Revision) 384 entryID := keybase1.KVEntryID{ 385 TeamID: *teamID, 386 Namespace: namespace, 387 EntryKey: entryKey, 388 } 389 getArg := keybase1.GetKVEntryArg{ 390 TeamName: teamName, 391 Namespace: namespace, 392 EntryKey: entryKey, 393 } 394 395 // Mutate the revision cache to simulate the cases where the server 396 // is lying to the client about the next fetched entry. First, fetch 397 // and assert some basic stuff about the revision cache. 398 kvRevCache := tc.G.GetKVRevisionCache().(*kvstore.KVRevisionCache) 399 entryHash, generation, revision := kvRevCache.Inspect(entryID) 400 require.NotEmpty(t, entryHash) 401 require.EqualValues(t, 1, generation) 402 require.Equal(t, 1, revision) 403 404 // bump the revision in a new cache and verify error when going through the handler 405 tc.G.SetKVRevisionCache(kvstore.NewKVRevisionCache(tc.G)) 406 revCache := tc.G.GetKVRevisionCache() 407 err = revCache.Check(mctx, entryID, &secretData, generation, 2) 408 require.NoError(t, err) 409 err = revCache.Put(mctx, entryID, &secretData, generation, 2) 410 require.NoError(t, err) 411 _, err = handler.GetKVEntry(ctx, getArg) 412 require.Error(t, err) 413 require.Contains(t, err.Error(), "revision") 414 415 // bump the team key generation in a new cache and verify error 416 tc.G.SetKVRevisionCache(kvstore.NewKVRevisionCache(tc.G)) 417 revCache = tc.G.GetKVRevisionCache() 418 err = revCache.Check(mctx, entryID, &secretData, keybase1.PerTeamKeyGeneration(2), revision) 419 require.NoError(t, err) 420 err = revCache.Put(mctx, entryID, &secretData, keybase1.PerTeamKeyGeneration(2), revision) 421 require.NoError(t, err) 422 _, err = handler.GetKVEntry(ctx, getArg) 423 require.Error(t, err) 424 require.Contains(t, err.Error(), "team key generation") 425 426 // mutate the entry hash and verify error 427 tc.G.SetKVRevisionCache(kvstore.NewKVRevisionCache(tc.G)) 428 revCache = tc.G.GetKVRevisionCache() 429 differentCiphertext := "this-is-wrong" 430 err = revCache.Check(mctx, entryID, &differentCiphertext, generation, revision) 431 require.NoError(t, err) 432 err = revCache.Put(mctx, entryID, &differentCiphertext, generation, revision) 433 require.NoError(t, err) 434 _, err = handler.GetKVEntry(ctx, getArg) 435 require.Error(t, err) 436 require.Contains(t, err.Error(), "hash of entry") 437 t.Logf("revision cache protects the client from the server lying in fetches") 438 439 // convenience checks for PUT and DELETE 440 // set a new cache and put an update 441 tc.G.SetKVRevisionCache(kvstore.NewKVRevisionCache(tc.G)) 442 putArg = keybase1.PutKVEntryArg{ 443 SessionID: 0, 444 TeamName: teamName, 445 Namespace: namespace, 446 EntryKey: entryKey, 447 EntryValue: secretData, 448 Revision: 2, 449 } 450 putRes, err = handler.PutKVEntry(ctx, putArg) 451 require.NoError(t, err) 452 require.Equal(t, 2, putRes.Revision) 453 // assert the revision cache is what we think 454 kvRevCache = tc.G.GetKVRevisionCache().(*kvstore.KVRevisionCache) 455 entryHash, generation, revision = kvRevCache.Inspect(entryID) 456 require.NotEmpty(t, entryHash) 457 require.EqualValues(t, 1, generation) 458 require.Equal(t, 2, revision) 459 460 // attempt a put with a revision that the cache knows is too low 461 putArg.Revision = 2 462 _, err = handler.PutKVEntry(ctx, putArg) 463 assertRevisionError(t, err, "cache") 464 // attempt a put with a revision that's too high, but the cache can't know that (so it's a server error) 465 putArg.Revision = 4 466 _, err = handler.PutKVEntry(ctx, putArg) 467 assertRevisionError(t, err, "server") 468 t.Logf("revision cache provides convenience checks (the server also catches these things too) on updates") 469 470 // and the same for deletes 471 delArg := keybase1.DelKVEntryArg{ 472 SessionID: 0, 473 TeamName: teamName, 474 Namespace: namespace, 475 EntryKey: entryKey, 476 } 477 delArg.Revision = 2 478 _, err = handler.DelKVEntry(ctx, delArg) 479 assertRevisionError(t, err, "cache") 480 delArg.Revision = 4 481 _, err = handler.DelKVEntry(ctx, delArg) 482 assertRevisionError(t, err, "server") 483 t.Logf("revision cache also provides the convenience checks for deletes") 484 } 485 486 var _ kvstore.KVStoreBoxer = (*KVStoreTestBoxer)(nil) 487 488 type KVStoreTestBoxer struct { 489 libkb.Contextified 490 BoxMutateRevision func(currentRevision int) (newRevision int) 491 UnboxMutateTeamGen func(gen keybase1.PerTeamKeyGeneration) (newGen keybase1.PerTeamKeyGeneration) 492 UnboxMutateCiphertext func(ciphertext string) string 493 } 494 495 func (b *KVStoreTestBoxer) Box(mctx libkb.MetaContext, entryID keybase1.KVEntryID, revision int, cleartextValue string) (ciphertext string, 496 teamKeyGen keybase1.PerTeamKeyGeneration, ciphertextVersion int, err error) { 497 realBoxer := kvstore.NewKVStoreBoxer(mctx.G()) 498 if b.BoxMutateRevision != nil { 499 revision = b.BoxMutateRevision(revision) 500 } 501 return realBoxer.Box(mctx, entryID, revision, cleartextValue) 502 } 503 504 func (b *KVStoreTestBoxer) Unbox(mctx libkb.MetaContext, entryID keybase1.KVEntryID, revision int, ciphertext string, teamKeyGen keybase1.PerTeamKeyGeneration, 505 formatVersion int, senderUID keybase1.UID, senderEldestSeqno keybase1.Seqno, senderDeviceID keybase1.DeviceID) (cleartext string, err error) { 506 realBoxer := kvstore.NewKVStoreBoxer(mctx.G()) 507 if b.UnboxMutateTeamGen != nil { 508 teamKeyGen = b.UnboxMutateTeamGen(teamKeyGen) 509 } 510 if b.UnboxMutateCiphertext != nil { 511 ciphertext = b.UnboxMutateCiphertext(ciphertext) 512 } 513 return realBoxer.Unbox(mctx, entryID, revision, ciphertext, teamKeyGen, formatVersion, senderUID, senderEldestSeqno, senderDeviceID) 514 } 515 516 func TestKVEncryptionAndVerification(t *testing.T) { 517 ctx := context.TODO() 518 tc := kvTestSetup(t) 519 defer tc.Cleanup() 520 user, err := kbtest.CreateAndSignupFakeUser("kvs", tc.G) 521 require.NoError(t, err) 522 handler := NewKVStoreHandler(nil, tc.G) 523 // inject a test Boxer into the handler which, at this point, 524 // is just a passthrough to the real Boxer, but ensure that 525 // any expected errors later are not false negatives. 526 handler.Boxer = &KVStoreTestBoxer{ 527 Contextified: libkb.NewContextified(tc.G), 528 } 529 teamName := user.Username + "t" 530 teamID, err := teams.CreateRootTeam(context.Background(), tc.G, teamName, keybase1.TeamSettings{}) 531 require.NoError(t, err) 532 require.NotNil(t, teamID) 533 err = teams.RotateKeyVisible(context.TODO(), tc.G, *teamID) 534 require.NoError(t, err) 535 err = teams.RotateKeyVisible(context.TODO(), tc.G, *teamID) 536 require.NoError(t, err) 537 namespace := "myapp" 538 entryKey := "entry-key-entry-key" 539 secretData := "supersecret" 540 putArg := keybase1.PutKVEntryArg{ 541 SessionID: 0, 542 TeamName: teamName, 543 Namespace: namespace, 544 EntryKey: entryKey, 545 EntryValue: secretData, 546 } 547 _, err = handler.PutKVEntry(ctx, putArg) 548 require.NoError(t, err) 549 _, err = handler.PutKVEntry(ctx, putArg) 550 require.NoError(t, err) 551 putRes, err := handler.PutKVEntry(ctx, putArg) 552 require.NoError(t, err) 553 require.Equal(t, 3, putRes.Revision) 554 getArg := keybase1.GetKVEntryArg{ 555 TeamName: teamName, 556 Namespace: namespace, 557 EntryKey: entryKey, 558 } 559 getRes, err := handler.GetKVEntry(ctx, getArg) 560 require.NoError(t, err) 561 require.Equal(t, secretData, *getRes.EntryValue) 562 require.Equal(t, 3, getRes.Revision) 563 t.Logf("entry exists for team at revision 3 and key generation 3") 564 565 // attempt to decrypt with the wrong team key generation 566 // should fail to decrypt 567 handler.Boxer = &KVStoreTestBoxer{ 568 Contextified: libkb.NewContextified(tc.G), 569 UnboxMutateTeamGen: func(gen keybase1.PerTeamKeyGeneration) (newGen keybase1.PerTeamKeyGeneration) { 570 require.EqualValues(t, 3, gen, "generation should be 3 at this point") 571 return keybase1.PerTeamKeyGeneration(2) 572 }, 573 } 574 _, err = handler.GetKVEntry(ctx, getArg) 575 require.Error(t, err) 576 require.IsType(t, signencrypt.Error{}, err) 577 require.Equal(t, err.(signencrypt.Error).Type, signencrypt.BadSecretbox) 578 t.Logf("attempting to decrypt with the wrong key generation fails") 579 580 // should fail to open if the server tells the client it's the wrong revision 581 handler.Boxer = &KVStoreTestBoxer{ 582 Contextified: libkb.NewContextified(tc.G), 583 BoxMutateRevision: func(currentRevision int) (newRevision int) { return 2 }, 584 } 585 putRes, err = handler.PutKVEntry(ctx, putArg) 586 require.NoError(t, err) 587 _, err = handler.GetKVEntry(ctx, getArg) 588 require.Error(t, err) 589 require.IsType(t, signencrypt.Error{}, err) 590 require.Equal(t, err.(signencrypt.Error).Type, signencrypt.AssociatedDataMismatch) 591 t.Logf("verifying a signature with the wrong revision fails") 592 593 // should error if given the wrong nonce 594 handler.Boxer = &KVStoreTestBoxer{ 595 Contextified: libkb.NewContextified(tc.G), 596 UnboxMutateCiphertext: func(currentCiphertext string) (newCiphertext string) { 597 decoded, err := base64.StdEncoding.DecodeString(currentCiphertext) 598 require.NoError(t, err) 599 var box keybase1.EncryptedKVEntry 600 err = msgpack.Decode(&box, decoded) 601 require.NoError(t, err) 602 require.Equal(t, len(box.N), 16, "there is an actual 16 byte nonce") 603 randBytes, err := libkb.RandBytes(16) 604 require.NoError(t, err) 605 box.N = randBytes 606 packed, err := msgpack.Encode(box) 607 require.NoError(t, err) 608 return base64.StdEncoding.EncodeToString(packed) 609 }, 610 } 611 _, err = handler.GetKVEntry(ctx, getArg) 612 require.Error(t, err) 613 require.IsType(t, signencrypt.Error{}, err) 614 require.Equal(t, err.(signencrypt.Error).Type, signencrypt.BadSecretbox) 615 t.Logf("cannot decrypt with the wrong nonce") 616 // switch to a new, non-broken entry key to test that the nonce changes 617 putArg.EntryKey = "not-broken" 618 getArg.EntryKey = "not-broken" 619 var firstNonce, secondNonce [16]byte 620 handler.Boxer = &KVStoreTestBoxer{ 621 Contextified: libkb.NewContextified(tc.G), 622 UnboxMutateCiphertext: func(currentCiphertext string) (newCiphertext string) { 623 decoded, err := base64.StdEncoding.DecodeString(currentCiphertext) 624 require.NoError(t, err) 625 var box keybase1.EncryptedKVEntry 626 err = msgpack.Decode(&box, decoded) 627 require.NoError(t, err) 628 require.Equal(t, len(box.N), 16, "there is an actual 16 byte nonce") 629 copy(firstNonce[:], box.N) 630 return currentCiphertext 631 }, 632 } 633 _, err = handler.PutKVEntry(ctx, putArg) 634 require.NoError(t, err) 635 _, err = handler.GetKVEntry(ctx, getArg) 636 require.NoError(t, err) 637 require.Equal(t, len(firstNonce), 16, "firstNonce got populated") 638 handler.Boxer = &KVStoreTestBoxer{ 639 Contextified: libkb.NewContextified(tc.G), 640 UnboxMutateCiphertext: func(ciphertext string) string { 641 decoded, err := base64.StdEncoding.DecodeString(ciphertext) 642 require.NoError(t, err) 643 var box keybase1.EncryptedKVEntry 644 err = msgpack.Decode(&box, decoded) 645 require.NoError(t, err) 646 require.Equal(t, len(box.N), 16, "there is an actual 16 byte nonce") 647 t.Logf("the nonce is 16 bytes") 648 copy(secondNonce[:], box.N) 649 return ciphertext 650 }, 651 } 652 _, err = handler.PutKVEntry(ctx, putArg) 653 require.NoError(t, err) 654 _, err = handler.GetKVEntry(ctx, getArg) 655 require.NoError(t, err) 656 require.Equal(t, len(secondNonce), 16, "secondNonce got populated") 657 require.NotEqual(t, firstNonce, secondNonce) 658 t.Logf("two puts with identical data and keys use different nonces") 659 } 660 661 // TestManualControlOfRevisionWithoutCache tests the server erroring correctly 662 // when fed the wrong revision, and that error bubbling up. This requires resetting 663 // the cache before each request. 664 func TestManualControlOfRevisionWithoutCache(t *testing.T) { 665 tc := kvTestSetup(t) 666 defer tc.Cleanup() 667 mctx := libkb.NewMetaContextForTest(tc) 668 ctx := mctx.Ctx() 669 user, err := kbtest.CreateAndSignupFakeUser("kv", tc.G) 670 require.NoError(t, err) 671 handler := NewKVStoreHandler(nil, tc.G) 672 teamName := user.Username + "t" 673 teamID, err := teams.CreateRootTeam(context.Background(), tc.G, teamName, keybase1.TeamSettings{}) 674 require.NoError(t, err) 675 require.NotNil(t, teamID) 676 677 // create a new entry and other basic setup 678 namespace := "manually-controlled-namespace" 679 entryKey := "ye-new-key" 680 secretData := "supersecret" 681 basePutArg := keybase1.PutKVEntryArg{ 682 SessionID: 0, 683 TeamName: teamName, 684 Namespace: namespace, 685 EntryKey: entryKey, 686 EntryValue: secretData, 687 } 688 689 putItWithRev := func(revision int) (res keybase1.KVPutResult, err error) { 690 // reset the cache before each PUT to avoid additional errors from there 691 tc.G.SetKVRevisionCache(kvstore.NewKVRevisionCache(tc.G)) 692 putArg := basePutArg 693 putArg.Revision = revision 694 return handler.PutKVEntry(ctx, putArg) 695 } 696 697 // errors if you specify a Revision > 1 for a new entry 698 _, err = putItWithRev(2) 699 assertRevisionError(t, err, "server") 700 701 // create it with revision 1 702 putRes, err := putItWithRev(1) 703 require.NoError(t, err) 704 require.Equal(t, putRes.Revision, 1) 705 706 // cannot update it again with revision 1 707 _, err = putItWithRev(1) 708 assertRevisionError(t, err, "server") 709 710 // updates correctly 711 putRes, err = putItWithRev(2) 712 require.NoError(t, err) 713 require.Equal(t, putRes.Revision, 2) 714 715 // cannot update with a revision that's too high 716 _, err = putItWithRev(4) 717 assertRevisionError(t, err, "server") 718 719 // cannot update with a revision that's too low 720 _, err = putItWithRev(2) 721 assertRevisionError(t, err, "server") 722 723 baseDelArg := keybase1.DelKVEntryArg{ 724 SessionID: 0, 725 TeamName: teamName, 726 Namespace: namespace, 727 EntryKey: entryKey, 728 } 729 730 deleteItWithRev := func(revision int) (res keybase1.KVDeleteEntryResult, err error) { 731 // reset the cache before each request to avoid additional errors from there 732 tc.G.SetKVRevisionCache(kvstore.NewKVRevisionCache(tc.G)) 733 delArg := baseDelArg 734 delArg.Revision = revision 735 return handler.DelKVEntry(ctx, delArg) 736 } 737 738 // cannot delete with a revision that's too low 739 _, err = deleteItWithRev(2) 740 assertRevisionError(t, err, "server") 741 742 // cannot delete with a revision that's too high 743 _, err = deleteItWithRev(4) 744 assertRevisionError(t, err, "server") 745 746 // deletes correctly 747 delRes, err := deleteItWithRev(3) 748 require.NoError(t, err) 749 require.Equal(t, delRes.Revision, 3) 750 } 751 752 // TestKVStoreRace tests that multiple requests by multiple users in rapid succession do not 753 // cause unexpected errors or busted caches 754 func TestKVStoreRace(t *testing.T) { 755 tc1 := kvTestSetup(t) 756 defer tc1.Cleanup() 757 mctx1 := libkb.NewMetaContextForTest(tc1) 758 ctx1 := mctx1.Ctx() 759 user1, err := kbtest.CreateAndSignupFakeUser("kv", tc1.G) 760 require.NoError(t, err) 761 handler1 := NewKVStoreHandler(nil, tc1.G) 762 teamName := user1.Username + "t" 763 teamID, err := teams.CreateRootTeam(ctx1, tc1.G, teamName, keybase1.TeamSettings{}) 764 require.NoError(t, err) 765 require.NotNil(t, teamID) 766 // add a second user to the team 767 tc2 := kvTestSetup(t) 768 defer tc2.Cleanup() 769 mctx2 := libkb.NewMetaContextForTest(tc2) 770 ctx2 := mctx2.Ctx() 771 user2, err := kbtest.CreateAndSignupFakeUser("kv", tc2.G) 772 require.NoError(t, err) 773 handler2 := NewKVStoreHandler(nil, tc2.G) 774 _, err = teams.AddMember(ctx1, tc1.G, teamName, user2.Username, keybase1.TeamRole_WRITER, nil) 775 require.NoError(t, err) 776 777 namespace := "race-namespace" 778 entryKey := "race-key" 779 secretData := "supersecret" 780 putArg := keybase1.PutKVEntryArg{ 781 SessionID: 0, 782 TeamName: teamName, 783 Namespace: namespace, 784 EntryKey: entryKey, 785 EntryValue: secretData, 786 } 787 var wg sync.WaitGroup 788 errChan := make(chan error, 10) 789 for i := 0; i < 5; i++ { 790 wg.Add(1) 791 go func() { 792 defer wg.Done() 793 _, err = handler1.PutKVEntry(ctx1, putArg) 794 errChan <- err 795 }() 796 wg.Add(1) 797 go func() { 798 defer wg.Done() 799 _, err = handler2.PutKVEntry(ctx2, putArg) 800 errChan <- err 801 }() 802 } 803 wg.Wait() 804 close(errChan) 805 for err := range errChan { 806 // any errors should be server-thrown revision errors 807 if err != nil { 808 assertRevisionError(t, err, "server") 809 } 810 } 811 812 // and now a fetch should work from both users, confirming that neither 813 // has a busted revision cache 814 getArg := keybase1.GetKVEntryArg{ 815 TeamName: teamName, 816 Namespace: namespace, 817 EntryKey: entryKey, 818 } 819 getRes, err := handler1.GetKVEntry(ctx1, getArg) 820 require.NoError(t, err) 821 require.Equal(t, secretData, *getRes.EntryValue) 822 getRes, err = handler2.GetKVEntry(ctx2, getArg) 823 require.NoError(t, err) 824 require.Equal(t, secretData, *getRes.EntryValue) 825 }