github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/sbs_test.go (about)

     1  package chat
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/keybase/client/go/chat/utils"
    11  	"github.com/keybase/client/go/emails"
    12  	"github.com/keybase/client/go/engine"
    13  	"github.com/keybase/client/go/kbtest"
    14  	"github.com/keybase/client/go/libkb"
    15  	"github.com/keybase/client/go/phonenumbers"
    16  	"github.com/keybase/client/go/protocol/chat1"
    17  	"github.com/keybase/client/go/protocol/gregor1"
    18  	"github.com/keybase/client/go/protocol/keybase1"
    19  	"github.com/stretchr/testify/require"
    20  )
    21  
    22  type ProveRooterUI struct {
    23  	Username string
    24  }
    25  
    26  func (p *ProveRooterUI) PromptUsername(_ context.Context, _ keybase1.PromptUsernameArg) (string, error) {
    27  	return p.Username, nil
    28  }
    29  
    30  func (p *ProveRooterUI) OutputInstructions(_ context.Context, arg keybase1.OutputInstructionsArg) error {
    31  	return nil
    32  }
    33  
    34  func (p *ProveRooterUI) PromptOverwrite(_ context.Context, _ keybase1.PromptOverwriteArg) (bool, error) {
    35  	return true, nil
    36  }
    37  
    38  func (p *ProveRooterUI) OutputPrechecks(_ context.Context, _ keybase1.OutputPrechecksArg) error {
    39  	return nil
    40  }
    41  
    42  func (p *ProveRooterUI) PreProofWarning(_ context.Context, _ keybase1.PreProofWarningArg) (bool, error) {
    43  	return true, nil
    44  }
    45  
    46  func (p *ProveRooterUI) DisplayRecheckWarning(_ context.Context, _ keybase1.DisplayRecheckWarningArg) error {
    47  	return nil
    48  }
    49  
    50  func (p *ProveRooterUI) OkToCheck(_ context.Context, _ keybase1.OkToCheckArg) (bool, error) {
    51  	return true, nil
    52  }
    53  
    54  func (p *ProveRooterUI) Checking(_ context.Context, _ keybase1.CheckingArg) error {
    55  	return nil
    56  }
    57  
    58  func (p *ProveRooterUI) ContinueChecking(_ context.Context, _ int) (bool, error) {
    59  	return true, nil
    60  }
    61  
    62  func proveRooter(t *testing.T, g *libkb.GlobalContext, fu *kbtest.FakeUser) (sigID keybase1.SigID) {
    63  	arg := keybase1.StartProofArg{
    64  		Service:  "rooter",
    65  		Username: fu.Username,
    66  		Auto:     true,
    67  	}
    68  
    69  	eng := engine.NewProve(g, &arg)
    70  
    71  	proveUI := &ProveRooterUI{Username: fu.Username}
    72  
    73  	uis := libkb.UIs{
    74  		LogUI:    g.UI.GetLogUI(),
    75  		SecretUI: fu.NewSecretUI(),
    76  		ProveUI:  proveUI,
    77  	}
    78  	m := libkb.NewMetaContextTODO(g).WithUIs(uis)
    79  
    80  	require.NoError(t, engine.RunEngine2(m, eng))
    81  
    82  	sigID = eng.SigID()
    83  	checkEng := engine.NewProveCheck(g, sigID)
    84  	require.NoError(t, engine.RunEngine2(m, checkEng))
    85  	found, status, state, text := checkEng.Results()
    86  	if !found {
    87  		t.Errorf("proof not found, expected to be found")
    88  	}
    89  	if status != 1 {
    90  		t.Errorf("proof status: %d, expected 1", int(status))
    91  	}
    92  	if state != 1 {
    93  		t.Errorf("proof state: %d, expected 1", int(state))
    94  	}
    95  	if len(text) == 0 {
    96  		t.Errorf("empty proof text, expected non-empty")
    97  	}
    98  	return sigID
    99  }
   100  
   101  func revokeRooter(t *testing.T, g *libkb.GlobalContext, fu *kbtest.FakeUser, sigID keybase1.SigID) {
   102  	uis := libkb.UIs{
   103  		LogUI:    g.UI.GetLogUI(),
   104  		SecretUI: fu.NewSecretUI(),
   105  	}
   106  	mctx := libkb.NewMetaContextTODO(g).WithUIs(uis)
   107  	eng := engine.NewRevokeSigsEngine(g, []string{sigID.String()})
   108  	err := engine.RunEngine2(mctx, eng)
   109  	require.NoError(t, err)
   110  }
   111  
   112  func addAndVerifyPhone(t *testing.T, g *libkb.GlobalContext, phoneNumber keybase1.PhoneNumber) {
   113  	mctx := libkb.NewMetaContextTODO(g)
   114  	require.NoError(t, phonenumbers.AddPhoneNumber(mctx, phoneNumber, keybase1.IdentityVisibility_PRIVATE))
   115  
   116  	code, err := kbtest.GetPhoneVerificationCode(libkb.NewMetaContextTODO(g), phoneNumber)
   117  	require.NoError(t, err)
   118  
   119  	require.NoError(t, phonenumbers.VerifyPhoneNumber(mctx, phoneNumber, code))
   120  
   121  	t.Logf("Added and verified phone number: %s", phoneNumber.String())
   122  }
   123  
   124  type sbsTestCase struct {
   125  	getChatAssertion func(user *kbtest.FakeUser) string
   126  	sbsVerify        func(user *kbtest.FakeUser, g *libkb.GlobalContext)
   127  	sbsRevoke        func(user *kbtest.FakeUser, g *libkb.GlobalContext)
   128  }
   129  
   130  func runChatSBSScenario(t *testing.T, testCase sbsTestCase) {
   131  	runWithMemberTypes(t, func(mt chat1.ConversationMembersType) {
   132  		runWithEphemeral(t, mt, func(ephemeralLifetime *gregor1.DurationSec) {
   133  			// Only run this test for imp teams
   134  			switch mt {
   135  			case chat1.ConversationMembersType_IMPTEAMNATIVE, chat1.ConversationMembersType_IMPTEAMUPGRADE:
   136  			default:
   137  				return
   138  			}
   139  
   140  			ctc := makeChatTestContext(t, "TestChatSrvSBS", 2)
   141  			defer ctc.cleanup()
   142  			users := ctc.users()
   143  
   144  			// If we are sending ephemeral messages make sure both users have
   145  			// user/device EKs
   146  			if ephemeralLifetime != nil {
   147  				u1 := ctc.as(t, users[0])
   148  				err := u1.h.G().GetEKLib().KeygenIfNeeded(u1.h.G().MetaContext(context.Background()))
   149  				require.NoError(t, err)
   150  				u2 := ctc.as(t, users[1])
   151  				err = u2.h.G().GetEKLib().KeygenIfNeeded(u2.h.G().MetaContext(context.Background()))
   152  				require.NoError(t, err)
   153  			}
   154  
   155  			tc1 := ctc.world.Tcs[users[1].Username]
   156  			ctx := ctc.as(t, users[0]).startCtx
   157  			listener0 := newServerChatListener()
   158  			ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0)
   159  			listener1 := newServerChatListener()
   160  			ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener1)
   161  
   162  			convoAssertions := []string{
   163  				users[0].Username,
   164  				testCase.getChatAssertion(users[1]),
   165  			}
   166  			displayName := strings.Join(convoAssertions, ",")
   167  
   168  			t.Logf("Creating a convo with display name %q", displayName)
   169  
   170  			ncres, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx,
   171  				chat1.NewConversationLocalArg{
   172  					TlfName:          displayName,
   173  					TopicType:        chat1.TopicType_CHAT,
   174  					TlfVisibility:    keybase1.TLFVisibility_PRIVATE,
   175  					MembersType:      mt,
   176  					IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI,
   177  				})
   178  			require.NoError(t, err)
   179  
   180  			mustPostLocalEphemeralForTest(t, ctc, users[0], ncres.Conv.Info,
   181  				chat1.NewMessageBodyWithText(chat1.MessageText{
   182  					Body: "Hi from user 0 (before resolution)",
   183  				}), ephemeralLifetime)
   184  			consumeNewMsgRemote(t, listener0, chat1.MessageType_TEXT)
   185  
   186  			// This message should never go in - user is not in the conv yet.
   187  			_, err = postLocalEphemeralForTest(t, ctc, users[1], ncres.Conv.Info,
   188  				chat1.NewMessageBodyWithText(chat1.MessageText{
   189  					Body: "HI",
   190  				}), ephemeralLifetime)
   191  			require.Error(t, err)
   192  			require.IsType(t, utils.ErrGetVerifiedConvNotFound, err)
   193  
   194  			t.Logf("running sbsVerify now")
   195  
   196  			kickTeamRekeyd(tc1.Context().ExternalG(), t)
   197  			testCase.sbsVerify(users[1], tc1.Context().ExternalG())
   198  
   199  			t.Logf("uid1: %s", users[1].User.GetUID())
   200  			t.Logf("teamID: %s", ncres.Conv.Info.Triple.Tlfid)
   201  			t.Logf("convID: %x", ncres.Conv.GetConvID().DbShortForm())
   202  
   203  			select {
   204  			case rres := <-listener0.membersUpdate:
   205  				require.Equal(t, ncres.Conv.GetConvID(), rres.ConvID)
   206  				require.Equal(t, 1, len(rres.Members))
   207  				require.Equal(t, users[1].Username, rres.Members[0].Member)
   208  			case <-time.After(20 * time.Second):
   209  				require.Fail(t, "no resolve")
   210  			}
   211  			select {
   212  			case rres := <-listener1.joinedConv:
   213  				require.NotNil(t, rres)
   214  				require.Equal(t, ncres.Conv.GetConvID().ConvIDStr(), rres.ConvID)
   215  				require.Equal(t, 2, len(rres.Participants))
   216  			case <-time.After(20 * time.Second):
   217  				require.Fail(t, "no resolve")
   218  			}
   219  			consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM)
   220  			consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM)
   221  
   222  			mustPostLocalEphemeralForTest(t, ctc, users[0], ncres.Conv.Info,
   223  				chat1.NewMessageBodyWithText(chat1.MessageText{
   224  					Body: "Hi from user 0 (after resolution)",
   225  				}), ephemeralLifetime)
   226  			consumeNewMsgRemote(t, listener0, chat1.MessageType_TEXT)
   227  			consumeNewMsgRemote(t, listener1, chat1.MessageType_TEXT)
   228  
   229  			mustPostLocalEphemeralForTest(t, ctc, users[1], ncres.Conv.Info,
   230  				chat1.NewMessageBodyWithText(chat1.MessageText{
   231  					Body: "Hi from user 1 (after resolution)",
   232  				}), ephemeralLifetime)
   233  			consumeNewMsgRemote(t, listener0, chat1.MessageType_TEXT)
   234  			consumeNewMsgRemote(t, listener1, chat1.MessageType_TEXT)
   235  
   236  			verifyThread := func(user *kbtest.FakeUser, local bool) {
   237  				var messages []chat1.MessageUnboxed
   238  				if local {
   239  					tvres, err := ctc.as(t, user).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{
   240  						ConversationID: ncres.Conv.GetConvID(),
   241  						Query: &chat1.GetThreadQuery{
   242  							MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT},
   243  						},
   244  					})
   245  					require.NoError(t, err)
   246  					messages = tvres.Thread.Messages
   247  				} else {
   248  					tc := ctc.world.Tcs[user.Username]
   249  					ctx := ctc.as(t, user).startCtx
   250  					// Nuke DB so we don't just pull cached messages.
   251  					_, err = tc.G.LocalDb.Nuke()
   252  					require.NoError(t, err)
   253  					_, err := tc.G.LocalChatDb.Nuke()
   254  					require.NoError(t, err)
   255  					tv, err := tc.Context().ConvSource.Pull(
   256  						ctx,
   257  						ncres.Conv.GetConvID(),
   258  						user.GetUID().ToBytes(),
   259  						chat1.GetThreadReason_GENERAL, nil, &chat1.GetThreadQuery{
   260  							MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT},
   261  						},
   262  						nil,
   263  					)
   264  					require.NoError(t, err)
   265  					messages = tv.Messages
   266  				}
   267  
   268  				for _, msg := range messages {
   269  					// Whether unboxing will succeed in the ephemeral case
   270  					// depends on whether pairwise MAC'ing was used, which in
   271  					// turn depends on the size of the team, in a way that we
   272  					// might tune in the future. Allow that specific failure.
   273  
   274  					if ephemeralLifetime != nil && msg.IsError() {
   275  						require.Equal(t, chat1.MessageUnboxedErrorType_PAIRWISE_MISSING, msg.Error().ErrType,
   276  							"Error is %s", msg.Error().ErrMsg)
   277  					} else {
   278  						if msg.IsError() {
   279  							require.FailNow(t, "verifyThread message error", msg.Error().ErrMsg)
   280  						}
   281  						require.True(t, msg.IsValid())
   282  					}
   283  				}
   284  
   285  				require.Equal(t, 3, len(messages))
   286  			}
   287  
   288  			verifyThread(users[0], true /* local */)
   289  			verifyThread(users[1], true /* local */)
   290  
   291  			if testCase.sbsRevoke != nil && mt == chat1.ConversationMembersType_IMPTEAMNATIVE {
   292  				t.Logf("running sbsRevoke now")
   293  				testCase.sbsRevoke(users[1], tc1.Context().ExternalG())
   294  
   295  				ctc.advanceFakeClock(time.Hour)
   296  
   297  				verifyThread(users[0], false /* local */)
   298  				verifyThread(users[1], false /* local */)
   299  			}
   300  		})
   301  	})
   302  
   303  }
   304  
   305  func TestChatSrvSBSRooter(t *testing.T) {
   306  	var sigID keybase1.SigID
   307  	runChatSBSScenario(t, sbsTestCase{
   308  		getChatAssertion: func(user *kbtest.FakeUser) string {
   309  			return fmt.Sprintf("%s@rooter", user.Username)
   310  		},
   311  		sbsVerify: func(user *kbtest.FakeUser, g *libkb.GlobalContext) {
   312  			sigID = proveRooter(t, g, user)
   313  		},
   314  		sbsRevoke: func(user *kbtest.FakeUser, g *libkb.GlobalContext) {
   315  			revokeRooter(t, g, user, sigID)
   316  		},
   317  	})
   318  }
   319  
   320  func TestChatSrvSBSPhone(t *testing.T) {
   321  	var phoneNumber keybase1.PhoneNumber
   322  	runChatSBSScenario(t, sbsTestCase{
   323  		getChatAssertion: func(user *kbtest.FakeUser) string {
   324  			phone := kbtest.GenerateTestPhoneNumber()
   325  			phoneNumber = keybase1.PhoneNumber("+" + phone)
   326  			return fmt.Sprintf("%s@phone", phone)
   327  		},
   328  		sbsVerify: func(user *kbtest.FakeUser, g *libkb.GlobalContext) {
   329  			addAndVerifyPhone(t, g, phoneNumber)
   330  			err := phonenumbers.SetVisibilityPhoneNumber(libkb.NewMetaContextTODO(g), phoneNumber, keybase1.IdentityVisibility_PUBLIC)
   331  			require.NoError(t, err)
   332  		},
   333  		sbsRevoke: func(user *kbtest.FakeUser, g *libkb.GlobalContext) {
   334  			err := phonenumbers.SetVisibilityPhoneNumber(libkb.NewMetaContextTODO(g), phoneNumber, keybase1.IdentityVisibility_PRIVATE)
   335  			require.NoError(t, err)
   336  		},
   337  	})
   338  }
   339  
   340  func TestChatSrvSBSEmail(t *testing.T) {
   341  	runChatSBSScenario(t, sbsTestCase{
   342  		getChatAssertion: func(user *kbtest.FakeUser) string {
   343  			return fmt.Sprintf("[%s]@email", user.Email)
   344  		},
   345  		sbsVerify: func(user *kbtest.FakeUser, g *libkb.GlobalContext) {
   346  			email := keybase1.EmailAddress(user.Email)
   347  			err := kbtest.VerifyEmailAuto(libkb.NewMetaContextTODO(g), email)
   348  			require.NoError(t, err)
   349  			err = emails.SetVisibilityEmail(libkb.NewMetaContextTODO(g), email, keybase1.IdentityVisibility_PUBLIC)
   350  			require.NoError(t, err)
   351  		},
   352  		sbsRevoke: func(user *kbtest.FakeUser, g *libkb.GlobalContext) {
   353  			email := keybase1.EmailAddress(user.Email)
   354  			err := emails.SetVisibilityEmail(libkb.NewMetaContextTODO(g), email, keybase1.IdentityVisibility_PRIVATE)
   355  			require.NoError(t, err)
   356  		},
   357  	})
   358  }