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 }