Skip to content

Commit

Permalink
feat: introduce CommunitiesKeyDistributor
Browse files Browse the repository at this point in the history
This component decouples key distribution from the Messenger, enhancing
code maintainability, extensibility and testability.
It also alleviates the need to impact all methods potentially affecting
encryption keys.
Moreover, it allows key distribution inspection for integration tests.

part of: status-im/status-desktop#10998
  • Loading branch information
osmaczko committed Jul 27, 2023
1 parent fa5b316 commit 527e51c
Show file tree
Hide file tree
Showing 10 changed files with 427 additions and 208 deletions.
14 changes: 9 additions & 5 deletions protocol/communities/community.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ func (o *Community) GetMemberPubkeys() []*ecdsa.PublicKey {
}
return nil
}

func (o *Community) initialize() {
if o.config.CommunityDescription == nil {
o.config.CommunityDescription = &protobuf.CommunityDescription{}
Expand Down Expand Up @@ -987,10 +988,6 @@ func (o *Community) Encrypted() bool {
return o.config.CommunityDescription.Encrypted
}

func (o *Community) SetEncrypted(encrypted bool) {
o.config.CommunityDescription.Encrypted = encrypted
}

func (o *Community) Joined() bool {
return o.config.Joined
}
Expand Down Expand Up @@ -1430,6 +1427,10 @@ func includes(channelIDs []string, channelID string) bool {
return false
}

func (o *Community) updateEncrypted() {
o.config.CommunityDescription.Encrypted = len(o.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_MEMBER)) > 0
}

func (o *Community) AddTokenPermission(permission *protobuf.CommunityTokenPermission) (*CommunityChanges, error) {
o.mutex.Lock()
defer o.mutex.Unlock()
Expand All @@ -1454,6 +1455,7 @@ func (o *Community) AddTokenPermission(permission *protobuf.CommunityTokenPermis
}

if isControlNode {
o.updateEncrypted()
o.increaseClock()
}

Expand Down Expand Up @@ -1484,6 +1486,7 @@ func (o *Community) UpdateTokenPermission(permissionID string, tokenPermission *
}

if isControlNode {
o.updateEncrypted()
o.increaseClock()
}

Expand Down Expand Up @@ -1520,6 +1523,7 @@ func (o *Community) DeleteTokenPermission(permissionID string) (*CommunityChange
}

if isControlNode {
o.updateEncrypted()
o.increaseClock()
}

Expand Down Expand Up @@ -1924,7 +1928,7 @@ func (o *Community) AddMemberWithRevealedAccounts(dbRequest *RequestToJoin, role
return changes, nil
}

func (o *Community) createDeepCopy() *Community {
func (o *Community) CreateDeepCopy() *Community {
return &Community{
config: &Config{
PrivateKey: o.config.PrivateKey,
Expand Down
18 changes: 18 additions & 0 deletions protocol/communities/community_encryption_key_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@ type EncryptionKeyActions struct {
}

func EvaluateCommunityEncryptionKeyActions(origin, modified *Community) *EncryptionKeyActions {
if origin == nil {
// `modified` is a new community, create empty `origin` community
origin = &Community{
config: &Config{
CommunityDescription: &protobuf.CommunityDescription{
Members: map[string]*protobuf.CommunityMember{},
Permissions: &protobuf.CommunityPermissions{},
Identity: &protobuf.ChatIdentity{},
Chats: map[string]*protobuf.CommunityChat{},
Categories: map[string]*protobuf.CommunityCategory{},
AdminSettings: &protobuf.CommunityAdminSettings{},
TokenPermissions: map[string]*protobuf.CommunityTokenPermission{},
CommunityTokensMetadata: []*protobuf.CommunityTokenMetadata{},
},
},
}
}

changes := EvaluateCommunityChanges(origin.Description(), modified.Description())

result := &EncryptionKeyActions{
Expand Down
45 changes: 41 additions & 4 deletions protocol/communities/community_encryption_key_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ func (s *CommunityEncryptionKeyActionSuite) TestCommunityLevelKeyActions_Permiss
for _, tc := range testCases {
s.Run(tc.name, func() {
origin := createTestCommunity(s.identity)
modified := origin.createDeepCopy()
modified := origin.CreateDeepCopy()

for _, permission := range tc.originPermissions {
_, err := origin.AddTokenPermission(permission)
Expand Down Expand Up @@ -498,7 +498,7 @@ func (s *CommunityEncryptionKeyActionSuite) TestCommunityLevelKeyActions_Members
_, err := origin.AddTokenPermission(permission)
s.Require().NoError(err)
}
modified := origin.createDeepCopy()
modified := origin.CreateDeepCopy()

for _, member := range tc.originMembers {
_, err := origin.AddMember(member, []protobuf.CommunityMember_Roles{})
Expand Down Expand Up @@ -595,7 +595,7 @@ func (s *CommunityEncryptionKeyActionSuite) TestCommunityLevelKeyActions_Permiss
for _, tc := range testCases {
s.Run(tc.name, func() {
origin := createTestCommunity(s.identity)
modified := origin.createDeepCopy()
modified := origin.CreateDeepCopy()

for _, permission := range tc.originPermissions {
_, err := origin.AddTokenPermission(permission)
Expand Down Expand Up @@ -743,7 +743,7 @@ func (s *CommunityEncryptionKeyActionSuite) TestChannelLevelKeyActions() {
})
s.Require().NoError(err)

modified := origin.createDeepCopy()
modified := origin.CreateDeepCopy()

for _, permission := range tc.originPermissions {
_, err := origin.AddTokenPermission(permission)
Expand Down Expand Up @@ -779,3 +779,40 @@ func (s *CommunityEncryptionKeyActionSuite) TestChannelLevelKeyActions() {
})
}
}

func (s *CommunityEncryptionKeyActionSuite) TestNilOrigin() {
newCommunity := createTestCommunity(s.identity)

chatID := "0x1234"
_, err := newCommunity.CreateChat(chatID, &protobuf.CommunityChat{
Members: map[string]*protobuf.CommunityMember{},
Permissions: &protobuf.CommunityPermissions{Access: protobuf.CommunityPermissions_NO_MEMBERSHIP},
Identity: &protobuf.ChatIdentity{},
})
s.Require().NoError(err)

newCommunityPermissions := []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id-1",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
&protobuf.CommunityTokenPermission{
Id: "some-id-2",
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{chatID},
},
}
for _, permission := range newCommunityPermissions {
_, err := newCommunity.AddTokenPermission(permission)
s.Require().NoError(err)
}

actions := EvaluateCommunityEncryptionKeyActions(nil, newCommunity)
s.Require().Equal(actions.CommunityKeyAction.ActionType, EncryptionKeyAdd)
s.Require().Len(actions.ChannelKeysActions, 1)
s.Require().NotNil(actions.ChannelKeysActions[chatID])
s.Require().Equal(actions.ChannelKeysActions[chatID].ActionType, EncryptionKeyAdd)
}
2 changes: 1 addition & 1 deletion protocol/communities/community_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func (o *Community) UpdateCommunityByEvents(communityEventMessage *CommunityEven
}

// Create a deep copy of current community so we can update CommunityDescription by new admin events
copy := o.createDeepCopy()
copy := o.CreateDeepCopy()

// Merge community admin events to existing community. Admin events must be stored to the db
// during saving the community
Expand Down
13 changes: 5 additions & 8 deletions protocol/communities/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,10 +283,6 @@ type Subscription struct {
DownloadingHistoryArchivesFinishedSignal *signal.DownloadingHistoryArchivesFinishedSignal
ImportingHistoryArchiveMessagesSignal *signal.ImportingHistoryArchiveMessagesSignal
CommunityEventsMessage *CommunityEventsMessage
MemberPermissionsCheckedSignal *MemberPermissionsCheckedSignal
}

type MemberPermissionsCheckedSignal struct {
}

type CommunityResponse struct {
Expand Down Expand Up @@ -700,10 +696,11 @@ func (m *Manager) CheckMemberPermissions(community *Community, removeAdmins bool
}
}

m.publish(&Subscription{
Community: community,
MemberPermissionsCheckedSignal: &MemberPermissionsCheckedSignal{},
})
err := m.saveAndPublish(community)
if err != nil {
return err
}

return nil
}

Expand Down
120 changes: 120 additions & 0 deletions protocol/communities_key_distributor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package protocol

import (
"context"
"crypto/ecdsa"

"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/communities"
"github.com/status-im/status-go/protocol/encryption"
"github.com/status-im/status-go/protocol/protobuf"
)

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

type CommunitiesKeyDistributorImpl struct {
sender *common.MessageSender
encryptor *encryption.Protocol
}

func (ckd *CommunitiesKeyDistributorImpl) Distribute(community *communities.Community, keyActions *communities.EncryptionKeyActions) error {
if !community.IsOwner() {
return communities.ErrNotOwner
}

err := ckd.distributeKey(community.ID(), community.ID(), &keyActions.CommunityKeyAction)
if err != nil {
return err
}

for chatID := range keyActions.ChannelKeysActions {
keyAction := keyActions.ChannelKeysActions[chatID]
err := ckd.distributeKey(community.ID(), []byte(chatID), &keyAction)
if err != nil {
return err
}
}

return nil
}

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

err := ckd.distributeKey(community.ID(), 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.ID(), []byte(community.IDString()+channelID), &communities.EncryptionKeyAction{
ActionType: communities.EncryptionKeyRekey,
Members: channel.Members,
})
if err != nil {
return err
}
}

return nil
}

func (ckd *CommunitiesKeyDistributorImpl) distributeKey(communityID, hashRatchetGroupID []byte, keyAction *communities.EncryptionKeyAction) error {
pubkeys := make([]*ecdsa.PublicKey, len(keyAction.Members))
i := 0
for hex := range keyAction.Members {
pubkeys[i], _ = common.HexToPubkey(hex)
i++
}

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

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

case communities.EncryptionKeyRekey:
err := ckd.sendKeyExchangeMessage(communityID, hashRatchetGroupID, pubkeys, common.KeyExMsgRekey)
if err != nil {
return err
}

case communities.EncryptionKeySendToMembers:
err := ckd.sendKeyExchangeMessage(communityID, hashRatchetGroupID, pubkeys, common.KeyExMsgReuse)
if err != nil {
return err
}
}

return nil
}

func (ckd *CommunitiesKeyDistributorImpl) sendKeyExchangeMessage(communityID, hashRatchetGroupID []byte, pubkeys []*ecdsa.PublicKey, msgType common.CommKeyExMsgType) error {
rawMessage := common.RawMessage{
SkipProtocolLayer: false,
CommunityID: communityID,
CommunityKeyExMsgType: msgType,
Recipients: pubkeys,
MessageType: protobuf.ApplicationMetadataMessage_CHAT_MESSAGE,
}
_, err := ckd.sender.SendCommunityMessage(context.Background(), rawMessage)

if err != nil {
return err
}
return nil
}
Loading

0 comments on commit 527e51c

Please sign in to comment.