use std::{cmp::Ordering, fmt};
use ruma::{DeviceId, OwnedDeviceId, OwnedUserId, UserId};
use serde::{de, de::Visitor, Deserialize, Deserializer, Serialize};
use tracing::error;
use vodozemac::Ed25519PublicKey;
use crate::{
types::{serialize_ed25519_key, DeviceKeys},
Device,
};
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct KnownSenderData {
pub user_id: OwnedUserId,
pub device_id: Option<OwnedDeviceId>,
#[serde(
serialize_with = "serialize_ed25519_key",
deserialize_with = "deserialize_sender_msk_base64_or_array"
)]
pub master_key: Box<Ed25519PublicKey>,
}
pub(crate) fn deserialize_sender_msk_base64_or_array<'de, D>(
de: D,
) -> Result<Box<Ed25519PublicKey>, D::Error>
where
D: Deserializer<'de>,
{
struct KeyVisitor;
impl<'de> Visitor<'de> for KeyVisitor {
type Value = Box<Ed25519PublicKey>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "a base64 string or an array of 32 bytes")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
let decoded = Ed25519PublicKey::from_base64(v)
.map_err(|_| de::Error::custom("Base64 decoding error"))?;
Ok(Box::new(decoded))
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: de::SeqAccess<'de>,
{
let mut buf = [0u8; Ed25519PublicKey::LENGTH];
for (i, item) in buf.iter_mut().enumerate() {
*item = seq.next_element()?.ok_or_else(|| de::Error::invalid_length(i, &self))?;
}
let key = Ed25519PublicKey::from_slice(&buf).map_err(|e| de::Error::custom(&e))?;
Ok(Box::new(key))
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
if v.len() == Ed25519PublicKey::LENGTH {
let mut buf = [0u8; Ed25519PublicKey::LENGTH];
buf.copy_from_slice(v);
let key = Ed25519PublicKey::from_slice(&buf).map_err(|e| de::Error::custom(&e))?;
Ok(Box::new(key))
} else {
Err(de::Error::invalid_length(v.len(), &self))
}
}
}
de.deserialize_any(KeyVisitor)
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(from = "SenderDataReader")]
pub enum SenderData {
UnknownDevice {
legacy_session: bool,
#[serde(skip_serializing_if = "std::ops::Not::not")]
#[serde(default)]
owner_check_failed: bool,
},
DeviceInfo {
device_keys: DeviceKeys,
legacy_session: bool,
},
VerificationViolation(KnownSenderData),
SenderUnverified(KnownSenderData),
SenderVerified(KnownSenderData),
}
impl SenderData {
pub fn should_recalculate(&self) -> bool {
matches!(
self,
SenderData::UnknownDevice { .. }
| SenderData::DeviceInfo { .. }
| SenderData::VerificationViolation { .. }
)
}
pub fn unknown() -> Self {
Self::UnknownDevice { legacy_session: false, owner_check_failed: false }
}
pub fn device_info(device_keys: DeviceKeys) -> Self {
Self::DeviceInfo { device_keys, legacy_session: false }
}
pub fn sender_verification_violation(
user_id: &UserId,
device_id: &DeviceId,
master_key: Ed25519PublicKey,
) -> Self {
Self::VerificationViolation(KnownSenderData {
user_id: user_id.to_owned(),
device_id: Some(device_id.to_owned()),
master_key: Box::new(master_key),
})
}
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),
})
}
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),
})
}
pub fn legacy() -> Self {
Self::UnknownDevice { legacy_session: true, owner_check_failed: false }
}
pub fn from_device(sender_device: &Device) -> Self {
let cross_signed = sender_device.is_cross_signed_by_owner();
if cross_signed {
SenderData::from_cross_signed_device(sender_device)
} else {
SenderData::device_info(sender_device.as_device_keys().clone())
}
}
fn from_cross_signed_device(sender_device: &Device) -> Self {
let user_id = sender_device.user_id().to_owned();
let device_id = Some(sender_device.device_id().to_owned());
let device_owner = sender_device.device_owner_identity.as_ref();
let master_key = device_owner.and_then(|i| i.master_key().get_first_key());
match (device_owner, master_key) {
(Some(device_owner), Some(master_key)) => {
let master_key = Box::new(master_key);
let known_sender_data = KnownSenderData { user_id, device_id, master_key };
if sender_device.is_cross_signing_trusted() {
Self::SenderVerified(known_sender_data)
} else if device_owner.was_previously_verified() {
Self::VerificationViolation(known_sender_data)
} else {
Self::SenderUnverified(known_sender_data)
}
}
(_, _) => {
error!("MasterPubkey for user {user_id} does not contain any keys!");
Self::device_info(sender_device.as_device_keys().clone())
}
}
}
pub(crate) fn compare_trust_level(&self, other: &Self) -> Ordering {
self.trust_number().cmp(&other.trust_number())
}
fn trust_number(&self) -> u8 {
match self {
SenderData::UnknownDevice { .. } => 0,
SenderData::DeviceInfo { .. } => 1,
SenderData::VerificationViolation(..) => 2,
SenderData::SenderUnverified(..) => 3,
SenderData::SenderVerified(..) => 4,
}
}
pub fn to_type(&self) -> SenderDataType {
match self {
Self::UnknownDevice { .. } => SenderDataType::UnknownDevice,
Self::DeviceInfo { .. } => SenderDataType::DeviceInfo,
Self::VerificationViolation { .. } => SenderDataType::VerificationViolation,
Self::SenderUnverified { .. } => SenderDataType::SenderUnverified,
Self::SenderVerified { .. } => SenderDataType::SenderVerified,
}
}
pub(crate) fn user_id(&self) -> Option<OwnedUserId> {
match &self {
SenderData::UnknownDevice { .. } => None,
SenderData::DeviceInfo { device_keys, .. } => Some(device_keys.user_id.clone()),
SenderData::VerificationViolation(known_sender_data) => {
Some(known_sender_data.user_id.clone())
}
SenderData::SenderUnverified(known_sender_data) => {
Some(known_sender_data.user_id.clone())
}
SenderData::SenderVerified(known_sender_data) => {
Some(known_sender_data.user_id.clone())
}
}
}
}
impl Default for SenderData {
fn default() -> Self {
Self::legacy()
}
}
#[derive(Deserialize)]
enum SenderDataReader {
UnknownDevice {
legacy_session: bool,
#[serde(default)]
owner_check_failed: bool,
},
DeviceInfo {
device_keys: DeviceKeys,
legacy_session: bool,
},
#[serde(alias = "SenderUnverifiedButPreviouslyVerified")]
VerificationViolation(KnownSenderData),
SenderUnverified(KnownSenderData),
SenderVerified(KnownSenderData),
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::VerificationViolation(data) => Self::VerificationViolation(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)
}
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub enum SenderDataType {
UnknownDevice = 1,
DeviceInfo = 2,
VerificationViolation = 3,
SenderUnverified = 4,
SenderVerified = 5,
}
#[cfg(test)]
mod tests {
use std::{cmp::Ordering, collections::BTreeMap, ops::Deref};
use assert_matches2::assert_let;
use insta::assert_json_snapshot;
use matrix_sdk_test::async_test;
use ruma::{
device_id, owned_device_id, owned_user_id, user_id, DeviceKeyAlgorithm, DeviceKeyId,
};
use serde_json::json;
use vodozemac::{base64_decode, Curve25519PublicKey, Ed25519PublicKey};
use super::SenderData;
use crate::{
machine::test_helpers::{
create_signed_device_of_unverified_user, create_signed_device_of_verified_user,
create_unsigned_device,
},
olm::{KnownSenderData, PickledInboundGroupSession, PrivateCrossSigningIdentity},
types::{DeviceKey, DeviceKeys, EventEncryptionAlgorithm, Signatures},
Account,
};
#[test]
fn serializing_unknown_device_correctly_preserves_owner_check_failed_if_true() {
let start = SenderData::UnknownDevice { legacy_session: false, owner_check_failed: true };
let json = serde_json::to_string(&start).unwrap();
let end: SenderData = serde_json::from_str(&json).unwrap();
assert_let!(SenderData::UnknownDevice { owner_check_failed, .. } = &end);
assert!(owner_check_failed);
assert_eq!(start, end);
}
#[test]
fn serializing_unknown_device_without_failed_owner_check_excludes_it() {
let start = SenderData::UnknownDevice { legacy_session: false, owner_check_failed: false };
let json = serde_json::to_string(&start).unwrap();
assert!(!json.contains("owner_check_failed"), "JSON contains 'owner_check_failed'!");
let end: SenderData = serde_json::from_str(&json).unwrap();
assert_eq!(start, end);
}
#[test]
fn deserializing_unknown_device_with_extra_retry_info_ignores_it() {
let json = r#"
{
"UnknownDevice":{
"retry_details":{
"retry_count":3,
"next_retry_time_ms":10000
},
"legacy_session":false
}
}
"#;
let end: SenderData = serde_json::from_str(json).expect("Failed to parse!");
assert_let!(SenderData::UnknownDevice { .. } = end);
}
#[test]
fn deserializing_senderknown_without_device_id_defaults_to_none() {
let json = r#"
{
"SenderKnown":{
"user_id":"@u:s.co",
"master_key":[
150,140,249,139,141,29,63,230,179,14,213,175,176,61,11,255,
26,103,10,51,100,154,183,47,181,117,87,204,33,215,241,92
],
"master_key_verified":true
}
}
"#;
let end: SenderData = serde_json::from_str(json).expect("Failed to parse!");
assert_let!(SenderData::SenderVerified { .. } = end);
}
#[test]
fn deserializing_sender_unverified_but_previously_verified_migrates_to_verification_violation()
{
let json = r#"
{
"SenderUnverifiedButPreviouslyVerified":{
"user_id":"@u:s.co",
"master_key":[
150,140,249,139,141,29,63,230,179,14,213,175,176,61,11,255,
26,103,10,51,100,154,183,47,181,117,87,204,33,215,241,92
],
"master_key_verified":true
}
}
"#;
let end: SenderData = serde_json::from_str(json).expect("Failed to parse!");
assert_let!(SenderData::VerificationViolation(KnownSenderData { user_id, .. }) = end);
assert_eq!(user_id, owned_user_id!("@u:s.co"));
}
#[test]
fn deserializing_verification_violation() {
let json = r#"
{
"VerificationViolation":{
"user_id":"@u:s.co",
"master_key":[
150,140,249,139,141,29,63,230,179,14,213,175,176,61,11,255,
26,103,10,51,100,154,183,47,181,117,87,204,33,215,241,92
],
"master_key_verified":true
}
}
"#;
let end: SenderData = serde_json::from_str(json).expect("Failed to parse!");
assert_let!(SenderData::VerificationViolation(KnownSenderData { user_id, .. }) = end);
assert_eq!(user_id, owned_user_id!("@u:s.co"));
}
#[test]
fn equal_sessions_have_same_trust_level() {
let unknown = SenderData::unknown();
let device_keys = SenderData::device_info(DeviceKeys::new(
owned_user_id!("@u:s.co"),
owned_device_id!("DEV"),
Vec::new(),
BTreeMap::new(),
Signatures::new(),
));
let master_key =
Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap();
let sender_unverified =
SenderData::sender_unverified(user_id!("@u:s.co"), device_id!("DEV"), master_key);
let sender_verified =
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);
assert_eq!(sender_unverified.compare_trust_level(&sender_unverified), Ordering::Equal);
assert_eq!(sender_verified.compare_trust_level(&sender_verified), Ordering::Equal);
}
#[test]
fn more_trust_data_makes_you_more_trusted() {
let unknown = SenderData::unknown();
let device_keys = SenderData::device_info(DeviceKeys::new(
owned_user_id!("@u:s.co"),
owned_device_id!("DEV"),
Vec::new(),
BTreeMap::new(),
Signatures::new(),
));
let master_key =
Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap();
let sender_verification_violation = SenderData::sender_verification_violation(
user_id!("@u:s.co"),
device_id!("DEV"),
master_key,
);
let sender_unverified =
SenderData::sender_unverified(user_id!("@u:s.co"), device_id!("DEV"), master_key);
let sender_verified =
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_verification_violation), 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_verification_violation.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_verification_violation.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_verification_violation.compare_trust_level(&sender_verified),
Ordering::Less
);
assert_eq!(
sender_verification_violation.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);
}
#[test]
fn snapshot_sender_data() {
assert_json_snapshot!(SenderData::UnknownDevice {
legacy_session: false,
owner_check_failed: true,
});
assert_json_snapshot!(SenderData::UnknownDevice {
legacy_session: true,
owner_check_failed: false,
});
assert_json_snapshot!(SenderData::DeviceInfo {
device_keys: DeviceKeys::new(
owned_user_id!("@foo:bar.baz"),
owned_device_id!("DEV"),
vec![
EventEncryptionAlgorithm::MegolmV1AesSha2,
EventEncryptionAlgorithm::OlmV1Curve25519AesSha2
],
BTreeMap::from_iter(vec![(
DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, device_id!("ABCDEFGH")),
DeviceKey::Curve25519(Curve25519PublicKey::from_bytes([0u8; 32])),
)]),
Default::default(),
),
legacy_session: false,
});
assert_json_snapshot!(SenderData::VerificationViolation(KnownSenderData {
user_id: owned_user_id!("@foo:bar.baz"),
device_id: Some(owned_device_id!("DEV")),
master_key: Box::new(Ed25519PublicKey::from_slice(&[0u8; 32]).unwrap()),
}));
assert_json_snapshot!(SenderData::SenderUnverified(KnownSenderData {
user_id: owned_user_id!("@foo:bar.baz"),
device_id: None,
master_key: Box::new(Ed25519PublicKey::from_slice(&[1u8; 32]).unwrap()),
}));
assert_json_snapshot!(SenderData::SenderVerified(KnownSenderData {
user_id: owned_user_id!("@foo:bar.baz"),
device_id: None,
master_key: Box::new(Ed25519PublicKey::from_slice(&[1u8; 32]).unwrap()),
}));
}
#[test]
fn test_sender_known_data_migration() {
let old_format = json!(
{
"SenderVerified": {
"user_id": "@foo:bar.baz",
"device_id": null,
"master_key": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
}
});
let migrated: SenderData = serde_json::from_value(old_format).unwrap();
assert_let!(SenderData::SenderVerified(KnownSenderData { master_key, .. }) = migrated);
assert_eq!(
master_key.to_base64(),
Ed25519PublicKey::from_slice(&[0u8; 32]).unwrap().to_base64()
);
}
#[test]
fn test_sender_known_data_migration_with_efficient_bytes_array() {
const SERIALIZED_B64: &str = "\
iaZwaWNrbGWEr2luaXRpYWxfcmF0Y2hldIKlaW5uZXLcAIABYMzfSnBRzMlPKF1uKjYbzLtkzNJ4RcylzN0HzP\
9DzON1Tm05zO7M2MzFQsy9Acz9zPnMqDvM4syQzNrMzxF5KzbM4sy9zPUbBWfM7m4/zJzM18zDzMESKgfMkE7M\
yszIHszqWjYyQURbzKTMkx7M58zANsy+AGPM2A8tbcyFYczge8ykzMFdbVxJMMyAzN8azJEXGsy8zPJazMMaP8\
ziDszmWwfM+My2ajLMr8y+eczTRm9TFadjb3VudGVyAKtzaWduaW5nX2tlecQgefpCr6Duu7QUWzKIeMOFmxv/\
NjfcsYwZz8IN2ZOhdaS0c2lnbmluZ19rZXlfdmVyaWZpZWTDpmNvbmZpZ4GndmVyc2lvbqJWMapzZW5kZXJfa2\
V52StoMkIySDg2ajFpYmk2SW13ak9UUkhzbTVMamtyT2kyUGtiSXVUb0w0TWtFq3NpZ25pbmdfa2V5gadlZDI1\
NTE52StUWHJqNS9UYXpia3Yram1CZDl4UlB4NWNVaFFzNUNnblc1Q1pNRjgvNjZzq3NlbmRlcl9kYXRhgbBTZW\
5kZXJVbnZlcmlmaWVkg6d1c2VyX2lks0B2YWxvdTM1Om1hdHJpeC5vcmepZGV2aWNlX2lkqkZJQlNaRlJLUE2q\
bWFzdGVyX2tlecQgkOp9s4ClyQujYD7rRZA8xgE6kvYlqKSNnMrQNmSrcuGncm9vbV9pZL4hRWt5VEtGdkViYl\
B6SmxhaUhFOm1hdHJpeC5vcmeoaW1wb3J0ZWTCqWJhY2tlZF91cMKyaGlzdG9yeV92aXNpYmlsaXR5wKlhbGdv\
cml0aG20bS5tZWdvbG0udjEuYWVzLXNoYTI";
let input = base64_decode(SERIALIZED_B64).unwrap();
let sender_data: PickledInboundGroupSession = rmp_serde::from_slice(&input)
.expect("Should be able to deserialize serialized inbound group session");
assert_let!(
SenderData::SenderUnverified(KnownSenderData { master_key, .. }) =
sender_data.sender_data
);
assert_eq!(master_key.to_base64(), "kOp9s4ClyQujYD7rRZA8xgE6kvYlqKSNnMrQNmSrcuE");
}
#[async_test]
async fn test_from_device_for_unsigned_device() {
let bob_account =
Account::with_device_id(user_id!("@bob:example.com"), device_id!("BOB_DEVICE"));
let bob_device = create_unsigned_device(bob_account.device_keys());
let sender_data = SenderData::from_device(&bob_device);
assert_eq!(
sender_data,
SenderData::DeviceInfo {
device_keys: bob_device.device_keys.deref().clone(),
legacy_session: false
}
);
}
#[async_test]
async fn test_from_device_for_unverified_user() {
let bob_identity =
PrivateCrossSigningIdentity::new(user_id!("@bob:example.com").to_owned());
let bob_account =
Account::with_device_id(user_id!("@bob:example.com"), device_id!("BOB_DEVICE"));
let bob_device = create_signed_device_of_unverified_user(
bob_account.device_keys().clone(),
&bob_identity,
)
.await;
let sender_data = SenderData::from_device(&bob_device);
assert_eq!(
sender_data,
SenderData::SenderUnverified(KnownSenderData {
user_id: bob_account.user_id().to_owned(),
device_id: Some(bob_account.device_id().to_owned()),
master_key: Box::new(
bob_identity.master_public_key().await.unwrap().get_first_key().unwrap()
),
})
);
}
#[async_test]
async fn test_from_device_for_verified_user() {
let alice_account =
Account::with_device_id(user_id!("@alice:example.com"), device_id!("ALICE_DEVICE"));
let alice_identity = PrivateCrossSigningIdentity::for_account(&alice_account);
let bob_identity =
PrivateCrossSigningIdentity::new(user_id!("@bob:example.com").to_owned());
let bob_account =
Account::with_device_id(user_id!("@bob:example.com"), device_id!("BOB_DEVICE"));
let bob_device = create_signed_device_of_verified_user(
bob_account.device_keys().clone(),
&bob_identity,
&alice_identity,
)
.await;
let sender_data = SenderData::from_device(&bob_device);
assert_eq!(
sender_data,
SenderData::SenderVerified(KnownSenderData {
user_id: bob_account.user_id().to_owned(),
device_id: Some(bob_account.device_id().to_owned()),
master_key: Box::new(
bob_identity.master_public_key().await.unwrap().get_first_key().unwrap()
),
})
);
}
#[async_test]
async fn test_from_device_for_verification_violation_user() {
let bob_identity =
PrivateCrossSigningIdentity::new(user_id!("@bob:example.com").to_owned());
let bob_account =
Account::with_device_id(user_id!("@bob:example.com"), device_id!("BOB_DEVICE"));
let bob_device =
create_signed_device_of_unverified_user(bob_account.device_keys(), &bob_identity).await;
bob_device
.device_owner_identity
.as_ref()
.unwrap()
.other()
.unwrap()
.mark_as_previously_verified();
let sender_data = SenderData::from_device(&bob_device);
assert_eq!(
sender_data,
SenderData::VerificationViolation(KnownSenderData {
user_id: bob_account.user_id().to_owned(),
device_id: Some(bob_account.device_id().to_owned()),
master_key: Box::new(
bob_identity.master_public_key().await.unwrap().get_first_key().unwrap()
),
})
);
}
}