-
Notifications
You must be signed in to change notification settings - Fork 248
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: encrypt
CommunityDescription
fields
Extended `CommunityDescription` with a `privateData` map. This map associates each hash ratchet `key_id` and `seq_no` with an encrypted `CommunityDescription`. Each encrypted instance includes only data requiring encryption. This commit introduces a description encryption for closed communities. The map is pupulated with a single entry. The key is hash ratchet for community-level encryption, and the value is an ecnrypted `CommunityDescription` defining only two fields: `chats` and `members`. As a follow-up, channel-level description encryption will be implemented. Each channel will have its unique entry in the map, with the key being a hash ratchet specific to channel-level encryption. closes: status-im/status-desktop#12851 closes: status-im/status-desktop#12852
- Loading branch information
Showing
17 changed files
with
2,008 additions
and
2,837 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package communities | ||
|
||
import ( | ||
"go.uber.org/zap" | ||
|
||
"github.com/status-im/status-go/protocol/protobuf" | ||
) | ||
|
||
type DescriptionEncryptor interface { | ||
encryptCommunityDescription(community *Community, d *protobuf.CommunityDescription) (string, []byte, error) | ||
decryptCommunityDescription(keyIDSeqNo string, d []byte) (*protobuf.CommunityDescription, error) | ||
} | ||
|
||
// Encrypts members and chats | ||
func encryptDescription(encryptor DescriptionEncryptor, community *Community, description *protobuf.CommunityDescription) error { | ||
descriptionToEncrypt := &protobuf.CommunityDescription{ | ||
Members: description.Members, | ||
Chats: description.Chats, | ||
} | ||
|
||
keyIDSeqNo, encryptedDescription, err := encryptor.encryptCommunityDescription(community, descriptionToEncrypt) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Set private data and cleanup unencrypted members and chats | ||
if description.PrivateData == nil { | ||
description.PrivateData = make(map[string][]byte) | ||
} | ||
description.PrivateData[keyIDSeqNo] = encryptedDescription | ||
description.Members = make(map[string]*protobuf.CommunityMember) | ||
description.Chats = make(map[string]*protobuf.CommunityChat) | ||
|
||
return nil | ||
} | ||
|
||
// Decrypts members and chats | ||
func decryptDescription(encryptor DescriptionEncryptor, description *protobuf.CommunityDescription, logger *zap.Logger) error { | ||
for keyIDSeqNo, encryptedDescription := range description.PrivateData { | ||
decryptedDescription, err := encryptor.decryptCommunityDescription(keyIDSeqNo, encryptedDescription) | ||
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 | ||
} | ||
|
||
for pk, member := range decryptedDescription.Members { | ||
if description.Members == nil { | ||
description.Members = make(map[string]*protobuf.CommunityMember) | ||
} | ||
description.Members[pk] = member | ||
} | ||
|
||
for id, channel := range decryptedDescription.Chats { | ||
if description.Chats == nil { | ||
description.Chats = make(map[string]*protobuf.CommunityChat) | ||
} | ||
description.Chats[id] = channel | ||
} | ||
} | ||
|
||
return nil | ||
} |
106 changes: 106 additions & 0 deletions
106
protocol/communities/community_description_encryption_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package communities | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
|
||
"github.com/google/uuid" | ||
"github.com/stretchr/testify/suite" | ||
"go.uber.org/zap" | ||
|
||
"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 | ||
logger *zap.Logger | ||
} | ||
|
||
func (s *CommunityEncryptionDescriptionSuite) SetupTest() { | ||
s.descriptionEncryptor = &DescriptionEncryptorMock{ | ||
descriptions: map[string]*protobuf.CommunityDescription{}, | ||
} | ||
var err error | ||
s.logger, err = zap.NewDevelopment() | ||
s.Require().NoError(err) | ||
} | ||
|
||
type DescriptionEncryptorMock struct { | ||
descriptions map[string]*protobuf.CommunityDescription | ||
} | ||
|
||
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) decryptCommunityDescription(keyIDSeqNo string, d []byte) (*protobuf.CommunityDescription, error) { | ||
description := dem.descriptions[keyIDSeqNo] | ||
if description == nil { | ||
return nil, errors.New("no key to decrypt private data") | ||
} | ||
return description, nil | ||
} | ||
|
||
func (dem *DescriptionEncryptorMock) forgetKeys() { | ||
dem.descriptions = make(map[string]*protobuf.CommunityDescription) | ||
} | ||
|
||
func (s *CommunityEncryptionDescriptionSuite) description() *protobuf.CommunityDescription { | ||
return &protobuf.CommunityDescription{ | ||
IntroMessage: "one of not encrypted fields", | ||
Members: map[string]*protobuf.CommunityMember{ | ||
"a": &protobuf.CommunityMember{}, | ||
"b": &protobuf.CommunityMember{}, | ||
}, | ||
Chats: map[string]*protobuf.CommunityChat{ | ||
"c": &protobuf.CommunityChat{}, | ||
"d": &protobuf.CommunityChat{}, | ||
}, | ||
PrivateData: map[string][]byte{}, | ||
} | ||
} | ||
|
||
func (s *CommunityEncryptionDescriptionSuite) TestEncryptionDecryption() { | ||
description := s.description() | ||
|
||
err := encryptDescription(s.descriptionEncryptor, &Community{}, description) | ||
s.Require().NoError(err) | ||
s.Require().Len(description.PrivateData, 1) | ||
|
||
// 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 | ||
err = decryptDescription(s.descriptionEncryptor, description, s.logger) | ||
s.Require().NoError(err) | ||
s.Require().Len(description.Members, 2) | ||
s.Require().Len(description.Chats, 2) | ||
s.Require().Equal(description.IntroMessage, "one of not encrypted fields") | ||
} | ||
|
||
func (s *CommunityEncryptionDescriptionSuite) TestDecryption_NoKeys() { | ||
description := s.description() | ||
|
||
err := encryptDescription(s.descriptionEncryptor, &Community{}, description) | ||
s.Require().NoError(err) | ||
|
||
// forget the keys, so description can't be decrypted | ||
s.descriptionEncryptor.forgetKeys() | ||
|
||
// members and chats should NOT be brought back | ||
err = decryptDescription(s.descriptionEncryptor, description, s.logger) | ||
s.Require().NoError(err) | ||
s.Require().Empty(description.Members) | ||
s.Require().Empty(description.Chats) | ||
s.Require().Equal(description.IntroMessage, "one of not encrypted fields") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.