From 5d0ff961b23c0e09ca79492832d865918804790d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 1 Dec 2020 14:50:04 +0100 Subject: [PATCH] crypto: Check the Olm message hash if we fail to decrypt an Olm message. Wether by accident (the next_batch token doesn't get stored properly) or by malicious intent (the server replays a message) an Olm encrypted to-device message may appear multiple times. This is usually fine since nothing bad happens, we don't decrypt the message and the message gets thrown away. Since the introduction of Olm session unwedging an undecryptable message leads to the creation of a new fresh Olm session. To avoid this we remember which Olm messages we already decrypted so they don't trigger an unwedging dance. --- matrix_sdk_crypto/src/error.rs | 9 ++++++++- matrix_sdk_crypto/src/olm/account.rs | 25 ++++++++++++++++++------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/matrix_sdk_crypto/src/error.rs b/matrix_sdk_crypto/src/error.rs index 6d9aacdc..24d68f1c 100644 --- a/matrix_sdk_crypto/src/error.rs +++ b/matrix_sdk_crypto/src/error.rs @@ -47,9 +47,16 @@ pub enum OlmError { Store(#[from] CryptoStoreError), /// The session with a device has become corrupted. - #[error("decryption failed likely because an Olm from {0} with sender key {1} was wedged")] + #[error( + "decryption failed likely because an Olm session from {0} with sender key {1} was wedged" + )] SessionWedged(UserId, String), + /// An Olm message got replayed while the Olm ratchet has already moved + /// forward. + #[error("decryption failed because an Olm message from {0} with sender key {1} was replayed")] + ReplayedMessage(UserId, String), + /// Encryption failed because the device does not have a valid Olm session /// with us. #[error( diff --git a/matrix_sdk_crypto/src/olm/account.rs b/matrix_sdk_crypto/src/olm/account.rs index 5fce59d5..1861c254 100644 --- a/matrix_sdk_crypto/src/olm/account.rs +++ b/matrix_sdk_crypto/src/olm/account.rs @@ -131,7 +131,10 @@ impl Account { .chain(&[message_type]) .chain(&ciphertext.body); - let hash = encode(sha.finalize().as_slice()); + let message_hash = OlmMessageHash { + sender_key: content.sender_key.clone(), + hash: encode(sha.finalize().as_slice()), + }; // Create a OlmMessage from the ciphertext and the type. let message = @@ -139,18 +142,26 @@ impl Account { .map_err(|_| EventError::UnsupportedOlmType)?; // Decrypt the OlmMessage and get a Ruma event out of it. - let (session, event, signing_key) = self + let (session, event, signing_key) = match self .decrypt_olm_message(&event.sender, &content.sender_key, message) - .await?; + .await + { + Ok(d) => d, + Err(OlmError::SessionWedged(user_id, sender_key)) => { + if self.store.is_message_known(&message_hash).await? { + return Err(OlmError::ReplayedMessage(user_id, sender_key)); + } else { + return Err(OlmError::SessionWedged(user_id, sender_key)); + } + } + Err(e) => return Err(e.into()), + }; debug!("Decrypted a to-device event {:?}", event); Ok(OlmDecryptionInfo { session, - message_hash: OlmMessageHash { - hash, - sender_key: content.sender_key.clone(), - }, + message_hash, event, signing_key, sender_key: content.sender_key.clone(),