status-im/status-go

View on GitHub
protocol/communities/community_description_encryption.go

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
package communities

import (
    "go.uber.org/zap"

    "github.com/status-im/status-go/eth-node/types"
    "github.com/status-im/status-go/protocol/protobuf"
)

type DescriptionEncryptor interface {
    encryptCommunityDescription(community *Community, d *protobuf.CommunityDescription) (string, []byte, error)
    encryptCommunityDescriptionChannel(community *Community, channelID string, d *protobuf.CommunityDescription) (string, []byte, error)
    decryptCommunityDescription(keyIDSeqNo string, d []byte) (*DecryptCommunityResponse, error)
}

// Encrypts members and chats
func encryptDescription(encryptor DescriptionEncryptor, community *Community, description *protobuf.CommunityDescription) error {
    description.PrivateData = make(map[string][]byte)

    for channelID, channel := range description.Chats {
        if !community.channelEncrypted(channelID) {
            continue
        }

        descriptionToEncrypt := &protobuf.CommunityDescription{
            Chats: map[string]*protobuf.CommunityChat{
                channelID: channel,
            },
        }

        keyIDSeqNo, encryptedDescription, err := encryptor.encryptCommunityDescriptionChannel(community, channelID, descriptionToEncrypt)
        if err != nil {
            return err
        }

        // Set private data and cleanup unencrypted channel's members
        description.PrivateData[keyIDSeqNo] = encryptedDescription
        channel.Members = make(map[string]*protobuf.CommunityMember)
    }

    if community.Encrypted() {
        descriptionToEncrypt := &protobuf.CommunityDescription{
            Members:            description.Members,
            ActiveMembersCount: description.ActiveMembersCount,
            Chats:              description.Chats,
            Categories:         description.Categories,
        }

        keyIDSeqNo, encryptedDescription, err := encryptor.encryptCommunityDescription(community, descriptionToEncrypt)
        if err != nil {
            return err
        }

        // Set private data and cleanup unencrypted members, chats and categories
        description.PrivateData[keyIDSeqNo] = encryptedDescription
        description.Members = make(map[string]*protobuf.CommunityMember)
        description.ActiveMembersCount = 0
        description.Chats = make(map[string]*protobuf.CommunityChat)
        description.Categories = make(map[string]*protobuf.CommunityCategory)
    }

    return nil
}

type CommunityPrivateDataFailedToDecrypt struct {
    GroupID []byte
    KeyID   []byte
}

// Decrypts members and chats
func decryptDescription(id types.HexBytes, encryptor DescriptionEncryptor, description *protobuf.CommunityDescription, logger *zap.Logger) ([]*CommunityPrivateDataFailedToDecrypt, error) {
    if len(description.PrivateData) == 0 {
        return nil, nil
    }

    var failedToDecrypt []*CommunityPrivateDataFailedToDecrypt

    for keyIDSeqNo, encryptedDescription := range description.PrivateData {
        decryptedDescriptionResponse, err := encryptor.decryptCommunityDescription(keyIDSeqNo, encryptedDescription)
        if decryptedDescriptionResponse != nil && !decryptedDescriptionResponse.Decrypted {
            failedToDecrypt = append(failedToDecrypt, &CommunityPrivateDataFailedToDecrypt{GroupID: id, KeyID: decryptedDescriptionResponse.KeyID})
        }
        if err != nil {
            // ignore error, try to decrypt next data
            logger.Debug("failed to decrypt community private data", zap.String("keyIDSeqNo", keyIDSeqNo), zap.Error(err))
            continue
        }
        decryptedDescription := decryptedDescriptionResponse.Description

        if len(decryptedDescription.Members) > 0 {
            description.Members = decryptedDescription.Members
        }

        if decryptedDescription.ActiveMembersCount > 0 {
            description.ActiveMembersCount = decryptedDescription.ActiveMembersCount
        }

        for id, decryptedChannel := range decryptedDescription.Chats {
            if description.Chats == nil {
                description.Chats = make(map[string]*protobuf.CommunityChat)
            }

            if channel := description.Chats[id]; channel != nil {
                if len(decryptedChannel.Members) > 0 {
                    channel.Members = decryptedChannel.Members
                }
            } else {
                description.Chats[id] = decryptedChannel
            }
        }

        if len(decryptedDescription.Categories) > 0 {
            description.Categories = decryptedDescription.Categories
        }
    }

    return failedToDecrypt, nil
}