github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/systests/passphrase_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 systests 5 6 import ( 7 "errors" 8 "fmt" 9 "io" 10 "testing" 11 "time" 12 13 "golang.org/x/net/context" 14 15 "github.com/keybase/client/go/client" 16 "github.com/keybase/client/go/engine" 17 "github.com/keybase/client/go/kbtest" 18 "github.com/keybase/client/go/libkb" 19 "github.com/keybase/client/go/protocol/keybase1" 20 "github.com/keybase/client/go/service" 21 "github.com/stretchr/testify/require" 22 ) 23 24 func TestPassphraseChange(t *testing.T) { 25 tc := setupTest(t, "pp") 26 defer tc.Cleanup() 27 28 tc2 := cloneContext(tc) 29 defer tc2.Cleanup() 30 31 stopCh := make(chan error) 32 svc := service.NewService(tc.G, false) 33 startCh := svc.GetStartChannel() 34 go func() { 35 err := svc.Run() 36 if err != nil { 37 t.Logf("Running the service produced an error: %v", err) 38 } 39 stopCh <- err 40 }() 41 <-startCh 42 43 userInfo := randomUser("pp") 44 45 sui := signupUI{ 46 info: userInfo, 47 Contextified: libkb.NewContextified(tc2.G), 48 } 49 tc2.G.SetUI(&sui) 50 signup := client.NewCmdSignupRunner(tc2.G) 51 signup.SetTest() 52 53 if err := signup.Run(); err != nil { 54 t.Fatal(err) 55 } 56 57 m := libkb.NewMetaContextForTest(*tc) 58 _, err := libkb.VerifyPassphraseForLoggedInUser(m, userInfo.passphrase) 59 require.NoError(t, err, "verified passphrase") 60 61 oldPassphrase := userInfo.passphrase 62 newPassphrase := userInfo.passphrase + userInfo.passphrase 63 sui.info.passphrase = newPassphrase 64 change := client.NewCmdPassphraseChangeRunner(tc2.G) 65 66 if err := change.Run(); err != nil { 67 t.Fatal(err) 68 } 69 70 _, err = libkb.VerifyPassphraseForLoggedInUser(m, newPassphrase) 71 require.NoError(t, err, "verified passphrase") 72 _, err = libkb.VerifyPassphraseForLoggedInUser(m, oldPassphrase) 73 require.Error(t, err, "old passphrase failed to verify") 74 75 if err := CtlStop(tc2.G); err != nil { 76 t.Fatal(err) 77 } 78 79 // If the server failed, it's also an error 80 if err := <-stopCh; err != nil { 81 t.Fatal(err) 82 } 83 } 84 85 type serviceHandle struct { 86 // Emits nil/err when stopped 87 stopCh <-chan error 88 svc *service.Service 89 } 90 91 func startNewService(tc *libkb.TestContext) (*serviceHandle, error) { 92 stopCh := make(chan error) 93 svc := service.NewService(tc.G, false) 94 startCh := svc.GetStartChannel() 95 go func() { 96 err := svc.Run() 97 if err != nil { 98 tc.T.Logf("Running the service produced an error: %v", err) 99 } 100 stopCh <- err 101 }() 102 103 // Wait for the service to start 104 <-startCh 105 106 return &serviceHandle{ 107 stopCh: stopCh, 108 svc: svc, 109 }, nil 110 } 111 112 // Tests recovering a passphrase on a second machine by logging in with paperkey. 113 func TestPassphraseRecover(t *testing.T) { 114 testPassphraseRecover(t, false /* createDeviceClone */) 115 } 116 117 func TestPassphraseRecoverWithDeviceClone(t *testing.T) { 118 testPassphraseRecover(t, true /* createDeviceClone */) 119 } 120 121 func testPassphraseRecover(t *testing.T, createDeviceClone bool) { 122 t.Logf("Start") 123 124 // Service contexts. 125 // Make a new context with cloneContext for each client session. 126 tc1 := setupTest(t, "ppa") 127 defer tc1.Cleanup() 128 tc2 := setupTest(t, "ppb") 129 defer tc2.Cleanup() 130 var tcClient *libkb.TestContext 131 132 t.Logf("Starting services") 133 s1, err := startNewService(tc1) 134 require.NoError(t, err) 135 s2, err := startNewService(tc2) 136 require.NoError(t, err) 137 138 userInfo := randomUser("pp") 139 140 t.Logf("Signup on tc1") 141 tcClient = cloneContext(tc1) 142 defer tcClient.Cleanup() 143 144 aSignupUI := signupUI{ 145 info: userInfo, 146 Contextified: libkb.NewContextified(tc1.G), 147 } 148 tcClient.G.SetUI(&aSignupUI) 149 signup := client.NewCmdSignupRunner(tcClient.G) 150 signup.SetTest() 151 err = signup.Run() 152 require.NoError(t, err) 153 tcClient = nil 154 155 // the paper key displayed during signup is in userInfo now 156 tc2.G.Log.Debug("signup paper key: %s", userInfo.displayedPaperKey) 157 158 // clone the device on tc1 159 m1 := libkb.NewMetaContextForTest(*tc1) 160 if createDeviceClone { 161 libkb.CreateClonedDevice(*tc1, m1) 162 } 163 164 t.Logf("Login on tc2") 165 tcClient = cloneContext(tc2) 166 defer tcClient.Cleanup() 167 168 aProvisionUI := &testRecoverUIProvision{ 169 username: userInfo.username, 170 paperkey: userInfo.displayedPaperKey, 171 deviceName: "away thing", 172 } 173 aUI := genericUI{ 174 g: tcClient.G, 175 LoginUI: aProvisionUI, 176 ProvisionUI: aProvisionUI, 177 SecretUI: aProvisionUI, 178 } 179 tcClient.G.SetUI(&aUI) 180 login := client.NewCmdLoginRunner(tcClient.G) 181 err = login.Run() 182 require.NoError(t, err) 183 tcClient = nil 184 185 t.Logf("Verify on tc1") 186 _, err = libkb.VerifyPassphraseForLoggedInUser(m1, userInfo.passphrase) 187 require.NoError(t, err) 188 189 oldPassphrase := userInfo.passphrase 190 newPassphrase := userInfo.passphrase + userInfo.passphrase 191 t.Logf("Passphrase %q -> %q", oldPassphrase, newPassphrase) 192 193 t.Logf("Recover on tc2") 194 tcClient = cloneContext(tc2) 195 defer tcClient.Cleanup() 196 197 aRecoverUI := &testRecoverUIRecover{ 198 Contextified: libkb.NewContextified(tc2.G), 199 passphrase: newPassphrase, 200 } 201 aUI = genericUI{ 202 g: tc2.G, 203 TerminalUI: aRecoverUI, 204 SecretUI: aRecoverUI, 205 ProvisionUI: aRecoverUI, 206 LoginUI: aRecoverUI, 207 } 208 tcClient.G.SetUI(&aUI) 209 changeCmd := client.NewCmdPassphraseChangeRunner(tcClient.G) 210 changeCmd.ForceArg = true 211 err = changeCmd.Run() 212 require.NoError(t, err) 213 tcClient = nil 214 215 t.Logf("Verify new passphrase on tc2") 216 m2 := libkb.NewMetaContextForTest(*tc2) 217 _, err = libkb.VerifyPassphraseForLoggedInUser(m2, newPassphrase) 218 require.NoError(t, err) 219 220 t.Logf("Verify new passphrase on tc1") 221 _, err = libkb.VerifyPassphraseForLoggedInUser(m1, newPassphrase) 222 require.NoError(t, err) 223 224 t.Logf("Verify old passphrase on tc1") 225 _, err = libkb.VerifyPassphraseForLoggedInUser(m1, oldPassphrase) 226 require.Error(t, err, "old passphrase passed verification after passphrase change") 227 228 t.Logf("Stop tc1") 229 err = CtlStop(tc1.G) 230 require.NoError(t, err) 231 232 t.Logf("Stop tc2") 233 err = CtlStop(tc2.G) 234 require.NoError(t, err) 235 236 t.Logf("Waiting for services to stop") 237 // If a service failed, that's an error 238 require.NoError(t, <-s1.stopCh) 239 require.NoError(t, <-s2.stopCh) 240 } 241 242 type testRecoverUIProvision struct { 243 baseNullUI 244 username string 245 deviceName string 246 paperkey string 247 } 248 249 var _ libkb.LoginUI = (*testRecoverUIProvision)(nil) 250 251 func (r *testRecoverUIProvision) GetEmailOrUsername(context.Context, int) (string, error) { 252 return r.username, nil 253 } 254 func (r *testRecoverUIProvision) PromptRevokePaperKeys(context.Context, keybase1.PromptRevokePaperKeysArg) (ret bool, err error) { 255 return false, nil 256 } 257 func (r *testRecoverUIProvision) DisplayPaperKeyPhrase(context.Context, keybase1.DisplayPaperKeyPhraseArg) error { 258 return nil 259 } 260 func (r *testRecoverUIProvision) DisplayPrimaryPaperKey(context.Context, keybase1.DisplayPrimaryPaperKeyArg) error { 261 return nil 262 } 263 func (r *testRecoverUIProvision) ChooseProvisioningMethod(context.Context, keybase1.ChooseProvisioningMethodArg) (ret keybase1.ProvisionMethod, err error) { 264 return keybase1.ProvisionMethod_PASSPHRASE, nil 265 } 266 func (r *testRecoverUIProvision) ChooseGPGMethod(context.Context, keybase1.ChooseGPGMethodArg) (ret keybase1.GPGMethod, err error) { 267 return ret, nil 268 } 269 func (r *testRecoverUIProvision) SwitchToGPGSignOK(context.Context, keybase1.SwitchToGPGSignOKArg) (ret bool, err error) { 270 return ret, nil 271 } 272 func (r *testRecoverUIProvision) ChooseDeviceType(context.Context, keybase1.ChooseDeviceTypeArg) (ret keybase1.DeviceType, err error) { 273 return ret, nil 274 } 275 func (r *testRecoverUIProvision) DisplayAndPromptSecret(context.Context, keybase1.DisplayAndPromptSecretArg) (ret keybase1.SecretResponse, err error) { 276 return ret, nil 277 } 278 func (r *testRecoverUIProvision) DisplaySecretExchanged(context.Context, int) error { 279 return nil 280 } 281 func (r *testRecoverUIProvision) PromptNewDeviceName(context.Context, keybase1.PromptNewDeviceNameArg) (ret string, err error) { 282 return r.deviceName, nil 283 } 284 func (r *testRecoverUIProvision) ProvisioneeSuccess(context.Context, keybase1.ProvisioneeSuccessArg) error { 285 return nil 286 } 287 func (r *testRecoverUIProvision) ProvisionerSuccess(context.Context, keybase1.ProvisionerSuccessArg) error { 288 return nil 289 } 290 func (r *testRecoverUIProvision) ChooseDevice(ctx context.Context, arg keybase1.ChooseDeviceArg) (ret keybase1.DeviceID, err error) { 291 for _, d := range arg.Devices { 292 if d.Type == keybase1.DeviceTypeV2_PAPER { 293 return d.DeviceID, nil 294 } 295 } 296 return "", nil 297 } 298 func (r *testRecoverUIProvision) GetPassphrase(p keybase1.GUIEntryArg, terminal *keybase1.SecretEntryArg) (res keybase1.GetPassphraseRes, err error) { 299 res.Passphrase = r.paperkey 300 return res, nil 301 } 302 func (r *testRecoverUIProvision) PromptResetAccount(_ context.Context, arg keybase1.PromptResetAccountArg) (keybase1.ResetPromptResponse, error) { 303 return keybase1.ResetPromptResponse_NOTHING, nil 304 } 305 func (r *testRecoverUIProvision) DisplayResetProgress(_ context.Context, arg keybase1.DisplayResetProgressArg) error { 306 return nil 307 } 308 func (r *testRecoverUIProvision) PromptPassphraseRecovery(_ context.Context, arg keybase1.PromptPassphraseRecoveryArg) (bool, error) { 309 return false, nil 310 } 311 func (r *testRecoverUIProvision) ExplainDeviceRecovery(_ context.Context, arg keybase1.ExplainDeviceRecoveryArg) error { 312 return nil 313 } 314 func (r *testRecoverUIProvision) ChooseDeviceToRecoverWith(_ context.Context, arg keybase1.ChooseDeviceToRecoverWithArg) (keybase1.DeviceID, error) { 315 return "", nil 316 } 317 func (r *testRecoverUIProvision) DisplayResetMessage(_ context.Context, arg keybase1.DisplayResetMessageArg) error { 318 return nil 319 } 320 321 type testRecoverUIRecover struct { 322 libkb.Contextified 323 kbtest.TestProvisionUI 324 libkb.TestLoginUI 325 passphrase string 326 } 327 328 func (n *testRecoverUIRecover) Prompt(pd libkb.PromptDescriptor, s string) (ret string, err error) { 329 n.G().Log.Debug("Terminal Prompt %d: %s -> %s (%v)\n", pd, s, ret, libkb.ErrToOk(err)) 330 return ret, fmt.Errorf("unexpected prompt") 331 } 332 func (n *testRecoverUIRecover) PromptPassword(pd libkb.PromptDescriptor, _ string) (string, error) { 333 return "", fmt.Errorf("unexpected prompt password") 334 } 335 func (n *testRecoverUIRecover) PromptPasswordMaybeScripted(pd libkb.PromptDescriptor, _ string) (string, error) { 336 return "", fmt.Errorf("unexpected prompt password") 337 } 338 func (n *testRecoverUIRecover) Output(s string) error { 339 n.G().Log.Debug("Terminal Output: %s", s) 340 return nil 341 } 342 func (n *testRecoverUIRecover) OutputDesc(od libkb.OutputDescriptor, s string) error { 343 n.G().Log.Debug("Terminal Output %d: %s", od, s) 344 return nil 345 } 346 func (n *testRecoverUIRecover) Printf(f string, args ...interface{}) (int, error) { 347 s := fmt.Sprintf(f, args...) 348 n.G().Log.Debug("Terminal Printf: %s", s) 349 return len(s), nil 350 } 351 func (n *testRecoverUIRecover) PrintfUnescaped(f string, args ...interface{}) (int, error) { 352 s := fmt.Sprintf(f, args...) 353 n.G().Log.Debug("Terminal PrintfUnescaped: %s", s) 354 return len(s), nil 355 } 356 func (n *testRecoverUIRecover) Write(b []byte) (int, error) { 357 n.G().Log.Debug("Terminal write: %s", string(b)) 358 return len(b), nil 359 } 360 func (n *testRecoverUIRecover) OutputWriter() io.Writer { 361 return n 362 } 363 func (n *testRecoverUIRecover) UnescapedOutputWriter() io.Writer { 364 return n 365 } 366 func (n *testRecoverUIRecover) ErrorWriter() io.Writer { 367 return n 368 } 369 func (n *testRecoverUIRecover) PromptYesNo(pd libkb.PromptDescriptor, s string, def libkb.PromptDefault) (ret bool, err error) { 370 n.G().Log.Debug("Terminal PromptYesNo %d: %s -> %s (%v)\n", pd, s, ret, libkb.ErrToOk(err)) 371 return ret, fmt.Errorf("unexpected prompt yes/no") 372 } 373 func (n *testRecoverUIRecover) PromptForConfirmation(prompt string) error { 374 return nil 375 } 376 func (n *testRecoverUIRecover) Tablify(headings []string, rowfunc func() []string) { 377 libkb.Tablify(n.OutputWriter(), headings, rowfunc) 378 } 379 func (n *testRecoverUIRecover) TerminalSize() (width int, height int) { 380 return 80, 24 381 } 382 func (n *testRecoverUIRecover) GetPassphrase(p keybase1.GUIEntryArg, terminal *keybase1.SecretEntryArg) (res keybase1.GetPassphraseRes, err error) { 383 res.Passphrase = n.passphrase 384 return res, nil 385 } 386 387 type errorAPIMock struct { 388 *libkb.APIArgRecorder 389 realAPI libkb.API 390 shouldError bool 391 } 392 393 func (r *errorAPIMock) GetDecode(mctx libkb.MetaContext, arg libkb.APIArg, w libkb.APIResponseWrapper) error { 394 if arg.Endpoint == "user/has_random_pw" { 395 if r.shouldError { 396 return errors.New("some api error") 397 } 398 } 399 return r.realAPI.GetDecode(mctx, arg, w) 400 } 401 402 func (r errorAPIMock) Get(mctx libkb.MetaContext, arg libkb.APIArg) (*libkb.APIRes, error) { 403 if arg.Endpoint == "user/has_random_pw" { 404 if r.shouldError { 405 return nil, errors.New("some api error") 406 } 407 } 408 return r.realAPI.Get(mctx, arg) 409 } 410 411 func TestPassphraseStateGregor(t *testing.T) { 412 set := newTestDeviceSet(t, nil) 413 defer set.cleanup() 414 dev1 := set.newDevice("primary").start(4) 415 set.signupUserWithRandomPassphrase(dev1, true) 416 dev2 := set.provisionNewDevice("secondary", 4) 417 dev3 := set.provisionNewStandaloneDevice("ternary", 4) 418 dev4 := set.provisionNewStandaloneDevice("quaternary", 4) 419 420 ucli1 := keybase1.UserClient{Cli: dev1.cli} 421 res, err := ucli1.LoadPassphraseState(context.Background(), 0) 422 require.NoError(t, err) 423 require.Equal(t, keybase1.PassphraseState_RANDOM, res) 424 425 ucli2 := keybase1.UserClient{Cli: dev2.cli} 426 res, err = ucli2.LoadPassphraseState(context.Background(), 0) 427 require.NoError(t, err) 428 require.Equal(t, keybase1.PassphraseState_RANDOM, res) 429 430 ucli3 := keybase1.UserClient{Cli: dev3.cli} 431 res, err = ucli3.LoadPassphraseState(context.Background(), 0) 432 require.NoError(t, err) 433 require.Equal(t, keybase1.PassphraseState_RANDOM, res) 434 435 mctx1 := libkb.NewMetaContextForTest(*dev1.tctx) 436 eng := engine.NewPassphraseChange(dev1.tctx.G, &keybase1.PassphraseChangeArg{ 437 Passphrase: "password2", 438 Force: true, 439 }) 440 err = eng.Run(mctx1) 441 require.NoError(t, err) 442 443 // The device that made the change learns about the state 444 pollForTrue(t, dev1.tctx.G, func(int) bool { 445 res, err = ucli1.LoadPassphraseState(context.Background(), 0) 446 if err != nil { 447 return false 448 } 449 return keybase1.PassphraseState_KNOWN == res 450 }) 451 452 // Devices that did not execute the passphrase change learns about the state 453 pollForTrue(t, dev2.tctx.G, func(int) bool { 454 res, err = ucli2.LoadPassphraseState(context.Background(), 0) 455 if err != nil { 456 return false 457 } 458 return keybase1.PassphraseState_KNOWN == res 459 }) 460 461 time.Sleep(1 * time.Second) // wait for any potential gregor messages to be received 462 463 res, err = ucli3.LoadPassphraseState(context.Background(), 0) 464 require.NoError(t, err) 465 // device not getting gregor messages will force repoll 466 require.Equal(t, res, keybase1.PassphraseState_KNOWN) 467 468 ucli4 := keybase1.UserClient{Cli: dev4.cli} 469 fakeAPI := &errorAPIMock{ 470 realAPI: dev4.tctx.G.API, 471 shouldError: true, 472 } 473 dev4.tctx.G.API = fakeAPI 474 res, err = ucli4.LoadPassphraseState(context.Background(), 0) 475 // device has no gregor state *and* api call failed, so this will error 476 require.Error(t, err) 477 require.Contains(t, err.Error(), "some api error") 478 }