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.
This commit is contained in:
Damir Jelić 2020-12-01 14:50:04 +01:00
parent 270350cd34
commit 5d0ff961b2
2 changed files with 26 additions and 8 deletions

View file

@ -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(

View file

@ -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(),