crypto: Allow decrypting message content without event id or ts

Replay attack prevention shouldn't store empty event id or ts to
database if we're decrypting without them. This may happen if we are
looking into a future delayed event for example as it doesn't yet have
those.

We still prevent doing that if we already know them meaning we have
gotten the actual event through sync as that's also when a delayed event
would move from scheduled to finalised and then it also contains those
fields.
This commit is contained in:
Toni Spets 2025-10-14 13:32:02 +03:00
commit 080ad4c0a0
3 changed files with 28 additions and 1 deletions

View file

@ -664,6 +664,20 @@ func (store *SQLCryptoStore) IsOutboundGroupSessionShared(ctx context.Context, u
// ValidateMessageIndex returns whether the given event information match the ones stored in the database
// for the given sender key, session ID and index. If the index hasn't been stored, this will store it.
func (store *SQLCryptoStore) ValidateMessageIndex(ctx context.Context, senderKey id.SenderKey, sessionID id.SessionID, eventID id.EventID, index uint, timestamp int64) (bool, error) {
if eventID == "" && timestamp == 0 {
var notOK bool
const validateEmptyQuery = `
SELECT EXISTS(SELECT 1 FROM crypto_message_index WHERE sender_key=$1 AND session_id=$2 AND "index"=$3)
`
err := store.DB.QueryRow(ctx, validateEmptyQuery, senderKey, sessionID, index).Scan(&notOK)
if notOK {
zerolog.Ctx(ctx).Debug().
Uint("message_index", index).
Msg("Rejecting event without event ID and timestamp due to already knowing them")
}
return !notOK, err
}
const validateQuery = `
INSERT INTO crypto_message_index (sender_key, session_id, "index", event_id, timestamp)
VALUES ($1, $2, $3, $4, $5)

View file

@ -525,6 +525,9 @@ func (gs *MemoryStore) ValidateMessageIndex(_ context.Context, senderKey id.Send
}
val, ok := gs.MessageIndices[key]
if !ok {
if eventID == "" && timestamp == 0 {
return true, nil
}
gs.MessageIndices[key] = messageIndexValue{
EventID: eventID,
Timestamp: timestamp,

View file

@ -75,8 +75,13 @@ func TestValidateMessageIndex(t *testing.T) {
t.Run(storeName, func(t *testing.T) {
acc := NewOlmAccount()
// Validating without event ID and timestamp before we have them should work
ok, err := store.ValidateMessageIndex(context.TODO(), acc.IdentityKey(), "sess1", "", 0, 0)
require.NoError(t, err, "Error validating message index")
assert.True(t, ok, "First message validation should be valid")
// First message should validate successfully
ok, err := store.ValidateMessageIndex(context.TODO(), acc.IdentityKey(), "sess1", "event1", 0, 1000)
ok, err = store.ValidateMessageIndex(context.TODO(), acc.IdentityKey(), "sess1", "event1", 0, 1000)
require.NoError(t, err, "Error validating message index")
assert.True(t, ok, "First message validation should be valid")
@ -94,6 +99,11 @@ func TestValidateMessageIndex(t *testing.T) {
ok, err = store.ValidateMessageIndex(context.TODO(), acc.IdentityKey(), "sess1", "event1", 0, 1000)
require.NoError(t, err, "Error validating message index")
assert.True(t, ok, "First message validation should be valid")
// Validating without event ID and timestamp must fail if we already know them
ok, err = store.ValidateMessageIndex(context.TODO(), acc.IdentityKey(), "sess1", "", 0, 0)
require.NoError(t, err, "Error validating message index")
assert.False(t, ok, "First message validation should be invalid")
})
}
}