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