2023-11-29 17:21:21 +00:00
|
|
|
package communities
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/ecdsa"
|
|
|
|
"errors"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
|
|
|
"github.com/status-im/status-go/eth-node/types"
|
|
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestCommunityEncryptionDescriptionSuite(t *testing.T) {
|
|
|
|
suite.Run(t, new(CommunityEncryptionDescriptionSuite))
|
|
|
|
}
|
|
|
|
|
|
|
|
type CommunityEncryptionDescriptionSuite struct {
|
|
|
|
suite.Suite
|
|
|
|
|
|
|
|
descriptionEncryptor *DescriptionEncryptorMock
|
|
|
|
identity *ecdsa.PrivateKey
|
|
|
|
communityID []byte
|
|
|
|
logger *zap.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *CommunityEncryptionDescriptionSuite) SetupTest() {
|
|
|
|
s.descriptionEncryptor = &DescriptionEncryptorMock{
|
|
|
|
descriptions: map[string]*protobuf.CommunityDescription{},
|
|
|
|
channelIDToKeyIDSeqNo: map[string]string{},
|
|
|
|
}
|
|
|
|
|
|
|
|
identity, err := crypto.GenerateKey()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
s.identity = identity
|
|
|
|
s.communityID = crypto.CompressPubkey(&identity.PublicKey)
|
|
|
|
|
|
|
|
s.logger, err = zap.NewDevelopment()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
type DescriptionEncryptorMock struct {
|
|
|
|
descriptions map[string]*protobuf.CommunityDescription
|
|
|
|
channelIDToKeyIDSeqNo map[string]string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dem *DescriptionEncryptorMock) encryptCommunityDescription(community *Community, d *protobuf.CommunityDescription) (string, []byte, error) {
|
|
|
|
keyIDSeqNo := uuid.New().String()
|
|
|
|
dem.descriptions[keyIDSeqNo] = d
|
|
|
|
return keyIDSeqNo, []byte("encryptedDescription"), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dem *DescriptionEncryptorMock) encryptCommunityDescriptionChannel(community *Community, channelID string, d *protobuf.CommunityDescription) (string, []byte, error) {
|
|
|
|
keyIDSeqNo := uuid.New().String()
|
|
|
|
dem.descriptions[keyIDSeqNo] = d
|
|
|
|
dem.channelIDToKeyIDSeqNo[channelID] = keyIDSeqNo
|
|
|
|
return keyIDSeqNo, []byte("encryptedDescription"), nil
|
|
|
|
}
|
|
|
|
|
2024-01-23 16:56:51 +00:00
|
|
|
func (dem *DescriptionEncryptorMock) decryptCommunityDescription(keyIDSeqNo string, d []byte) (*DecryptCommunityResponse, error) {
|
2023-11-29 17:21:21 +00:00
|
|
|
description := dem.descriptions[keyIDSeqNo]
|
|
|
|
if description == nil {
|
|
|
|
return nil, errors.New("no key to decrypt private data")
|
|
|
|
}
|
2024-01-23 16:56:51 +00:00
|
|
|
return &DecryptCommunityResponse{Description: description}, nil
|
2023-11-29 17:21:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (dem *DescriptionEncryptorMock) forgetAllKeys() {
|
|
|
|
dem.descriptions = make(map[string]*protobuf.CommunityDescription)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dem *DescriptionEncryptorMock) forgetChannelKeys() {
|
|
|
|
for _, keyIDSeqNo := range dem.channelIDToKeyIDSeqNo {
|
|
|
|
delete(dem.descriptions, keyIDSeqNo)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *CommunityEncryptionDescriptionSuite) description() *protobuf.CommunityDescription {
|
|
|
|
return &protobuf.CommunityDescription{
|
|
|
|
IntroMessage: "one of not encrypted fields",
|
|
|
|
Members: map[string]*protobuf.CommunityMember{
|
|
|
|
"memberA": &protobuf.CommunityMember{},
|
|
|
|
"memberB": &protobuf.CommunityMember{},
|
|
|
|
},
|
|
|
|
Chats: map[string]*protobuf.CommunityChat{
|
|
|
|
"channelA": &protobuf.CommunityChat{
|
|
|
|
Members: map[string]*protobuf.CommunityMember{
|
|
|
|
"memberA": &protobuf.CommunityMember{},
|
|
|
|
"memberB": &protobuf.CommunityMember{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"channelB": &protobuf.CommunityChat{
|
|
|
|
Members: map[string]*protobuf.CommunityMember{
|
|
|
|
"memberA": &protobuf.CommunityMember{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
PrivateData: map[string][]byte{},
|
|
|
|
|
|
|
|
// ensure community and channel encryption
|
|
|
|
TokenPermissions: map[string]*protobuf.CommunityTokenPermission{
|
|
|
|
"community-level-permission": &protobuf.CommunityTokenPermission{
|
|
|
|
Id: "community-level-permission",
|
|
|
|
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
|
|
|
TokenCriteria: []*protobuf.TokenCriteria{},
|
|
|
|
ChatIds: []string{},
|
|
|
|
},
|
|
|
|
"channel-level-permission": &protobuf.CommunityTokenPermission{
|
2024-01-08 12:41:13 +00:00
|
|
|
Id: "channel-level-permission",
|
|
|
|
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
|
2023-11-29 17:21:21 +00:00
|
|
|
TokenCriteria: []*protobuf.TokenCriteria{},
|
|
|
|
ChatIds: []string{types.EncodeHex(crypto.CompressPubkey(&s.identity.PublicKey)) + "channelB"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *CommunityEncryptionDescriptionSuite) TestEncryptionDecryption() {
|
|
|
|
description := s.description()
|
|
|
|
|
|
|
|
err := encryptDescription(s.descriptionEncryptor, &Community{
|
|
|
|
config: &Config{ID: &s.identity.PublicKey, CommunityDescription: description},
|
|
|
|
}, description)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
s.Require().Len(description.PrivateData, 2)
|
|
|
|
|
|
|
|
// members and chats should become empty (encrypted)
|
|
|
|
s.Require().Empty(description.Members)
|
|
|
|
s.Require().Empty(description.Chats)
|
|
|
|
s.Require().Equal(description.IntroMessage, "one of not encrypted fields")
|
|
|
|
|
|
|
|
// members and chats should be brought back
|
2024-01-23 16:56:51 +00:00
|
|
|
_, err = decryptDescription([]byte("some-id"), s.descriptionEncryptor, description, s.logger)
|
2023-11-29 17:21:21 +00:00
|
|
|
s.Require().NoError(err)
|
|
|
|
s.Require().Len(description.Members, 2)
|
|
|
|
s.Require().Len(description.Chats, 2)
|
|
|
|
s.Require().Len(description.Chats["channelA"].Members, 2)
|
|
|
|
s.Require().Len(description.Chats["channelB"].Members, 1)
|
|
|
|
s.Require().Equal(description.IntroMessage, "one of not encrypted fields")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *CommunityEncryptionDescriptionSuite) TestDecryption_NoKeys() {
|
|
|
|
encryptedDescription := func() *protobuf.CommunityDescription {
|
|
|
|
description := s.description()
|
|
|
|
|
|
|
|
err := encryptDescription(s.descriptionEncryptor, &Community{
|
|
|
|
config: &Config{ID: &s.identity.PublicKey, CommunityDescription: description},
|
|
|
|
}, description)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
return description
|
|
|
|
}()
|
|
|
|
|
|
|
|
description := proto.Clone(encryptedDescription).(*protobuf.CommunityDescription)
|
|
|
|
// forget channel keys, so channel members can't be decrypted
|
|
|
|
s.descriptionEncryptor.forgetChannelKeys()
|
|
|
|
|
|
|
|
// encrypted channel should have no members
|
2024-01-23 16:56:51 +00:00
|
|
|
_, err := decryptDescription([]byte("some-id"), s.descriptionEncryptor, description, s.logger)
|
2023-11-29 17:21:21 +00:00
|
|
|
s.Require().NoError(err)
|
|
|
|
s.Require().Len(description.Members, 2)
|
|
|
|
s.Require().Len(description.Chats, 2)
|
|
|
|
s.Require().Len(description.Chats["channelA"].Members, 2)
|
|
|
|
s.Require().Len(description.Chats["channelB"].Members, 0) // encrypted channel
|
|
|
|
s.Require().Equal(description.IntroMessage, "one of not encrypted fields")
|
|
|
|
|
|
|
|
description = proto.Clone(encryptedDescription).(*protobuf.CommunityDescription)
|
|
|
|
// forget the keys, so chats and members can't be decrypted
|
|
|
|
s.descriptionEncryptor.forgetAllKeys()
|
|
|
|
|
|
|
|
// members and chats should be empty
|
2024-01-23 16:56:51 +00:00
|
|
|
_, err = decryptDescription([]byte("some-id"), s.descriptionEncryptor, description, s.logger)
|
2023-11-29 17:21:21 +00:00
|
|
|
s.Require().NoError(err)
|
|
|
|
s.Require().Empty(description.Members)
|
|
|
|
s.Require().Empty(description.Chats)
|
|
|
|
s.Require().Equal(description.IntroMessage, "one of not encrypted fields")
|
|
|
|
}
|