Delete expired keys periodically

This commit is contained in:
Tulir Asokan 2023-04-11 13:13:45 +03:00
commit 00090f6e78
5 changed files with 76 additions and 5 deletions

View file

@ -176,11 +176,12 @@ type EncryptionConfig struct {
PlaintextMentions bool `yaml:"plaintext_mentions"`
DeleteKeys struct {
DeleteOutboundOnAck bool `yaml:"delete_outbound_on_ack"`
RatchetOnDecrypt bool `yaml:"ratchet_on_decrypt"`
DeleteFullyUsedOnDecrypt bool `yamL:"delete_fully_used_on_decrypt"`
DeletePrevOnNewSession bool `yaml:"delete_prev_on_new_session"`
DeleteOnDeviceDelete bool `yaml:"delete_on_device_delete"`
DeleteOutboundOnAck bool `yaml:"delete_outbound_on_ack"`
RatchetOnDecrypt bool `yaml:"ratchet_on_decrypt"`
DeleteFullyUsedOnDecrypt bool `yamL:"delete_fully_used_on_decrypt"`
DeletePrevOnNewSession bool `yaml:"delete_prev_on_new_session"`
DeleteOnDeviceDelete bool `yaml:"delete_on_device_delete"`
PeriodicallyDeleteExpired bool `yaml:"periodically_delete_expired"`
} `yaml:"delete_keys"`
VerificationLevels struct {

View file

@ -45,6 +45,8 @@ type CryptoHelper struct {
lock sync.RWMutex
syncDone sync.WaitGroup
cancelSync func()
cancelPeriodicDeleteLoop func()
}
func NewCryptoHelper(bridge *Bridge) Crypto {
@ -100,6 +102,11 @@ func (helper *CryptoHelper) Init() error {
helper.mach.DeleteFullyUsedKeysOnDecrypt = encryptionConfig.DeleteKeys.DeleteFullyUsedOnDecrypt
helper.mach.DeletePreviousKeysOnReceive = encryptionConfig.DeleteKeys.DeletePrevOnNewSession
helper.mach.DeleteKeysOnDeviceDelete = encryptionConfig.DeleteKeys.DeleteOnDeviceDelete
if encryptionConfig.DeleteKeys.PeriodicallyDeleteExpired {
ctx, cancel := context.WithCancel(context.Background())
helper.cancelPeriodicDeleteLoop = cancel
go helper.mach.ExpiredKeyDeleteLoop(ctx)
}
helper.client.Syncer = &cryptoSyncer{helper.mach}
helper.client.Store = helper.store
@ -281,6 +288,9 @@ func (helper *CryptoHelper) Stop() {
if helper.cancelSync != nil {
helper.cancelSync()
}
if helper.cancelPeriodicDeleteLoop != nil {
helper.cancelPeriodicDeleteLoop()
}
helper.syncDone.Wait()
}

View file

@ -650,3 +650,23 @@ func (mach *OlmMachine) ShareKeys(ctx context.Context, currentOTKCount int) erro
mach.saveAccount()
return nil
}
func (mach *OlmMachine) ExpiredKeyDeleteLoop(ctx context.Context) {
log := mach.Log.With().Str("action", "redact expired sessions").Logger()
for {
sessionIDs, err := mach.CryptoStore.RedactExpiredGroupSessions()
if err != nil {
log.Err(err).Msg("Failed to redact expired megolm sessions")
} else if len(sessionIDs) > 0 {
log.Info().Strs("session_ids", stringifyArray(sessionIDs)).Msg("Redacted expired megolm sessions")
} else {
log.Debug().Msg("Didn't find any expired megolm sessions")
}
select {
case <-ctx.Done():
log.Debug().Msg("Loop stopped")
return
case <-time.After(24 * time.Hour):
}
}
}

View file

@ -373,6 +373,40 @@ func (store *SQLCryptoStore) RedactGroupSessions(roomID id.RoomID, senderKey id.
return sessionIDs, err
}
func (store *SQLCryptoStore) RedactExpiredGroupSessions() ([]id.SessionID, error) {
var query string
switch store.DB.Dialect {
case dbutil.Postgres:
query = `
UPDATE crypto_megolm_inbound_session
SET withheld_code=$1, withheld_reason=$2, session=NULL, forwarding_chains=NULL
WHERE account_id=$3 AND session IS NOT NULL AND is_scheduled=false
AND received_at IS NOT NULL and max_age IS NOT NULL
AND received_at + 2 * (max_age * interval '1 millisecond') < now()
RETURNING session_id
`
case dbutil.SQLite:
query = `
UPDATE crypto_megolm_inbound_session
SET withheld_code=$1, withheld_reason=$2, session=NULL, forwarding_chains=NULL
WHERE account_id=$3 AND session IS NOT NULL AND is_scheduled=false
AND received_at IS NOT NULL and max_age IS NOT NULL
AND unixepoch(received_at) + (2 * max_age / 1000) < unixepoch(date('now'))
RETURNING session_id
`
default:
return nil, fmt.Errorf("unsupported dialect")
}
res, err := store.DB.Query(query, event.RoomKeyWithheldBeeperRedacted, "Session redacted: expired", store.AccountID)
var sessionIDs []id.SessionID
for res.Next() {
var sessionID id.SessionID
_ = res.Scan(&sessionID)
sessionIDs = append(sessionIDs, sessionID)
}
return sessionIDs, err
}
func (store *SQLCryptoStore) PutWithheldGroupSession(content event.RoomKeyWithheldEventContent) error {
_, err := store.DB.Exec("INSERT INTO crypto_megolm_inbound_session (session_id, sender_key, room_id, withheld_code, withheld_reason, received_at, account_id) VALUES ($1, $2, $3, $4, $5, $6. $7)",
content.SessionID, content.SenderKey, content.RoomID, content.Code, content.Reason, time.Now().UTC(), store.AccountID)

View file

@ -58,6 +58,8 @@ type Store interface {
RedactGroupSession(id.RoomID, id.SenderKey, id.SessionID, string) error
// RedactGroupSessions removes the session data for all inbound Megolm sessions from a specific device and/or in a specific room.
RedactGroupSessions(id.RoomID, id.SenderKey, string) ([]id.SessionID, error)
// RedactExpiredGroupSessions removes the session data for all inbound Megolm sessions that have expired.
RedactExpiredGroupSessions() ([]id.SessionID, error)
// PutWithheldGroupSession tells the store that a specific Megolm session was withheld.
PutWithheldGroupSession(event.RoomKeyWithheldEventContent) error
// GetWithheldGroupSession gets the event content that was previously inserted with PutWithheldGroupSession.
@ -312,6 +314,10 @@ func (gs *MemoryStore) RedactGroupSessions(roomID id.RoomID, senderKey id.Sender
return sessionIDs, err
}
func (gs *MemoryStore) RedactExpiredGroupSessions() ([]id.SessionID, error) {
return nil, fmt.Errorf("not implemented")
}
func (gs *MemoryStore) getWithheldGroupSessions(roomID id.RoomID, senderKey id.SenderKey) map[id.SessionID]*event.RoomKeyWithheldEventContent {
room, ok := gs.WithheldGroupSessions[roomID]
if !ok {