github.com/status-im/status-go@v1.1.0/protocol/communities/community_description_encryption_test.go (about)

     1  package communities
     2  
     3  import (
     4  	"crypto/ecdsa"
     5  	"errors"
     6  	"testing"
     7  
     8  	"github.com/golang/protobuf/proto"
     9  	"github.com/google/uuid"
    10  	"github.com/stretchr/testify/suite"
    11  	"go.uber.org/zap"
    12  
    13  	"github.com/status-im/status-go/eth-node/crypto"
    14  	"github.com/status-im/status-go/eth-node/types"
    15  	"github.com/status-im/status-go/protocol/protobuf"
    16  )
    17  
    18  func TestCommunityEncryptionDescriptionSuite(t *testing.T) {
    19  	suite.Run(t, new(CommunityEncryptionDescriptionSuite))
    20  }
    21  
    22  type CommunityEncryptionDescriptionSuite struct {
    23  	suite.Suite
    24  
    25  	descriptionEncryptor *DescriptionEncryptorMock
    26  	identity             *ecdsa.PrivateKey
    27  	communityID          []byte
    28  	logger               *zap.Logger
    29  }
    30  
    31  func (s *CommunityEncryptionDescriptionSuite) SetupTest() {
    32  	s.descriptionEncryptor = &DescriptionEncryptorMock{
    33  		descriptions:          map[string]*protobuf.CommunityDescription{},
    34  		channelIDToKeyIDSeqNo: map[string]string{},
    35  	}
    36  
    37  	identity, err := crypto.GenerateKey()
    38  	s.Require().NoError(err)
    39  	s.identity = identity
    40  	s.communityID = crypto.CompressPubkey(&identity.PublicKey)
    41  
    42  	s.logger, err = zap.NewDevelopment()
    43  	s.Require().NoError(err)
    44  }
    45  
    46  type DescriptionEncryptorMock struct {
    47  	descriptions          map[string]*protobuf.CommunityDescription
    48  	channelIDToKeyIDSeqNo map[string]string
    49  }
    50  
    51  func (dem *DescriptionEncryptorMock) encryptCommunityDescription(community *Community, d *protobuf.CommunityDescription) (string, []byte, error) {
    52  	keyIDSeqNo := uuid.New().String()
    53  	dem.descriptions[keyIDSeqNo] = proto.Clone(d).(*protobuf.CommunityDescription)
    54  	return keyIDSeqNo, []byte("encryptedDescription"), nil
    55  }
    56  
    57  func (dem *DescriptionEncryptorMock) encryptCommunityDescriptionChannel(community *Community, channelID string, d *protobuf.CommunityDescription) (string, []byte, error) {
    58  	keyIDSeqNo := uuid.New().String()
    59  	dem.descriptions[keyIDSeqNo] = proto.Clone(d).(*protobuf.CommunityDescription)
    60  	dem.channelIDToKeyIDSeqNo[channelID] = keyIDSeqNo
    61  	return keyIDSeqNo, []byte("encryptedDescription"), nil
    62  }
    63  
    64  func (dem *DescriptionEncryptorMock) decryptCommunityDescription(keyIDSeqNo string, d []byte) (*DecryptCommunityResponse, error) {
    65  	description := dem.descriptions[keyIDSeqNo]
    66  	if description == nil {
    67  		return nil, errors.New("no key to decrypt private data")
    68  	}
    69  	return &DecryptCommunityResponse{Description: description}, nil
    70  }
    71  
    72  func (dem *DescriptionEncryptorMock) forgetAllKeys() {
    73  	dem.descriptions = make(map[string]*protobuf.CommunityDescription)
    74  }
    75  
    76  func (dem *DescriptionEncryptorMock) forgetChannelKeys() {
    77  	for _, keyIDSeqNo := range dem.channelIDToKeyIDSeqNo {
    78  		delete(dem.descriptions, keyIDSeqNo)
    79  	}
    80  }
    81  
    82  func (s *CommunityEncryptionDescriptionSuite) description() *protobuf.CommunityDescription {
    83  	return &protobuf.CommunityDescription{
    84  		IntroMessage: "one of not encrypted fields",
    85  		Members: map[string]*protobuf.CommunityMember{
    86  			"memberA": &protobuf.CommunityMember{},
    87  			"memberB": &protobuf.CommunityMember{},
    88  		},
    89  		ActiveMembersCount: 1,
    90  		Chats: map[string]*protobuf.CommunityChat{
    91  			"channelA": &protobuf.CommunityChat{
    92  				Members: map[string]*protobuf.CommunityMember{
    93  					"memberA": &protobuf.CommunityMember{},
    94  					"memberB": &protobuf.CommunityMember{},
    95  				},
    96  			},
    97  			"channelB": &protobuf.CommunityChat{
    98  				Members: map[string]*protobuf.CommunityMember{
    99  					"memberA": &protobuf.CommunityMember{},
   100  				},
   101  			},
   102  		},
   103  		Categories: map[string]*protobuf.CommunityCategory{
   104  			"categoryA": &protobuf.CommunityCategory{
   105  				CategoryId: "categoryA",
   106  				Name:       "categoryA",
   107  				Position:   0,
   108  			},
   109  		},
   110  		PrivateData: map[string][]byte{},
   111  
   112  		// ensure community and channel encryption
   113  		TokenPermissions: map[string]*protobuf.CommunityTokenPermission{
   114  			"community-level-permission": &protobuf.CommunityTokenPermission{
   115  				Id:            "community-level-permission",
   116  				Type:          protobuf.CommunityTokenPermission_BECOME_MEMBER,
   117  				TokenCriteria: []*protobuf.TokenCriteria{},
   118  				ChatIds:       []string{},
   119  			},
   120  			"channel-level-permission": &protobuf.CommunityTokenPermission{
   121  				Id:            "channel-level-permission",
   122  				Type:          protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
   123  				TokenCriteria: []*protobuf.TokenCriteria{&protobuf.TokenCriteria{}},
   124  				ChatIds:       []string{types.EncodeHex(crypto.CompressPubkey(&s.identity.PublicKey)) + "channelB"},
   125  			},
   126  		},
   127  	}
   128  }
   129  
   130  func (s *CommunityEncryptionDescriptionSuite) TestEncryptionDecryption() {
   131  	description := s.description()
   132  
   133  	err := encryptDescription(s.descriptionEncryptor, &Community{
   134  		config: &Config{ID: &s.identity.PublicKey, CommunityDescription: description},
   135  	}, description)
   136  	s.Require().NoError(err)
   137  	s.Require().Len(description.PrivateData, 2)
   138  
   139  	// members, chats, categories should become empty (encrypted)
   140  	s.Require().Empty(description.Members)
   141  	s.Require().Empty(description.ActiveMembersCount)
   142  	s.Require().Empty(description.Chats)
   143  	s.Require().Empty(description.Categories)
   144  	s.Require().Equal(description.IntroMessage, "one of not encrypted fields")
   145  
   146  	// members and chats should be brought back
   147  	_, err = decryptDescription([]byte("some-id"), s.descriptionEncryptor, description, s.logger)
   148  	s.Require().NoError(err)
   149  	s.Require().Len(description.Members, 2)
   150  	s.Require().EqualValues(description.ActiveMembersCount, 1)
   151  	s.Require().Len(description.Chats, 2)
   152  	s.Require().Len(description.Chats["channelA"].Members, 2)
   153  	s.Require().Len(description.Chats["channelB"].Members, 1)
   154  	s.Require().Len(description.Categories, 1)
   155  	s.Require().Equal(description.Categories["categoryA"].Name, "categoryA")
   156  	s.Require().Equal(description.IntroMessage, "one of not encrypted fields")
   157  }
   158  
   159  func (s *CommunityEncryptionDescriptionSuite) TestDecryption_NoKeys() {
   160  	encryptedDescription := func() *protobuf.CommunityDescription {
   161  		description := s.description()
   162  
   163  		err := encryptDescription(s.descriptionEncryptor, &Community{
   164  			config: &Config{ID: &s.identity.PublicKey, CommunityDescription: description},
   165  		}, description)
   166  		s.Require().NoError(err)
   167  
   168  		return description
   169  	}()
   170  
   171  	description := proto.Clone(encryptedDescription).(*protobuf.CommunityDescription)
   172  	// forget channel keys, so channel members can't be decrypted
   173  	s.descriptionEncryptor.forgetChannelKeys()
   174  
   175  	// encrypted channel should have no members
   176  	_, err := decryptDescription([]byte("some-id"), s.descriptionEncryptor, description, s.logger)
   177  	s.Require().NoError(err)
   178  	s.Require().Len(description.Members, 2)
   179  	s.Require().EqualValues(description.ActiveMembersCount, 1)
   180  	s.Require().Len(description.Chats, 2)
   181  	s.Require().Len(description.Chats["channelA"].Members, 2)
   182  	s.Require().Len(description.Chats["channelB"].Members, 0) // encrypted channel
   183  	s.Require().Len(description.Categories, 1)
   184  	s.Require().Equal(description.Categories["categoryA"].Name, "categoryA")
   185  	s.Require().Equal(description.IntroMessage, "one of not encrypted fields")
   186  
   187  	description = proto.Clone(encryptedDescription).(*protobuf.CommunityDescription)
   188  	// forget the keys, so members, chats, categories can't be decrypted
   189  	s.descriptionEncryptor.forgetAllKeys()
   190  
   191  	// members, chats, categories should be empty
   192  	_, err = decryptDescription([]byte("some-id"), s.descriptionEncryptor, description, s.logger)
   193  	s.Require().NoError(err)
   194  	s.Require().Empty(description.Members)
   195  	s.Require().Empty(description.ActiveMembersCount)
   196  	s.Require().Empty(description.Chats)
   197  	s.Require().Empty(description.Categories)
   198  	s.Require().Equal(description.IntroMessage, "one of not encrypted fields")
   199  }