crypto: Add variants to SenderData to represent different verification states

This commit is contained in:
Hubert Chathi
2024-08-19 18:20:34 -04:00
committed by Andy Balaam
parent 6e5601db1f
commit e01c1aecbb
9 changed files with 222 additions and 73 deletions

View File

@@ -27,6 +27,7 @@ use crate::debug::{DebugRawEvent, DebugStructExt};
const AUTHENTICITY_NOT_GUARANTEED: &str =
"The authenticity of this encrypted message can't be guaranteed on this device.";
const UNVERIFIED_IDENTITY: &str = "Encrypted by an unverified user.";
const PREVIOUSLY_VERIFIED: &str = "Encrypted by a previously-verified user.";
const UNSIGNED_DEVICE: &str = "Encrypted by a device not verified by its owner.";
const UNKNOWN_DEVICE: &str = "Encrypted by an unknown or deleted device.";
pub const SENT_IN_CLEAR: &str = "Not encrypted.";
@@ -88,12 +89,12 @@ impl VerificationState {
match self {
VerificationState::Verified => ShieldState::None,
VerificationState::Unverified(level) => match level {
VerificationLevel::UnverifiedIdentity | VerificationLevel::UnsignedDevice => {
ShieldState::Red {
code: ShieldStateCode::UnverifiedIdentity,
message: UNVERIFIED_IDENTITY,
}
}
VerificationLevel::UnverifiedIdentity
| VerificationLevel::PreviouslyVerified
| VerificationLevel::UnsignedDevice => ShieldState::Red {
code: ShieldStateCode::UnverifiedIdentity,
message: UNVERIFIED_IDENTITY,
},
VerificationLevel::None(link) => match link {
DeviceLinkProblem::MissingDevice => ShieldState::Red {
code: ShieldStateCode::UnknownDevice,
@@ -122,10 +123,16 @@ impl VerificationState {
VerificationLevel::UnverifiedIdentity => {
// If you didn't show interest in verifying that user we don't
// nag you with an error message.
// TODO: We should detect identity rotation of a previously trusted identity and
// then warn see https://github.com/matrix-org/matrix-rust-sdk/issues/1129
ShieldState::None
}
VerificationLevel::PreviouslyVerified => {
// This is a high warning. The sender was previously
// verified, but changed their identity.
ShieldState::Red {
code: ShieldStateCode::PreviouslyVerified,
message: PREVIOUSLY_VERIFIED,
}
}
VerificationLevel::UnsignedDevice => {
// This is a high warning. The sender hasn't verified his own device.
ShieldState::Red {
@@ -164,6 +171,10 @@ pub enum VerificationLevel {
/// The message was sent by a user identity we have not verified.
UnverifiedIdentity,
/// The message was sent by a user identity we have not verified, but the
/// user was previously verified.
PreviouslyVerified,
/// The message was sent by a device not linked to (signed by) any user
/// identity.
UnsignedDevice,
@@ -227,6 +238,8 @@ pub enum ShieldStateCode {
UnverifiedIdentity,
/// An unencrypted event in an encrypted room.
SentInClear,
/// The sender was previously verified but changed their identity.
PreviouslyVerified,
}
/// The algorithm specific information of a decrypted event.

View File

@@ -32,16 +32,25 @@ Changes:
Breaking changes:
**NOTE**: this version causes changes to the format of the serialised data in
the CryptoStore, meaning that, once upgraded, it will not be possible to roll
back applications to earlier versions without breaking user sessions.
- Change the structure of the `SenderData` enum to separate variants for
previously-verified, unverified and verified.
([#3877](https://github.com/matrix-org/matrix-rust-sdk/pull/3877))
- Where `EncryptionInfo` is returned it may include the new `PreviouslyVerified`
variant of `VerificationLevel` to indicate that the user was previously
verified and is no longer verified.
([#3877](https://github.com/matrix-org/matrix-rust-sdk/pull/3877))
- Expose new methods `OwnUserIdentity::was_previously_verified`,
`OwnUserIdentity::withdraw_verification`, and
`OwnUserIdentity::has_verification_violation`, which track whether our own
identity was previously verified.
([#3846](https://github.com/matrix-org/matrix-rust-sdk/pull/3846))
**NOTE**: this causes changes to the format of the serialised data in the
CryptoStore, meaning that, once upgraded, it will not be possible to roll
back applications to earlier versions without breaking user sessions.
- Add a new `error_on_verified_user_problem` property to
`CollectStrategy::DeviceBasedStrategy`, which, when set, causes
`OlmMachine::share_room_key` to fail with an error if any verified users on

View File

@@ -66,8 +66,8 @@ use crate::{
identities::{user::UserIdentities, Device, IdentityManager, UserDevices},
olm::{
Account, CrossSigningStatus, EncryptionSettings, IdentityKeys, InboundGroupSession,
OlmDecryptionInfo, PrivateCrossSigningIdentity, SenderData, SenderDataFinder, SessionType,
StaticAccountData,
KnownSenderData, OlmDecryptionInfo, PrivateCrossSigningIdentity, SenderData,
SenderDataFinder, SessionType, StaticAccountData,
},
requests::{IncomingResponse, OutgoingRequest, UploadSigningKeysRequest},
session_manager::{GroupSessionManager, SessionManager},
@@ -2359,10 +2359,13 @@ fn sender_data_to_verification_state(
VerificationState::Unverified(VerificationLevel::UnsignedDevice),
Some(device_keys.device_id),
),
SenderData::SenderKnown { master_key_verified: false, device_id, .. } => {
SenderData::SenderUnverifiedButPreviouslyVerified(KnownSenderData {
device_id, ..
}) => (VerificationState::Unverified(VerificationLevel::PreviouslyVerified), device_id),
SenderData::SenderUnverified(KnownSenderData { device_id, .. }) => {
(VerificationState::Unverified(VerificationLevel::UnverifiedIdentity), device_id)
}
SenderData::SenderKnown { master_key_verified: true, device_id, .. } => {
SenderData::SenderVerified(KnownSenderData { device_id, .. }) => {
(VerificationState::Verified, device_id)
}
}

View File

@@ -187,7 +187,7 @@ async fn test_decryption_verification_state() {
// And the updated SenderData should have been saved into the store.
let session = load_session(&bob, room_id, &event).await.unwrap().unwrap();
assert_let!(SenderData::SenderKnown { .. } = session.sender_data);
assert_let!(SenderData::SenderVerified { .. } = session.sender_data);
// Simulate an imported session, to change verification state
let imported = InboundGroupSession::from_export(&export).unwrap();

View File

@@ -25,7 +25,7 @@ pub(crate) use outbound::ShareState;
pub use outbound::{
EncryptionSettings, OutboundGroupSession, PickledOutboundGroupSession, ShareInfo,
};
pub use sender_data::SenderData;
pub use sender_data::{KnownSenderData, SenderData};
pub(crate) use sender_data_finder::SenderDataFinder;
use thiserror::Error;
pub use vodozemac::megolm::{ExportedSessionKey, SessionKey};

View File

@@ -20,12 +20,32 @@ use vodozemac::Ed25519PublicKey;
use crate::types::DeviceKeys;
/// Information about the sender of a megolm session where we know the
/// cross-signing identity of the sender.
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct KnownSenderData {
/// The user ID of the user who established this session.
pub user_id: OwnedUserId,
/// The device ID of the device that send the session.
/// This is an `Option` for backwards compatibility, but we should always
/// populate it on creation.
pub device_id: Option<OwnedDeviceId>,
/// The cross-signing key of the user who established this session.
pub master_key: Box<Ed25519PublicKey>,
}
/// Information on the device and user that sent the megolm session data to us
///
/// Sessions start off in `UnknownDevice` state, and progress into `DeviceInfo`
/// state when we get the device info. Finally, if we can look up the sender
/// using the device info, the session can be moved into `SenderKnown` state.
/// using the device info, the session can be moved into
/// `SenderUnverifiedButPreviouslyVerified`, `SenderUnverified`, or
/// `SenderVerified` state, depending on the verification status of the user.
/// If the user's verification state changes, the state may change accordingly.
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(from = "SenderDataReader")]
pub enum SenderData {
/// We have not yet found the (signed) device info for the sending device,
/// or we did find a device but it does not own the session.
@@ -56,25 +76,20 @@ pub enum SenderData {
},
/// We have found proof that this user, with this cross-signing key, sent
/// the to-device message that established this session.
SenderKnown {
/// The user ID of the user who established this session.
user_id: OwnedUserId,
/// the to-device message that established this session, but we have not yet
/// verified the cross-signing key, and we had verified a previous
/// cross-signing key for this user.
SenderUnverifiedButPreviouslyVerified(KnownSenderData),
/// The device ID of the device that send the session.
/// This is an `Option` for backwards compatibility, but we should
/// always populate it on creation.
device_id: Option<OwnedDeviceId>,
/// We have found proof that this user, with this cross-signing key, sent
/// the to-device message that established this session, but we have not yet
/// verified the cross-signing key.
SenderUnverified(KnownSenderData),
/// The cross-signing key of the user who established this session.
master_key: Box<Ed25519PublicKey>,
/// Whether, at the time we checked the signature on the device,
/// we had actively verified that `master_key` belongs to the user.
/// If false, we had simply accepted the key as this user's latest
/// key.
master_key_verified: bool,
},
/// We have found proof that this user, with this cross-signing key, sent
/// the to-device message that established this session, and we have
/// verified the cross-signing key.
SenderVerified(KnownSenderData),
}
impl SenderData {
@@ -102,19 +117,44 @@ impl SenderData {
}
}
/// Create a [`SenderData`] with a known sender.
pub fn sender_known(
/// Create a [`SenderData`] with a known but unverified sender, where the
/// sender was previously verified.
pub fn sender_previously_verified(
user_id: &UserId,
device_id: &DeviceId,
master_key: Ed25519PublicKey,
master_key_verified: bool,
) -> Self {
Self::SenderKnown {
Self::SenderUnverifiedButPreviouslyVerified(KnownSenderData {
user_id: user_id.to_owned(),
device_id: Some(device_id.to_owned()),
master_key: Box::new(master_key),
master_key_verified,
}
})
}
/// Create a [`SenderData`] with a known but unverified sender.
pub fn sender_unverified(
user_id: &UserId,
device_id: &DeviceId,
master_key: Ed25519PublicKey,
) -> Self {
Self::SenderUnverified(KnownSenderData {
user_id: user_id.to_owned(),
device_id: Some(device_id.to_owned()),
master_key: Box::new(master_key),
})
}
/// Create a [`SenderData`] with a verified sender.
pub fn sender_verified(
user_id: &UserId,
device_id: &DeviceId,
master_key: Ed25519PublicKey,
) -> Self {
Self::SenderVerified(KnownSenderData {
user_id: user_id.to_owned(),
device_id: Some(device_id.to_owned()),
master_key: Box::new(master_key),
})
}
/// Create a [`SenderData`] which has the legacy flag set. Caution: messages
@@ -128,7 +168,9 @@ impl SenderData {
/// Returns true if this is SenderKnown and `master_key_verified` is true.
pub(crate) fn is_known(&self) -> bool {
matches!(self, SenderData::SenderKnown { .. })
matches!(self, SenderData::SenderUnverifiedButPreviouslyVerified { .. })
|| matches!(self, SenderData::SenderUnverified { .. })
|| matches!(self, SenderData::SenderVerified { .. })
}
/// Returns `Greater` if this `SenderData` represents a greater level of
@@ -151,8 +193,9 @@ impl SenderData {
match self {
SenderData::UnknownDevice { .. } => 0,
SenderData::DeviceInfo { .. } => 1,
SenderData::SenderKnown { master_key_verified: false, .. } => 2,
SenderData::SenderKnown { master_key_verified: true, .. } => 3,
SenderData::SenderUnverifiedButPreviouslyVerified(..) => 2,
SenderData::SenderUnverified(..) => 3,
SenderData::SenderVerified(..) => 4,
}
}
}
@@ -169,6 +212,67 @@ impl Default for SenderData {
}
}
/// Deserialisation type, to handle conversion from older formats
#[derive(Deserialize)]
enum SenderDataReader {
UnknownDevice {
legacy_session: bool,
#[serde(default)]
owner_check_failed: bool,
},
DeviceInfo {
device_keys: DeviceKeys,
legacy_session: bool,
},
SenderUnverifiedButPreviouslyVerified(KnownSenderData),
SenderUnverified(KnownSenderData),
SenderVerified(KnownSenderData),
// If we read this older variant, it gets changed to SenderUnverified or
// SenderVerified, depending on the master_key_verified flag.
SenderKnown {
user_id: OwnedUserId,
device_id: Option<OwnedDeviceId>,
master_key: Box<Ed25519PublicKey>,
master_key_verified: bool,
},
}
impl From<SenderDataReader> for SenderData {
fn from(data: SenderDataReader) -> Self {
match data {
SenderDataReader::UnknownDevice { legacy_session, owner_check_failed } => {
Self::UnknownDevice { legacy_session, owner_check_failed }
}
SenderDataReader::DeviceInfo { device_keys, legacy_session } => {
Self::DeviceInfo { device_keys, legacy_session }
}
SenderDataReader::SenderUnverifiedButPreviouslyVerified(data) => {
Self::SenderUnverifiedButPreviouslyVerified(data)
}
SenderDataReader::SenderUnverified(data) => Self::SenderUnverified(data),
SenderDataReader::SenderVerified(data) => Self::SenderVerified(data),
SenderDataReader::SenderKnown {
user_id,
device_id,
master_key,
master_key_verified,
} => {
let known_sender_data = KnownSenderData { user_id, device_id, master_key };
if master_key_verified {
Self::SenderVerified(known_sender_data)
} else {
Self::SenderUnverified(known_sender_data)
}
}
}
}
}
#[cfg(test)]
mod tests {
use std::{cmp::Ordering, collections::BTreeMap};
@@ -249,7 +353,7 @@ mod tests {
"#;
let end: SenderData = serde_json::from_str(json).expect("Failed to parse!");
assert_let!(SenderData::SenderKnown { .. } = end);
assert_let!(SenderData::SenderVerified { .. } = end);
}
#[test]
@@ -259,8 +363,10 @@ mod tests {
let master_key =
Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap();
assert!(SenderData::sender_known(user_id, device_id, master_key, true).is_known());
assert!(SenderData::sender_known(user_id, device_id, master_key, false).is_known());
assert!(!SenderData::unknown().is_known());
assert!(SenderData::sender_previously_verified(user_id, device_id, master_key).is_known());
assert!(SenderData::sender_unverified(user_id, device_id, master_key).is_known());
assert!(SenderData::sender_verified(user_id, device_id, master_key).is_known());
}
#[test]
@@ -291,9 +397,9 @@ mod tests {
let master_key =
Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap();
let sender_unverified =
SenderData::sender_known(user_id!("@u:s.co"), device_id!("DEV"), master_key, false);
SenderData::sender_unverified(user_id!("@u:s.co"), device_id!("DEV"), master_key);
let sender_verified =
SenderData::sender_known(user_id!("@u:s.co"), device_id!("DEV"), master_key, true);
SenderData::sender_verified(user_id!("@u:s.co"), device_id!("DEV"), master_key);
assert_eq!(unknown.compare_trust_level(&unknown), Ordering::Equal);
assert_eq!(device_keys.compare_trust_level(&device_keys), Ordering::Equal);
@@ -313,23 +419,39 @@ mod tests {
));
let master_key =
Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap();
let sender_previously_verified = SenderData::sender_previously_verified(
user_id!("@u:s.co"),
device_id!("DEV"),
master_key,
);
let sender_unverified =
SenderData::sender_known(user_id!("@u:s.co"), device_id!("DEV"), master_key, false);
SenderData::sender_unverified(user_id!("@u:s.co"), device_id!("DEV"), master_key);
let sender_verified =
SenderData::sender_known(user_id!("@u:s.co"), device_id!("DEV"), master_key, true);
SenderData::sender_verified(user_id!("@u:s.co"), device_id!("DEV"), master_key);
assert_eq!(unknown.compare_trust_level(&device_keys), Ordering::Less);
assert_eq!(unknown.compare_trust_level(&sender_previously_verified), Ordering::Less);
assert_eq!(unknown.compare_trust_level(&sender_unverified), Ordering::Less);
assert_eq!(unknown.compare_trust_level(&sender_verified), Ordering::Less);
assert_eq!(device_keys.compare_trust_level(&unknown), Ordering::Greater);
assert_eq!(sender_previously_verified.compare_trust_level(&unknown), Ordering::Greater);
assert_eq!(sender_unverified.compare_trust_level(&unknown), Ordering::Greater);
assert_eq!(sender_verified.compare_trust_level(&unknown), Ordering::Greater);
assert_eq!(device_keys.compare_trust_level(&sender_unverified), Ordering::Less);
assert_eq!(device_keys.compare_trust_level(&sender_verified), Ordering::Less);
assert_eq!(sender_previously_verified.compare_trust_level(&device_keys), Ordering::Greater);
assert_eq!(sender_unverified.compare_trust_level(&device_keys), Ordering::Greater);
assert_eq!(sender_verified.compare_trust_level(&device_keys), Ordering::Greater);
assert_eq!(
sender_previously_verified.compare_trust_level(&sender_verified),
Ordering::Less
);
assert_eq!(
sender_previously_verified.compare_trust_level(&sender_unverified),
Ordering::Less
);
assert_eq!(sender_unverified.compare_trust_level(&sender_verified), Ordering::Less);
assert_eq!(sender_verified.compare_trust_level(&sender_unverified), Ordering::Greater);
}

View File

@@ -16,7 +16,7 @@ use ruma::UserId;
use tracing::error;
use vodozemac::Curve25519PublicKey;
use super::{InboundGroupSession, SenderData};
use super::{InboundGroupSession, KnownSenderData, SenderData};
use crate::{
error::MismatchedIdentityKeysError,
store::Store,
@@ -269,8 +269,18 @@ impl<'a> SenderDataFinder<'a> {
if let Some(master_key) = master_key {
// We have user_id and master_key for the user sending the to-device message.
let master_key = Box::new(master_key);
let master_key_verified = sender_device.is_cross_signing_trusted();
SenderData::SenderKnown { user_id, device_id, master_key, master_key_verified }
let known_sender_data = KnownSenderData { user_id, device_id, master_key };
if sender_device.is_cross_signing_trusted() {
SenderData::SenderVerified(known_sender_data)
} else if sender_device
.device_owner_identity
.expect("User with master key must have identity")
.was_previously_verified()
{
SenderData::SenderUnverifiedButPreviouslyVerified(known_sender_data)
} else {
SenderData::SenderUnverified(known_sender_data)
}
} else {
// Surprisingly, there was no key in the MasterPubkey. We did not expect this:
// treat it as if the device was not signed by this master key.
@@ -384,7 +394,7 @@ mod tests {
error::MismatchedIdentityKeysError,
olm::{
group_sessions::sender_data_finder::SessionDeviceKeysCheckError, InboundGroupSession,
PrivateCrossSigningIdentity, SenderData,
KnownSenderData, PrivateCrossSigningIdentity, SenderData,
},
store::{Changes, CryptoStoreWrapper, MemoryStore, Store},
types::{
@@ -520,13 +530,12 @@ mod tests {
// Then we get back the information about the sender
assert_let!(
SenderData::SenderKnown { user_id, device_id, master_key, master_key_verified } =
SenderData::SenderUnverified(KnownSenderData { user_id, device_id, master_key }) =
sender_data
);
assert_eq!(user_id, setup.sender.user_id);
assert_eq!(device_id.unwrap(), setup.sender_device.device_id());
assert_eq!(*master_key, setup.sender_master_key());
assert!(!master_key_verified);
}
#[async_test]
@@ -549,13 +558,12 @@ mod tests {
// Then we get back the information about the sender
assert_let!(
SenderData::SenderKnown { user_id, device_id, master_key, master_key_verified } =
SenderData::SenderUnverified(KnownSenderData { user_id, device_id, master_key }) =
sender_data
);
assert_eq!(user_id, setup.sender.user_id);
assert_eq!(device_id.unwrap(), setup.sender_device.device_id());
assert_eq!(*master_key, setup.sender_master_key());
assert!(!master_key_verified);
}
#[async_test]
@@ -579,13 +587,12 @@ mod tests {
// Then we get back the information about the sender
assert_let!(
SenderData::SenderKnown { user_id, device_id, master_key, master_key_verified } =
SenderData::SenderUnverified(KnownSenderData { user_id, device_id, master_key }) =
sender_data
);
assert_eq!(user_id, setup.sender.user_id);
assert_eq!(device_id.unwrap(), setup.sender_device.device_id());
assert_eq!(*master_key, setup.sender_master_key());
assert!(!master_key_verified);
}
#[async_test]
@@ -608,13 +615,12 @@ mod tests {
// Then we get back the information about the sender
assert_let!(
SenderData::SenderKnown { user_id, device_id, master_key, master_key_verified } =
SenderData::SenderUnverified(KnownSenderData { user_id, device_id, master_key }) =
sender_data
);
assert_eq!(user_id, setup.sender.user_id);
assert_eq!(device_id.unwrap(), setup.sender_device.device_id());
assert_eq!(*master_key, setup.sender_master_key());
assert!(!master_key_verified);
}
#[async_test]
@@ -712,14 +718,12 @@ mod tests {
// Then we get back the information about the sender
assert_let!(
SenderData::SenderKnown { user_id, device_id, master_key, master_key_verified } =
SenderData::SenderVerified(KnownSenderData { user_id, device_id, master_key }) =
sender_data
);
assert_eq!(user_id, setup.sender.user_id);
assert_eq!(device_id.unwrap(), setup.sender_device.device_id());
assert_eq!(*master_key, setup.sender_master_key());
// Including the fact that it was verified
assert!(master_key_verified);
}
#[async_test]
@@ -745,14 +749,12 @@ mod tests {
// Then we get back the information about the sender
assert_let!(
SenderData::SenderKnown { user_id, device_id, master_key, master_key_verified } =
SenderData::SenderVerified(KnownSenderData { user_id, device_id, master_key }) =
sender_data
);
assert_eq!(user_id, setup.sender.user_id);
assert_eq!(device_id.unwrap(), setup.sender_device.device_id());
assert_eq!(*master_key, setup.sender_master_key());
// Including the fact that it was verified
assert!(master_key_verified);
}
#[async_test]
@@ -769,13 +771,12 @@ mod tests {
// Then it is found using the device we supplied
assert_let!(
SenderData::SenderKnown { user_id, device_id, master_key, master_key_verified } =
SenderData::SenderUnverified(KnownSenderData { user_id, device_id, master_key }) =
sender_data
);
assert_eq!(user_id, setup.sender.user_id);
assert_eq!(device_id.unwrap(), setup.sender_device.device_id());
assert_eq!(*master_key, setup.sender_master_key());
assert!(!master_key_verified);
}
struct TestOptions {

View File

@@ -26,7 +26,7 @@ mod utility;
pub use account::{Account, OlmMessageHash, PickledAccount, StaticAccountData};
pub(crate) use account::{OlmDecryptionInfo, SessionType};
pub use group_sessions::{
BackedUpRoomKey, EncryptionSettings, ExportedRoomKey, InboundGroupSession,
BackedUpRoomKey, EncryptionSettings, ExportedRoomKey, InboundGroupSession, KnownSenderData,
OutboundGroupSession, PickledInboundGroupSession, PickledOutboundGroupSession, SenderData,
SessionCreationError, SessionExportError, SessionKey, ShareInfo,
};

View File

@@ -2,6 +2,7 @@
Breaking changes:
- Add a `PreviouslyVerified` variant to `VerificationLevel` indicating that the identity is unverified and previously it was verified.
- Replace the `Notification` type from Ruma in `SyncResponse` and `Client::register_notification_handler`
by a custom one
- `Room::can_user_redact` and `Member::can_redact` are split between `*_redact_own` and `*_redact_other`