github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/engine/deprovision_test.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package engine 5 6 import ( 7 "os" 8 "testing" 9 10 "github.com/keybase/client/go/libkb" 11 "github.com/stretchr/testify/require" 12 "golang.org/x/sync/errgroup" 13 ) 14 15 func forceOpenDBs(tc libkb.TestContext) { 16 // We need to ensure these dbs are open since we test that we can delete 17 // them on deprovision 18 err := tc.G.LocalDb.ForceOpen() 19 require.NoError(tc.T, err) 20 err = tc.G.LocalChatDb.ForceOpen() 21 require.NoError(tc.T, err) 22 } 23 24 func assertFileExists(t libkb.TestingTB, path string) { 25 if _, err := os.Stat(path); os.IsNotExist(err) { 26 t.Fatalf("%s unexpectedly does not exist", path) 27 } 28 } 29 30 func assertFileDoesNotExist(t libkb.TestingTB, path string) { 31 if _, err := os.Stat(path); err == nil { 32 t.Fatalf("%s unexpectedly exists", path) 33 } 34 } 35 36 func isUserInConfigFile(tc libkb.TestContext, fu FakeUser) bool { 37 _, err := tc.G.Env.GetConfig().GetUserConfigForUsername(fu.NormalizedUsername()) 38 return err == nil 39 } 40 41 func isUserConfigInMemory(tc libkb.TestContext) bool { 42 config, _ := tc.G.Env.GetConfig().GetUserConfig() 43 return config != nil 44 } 45 46 func getNumKeys(tc libkb.TestContext, fu FakeUser) int { 47 loaded, err := libkb.LoadUser(libkb.NewLoadUserArg(tc.G).WithName(fu.Username).WithForceReload()) 48 if err != nil { 49 switch err.(type) { 50 case libkb.NoKeyError: 51 return 0 52 default: 53 require.NoError(tc.T, err) 54 } 55 } 56 ckf := loaded.GetComputedKeyFamily() 57 return len(ckf.GetAllActiveSibkeys()) + len(ckf.GetAllActiveSubkeys()) 58 } 59 60 type assertDeprovisionWithSetupArg struct { 61 // create and then revoke one extra paper key 62 makeAndRevokePaperKey bool 63 64 // revoke the final paper key 65 revokePaperKey bool 66 } 67 68 func assertDeprovisionWithSetup(tc libkb.TestContext, targ assertDeprovisionWithSetupArg) *FakeUser { 69 // Sign up a new user and have it store its secret in the 70 // secret store (if possible). 71 fu := NewFakeUserOrBust(tc.T, "dpr") 72 arg := MakeTestSignupEngineRunArg(fu) 73 arg.SkipPaper = false 74 arg.StoreSecret = tc.G.SecretStore() != nil 75 uis := libkb.UIs{ 76 LogUI: tc.G.UI.GetLogUI(), 77 GPGUI: &gpgtestui{}, 78 SecretUI: fu.NewSecretUI(), 79 LoginUI: &libkb.TestLoginUI{Username: fu.Username}, 80 } 81 s := NewSignupEngine(tc.G, &arg) 82 err := RunEngine2(NewMetaContextForTest(tc).WithUIs(uis), s) 83 if err != nil { 84 tc.T.Fatal(err) 85 } 86 87 m := NewMetaContextForTest(tc) 88 if tc.G.SecretStore() != nil { 89 secretStore := libkb.NewSecretStore(m, fu.NormalizedUsername()) 90 _, err := secretStore.RetrieveSecret(m) 91 if err != nil { 92 tc.T.Fatal(err) 93 } 94 } 95 96 forceOpenDBs(tc) 97 dbPath := tc.G.Env.GetDbFilename() 98 chatDBPath := tc.G.Env.GetChatDbFilename() 99 secretKeysPath := tc.G.SKBFilenameForUser(fu.NormalizedUsername()) 100 numKeys := getNumKeys(tc, *fu) 101 expectedNumKeys := numKeys 102 103 assertFileExists(tc.T, dbPath) 104 assertFileExists(tc.T, chatDBPath) 105 assertFileExists(tc.T, secretKeysPath) 106 if !isUserInConfigFile(tc, *fu) { 107 tc.T.Fatalf("User %s is not in the config file %s", fu.Username, tc.G.Env.GetConfigFilename()) 108 } 109 if !isUserConfigInMemory(tc) { 110 tc.T.Fatal("user config is not in memory") 111 } 112 113 if !LoggedIn(tc) { 114 tc.T.Fatal("Unexpectedly logged out") 115 } 116 117 if targ.makeAndRevokePaperKey { 118 t := tc.T 119 t.Logf("generate a paper key (targ)") 120 uis := libkb.UIs{ 121 LogUI: tc.G.UI.GetLogUI(), 122 LoginUI: &libkb.TestLoginUI{}, 123 SecretUI: &libkb.TestSecretUI{}, 124 } 125 eng := NewPaperKey(tc.G) 126 m := NewMetaContextForTest(tc).WithUIs(uis) 127 err := RunEngine2(m, eng) 128 require.NoError(t, err) 129 require.NotEqual(t, 0, len(eng.Passphrase()), "empty passphrase") 130 131 revokeAnyPaperKey(tc, fu) 132 } 133 134 if targ.revokePaperKey { 135 tc.T.Logf("revoking paper key (targ)") 136 revokeAnyPaperKey(tc, fu) 137 expectedNumKeys -= 2 138 } 139 140 e := NewDeprovisionEngine(tc.G, fu.Username, true /* doRevoke */, libkb.LogoutOptions{}) 141 uis = libkb.UIs{ 142 LogUI: tc.G.UI.GetLogUI(), 143 SecretUI: fu.NewSecretUI(), 144 } 145 m = m.WithUIs(uis) 146 if err := RunEngine2(m, e); err != nil { 147 tc.T.Fatal(err) 148 } 149 expectedNumKeys -= 2 150 151 if LoggedIn(tc) { 152 tc.T.Error("Unexpectedly still logged in") 153 } 154 155 if tc.G.SecretStore() != nil { 156 secretStore := libkb.NewSecretStore(m, fu.NormalizedUsername()) 157 secret, err := secretStore.RetrieveSecret(m) 158 if err == nil { 159 tc.T.Errorf("Unexpectedly got secret %v", secret) 160 } 161 } 162 163 assertFileDoesNotExist(tc.T, dbPath) 164 assertFileDoesNotExist(tc.T, chatDBPath) 165 assertFileDoesNotExist(tc.T, secretKeysPath) 166 167 if isUserInConfigFile(tc, *fu) { 168 tc.T.Fatalf("User %s is still in the config file %s", fu.Username, tc.G.Env.GetConfigFilename()) 169 } 170 if isUserConfigInMemory(tc) { 171 tc.T.Fatal("user config is still in memory") 172 } 173 174 newKeys := getNumKeys(tc, *fu) 175 require.Equal(tc.T, expectedNumKeys, newKeys, "unexpected number of keys (failed to revoke device keys)") 176 177 return fu 178 } 179 180 func TestDeprovision(t *testing.T) { 181 testDeprovision(t, false) 182 } 183 184 func TestDeprovisionPUK(t *testing.T) { 185 testDeprovision(t, true) 186 } 187 188 func testDeprovision(t *testing.T, upgradePerUserKey bool) { 189 tc := SetupEngineTest(t, "deprovision") 190 defer tc.Cleanup() 191 tc.Tp.DisableUpgradePerUserKey = !upgradePerUserKey 192 if tc.G.SecretStore() == nil { 193 t.Fatal("Need a secret store for this test") 194 } 195 assertDeprovisionWithSetup(tc, assertDeprovisionWithSetupArg{}) 196 } 197 198 func TestDeprovisionAfterRevokePaper(t *testing.T) { 199 testDeprovisionAfterRevokePaper(t, false) 200 } 201 202 func TestDeprovisionAfterRevokePaperPUK(t *testing.T) { 203 testDeprovisionAfterRevokePaper(t, true) 204 } 205 206 func testDeprovisionAfterRevokePaper(t *testing.T, upgradePerUserKey bool) { 207 tc := SetupEngineTest(t, "deprovision") 208 defer tc.Cleanup() 209 210 tc.Tp.DisableUpgradePerUserKey = !upgradePerUserKey 211 if tc.G.SecretStore() == nil { 212 t.Fatal("Need a secret store for this test") 213 } 214 assertDeprovisionWithSetup(tc, assertDeprovisionWithSetupArg{ 215 makeAndRevokePaperKey: true, 216 }) 217 } 218 219 func assertDeprovisionLoggedOut(tc libkb.TestContext) { 220 221 // Sign up a new user and have it store its secret in the 222 // secret store (if possible). 223 fu := NewFakeUserOrBust(tc.T, "dpr") 224 arg := MakeTestSignupEngineRunArg(fu) 225 226 arg.StoreSecret = tc.G.SecretStore() != nil 227 uis := libkb.UIs{ 228 LogUI: tc.G.UI.GetLogUI(), 229 GPGUI: &gpgtestui{}, 230 SecretUI: fu.NewSecretUI(), 231 LoginUI: &libkb.TestLoginUI{Username: fu.Username}, 232 } 233 s := NewSignupEngine(tc.G, &arg) 234 err := RunEngine2(NewMetaContextForTest(tc).WithUIs(uis), s) 235 if err != nil { 236 tc.T.Fatal(err) 237 } 238 239 m := NewMetaContextForTest(tc) 240 if tc.G.SecretStore() != nil { 241 secretStore := libkb.NewSecretStore(m, fu.NormalizedUsername()) 242 _, err := secretStore.RetrieveSecret(m) 243 if err != nil { 244 tc.T.Fatal(err) 245 } 246 } 247 248 forceOpenDBs(tc) 249 dbPath := tc.G.Env.GetDbFilename() 250 chatDBPath := tc.G.Env.GetChatDbFilename() 251 secretKeysPath := tc.G.SKBFilenameForUser(fu.NormalizedUsername()) 252 numKeys := getNumKeys(tc, *fu) 253 254 assertFileExists(tc.T, dbPath) 255 assertFileExists(tc.T, chatDBPath) 256 assertFileExists(tc.T, secretKeysPath) 257 if !isUserInConfigFile(tc, *fu) { 258 tc.T.Fatalf("User %s is not in the config file %s", fu.Username, tc.G.Env.GetConfigFilename()) 259 } 260 if !isUserConfigInMemory(tc) { 261 tc.T.Fatalf("user config is not in memory") 262 } 263 264 if !LoggedIn(tc) { 265 tc.T.Fatal("Unexpectedly logged out") 266 } 267 268 // Unlike the first test, this time we log out before we run the 269 // deprovision. We should be able to do a deprovision with revocation 270 // disabled. 271 if err := m.LogoutKillSecrets(); err != nil { 272 tc.T.Fatal(err) 273 } 274 275 e := NewDeprovisionEngine(tc.G, fu.Username, false /* doRevoke */, libkb.LogoutOptions{}) 276 uis = libkb.UIs{ 277 LogUI: tc.G.UI.GetLogUI(), 278 SecretUI: fu.NewSecretUI(), 279 } 280 m = m.WithUIs(uis) 281 if err := RunEngine2(m, e); err != nil { 282 tc.T.Fatal(err) 283 } 284 285 if LoggedIn(tc) { 286 tc.T.Error("Unexpectedly still logged in") 287 } 288 289 if tc.G.SecretStore() != nil { 290 secretStore := libkb.NewSecretStore(m, fu.NormalizedUsername()) 291 secret, err := secretStore.RetrieveSecret(m) 292 if err == nil { 293 tc.T.Errorf("Unexpectedly got secret %v", secret) 294 } 295 } 296 297 assertFileDoesNotExist(tc.T, dbPath) 298 assertFileDoesNotExist(tc.T, chatDBPath) 299 assertFileDoesNotExist(tc.T, secretKeysPath) 300 if isUserInConfigFile(tc, *fu) { 301 tc.T.Fatalf("User %s is still in the config file %s", fu.Username, tc.G.Env.GetConfigFilename()) 302 } 303 if isUserConfigInMemory(tc) { 304 tc.T.Fatalf("user config is still in memory") 305 } 306 307 newNumKeys := getNumKeys(tc, *fu) 308 if newNumKeys != numKeys { 309 tc.T.Fatalf("expected the same number of device keys, before: %d, after: %d", numKeys, newNumKeys) 310 } 311 } 312 313 func TestDeprovisionLoggedOut(t *testing.T) { 314 tc := SetupEngineTest(t, "deprovision") 315 defer tc.Cleanup() 316 if tc.G.SecretStore() == nil { 317 t.Fatalf("Need a secret store for this test") 318 } 319 assertDeprovisionLoggedOut(tc) 320 } 321 322 func assertCurrentDeviceRevoked(tc libkb.TestContext) { 323 324 // Sign up a new user and have it store its secret in the 325 // secret store (if possible). 326 fu := NewFakeUserOrBust(tc.T, "dpr") 327 arg := MakeTestSignupEngineRunArg(fu) 328 arg.SkipPaper = false 329 arg.StoreSecret = tc.G.SecretStore() != nil 330 uis := libkb.UIs{ 331 LogUI: tc.G.UI.GetLogUI(), 332 GPGUI: &gpgtestui{}, 333 SecretUI: fu.NewSecretUI(), 334 LoginUI: &libkb.TestLoginUI{Username: fu.Username}, 335 } 336 s := NewSignupEngine(tc.G, &arg) 337 err := RunEngine2(NewMetaContextForTest(tc).WithUIs(uis), s) 338 if err != nil { 339 tc.T.Fatal(err) 340 } 341 342 if tc.G.SecretStore() != nil { 343 secretStore := libkb.NewSecretStore(tc.MetaContext(), fu.NormalizedUsername()) 344 _, err := secretStore.RetrieveSecret(NewMetaContextForTest(tc)) 345 if err != nil { 346 tc.T.Fatal(err) 347 } 348 } 349 350 forceOpenDBs(tc) 351 dbPath := tc.G.Env.GetDbFilename() 352 chatDBPath := tc.G.Env.GetChatDbFilename() 353 secretKeysPath := tc.G.SKBFilenameForUser(fu.NormalizedUsername()) 354 numKeys := getNumKeys(tc, *fu) 355 356 assertFileExists(tc.T, dbPath) 357 assertFileExists(tc.T, chatDBPath) 358 assertFileExists(tc.T, secretKeysPath) 359 if !isUserInConfigFile(tc, *fu) { 360 tc.T.Fatalf("User %s is not in the config file %s", fu.Username, tc.G.Env.GetConfigFilename()) 361 } 362 if !isUserConfigInMemory(tc) { 363 tc.T.Fatal("user config is not in memory") 364 } 365 366 if !LoggedIn(tc) { 367 tc.T.Fatal("Unexpectedly logged out") 368 } 369 370 // Revoke the current device! This will cause an error when deprovision 371 // tries to revoke the device again, but deprovision should carry on. 372 err = doRevokeDevice(tc, fu, tc.G.Env.GetDeviceID(), true /* force */, false /* forceLast */) 373 if err != nil { 374 tc.T.Fatal(err) 375 } 376 377 e := NewDeprovisionEngine(tc.G, fu.Username, true /* doRevoke */, libkb.LogoutOptions{}) 378 uis = libkb.UIs{ 379 LogUI: tc.G.UI.GetLogUI(), 380 SecretUI: fu.NewSecretUI(), 381 } 382 m := NewMetaContextForTest(tc).WithUIs(uis) 383 if err := RunEngine2(m, e); err != nil { 384 tc.T.Fatal(err) 385 } 386 387 if LoggedIn(tc) { 388 tc.T.Error("Unexpectedly still logged in") 389 } 390 391 if tc.G.SecretStore() != nil { 392 secretStore := libkb.NewSecretStore(m, fu.NormalizedUsername()) 393 secret, err := secretStore.RetrieveSecret(NewMetaContextForTest(tc)) 394 if err == nil { 395 tc.T.Errorf("Unexpectedly got secret %v", secret) 396 } 397 } 398 399 assertFileDoesNotExist(tc.T, dbPath) 400 assertFileDoesNotExist(tc.T, chatDBPath) 401 assertFileDoesNotExist(tc.T, secretKeysPath) 402 if isUserInConfigFile(tc, *fu) { 403 tc.T.Fatalf("User %s is still in the config file %s", fu.Username, tc.G.Env.GetConfigFilename()) 404 } 405 if isUserConfigInMemory(tc) { 406 tc.T.Fatal("user config is still in memory") 407 } 408 409 newNumKeys := getNumKeys(tc, *fu) 410 if newNumKeys != numKeys-2 { 411 tc.T.Fatalf("failed to revoke device keys, before: %d, after: %d", numKeys, newNumKeys) 412 } 413 } 414 415 func TestCurrentDeviceRevoked(t *testing.T) { 416 tc := SetupEngineTest(t, "deprovision") 417 defer tc.Cleanup() 418 419 if tc.G.SecretStore() == nil { 420 t.Fatalf("Need a secret store for this test") 421 } 422 assertCurrentDeviceRevoked(tc) 423 } 424 425 func TestDeprovisionLastDevice(t *testing.T) { 426 testDeprovisionLastDevice(t, false) 427 } 428 429 func TestDeprovisionLastDevicePUK(t *testing.T) { 430 testDeprovisionLastDevice(t, true) 431 } 432 433 // A user should be able to revoke all of their devices. 434 func testDeprovisionLastDevice(t *testing.T, upgradePerUserKey bool) { 435 tc := SetupEngineTest(t, "deprovision") 436 defer tc.Cleanup() 437 438 tc.Tp.DisableUpgradePerUserKey = !upgradePerUserKey 439 if tc.G.SecretStore() == nil { 440 t.Fatal("Need a secret store for this test") 441 } 442 fu := assertDeprovisionWithSetup(tc, assertDeprovisionWithSetupArg{ 443 revokePaperKey: true, 444 }) 445 assertNumDevicesAndKeys(tc, fu, 0, 0) 446 } 447 448 func TestConcurrentDeprovision(t *testing.T) { 449 tc := SetupEngineTest(t, "deprovision-concurrent") 450 defer tc.Cleanup() 451 if tc.G.SecretStore() == nil { 452 t.Fatal("Need a secret store for this test") 453 } 454 455 // Sign up a new user and have it store its secret in the 456 // secret store (if possible). 457 fu := NewFakeUserOrBust(tc.T, "dpr") 458 arg := MakeTestSignupEngineRunArg(fu) 459 arg.SkipPaper = false 460 arg.StoreSecret = tc.G.SecretStore() != nil 461 uis := libkb.UIs{ 462 LogUI: tc.G.UI.GetLogUI(), 463 GPGUI: &gpgtestui{}, 464 SecretUI: fu.NewSecretUI(), 465 LoginUI: &libkb.TestLoginUI{Username: fu.Username}, 466 } 467 s := NewSignupEngine(tc.G, &arg) 468 err := RunEngine2(NewMetaContextForTest(tc).WithUIs(uis), s) 469 if err != nil { 470 tc.T.Fatal(err) 471 } 472 473 m := NewMetaContextForTest(tc) 474 if tc.G.SecretStore() != nil { 475 secretStore := libkb.NewSecretStore(m, fu.NormalizedUsername()) 476 _, err := secretStore.RetrieveSecret(m) 477 if err != nil { 478 tc.T.Fatal(err) 479 } 480 } 481 482 forceOpenDBs(tc) 483 dbPath := tc.G.Env.GetDbFilename() 484 chatDBPath := tc.G.Env.GetChatDbFilename() 485 secretKeysPath := tc.G.SKBFilenameForUser(fu.NormalizedUsername()) 486 numKeys := getNumKeys(tc, *fu) 487 expectedNumKeys := numKeys 488 489 assertFileExists(tc.T, dbPath) 490 assertFileExists(tc.T, chatDBPath) 491 assertFileExists(tc.T, secretKeysPath) 492 if !isUserInConfigFile(tc, *fu) { 493 tc.T.Fatalf("User %s is not in the config file %s", fu.Username, tc.G.Env.GetConfigFilename()) 494 } 495 if !isUserConfigInMemory(tc) { 496 tc.T.Fatal("user config is not in memory") 497 } 498 499 if !LoggedIn(tc) { 500 tc.T.Fatal("Unexpectedly logged out") 501 } 502 503 g := new(errgroup.Group) 504 for i := 0; i < 5; i++ { 505 g.Go(func() error { 506 e := NewDeprovisionEngine(tc.G, fu.Username, false, libkb.LogoutOptions{}) 507 uis = libkb.UIs{ 508 LogUI: tc.G.UI.GetLogUI(), 509 SecretUI: fu.NewSecretUI(), 510 } 511 m = m.WithUIs(uis) 512 return RunEngine2(m, e) 513 }) 514 } 515 require.NoError(t, g.Wait()) 516 517 if tc.G.SecretStore() != nil { 518 secretStore := libkb.NewSecretStore(m, fu.NormalizedUsername()) 519 secret, err := secretStore.RetrieveSecret(m) 520 if err == nil { 521 tc.T.Errorf("Unexpectedly got secret %v", secret) 522 } 523 } 524 525 assertFileDoesNotExist(tc.T, dbPath) 526 assertFileDoesNotExist(tc.T, chatDBPath) 527 assertFileDoesNotExist(tc.T, secretKeysPath) 528 529 if isUserInConfigFile(tc, *fu) { 530 tc.T.Fatalf("User %s is still in the config file %s", fu.Username, tc.G.Env.GetConfigFilename()) 531 } 532 if isUserConfigInMemory(tc) { 533 tc.T.Fatal("user config is still in memory") 534 } 535 536 newKeys := getNumKeys(tc, *fu) 537 require.Equal(tc.T, expectedNumKeys, newKeys, "unexpected number of keys (failed to revoke device keys)") 538 }