status-go/protocol/communities/community_encryption_key_action.go
Patryk Osmaczko 367b7722d1 feat: introduce channel-level encryption
- distribute ratchet keys at both community and channel levels
- use explicit `HashRatchetGroupID` in ecryption layer, instead of
  inheriting `groupID` from `CommunityID`
- populate `HashRatchetGroupID` with `CommunityID+ChannelID` for
  channels, and `CommunityID` for whole community
- hydrate channels with members; channel members are now subset of
  community members
- include channel permissions in periodic permissions check

closes: status-im/status-desktop#10998
2023-07-27 11:35:59 +02:00

135 lines
5.2 KiB
Go

package communities
import "github.com/status-im/status-go/protocol/protobuf"
type EncryptionKeyActionType int
const (
EncryptionKeyNone EncryptionKeyActionType = iota
EncryptionKeyAdd
EncryptionKeyRemove
EncryptionKeyRekey
EncryptionKeySendToMembers
)
type EncryptionKeyAction struct {
ActionType EncryptionKeyActionType
Members map[string]*protobuf.CommunityMember
}
type EncryptionKeyActions struct {
// community-level encryption key action
CommunityKeyAction EncryptionKeyAction
// channel-level encryption key actions
ChannelKeysActions map[string]EncryptionKeyAction // key is: chatID
}
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{
CommunityKeyAction: *evaluateCommunityLevelEncryptionKeyAction(origin, modified, changes),
ChannelKeysActions: *evaluateChannelLevelEncryptionKeyActions(origin, modified, changes),
}
return result
}
func evaluateCommunityLevelEncryptionKeyAction(origin, modified *Community, changes *CommunityChanges) *EncryptionKeyAction {
originBecomeMemberPermissions := origin.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_MEMBER)
modifiedBecomeMemberPermissions := modified.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_MEMBER)
return evaluateEncryptionKeyAction(originBecomeMemberPermissions, modifiedBecomeMemberPermissions, modified.config.CommunityDescription.Members, changes.MembersAdded, changes.MembersRemoved)
}
func evaluateChannelLevelEncryptionKeyActions(origin, modified *Community, changes *CommunityChanges) *map[string]EncryptionKeyAction {
result := make(map[string]EncryptionKeyAction)
for channelID := range modified.config.CommunityDescription.Chats {
chatID := modified.IDString() + channelID
originChannelViewOnlyPermissions := origin.ChannelTokenPermissionsByType(chatID, protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL)
originChannelViewAndPostPermissions := origin.ChannelTokenPermissionsByType(chatID, protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL)
originChannelPermissions := append(originChannelViewOnlyPermissions, originChannelViewAndPostPermissions...)
modifiedChannelViewOnlyPermissions := modified.ChannelTokenPermissionsByType(chatID, protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL)
modifiedChannelViewAndPostPermissions := modified.ChannelTokenPermissionsByType(chatID, protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL)
modifiedChannelPermissions := append(modifiedChannelViewOnlyPermissions, modifiedChannelViewAndPostPermissions...)
membersAdded := make(map[string]*protobuf.CommunityMember)
membersRemoved := make(map[string]*protobuf.CommunityMember)
chatChanges, ok := changes.ChatsModified[channelID]
if ok {
membersAdded = chatChanges.MembersAdded
membersRemoved = chatChanges.MembersRemoved
}
result[channelID] = *evaluateEncryptionKeyAction(originChannelPermissions, modifiedChannelPermissions, modified.config.CommunityDescription.Chats[channelID].Members, membersAdded, membersRemoved)
}
return &result
}
func evaluateEncryptionKeyAction(originPermissions, modifiedPermissions []*protobuf.CommunityTokenPermission, allMembers, membersAdded, membersRemoved map[string]*protobuf.CommunityMember) *EncryptionKeyAction {
result := &EncryptionKeyAction{
ActionType: EncryptionKeyNone,
Members: map[string]*protobuf.CommunityMember{},
}
copyMap := func(source map[string]*protobuf.CommunityMember) map[string]*protobuf.CommunityMember {
to := make(map[string]*protobuf.CommunityMember)
for pubKey, member := range source {
to[pubKey] = member
}
return to
}
// permission was just added
if len(modifiedPermissions) > 0 && len(originPermissions) == 0 {
result.ActionType = EncryptionKeyAdd
result.Members = copyMap(allMembers)
return result
}
// permission was just removed
if len(modifiedPermissions) == 0 && len(originPermissions) > 0 {
result.ActionType = EncryptionKeyRemove
result.Members = copyMap(allMembers)
return result
}
// open community/channel does not require any actions
if len(modifiedPermissions) == 0 {
return result
}
if len(membersRemoved) > 0 {
result.ActionType = EncryptionKeyRekey
result.Members = copyMap(allMembers)
} else if len(membersAdded) > 0 {
result.ActionType = EncryptionKeySendToMembers
result.Members = copyMap(membersAdded)
}
return result
}