use std::{cmp::Ordering, fmt};
use ruma::{DeviceId, OwnedDeviceId, OwnedUserId, UserId};
use serde::{de, de::Visitor, Deserialize, Deserializer, Serialize};
use vodozemac::Ed25519PublicKey;
use crate::types::{serialize_ed25519_key, DeviceKeys};
#[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 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(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,
}
}
}
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};
use assert_matches2::assert_let;
use insta::assert_json_snapshot;
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::{
olm::{KnownSenderData, PickledInboundGroupSession},
types::{DeviceKey, DeviceKeys, EventEncryptionAlgorithm, Signatures},
};
#[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");
}
}