Implemented testing to check rekeying is happening

This commit is contained in:
Samuel Hawksby-Robinson 2023-05-24 15:25:10 +01:00
parent 1e09a4bc37
commit 2536d9c8ba
9 changed files with 143 additions and 431 deletions

View File

@ -1 +1 @@
0.162.6
0.162.7

View File

@ -1041,3 +1041,8 @@ func (s *MessageSender) StartDatasync() {
s.datasync = ds
}
// GetKeyIDsForGroup returns a slice of key IDs belonging to a given group ID
func (s *MessageSender) GetKeyIDsForGroup(groupID []byte) ([]uint32, error) {
return s.protocol.GetKeyIDsForGroup(groupID)
}

View File

@ -84,7 +84,7 @@ type Manager struct {
torrentTasks map[string]metainfo.Hash
historyArchiveDownloadTasks map[string]*HistoryArchiveDownloadTask
stopped bool
RekeyInterval *time.Duration
RekeyInterval time.Duration
}
type HistoryArchiveDownloadTask struct {

View File

@ -481,7 +481,7 @@ func (s *PersistenceSuite) TestGetRekeyedAtClock() {
s.Require().NoError(err)
s.Require().Len(communities, 1)
community := Community{
community := &Community{
config: &Config{
PrivateKey: key,
ID: &key.PublicKey,
@ -491,22 +491,23 @@ func (s *PersistenceSuite) TestGetRekeyedAtClock() {
CommunityDescription: &protobuf.CommunityDescription{},
},
}
s.Require().NoError(s.db.SaveCommunity(&community))
s.Require().NoError(s.db.SaveCommunity(community))
communities, err = s.db.AllCommunities(&key.PublicKey)
c := communities[1]
s.Require().NoError(err)
s.Require().Len(communities, 2)
s.Equal(types.HexBytes(crypto.CompressPubkey(&key.PublicKey)), communities[1].ID())
s.True(communities[1].Joined())
s.True(communities[1].Spectated())
s.True(communities[1].Verified())
s.Zero(communities[1].config.RekeyedAt.Unix())
s.Equal(types.HexBytes(crypto.CompressPubkey(&key.PublicKey)), c.ID())
s.True(c.Joined())
s.True(c.Spectated())
s.True(c.Verified())
s.Zero(c.config.RekeyedAt.Unix())
now := time.Now()
err = s.db.SetRekeyedAtClock(communities[1].ID(), now)
s.NoError(err)
err = s.db.SetRekeyedAtClock(c.ID(), now)
s.Require().NoError(err)
then, err := s.db.GetRekeyedAtClock(communities[0].ID())
s.NoError(err)
now.Equal(then)
then, err := s.db.GetRekeyedAtClock(c.ID())
s.Require().NoError(err)
s.Require().True(now.Equal(then))
}

View File

@ -60,6 +60,9 @@ func (s *MessengerCommunitiesSuite) SetupTest() {
s.admin = s.newMessenger()
s.bob = s.newMessenger()
s.alice = s.newMessenger()
s.admin.communitiesManager.RekeyInterval = 50 * time.Millisecond
_, err := s.admin.Start()
s.Require().NoError(err)
_, err = s.bob.Start()
@ -3422,3 +3425,68 @@ func (s *MessengerCommunitiesSuite) TestGetCommunityIdFromKey() {
communityID = s.bob.GetCommunityIDFromKey(privateKey)
s.Require().Equal(communityID, publicKey)
}
func (s *MessengerCommunitiesSuite) TestStartCommunityRekeyLoop() {
// Create a new community
response, err := s.admin.CreateCommunity(
&requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP,
Name: "status",
Color: "#57a7e5",
Description: "status community description",
},
true,
)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
// Check community is present in the DB and has default values we care about
c, err := s.admin.GetCommunityByID(response.Communities()[0].ID())
s.Require().NoError(err)
s.Require().False(c.Encrypted())
s.Require().Zero(c.RekeyedAt().Unix())
// Update the community to use encryption and check the values
err = s.admin.UpdateCommunityEncryption(c, true)
s.Require().NoError(err)
c, err = s.admin.GetCommunityByID(c.ID())
s.Require().NoError(err)
s.Require().True(c.Encrypted())
// Add Alice and Bob to the community
response, err = s.admin.InviteUsersToCommunity(
&requests.InviteUsersToCommunity{
CommunityID: c.ID(),
Users: []types.HexBytes{
common.PubkeyToHexBytes(&s.alice.identity.PublicKey),
common.PubkeyToHexBytes(&s.bob.identity.PublicKey),
},
},
)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
// Check the Alice and Bob are members of the community
c, err = s.admin.GetCommunityByID(c.ID())
s.Require().NoError(err)
s.Require().True(c.HasMember(&s.alice.identity.PublicKey))
s.Require().True(c.HasMember(&s.bob.identity.PublicKey))
// Check the keys in the database
keys, err := s.admin.sender.GetKeyIDsForGroup(c.ID())
s.Require().NoError(err)
keyCount := len(keys)
// Check that rekeying is occurring by counting the number of keyIDs in the encryptor's DB
// This test could be flaky, as the rekey function may not be finished before RekeyInterval * 2 has passed
for i := 0; i < 5; i++ {
time.Sleep(s.admin.communitiesManager.RekeyInterval * 2)
keys, err = s.admin.sender.GetKeyIDsForGroup(c.ID())
s.Require().NoError(err)
s.Require().Greater(len(keys), keyCount)
keyCount = len(keys)
}
}

View File

@ -245,6 +245,11 @@ func (p *Protocol) GetAllHREncodedKeys(groupID []byte) ([]byte, error) {
return p.GetHREncodedKeys(groupID, keyIDs)
}
// GetKeyIDsForGroup returns a slice of key IDs belonging to a given group ID
func (p *Protocol) GetKeyIDsForGroup(groupID []byte) ([]uint32, error) {
return p.encryptor.persistence.GetKeyIDsForGroup(groupID)
}
func (p *Protocol) GetHREncodedKeys(groupID []byte, keyIDs []uint32) ([]byte, error) {
keys := &HRKeys{}
for _, keyID := range keyIDs {

View File

@ -5,7 +5,6 @@ import (
"crypto/ecdsa"
"encoding/json"
"errors"
_errors "errors"
"fmt"
"os"
"strings"
@ -237,7 +236,8 @@ func (m *Messenger) handleCommunitiesSubscription(c chan *communities.Subscripti
}
if sub.MemberPermissionsCheckedSignal != nil {
err := m.UpdateCommunityEncryption(sub.Community)
becomeMemberPermissions := sub.Community.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_MEMBER)
err := m.UpdateCommunityEncryption(sub.Community, len(becomeMemberPermissions) > 0)
if err != nil {
m.logger.Warn("failed to update community encryption", zap.Error(err))
}
@ -3315,7 +3315,7 @@ func (m *Messenger) RequestImportDiscordCommunity(request *requests.ImportDiscor
if err != nil {
m.cleanUpImport(communityID)
errmsg := err.Error()
if _errors.Is(err, communities.ErrInvalidCommunityDescriptionDuplicatedName) {
if errors.Is(err, communities.ErrInvalidCommunityDescriptionDuplicatedName) {
errmsg = fmt.Sprintf("Couldn't create channel '%s': %s", communityChat.Identity.DisplayName, err.Error())
}
importProgress.AddTaskError(discord.ChannelsCreationTask, discord.Error(errmsg))
@ -4081,26 +4081,25 @@ func (m *Messenger) UpdateCommunityTokenSupply(chainID int, contractAddress stri
return m.communitiesManager.UpdateCommunityTokenSupply(chainID, contractAddress, supply)
}
// UpdateCommunityEncryption takes a community and encrypts / decrypts the community
// based on TokenPermission. Community is republished along with any keys if needed.
// UpdateCommunityEncryption takes a communityID string and an encryption state, then finds the community and
// encrypts / decrypts the community. Community is republished along with any keys if needed.
//
// Note: This function cannot decrypt previously encrypted messages, and it cannot encrypt previous unencrypted messages.
// This functionality introduces some race conditions:
// - community description is processed by members before the receiving the key exchange messages
// - members maybe sending encrypted messages after the community description is updated and a new member joins
func (m *Messenger) UpdateCommunityEncryption(community *communities.Community) error {
func (m *Messenger) UpdateCommunityEncryption(community *communities.Community, useEncryption bool) error {
if community == nil {
return errors.New("community is nil")
}
becomeMemberPermissions := community.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_MEMBER)
isEncrypted := len(becomeMemberPermissions) > 0
if community.Encrypted() == isEncrypted {
// Check isEncrypted is different to Community's value
// If not different return
if community.Encrypted() == useEncryption {
return nil
}
if isEncrypted {
if useEncryption {
// 🪄 The magic that encrypts a community
_, err := m.encryptor.GenerateHashRatchetKey(community.ID())
if err != nil {
@ -4116,7 +4115,7 @@ func (m *Messenger) UpdateCommunityEncryption(community *communities.Community)
// 🧙 There is no magic that decrypts a community, we just need to tell everyone to not use encryption
// Republish the community.
community.SetEncrypted(isEncrypted)
community.SetEncrypted(useEncryption)
err := m.communitiesManager.UpdateCommunity(community)
if err != nil {
return err
@ -4229,7 +4228,14 @@ func chunkAttachmentsByByteSize(slice []*protobuf.DiscordMessageAttachment, maxF
func (m *Messenger) startCommunityRekeyLoop() {
logger := m.logger.Named("CommunityRekeyLoop")
ticker := time.NewTicker(5 * time.Minute)
var d time.Duration
if m.communitiesManager.RekeyInterval != 0 {
d = m.communitiesManager.RekeyInterval / 10
} else {
d = 5 * time.Minute
}
ticker := time.NewTicker(d)
go func() {
for {
select {
@ -4256,10 +4262,10 @@ func (m *Messenger) rekeyAllCommunities(logger *zap.Logger) {
// default to one hour
// TODO in future perhaps have a community level rki rather than a global rki
var rki time.Duration
if m.communitiesManager.RekeyInterval == nil {
if m.communitiesManager.RekeyInterval == 0 {
rki = time.Hour
} else {
rki = *m.communitiesManager.RekeyInterval
rki = m.communitiesManager.RekeyInterval
}
// Get and loop over all communities in persistence

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
ALTER TABLE communities_communities ADD COLUMN rekeyed_at TIMESTAMP DEFAULT 0 NOT NULL;