Skip to content

Commit

Permalink
chore: enable community rekey loop
Browse files Browse the repository at this point in the history
  • Loading branch information
osmaczko committed Oct 27, 2023
1 parent a38b34a commit e304fe3
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 136 deletions.
4 changes: 4 additions & 0 deletions protocol/communities/community.go
Original file line number Diff line number Diff line change
Expand Up @@ -1547,6 +1547,10 @@ func (o *Community) HasTokenPermissions() bool {
return len(o.tokenPermissions()) > 0
}

func (o *Community) ChannelEncrypted(channelID string) bool {
return o.ChannelHasTokenPermissions(o.IDString() + channelID)
}

func (o *Community) ChannelHasTokenPermissions(chatID string) bool {
o.mutex.Lock()
defer o.mutex.Unlock()
Expand Down
3 changes: 2 additions & 1 deletion protocol/communities/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -3311,7 +3311,8 @@ func (m *Manager) IsChannelEncrypted(communityID string, chatID string) (bool, e
return false, err
}

return community.ChannelHasTokenPermissions(chatID), nil
channelID := strings.TrimPrefix(chatID, communityID)
return community.ChannelEncrypted(channelID), nil
}

func (m *Manager) ShouldHandleSyncCommunity(community *protobuf.SyncInstallationCommunity) (bool, error) {
Expand Down
37 changes: 1 addition & 36 deletions protocol/communities_key_distributor.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

type CommunitiesKeyDistributor interface {
Distribute(community *communities.Community, keyActions *communities.EncryptionKeyActions) error
Rekey(community *communities.Community) error
}

type CommunitiesKeyDistributorImpl struct {
Expand Down Expand Up @@ -41,32 +40,6 @@ func (ckd *CommunitiesKeyDistributorImpl) Distribute(community *communities.Comm
return nil
}

func (ckd *CommunitiesKeyDistributorImpl) Rekey(community *communities.Community) error {
if !community.IsControlNode() {
return communities.ErrNotControlNode
}

err := ckd.distributeKey(community, community.ID(), &communities.EncryptionKeyAction{
ActionType: communities.EncryptionKeyRekey,
Members: community.Members(),
})
if err != nil {
return err
}

for channelID, channel := range community.Chats() {
err := ckd.distributeKey(community, []byte(community.IDString()+channelID), &communities.EncryptionKeyAction{
ActionType: communities.EncryptionKeyRekey,
Members: channel.Members,
})
if err != nil {
return err
}
}

return nil
}

func (ckd *CommunitiesKeyDistributorImpl) distributeKey(community *communities.Community, hashRatchetGroupID []byte, keyAction *communities.EncryptionKeyAction) error {
pubkeys := make([]*ecdsa.PublicKey, len(keyAction.Members))
i := 0
Expand All @@ -77,15 +50,7 @@ func (ckd *CommunitiesKeyDistributorImpl) distributeKey(community *communities.C

switch keyAction.ActionType {
case communities.EncryptionKeyAdd:
_, err := ckd.encryptor.GenerateHashRatchetKey(hashRatchetGroupID)
if err != nil {
return err
}

err = ckd.sendKeyExchangeMessage(community, hashRatchetGroupID, pubkeys, common.KeyExMsgReuse)
if err != nil {
return err
}
fallthrough

case communities.EncryptionKeyRekey:
err := ckd.sendKeyExchangeMessage(community, hashRatchetGroupID, pubkeys, common.KeyExMsgRekey)
Expand Down
81 changes: 42 additions & 39 deletions protocol/communities_messenger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ func (s *MessengerCommunitiesSuite) SetupTest() {
s.bob = s.newMessenger()
s.alice = s.newMessenger()

enableRekeyLoop = true
s.owner.communitiesManager.RekeyInterval = 50 * time.Millisecond

_, err := s.owner.Start()
Expand Down Expand Up @@ -3352,28 +3351,12 @@ func (t *testPermissionChecker) CheckPermissions(permissions []*communities.Comm
}

func (s *MessengerCommunitiesSuite) TestStartCommunityRekeyLoop() {
// Create a new community
response, err := s.owner.CreateCommunity(
&requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
Name: "status",
Color: "#57a7e5",
Description: "status community description",
},
true,
)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)

// Check community is present in the DB and has default values we care about
c, err := s.owner.GetCommunityByID(response.Communities()[0].ID())
s.Require().NoError(err)
s.Require().False(c.Encrypted())
// TODO some check that there are no keys for the community. Alt for s.Require().Zero(c.RekeyedAt().Unix())
community, chat := s.createCommunity()
s.Require().False(community.Encrypted())

_, err = s.owner.CreateCommunityTokenPermission(&requests.CreateCommunityTokenPermission{
CommunityID: c.ID(),
// Add community permission
_, err := s.owner.CreateCommunityTokenPermission(&requests.CreateCommunityTokenPermission{
CommunityID: community.ID(),
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: []*protobuf.TokenCriteria{{
ContractAddresses: map[uint64]string{3: "0x933"},
Expand All @@ -3386,37 +3369,57 @@ func (s *MessengerCommunitiesSuite) TestStartCommunityRekeyLoop() {
})
s.Require().NoError(err)

c, err = s.owner.GetCommunityByID(c.ID())
// Add channel permission
response, err := s.owner.CreateCommunityTokenPermission(&requests.CreateCommunityTokenPermission{
CommunityID: community.ID(),
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
TokenCriteria: []*protobuf.TokenCriteria{
&protobuf.TokenCriteria{
ContractAddresses: map[uint64]string{3: "0x933"},
Type: protobuf.CommunityTokenType_ERC20,
Symbol: "STT",
Name: "Status Test Token",
Amount: "10",
Decimals: 18,
},
},
ChatIds: []string{chat.ID},
})
s.Require().NoError(err)
s.Require().True(c.Encrypted())

s.advertiseCommunityTo(c, s.owner, s.bob)
s.advertiseCommunityTo(c, s.owner, s.alice)
s.Require().Len(response.Communities(), 1)
community = response.Communities()[0]
s.Require().True(community.Encrypted())
s.Require().True(community.ChannelEncrypted(chat.CommunityChatID()))

s.owner.communitiesManager.PermissionChecker = &testPermissionChecker{}

s.joinCommunity(c, s.owner, s.bob)
s.joinCommunity(c, s.owner, s.alice)
s.advertiseCommunityTo(community, s.owner, s.bob)
s.advertiseCommunityTo(community, s.owner, s.alice)
s.joinCommunity(community, s.owner, s.bob)
s.joinCommunity(community, s.owner, s.alice)

// Check the Alice and Bob are members of the community
c, err = s.owner.GetCommunityByID(c.ID())
// Check keys in the database
communityKeys, err := s.owner.sender.GetKeysForGroup(community.ID())
s.Require().NoError(err)
s.Require().True(c.HasMember(&s.alice.identity.PublicKey))
s.Require().True(c.HasMember(&s.bob.identity.PublicKey))
communityKeyCount := len(communityKeys)

// Check the keys in the database
keys, err := s.owner.sender.GetKeysForGroup(c.ID())
channelKeys, err := s.owner.sender.GetKeysForGroup([]byte(chat.ID))
s.Require().NoError(err)
keyCount := len(keys)
channelKeyCount := len(channelKeys)

// Check that rekeying is occurring by counting the number of keyIDs in the encryptor's DB
// This test could be flaky, as the rekey function may not be finished before RekeyInterval * 2 has passed
for i := 0; i < 5; i++ {
time.Sleep(s.owner.communitiesManager.RekeyInterval * 2)
keys, err = s.owner.sender.GetKeysForGroup(c.ID())
communityKeys, err = s.owner.sender.GetKeysForGroup(community.ID())
s.Require().NoError(err)
s.Require().Greater(len(communityKeys), communityKeyCount)
communityKeyCount = len(communityKeys)

channelKeys, err = s.owner.sender.GetKeysForGroup([]byte(chat.ID))
s.Require().NoError(err)
s.Require().Greater(len(keys), keyCount)
keyCount = len(keys)
s.Require().Greater(len(channelKeys), channelKeyCount)
channelKeyCount = len(channelKeys)
}
}

Expand Down
108 changes: 48 additions & 60 deletions protocol/messenger_communities.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import (
"github.com/status-im/status-go/protocol/communities"
"github.com/status-im/status-go/protocol/communities/token"
"github.com/status-im/status-go/protocol/discord"
"github.com/status-im/status-go/protocol/encryption"
"github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/protocol/requests"
"github.com/status-im/status-go/protocol/transport"
Expand Down Expand Up @@ -511,6 +510,10 @@ func (m *Messenger) Communities() ([]*communities.Community, error) {
return m.communitiesManager.All()
}

func (m *Messenger) ControlledCommunities() ([]*communities.Community, error) {
return m.communitiesManager.Controlled()
}

func (m *Messenger) JoinedCommunities() ([]*communities.Community, error) {
return m.communitiesManager.Joined()
}
Expand Down Expand Up @@ -2302,9 +2305,6 @@ func (m *Messenger) ImportCommunity(ctx context.Context, key *ecdsa.PrivateKey)
return nil, err
}

// TODO Init hash ratchet for community
_, err = m.encryptor.GenerateHashRatchetKey(community.ID())

if err != nil {
return nil, err
}
Expand Down Expand Up @@ -5733,33 +5733,8 @@ func chunkAttachmentsByByteSize(slice []*protobuf.DiscordMessageAttachment, maxF
return chunks
}

// GetCurrentKeyForGroup returns the latest key timestampID belonging to a key group
func (m *Messenger) GetCurrentKeyForGroup(groupID []byte) (*encryption.HashRatchetKeyCompatibility, error) {
return m.sender.GetCurrentKeyForGroup(groupID)
}

// RekeyCommunity takes a communities.Community.config.ID and triggers a force rekey event for that community
func (m *Messenger) RekeyCommunity(cID types.HexBytes) error {
// Get the community as the member list could have changed
c, err := m.GetCommunityByID(cID)
if err != nil {
return err
}

// RekeyCommunity
return m.communitiesKeyDistributor.Rekey(c)
}

// NOTE: disabling rekey loop as it rekeys too aggressively

var enableRekeyLoop = false

// startCommunityRekeyLoop creates a 5-minute ticker and starts a routine that attempts to rekey every community every tick
func (m *Messenger) startCommunityRekeyLoop() {
if !enableRekeyLoop {
return
}

logger := m.logger.Named("CommunityRekeyLoop")
var d time.Duration
if m.communitiesManager.RekeyInterval != 0 {
Expand All @@ -5777,7 +5752,7 @@ func (m *Messenger) startCommunityRekeyLoop() {
for {
select {
case <-ticker.C:
m.rekeyAllCommunities(logger)
m.rekeyCommunities(logger)
case <-m.quit:
ticker.Stop()
logger.Debug("CommunityRekeyLoop stopped")
Expand All @@ -5787,47 +5762,60 @@ func (m *Messenger) startCommunityRekeyLoop() {
}()
}

// rekeyAllCommunities attempts to rekey every community in persistence.
// A community will be rekeyed if it meets all the following criteria:
// - Community.IsAdmin()
// - Community.Encrypted()
// - Community.RekeyedAt().Add(rki).Before(time.Now()) where rki is a defined rekey interval
func (m *Messenger) rekeyAllCommunities(logger *zap.Logger) {
// Determine the rekey interval, if the value is not set as a property of m.communitiesManager
// default to one hour
// rekeyCommunities loops over controlled communities and rekeys if rekey interval elapsed
func (m *Messenger) rekeyCommunities(logger *zap.Logger) {
// TODO in future have a community level rki rather than a global rki
/*
var rki time.Duration
if m.communitiesManager.RekeyInterval == 0 {
rki = time.Hour
} else {
rki = m.communitiesManager.RekeyInterval
}*/
var rekeyInterval time.Duration
if m.communitiesManager.RekeyInterval == 0 {
rekeyInterval = 48 * time.Hour
} else {
rekeyInterval = m.communitiesManager.RekeyInterval
}

// Get and loop over all communities in persistence
cs, err := m.Communities()
shouldRekey := func(hashRatchetGroupID []byte) bool {
key, err := m.sender.GetCurrentKeyForGroup(hashRatchetGroupID)
if err != nil {
logger.Error("failed to get current hash ratchet key", zap.Error(err))
return false
}

keyDistributedAt := time.UnixMilli(int64(key.Timestamp))
return time.Now().After(keyDistributedAt.Add(rekeyInterval))
}

controlledCommunities, err := m.ControlledCommunities()
if err != nil {
logger.Error("error getting communities", zap.Error(err))
return
}
for _, c := range cs {
if err != nil {
logger.Error("error getting current keyTimestampID for community", zap.Error(err), zap.Binary("community ID", c.ID()))
continue

for _, c := range controlledCommunities {
keyActions := &communities.EncryptionKeyActions{
CommunityKeyAction: communities.EncryptionKeyAction{},
ChannelKeysActions: map[string]communities.EncryptionKeyAction{},
}

// TODO add functionality to encryptor.go that compares the timestamps and returns a bool
// c.RekeyedAt().Add(rki).Before(time.Now())
// keyTimestampID + rki < time.Now()
// Just using the vars that will be used later
if c.Encrypted() && shouldRekey(c.ID()) {
keyActions.CommunityKeyAction = communities.EncryptionKeyAction{
ActionType: communities.EncryptionKeyRekey,
Members: c.Members(),
}
}

if c.IsControlNode() && c.Encrypted() { // && c.RekeyedAt().Add(rki).Before(time.Now())
err := m.RekeyCommunity(c.ID())
if err != nil {
logger.Error("error sending rekey message", zap.Error(err), zap.Binary("community ID", c.ID()))
continue
for channelID, channel := range c.Chats() {
if c.ChannelEncrypted(channelID) && shouldRekey([]byte(c.IDString()+channelID)) {
keyActions.ChannelKeysActions[channelID] = communities.EncryptionKeyAction{
ActionType: communities.EncryptionKeyRekey,
Members: channel.Members,
}
}
}

err = m.communitiesKeyDistributor.Distribute(c, keyActions)
if err != nil {
logger.Error("failed to rekey community", zap.Error(err), zap.String("community ID", c.IDString()))
continue
}
}
}

Expand Down

0 comments on commit e304fe3

Please sign in to comment.