From 57b65ec8c4514266798bc6dadb514aa5c88d0847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 28 Jul 2020 14:45:53 +0200 Subject: [PATCH] crypto: Add a verification machine. --- matrix_sdk_crypto/src/store/mod.rs | 1 - matrix_sdk_crypto/src/verification/machine.rs | 277 ++++++++++++++++++ matrix_sdk_crypto/src/verification/mod.rs | 42 +++ matrix_sdk_crypto/src/verification/sas.rs | 64 ++-- 4 files changed, 361 insertions(+), 23 deletions(-) create mode 100644 matrix_sdk_crypto/src/verification/machine.rs diff --git a/matrix_sdk_crypto/src/store/mod.rs b/matrix_sdk_crypto/src/store/mod.rs index 2e40503e..95a1f040 100644 --- a/matrix_sdk_crypto/src/store/mod.rs +++ b/matrix_sdk_crypto/src/store/mod.rs @@ -180,7 +180,6 @@ pub trait CryptoStore: Debug { /// * `user_id` - The user that the device belongs to. /// /// * `device_id` - The unique id of the device. - #[allow(clippy::ptr_arg)] async fn get_device(&self, user_id: &UserId, device_id: &DeviceId) -> Result>; /// Get all the devices of the given user. diff --git a/matrix_sdk_crypto/src/verification/machine.rs b/matrix_sdk_crypto/src/verification/machine.rs new file mode 100644 index 00000000..532c8eb0 --- /dev/null +++ b/matrix_sdk_crypto/src/verification/machine.rs @@ -0,0 +1,277 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::BTreeMap; +use std::sync::Arc; + +use dashmap::DashMap; + +use matrix_sdk_common::{ + api::r0::to_device::{send_event_to_device::Request as ToDeviceRequest, DeviceIdOrAllDevices}, + events::{ + key::verification::start::StartEventContent, AnyToDeviceEvent, AnyToDeviceEventContent, + EventType, + }, + identifiers::{DeviceId, UserId}, + uuid::Uuid, +}; + +use super::Sas; +use crate::{Account, CryptoStore, CryptoStoreError}; + +#[derive(Clone, Debug)] +struct VerificationMachine { + account: Account, + store: Arc>, + verifications: Arc>, + outgoing_to_device_messages: Arc>, +} + +impl VerificationMachine { + pub(crate) fn new(account: Account, store: Box) -> Self { + Self { + account, + store: Arc::new(store), + verifications: Arc::new(DashMap::new()), + outgoing_to_device_messages: Arc::new(DashMap::new()), + } + } + + fn get_sas(&self, transaction_id: &str) -> Option { + self.verifications.get(transaction_id).map(|s| s.clone()) + } + + fn content_to_request( + &self, + recipient: &UserId, + recipient_device: &DeviceId, + content: AnyToDeviceEventContent, + ) -> ToDeviceRequest { + let mut messages = BTreeMap::new(); + let mut user_messages = BTreeMap::new(); + + user_messages.insert( + DeviceIdOrAllDevices::DeviceId(recipient_device.into()), + serde_json::value::to_raw_value(&content).expect("Can't serialize to-device content"), + ); + messages.insert(recipient.clone(), user_messages); + + ToDeviceRequest { + txn_id: Uuid::new_v4().to_string(), + event_type: EventType::KeyVerificationAccept, + messages, + } + } + + fn queue_up_content( + &self, + recipient: &UserId, + recipient_device: &DeviceId, + content: AnyToDeviceEventContent, + ) { + let request = self.content_to_request(recipient, recipient_device, content); + + self.outgoing_to_device_messages + .insert(request.txn_id.clone(), request); + } + + fn recieve_event_helper(&self, sas: &Sas, event: &mut AnyToDeviceEvent) { + if let Some(c) = sas.receive_event(event) { + self.queue_up_content(sas.other_user_id(), sas.other_device_id(), c); + } + } + + pub fn mark_requests_as_sent(&self, uuid: &str) { + self.outgoing_to_device_messages.remove(uuid); + } + + async fn receive_event(&self, event: &mut AnyToDeviceEvent) -> Result<(), CryptoStoreError> { + match event { + AnyToDeviceEvent::KeyVerificationStart(e) => match &e.content { + StartEventContent::MSasV1(content) => { + if let Some(d) = self + .store + .get_device(&e.sender, &content.from_device) + .await? + { + match Sas::from_start_event(self.account.clone(), d, e) { + Ok(s) => { + self.verifications.insert(content.transaction_id.clone(), s); + } + Err(c) => self.queue_up_content(&e.sender, &content.from_device, c), + } + }; + } + _ => (), + }, + AnyToDeviceEvent::KeyVerificationCancel(e) => { + self.verifications.remove(&e.content.transaction_id); + } + AnyToDeviceEvent::KeyVerificationAccept(e) => { + if let Some(s) = self.get_sas(&e.content.transaction_id) { + self.recieve_event_helper(&s, event) + }; + } + AnyToDeviceEvent::KeyVerificationKey(e) => { + if let Some(s) = self.get_sas(&e.content.transaction_id) { + self.recieve_event_helper(&s, event) + }; + } + AnyToDeviceEvent::KeyVerificationMac(e) => { + if let Some(s) = self.get_sas(&e.content.transaction_id) { + self.recieve_event_helper(&s, event) + }; + } + _ => (), + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + + use std::convert::TryFrom; + + use matrix_sdk_common::{ + events::AnyToDeviceEventContent, + identifiers::{DeviceId, UserId}, + }; + + use super::{Sas, VerificationMachine}; + use crate::verification::test::wrap_any_to_device_content; + use crate::{store::memorystore::MemoryStore, Account, CryptoStore, Device}; + + fn alice_id() -> UserId { + UserId::try_from("@alice:example.org").unwrap() + } + + fn alice_device_id() -> Box { + "JLAFKJWSCS".into() + } + + fn bob_id() -> UserId { + UserId::try_from("@bob:example.org").unwrap() + } + + fn bob_device_id() -> Box { + "BOBDEVCIE".into() + } + + async fn setup_verification_machine() -> (VerificationMachine, Sas) { + let alice = Account::new(&alice_id(), &alice_device_id()); + let bob = Account::new(&bob_id(), &bob_device_id()); + let store = MemoryStore::new(); + + let bob_device = Device::from_account(&bob).await; + let alice_device = Device::from_account(&alice).await; + + store.save_devices(&[bob_device]).await.unwrap(); + + let machine = VerificationMachine::new(alice, Box::new(store)); + let (bob_sas, start_content) = Sas::start(bob, alice_device); + machine + .receive_event(&mut wrap_any_to_device_content( + bob_sas.user_id(), + AnyToDeviceEventContent::KeyVerificationStart(start_content), + )) + .await + .unwrap(); + + (machine, bob_sas) + } + + #[test] + fn create() { + let alice = Account::new(&alice_id(), &alice_device_id()); + let store = MemoryStore::new(); + let _ = VerificationMachine::new(alice, Box::new(store)); + } + + #[tokio::test] + async fn full_flow() { + let (alice_machine, bob) = setup_verification_machine().await; + + let alice = alice_machine.verifications.get(bob.flow_id()).unwrap(); + + let mut event = alice + .accept() + .map(|c| { + wrap_any_to_device_content( + alice.user_id(), + AnyToDeviceEventContent::KeyVerificationAccept(c), + ) + }) + .unwrap(); + + let mut event = bob + .receive_event(&mut event) + .map(|c| wrap_any_to_device_content(bob.user_id(), c)) + .unwrap(); + + assert!(alice_machine.outgoing_to_device_messages.is_empty()); + alice_machine.receive_event(&mut event).await.unwrap(); + assert!(!alice_machine.outgoing_to_device_messages.is_empty()); + + let request = alice_machine + .outgoing_to_device_messages + .iter() + .next() + .unwrap(); + + let txn_id = request.txn_id.clone(); + + let mut event = wrap_any_to_device_content( + alice.user_id(), + AnyToDeviceEventContent::KeyVerificationKey( + serde_json::from_str( + request + .messages + .values() + .next() + .unwrap() + .values() + .next() + .unwrap() + .get(), + ) + .unwrap(), + ), + ); + drop(request); + alice_machine.mark_requests_as_sent(&txn_id); + + assert!(bob.receive_event(&mut event).is_none()); + + assert!(alice.emoji().is_some()); + assert!(bob.emoji().is_some()); + + assert_eq!(alice.emoji(), bob.emoji()); + + let mut event = wrap_any_to_device_content( + alice.user_id(), + AnyToDeviceEventContent::KeyVerificationMac(alice.confirm().unwrap()), + ); + bob.receive_event(&mut event); + + let mut event = wrap_any_to_device_content( + bob.user_id(), + AnyToDeviceEventContent::KeyVerificationMac(bob.confirm().unwrap()), + ); + alice.receive_event(&mut event); + + assert!(alice.is_done()); + assert!(bob.is_done()); + } +} diff --git a/matrix_sdk_crypto/src/verification/mod.rs b/matrix_sdk_crypto/src/verification/mod.rs index f353fdc4..7675c756 100644 --- a/matrix_sdk_crypto/src/verification/mod.rs +++ b/matrix_sdk_crypto/src/verification/mod.rs @@ -23,6 +23,8 @@ use matrix_sdk_common::identifiers::DeviceId; use crate::{Account, Device}; +#[allow(dead_code)] +mod machine; #[allow(dead_code)] mod sas; @@ -396,3 +398,43 @@ fn get_decimal(sas: &OlmSas, ids: &SasIds, flow_id: &str, we_started: bool) -> ( (first + 1000, second + 1000, third + 1000) } + +#[cfg(test)] +mod test { + use matrix_sdk_common::events::{AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent}; + use matrix_sdk_common::identifiers::UserId; + + pub(crate) fn wrap_any_to_device_content( + sender: &UserId, + content: AnyToDeviceEventContent, + ) -> AnyToDeviceEvent { + match content { + AnyToDeviceEventContent::KeyVerificationKey(c) => { + AnyToDeviceEvent::KeyVerificationKey(ToDeviceEvent { + sender: sender.clone(), + content: c, + }) + } + AnyToDeviceEventContent::KeyVerificationStart(c) => { + AnyToDeviceEvent::KeyVerificationStart(ToDeviceEvent { + sender: sender.clone(), + content: c, + }) + } + AnyToDeviceEventContent::KeyVerificationAccept(c) => { + AnyToDeviceEvent::KeyVerificationAccept(ToDeviceEvent { + sender: sender.clone(), + content: c, + }) + } + AnyToDeviceEventContent::KeyVerificationMac(c) => { + AnyToDeviceEvent::KeyVerificationMac(ToDeviceEvent { + sender: sender.clone(), + content: c, + }) + } + + _ => unreachable!(), + } + } +} diff --git a/matrix_sdk_crypto/src/verification/sas.rs b/matrix_sdk_crypto/src/verification/sas.rs index 5e1664a8..0ed3fb1a 100644 --- a/matrix_sdk_crypto/src/verification/sas.rs +++ b/matrix_sdk_crypto/src/verification/sas.rs @@ -45,6 +45,7 @@ pub struct Sas { inner: Arc>, account: Account, other_device: Device, + flow_id: Arc, } impl Sas { @@ -67,6 +68,21 @@ impl Sas { self.account.device_id() } + /// Get the user id of the other side. + pub fn other_user_id(&self) -> &UserId { + self.other_device.user_id() + } + + /// Get the device id of the other side. + pub fn other_device_id(&self) -> &DeviceId { + self.other_device.device_id() + } + + /// Get the unique ID that identifies this SAS verification flow. + pub fn flow_id(&self) -> &str { + &self.flow_id + } + /// Start a new SAS auth flow with the given device. /// /// # Arguments @@ -77,13 +93,15 @@ impl Sas { /// /// Returns the new `Sas` object and a `StartEventContent` that needs to be /// sent out through the server to the other device. - fn start(account: Account, other_device: Device) -> (Sas, StartEventContent) { + pub(crate) fn start(account: Account, other_device: Device) -> (Sas, StartEventContent) { let (inner, content) = InnerSas::start(account.clone(), other_device.clone()); + let flow_id = inner.verification_flow_id(); let sas = Sas { inner: Arc::new(Mutex::new(inner)), account, other_device, + flow_id, }; (sas, content) @@ -99,16 +117,18 @@ impl Sas { /// /// * `event` - The m.key.verification.start event that was sent to us by /// the other side. - fn from_start_event( + pub(crate) fn from_start_event( account: Account, other_device: Device, event: &ToDeviceEvent, ) -> Result { let inner = InnerSas::from_start_event(account.clone(), other_device.clone(), event)?; + let flow_id = inner.verification_flow_id(); Ok(Sas { inner: Arc::new(Mutex::new(inner)), account, other_device, + flow_id, }) } @@ -162,7 +182,10 @@ impl Sas { self.inner.lock().unwrap().decimals() } - fn receive_event(&self, event: &mut AnyToDeviceEvent) -> Option { + pub(crate) fn receive_event( + &self, + event: &mut AnyToDeviceEvent, + ) -> Option { let mut guard = self.inner.lock().unwrap(); let sas: InnerSas = (*guard).clone(); let (sas, content) = sas.receive_event(event); @@ -171,7 +194,7 @@ impl Sas { content } - fn verified_devices(&self) -> Option>>> { + pub(crate) fn verified_devices(&self) -> Option>>> { self.inner.lock().unwrap().verified_devices() } } @@ -314,6 +337,19 @@ impl InnerSas { } } + fn verification_flow_id(&self) -> Arc { + match self { + InnerSas::Created(s) => s.verification_flow_id.clone(), + InnerSas::Started(s) => s.verification_flow_id.clone(), + InnerSas::Canceled(s) => s.verification_flow_id.clone(), + InnerSas::Accepted(s) => s.verification_flow_id.clone(), + 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::Done(s) => s.verification_flow_id.clone(), + } + } + fn emoji(&self) -> Option> { match self { InnerSas::KeyRecieved(s) => Some(s.get_emoji()), @@ -1017,10 +1053,9 @@ impl SasState { mod test { use std::convert::TryFrom; + use crate::verification::test::wrap_any_to_device_content; use crate::{Account, Device}; - use matrix_sdk_common::events::{ - AnyToDeviceEvent, AnyToDeviceEventContent, EventContent, ToDeviceEvent, - }; + use matrix_sdk_common::events::{AnyToDeviceEvent, EventContent, ToDeviceEvent}; use matrix_sdk_common::identifiers::{DeviceId, UserId}; use super::{Accepted, Created, Sas, SasState, Started}; @@ -1048,21 +1083,6 @@ mod test { } } - fn wrap_any_to_device_content( - sender: &UserId, - content: AnyToDeviceEventContent, - ) -> AnyToDeviceEvent { - match content { - AnyToDeviceEventContent::KeyVerificationKey(c) => { - AnyToDeviceEvent::KeyVerificationKey(ToDeviceEvent { - sender: sender.clone(), - content: c, - }) - } - _ => unreachable!(), - } - } - async fn get_sas_pair() -> (SasState, SasState) { let alice = Account::new(&alice_id(), &alice_device_id()); let alice_device = Device::from_account(&alice).await;