Skip to content

Commit

Permalink
Handle missed GroupChanges using group history
Browse files Browse the repository at this point in the history
  • Loading branch information
maltee1 committed Mar 26, 2024
1 parent 00f58da commit 3d5c45f
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 11 deletions.
110 changes: 102 additions & 8 deletions pkg/signalmeow/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ type BannedMember struct {
type GroupChange struct {
groupMasterKey types.SerializedGroupMasterKey

SourceServiceId uuid.UUID
Revision uint32
AddMembers []*AddMember
DeleteMembers []*uuid.UUID
Expand Down Expand Up @@ -315,6 +316,11 @@ func (groupChange *GroupChange) GetAvatarPath() *string {
return groupChange.ModifyAvatar
}

type GroupChangeState struct {
GroupState *Group
GroupChange *GroupChange
}

type GroupAuth struct {
Username string
Password string
Expand Down Expand Up @@ -816,21 +822,26 @@ type GroupCache struct {
func (cli *Client) DecryptGroupChange(ctx context.Context, groupContext *signalpb.GroupContextV2) (*GroupChange, error) {
masterKeyBytes := libsignalgo.GroupMasterKey(groupContext.MasterKey)
groupMasterKey := masterKeyFromBytes(masterKeyBytes)
log := zerolog.Ctx(ctx).With().Str("action", "decrypt group change").Logger()

encryptedGroupChange := &signalpb.GroupChange{}

groupChangeBytes := groupContext.GroupChange
encryptedGroupChange := &signalpb.GroupChange{}
err := proto.Unmarshal(groupChangeBytes, encryptedGroupChange)
if err != nil {
return nil, fmt.Errorf("Error unmarshalling group change: %w", err)
}
return cli.decryptGroupChange(ctx, encryptedGroupChange, groupMasterKey, true)
}
func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange *signalpb.GroupChange, groupMasterKey types.SerializedGroupMasterKey, verifySignature bool) (*GroupChange, error) {
log := zerolog.Ctx(ctx).With().Str("action", "decrypt group change").Logger()
serverSignature := encryptedGroupChange.ServerSignature
encryptedActionsBytes := encryptedGroupChange.Actions

err = libsignalgo.ServerPublicParamsVerifySignature(prodServerPublicParams, encryptedActionsBytes, libsignalgo.NotarySignature(serverSignature))
if err != nil {
return nil, fmt.Errorf("Failed to verify Server Signature: %w", err)
var err error
if verifySignature {
err = libsignalgo.ServerPublicParamsVerifySignature(prodServerPublicParams, encryptedActionsBytes, libsignalgo.NotarySignature(serverSignature))
if err != nil {
return nil, fmt.Errorf("Failed to verify Server Signature: %w", err)
}
}

encryptedActions := signalpb.GroupChange_Actions{}
Expand All @@ -846,9 +857,16 @@ func (cli *Client) DecryptGroupChange(ctx context.Context, groupContext *signalp
return nil, err
}

sourceServiceId, err := groupSecretParams.DecryptUUID(libsignalgo.UUIDCiphertext(encryptedActions.SourceServiceId))
if err != nil {
log.Err(err).Msg("Couldn't decrypt source serviceID")
return nil, err
}

decryptedGroupChange := &GroupChange{
groupMasterKey: groupMasterKey,
Revision: encryptedActions.Revision,
groupMasterKey: groupMasterKey,
Revision: encryptedActions.Revision,
SourceServiceId: sourceServiceId,
}

if encryptedActions.ModifyTitle != nil {
Expand Down Expand Up @@ -1708,3 +1726,79 @@ func GenerateInviteLinkPassword() types.SerializedInviteLinkPassword {
rand.Read(inviteLinkPasswordBytes)
return InviteLinkPasswordFromBytes(inviteLinkPasswordBytes)
}

func (cli *Client) GetGroupHistoryPage(ctx context.Context, gid types.GroupIdentifier, fromRevision uint32, includeFirstState bool) ([]*GroupChangeState, error) {
log := zerolog.Ctx(ctx).With().Str("action", "GetGroupHistoryPage").Logger()
groupMasterKey, err := cli.Store.GroupStore.MasterKeyFromGroupIdentifier(ctx, gid)
if err != nil {
log.Err(err).Msg("Failed to get group master key")
return nil, err
}
if groupMasterKey == "" {
return nil, fmt.Errorf("No group master key found for group identifier %s", gid)
}
masterKeyBytes := masterKeyToBytes(groupMasterKey)
groupAuth, err := cli.GetAuthorizationForToday(ctx, masterKeyBytes)
if err != nil {
return nil, err
}
opts := &web.HTTPReqOpt{
Username: &groupAuth.Username,
Password: &groupAuth.Password,
ContentType: web.ContentTypeProtobuf,
Host: web.StorageHostname,
}
// highest known epoch seems to always be 5, but that may change in the future. includeLastState is always false
path := fmt.Sprintf("/v1/groups/logs/%d?maxSupportedChangeEpoch=%d&includeFirstState=%t&includeLastState=false", fromRevision, 5, includeFirstState)
response, err := web.SendHTTPRequest(ctx, http.MethodGet, path, opts)
if err != nil {
return nil, err
}
if response.StatusCode != 200 {
return nil, fmt.Errorf("fetchGroupByID SendHTTPRequest bad status: %d", response.StatusCode)
}
var encryptedGroupChanges signalpb.GroupChanges
groupChangesBytes, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
err = proto.Unmarshal(groupChangesBytes, &encryptedGroupChanges)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal group: %w", err)
}

groupChanges, err := cli.decryptGroupChanges(ctx, &encryptedGroupChanges, groupMasterKey)
if err != nil {
return nil, fmt.Errorf("failed to decrypt group: %w", err)
}
return groupChanges, nil
}

func (cli *Client) decryptGroupChanges(ctx context.Context, encryptedGroupChanges *signalpb.GroupChanges, groupMasterKey types.SerializedGroupMasterKey) ([]*GroupChangeState, error) {
log := zerolog.Ctx(ctx).With().Str("action", "decryptGroupChanges").Logger()
var groupChanges []*GroupChangeState
for _, groupChangeState := range encryptedGroupChanges.GroupChanges {
var group *Group
var err error
if groupChangeState.GroupState != nil {
group, err = decryptGroup(ctx, groupChangeState.GroupState, groupMasterKey)
if err != nil {
log.Err(err).Msg("Failed to decrypt Group")
return nil, err
}
}
var groupChange *GroupChange
if groupChangeState.GroupChange != nil {
groupChange, err = cli.decryptGroupChange(ctx, groupChangeState.GroupChange, groupMasterKey, false)
if err != nil {
log.Err(err).Msg("Failed to decrypt GroupChange")
return nil, err
}
}
groupChanges = append(groupChanges, &GroupChangeState{
GroupState: group,
GroupChange: groupChange,
})
}
return groupChanges, nil
}
39 changes: 36 additions & 3 deletions portal.go
Original file line number Diff line number Diff line change
Expand Up @@ -893,9 +893,14 @@ func (portal *Portal) handleSignalDataMessage(source *User, sender *Puppet, msg
// Always update sender info when we receive a message from them, there's caching inside the function
sender.UpdateInfo(genericCtx, source)
// Handle earlier missed group changes here.
// If this message is a group change, don't handle it here, it's handled below.
if msg.GetGroupV2().GetGroupChange() == nil && portal.Revision < msg.GetGroupV2().GetRevision() {
portal.UpdateInfo(genericCtx, source, nil, msg.GetGroupV2().GetRevision())
if msg.GetGroupV2() != nil {
requiredRevision := msg.GetGroupV2().GetRevision()
if msg.GetGroupV2().GetGroupChange() != nil {
requiredRevision = requiredRevision - 1
}
if portal.Revision < requiredRevision {
portal.catchUpHistory(source, portal.Revision+1, requiredRevision, msg.GetTimestamp())
}
} else if portal.IsPrivateChat() && portal.UserID().UUID == portal.Receiver && portal.Name != NoteToSelfName {
// Slightly hacky way to make note to self names backfill
portal.UpdateDMInfo(genericCtx, false)
Expand All @@ -921,6 +926,28 @@ func (portal *Portal) handleSignalDataMessage(source *User, sender *Puppet, msg
}
}

func (portal *Portal) catchUpHistory(source *User, fromRevision uint32, toRevision uint32, ts uint64) {
log := portal.log.With().
Str("action", "catchUpHistory").
Stringer("source", source.MXID).
Uint32("from revision", fromRevision).
Uint32("to revision", toRevision).
Logger()
ctx := log.WithContext(context.TODO())
groupChanges, err := source.Client.GetGroupHistoryPage(ctx, portal.GroupID(), fromRevision, false)
if err != nil {
log.Err(err).Msg("Failed to get GroupChanges")
}
for _, groupChangeState := range groupChanges {
sender := portal.bridge.GetPuppetBySignalID(groupChangeState.GroupChange.SourceServiceId)
portal.applySignalGroupChange(ctx, source, sender, groupChangeState.GroupChange, ts)
// for revision > toRevision, we should have received a group change already
if groupChangeState.GroupChange.Revision == toRevision {
break
}
}
}

func (portal *Portal) handleSignalGroupChange(source *User, sender *Puppet, groupMeta *signalpb.GroupContextV2, ts uint64) {
log := portal.log.With().
Str("action", "handle signal group change").
Expand All @@ -934,6 +961,11 @@ func (portal *Portal) handleSignalGroupChange(source *User, sender *Puppet, grou
log.Err(err).Msg("Handling GroupChange failed")
return
}
portal.applySignalGroupChange(ctx, source, sender, groupChange, ts)
}

func (portal *Portal) applySignalGroupChange(ctx context.Context, source *User, sender *Puppet, groupChange *signalmeow.GroupChange, ts uint64) {
log := zerolog.Ctx(ctx)
if groupChange.Revision <= portal.Revision {
return
}
Expand All @@ -952,6 +984,7 @@ func (portal *Portal) handleSignalGroupChange(source *User, sender *Puppet, grou
}
intent := sender.IntentFor(portal)
modifyRoles := groupChange.ModifyMemberRoles
var err error
for _, deleteBannedMember := range groupChange.DeleteBannedMembers {
_, err := portal.sendMembershipForPuppetAndUser(ctx, sender, *deleteBannedMember, event.MembershipLeave, "unbanned")
if err != nil {
Expand Down

0 comments on commit 3d5c45f

Please # to comment.