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

View File

@ -3334,3 +3334,17 @@ func (m *Manager) SetCommunityActiveMembersCount(communityID string, activeMembe
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"
"time"
"github.com/golang/protobuf/proto"
"github.com/google/uuid"
"github.com/stretchr/testify/suite"
"go.uber.org/zap"
@ -28,7 +27,6 @@ import (
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/communities"
"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/protobuf"
"github.com/status-im/status-go/protocol/requests"
@ -640,7 +638,6 @@ func (s *MessengerCommunitiesSuite) TestPostToCommunityChat() {
Name: "status",
Color: "#ffffff",
Description: "status community description",
Encrypted: true,
}
// Create an community chat
@ -2578,7 +2575,6 @@ func (s *MessengerCommunitiesSuite) TestSyncCommunity() {
createCommunityReq := &requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_ON_REQUEST,
Name: "new community",
Encrypted: true,
Color: "#000000",
Description: "new community description",
}
@ -2593,15 +2589,6 @@ func (s *MessengerCommunitiesSuite) TestSyncCommunity() {
}
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
cs, err := s.alice.communitiesManager.All()
s.Require().NoError(err, "communitiesManager.All")
@ -2629,15 +2616,6 @@ func (s *MessengerCommunitiesSuite) TestSyncCommunity() {
s.Require().NoError(err)
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))
// 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
}
// 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) {
// 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
// at any given time there is a single key used)
func (s *sqlitePersistence) GetCurrentKeyForGroup(groupID []byte) (uint32, error) {
@ -845,7 +845,7 @@ func (s *sqlitePersistence) GetKeyIDsForGroup(groupID []byte) ([]uint32, error)
return keyIDs, nil
}
// SaveHashRachetKeyHash saves a hash ratchet key cache data
// SaveHashRatchetKeyHash saves a hash ratchet key cache data
func (s *sqlitePersistence) SaveHashRatchetKeyHash(
groupID []byte,
keyID uint32,

View File

@ -4,6 +4,7 @@ import (
"context"
"crypto/ecdsa"
"encoding/json"
"errors"
_errors "errors"
"fmt"
"os"
@ -1238,15 +1239,6 @@ func (m *Messenger) CreateCommunity(request *requests.CreateCommunity, createDef
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.AddCommunitySettings(&communitySettings)
err = m.syncCommunity(context.Background(), community, m.dispatchMessage)
@ -1271,8 +1263,16 @@ func (m *Messenger) CreateCommunityTokenPermission(request *requests.CreateCommu
return nil, err
}
response := &MessengerResponse{}
response.AddCommunity(community)
response, err := m.UpdateCommunityEncryption(community)
if err != nil {
return nil, err
}
if response == nil {
response = &MessengerResponse{}
response.AddCommunity(community)
}
response.CommunityChanges = []*communities.CommunityChanges{changes}
return response, nil
@ -1288,8 +1288,16 @@ func (m *Messenger) EditCommunityTokenPermission(request *requests.EditCommunity
return nil, err
}
response := &MessengerResponse{}
response.AddCommunity(community)
response, err := m.UpdateCommunityEncryption(community)
if err != nil {
return nil, err
}
if response == nil {
response = &MessengerResponse{}
response.AddCommunity(community)
}
response.CommunityChanges = []*communities.CommunityChanges{changes}
return response, nil
@ -1305,8 +1313,16 @@ func (m *Messenger) DeleteCommunityTokenPermission(request *requests.DeleteCommu
return nil, err
}
response := &MessengerResponse{}
response.AddCommunity(community)
response, err := m.UpdateCommunityEncryption(community)
if err != nil {
return nil, err
}
if response == nil {
response = &MessengerResponse{}
response.AddCommunity(community)
}
response.CommunityChanges = []*communities.CommunityChanges{changes}
return response, nil
}
@ -2624,19 +2640,6 @@ func (m *Messenger) RequestImportDiscordCommunity(request *requests.ImportDiscor
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()
// 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 {
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"`
HistoryArchiveSupportEnabled bool `json:"historyArchiveSupportEnabled,omitempty"`
PinMessageAllMembersEnabled bool `json:"pinMessageAllMembersEnabled,omitempty"`
Encrypted bool `json:"encrypted,omitempty"`
Tags []string `json:"tags,omitempty"`
}
@ -129,7 +128,7 @@ func (c *CreateCommunity) ToCommunityDescription() (*protobuf.CommunityDescripti
},
IntroMessage: c.IntroMessage,
OutroMessage: c.OutroMessage,
Encrypted: c.Encrypted,
Encrypted: false,
Tags: c.Tags,
}
return description, nil