From 4ad4ad1e947f2081120a10c9504eeae02f373ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 17 Dec 2020 15:50:13 +0100 Subject: [PATCH] crypto: Send out done events for in-room verifications. --- matrix_sdk_crypto/src/verification/machine.rs | 27 ++- .../src/verification/sas/event_enums.rs | 27 +++ .../src/verification/sas/inner_sas.rs | 54 +++++- .../src/verification/sas/sas_state.rs | 159 +++++++++++++++++- 4 files changed, 260 insertions(+), 7 deletions(-) diff --git a/matrix_sdk_crypto/src/verification/machine.rs b/matrix_sdk_crypto/src/verification/machine.rs index fc168782..b0ebffe2 100644 --- a/matrix_sdk_crypto/src/verification/machine.rs +++ b/matrix_sdk_crypto/src/verification/machine.rs @@ -222,7 +222,7 @@ impl VerificationMachine { event: &AnySyncRoomEvent, ) -> Result<(), CryptoStoreError> { if let AnySyncRoomEvent::Message(m) = event { - // Since this are room events we will get events that we send out on + // Since these are room events we will get events that we send out on // our own as well. if m.sender() == self.account.user_id() { return Ok(()); @@ -308,10 +308,25 @@ impl VerificationMachine { &s, &m.clone().into_full_event(room_id.clone()), ); + } + } + + AnySyncMessageEvent::KeyVerificationDone(e) => { + if let Some(s) = self.room_verifications.get(&e.content.relation.event_id) { + let content = + s.receive_room_event(&m.clone().into_full_event(room_id.clone())); if s.is_done() { match s.mark_as_done().await? { - VerificationResult::Ok => (), + VerificationResult::Ok => { + if let Some(c) = content { + self.queue_up_content( + s.other_user_id(), + s.other_device_id(), + c, + ); + } + } VerificationResult::Cancel(r) => { self.outgoing_to_device_messages .insert(r.request_id(), r.into()); @@ -321,6 +336,14 @@ impl VerificationMachine { self.outgoing_to_device_messages .insert(request.request_id, request); + + if let Some(c) = content { + self.queue_up_content( + s.other_user_id(), + s.other_device_id(), + c, + ); + } } } } diff --git a/matrix_sdk_crypto/src/verification/sas/event_enums.rs b/matrix_sdk_crypto/src/verification/sas/event_enums.rs index 35bf3034..7b6b2629 100644 --- a/matrix_sdk_crypto/src/verification/sas/event_enums.rs +++ b/matrix_sdk_crypto/src/verification/sas/event_enums.rs @@ -21,6 +21,7 @@ use matrix_sdk_common::{ key::verification::{ accept::{AcceptEventContent, AcceptToDeviceEventContent}, cancel::{CancelEventContent, CancelToDeviceEventContent}, + done::DoneEventContent, key::{KeyEventContent, KeyToDeviceEventContent}, mac::{MacEventContent, MacToDeviceEventContent}, start::{StartEventContent, StartMethod, StartToDeviceEventContent}, @@ -187,6 +188,24 @@ impl From for CancelContent { } } +pub enum DoneContent { + Room(RoomId, DoneEventContent), +} + +impl DoneContent { + pub fn flow_id(&self) -> FlowId { + match self { + DoneContent::Room(r, c) => FlowId::InRoom(r.clone(), c.relation.event_id.clone()), + } + } +} + +impl From<(RoomId, DoneEventContent)> for DoneContent { + fn from(content: (RoomId, DoneEventContent)) -> Self { + DoneContent::Room(content.0, content.1) + } +} + #[derive(Clone, Debug)] pub enum OutgoingContent { Room(RoomId, AnyMessageEventContent), @@ -222,6 +241,14 @@ impl From for OutgoingContent { } } +impl From for OutgoingContent { + fn from(content: DoneContent) -> Self { + match content { + DoneContent::Room(r, c) => (r, AnyMessageEventContent::KeyVerificationDone(c)).into(), + } + } +} + impl From for OutgoingContent { fn from(content: AnyToDeviceEventContent) -> Self { OutgoingContent::ToDevice(content) diff --git a/matrix_sdk_crypto/src/verification/sas/inner_sas.rs b/matrix_sdk_crypto/src/verification/sas/inner_sas.rs index ffebb8bc..f10388b0 100644 --- a/matrix_sdk_crypto/src/verification/sas/inner_sas.rs +++ b/matrix_sdk_crypto/src/verification/sas/inner_sas.rs @@ -30,6 +30,7 @@ use matrix_sdk_common::{ }, identifiers::{EventId, RoomId, UserId}, }; +use tracing::trace; use crate::{ identities::{ReadOnlyDevice, UserIdentities}, @@ -40,7 +41,7 @@ use super::{ event_enums::{AcceptContent, CancelContent, MacContent, OutgoingContent}, sas_state::{ Accepted, Canceled, Confirmed, Created, Done, FlowId, KeyReceived, MacReceived, SasState, - Started, + Started, WaitingForDone, }, StartContent, }; @@ -53,6 +54,8 @@ pub enum InnerSas { KeyRecieved(SasState), Confirmed(SasState), MacReceived(SasState), + WaitingForDone(SasState), + WaitingForDoneUnconfirmed(SasState), Done(SasState), Canceled(SasState), } @@ -125,6 +128,8 @@ impl InnerSas { InnerSas::Confirmed(s) => s.set_creation_time(time), InnerSas::MacReceived(s) => s.set_creation_time(time), InnerSas::Done(s) => s.set_creation_time(time), + InnerSas::WaitingForDone(s) => s.set_creation_time(time), + InnerSas::WaitingForDoneUnconfirmed(s) => s.set_creation_time(time), } } @@ -151,9 +156,17 @@ impl InnerSas { (InnerSas::Confirmed(sas), Some(content)) } InnerSas::MacReceived(s) => { - let sas = s.confirm(); - let content = sas.as_content(); - (InnerSas::Done(sas), Some(content)) + if s.is_dm_verification() { + let sas = s.confirm_and_wait_for_done(); + let content = sas.as_content(); + + (InnerSas::WaitingForDoneUnconfirmed(sas), Some(content)) + } else { + let sas = s.confirm(); + let content = sas.as_content(); + + (InnerSas::Done(sas), Some(content)) + } } _ => (self, None), } @@ -201,6 +214,22 @@ impl InnerSas { } } InnerSas::Confirmed(s) => { + match s.into_waiting_for_done(&e.sender, (e.room_id.clone(), e.content.clone())) + { + Ok(s) => { + let content = s.done_content(); + (InnerSas::WaitingForDone(s), Some(content.into())) + } + Err(s) => { + let content = s.as_content(); + (InnerSas::Canceled(s), Some(content.into())) + } + } + } + _ => (self, None), + }, + AnyMessageEvent::KeyVerificationDone(e) => match self { + InnerSas::WaitingForDone(s) => { match s.into_done(&e.sender, (e.room_id.clone(), e.content.clone())) { Ok(s) => (InnerSas::Done(s), None), Err(s) => { @@ -209,6 +238,19 @@ impl InnerSas { } } } + InnerSas::WaitingForDoneUnconfirmed(s) => { + match s.into_done(&e.sender, (e.room_id.clone(), e.content.clone())) { + Ok(s) => { + let content = s.done_content(); + (InnerSas::Done(s), Some(content.into())) + } + Err(s) => { + let content = s.as_content(); + (InnerSas::Canceled(s), Some(content.into())) + } + } + } + _ => (self, None), }, _ => (self, None), @@ -300,6 +342,8 @@ impl InnerSas { InnerSas::KeyRecieved(s) => s.timed_out(), InnerSas::Confirmed(s) => s.timed_out(), InnerSas::MacReceived(s) => s.timed_out(), + InnerSas::WaitingForDone(s) => s.timed_out(), + InnerSas::WaitingForDoneUnconfirmed(s) => s.timed_out(), InnerSas::Done(s) => s.timed_out(), } } @@ -313,6 +357,8 @@ impl InnerSas { InnerSas::KeyRecieved(s) => s.verification_flow_id.clone(), InnerSas::Confirmed(s) => s.verification_flow_id.clone(), InnerSas::MacReceived(s) => s.verification_flow_id.clone(), + InnerSas::WaitingForDone(s) => s.verification_flow_id.clone(), + InnerSas::WaitingForDoneUnconfirmed(s) => s.verification_flow_id.clone(), InnerSas::Done(s) => s.verification_flow_id.clone(), } } diff --git a/matrix_sdk_crypto/src/verification/sas/sas_state.rs b/matrix_sdk_crypto/src/verification/sas/sas_state.rs index d7533c75..ed4876cb 100644 --- a/matrix_sdk_crypto/src/verification/sas/sas_state.rs +++ b/matrix_sdk_crypto/src/verification/sas/sas_state.rs @@ -29,6 +29,7 @@ use matrix_sdk_common::{ MSasV1Content as AcceptV1Content, MSasV1ContentInit as AcceptV1ContentInit, }, cancel::{CancelCode, CancelEventContent, CancelToDeviceEventContent}, + done::DoneEventContent, key::{KeyEventContent, KeyToDeviceEventContent}, mac::MacToDeviceEventContent, start::{ @@ -47,7 +48,8 @@ use tracing::error; use super::{ event_enums::{ - AcceptContent, CancelContent, KeyContent, MacContent, OutgoingContent, StartContent, + AcceptContent, CancelContent, DoneContent, KeyContent, MacContent, OutgoingContent, + StartContent, }, helpers::{ calculate_commitment, get_decimal, get_emoji, get_mac_content, receive_mac_event, SasIds, @@ -251,6 +253,15 @@ pub struct MacReceived { verified_master_keys: Arc<[UserIdentities]>, } +/// The SAS state we're going to be in after we receive a MAC event in a DM. DMs +/// require a final message `m.key.verification.done` message to conclude the +/// verificaton. This state waits for such a message. +#[derive(Clone, Debug)] +pub struct WaitingForDone { + verified_devices: Arc<[ReadOnlyDevice]>, + verified_master_keys: Arc<[UserIdentities]>, +} + /// The SAS state indicating that the verification finished successfully. /// /// We can now mark the device in our verified devices lits as verified and sign @@ -300,6 +311,11 @@ impl SasState { self.creation_time.elapsed() > MAX_AGE || self.last_event_time.elapsed() > MAX_EVENT_TIMEOUT } + /// Is this verification happening inside a DM. + pub fn is_dm_verification(&self) -> bool { + matches!(&*self.verification_flow_id, FlowId::InRoom(_, _)) + } + #[cfg(test)] #[allow(dead_code)] pub fn set_creation_time(&mut self, time: Instant) { @@ -880,6 +896,48 @@ impl SasState { }) } + /// Receive a m.key.verification.mac event, changing the state into + /// a `WaitingForDone` one. This method should be used instead of + /// `into_done()` if the verification started with a + /// `m.key.verification.request`. + /// + /// # Arguments + /// + /// * `event` - The m.key.verification.mac event that was sent to us by + /// the other side. + pub fn into_waiting_for_done( + self, + sender: &UserId, + content: impl Into, + ) -> Result, SasState> { + let content = content.into(); + + self.check_event(&sender, &content.flow_id().as_str()) + .map_err(|c| self.clone().cancel(c))?; + + let (devices, master_keys) = receive_mac_event( + &self.inner.lock().unwrap(), + &self.ids, + &self.verification_flow_id.as_str(), + sender, + &content, + ) + .map_err(|c| self.clone().cancel(c))?; + + Ok(SasState { + inner: self.inner, + creation_time: self.creation_time, + last_event_time: self.last_event_time, + verification_flow_id: self.verification_flow_id, + ids: self.ids, + + state: Arc::new(WaitingForDone { + verified_devices: devices.into(), + verified_master_keys: master_keys.into(), + }), + }) + } + /// Get the content for the mac event. /// /// The content needs to be automatically sent to the other side. @@ -911,6 +969,26 @@ impl SasState { } } + /// Confirm that the short auth string matches but wait for the other side + /// to confirm that it's done. + /// + /// This needs to be done by the user, this will put us in the `WaitForDone` + /// state where we wait for the other side to confirm that the MAC event was + /// successfully received. + pub fn confirm_and_wait_for_done(self) -> SasState { + SasState { + inner: self.inner, + verification_flow_id: self.verification_flow_id, + creation_time: self.creation_time, + last_event_time: self.last_event_time, + ids: self.ids, + state: Arc::new(WaitingForDone { + verified_devices: self.state.verified_devices.clone(), + verified_master_keys: self.state.verified_master_keys.clone(), + }), + } + } + /// Get the emoji version of the short authentication string. /// /// Returns a vector of tuples where the first element is the emoji and the @@ -940,6 +1018,68 @@ impl SasState { } } +impl SasState { + /// Get the content for the mac event. + /// + /// The content needs to be automatically sent to the other side if it + /// wasn't already sent. + pub fn as_content(&self) -> MacContent { + get_mac_content( + &self.inner.lock().unwrap(), + &self.ids, + &self.verification_flow_id, + ) + } + + pub fn done_content(&self) -> DoneContent { + match self.verification_flow_id.as_ref() { + FlowId::ToDevice(_) => { + unreachable!("The done content isn't supported yet for to-device verifications") + } + FlowId::InRoom(r, e) => ( + r.clone(), + DoneEventContent { + relation: Relation { + event_id: e.clone(), + }, + }, + ) + .into(), + } + } + + /// Receive a m.key.verification.mac event, changing the state into + /// a `Done` one + /// + /// # Arguments + /// + /// * `event` - The m.key.verification.mac event that was sent to us by + /// the other side. + pub fn into_done( + self, + sender: &UserId, + content: impl Into, + ) -> Result, SasState> { + let content = content.into(); + + self.check_event(&sender, &content.flow_id().as_str()) + .map_err(|c| self.clone().cancel(c))?; + + Ok(SasState { + inner: self.inner, + creation_time: self.creation_time, + last_event_time: self.last_event_time, + verification_flow_id: self.verification_flow_id, + ids: self.ids, + + state: Arc::new(Done { + verified_devices: self.state.verified_devices.clone(), + verified_master_keys: self.state.verified_master_keys.clone(), + }), + }) + } +} + impl SasState { /// Get the content for the mac event. /// @@ -953,6 +1093,23 @@ impl SasState { ) } + pub fn done_content(&self) -> DoneContent { + match self.verification_flow_id.as_ref() { + FlowId::ToDevice(_) => { + unreachable!("The done content isn't supported yet for to-device verifications") + } + FlowId::InRoom(r, e) => ( + r.clone(), + DoneEventContent { + relation: Relation { + event_id: e.clone(), + }, + }, + ) + .into(), + } + } + /// Get the list of verified devices. pub fn verified_devices(&self) -> Arc<[ReadOnlyDevice]> { self.state.verified_devices.clone()