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.master
parent
270350cd34
commit
5d0ff961b2
|
@ -47,9 +47,16 @@ pub enum OlmError {
|
||||||
Store(#[from] CryptoStoreError),
|
Store(#[from] CryptoStoreError),
|
||||||
|
|
||||||
/// The session with a device has become corrupted.
|
/// 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),
|
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
|
/// Encryption failed because the device does not have a valid Olm session
|
||||||
/// with us.
|
/// with us.
|
||||||
#[error(
|
#[error(
|
||||||
|
|
|
@ -131,7 +131,10 @@ impl Account {
|
||||||
.chain(&[message_type])
|
.chain(&[message_type])
|
||||||
.chain(&ciphertext.body);
|
.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.
|
// Create a OlmMessage from the ciphertext and the type.
|
||||||
let message =
|
let message =
|
||||||
|
@ -139,18 +142,26 @@ impl Account {
|
||||||
.map_err(|_| EventError::UnsupportedOlmType)?;
|
.map_err(|_| EventError::UnsupportedOlmType)?;
|
||||||
|
|
||||||
// Decrypt the OlmMessage and get a Ruma event out of it.
|
// 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)
|
.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);
|
debug!("Decrypted a to-device event {:?}", event);
|
||||||
|
|
||||||
Ok(OlmDecryptionInfo {
|
Ok(OlmDecryptionInfo {
|
||||||
session,
|
session,
|
||||||
message_hash: OlmMessageHash {
|
message_hash,
|
||||||
hash,
|
|
||||||
sender_key: content.sender_key.clone(),
|
|
||||||
},
|
|
||||||
event,
|
event,
|
||||||
signing_key,
|
signing_key,
|
||||||
sender_key: content.sender_key.clone(),
|
sender_key: content.sender_key.clone(),
|
||||||
|
|
Loading…
Reference in New Issue