Community encryption should automatically be enabled/disabled depending on community permissions (closed/open)

This commit is contained in:
mprakhov 2023-05-05 00:17:54 +02:00 committed by Mykhailo Prakhov
parent 44ded4dd64
commit 57b2432290
7 changed files with 101 additions and 57 deletions

View File

@ -969,8 +969,8 @@ func (o *Community) Encrypted() bool {
return o.config.CommunityDescription.Encrypted return o.config.CommunityDescription.Encrypted
} }
func (o *Community) Encrypt() { func (o *Community) SetEncrypted(encrypted bool) {
o.config.CommunityDescription.Encrypted = true o.config.CommunityDescription.Encrypted = encrypted
} }
func (o *Community) Joined() bool { func (o *Community) Joined() bool {
@ -1468,6 +1468,7 @@ func (o *Community) AddTokenPermission(permission *protobuf.CommunityTokenPermis
} }
o.config.CommunityDescription.TokenPermissions[permission.Id] = permission o.config.CommunityDescription.TokenPermissions[permission.Id] = permission
o.increaseClock() o.increaseClock()
changes := o.emptyCommunityChanges() changes := o.emptyCommunityChanges()

View File

@ -3334,3 +3334,17 @@ func (m *Manager) SetCommunityActiveMembersCount(communityID string, activeMembe
return nil return nil
} }
// UpdateCommunity takes a Community persists it and republishes it.
// The clock is incremented meaning even a no change update will be republished by the admin, and parsed by the member.
func (m *Manager) UpdateCommunity(c *Community) error {
c.increaseClock()
err := m.persistence.SaveCommunity(c)
if err != nil {
return err
}
m.publish(&Subscription{Community: c})
return nil
}

View File

@ -13,7 +13,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/golang/protobuf/proto"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"go.uber.org/zap" "go.uber.org/zap"
@ -28,7 +27,6 @@ import (
"github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/communities" "github.com/status-im/status-go/protocol/communities"
"github.com/status-im/status-go/protocol/discord" "github.com/status-im/status-go/protocol/discord"
"github.com/status-im/status-go/protocol/encryption"
"github.com/status-im/status-go/protocol/encryption/multidevice" "github.com/status-im/status-go/protocol/encryption/multidevice"
"github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/protocol/requests" "github.com/status-im/status-go/protocol/requests"
@ -640,7 +638,6 @@ func (s *MessengerCommunitiesSuite) TestPostToCommunityChat() {
Name: "status", Name: "status",
Color: "#ffffff", Color: "#ffffff",
Description: "status community description", Description: "status community description",
Encrypted: true,
} }
// Create an community chat // Create an community chat
@ -2578,7 +2575,6 @@ func (s *MessengerCommunitiesSuite) TestSyncCommunity() {
createCommunityReq := &requests.CreateCommunity{ createCommunityReq := &requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_ON_REQUEST, Membership: protobuf.CommunityPermissions_ON_REQUEST,
Name: "new community", Name: "new community",
Encrypted: true,
Color: "#000000", Color: "#000000",
Description: "new community description", Description: "new community description",
} }
@ -2593,15 +2589,6 @@ func (s *MessengerCommunitiesSuite) TestSyncCommunity() {
} }
s.Require().NotNil(newCommunity) s.Require().NotNil(newCommunity)
// Check HR keys are created
encodedKeys, err := s.alice.encryptor.GetAllHREncodedKeys(newCommunity.ID())
s.Require().NoError(err)
s.Require().NotNil(encodedKeys)
keys := &encryption.HRKeys{}
s.Require().NoError(proto.Unmarshal(encodedKeys, keys))
s.Require().Len(keys.Keys, 1)
// Check that Alice has 2 communities // Check that Alice has 2 communities
cs, err := s.alice.communitiesManager.All() cs, err := s.alice.communitiesManager.All()
s.Require().NoError(err, "communitiesManager.All") s.Require().NoError(err, "communitiesManager.All")
@ -2629,15 +2616,6 @@ func (s *MessengerCommunitiesSuite) TestSyncCommunity() {
s.Require().NoError(err) s.Require().NoError(err)
s.Len(tcs, 2, "There must be 2 communities") s.Len(tcs, 2, "There must be 2 communities")
// Check HR keys are synced
encodedKeys, err = alicesOtherDevice.encryptor.GetAllHREncodedKeys(newCommunity.ID())
s.Require().NoError(err)
s.Require().NotNil(encodedKeys)
keys = &encryption.HRKeys{}
s.Require().NoError(proto.Unmarshal(encodedKeys, keys))
s.Require().Len(keys.Keys, 1)
s.logger.Debug("", zap.Any("tcs", tcs)) s.logger.Debug("", zap.Any("tcs", tcs))
// Get the new community from their db // Get the new community from their db

View File

@ -623,7 +623,7 @@ func (s *encryptor) getNextHashRatchetKeyID(groupID []byte) (uint32, error) {
return latestKeyID + 1, nil return latestKeyID + 1, nil
} }
// Generates and stores a hash ratchet key given a group ID // GenerateHashRatchetKey Generates and stores a hash ratchet key given a group ID
func (s *encryptor) GenerateHashRatchetKey(groupID []byte) (uint32, error) { func (s *encryptor) GenerateHashRatchetKey(groupID []byte) (uint32, error) {
// Randomly generate a hash ratchet key // Randomly generate a hash ratchet key

View File

@ -790,7 +790,7 @@ func (s *sqlitePersistence) GetHashRatchetKeyByID(groupID []byte, keyID uint32,
} }
} }
// GetCurrentKeyIDForGroup retrieves a key ID for given group ID // GetCurrentKeyForGroup retrieves a key ID for given group ID
// (with an assumption that key ids are shared in the group, and // (with an assumption that key ids are shared in the group, and
// at any given time there is a single key used) // at any given time there is a single key used)
func (s *sqlitePersistence) GetCurrentKeyForGroup(groupID []byte) (uint32, error) { func (s *sqlitePersistence) GetCurrentKeyForGroup(groupID []byte) (uint32, error) {
@ -845,7 +845,7 @@ func (s *sqlitePersistence) GetKeyIDsForGroup(groupID []byte) ([]uint32, error)
return keyIDs, nil return keyIDs, nil
} }
// SaveHashRachetKeyHash saves a hash ratchet key cache data // SaveHashRatchetKeyHash saves a hash ratchet key cache data
func (s *sqlitePersistence) SaveHashRatchetKeyHash( func (s *sqlitePersistence) SaveHashRatchetKeyHash(
groupID []byte, groupID []byte,
keyID uint32, keyID uint32,

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"encoding/json" "encoding/json"
"errors"
_errors "errors" _errors "errors"
"fmt" "fmt"
"os" "os"
@ -1238,15 +1239,6 @@ func (m *Messenger) CreateCommunity(request *requests.CreateCommunity, createDef
response.AddChat(chatResponse.Chats()[0]) response.AddChat(chatResponse.Chats()[0])
} }
if request.Encrypted {
// Init hash ratchet for community
_, err = m.encryptor.GenerateHashRatchetKey(community.ID())
if err != nil {
return nil, err
}
}
response.AddCommunity(community) response.AddCommunity(community)
response.AddCommunitySettings(&communitySettings) response.AddCommunitySettings(&communitySettings)
err = m.syncCommunity(context.Background(), community, m.dispatchMessage) err = m.syncCommunity(context.Background(), community, m.dispatchMessage)
@ -1271,8 +1263,16 @@ func (m *Messenger) CreateCommunityTokenPermission(request *requests.CreateCommu
return nil, err return nil, err
} }
response := &MessengerResponse{} response, err := m.UpdateCommunityEncryption(community)
response.AddCommunity(community) if err != nil {
return nil, err
}
if response == nil {
response = &MessengerResponse{}
response.AddCommunity(community)
}
response.CommunityChanges = []*communities.CommunityChanges{changes} response.CommunityChanges = []*communities.CommunityChanges{changes}
return response, nil return response, nil
@ -1288,8 +1288,16 @@ func (m *Messenger) EditCommunityTokenPermission(request *requests.EditCommunity
return nil, err return nil, err
} }
response := &MessengerResponse{} response, err := m.UpdateCommunityEncryption(community)
response.AddCommunity(community) if err != nil {
return nil, err
}
if response == nil {
response = &MessengerResponse{}
response.AddCommunity(community)
}
response.CommunityChanges = []*communities.CommunityChanges{changes} response.CommunityChanges = []*communities.CommunityChanges{changes}
return response, nil return response, nil
@ -1305,8 +1313,16 @@ func (m *Messenger) DeleteCommunityTokenPermission(request *requests.DeleteCommu
return nil, err return nil, err
} }
response := &MessengerResponse{} response, err := m.UpdateCommunityEncryption(community)
response.AddCommunity(community) if err != nil {
return nil, err
}
if response == nil {
response = &MessengerResponse{}
response.AddCommunity(community)
}
response.CommunityChanges = []*communities.CommunityChanges{changes} response.CommunityChanges = []*communities.CommunityChanges{changes}
return response, nil return response, nil
} }
@ -2624,19 +2640,6 @@ func (m *Messenger) RequestImportDiscordCommunity(request *requests.ImportDiscor
return return
} }
if createCommunityRequest.Encrypted {
// Init hash ratchet for community
_, err = m.encryptor.GenerateHashRatchetKey(discordCommunity.ID())
if err != nil {
m.cleanUpImport(discordCommunity.IDString())
importProgress.AddTaskError(discord.CommunityCreationTask, discord.Error(err.Error()))
importProgress.StopTask(discord.CommunityCreationTask)
progressUpdates <- importProgress
return
}
}
communityID := discordCommunity.IDString() communityID := discordCommunity.IDString()
// marking import as not cancelled // marking import as not cancelled
@ -3536,3 +3539,52 @@ func (m *Messenger) AddCommunityToken(token *communities.CommunityToken) (*commu
func (m *Messenger) UpdateCommunityTokenState(contractAddress string, deployState communities.DeployState) error { func (m *Messenger) UpdateCommunityTokenState(contractAddress string, deployState communities.DeployState) error {
return m.communitiesManager.UpdateCommunityTokenState(contractAddress, deployState) return m.communitiesManager.UpdateCommunityTokenState(contractAddress, deployState)
} }
// UpdateCommunityEncryption takes a community and encrypts / decrypts the community
// based on TokenPermission. Community is republished along with any keys if needed.
//
// Note: This function cannot decrypt previously encrypted messages, and it cannot encrypt previous unencrypted messages.
// This functionality introduces some race conditions:
// - community description is processed by members before the receiving the key exchange messages
// - members maybe sending encrypted messages after the community description is updated and a new member joins
func (m *Messenger) UpdateCommunityEncryption(community *communities.Community) (*MessengerResponse, error) {
if community == nil {
return nil, errors.New("community is nil")
}
becomeMemberPermissions := community.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_MEMBER)
isEncrypted := len(becomeMemberPermissions) > 0
// Check isEncrypted is different to Community's value
// If not different return
if community.Encrypted() == isEncrypted {
return nil, nil
}
if isEncrypted {
// 🪄 The magic that encrypts a community
_, err := m.encryptor.GenerateHashRatchetKey(community.ID())
if err != nil {
return nil, err
}
err = m.SendKeyExchangeMessage(community.ID(), community.GetMemberPubkeys(), common.KeyExMsgReuse)
if err != nil {
return nil, err
}
}
// 🧙 There is no magic that decrypts a community, we just need to tell everyone to not use encryption
// Republish the community.
community.SetEncrypted(isEncrypted)
err := m.communitiesManager.UpdateCommunity(community)
if err != nil {
return nil, err
}
response := &MessengerResponse{}
response.AddCommunity(community)
return response, nil
}

View File

@ -42,7 +42,6 @@ type CreateCommunity struct {
Banner images.CroppedImage `json:"banner"` Banner images.CroppedImage `json:"banner"`
HistoryArchiveSupportEnabled bool `json:"historyArchiveSupportEnabled,omitempty"` HistoryArchiveSupportEnabled bool `json:"historyArchiveSupportEnabled,omitempty"`
PinMessageAllMembersEnabled bool `json:"pinMessageAllMembersEnabled,omitempty"` PinMessageAllMembersEnabled bool `json:"pinMessageAllMembersEnabled,omitempty"`
Encrypted bool `json:"encrypted,omitempty"`
Tags []string `json:"tags,omitempty"` Tags []string `json:"tags,omitempty"`
} }
@ -129,7 +128,7 @@ func (c *CreateCommunity) ToCommunityDescription() (*protobuf.CommunityDescripti
}, },
IntroMessage: c.IntroMessage, IntroMessage: c.IntroMessage,
OutroMessage: c.OutroMessage, OutroMessage: c.OutroMessage,
Encrypted: c.Encrypted, Encrypted: false,
Tags: c.Tags, Tags: c.Tags,
} }
return description, nil return description, nil