github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/engine/revoke_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  	"context"
     8  	"fmt"
     9  	"testing"
    10  
    11  	"github.com/keybase/client/go/kbcrypto"
    12  	"github.com/keybase/client/go/libkb"
    13  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func getActiveDevicesAndKeys(tc libkb.TestContext, u *FakeUser) ([]libkb.DeviceWithDeviceNumber, []libkb.GenericKey) {
    18  	arg := libkb.NewLoadUserByNameArg(tc.G, u.Username).WithPublicKeyOptional()
    19  	user, err := libkb.LoadUser(arg)
    20  	require.NoError(tc.T, err)
    21  
    22  	sibkeys := user.GetComputedKeyFamily().GetAllActiveSibkeys()
    23  	subkeys := user.GetComputedKeyFamily().GetAllActiveSubkeys()
    24  
    25  	activeDevices := []libkb.DeviceWithDeviceNumber{}
    26  	for _, device := range user.GetComputedKeyFamily().GetAllDevices() {
    27  		if device.Status != nil && *device.Status == libkb.DeviceStatusActive {
    28  			activeDevices = append(activeDevices, device)
    29  		}
    30  	}
    31  	return activeDevices, append(sibkeys, subkeys...)
    32  }
    33  
    34  func doRevokeKey(tc libkb.TestContext, u *FakeUser, kid keybase1.KID) error {
    35  	revokeEngine := NewRevokeKeyEngine(tc.G, kid)
    36  	uis := libkb.UIs{
    37  		LogUI:    tc.G.UI.GetLogUI(),
    38  		SecretUI: u.NewSecretUI(),
    39  	}
    40  	m := NewMetaContextForTest(tc).WithUIs(uis)
    41  	err := RunEngine2(m, revokeEngine)
    42  	return err
    43  }
    44  
    45  func doRevokeSig(tc libkb.TestContext, u *FakeUser, sig keybase1.SigID) error {
    46  	revokeEngine := NewRevokeSigsEngine(tc.G, []string{sig.String()})
    47  	uis := libkb.UIs{
    48  		LogUI:    tc.G.UI.GetLogUI(),
    49  		SecretUI: u.NewSecretUI(),
    50  	}
    51  	m := NewMetaContextForTest(tc).WithUIs(uis)
    52  	err := RunEngine2(m, revokeEngine)
    53  	return err
    54  }
    55  
    56  func doRevokeDevice(tc libkb.TestContext, u *FakeUser, id keybase1.DeviceID, forceSelf, forceLast bool) error {
    57  	revokeEngine := NewRevokeDeviceEngine(tc.G, RevokeDeviceEngineArgs{ID: id, ForceSelf: forceSelf, ForceLast: forceLast})
    58  	uis := libkb.UIs{
    59  		LogUI:    tc.G.UI.GetLogUI(),
    60  		SecretUI: u.NewSecretUI(),
    61  	}
    62  	m := NewMetaContextForTest(tc).WithUIs(uis)
    63  	err := RunEngine2(m, revokeEngine)
    64  	return err
    65  }
    66  
    67  func assertNumDevicesAndKeys(tc libkb.TestContext, u *FakeUser, numDevices, numKeys int) {
    68  	devices, keys := getActiveDevicesAndKeys(tc, u)
    69  	if len(devices) != numDevices {
    70  		for i, d := range devices {
    71  			tc.T.Logf("device %d: %+v", i, d)
    72  		}
    73  		require.Fail(tc.T, fmt.Sprintf("Expected to find %d devices. Found %d.", numDevices, len(devices)))
    74  	}
    75  	if len(keys) != numKeys {
    76  		for i, k := range keys {
    77  			tc.T.Logf("key %d: %+v", i, k)
    78  		}
    79  		require.Fail(tc.T, fmt.Sprintf("Expected to find %d keys. Found %d.", numKeys, len(keys)))
    80  	}
    81  }
    82  
    83  func TestRevokeDevice(t *testing.T) {
    84  	testRevokeDevice(t, false)
    85  }
    86  
    87  func TestRevokeDevicePUK(t *testing.T) {
    88  	testRevokeDevice(t, true)
    89  }
    90  
    91  func testRevokeDevice(t *testing.T, upgradePerUserKey bool) {
    92  	tc := SetupEngineTest(t, "rev")
    93  	defer tc.Cleanup()
    94  	tc.Tp.DisableUpgradePerUserKey = !upgradePerUserKey
    95  
    96  	u := CreateAndSignupFakeUserPaper(tc, "rev")
    97  
    98  	assertNumDevicesAndKeys(tc, u, 2, 4)
    99  
   100  	devices, _ := getActiveDevicesAndKeys(tc, u)
   101  	var thisDevice libkb.DeviceWithDeviceNumber
   102  	for _, device := range devices {
   103  		if device.Type != keybase1.DeviceTypeV2_PAPER {
   104  			thisDevice = device
   105  		}
   106  	}
   107  
   108  	// Revoking the current device should fail.
   109  	err := doRevokeDevice(tc, u, thisDevice.ID, false, false)
   110  	if err == nil {
   111  		tc.T.Fatal("Expected revoking the current device to fail.")
   112  	}
   113  
   114  	assertNumDevicesAndKeys(tc, u, 2, 4)
   115  
   116  	// But it should succeed with the --force flag.
   117  	err = doRevokeDevice(tc, u, thisDevice.ID, true, false)
   118  	if err != nil {
   119  		tc.T.Fatal(err)
   120  	}
   121  
   122  	assertNumDevicesAndKeys(tc, u, 1, 2)
   123  }
   124  
   125  func TestRevokePaperDevice(t *testing.T) {
   126  	testRevokePaperDevice(t, false)
   127  }
   128  
   129  func TestRevokePaperDevicePUK(t *testing.T) {
   130  	testRevokePaperDevice(t, true)
   131  }
   132  
   133  func testRevokePaperDevice(t *testing.T, upgradePerUserKey bool) {
   134  	tc := SetupEngineTest(t, "rev")
   135  	defer tc.Cleanup()
   136  	tc.Tp.DisableUpgradePerUserKey = !upgradePerUserKey
   137  
   138  	u := CreateAndSignupFakeUserPaper(tc, "rev")
   139  
   140  	t.Logf("username: %s", u.Username)
   141  
   142  	assertNumDevicesAndKeys(tc, u, 2, 4)
   143  
   144  	assertNumDevicesAndKeys(tc, u, 2, 4)
   145  
   146  	revokeAnyPaperKey(tc, u)
   147  
   148  	assertNumDevicesAndKeys(tc, u, 1, 2)
   149  
   150  	if tc.G.Env.GetUpgradePerUserKey() {
   151  		checkPerUserKeyring(t, tc.G, 2)
   152  	} else {
   153  		checkPerUserKeyring(t, tc.G, 0)
   154  	}
   155  
   156  	arg := libkb.NewLoadUserByNameArg(tc.G, u.Username)
   157  	user, err := libkb.LoadUser(arg)
   158  	require.NoError(t, err)
   159  
   160  	var nextSeqno int
   161  	var postedSeqno int
   162  	if upgradePerUserKey {
   163  		nextSeqno = 7
   164  		postedSeqno = 4
   165  	} else {
   166  		nextSeqno = 5
   167  		postedSeqno = 3
   168  	}
   169  	nextExpected, err := user.GetExpectedNextHighSkip(libkb.NewMetaContextForTest(tc))
   170  	require.NoError(t, err)
   171  	require.Equal(t, nextExpected.Seqno, keybase1.Seqno(nextSeqno))
   172  	assertPostedHighSkipSeqno(t, tc, user.GetName(), postedSeqno)
   173  }
   174  
   175  func TestRevokerPaperDeviceTwice(t *testing.T) {
   176  	testRevokerPaperDeviceTwice(t, false)
   177  }
   178  
   179  func TestRevokerPaperDeviceTwicePUK(t *testing.T) {
   180  	testRevokerPaperDeviceTwice(t, true)
   181  }
   182  
   183  func testRevokerPaperDeviceTwice(t *testing.T, upgradePerUserKey bool) {
   184  	tc := SetupEngineTest(t, "rev")
   185  	defer tc.Cleanup()
   186  	tc.Tp.DisableUpgradePerUserKey = !upgradePerUserKey
   187  
   188  	u := CreateAndSignupFakeUserPaper(tc, "rev")
   189  
   190  	t.Logf("username: %s", u.Username)
   191  
   192  	t.Logf("generate second paper key")
   193  	{
   194  		uis := libkb.UIs{
   195  			LogUI:    tc.G.UI.GetLogUI(),
   196  			LoginUI:  &libkb.TestLoginUI{},
   197  			SecretUI: &libkb.TestSecretUI{},
   198  		}
   199  		eng := NewPaperKey(tc.G)
   200  		m := NewMetaContextForTest(tc).WithUIs(uis)
   201  		err := RunEngine2(m, eng)
   202  		require.NoError(t, err)
   203  		require.NotEqual(t, 0, len(eng.Passphrase()), "empty passphrase")
   204  	}
   205  
   206  	t.Logf("check")
   207  	assertNumDevicesAndKeys(tc, u, 3, 6)
   208  
   209  	t.Logf("revoke paper key 1")
   210  	revokeAnyPaperKey(tc, u)
   211  
   212  	t.Logf("revoke paper key 2")
   213  	revokeAnyPaperKey(tc, u)
   214  
   215  	t.Logf("check")
   216  	assertNumDevicesAndKeys(tc, u, 1, 2)
   217  
   218  	if tc.G.Env.GetUpgradePerUserKey() {
   219  		checkPerUserKeyring(t, tc.G, 3)
   220  	}
   221  }
   222  
   223  func checkPerUserKeyring(t *testing.T, g *libkb.GlobalContext, expectedCurrentGeneration int) {
   224  	pukring, err := g.GetPerUserKeyring(context.Background())
   225  	require.NoError(t, err)
   226  	// Weakly check. If the keyring was not initialized, don't worry about it.
   227  	if pukring.HasAnyKeys() == (expectedCurrentGeneration > 0) {
   228  		require.Equal(t, keybase1.PerUserKeyGeneration(expectedCurrentGeneration), pukring.CurrentGeneration())
   229  	}
   230  	pukring = nil
   231  
   232  	// double check that the per-user-keyring is correct
   233  	g.ClearPerUserKeyring()
   234  	pukring, err = g.GetPerUserKeyring(context.Background())
   235  	require.NoError(t, err)
   236  	require.NoError(t, pukring.Sync(libkb.NewMetaContextTODO(g)))
   237  	require.Equal(t, keybase1.PerUserKeyGeneration(expectedCurrentGeneration), pukring.CurrentGeneration())
   238  }
   239  
   240  func TestRevokeKey(t *testing.T) {
   241  	tc := SetupEngineTest(t, "rev")
   242  	defer tc.Cleanup()
   243  
   244  	u := createFakeUserWithPGPSibkeyPaper(tc)
   245  
   246  	assertNumDevicesAndKeys(tc, u, 2, 5)
   247  
   248  	_, keys := getActiveDevicesAndKeys(tc, u)
   249  	var pgpKey *libkb.GenericKey
   250  	for i, key := range keys {
   251  		if libkb.IsPGP(key) {
   252  			// XXX: Don't use &key. That refers to the loop variable, which
   253  			// gets overwritten.
   254  			pgpKey = &keys[i] //
   255  			break
   256  		}
   257  	}
   258  	if pgpKey == nil {
   259  		t.Fatal("Expected to find PGP key")
   260  	}
   261  
   262  	err := doRevokeKey(tc, u, (*pgpKey).GetKID())
   263  	if err != nil {
   264  		tc.T.Fatal(err)
   265  	}
   266  
   267  	assertNumDevicesAndKeys(tc, u, 2, 4)
   268  }
   269  
   270  // See issue #370.
   271  func TestTrackAfterRevoke(t *testing.T) {
   272  	doWithSigChainVersions(func(sigVersion libkb.SigVersion) {
   273  		_testTrackAfterRevoke(t, sigVersion)
   274  	})
   275  }
   276  
   277  func _testTrackAfterRevoke(t *testing.T, sigVersion libkb.SigVersion) {
   278  	tc1 := SetupEngineTest(t, "rev")
   279  	defer tc1.Cleanup()
   280  
   281  	// We need two devices. Use a paperkey to sign into the second device.
   282  
   283  	// Sign up on tc1:
   284  	u := CreateAndSignupFakeUserGPG(tc1, "pgp")
   285  
   286  	t.Logf("create a paperkey")
   287  	beng := NewPaperKey(tc1.G)
   288  	uis := libkb.UIs{
   289  		LogUI:    tc1.G.UI.GetLogUI(),
   290  		LoginUI:  &libkb.TestLoginUI{},
   291  		SecretUI: &libkb.TestSecretUI{},
   292  	}
   293  	m := NewMetaContextForTest(tc1).WithUIs(uis)
   294  	err := RunEngine2(m, beng)
   295  	require.NoError(t, err)
   296  	paperkey := beng.Passphrase()
   297  
   298  	// Redo SetupEngineTest to get a new home directory...should look like a new device.
   299  	tc2 := SetupEngineTest(t, "login")
   300  	defer tc2.Cleanup()
   301  
   302  	// Login on device tc2 using the paperkey.
   303  	t.Logf("running LoginWithPaperKey")
   304  	secUI := u.NewSecretUI()
   305  	secUI.Passphrase = paperkey
   306  	provUI := newTestProvisionUIPaper()
   307  	provLoginUI := &libkb.TestLoginUI{Username: u.Username}
   308  	uis = libkb.UIs{
   309  		ProvisionUI: provUI,
   310  		LogUI:       tc2.G.UI.GetLogUI(),
   311  		SecretUI:    secUI,
   312  		LoginUI:     provLoginUI,
   313  		GPGUI:       &gpgtestui{},
   314  	}
   315  	eng := NewLogin(tc2.G, keybase1.DeviceTypeV2_DESKTOP, "", keybase1.ClientType_CLI)
   316  	m = NewMetaContextForTest(tc2).WithUIs(uis)
   317  	err = RunEngine2(m, eng)
   318  	require.NoError(t, err)
   319  
   320  	t.Logf("tc2 revokes tc1 device:")
   321  	err = doRevokeDevice(tc2, u, tc1.G.Env.GetDeviceID(), false, false)
   322  	require.NoError(t, err)
   323  
   324  	// Still logged in on tc1.  Try to use it to track someone.  It should fail
   325  	// with a KeyRevokedError.
   326  	_, _, err = runTrack(tc1, u, "t_alice", sigVersion)
   327  	if err == nil {
   328  		t.Fatal("expected runTrack to return an error")
   329  	}
   330  	if _, ok := err.(libkb.BadSessionError); !ok {
   331  		t.Errorf("expected libkb.BadSessionError, got %T", err)
   332  	}
   333  }
   334  
   335  func TestSignAfterRevoke(t *testing.T) {
   336  	tc1 := SetupEngineTest(t, "rev")
   337  	defer tc1.Cleanup()
   338  
   339  	// We need two devices. Use a paperkey to sign into the second device.
   340  
   341  	// Sign up on tc1:
   342  	u := CreateAndSignupFakeUserGPG(tc1, "pgp")
   343  
   344  	t.Logf("create a paperkey")
   345  	beng := NewPaperKey(tc1.G)
   346  	uis := libkb.UIs{
   347  		LogUI:    tc1.G.UI.GetLogUI(),
   348  		LoginUI:  &libkb.TestLoginUI{},
   349  		SecretUI: &libkb.TestSecretUI{},
   350  	}
   351  	m := NewMetaContextForTest(tc1).WithUIs(uis)
   352  	err := RunEngine2(m, beng)
   353  	require.NoError(t, err)
   354  	paperkey := beng.Passphrase()
   355  
   356  	// Redo SetupEngineTest to get a new home directory...should look like a new device.
   357  	tc2 := SetupEngineTest(t, "login")
   358  	defer tc2.Cleanup()
   359  
   360  	// Login on device tc2 using the paperkey.
   361  	t.Logf("running LoginWithPaperKey")
   362  	secUI := u.NewSecretUI()
   363  	secUI.Passphrase = paperkey
   364  	provUI := newTestProvisionUIPaper()
   365  	provLoginUI := &libkb.TestLoginUI{Username: u.Username}
   366  	uis = libkb.UIs{
   367  		ProvisionUI: provUI,
   368  		LogUI:       tc2.G.UI.GetLogUI(),
   369  		SecretUI:    secUI,
   370  		LoginUI:     provLoginUI,
   371  		GPGUI:       &gpgtestui{},
   372  	}
   373  	eng := NewLogin(tc2.G, keybase1.DeviceTypeV2_DESKTOP, "", keybase1.ClientType_CLI)
   374  	m = NewMetaContextForTest(tc2).WithUIs(uis)
   375  	err = RunEngine2(m, eng)
   376  	require.NoError(t, err)
   377  
   378  	t.Logf("tc2 revokes tc1 device:")
   379  	err = doRevokeDevice(tc2, u, tc1.G.Env.GetDeviceID(), false, false)
   380  	require.NoError(t, err)
   381  
   382  	// Still logged in on tc1, a revoked device.
   383  
   384  	// Test signing with (revoked) device key on tc1, which works...
   385  	msg := []byte("test message")
   386  	ret, err := SignED25519(context.TODO(), tc1.G, keybase1.SignED25519Arg{
   387  		Msg: msg,
   388  	})
   389  	if err != nil {
   390  		t.Fatal(err)
   391  	}
   392  	publicKey := kbcrypto.NaclSigningKeyPublic(ret.PublicKey)
   393  	if !publicKey.Verify(msg, kbcrypto.NaclSignature(ret.Sig)) {
   394  		t.Error(kbcrypto.VerificationError{})
   395  	}
   396  
   397  	// This should log out tc1:
   398  	if err := NewMetaContextForTest(tc1).LogoutAndDeprovisionIfRevoked(); err != nil {
   399  		t.Fatal(err)
   400  	}
   401  
   402  	err = AssertLoggedOut(tc1)
   403  	require.NoError(t, err)
   404  
   405  	// And now this should fail.
   406  	ret, err = SignED25519(context.TODO(), tc1.G, keybase1.SignED25519Arg{
   407  		Msg: msg,
   408  	})
   409  	if err == nil {
   410  		t.Fatal("nil error signing after LogoutAndDeprovisionIfRevoked")
   411  	}
   412  	if _, ok := err.(libkb.LoginRequiredError); !ok {
   413  		t.Errorf("error type: %T, expected libkb.LoginRequiredError", err)
   414  	}
   415  }
   416  
   417  // Check that if not on a revoked device that LogoutAndDeprovisionIfRevoked doesn't do anything.
   418  func TestLogoutAndDeprovisionIfRevokedNoop(t *testing.T) {
   419  	tc := SetupEngineTest(t, "rev")
   420  	defer tc.Cleanup()
   421  
   422  	CreateAndSignupFakeUser(tc, "rev")
   423  
   424  	err := AssertLoggedIn(tc)
   425  	require.NoError(t, err)
   426  
   427  	if err := NewMetaContextForTest(tc).LogoutAndDeprovisionIfRevoked(); err != nil {
   428  		t.Fatal(err)
   429  	}
   430  
   431  	err = AssertLoggedIn(tc)
   432  	require.NoError(t, err)
   433  
   434  	msg := []byte("test message")
   435  	ret, err := SignED25519(context.TODO(), tc.G, keybase1.SignED25519Arg{
   436  		Msg: msg,
   437  	})
   438  	if err != nil {
   439  		t.Fatal(err)
   440  	}
   441  	publicKey := kbcrypto.NaclSigningKeyPublic(ret.PublicKey)
   442  	if !publicKey.Verify(msg, kbcrypto.NaclSignature(ret.Sig)) {
   443  		t.Error(kbcrypto.VerificationError{})
   444  	}
   445  }
   446  
   447  func revokeAnyPaperKey(tc libkb.TestContext, fu *FakeUser) *libkb.Device {
   448  	t := tc.T
   449  	t.Logf("revoke a paper key")
   450  	devices, _ := getActiveDevicesAndKeys(tc, fu)
   451  	var revokeDevice libkb.DeviceWithDeviceNumber
   452  	for _, device := range devices {
   453  		if device.Type == keybase1.DeviceTypeV2_PAPER {
   454  			revokeDevice = device
   455  		}
   456  	}
   457  	require.NotNil(t, revokeDevice, "no paper key found to revoke")
   458  	t.Logf("revoke %s", revokeDevice.ID)
   459  	err := doRevokeDevice(tc, fu, revokeDevice.ID, false, false)
   460  	require.NoError(t, err)
   461  	return revokeDevice.Device
   462  }
   463  
   464  func TestRevokeLastDevice(t *testing.T) {
   465  	tc := SetupEngineTest(t, "rev")
   466  	defer tc.Cleanup()
   467  
   468  	u := CreateAndSignupFakeUser(tc, "rev")
   469  
   470  	assertNumDevicesAndKeys(tc, u, 1, 2)
   471  
   472  	devices, _ := getActiveDevicesAndKeys(tc, u)
   473  	thisDevice := devices[0]
   474  
   475  	// Revoking the current device should fail.
   476  	err := doRevokeDevice(tc, u, thisDevice.ID, false, false)
   477  	if err == nil {
   478  		t.Fatal("Expected revoking the current device to fail.")
   479  	}
   480  
   481  	assertNumDevicesAndKeys(tc, u, 1, 2)
   482  
   483  	// Since this is the last device, it should fail with `force` too:
   484  	err = doRevokeDevice(tc, u, thisDevice.ID, true, false)
   485  	if err == nil {
   486  		t.Fatal("Expected revoking the current last device to fail.")
   487  	}
   488  
   489  	assertNumDevicesAndKeys(tc, u, 1, 2)
   490  
   491  	// With `force` and `forceLast`, the revoke should succeed
   492  	err = doRevokeDevice(tc, u, thisDevice.ID, true, true)
   493  	if err != nil {
   494  		t.Fatal(err)
   495  	}
   496  
   497  	assertNumDevicesAndKeys(tc, u, 0, 0)
   498  }
   499  
   500  func TestRevokeLastDevicePGP(t *testing.T) {
   501  	tc := SetupEngineTest(t, "rev")
   502  	u1 := createFakeUserWithPGPOnly(t, tc)
   503  	assertNumDevicesAndKeys(tc, u1, 0, 1)
   504  	Logout(tc)
   505  	tc.Cleanup()
   506  
   507  	tc = SetupEngineTest(t, "rev")
   508  	defer tc.Cleanup()
   509  
   510  	uis := libkb.UIs{
   511  		ProvisionUI: newTestProvisionUIPassphrase(),
   512  		LoginUI:     &libkb.TestLoginUI{Username: u1.Username},
   513  		LogUI:       tc.G.UI.GetLogUI(),
   514  		SecretUI:    u1.NewSecretUI(),
   515  		GPGUI:       &gpgtestui{},
   516  	}
   517  	eng := NewLogin(tc.G, keybase1.DeviceTypeV2_DESKTOP, "", keybase1.ClientType_CLI)
   518  	m := NewMetaContextForTest(tc).WithUIs(uis)
   519  	if err := RunEngine2(m, eng); err != nil {
   520  		t.Fatal(err)
   521  	}
   522  	testUserHasDeviceKey(tc)
   523  	hasZeroPaperDev(tc, u1)
   524  	if err := AssertProvisioned(tc); err != nil {
   525  		t.Fatal(err)
   526  	}
   527  
   528  	devices, _ := getActiveDevicesAndKeys(tc, u1)
   529  	thisDevice := devices[0]
   530  
   531  	// Revoking the current device should fail.
   532  	err := doRevokeDevice(tc, u1, thisDevice.ID, false, false)
   533  	if err == nil {
   534  		t.Fatal("Expected revoking the current device to fail.")
   535  	}
   536  	if _, ok := err.(libkb.RevokeLastDevicePGPError); !ok {
   537  		t.Fatalf("expected libkb.RevokeLastDevicePGPError, got %T", err)
   538  	}
   539  
   540  	assertNumDevicesAndKeys(tc, u1, 1, 3)
   541  
   542  	// Since this is the last device, it should fail with `force` too:
   543  	err = doRevokeDevice(tc, u1, thisDevice.ID, true, false)
   544  	if err == nil {
   545  		t.Fatal("Expected revoking the current last device to fail.")
   546  	}
   547  	if _, ok := err.(libkb.RevokeLastDevicePGPError); !ok {
   548  		t.Fatalf("expected libkb.RevokeLastDevicePGPError, got %T", err)
   549  	}
   550  
   551  	assertNumDevicesAndKeys(tc, u1, 1, 3)
   552  
   553  	// With `force` and `forceLast`, the revoke should also fail because of pgp key
   554  	err = doRevokeDevice(tc, u1, thisDevice.ID, true, true)
   555  	if err == nil {
   556  		t.Fatal("Expected revoking current last device with forceLast to fail")
   557  	}
   558  	if _, ok := err.(libkb.RevokeLastDevicePGPError); !ok {
   559  		t.Fatalf("expected libkb.RevokeLastDevicePGPError, got %T", err)
   560  	}
   561  
   562  	assertNumDevicesAndKeys(tc, u1, 1, 3)
   563  }