Implemented testing to check rekeying is happening
This commit is contained in:
parent
1e09a4bc37
commit
2536d9c8ba
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
@ -0,0 +1 @@
|
|||
ALTER TABLE communities_communities ADD COLUMN rekeyed_at TIMESTAMP DEFAULT 0 NOT NULL;
|
Loading…
Reference in New Issue