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 }