From 00090f6e78187251effc43c544ac7522f2949fd4 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 11 Apr 2023 13:13:45 +0300 Subject: [PATCH] Delete expired keys periodically --- bridge/bridgeconfig/config.go | 11 ++++++----- bridge/crypto.go | 10 ++++++++++ crypto/machine.go | 20 ++++++++++++++++++++ crypto/sql_store.go | 34 ++++++++++++++++++++++++++++++++++ crypto/store.go | 6 ++++++ 5 files changed, 76 insertions(+), 5 deletions(-) diff --git a/bridge/bridgeconfig/config.go b/bridge/bridgeconfig/config.go index b7828571..c7534de7 100644 --- a/bridge/bridgeconfig/config.go +++ b/bridge/bridgeconfig/config.go @@ -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 { diff --git a/bridge/crypto.go b/bridge/crypto.go index ab26c384..61eb039a 100644 --- a/bridge/crypto.go +++ b/bridge/crypto.go @@ -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() } diff --git a/crypto/machine.go b/crypto/machine.go index f4970de6..d23e4634 100644 --- a/crypto/machine.go +++ b/crypto/machine.go @@ -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): + } + } +} diff --git a/crypto/sql_store.go b/crypto/sql_store.go index 57305d5e..faf57ec6 100644 --- a/crypto/sql_store.go +++ b/crypto/sql_store.go @@ -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) diff --git a/crypto/store.go b/crypto/store.go index 1fa89364..eda9ec5f 100644 --- a/crypto/store.go +++ b/crypto/store.go @@ -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 {