fix(communities)_: prevent unsigned `CommunityDescription` persistence

fixes: status-im/status-mobile#21303
This commit is contained in:
Patryk Osmaczko 2024-09-25 20:47:06 +02:00 committed by osmaczko
parent 9ba44651c7
commit 9294ffc12b
5 changed files with 57 additions and 68 deletions

View File

@ -3491,6 +3491,17 @@ type CheckAllChannelsPermissionsResponse struct {
}
func (m *Manager) HandleCommunityRequestToJoinResponse(signer *ecdsa.PublicKey, request *protobuf.CommunityRequestToJoinResponse) (*RequestToJoin, error) {
if len(request.CommunityDescriptionProtocolMessage) > 0 {
description, err := unmarshalCommunityDescriptionMessage(request.CommunityDescriptionProtocolMessage, signer)
if err != nil {
return nil, err
}
_, err = m.HandleCommunityDescriptionMessage(signer, description, request.CommunityDescriptionProtocolMessage, nil, nil)
if err != nil {
return nil, err
}
}
m.communityLock.Lock(request.CommunityId)
defer m.communityLock.Unlock(request.CommunityId)
@ -3501,46 +3512,6 @@ func (m *Manager) HandleCommunityRequestToJoinResponse(signer *ecdsa.PublicKey,
return nil, err
}
communityDescriptionBytes, err := proto.Marshal(request.Community)
if err != nil {
return nil, err
}
// We need to wrap `request.Community` in an `ApplicationMetadataMessage`
// of type `CommunityDescription` because `UpdateCommunityDescription` expects this.
//
// This is merely for marsheling/unmarsheling, hence we attaching a `Signature`
// is not needed.
metadataMessage := &protobuf.ApplicationMetadataMessage{
Payload: communityDescriptionBytes,
Type: protobuf.ApplicationMetadataMessage_COMMUNITY_DESCRIPTION,
}
appMetadataMsg, err := proto.Marshal(metadataMessage)
if err != nil {
return nil, err
}
isControlNodeSigner := common.IsPubKeyEqual(community.ControlNode(), signer)
if !isControlNodeSigner {
m.logger.Debug("signer is not control node", zap.String("signer", common.PubkeyToHex(signer)), zap.String("controlNode", common.PubkeyToHex(community.ControlNode())))
return nil, ErrNotAuthorized
}
_, processedDescription, err := m.preprocessDescription(community.ID(), request.Community)
if err != nil {
return nil, err
}
_, err = community.UpdateCommunityDescription(processedDescription, appMetadataMsg, nil)
if err != nil {
return nil, err
}
if err = m.handleCommunityTokensMetadata(community); err != nil {
return nil, err
}
if community.Encrypted() && len(request.Grant) > 0 {
_, err = m.HandleCommunityGrant(community, request.Grant, request.Clock)
if err != nil && err != ErrGrantOlder && err != ErrGrantExpired {
@ -3548,19 +3519,12 @@ func (m *Manager) HandleCommunityRequestToJoinResponse(signer *ecdsa.PublicKey,
}
}
err = m.persistence.SaveCommunity(community)
if err != nil {
return nil, err
}
if request.Accepted {
err = m.markRequestToJoinAsAccepted(&m.identity.PublicKey, community)
if err != nil {
return nil, err
}
} else {
err = m.persistence.SetRequestToJoinState(pkString, community.ID(), RequestToJoinStateDeclined)
if err != nil {
return nil, err

View File

@ -2917,6 +2917,26 @@ func (s *MessengerCommunitiesSuite) TestSyncCommunity_RequestToJoin() {
s.Equal(aRtj.CustomizationColor, bobRtj.CustomizationColor)
}
func (s *MessengerCommunitiesSuite) TestSyncCommunity_Join() {
community, _ := s.createCommunity()
s.advertiseCommunityTo(community, s.owner, s.alice)
alicesOtherDevice := s.createOtherDevice(s.alice)
defer TearDownMessenger(&s.Suite, alicesOtherDevice)
PairDevices(&s.Suite, alicesOtherDevice, s.alice)
s.joinCommunity(community, s.owner, s.alice)
_, err := WaitOnMessengerResponse(alicesOtherDevice, func(response *MessengerResponse) bool {
if len(response.Communities()) != 1 {
return false
}
c := response.Communities()[0]
return c.IDString() == community.IDString() && c.Joined()
}, "community not synced")
s.Require().NoError(err)
}
func (s *MessengerCommunitiesSuite) TestSyncCommunity_Leave() {
// Set Alice's installation metadata
aim := &multidevice.InstallationMetadata{
@ -4661,17 +4681,17 @@ func (s *MessengerCommunitiesSuite) TestAliceDidNotProcessOutdatedCommunityReque
s.Require().NoError(err)
}
encryptedDescription, err := community.EncryptedDescription()
descriptionMessage, err := community.ToProtocolMessageBytes()
s.Require().NoError(err)
requestToJoinResponse := &protobuf.CommunityRequestToJoinResponse{
Clock: community.Clock(),
Accepted: true,
CommunityId: community.ID(),
Community: encryptedDescription,
Grant: grant,
ProtectedTopicPrivateKey: crypto.FromECDSA(key),
Shard: community.Shard().Protobuffer(),
Clock: community.Clock(),
Accepted: true,
CommunityId: community.ID(),
Grant: grant,
ProtectedTopicPrivateKey: crypto.FromECDSA(key),
Shard: community.Shard().Protobuffer(),
CommunityDescriptionProtocolMessage: descriptionMessage,
}
// alice handle duplicated request to join response

View File

@ -1989,14 +1989,20 @@ func (m *Messenger) acceptRequestToJoinCommunity(requestToJoin *communities.Requ
return nil, err
}
descriptionMessage, err := community.ToProtocolMessageBytes()
if err != nil {
return nil, err
}
requestToJoinResponseProto := &protobuf.CommunityRequestToJoinResponse{
Clock: community.Clock(),
Accepted: true,
CommunityId: community.ID(),
Community: encryptedDescription,
Grant: grant,
ProtectedTopicPrivateKey: crypto.FromECDSA(key),
Shard: community.Shard().Protobuffer(),
Clock: community.Clock(),
Accepted: true,
CommunityId: community.ID(),
Community: encryptedDescription, // Deprecated but kept for backward compatibility, to be removed in future
Grant: grant,
ProtectedTopicPrivateKey: crypto.FromECDSA(key),
Shard: community.Shard().Protobuffer(),
CommunityDescriptionProtocolMessage: descriptionMessage,
}
// The purpose of this torrent code is to get the 'magnetlink' to populate 'requestToJoinResponseProto.MagnetUri'
@ -3807,7 +3813,7 @@ func (m *Messenger) handleSyncInstallationCommunity(messageState *ReceivedMessag
// This is good to do so that we don't have to queue all the actions done after the handled community description.
// `signer` is `communityID` for a community with no owner token and `owner public key` otherwise
signer, err := utils.RecoverKey(&amm)
if err != nil {
if signer == nil || err != nil {
logger.Debug("failed to recover community description signer", zap.Error(err))
return err
}

View File

@ -1702,7 +1702,7 @@ func (m *Messenger) HandleCommunityRequestToJoinResponse(state *ReceivedMessageS
communityShardKey := &protobuf.CommunityShardKey{
CommunityId: requestToJoinResponseProto.CommunityId,
PrivateKey: requestToJoinResponseProto.ProtectedTopicPrivateKey,
Clock: requestToJoinResponseProto.Community.Clock,
Clock: community.Clock(),
Shard: requestToJoinResponseProto.Shard,
}
@ -1767,9 +1767,6 @@ func (m *Messenger) HandleCommunityRequestToJoinResponse(state *ReceivedMessageS
m.downloadAndImportHistoryArchives(community.ID(), magnetlink, task.CancelChan)
}(currentTask)
clock := requestToJoinResponseProto.Community.ArchiveMagnetlinkClock
return m.communitiesManager.UpdateMagnetlinkMessageClock(community.ID(), clock)
}
}

View File

@ -200,13 +200,15 @@ message CommunityUserKicked {
message CommunityRequestToJoinResponse {
uint64 clock = 1;
CommunityDescription community = 2;
CommunityDescription community = 2 [deprecated = true];
bool accepted = 3;
bytes grant = 4;
bytes community_id = 5;
string magnet_uri = 6;
bytes protected_topic_private_key = 7;
Shard shard = 8;
// CommunityDescription protocol message with owner signature
bytes community_description_protocol_message = 9;
}
message CommunityRequestToLeave {